fix(agents): in_use_hints lists the processes holding the conversation file (lsof) (#78)

This commit is contained in:
Godopu committed 2026-10-29 12:52:17 +00:00
1 parent a3364e82e1
commit c6eb4564d2
4 files changed
+312 -6

No files matched your search

+42 -3
View File
@@ -1,7 +1,7 @@
# base.py — BaseAgentAdapter abstract base class, SpawnSpec, and DiscoveryContext
import os, json, sqlite3, shutil, subprocess, tempfile
from typing import Optional, Dict, Any, List
from typing import Optional, Dict, Any, List, Tuple
from urllib.parse import quote
from lib_py.paths import resolve_home
from lib_py.verify_session import verify_session_uuid, workspace_key
@@ -86,6 +86,34 @@ def conversation_in_use(uuid: str, paths=()) -> bool:
return False
def file_holders(paths=()) -> List[Tuple[int, str]]:
"""(pid, command_line) for each process holding one of `paths` open via lsof (#78).
Informational only: returns [] when lsof is absent or fails."""
existing = [p for p in paths if p and os.path.exists(p)]
if not existing or not shutil.which("lsof"):
return []
try:
r = subprocess.run(["lsof", "-t"] + existing, capture_output=True, text=True, timeout=10)
except Exception:
return []
pids: List[int] = []
for tok in (r.stdout or "").split():
if tok.isdigit():
pid = int(tok)
if pid not in pids:
pids.append(pid)
out: List[Tuple[int, str]] = []
for pid in pids:
try:
cmd = subprocess.run(["ps", "-o", "command=", "-p", str(pid)],
capture_output=True, text=True, timeout=10).stdout.strip()
except Exception:
continue
if cmd:
out.append((pid, cmd))
return out
def _plausible_id(uuid: str) -> bool:
"""An id safe to build a path from and to search for: not empty, not
made of dots alone ("." names the parent dir itself), no "/", ".." or "*"."""
@@ -354,14 +382,25 @@ class BaseAgentAdapter:
path = self.artifact_path(uuid, ctx)
return uuid in path and os.path.exists(path)
def in_use_paths(self, uuid: str, ctx: DiscoveryContext) -> list:
"""On-disk paths checked with lsof for conversation_in_use and in_use_hints (#78)."""
return [self.artifact_path(uuid, ctx)]
def _holder_counts(self, cmd: str) -> bool:
return True
def conversation_in_use(self, uuid: str, ctx: DiscoveryContext) -> bool:
"""True while a live process still references the conversation."""
return conversation_in_use(uuid, [self.artifact_path(uuid, ctx)])
return conversation_in_use(uuid, self.in_use_paths(uuid, ctx))
def in_use_hints(self, uuid: str, ctx: DiscoveryContext) -> list:
"""Where to look for what holds the conversation, shown when a purge
is refused because it is in use."""
return [f"any process with it on its command line (an editor, a grep, ...): ps -axww | grep {uuid}"]
hints = [f"any process with it on its command line (an editor, a grep, ...): ps -axww | grep {uuid}"]
for pid, cmd in file_holders(self.in_use_paths(uuid, ctx)):
if self._holder_counts(cmd):
hints.append(f"held open by pid {pid}: {cmd} (not stopped by MAM; end it yourself if it is safe)")
return hints
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
raise NotImplementedError