""" Kill switch. state/orders_disabled 파일 존재 시 모든 진입점에서 거부. LLM 에이전트(클로·레이) 세션에서 호출 시 즉시 차단 (OPENCLAW_AGENT 환경변수). 모든 매매 진입점 함수 첫 줄에서 guard_or_raise() 호출이 강제이다. """ from __future__ import annotations import json import os import sys from datetime import datetime, timezone, timedelta from pathlib import Path WORKSPACE_ROOT = Path(__file__).resolve().parent.parent.parent LIMITS_FILE = Path(__file__).resolve().parent / 'limits.json' KST = timezone(timedelta(hours=9)) class SidecarBlocked(RuntimeError): pass def _limits() -> dict: return json.loads(LIMITS_FILE.read_text(encoding='utf-8')) def _kill_switch_path() -> Path: return WORKSPACE_ROOT / _limits()['kill_switch_file'] def _agent_env_blocked() -> bool: var = _limits().get('telegram', {}).get('block_when_agent_env_set') return bool(var and os.getenv(var)) def is_disabled() -> bool: return _kill_switch_path().exists() def guard_or_raise() -> None: if _agent_env_blocked(): raise SidecarBlocked('LLM agent session cannot invoke order module (OPENCLAW_AGENT env set)') if is_disabled(): raise SidecarBlocked('Order module is disabled (sidecar ON)') def disable(reason: str = 'manual') -> Path: p = _kill_switch_path() p.parent.mkdir(parents=True, exist_ok=True) p.write_text( json.dumps({'disabled_at': datetime.now(KST).isoformat(), 'reason': reason}, ensure_ascii=False, indent=2), encoding='utf-8', ) try: os.chmod(p, 0o600) except OSError: pass return p def enable() -> bool: p = _kill_switch_path() if not p.exists(): return False p.unlink() return True def status() -> dict: p = _kill_switch_path() if not p.exists(): return {'disabled': False, 'path': str(p), 'agent_env_blocked': _agent_env_blocked()} try: meta = json.loads(p.read_text(encoding='utf-8')) except (OSError, ValueError): meta = {} meta.update({'disabled': True, 'path': str(p), 'agent_env_blocked': _agent_env_blocked()}) return meta def _cli(argv: list[str]) -> int: if not argv or argv[0] == 'status': print(json.dumps(status(), ensure_ascii=False, indent=2)) return 0 cmd = argv[0] if cmd == 'disable': reason = ' '.join(argv[1:]) or 'manual' p = disable(reason) print(f'sidecar disabled: {p}') return 0 if cmd == 'enable': ok = enable() print('sidecar enabled' if ok else 'sidecar already enabled (no file)') return 0 print(f'unknown command: {cmd}', file=sys.stderr) print('usage: sidecar.py {status | disable [reason] | enable}', file=sys.stderr) return 2 if __name__ == '__main__': sys.exit(_cli(sys.argv[1:]))