Round-3 review of f5a6575 (planner-reviewer-codex-01, R1/R2).
R1 - the adapter's in-use check handed only the conversation DB to lsof, so a
holder that keeps the presence lock (or a sidecar) open while naming the uuid
on no command line and in no session variable was invisible, and the held
lock was unlinked from under it. AgyAgentAdapter.conversation_in_use now
passes every path of the conversation (DB, -wal/-shm/-journal, brain,
annotation, presence lock).
R2 - both resolver stages required the DB, so once an interrupted or pre-#10
purge had removed it, the WAL/annotation/lock left behind were "no artifact"
(exit 5) forever. BaseAgentAdapter.has_artifacts (default: a uuid-bearing
artifact path that exists) replaces the resolver's path fallback, and agy's
override accepts any of its pieces.
Tests: a flock holder that names the uuid nowhere is refused and nothing is
removed; orphaned leftovers resolve to agy and are purged. VERSIONS.md
v4.4.3 items 1-2 and orc-onboard SKILL.md updated.
Refs #10 #13
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
154 lines
6.3 KiB
Python
154 lines
6.3 KiB
Python
# __main__.py — CLI bridge for lib_py.agents facts and resolution (N4)
|
|
|
|
import os, sys, json, shlex
|
|
from lib_py.agents.registry import get_adapter, own_key, agent_of_row
|
|
|
|
# `purge auto` probes adapters in this order; opencode last because its
|
|
# artifact path is one shared DB that exists for every uuid.
|
|
PURGE_PROBE_ORDER = ('agy', 'claude', 'hermes', 'grok', 'codex', 'opencode')
|
|
|
|
|
|
def _resolve_purge_target(agent_hint: str, uuid: str, workspace: str):
|
|
"""(agent, adapter, ctx) whose on-disk artifact carries `uuid`, else None.
|
|
|
|
An explicit agent is trusted as long as its artifact exists. `auto` tries
|
|
each adapter's own verification first (revalidate mode: an orchestrator
|
|
conversation is not in any workspace cache), then the adapter's own
|
|
`has_artifacts` - which, for agy, also accepts orphaned sidecars, the
|
|
annotation or the presence lock left behind after the DB is gone.
|
|
"""
|
|
from lib_py.agents.base import DiscoveryContext
|
|
names = [agent_hint] if agent_hint and agent_hint != 'auto' else list(PURGE_PROBE_ORDER)
|
|
probes = []
|
|
for name in names:
|
|
adapter = get_adapter(name)
|
|
if not adapter:
|
|
continue
|
|
probes.append((name, adapter, DiscoveryContext(workspace, name, mode="revalidate")))
|
|
for name, adapter, ctx in probes:
|
|
try:
|
|
if adapter.verify_artifact(uuid, ctx):
|
|
return name, adapter, ctx
|
|
except Exception:
|
|
pass
|
|
for name, adapter, ctx in probes:
|
|
try:
|
|
if adapter.has_artifacts(uuid, ctx):
|
|
return name, adapter, ctx
|
|
except Exception:
|
|
continue
|
|
return None
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python -m lib_py.agents <facts|resolve|purge> [args...]", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
cmd = sys.argv[1]
|
|
if cmd == 'facts':
|
|
agent_name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
adapter = get_adapter(agent_name)
|
|
if not adapter:
|
|
print(f"ERROR: Unknown agent {agent_name!r}", file=sys.stderr)
|
|
sys.exit(1)
|
|
# Emit shell-eval friendly facts
|
|
q = shlex.quote
|
|
print(f"MAM_AGENT_NAME={q(adapter.name)}")
|
|
print(f"MAM_OWN_KEY={q(adapter.own_key)}")
|
|
print(f"MAM_INPUT_PROMPT={q(adapter.input_prompt or '')}")
|
|
print(f"MAM_INPUT_PLACEHOLDER={q(adapter.input_placeholder or '')}")
|
|
print(f"MAM_INPUT_RULE_PATTERN={q(adapter.input_rule_pattern or '')}")
|
|
print(f"MAM_READY_TOKENS={q(adapter.ready_tokens)}")
|
|
print(f"MAM_STRONG_READY_TOKENS={q(adapter.strong_ready_tokens)}")
|
|
print(f"MAM_WEAK_READY_TOKENS={q(adapter.weak_ready_tokens)}")
|
|
print(f"MAM_MODAL_TOKENS={q(adapter.modal_tokens or '')}")
|
|
print(f"MAM_EXIT_KEY={q(adapter.exit_key)}")
|
|
print(f"MAM_DELEGATE_AGENT_KEY={q(adapter.delegate_agent_key)}")
|
|
|
|
elif cmd == 'resolve':
|
|
name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
resolved = agent_of_row({}, session_name=name)
|
|
if resolved:
|
|
print(resolved)
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
|
|
elif cmd == 'input-region':
|
|
agent_name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
pane_text = sys.stdin.read()
|
|
from lib_py.agents.input_region import extract_input_region
|
|
try:
|
|
region = extract_input_region(pane_text, agent_name)
|
|
print(region)
|
|
sys.exit(0)
|
|
except Exception as ex:
|
|
print(f"ERROR: {ex}", file=sys.stderr)
|
|
sys.exit(5)
|
|
|
|
elif cmd == 'spawn-spec':
|
|
agent_name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
binary = sys.argv[3] if len(sys.argv) > 3 else agent_name
|
|
uuid = sys.argv[4] if len(sys.argv) > 4 else ''
|
|
use_wrapper = (sys.argv[5].lower() in ('1', 'true', 'yes')) if len(sys.argv) > 5 else False
|
|
adapter = get_adapter(agent_name)
|
|
if not adapter:
|
|
sys.exit(1)
|
|
print(adapter.spawn_spec(binary, uuid, use_wrapper=use_wrapper))
|
|
sys.exit(0)
|
|
|
|
elif cmd == 'resume-spec':
|
|
agent_name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
binary = sys.argv[3] if len(sys.argv) > 3 else agent_name
|
|
uuid = sys.argv[4] if len(sys.argv) > 4 else ''
|
|
workspace = sys.argv[5] if len(sys.argv) > 5 else ''
|
|
adapter = get_adapter(agent_name)
|
|
if not adapter:
|
|
sys.exit(1)
|
|
from lib_py.agents.base import DiscoveryContext
|
|
ctx = DiscoveryContext(workspace, agent_name) if workspace else None
|
|
mat = adapter.verify_artifact(uuid, ctx) if (ctx and uuid) else False
|
|
print(adapter.resume_spec(binary, uuid, materialized=mat))
|
|
sys.exit(0)
|
|
|
|
elif cmd == 'exit-key':
|
|
agent_name = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
adapter = get_adapter(agent_name)
|
|
if adapter:
|
|
print(adapter.exit_key)
|
|
sys.exit(0)
|
|
print('/exit')
|
|
sys.exit(0)
|
|
|
|
elif cmd == 'purge':
|
|
# purge <agent|auto> <uuid> [workspace] (Issue #13)
|
|
# Deletes a conversation's on-disk artifacts through its adapter and
|
|
# prints one JSON object: {"agent", "purged", "residual", "in_use"}.
|
|
# Exit 0 purged; 4 refused, conversation still in use (nothing
|
|
# removed); 5 no adapter has an artifact for this uuid.
|
|
agent_hint = sys.argv[2] if len(sys.argv) > 2 else 'auto'
|
|
uuid = sys.argv[3] if len(sys.argv) > 3 else ''
|
|
workspace = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else os.getcwd()
|
|
if not uuid:
|
|
print("ERROR: purge requires <agent|auto> <uuid>", file=sys.stderr)
|
|
sys.exit(1)
|
|
target = _resolve_purge_target(agent_hint, uuid, workspace)
|
|
if not target:
|
|
print(json.dumps({"agent": None, "purged": [], "residual": [], "in_use": False}))
|
|
sys.exit(5)
|
|
name, adapter, ctx = target
|
|
if adapter.conversation_in_use(uuid, ctx):
|
|
print(json.dumps({"agent": name, "purged": [], "residual": [], "in_use": True}))
|
|
sys.exit(4)
|
|
purged = adapter.purge_artifacts(uuid, ctx)
|
|
residual = adapter.residual_artifacts(uuid, ctx)
|
|
print(json.dumps({"agent": name, "purged": purged, "residual": residual, "in_use": False}))
|
|
sys.exit(0)
|
|
|
|
else:
|
|
print(f"ERROR: Unknown CLI command {cmd!r}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|