refactor(workspace_uuid): find_workspace_uuid() returns the id and skips exclude_ids; main prints it (Issue #63)
This commit is contained in:
1 parent
dc886ecd6b
commit
ead490bddc
2 files changed
+180
-22
No files matched your search
@@ -15,17 +15,10 @@ OWN_KEY = {
|
|||||||
|
|
||||||
from lib_py.paths import resolve_home
|
from lib_py.paths import resolve_home
|
||||||
|
|
||||||
def find_workspace_uuid_main():
|
def find_workspace_uuid(ws, agent, target='', state=None, home=None, claude_dir=None, exclude_ids=()):
|
||||||
ws = os.environ['WS_ABS']
|
d = state if isinstance(state, dict) else {}
|
||||||
agent = os.environ['AGENT']
|
home = resolve_home(home)
|
||||||
home = resolve_home()
|
claude_project_dir = claude_dir or os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
||||||
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
|
||||||
target = os.environ.get('TARGET_SESSION', '')
|
|
||||||
|
|
||||||
try:
|
|
||||||
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
|
|
||||||
except Exception:
|
|
||||||
d = {}
|
|
||||||
|
|
||||||
running_ids = set()
|
running_ids = set()
|
||||||
for s_item in d.get('herdr_sessions', []):
|
for s_item in d.get('herdr_sessions', []):
|
||||||
@@ -38,6 +31,10 @@ def find_workspace_uuid_main():
|
|||||||
running_ids.add(val)
|
running_ids.add(val)
|
||||||
|
|
||||||
orchestrator_ids = set(mam_orchestrator_uuids())
|
orchestrator_ids = set(mam_orchestrator_uuids())
|
||||||
|
if "MAM_ORCHESTRATOR_UUIDS" not in os.environ:
|
||||||
|
raw_orc = d.get('orchestrator_uuids')
|
||||||
|
if isinstance(raw_orc, list) and all(isinstance(x, str) and x.strip() for x in raw_orc):
|
||||||
|
orchestrator_ids |= set(raw_orc)
|
||||||
if target:
|
if target:
|
||||||
for s_item in d.get('herdr_sessions', []):
|
for s_item in d.get('herdr_sessions', []):
|
||||||
if s_item.get('name') == target:
|
if s_item.get('name') == target:
|
||||||
@@ -45,11 +42,7 @@ def find_workspace_uuid_main():
|
|||||||
if own:
|
if own:
|
||||||
orchestrator_ids.discard(own)
|
orchestrator_ids.discard(own)
|
||||||
|
|
||||||
def emit(u):
|
excluded = running_ids | orchestrator_ids | {u for u in (exclude_ids or ()) if u}
|
||||||
if u in running_ids or u in orchestrator_ids:
|
|
||||||
return
|
|
||||||
print(u)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Fetch all sessions matching this workspace
|
# Fetch all sessions matching this workspace
|
||||||
sessions = []
|
sessions = []
|
||||||
@@ -63,15 +56,17 @@ def find_workspace_uuid_main():
|
|||||||
if s.get('name') != target:
|
if s.get('name') != target:
|
||||||
continue
|
continue
|
||||||
cand = s.get(OWN_KEY.get(agent, ''), None)
|
cand = s.get(OWN_KEY.get(agent, ''), None)
|
||||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
if cand and verify_session_uuid(ws, agent, cand, s, home_dir=home, claude_dir=claude_project_dir, mode="revalidate"):
|
||||||
emit(cand)
|
if cand not in excluded:
|
||||||
|
return cand
|
||||||
|
|
||||||
for s in sessions:
|
for s in sessions:
|
||||||
name = s.get('name', '')
|
name = s.get('name', '')
|
||||||
if name.endswith(f"-creator-{agent}"):
|
if name.endswith(f"-creator-{agent}"):
|
||||||
cand = s.get(OWN_KEY.get(agent, ''))
|
cand = s.get(OWN_KEY.get(agent, ''))
|
||||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
if cand and verify_session_uuid(ws, agent, cand, s, home_dir=home, claude_dir=claude_project_dir, mode="revalidate"):
|
||||||
emit(cand)
|
if cand not in excluded:
|
||||||
|
return cand
|
||||||
|
|
||||||
from lib_py.agents.registry import get_adapter
|
from lib_py.agents.registry import get_adapter
|
||||||
from lib_py.agents.base import DiscoveryContext
|
from lib_py.agents.base import DiscoveryContext
|
||||||
@@ -80,9 +75,25 @@ def find_workspace_uuid_main():
|
|||||||
if adapter:
|
if adapter:
|
||||||
ctx = DiscoveryContext(workspace=ws, agent_name=agent, home_dir=home, claude_dir=claude_project_dir)
|
ctx = DiscoveryContext(workspace=ws, agent_name=agent, home_dir=home, claude_dir=claude_project_dir)
|
||||||
for cand in adapter.discover(ctx):
|
for cand in adapter.discover(ctx):
|
||||||
emit(cand)
|
if cand and cand not in excluded:
|
||||||
|
return cand
|
||||||
|
|
||||||
print('')
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def find_workspace_uuid_main():
|
||||||
|
ws = os.environ['WS_ABS']
|
||||||
|
agent = os.environ['AGENT']
|
||||||
|
home = resolve_home()
|
||||||
|
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
||||||
|
target = os.environ.get('TARGET_SESSION', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
|
||||||
|
except Exception:
|
||||||
|
d = {}
|
||||||
|
|
||||||
|
print(find_workspace_uuid(ws, agent, target, d, home, claude_project_dir))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
find_workspace_uuid_main()
|
find_workspace_uuid_main()
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
"""Issue #63: same-cwd conversation-id resolution in stop/purge/resume and
|
||||||
|
same-type creation ordering for post-discovered agents.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parents[1]
|
||||||
|
SKILLS = REPO / ".agents" / "skills"
|
||||||
|
if str(SKILLS) not in sys.path:
|
||||||
|
sys.path.insert(0, str(SKILLS))
|
||||||
|
|
||||||
|
UA = "a6300001-0000-4000-8000-000000000001"
|
||||||
|
UB = "b6300002-0000-4000-8000-000000000002"
|
||||||
|
UC = "c6300003-0000-4000-8000-000000000003"
|
||||||
|
UX = "d6300004-0000-4000-8000-000000000004"
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures("own_workspace_root")
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- helpers
|
||||||
|
|
||||||
|
def _agy_conv(home: Path, uuid: str, mtime: Optional[float] = None) -> Path:
|
||||||
|
db = Path(home) / ".gemini" / "antigravity-cli" / "conversations" / f"{uuid}.db"
|
||||||
|
db.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
conn = sqlite3.connect(str(db))
|
||||||
|
conn.execute("CREATE TABLE steps (id INTEGER)")
|
||||||
|
conn.execute("INSERT INTO steps VALUES (1)")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
if mtime is not None:
|
||||||
|
os.utime(db, (mtime, mtime))
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def _agy_full_artifacts(home: Path, uuid: str) -> List[Path]:
|
||||||
|
base = Path(home) / ".gemini" / "antigravity-cli"
|
||||||
|
db = _agy_conv(home, uuid)
|
||||||
|
wal = Path(str(db) + "-wal")
|
||||||
|
shm = Path(str(db) + "-shm")
|
||||||
|
wal.write_text("wal")
|
||||||
|
shm.write_text("shm")
|
||||||
|
brain = base / "brain" / uuid
|
||||||
|
brain.mkdir(parents=True, exist_ok=True)
|
||||||
|
(brain / "note.md").write_text("note")
|
||||||
|
ann = base / "annotations" / f"{uuid}.pbtxt"
|
||||||
|
ann.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
ann.write_text("pbtxt")
|
||||||
|
lock = base / "presence" / f"{uuid}.lock"
|
||||||
|
lock.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
lock.write_text("")
|
||||||
|
return [db, wal, shm, brain, ann, lock]
|
||||||
|
|
||||||
|
|
||||||
|
def _agy_cache(home: Path, mapping: dict) -> None:
|
||||||
|
lc = Path(home) / ".gemini" / "antigravity-cli" / "cache" / "last_conversations.json"
|
||||||
|
lc.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
lc.write_text(json.dumps({str(k): v for k, v in mapping.items()}))
|
||||||
|
|
||||||
|
|
||||||
|
def _claude_transcript(claude_dir: Path, ws: Path, uuid: str, mtime: Optional[float] = None) -> Path:
|
||||||
|
from lib_py.verify_session import workspace_key
|
||||||
|
pdir = Path(claude_dir) / workspace_key(str(ws))
|
||||||
|
pdir.mkdir(parents=True, exist_ok=True)
|
||||||
|
f = pdir / f"{uuid}.jsonl"
|
||||||
|
f.write_text(json.dumps({"sessionId": uuid, "cwd": str(ws), "type": "user"}) + "\n")
|
||||||
|
if mtime is not None:
|
||||||
|
os.utime(f, (mtime, mtime))
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def _row(name: str, agent: str, cwd: Path, epoch: int, pid: int = 4242, **extra) -> dict:
|
||||||
|
own = {"agy": "agy_conversation_id_own", "opencode": "opencode_session_id_own",
|
||||||
|
"codex": "codex_session_id_own", "hermes": "hermes_conversation_id_own",
|
||||||
|
"claude": "claude_session_id_own", "grok": "grok_session_id_own"}[agent]
|
||||||
|
row = {"name": name, "status": "running", "role": "creator", "agent": agent,
|
||||||
|
"herdr_session_epoch": epoch, own: None,
|
||||||
|
"session_id_source": "pending-discovery", "session_id_verified": False,
|
||||||
|
"last_visible_status": "unverified",
|
||||||
|
"pane": {"cwd": str(cwd), "pid": pid, "cmd": agent}}
|
||||||
|
row.update(extra)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- 2.1 workspace_uuid
|
||||||
|
|
||||||
|
def test_find_workspace_uuid_skips_exclude_ids(tmp_path):
|
||||||
|
"""rev 2 (B): find_workspace_uuid skips exclude_ids across sibling rows and discover()."""
|
||||||
|
from lib_py.workspace_uuid import find_workspace_uuid
|
||||||
|
|
||||||
|
home = tmp_path / "home"
|
||||||
|
claude_dir = home / ".claude" / "projects"
|
||||||
|
ws = tmp_path / "ws"
|
||||||
|
ws.mkdir(parents=True)
|
||||||
|
now = time.time()
|
||||||
|
_claude_transcript(claude_dir, ws, UA, mtime=now)
|
||||||
|
_claude_transcript(claude_dir, ws, UB, mtime=now - 10)
|
||||||
|
state = {
|
||||||
|
"herdr_sessions": [
|
||||||
|
_row("ws-creator-claude", "claude", ws, int(now) - 100, status="stopped",
|
||||||
|
claude_session_id_own=UA, session_id_source="discovered", session_id_verified=True),
|
||||||
|
_row("ws-reviewer-claude", "claude", ws, int(now) - 100, status="running",
|
||||||
|
claude_session_id_own=None),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
assert find_workspace_uuid(str(ws), "claude", "ws-reviewer-claude", state,
|
||||||
|
str(home), str(claude_dir), exclude_ids=()) == UA
|
||||||
|
assert find_workspace_uuid(str(ws), "claude", "ws-reviewer-claude", state,
|
||||||
|
str(home), str(claude_dir), exclude_ids={UA}) == UB
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_workspace_uuid_uses_explicit_home_and_claude_dir(tmp_path, monkeypatch):
|
||||||
|
"""rev 2 (B-sub): explicit home and claude_dir are forwarded to tier-1 revalidate."""
|
||||||
|
from lib_py.workspace_uuid import find_workspace_uuid
|
||||||
|
|
||||||
|
monkeypatch.setenv("HOME", str(tmp_path / "wrong-home"))
|
||||||
|
monkeypatch.delenv("HOME_DIR", raising=False)
|
||||||
|
monkeypatch.delenv("CLAUDE_PROJECT_DIR", raising=False)
|
||||||
|
|
||||||
|
home = tmp_path / "custom-home"
|
||||||
|
claude_dir = tmp_path / "custom-claude"
|
||||||
|
ws = tmp_path / "ws"
|
||||||
|
ws.mkdir(parents=True)
|
||||||
|
_agy_conv(home, UA)
|
||||||
|
_claude_transcript(claude_dir, ws, UB)
|
||||||
|
state = {
|
||||||
|
"herdr_sessions": [
|
||||||
|
_row("ws-creator-agy", "agy", ws, 100, status="stopped",
|
||||||
|
agy_conversation_id_own=UA, session_id_source="discovered", session_id_verified=True),
|
||||||
|
_row("ws-creator-claude", "claude", ws, 100, status="stopped",
|
||||||
|
claude_session_id_own=UB, session_id_source="discovered", session_id_verified=True),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
assert find_workspace_uuid(str(ws), "agy", "ws-creator-agy", state,
|
||||||
|
home=str(home), claude_dir=str(claude_dir)) == UA
|
||||||
|
assert find_workspace_uuid(str(ws), "claude", "ws-creator-claude", state,
|
||||||
|
home=str(home), claude_dir=str(claude_dir)) == UB
|
||||||
Reference in new issue
Block a user