fix(agents): in_use_hints lists the processes holding the conversation file (lsof) (#78)
This commit is contained in:
1 parent
a3364e82e1
commit
c6eb4564d2
4 files changed
+312
-6
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user