# base.py — BaseAgentAdapter abstract base class, SpawnSpec, and DiscoveryContext import os, json, sqlite3 from typing import Optional, Dict, Any, List from lib_py.paths import resolve_home from lib_py.verify_session import verify_session_uuid, workspace_key class SpawnSpec: def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None): self.agent_name = agent_name self.session_name = session_name self.command = command self.env = env or {} class DiscoveryContext: def __init__(self, workspace: str, agent_name: str = "", home_dir: Optional[str] = None, claude_dir: Optional[str] = None, epoch: int = 0, row: Optional[Dict[str, Any]] = None, mode: str = "discover"): self.workspace = workspace self.agent_name = agent_name self.home_dir = resolve_home(home_dir) self.claude_dir = claude_dir or os.environ.get("CLAUDE_PROJECT_DIR", f"{self.home_dir}/.claude/projects") self.epoch = epoch self.row = row or {} self.mode = mode @property def ws_key(self) -> str: return workspace_key(self.workspace) @property def cwd(self) -> str: return (self.row.get("pane", {}).get("cwd", "") if isinstance(self.row, dict) else "") or self.workspace class BaseAgentAdapter: @property def name(self) -> str: raise NotImplementedError @property def own_key(self) -> str: raise NotImplementedError @property def ready_tokens(self) -> str: raise NotImplementedError @property def exit_key(self) -> str: raise NotImplementedError @property def delegate_agent_key(self) -> str: raise NotImplementedError @property def identity_cache_fields(self) -> tuple: """agent_identities 캐시의 에이전트별 필드명. B-10(Option A)로 캐시 읽기 경로가 제거되어 현재 생산 소비자는 0건이지만, 캐시 쓰기 경로가 도입되면 즉시 필요한 유일한 스키마 기술이므로 존치한다. 임의 삭제 금지 — 삭제 시 4개 어댑터에 필드명을 다시 흩뿌려야 한다.""" raise NotImplementedError @property def input_prompt(self) -> Optional[str]: return None @property def input_placeholder(self) -> Optional[str]: return None @property def input_rule_pattern(self) -> Optional[str]: return None def derive_session_name(self, slug: str, role: str = "creator") -> str: r = role.lower() return f"{slug}-{r}-{self.name}" def matches_session_name(self, session_name: str) -> bool: for r in ('creator', 'planner', 'reviewer'): if session_name.endswith(f"-{r}-{self.name}"): return True return session_name.endswith(f"-{self.name}") def verify_session(self, ws: str, uuid: str, row: Optional[Dict[str, Any]] = None, mode: str = "discover") -> bool: return verify_session_uuid(ws, self.name, uuid, row=row, mode=mode) def artifact_path(self, uuid: str, ctx: DiscoveryContext) -> str: raise NotImplementedError def verify_artifact(self, uuid: str, ctx: DiscoveryContext) -> bool: raise NotImplementedError def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list: raise NotImplementedError def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str: raise NotImplementedError def resume_spec(self, binary: str, session_uuid: str, materialized: bool = False) -> str: raise NotImplementedError def auth_ok(self, run_cmd: Optional[Any] = None) -> bool: raise NotImplementedError def discover(self, ctx: DiscoveryContext) -> list: raise NotImplementedError