feat(agents): session_ids - the stop/purge/resume id decision (Issue #63)
This commit is contained in:
1 parent
ead490bddc
commit
b8a30d5b93
3 files changed
+445
No files matched your search
@@ -145,3 +145,106 @@ def test_find_workspace_uuid_uses_explicit_home_and_claude_dir(tmp_path, monkeyp
|
||||
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
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- 2.2 session_ids unit tests
|
||||
|
||||
@pytest.fixture
|
||||
def owners(monkeypatch):
|
||||
"""agy presence ownership without lsof/ps: {uuid: [holder pids]} and a
|
||||
pid -> ppid table the test fills in."""
|
||||
from lib_py.agents.adapters import agy
|
||||
held, parents = {}, {}
|
||||
|
||||
def holders(paths):
|
||||
if held.get("__lsof__") is None and "__lsof__" in held:
|
||||
return None
|
||||
return sorted({p for path in paths for u, pids in held.items() if u in path for p in pids})
|
||||
|
||||
monkeypatch.setattr(agy, "_holder_pids", holders)
|
||||
monkeypatch.setattr(agy, "_process_parents", lambda: dict(parents) or None)
|
||||
return held, parents
|
||||
|
||||
|
||||
def test_session_ids_decide_stop_and_resume_unit(tmp_path, owners):
|
||||
from lib_py.agents.session_ids import decide_resume, decide_stop
|
||||
|
||||
home = tmp_path / "home"
|
||||
ws = tmp_path / "ws"
|
||||
ws.mkdir(parents=True)
|
||||
now = time.time()
|
||||
epoch = int(now) - 100
|
||||
_agy_conv(home, UA, mtime=now - 5)
|
||||
_agy_conv(home, UB, mtime=now - 2)
|
||||
_agy_cache(home, {ws: UA})
|
||||
held, parents = owners
|
||||
held[UA] = [5001]
|
||||
held[UB] = [6001]
|
||||
parents.update({5001: 1, 6001: 1})
|
||||
|
||||
state = {
|
||||
"herdr_sessions": [
|
||||
_row("ws-creator-agy", "agy", ws, epoch, pid=5001),
|
||||
_row("ws-reviewer-agy", "agy", ws, epoch, pid=6001),
|
||||
]
|
||||
}
|
||||
res_a = decide_stop(state, "ws-creator-agy", "agy", str(ws), True, pane_pid=5001, purge=False, home=str(home))
|
||||
assert res_a["captured"] == UA and res_a["status"] == "captured"
|
||||
|
||||
res_b = decide_stop(state, "ws-reviewer-agy", "agy", str(ws), True, pane_pid=6001, purge=False, home=str(home))
|
||||
assert res_b["captured"] == "" and res_b["status"] == "owner-mismatch"
|
||||
|
||||
res_dead = decide_stop(state, "ws-reviewer-agy", "agy", str(ws), False, pane_pid=None, purge=True, home=str(home))
|
||||
assert res_dead["captured"] == "" and res_dead["purge_uuid"] == ""
|
||||
assert "herdr session was already gone" in res_dead["note"]
|
||||
|
||||
# Two same-type rows in one cwd and B has no own id -> fresh spawn with NOTE
|
||||
state["herdr_sessions"][0]["status"] = "stopped"
|
||||
state["herdr_sessions"][0]["agy_conversation_id_own"] = UA
|
||||
state["herdr_sessions"][1]["status"] = "stopped"
|
||||
r_b = decide_resume(state, "ws-reviewer-agy", "agy", str(ws), herdr_alive=False, home=str(home))
|
||||
assert r_b["uuid"] == "" and r_b["error"] is None and not r_b["drop_stale_id"]
|
||||
assert any("2 agy sessions share" in n for n in r_b["notes"])
|
||||
|
||||
|
||||
def test_session_ids_own_id_priority_rules_unit(tmp_path):
|
||||
"""rev 2 (A): own id never falls through to tier-2; broken registry refuses even when DB is missing."""
|
||||
from lib_py.agents.session_ids import decide_resume, decide_stop
|
||||
|
||||
home = tmp_path / "home"
|
||||
ws = tmp_path / "ws"
|
||||
ws.mkdir(parents=True)
|
||||
epoch = int(time.time()) - 100
|
||||
# Missing UA.db on disk, but both rows claim UA
|
||||
state_dup = {
|
||||
"herdr_sessions": [
|
||||
_row("ws-creator-agy", "agy", ws, epoch, status="stopped", agy_conversation_id_own=UA,
|
||||
session_id_source="discovered", session_id_verified=True),
|
||||
_row("ws-reviewer-agy", "agy", ws, epoch, status="stopped", agy_conversation_id_own=UA,
|
||||
session_id_source="discovered", session_id_verified=True),
|
||||
]
|
||||
}
|
||||
r_dup = decide_resume(state_dup, "ws-reviewer-agy", "agy", str(ws), herdr_alive=False, home=str(home))
|
||||
assert r_dup["error"] and "ws-creator-agy" in r_dup["error"]
|
||||
assert r_dup["notes"] == []
|
||||
|
||||
s_dup = decide_stop(state_dup, "ws-reviewer-agy", "agy", str(ws), False, purge=True, home=str(home))
|
||||
assert s_dup["captured"] == "" and s_dup["purge_uuid"] == ""
|
||||
assert "also recorded by ws-creator-agy" in s_dup["note"]
|
||||
|
||||
# Single row with stale own id UX (missing UX.db) while cache has UC
|
||||
_agy_conv(home, UC)
|
||||
_agy_cache(home, {ws: UC})
|
||||
state_stale = {
|
||||
"herdr_sessions": [
|
||||
_row("ws-creator-agy", "agy", ws, epoch, status="stopped", agy_conversation_id_own=UX,
|
||||
session_id_source="discovered", session_id_verified=True),
|
||||
]
|
||||
}
|
||||
r_stale = decide_resume(state_stale, "ws-creator-agy", "agy", str(ws), herdr_alive=False, home=str(home))
|
||||
assert r_stale["uuid"] == "" and r_stale["drop_stale_id"] is True and r_stale["error"] is None
|
||||
assert any(f"recorded agy id {UX} no longer verifies" in n for n in r_stale["notes"])
|
||||
|
||||
s_stale = decide_stop(state_stale, "ws-creator-agy", "agy", str(ws), False, purge=False, home=str(home))
|
||||
assert s_stale["captured"] == "" and "no longer verifies; kept as is" in s_stale["note"]
|
||||
|
||||
Reference in new issue
Block a user