fix(session_ids,id_order): optional --session resolution, fresh-marker retention, and canonical cwd matching (Issue #63)
This commit is contained in:
1 parent
8c9a68cf1c
commit
c747374aa6
3 files changed
+273
-23
No files matched your search
@@ -22,9 +22,8 @@ from lib_py import ints
|
||||
from lib_py.agents.autopin import claimed_elsewhere
|
||||
from lib_py.agents.base import POST_DISCOVERED
|
||||
from lib_py.agents.registry import agent_of_row, get_adapter
|
||||
from lib_py.agents.session_ids import claiming_rows, own_id
|
||||
from lib_py.agents.session_ids import canonical_cwd, claiming_rows, own_id
|
||||
from lib_py.state import load_state_json
|
||||
from lib_py.verify_session import workspace_key
|
||||
|
||||
DEFAULT_WAIT_S = 600
|
||||
|
||||
@@ -54,7 +53,7 @@ def pid_alive(pid: int) -> bool:
|
||||
def order_dir(cwd: str, agent: str, env: Mapping[str, str] = os.environ,
|
||||
home: Optional[str] = None) -> str:
|
||||
h = home or env.get("HOME") or env.get("HOME_DIR") or os.path.expanduser("~")
|
||||
real_cwd = os.path.realpath(cwd or ".")
|
||||
real_cwd = canonical_cwd(cwd or ".")
|
||||
digest = hashlib.sha256(real_cwd.encode("utf-8")).hexdigest()[:16]
|
||||
return os.path.join(h, ".local", "state", "mam", "id-order", f"{digest}-{agent}")
|
||||
|
||||
@@ -71,7 +70,7 @@ def _parse_pos_int(val: Any) -> Optional[int]:
|
||||
def blockers(state: Dict[str, Any], cwd: str, agent: str, me: str,
|
||||
markers: List[Dict[str, Any]],
|
||||
is_alive: Callable[[int], bool] = pid_alive) -> List[str]:
|
||||
target_key = workspace_key(cwd)
|
||||
target_cwd = canonical_cwd(cwd)
|
||||
found: List[str] = []
|
||||
rows_by_name: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
@@ -84,7 +83,7 @@ def blockers(state: Dict[str, Any], cwd: str, agent: str, me: str,
|
||||
if not rname or rname == me:
|
||||
continue
|
||||
s_cwd = (s.get("pane") or {}).get("cwd") or ""
|
||||
if not s_cwd or workspace_key(s_cwd) != target_key:
|
||||
if not s_cwd or canonical_cwd(s_cwd) != target_cwd:
|
||||
continue
|
||||
if agent_of_row(s) != agent or s.get("status") != "running":
|
||||
continue
|
||||
@@ -101,17 +100,40 @@ def blockers(state: Dict[str, Any], cwd: str, agent: str, me: str,
|
||||
m_sess = str(m.get("session") or "")
|
||||
if not m_sess or m_sess == me or m_sess in found:
|
||||
continue
|
||||
m_cwd = str(m.get("cwd") or "")
|
||||
if m_cwd and canonical_cwd(m_cwd) != target_cwd:
|
||||
continue
|
||||
m_agent = str(m.get("agent") or "")
|
||||
if m_agent and m_agent != agent:
|
||||
continue
|
||||
owner_pid = _parse_pos_int(m.get("owner_pid"))
|
||||
if owner_pid is None or not is_alive(owner_pid):
|
||||
continue
|
||||
row = rows_by_name.get(m_sess)
|
||||
if row is None:
|
||||
found.append(m_sess)
|
||||
elif not own_id(row, agent):
|
||||
status = row.get("status")
|
||||
end_epoch = int(row.get("stopped_at_epoch") or row.get("terminated_at_epoch") or 0)
|
||||
m_at = int(m.get("at") or 0)
|
||||
if status == "running" or end_epoch <= m_at:
|
||||
continue
|
||||
status = str(row.get("status") or "")
|
||||
cur_own = own_id(row, agent)
|
||||
row_epoch = int(row.get("herdr_session_epoch") or 0)
|
||||
end_epoch = int(row.get("stopped_at_epoch") or row.get("terminated_at_epoch") or 0)
|
||||
m_at = int(m.get("at") or 0)
|
||||
prev_own = str(m.get("prev_own_id") or "")
|
||||
prev_epoch = int(m.get("prev_epoch") or 0)
|
||||
prev_end_epoch = int(m.get("prev_end_epoch") or 0)
|
||||
|
||||
if status == "running":
|
||||
pinned_for_attempt = bool(cur_own) and (
|
||||
cur_own != prev_own or row_epoch > prev_epoch
|
||||
)
|
||||
if not pinned_for_attempt:
|
||||
found.append(m_sess)
|
||||
else:
|
||||
ended_after_attempt = (
|
||||
end_epoch > max(m_at, prev_end_epoch)
|
||||
and (prev_epoch == 0 or row_epoch > prev_epoch)
|
||||
)
|
||||
if not ended_after_attempt:
|
||||
found.append(m_sess)
|
||||
|
||||
return found
|
||||
@@ -151,18 +173,26 @@ def _read_and_prune_markers(dpath: str, me: str,
|
||||
return out
|
||||
|
||||
|
||||
def write_marker(dpath: str, session: str, owner_pid: int, cwd: str, agent: str) -> str:
|
||||
def write_marker(dpath: str, session: str, owner_pid: int, cwd: str, agent: str,
|
||||
prev_row: Optional[Dict[str, Any]] = None) -> str:
|
||||
os.makedirs(dpath, exist_ok=True)
|
||||
marker_path = os.path.join(dpath, f"{session}.json")
|
||||
payload: Dict[str, Any] = {
|
||||
"session": session,
|
||||
"owner_pid": int(owner_pid),
|
||||
"cwd": canonical_cwd(cwd),
|
||||
"agent": agent,
|
||||
"at": int(time.time()),
|
||||
}
|
||||
if isinstance(prev_row, dict):
|
||||
payload["prev_own_id"] = own_id(prev_row, agent)
|
||||
payload["prev_epoch"] = int(prev_row.get("herdr_session_epoch") or 0)
|
||||
payload["prev_end_epoch"] = int(
|
||||
prev_row.get("stopped_at_epoch") or prev_row.get("terminated_at_epoch") or 0
|
||||
)
|
||||
fd, tmp = tempfile.mkstemp(dir=dpath, prefix=".marker.", suffix=".tmp")
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"session": session,
|
||||
"owner_pid": int(owner_pid),
|
||||
"cwd": os.path.realpath(cwd),
|
||||
"agent": agent,
|
||||
"at": int(time.time()),
|
||||
}, f)
|
||||
json.dump(payload, f)
|
||||
os.replace(tmp, marker_path)
|
||||
return marker_path
|
||||
|
||||
@@ -202,7 +232,14 @@ def wait(cwd: str, agent: str, session: str, owner_pid: int,
|
||||
blks = blockers(state, cwd, agent, session, markers, is_alive=is_alive)
|
||||
if not blks:
|
||||
if not uuid and owner_pid and owner_pid > 0:
|
||||
write_marker(dpath, session, owner_pid, cwd, agent)
|
||||
prev_row = next(
|
||||
(
|
||||
s for s in (state.get("herdr_sessions") or [])
|
||||
if isinstance(s, dict) and s.get("name") == session
|
||||
),
|
||||
None,
|
||||
)
|
||||
write_marker(dpath, session, owner_pid, cwd, agent, prev_row=prev_row)
|
||||
return 0
|
||||
|
||||
names = ", ".join(blks)
|
||||
|
||||
@@ -21,10 +21,19 @@ from lib_py.agents.base import DiscoveryContext, POST_DISCOVERED
|
||||
from lib_py.agents.registry import agent_of_row, get_adapter
|
||||
from lib_py.paths import resolve_home
|
||||
from lib_py.state import load_state_json
|
||||
from lib_py.verify_session import verify_session_uuid, workspace_key
|
||||
from lib_py.verify_session import verify_session_uuid
|
||||
from lib_py.workspace_uuid import find_workspace_uuid
|
||||
|
||||
|
||||
def canonical_cwd(path: str) -> str:
|
||||
if not path:
|
||||
return ""
|
||||
try:
|
||||
return os.path.realpath(path)
|
||||
except Exception:
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def own_id(row: Optional[Dict[str, Any]], agent: str) -> str:
|
||||
adapter = get_adapter(agent)
|
||||
if not adapter or not isinstance(row, dict):
|
||||
@@ -51,13 +60,13 @@ def claiming_rows(row: Dict[str, Any], uuid: str, state: Dict[str, Any]) -> List
|
||||
def same_type_rows(state: Dict[str, Any], cwd: str, agent: str) -> List[Dict[str, Any]]:
|
||||
if not cwd or not agent:
|
||||
return []
|
||||
target_key = workspace_key(cwd)
|
||||
target_cwd = canonical_cwd(cwd)
|
||||
out: List[Dict[str, Any]] = []
|
||||
for s in state.get("herdr_sessions") or []:
|
||||
if not isinstance(s, dict):
|
||||
continue
|
||||
s_cwd = (s.get("pane") or {}).get("cwd") or ""
|
||||
if s_cwd and workspace_key(s_cwd) == target_key and agent_of_row(s) == agent:
|
||||
if s_cwd and canonical_cwd(s_cwd) == target_cwd and agent_of_row(s) == agent:
|
||||
out.append(s)
|
||||
return out
|
||||
|
||||
@@ -195,9 +204,22 @@ def decide_resume(state: Dict[str, Any], name: str, agent: str, workspace: str,
|
||||
home_resolved = resolve_home(home)
|
||||
claude_resolved = claude_dir or os.environ.get("CLAUDE_PROJECT_DIR") or f"{home_resolved}/.claude/projects"
|
||||
row = _find_row(state, name)
|
||||
adapter = get_adapter(agent)
|
||||
if row is None and not name and workspace:
|
||||
same = same_type_rows(state, workspace, agent)
|
||||
if len(same) == 1:
|
||||
row = same[0]
|
||||
name = str(row.get("name") or "")
|
||||
elif adapter is None or adapter.id_lifecycle != POST_DISCOVERED:
|
||||
creator = next(
|
||||
(s for s in same if str(s.get("name") or "").endswith(f"-creator-{agent}")),
|
||||
None,
|
||||
)
|
||||
if creator is not None:
|
||||
row = creator
|
||||
name = str(row.get("name") or "")
|
||||
row_ref = row if row is not None else ({"name": name} if name else {})
|
||||
claimed = set(claimed_elsewhere(row_ref, state))
|
||||
adapter = get_adapter(agent)
|
||||
own = own_id(row, agent)
|
||||
|
||||
if own:
|
||||
|
||||
Reference in new issue
Block a user