feat(create/monitor): implement automatic UUID materialization, auto-pinning, and drift-C2 resolution (13/13 PASS)
This commit is contained in:
+123
-33
@@ -796,6 +796,52 @@ derive_workspace_slug() {
|
||||
printf 'mam-%s' "$slug"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mam_gen_uuid
|
||||
# ---------------------------------------------------------------------------
|
||||
mam_gen_uuid() {
|
||||
if command -v uuidgen >/dev/null 2>&1; then
|
||||
uuidgen | tr 'A-Z' 'a-z'
|
||||
else
|
||||
python3 -c 'import uuid; print(uuid.uuid4())'
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mam_abs_workspace <path>
|
||||
# ---------------------------------------------------------------------------
|
||||
mam_abs_workspace() {
|
||||
local p="${1:-}"
|
||||
( cd -P "$p" 2>/dev/null && pwd -P ) || printf '%s' "$p"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mam_workspace_key <workspace>
|
||||
# ---------------------------------------------------------------------------
|
||||
mam_workspace_key() {
|
||||
printf '%s' "$(mam_abs_workspace "$1")" | tr '/_' '--'
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mam_session_iso_root <session_name>
|
||||
# ---------------------------------------------------------------------------
|
||||
mam_session_iso_root() {
|
||||
MAM_STATE_JSON="$(load_state_json)" MAM_ISO_SESSION="$1" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
|
||||
import json, os
|
||||
name = os.environ.get('MAM_ISO_SESSION', '')
|
||||
try:
|
||||
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
|
||||
except Exception:
|
||||
d = {}
|
||||
for s in (d.get('herdr_sessions') or []):
|
||||
if s.get('name') == name:
|
||||
iso = s.get('isolation')
|
||||
if isinstance(iso, dict) and iso.get('root'):
|
||||
print(iso['root'])
|
||||
break
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# derive_session_name <workspace> <agent>
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1126,13 +1172,21 @@ PYEOF
|
||||
# ---------------------------------------------------------------------------
|
||||
VERIFY_SESSION_PYTHON='
|
||||
def workspace_key(path):
|
||||
return path.replace("/", "-").replace("_", "-")
|
||||
import os
|
||||
try:
|
||||
p = os.path.realpath(path)
|
||||
except Exception:
|
||||
p = path
|
||||
return p.replace("/", "-").replace("_", "-")
|
||||
|
||||
def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=None, mode="discover"):
|
||||
import os, json, sqlite3
|
||||
home = home_dir or os.environ.get("HOME_DIR", "")
|
||||
c_dir = claude_dir or os.environ.get("CLAUDE_PROJECT_DIR", f"{home}/.claude/projects")
|
||||
row = row or {}
|
||||
_iso = row.get("isolation")
|
||||
iso_root = _iso.get("root") if isinstance(_iso, dict) and _iso.get("root") else None
|
||||
|
||||
# The floor answers "could this transcript belong to a PREVIOUS incarnation
|
||||
# of this session?", which only matters while picking an unknown uuid off
|
||||
# disk. In "revalidate" the uuid is one this row already recorded, and a
|
||||
@@ -1142,11 +1196,20 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
|
||||
epoch = row.get("herdr_session_epoch", 0) if mode == "discover" else 0
|
||||
cwd = row.get("pane", {}).get("cwd", "") or ws
|
||||
|
||||
# ORDERING INVARIANT: the workspace check below MUST run before the
|
||||
# assigned-id shortcut. Moving the shortcut above it would return True for a
|
||||
# row belonging to a different workspace purely because it is assigned and
|
||||
# unverified, breaking the one guarantee find_workspace_uuid exists to give
|
||||
# -- never hand back an id that belongs to a different workspace. (T-12)
|
||||
if workspace_key(cwd) != workspace_key(ws):
|
||||
return False
|
||||
|
||||
|
||||
if (mode == "revalidate" and row.get("session_id_source") == "assigned"
|
||||
and not row.get("session_id_verified")):
|
||||
return True
|
||||
|
||||
if agent == "claude":
|
||||
base = c_dir
|
||||
base = (iso_root + "/projects") if iso_root else c_dir
|
||||
key = workspace_key(ws)
|
||||
path = f"{base}/{key}/{uuid}.jsonl"
|
||||
if not os.path.exists(path):
|
||||
@@ -1154,27 +1217,41 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
|
||||
if epoch and os.path.getmtime(path) < epoch:
|
||||
return False
|
||||
try:
|
||||
valid_session = False
|
||||
found_cwd = None
|
||||
with open(path) as f:
|
||||
first = f.readline().strip()
|
||||
if not first:
|
||||
for _ in range(50):
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
payload = json.loads(line)
|
||||
if payload.get("sessionId") == uuid:
|
||||
valid_session = True
|
||||
if payload.get("cwd"):
|
||||
found_cwd = payload.get("cwd")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not valid_session:
|
||||
return False
|
||||
payload = json.loads(first)
|
||||
if payload.get("sessionId") != uuid:
|
||||
return False
|
||||
if payload.get("cwd") and payload.get("cwd") != cwd:
|
||||
if found_cwd and found_cwd != cwd:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
elif agent == "agy":
|
||||
base = f"{home}/.gemini/antigravity-cli/conversations"
|
||||
base = f"{iso_root or home}/.gemini/antigravity-cli/conversations"
|
||||
path = f"{base}/{uuid}.db"
|
||||
if not os.path.exists(path):
|
||||
return False
|
||||
if epoch and os.path.getmtime(path) < epoch:
|
||||
return False
|
||||
if mode == "discover":
|
||||
lc = f"{home}/.gemini/antigravity-cli/cache/last_conversations.json"
|
||||
lc = f"{iso_root or home}/.gemini/antigravity-cli/cache/last_conversations.json"
|
||||
cache_match = False
|
||||
if os.path.exists(lc):
|
||||
try:
|
||||
@@ -1196,7 +1273,7 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
|
||||
return False
|
||||
|
||||
elif agent == "hermes":
|
||||
hdb = f"{home}/.hermes/state.db"
|
||||
hdb = f"{iso_root or home}/.hermes/state.db"
|
||||
if not os.path.exists(hdb):
|
||||
return False
|
||||
if epoch and os.path.getmtime(hdb) < epoch:
|
||||
@@ -1211,7 +1288,7 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
|
||||
return False
|
||||
|
||||
elif agent == "cline":
|
||||
base = f"{home}/.cline/data/sessions"
|
||||
base = (iso_root + "/sessions") if iso_root else f"{home}/.cline/data/sessions"
|
||||
path = f"{base}/{uuid}/{uuid}.json"
|
||||
if not os.path.exists(path):
|
||||
return False
|
||||
@@ -1260,7 +1337,7 @@ PYEOF
|
||||
# 2 = check not possible (capture failed, session gone)
|
||||
verify_tui_viewport() {
|
||||
local session_name="$1" agent="$2" workspace="$3"
|
||||
local abs; abs="$(cd "$workspace" 2>/dev/null && pwd)" || abs="$workspace"
|
||||
local abs; abs="$(mam_abs_workspace "$workspace")"
|
||||
local base; base="$(basename "$abs")"
|
||||
|
||||
if ! _herdr has-session -t "$session_name" >/dev/null 2>&1; then
|
||||
@@ -1273,23 +1350,16 @@ verify_tui_viewport() {
|
||||
return 2
|
||||
fi
|
||||
|
||||
case "$agent" in
|
||||
claude)
|
||||
if printf '%s\n' "$pane_content" | grep -q "$base"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
;;
|
||||
agy|hermes|cline)
|
||||
if printf '%s\n' "$pane_content" | grep -q "$base"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
local flat base_flat
|
||||
flat=$(printf '%s' "$pane_content" | tr -d '[:space:]')
|
||||
base_flat=$(printf '%s' "$base" | tr -d '[:space:]')
|
||||
if printf '%s' "$flat" | grep -Fq "$base_flat"; then
|
||||
return 0
|
||||
fi
|
||||
if printf '%s\n' "$pane_content" | grep -Eq '(^|[^[:alnum:]])/[A-Za-z0-9._-]+/[A-Za-z0-9._/-]+'; then
|
||||
return 1
|
||||
fi
|
||||
return 2
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1308,7 +1378,7 @@ verify_tui_viewport() {
|
||||
# ---------------------------------------------------------------------------
|
||||
find_workspace_uuid() {
|
||||
local workspace="$1" agent="$2" session_name="${3:-}"
|
||||
local abs; abs="$(cd "$workspace" 2>/dev/null && pwd)" || abs="$workspace"
|
||||
local abs; abs="$(mam_abs_workspace "$workspace")"
|
||||
MAM_STATE_JSON="$(load_state_json)" WS_ABS="$abs" AGENT="$agent" TARGET_SESSION="$session_name" MAM_VERIFY_PY="$VERIFY_SESSION_PYTHON" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
|
||||
import os, sys, json, glob, sqlite3
|
||||
|
||||
@@ -1791,7 +1861,27 @@ _sks_herdr() {
|
||||
mam_herdr "$@"
|
||||
}
|
||||
|
||||
_pane_capture() { _sks_herdr capture-pane -p -t "$1" 2>/dev/null || echo ""; }
|
||||
_pane_capture() {
|
||||
local raw
|
||||
raw="$(_sks_herdr capture-pane -p -t "$1" 2>/dev/null || echo "")"
|
||||
if [ -z "$raw" ]; then
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
python3 -c '
|
||||
import sys, json
|
||||
raw = sys.stdin.read()
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
text = data.get("result", {}).get("read", {}).get("text")
|
||||
if text is not None:
|
||||
print(text, end="")
|
||||
else:
|
||||
print(raw, end="")
|
||||
except Exception:
|
||||
print(raw, end="")
|
||||
' <<< "$raw"
|
||||
}
|
||||
|
||||
# Bottom-N *content* lines: capture-pane -p pads the viewport with trailing
|
||||
# blank rows; window on non-blank lines or top-anchored dialogs are invisible.
|
||||
|
||||
@@ -233,10 +233,10 @@ The script handles the YAML append, pane capture, and the `last_visible_status`
|
||||
## Pitfalls
|
||||
|
||||
- **Don't use `nohup`/`disown`/`setsid` for the agent itself** — those background the agent outside herdr. The whole point of this skill is *the herdr session is the supervisor*. `nohup` is OK only for *launching the wrapper* (which itself creates the herdr session via `herdr new-session -d`).
|
||||
- **Don't trust `--session-id <uuid>` flags blindly** — claude/agy may not accept a fixed session id on first spawn. The session id is *assigned* on first user message; you can read it back from `~/.claude/projects/.../session.jsonl` headers or `~/.gemini/.../cache/last_conversations.json` AFTER the first message.
|
||||
- **Claude Session ID Auto-Assignment**: Fresh `claude` sessions automatically generate a new UUID (`mam_gen_uuid`) passed via `claude --session-id <uuid>`. `claude_session_id_own` is stored in `.mam/agent-sessions.yaml` with `session_id_source: assigned` and `session_id_verified: false`.
|
||||
- **First Message Materialization**: The transcript file `.jsonl` is created on disk when the first user prompt is sent. The monitor loop (`reconcile.sh`) verifies the transcript and promotes `session_id_verified: true` and `last_visible_status: pinned`.
|
||||
- **Wrapper script MUST NOT be created via `hermes profile alias`** — that command writes a `hermes -p <profile>` wrapper that destroys the herdr behavior. Create wrappers manually (see `lab-landing-page-creator-claude` template).
|
||||
- **Always use the workspace-relative path** in herdr `cwd` — relative paths break when herdr respawns in a different shell context.
|
||||
- **The first `claude` message generates the session id** — `multi-agent-mux-create` only sets up the *container*. If you need a known session id for later resume, send a placeholder message (e.g. "init") and read it back, then call `multi-agent-mux-resume` later.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -148,8 +148,13 @@ if [ "$(uname)" = "Darwin" ] && [ -f "$RESOLVED_BIN" ]; then
|
||||
xattr -d com.apple.quarantine "$RESOLVED_BIN" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
SESSION_UUID=""
|
||||
if [ "$AGENT" = "claude" ]; then
|
||||
SESSION_UUID="$(mam_gen_uuid)"
|
||||
fi
|
||||
|
||||
case "$AGENT" in
|
||||
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
|
||||
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --session-id ${SESSION_UUID}" ;;
|
||||
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
|
||||
hermes) CMD_FULL="${RESOLVED_BIN}" ;;
|
||||
cline) CMD_FULL="${RESOLVED_BIN} -i" ;;
|
||||
@@ -162,6 +167,7 @@ spawn() {
|
||||
case "$AGENT" in
|
||||
claude)
|
||||
if { [ -x "$WRAPPER" ] && [ "$(basename "$WRAPPER")" != "claude" ]; } || [ "$USE_WRAPPER" = "1" ]; then
|
||||
SESSION_UUID=""
|
||||
nohup "$WRAPPER" >/dev/null 2>&1 &
|
||||
disown
|
||||
else
|
||||
@@ -262,6 +268,7 @@ atomic_dump_yaml "$AGENT_SESSIONS_YAML" \
|
||||
HERDR_EPOCH="$HERDR_EPOCH" PANE_PID="$PANE_PID" PANE_CWD="$PANE_CWD" \
|
||||
CMD_FULL="$CMD_FULL" START_CMD="$START_CMD" CHILD_PID="$CHILD_PID" \
|
||||
HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-default}" \
|
||||
SESSION_UUID="$SESSION_UUID" \
|
||||
DELEGATE_JOB_ID="$DELEGATE_JOB_ID" ROLE="$ROLE" <<'PYEOF'
|
||||
name = os.environ['SESSION_NAME']
|
||||
agent = os.environ['AGENT']
|
||||
@@ -302,8 +309,6 @@ entry = {
|
||||
'kill_command': f'HERDR_SESSION_NAME={server_name} herdr kill-session -t {name}',
|
||||
}
|
||||
|
||||
|
||||
|
||||
if agent == 'claude':
|
||||
entry['tui'] = {
|
||||
'model': '(unknown — capture after first message)',
|
||||
@@ -312,8 +317,11 @@ if agent == 'claude':
|
||||
'account': '(unknown — read from claude auth status)',
|
||||
'version': '(unknown — read from TUI)',
|
||||
}
|
||||
entry['claude_session_id_own'] = None
|
||||
entry['last_visible_status'] = "unverified"
|
||||
assigned = os.environ.get('SESSION_UUID', '') or None
|
||||
entry['claude_session_id_own'] = assigned
|
||||
entry['session_id_source'] = 'assigned' if assigned else 'pending-discovery'
|
||||
entry['session_id_verified'] = False
|
||||
entry['last_visible_status'] = "assigned (awaiting first message)" if assigned else "unverified"
|
||||
elif agent == 'agy':
|
||||
cp = os.environ.get('CHILD_PID', '0')
|
||||
entry['child_pid'] = int(cp) if cp.isdigit() else 0
|
||||
|
||||
@@ -124,15 +124,18 @@ YAML: no such session
|
||||
→ report: "lab-paper-pdf2md-creator-agy: herdr found but not in YAML. Auto-registered."
|
||||
```
|
||||
|
||||
### C. New session id materializes (claude first message sent)
|
||||
### C. Session ID Discovery & Confirmation
|
||||
|
||||
```
|
||||
YAML: claude_session_id_own=null (placeholder)
|
||||
disk: ~/.claude/projects/.../b3a7...c2f.jsonl exists, mtime=now,
|
||||
first line sessionId=b3a7...c2f
|
||||
→ update claude_session_id_own=b3a7...c2f
|
||||
→ report: "lab-landing-page-creator-claude: session id materialized b3a7...c2f"
|
||||
```
|
||||
- **C0. Assigned ID Confirmation**: For sessions created with an auto-assigned UUID (`session_id_source: assigned`, `session_id_verified: false`), the monitor verifies that the transcript file `.jsonl` has materialized on disk. Once verified, it promotes `session_id_verified: true` and updates `last_visible_status: pinned`.
|
||||
- **C. New session id materializes (unassigned/legacy)**:
|
||||
```
|
||||
YAML: claude_session_id_own=null (placeholder)
|
||||
disk: ~/.claude/projects/.../b3a7...c2f.jsonl exists, mtime=now,
|
||||
first line sessionId=b3a7...c2f
|
||||
→ update claude_session_id_own=b3a7...c2f
|
||||
→ report: "lab-landing-page-creator-claude: session id materialized b3a7...c2f"
|
||||
```
|
||||
- **C-ambiguous. Multiple candidates detected**: If multiple candidate transcripts match an unassigned session, the monitor avoids random pinning, reports `C-ambiguous`, and sets `last_visible_status: "ambiguous: N candidates"`.
|
||||
|
||||
### D. Stale UUID (artifact gone)
|
||||
|
||||
|
||||
@@ -565,9 +565,50 @@ if herdr_confirmed:
|
||||
'msg': f"{name}: herdr found but not in YAML. Auto-registered (pane {pm['pid']}, cmd {pm['cmd']}, cwd {pm['cwd']})."})
|
||||
actions.append(f"registered: {name}")
|
||||
|
||||
def row_agent(s):
|
||||
cmd = ((s.get('pane') or {}).get('cmd') or '').strip()
|
||||
if cmd in ('claude', 'agy', 'hermes', 'cline'):
|
||||
return cmd
|
||||
full = ((s.get('pane') or {}).get('cmd_full') or '')
|
||||
for a in ('claude', 'agy', 'hermes', 'cline'):
|
||||
if a in full:
|
||||
return a
|
||||
name = s.get('name', '')
|
||||
for a in ('claude', 'agy', 'hermes', 'cline'):
|
||||
if name.endswith('-creator-' + a):
|
||||
return a
|
||||
return None
|
||||
|
||||
OWN_KEY_BY_AGENT = {
|
||||
'claude': 'claude_session_id_own',
|
||||
'agy': 'agy_conversation_id_own',
|
||||
'hermes': 'hermes_conversation_id_own',
|
||||
'cline': 'cline_conversation_id_own'
|
||||
}
|
||||
|
||||
# === drift C0: 지정된 ID 는 발견이 아니라 '확인'만 필요하다 ===
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if s.get('status') != 'running':
|
||||
continue
|
||||
if s.get('session_id_source') != 'assigned' or s.get('session_id_verified'):
|
||||
continue
|
||||
agent = row_agent(s)
|
||||
if not agent:
|
||||
continue
|
||||
uuid = s.get(OWN_KEY_BY_AGENT[agent])
|
||||
cwd = (s.get('pane') or {}).get('cwd', '')
|
||||
if not uuid or not cwd:
|
||||
continue
|
||||
if verify_session_uuid(cwd, agent, uuid, s, mode="discover"):
|
||||
s['session_id_verified'] = True
|
||||
s['last_visible_status'] = 'pinned'
|
||||
drifts.append({'class': 'C', 'name': s['name'],
|
||||
'msg': f"{s['name']}: assigned session id confirmed on disk: {uuid}"})
|
||||
actions.append(f"confirmed session id: {uuid}")
|
||||
|
||||
# === drift C: claude 새 session id materialize (per-row own id) ===
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if not s.get('name', '').endswith('-creator-claude'):
|
||||
if row_agent(s) != 'claude':
|
||||
continue
|
||||
if s.get('status') != 'running':
|
||||
continue
|
||||
@@ -588,6 +629,11 @@ for s in d.get('herdr_sessions', []):
|
||||
if verify_session_uuid(cwd, 'claude', uuid, s, mode="discover"):
|
||||
valid_candidates.append(uuid)
|
||||
|
||||
if len(valid_candidates) > 1:
|
||||
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
|
||||
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
|
||||
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
|
||||
actions.append(f"ambiguous candidates: {s['name']}")
|
||||
if len(valid_candidates) == 1:
|
||||
uuid = valid_candidates[0]
|
||||
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "claude" "{cwd}"']
|
||||
@@ -601,7 +647,7 @@ for s in d.get('herdr_sessions', []):
|
||||
|
||||
# === drift C (agy): agy 새 session id materialize (per-row own id) ===
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if not s.get('name', '').endswith('-creator-agy'):
|
||||
if row_agent(s) != 'agy':
|
||||
continue
|
||||
if s.get('status') != 'running':
|
||||
continue
|
||||
@@ -632,6 +678,11 @@ for s in d.get('herdr_sessions', []):
|
||||
if verify_session_uuid(cwd, 'agy', uuid, s_eval, mode="discover"):
|
||||
valid_candidates.append(uuid)
|
||||
|
||||
if len(valid_candidates) > 1:
|
||||
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
|
||||
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
|
||||
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
|
||||
actions.append(f"ambiguous candidates: {s['name']}")
|
||||
if len(valid_candidates) == 1:
|
||||
uuid = valid_candidates[0]
|
||||
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "agy" "{cwd}"']
|
||||
@@ -645,7 +696,7 @@ for s in d.get('herdr_sessions', []):
|
||||
|
||||
# === drift C (hermes): hermes 새 session id materialize (per-row own id) ===
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if not s.get('name', '').endswith('-creator-hermes'):
|
||||
if row_agent(s) != 'hermes':
|
||||
continue
|
||||
if s.get('status') != 'running':
|
||||
continue
|
||||
@@ -670,6 +721,11 @@ for s in d.get('herdr_sessions', []):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if len(valid_candidates) > 1:
|
||||
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
|
||||
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
|
||||
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
|
||||
actions.append(f"ambiguous candidates: {s['name']}")
|
||||
if len(valid_candidates) == 1:
|
||||
uuid = valid_candidates[0]
|
||||
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "hermes" "{cwd}"']
|
||||
@@ -683,7 +739,7 @@ for s in d.get('herdr_sessions', []):
|
||||
|
||||
# === drift C (cline): cline 새 session id materialize (per-row own id) ===
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if not s.get('name', '').endswith('-creator-cline'):
|
||||
if row_agent(s) != 'cline':
|
||||
continue
|
||||
if s.get('status') != 'running':
|
||||
continue
|
||||
@@ -709,8 +765,12 @@ for s in d.get('herdr_sessions', []):
|
||||
uuid = os.path.basename(j)[:-5]
|
||||
if verify_session_uuid(cwd, 'cline', uuid, s, mode="discover"):
|
||||
valid_candidates.append(uuid)
|
||||
break
|
||||
|
||||
if len(valid_candidates) > 1:
|
||||
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
|
||||
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
|
||||
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
|
||||
actions.append(f"ambiguous candidates: {s['name']}")
|
||||
if len(valid_candidates) == 1:
|
||||
uuid = valid_candidates[0]
|
||||
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "cline" "{cwd}"']
|
||||
|
||||
@@ -85,8 +85,9 @@ if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||
fi
|
||||
|
||||
# 3. Determine CMD_FULL
|
||||
# For claude: if assigned UUID transcript .jsonl is unmaterialized on disk, use --session-id <uuid>; otherwise use -r <uuid>
|
||||
case "$AGENT" in
|
||||
claude) CMD_FULL="claude --dangerously-skip-permissions -r $UUID" ;;
|
||||
claude) CMD_FULL="claude --dangerously-skip-permissions $CLAUDE_ID_FLAG $UUID" ;;
|
||||
agy) CMD_FULL="agy --dangerously-skip-permissions --conversation $UUID" ;;
|
||||
hermes) CMD_FULL="hermes --resume $UUID" ;;
|
||||
cline) CMD_FULL="cline -i --id $UUID" ;;
|
||||
|
||||
@@ -80,9 +80,23 @@ if [ "$(uname)" = "Darwin" ] && [ -f "$RESOLVED_BIN" ]; then
|
||||
xattr -d com.apple.quarantine "$RESOLVED_BIN" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
CLAUDE_ID_FLAG="-r"
|
||||
if [ "$AGENT" = "claude" ]; then
|
||||
_ws_key="$(mam_workspace_key "$WORKSPACE")"
|
||||
_iso_root="$(mam_session_iso_root "$SESSION_NAME" 2>/dev/null || true)"
|
||||
if [ -n "$_iso_root" ]; then
|
||||
_proj_dir="$_iso_root/projects"
|
||||
else
|
||||
_proj_dir="${CLAUDE_PROJECT_DIR:-$HOME/.claude/projects}"
|
||||
fi
|
||||
if [ ! -f "${_proj_dir}/${_ws_key}/${UUID}.jsonl" ]; then
|
||||
CLAUDE_ID_FLAG="--session-id"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine CMD_FULL
|
||||
case "$AGENT" in
|
||||
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions -r $UUID" ;;
|
||||
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions $CLAUDE_ID_FLAG $UUID" ;;
|
||||
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --conversation $UUID" ;;
|
||||
hermes) CMD_FULL="${RESOLVED_BIN} --resume $UUID" ;;
|
||||
cline) CMD_FULL="${RESOLVED_BIN} -i --id $UUID" ;;
|
||||
|
||||
Reference in New Issue
Block a user