fix(agents): open agy, codex and hermes databases read-only (Issue #64)

This commit is contained in:
Godopu committed 2026-10-19 23:12:55 +09:00
1 parent 3ce5d5fdc1
commit b0eccc5301
8 files changed
+461 -56

No files matched your search

+29 -3
View File
@@ -1,6 +1,6 @@
# base.py — BaseAgentAdapter abstract base class, SpawnSpec, and DiscoveryContext
import os, json, sqlite3, shutil, subprocess
import os, json, sqlite3, shutil, subprocess, tempfile
from typing import Optional, Dict, Any, List
from urllib.parse import quote
from lib_py.paths import resolve_home
@@ -108,15 +108,41 @@ def scan_names(root: str, uuid: str, skip=()) -> list:
return sorted(found)
def _ro_connect(db: str):
"""A read that neither creates nor removes anything beside db (Issues #60, #64).
Both WAL sidecars (a live writer keeps them): read through them. No -wal:
nothing to apply, so the file is read as immutable. A -wal without its
-shm (left behind - no writer runs without one) still holds committed
rows: they are read from a private copy, where SQLite may rebuild the
index, loaded into memory."""
path = os.path.abspath(db)
wal, shm = os.path.exists(path + "-wal"), os.path.exists(path + "-shm")
if wal and shm:
return sqlite3.connect(f"file:{quote(path)}?mode=ro", uri=True)
if not wal:
return sqlite3.connect(f"file:{quote(path)}?mode=ro&immutable=1", uri=True)
mem = sqlite3.connect(":memory:")
with tempfile.TemporaryDirectory(prefix="mam-ro-") as tmp:
copy = os.path.join(tmp, os.path.basename(path) or "state.db")
shutil.copyfile(path, copy)
shutil.copyfile(path + "-wal", copy + "-wal")
src = sqlite3.connect(copy)
try:
src.backup(mem)
finally:
src.close()
return mem
def db_rows_naming(db: str, uuid: str, columns=(), id_tables=(), connect=None) -> 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.
`connect(db)` opens the database (default: a mode=ro URI)."""
`connect(db)` opens the database (default: `_ro_connect`)."""
if not _plausible_id(uuid) or not db or not os.path.isfile(db):
return []
try:
conn = connect(db) if connect else sqlite3.connect(f"file:{quote(os.path.abspath(db))}?mode=ro", uri=True)
conn = (connect or _ro_connect)(db)
except Exception:
return []
found = []