feat: multi-agent-mux-reset skill; purge refusals list lsof holders; codex purge removes its tui-thread capability (Issue #78) #82

Open
Godopu wants to merge 6 commits from fix/issue-78 into main
pull from: fix/issue-78
2 changed files with 98 additions and 4 deletions
Showing only changes of commit 51dfd38f0b - Show all commits

No files matched your search

+25 -4
View File
@@ -1,6 +1,6 @@
import os, json, glob, sqlite3, subprocess import os, json, glob, shutil, sqlite3, subprocess
from typing import Optional, Any, List from typing import Optional, Any, List
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, _ro_connect, scan_names, db_rows_naming from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, _plausible_id, _ro_connect, scan_names, db_rows_naming
from lib_py.verify_session import workspace_key from lib_py.verify_session import workspace_key
class CodexAgentAdapter(BaseAgentAdapter): class CodexAgentAdapter(BaseAgentAdapter):
@@ -131,6 +131,22 @@ class CodexAgentAdapter(BaseAgentAdapter):
except Exception: except Exception:
return False return False
def _purge_tui_thread_capability(self, uuid: str, ctx: DiscoveryContext, purged: List[str]) -> None:
if not _plausible_id(uuid):
return
cap = os.path.join(self._codex_home(ctx), "tui-thread-reference-capabilities", uuid)
if not os.path.lexists(cap):
return
try:
if os.path.isdir(cap) and not os.path.islink(cap):
shutil.rmtree(cap)
else:
os.unlink(cap)
if not os.path.lexists(cap):
purged.append(cap)
except Exception:
pass
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]: def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]:
purged = [] purged = []
path = self._find_rollout(uuid, ctx) path = self._find_rollout(uuid, ctx)
@@ -140,12 +156,16 @@ class CodexAgentAdapter(BaseAgentAdapter):
if res.returncode == 0: if res.returncode == 0:
if path: if path:
purged.append(path) purged.append(path)
self._purge_tui_thread_capability(uuid, ctx, purged)
return purged return purged
except Exception: except Exception:
pass pass
if path and os.path.exists(path): if path and os.path.exists(path):
os.remove(path) try:
purged.append(path) os.remove(path)
purged.append(path)
except Exception:
pass
db = self._state_db(ctx) db = self._state_db(ctx)
if db and os.path.exists(db): if db and os.path.exists(db):
try: try:
@@ -156,6 +176,7 @@ class CodexAgentAdapter(BaseAgentAdapter):
purged.append(f"state db row for thread: {uuid}") purged.append(f"state db row for thread: {uuid}")
except Exception: except Exception:
pass pass
self._purge_tui_thread_capability(uuid, ctx, purged)
return purged return purged
def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]: def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]:
+73
View File
@@ -249,3 +249,76 @@ def test_stop_purge_refusal_prints_holders(mam_sandbox, mock_herdr, monkeypatch)
finally: finally:
holder.kill() holder.kill()
holder.wait() holder.wait()
@pytest.mark.parametrize("delete_ok", [True, False], ids=["delete_ok", "delete_fails"])
def test_codex_purge_removes_tui_thread_capability(tmp_path, monkeypatch, delete_ok):
home = tmp_path / "home"
codex_home = tmp_path / "codex-home"
ws = tmp_path / "ws"
bindir = tmp_path / "bin"
for d in (home, codex_home, ws, bindir):
d.mkdir(parents=True, exist_ok=True)
conv = str(uuidlib.uuid4())
other = str(uuidlib.uuid4())
rollout = codex_home / "sessions" / "2026" / "10" / "28" / f"rollout-2026-10-28T00-00-00-{conv}.jsonl"
rollout.parent.mkdir(parents=True, exist_ok=True)
rollout.write_text(json.dumps({"payload": {"id": conv, "cwd": str(ws)}}) + "\n", encoding="utf-8")
cap_dir = codex_home / "tui-thread-reference-capabilities"
cap_dir.mkdir(parents=True, exist_ok=True)
cap_mine = cap_dir / conv
cap_other = cap_dir / other
cap_mine.write_text("cap-mine", encoding="utf-8")
cap_other.write_text("cap-other", encoding="utf-8")
state_db = codex_home / "state_5.sqlite"
conn = sqlite3.connect(str(state_db))
conn.execute("CREATE TABLE threads (id TEXT PRIMARY KEY, rollout_path TEXT, cwd TEXT)")
conn.execute("INSERT INTO threads VALUES (?, ?, ?)", (conv, str(rollout), str(ws)))
conn.execute("INSERT INTO threads VALUES (?, ?, ?)", (other, "", str(ws)))
conn.commit()
conn.close()
fake_codex = bindir / "codex"
if delete_ok:
# Simulate `codex delete <uuid> --force` removing rollout + DB row, leaving tui-thread-reference-capabilities.
fake_codex.write_text(
f"#!{sys.executable}\n"
"import os, sqlite3, sys\n"
"if len(sys.argv) >= 3 and sys.argv[1] == 'delete':\n"
f" p = {str(rollout)!r}\n"
" if os.path.exists(p):\n"
" os.remove(p)\n"
f" conn = sqlite3.connect({str(state_db)!r})\n"
" conn.execute('DELETE FROM threads WHERE id=?', (sys.argv[2],))\n"
" conn.commit()\n"
" conn.close()\n"
" sys.exit(0)\n"
"sys.exit(1)\n",
encoding="utf-8",
)
else:
fake_codex.write_text("#!/bin/sh\nexit 1\n", encoding="utf-8")
fake_codex.chmod(0o755)
monkeypatch.setenv("HOME", str(home))
monkeypatch.setenv("HOME_DIR", str(home))
monkeypatch.setenv("CODEX_HOME", str(codex_home))
monkeypatch.setenv("PATH", f"{bindir}:{os.environ.get('PATH', '')}")
adapter = get_adapter("codex")
ctx = DiscoveryContext(workspace=str(ws), agent_name="codex", home_dir=str(home))
assert str(cap_mine) in adapter.residual_artifacts(conv, ctx)
purged = adapter.purge_artifacts(conv, ctx)
assert str(cap_mine) in purged
assert not cap_mine.exists()
assert cap_other.exists()
assert not rollout.exists()
assert adapter.residual_artifacts(conv, ctx) == []
assert str(cap_other) in adapter.residual_artifacts(other, ctx)