diff --git a/.agents/skills/lib_py/agents/adapters/claude.py b/.agents/skills/lib_py/agents/adapters/claude.py index a8bef1d..f08103a 100644 --- a/.agents/skills/lib_py/agents/adapters/claude.py +++ b/.agents/skills/lib_py/agents/adapters/claude.py @@ -1,6 +1,6 @@ import os, re, json, glob, time, shutil, subprocess from typing import Optional, Any -from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_in_use, _plausible_id +from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_in_use, scan_names, _plausible_id from lib_py.verify_session import workspace_key @@ -256,6 +256,40 @@ class ClaudeAgentAdapter(BaseAgentAdapter): purged.append(_label(path)) return purged + def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list: + """What still carries this session: names holding the uuid under the + config root, in projects (two levels: key, then entry) and in the tmp + roots (two levels), plus the content-matched jobs dir and session + files. projects is not walked deeper; it can be very large.""" + if not _plausible_id(uuid): + return [] + cfg = _config_dir(ctx) + found = set(scan_names(cfg, uuid, skip=(os.path.join(cfg, "projects"), ctx.claude_dir))) + try: + keys = sorted(os.listdir(ctx.claude_dir)) + except OSError: + keys = [] + for key in keys: + kdir = os.path.join(ctx.claude_dir, key) + if uuid in key: + found.add(kdir) + elif os.path.isdir(kdir): + try: + found.update(os.path.join(kdir, n) for n in os.listdir(kdir) if uuid in n) + except OSError: + pass + job = self._owned_job(uuid, ctx) + if job: + found.add(job) + found.update(path for path, _, _ in self._session_files(uuid, ctx)) + for root in _tmp_roots(): + try: + tkeys = os.listdir(root) + except OSError: + continue + found.update(p for p in (os.path.join(root, k, uuid) for k in tkeys) if os.path.lexists(p)) + return sorted(found) + def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str: if use_wrapper or not session_uuid: return f"{binary} --dangerously-skip-permissions" diff --git a/.agents/skills/lib_py/agents/base.py b/.agents/skills/lib_py/agents/base.py index 7b5e977..50ccc00 100644 --- a/.agents/skills/lib_py/agents/base.py +++ b/.agents/skills/lib_py/agents/base.py @@ -91,6 +91,22 @@ def _plausible_id(uuid: str) -> bool: return isinstance(uuid, str) and bool(uuid.strip(".")) and not any(s in uuid for s in ("/", "..", "*")) +def scan_names(root: str, uuid: str, skip=()) -> list: + """Paths under `root` whose name carries `uuid` (Issue #31). A matched + directory is reported whole and not descended; `skip` dirs are not walked.""" + if not _plausible_id(uuid) or not os.path.isdir(root): + return [] + skip_real = {os.path.realpath(s) for s in skip if s} + found = [] + for dirpath, dirnames, filenames in os.walk(root): + for name in dirnames + filenames: + if uuid in name: + found.append(os.path.join(dirpath, name)) + dirnames[:] = [d for d in dirnames if uuid not in d + and not (skip_real and os.path.realpath(os.path.join(dirpath, d)) in skip_real)] + return sorted(found) + + class SpawnSpec: def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None): self.agent_name = agent_name