- Add --yolo and --accept-hooks headless auto-approval flags to spawn_spec and resume_spec
- Define Hermes TUI input delimiters (input_prompt='❯', input_rule_pattern='─{10,}')
- Refactor verify_artifact and discover in hermes.py using session started_at timestamps
- Fix HERDR_EPOCH capture timing before spawn in create_session.sh to prevent epoch race
- Update reconcile.sh for hermes drift-C multi-candidate and sibling claimed exclusion
- Add Hermes contract, epoch filtering, and C-ambiguous unit tests (140 passed)
- Add agent evaluation report and final review reports for Hermes support
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
import os, sys, sqlite3
|
||
from typing import Optional, Any
|
||
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
|
||
from lib_py.verify_session import workspace_key
|
||
|
||
class HermesAgentAdapter(BaseAgentAdapter):
|
||
@property
|
||
def name(self) -> str:
|
||
return 'hermes'
|
||
|
||
@property
|
||
def own_key(self) -> str:
|
||
return 'hermes_conversation_id_own'
|
||
|
||
@property
|
||
def ready_tokens(self) -> str:
|
||
return 'Hermes|Welcome to Hermes Agent|NOUS HERMES'
|
||
|
||
@property
|
||
def exit_key(self) -> str:
|
||
return '/exit'
|
||
|
||
@property
|
||
def delegate_agent_key(self) -> str:
|
||
return 'hermes-agent'
|
||
|
||
@property
|
||
def identity_cache_fields(self) -> tuple:
|
||
return ('session_id',)
|
||
|
||
@property
|
||
def input_prompt(self) -> str:
|
||
return '❯'
|
||
|
||
@property
|
||
def input_placeholder(self) -> str:
|
||
return ''
|
||
|
||
@property
|
||
def input_rule_pattern(self) -> str:
|
||
return '─{10,}'
|
||
|
||
def artifact_path(self, uuid: str, ctx: DiscoveryContext) -> str:
|
||
return f"{ctx.home_dir}/.hermes/sessions/session_{uuid}.json"
|
||
|
||
def verify_artifact(self, uuid: str, ctx: DiscoveryContext) -> bool:
|
||
hdb = f"{ctx.home_dir}/.hermes/state.db"
|
||
if not os.path.exists(hdb):
|
||
return False
|
||
try:
|
||
conn = sqlite3.connect(hdb)
|
||
r = conn.execute("SELECT cwd, started_at FROM sessions WHERE id=?", (uuid,)).fetchone()
|
||
conn.close()
|
||
if not r:
|
||
return False
|
||
found_cwd, started_at = r[0], r[1]
|
||
if ctx.epoch and started_at and float(started_at) < float(ctx.epoch):
|
||
return False
|
||
if found_cwd and workspace_key(found_cwd) != workspace_key(ctx.cwd):
|
||
return False
|
||
except Exception:
|
||
return False
|
||
return True
|
||
|
||
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
|
||
purged = []
|
||
json_file = self.artifact_path(uuid, ctx)
|
||
if os.path.exists(json_file):
|
||
os.remove(json_file)
|
||
purged.append(json_file)
|
||
hdb = f"{ctx.home_dir}/.hermes/state.db"
|
||
if os.path.exists(hdb):
|
||
try:
|
||
conn = sqlite3.connect(hdb)
|
||
conn.execute("DELETE FROM sessions WHERE id=?", (uuid,))
|
||
conn.execute("DELETE FROM messages WHERE session_id=?", (uuid,))
|
||
conn.commit()
|
||
conn.close()
|
||
purged.append(f"db records for session: {uuid}")
|
||
except Exception as e:
|
||
sys.stderr.write(f"WARN: purge hermes db records failed: {e}\n")
|
||
return purged
|
||
|
||
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
||
return f"{binary} --yolo --accept-hooks"
|
||
|
||
def resume_spec(self, binary: str, session_uuid: str, materialized: bool = False) -> str:
|
||
if materialized and session_uuid:
|
||
return f"{binary} --resume {session_uuid} --no-restore-cwd --yolo --accept-hooks"
|
||
return f"{binary} --yolo --accept-hooks"
|
||
|
||
def auth_ok(self, run_cmd: Optional[Any] = None) -> bool:
|
||
return True
|
||
|
||
def discover(self, ctx: DiscoveryContext) -> list:
|
||
hdb = f"{ctx.home_dir}/.hermes/state.db"
|
||
if not os.path.exists(hdb):
|
||
return []
|
||
try:
|
||
conn = sqlite3.connect(hdb)
|
||
if ctx.epoch:
|
||
rows = conn.execute(
|
||
"SELECT id FROM sessions WHERE cwd=? AND started_at >= ? ORDER BY started_at DESC LIMIT 20",
|
||
(ctx.workspace, ctx.epoch)
|
||
).fetchall()
|
||
else:
|
||
rows = conn.execute(
|
||
"SELECT id FROM sessions WHERE cwd=? ORDER BY started_at DESC LIMIT 20",
|
||
(ctx.workspace,)
|
||
).fetchall()
|
||
conn.close()
|
||
candidates = []
|
||
for (cand,) in rows:
|
||
if cand and self.verify_artifact(cand, ctx):
|
||
candidates.append(cand)
|
||
return candidates
|
||
except Exception:
|
||
return []
|