fix(agents): codex, grok and opencode report their residual (Issue #31)
Their purge is unchanged; what it leaves is now reported: codex names and thread rows (state_*, thread_history_*; logs_* is not scanned), grok memory-v2 observations, opencode event/todo rows. db_rows_naming opens the DB read-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ae98cf18c4
commit
b3461377ae
4 files changed
+61
-3
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
import os, json, glob, sqlite3, subprocess
|
import os, json, glob, sqlite3, subprocess
|
||||||
from typing import Optional, Any, List
|
from typing import Optional, Any, List
|
||||||
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
|
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, 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):
|
||||||
@@ -158,6 +158,17 @@ class CodexAgentAdapter(BaseAgentAdapter):
|
|||||||
pass
|
pass
|
||||||
return purged
|
return purged
|
||||||
|
|
||||||
|
def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]:
|
||||||
|
# Issue #31: names under CODEX_HOME plus thread rows. logs_*.sqlite is
|
||||||
|
# not scanned (no thread index: a full scan of a large file).
|
||||||
|
root = self._codex_home(ctx)
|
||||||
|
found = scan_names(root, uuid)
|
||||||
|
for db in sorted(glob.glob(f"{root}/state_*.sqlite")):
|
||||||
|
found += db_rows_naming(db, uuid, ("thread_id",), id_tables=(("threads", "id"),))
|
||||||
|
for db in sorted(glob.glob(f"{root}/thread_history_*.sqlite")):
|
||||||
|
found += db_rows_naming(db, uuid, ("thread_id",))
|
||||||
|
return found
|
||||||
|
|
||||||
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
||||||
return f"{binary} --dangerously-bypass-approvals-and-sandbox"
|
return f"{binary} --dangerously-bypass-approvals-and-sandbox"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os, json, glob, shutil
|
import os, json, glob, shutil
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
|
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, scan_names
|
||||||
from lib_py.verify_session import workspace_key
|
from lib_py.verify_session import workspace_key
|
||||||
|
|
||||||
|
|
||||||
@@ -108,6 +108,11 @@ class GrokAgentAdapter(BaseAgentAdapter):
|
|||||||
purged.append(self.artifact_path(uuid, ctx))
|
purged.append(self.artifact_path(uuid, ctx))
|
||||||
return purged
|
return purged
|
||||||
|
|
||||||
|
def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
|
||||||
|
# Issue #31: reported, not removed - memory-v2 observations are the
|
||||||
|
# shared store of grok's memory pipeline.
|
||||||
|
return scan_names(f"{ctx.home_dir}/.grok", uuid)
|
||||||
|
|
||||||
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
||||||
if session_uuid:
|
if session_uuid:
|
||||||
return f"{binary} --session-id {session_uuid} --permission-mode bypassPermissions"
|
return f"{binary} --session-id {session_uuid} --permission-mode bypassPermissions"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os, sys, sqlite3, shutil
|
import os, sys, sqlite3, shutil
|
||||||
from typing import Optional, Any, List
|
from typing import Optional, Any, List
|
||||||
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
|
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, scan_names, db_rows_naming
|
||||||
from lib_py.verify_session import workspace_key
|
from lib_py.verify_session import workspace_key
|
||||||
|
|
||||||
_PROVIDER_ENV_VARS = (
|
_PROVIDER_ENV_VARS = (
|
||||||
@@ -194,6 +194,13 @@ class OpenCodeAgentAdapter(BaseAgentAdapter):
|
|||||||
sys.stderr.write(f"WARN: purge opencode db records failed: {e}\n")
|
sys.stderr.write(f"WARN: purge opencode db records failed: {e}\n")
|
||||||
return purged
|
return purged
|
||||||
|
|
||||||
|
def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
|
||||||
|
# Issue #31: the purge leaves event-sourced rows (event, todo, ...);
|
||||||
|
# deleting those is not known to be safe, so they are reported.
|
||||||
|
found = scan_names(f"{ctx.home_dir}/.local/share/opencode", uuid)
|
||||||
|
return found + db_rows_naming(self._resolve_db(ctx), uuid, ("session_id", "aggregate_id"),
|
||||||
|
id_tables=(("session", "id"),))
|
||||||
|
|
||||||
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
||||||
return f"{binary} --auto --agent build"
|
return f"{binary} --auto --agent build"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import os, json, sqlite3, shutil, subprocess
|
import os, json, sqlite3, shutil, subprocess
|
||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
|
from urllib.parse import quote
|
||||||
from lib_py.paths import resolve_home
|
from lib_py.paths import resolve_home
|
||||||
from lib_py.verify_session import verify_session_uuid, workspace_key
|
from lib_py.verify_session import verify_session_uuid, workspace_key
|
||||||
|
|
||||||
@@ -107,6 +108,40 @@ def scan_names(root: str, uuid: str, skip=()) -> list:
|
|||||||
return sorted(found)
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
|
def db_rows_naming(db: str, uuid: str, columns=(), id_tables=()) -> list:
|
||||||
|
""""<db> (<table>.<col>: <n> rows)" for every table holding rows of this
|
||||||
|
conversation in one of `columns`, or in the id column of an `id_tables`
|
||||||
|
(table, column) pair (Issue #31). Read-only; unreadable tables are skipped."""
|
||||||
|
if not _plausible_id(uuid) or not db or not os.path.isfile(db):
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(f"file:{quote(os.path.abspath(db))}?mode=ro", uri=True)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
found = []
|
||||||
|
try:
|
||||||
|
tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")]
|
||||||
|
for table in tables:
|
||||||
|
qt = table.replace('"', '""')
|
||||||
|
try:
|
||||||
|
cols = {r[1] for r in conn.execute(f'PRAGMA table_info("{qt}")')}
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
wanted = [c for c in columns if c in cols]
|
||||||
|
wanted += [c for t, c in id_tables if t == table and c in cols and c not in wanted]
|
||||||
|
for col in wanted:
|
||||||
|
try:
|
||||||
|
n = conn.execute(f'SELECT count(*) FROM "{qt}" WHERE "{col}"=?', (uuid,)).fetchone()[0]
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if n:
|
||||||
|
found.append(f"{db} ({table}.{col}: {n} rows)")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
return found
|
||||||
|
|
||||||
class SpawnSpec:
|
class SpawnSpec:
|
||||||
def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None):
|
def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None):
|
||||||
self.agent_name = agent_name
|
self.agent_name = agent_name
|
||||||
|
|||||||
Reference in new issue
Block a user