feat(mux): implement onboarding prompt initialization, 4-tier UUID validation, and TUI 1:1 cross-matching
This commit is contained in:
+196
-94
@@ -874,6 +874,171 @@ PYEOF
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# verify_session_uuid <workspace> <agent> <uuid> [session_row_json] [mode]
|
||||
# Returns 0 if valid (passes Stage 1, 2, 3), 1 otherwise.
|
||||
# ---------------------------------------------------------------------------
|
||||
VERIFY_SESSION_PYTHON='
|
||||
def workspace_key(path):
|
||||
return path.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 {}
|
||||
epoch = row.get("herdr_session_epoch", 0)
|
||||
cwd = row.get("pane", {}).get("cwd", "") or ws
|
||||
iso = row.get("isolation", {}).get("root") if isinstance(row.get("isolation"), dict) else None
|
||||
|
||||
if workspace_key(cwd) != workspace_key(ws):
|
||||
return False
|
||||
|
||||
if agent == "claude":
|
||||
base = f"{iso}/projects" if iso else c_dir
|
||||
key = workspace_key(ws)
|
||||
path = f"{base}/{key}/{uuid}.jsonl"
|
||||
if not os.path.exists(path):
|
||||
return False
|
||||
if epoch and os.path.getmtime(path) < epoch:
|
||||
return False
|
||||
try:
|
||||
with open(path) as f:
|
||||
first = f.readline().strip()
|
||||
if not first:
|
||||
return False
|
||||
payload = json.loads(first)
|
||||
if payload.get("sessionId") != uuid:
|
||||
return False
|
||||
if payload.get("cwd") and payload.get("cwd") != cwd:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
elif agent == "agy":
|
||||
base = f"{iso}/.gemini/antigravity-cli/conversations" if iso else f"{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"{iso}/.gemini/antigravity-cli/cache/last_conversations.json" if iso else f"{home}/.gemini/antigravity-cli/cache/last_conversations.json"
|
||||
if os.path.exists(lc):
|
||||
try:
|
||||
with open(lc) as f:
|
||||
lc_data = json.load(f)
|
||||
if lc_data.get(cwd) != uuid:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
try:
|
||||
conn = sqlite3.connect(path)
|
||||
r = conn.execute("SELECT count(*) FROM steps").fetchone()
|
||||
conn.close()
|
||||
if not r or r[0] < 1:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
elif agent == "hermes":
|
||||
hdb = f"{iso}/.hermes/state.db" if iso else f"{home}/.hermes/state.db"
|
||||
if not os.path.exists(hdb):
|
||||
return False
|
||||
if epoch and os.path.getmtime(hdb) < epoch:
|
||||
return False
|
||||
try:
|
||||
conn = sqlite3.connect(hdb)
|
||||
r = conn.execute("SELECT 1 FROM sessions WHERE id=?", (uuid,)).fetchone()
|
||||
conn.close()
|
||||
if not r:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
elif agent == "cline":
|
||||
base = f"{iso}/sessions" if iso else f"{home}/.cline/data/sessions"
|
||||
path = f"{base}/{uuid}/{uuid}.json"
|
||||
if not os.path.exists(path):
|
||||
return False
|
||||
if epoch and os.path.getmtime(path) < epoch:
|
||||
return False
|
||||
try:
|
||||
with open(path) as f:
|
||||
sdata = json.load(f)
|
||||
if sdata.get("session_id") != uuid:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
'
|
||||
|
||||
verify_session_uuid() {
|
||||
local workspace="$1" agent="$2" uuid="$3" row_json="${4:-}" mode="${5:-discover}"
|
||||
MAM_VERIFY_PY="$VERIFY_SESSION_PYTHON" WS_ABS="$workspace" AGENT="$agent" UUID="$uuid" ROW_JSON="$row_json" MODE="$mode" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
|
||||
import os, sys, json
|
||||
exec(os.environ['MAM_VERIFY_PY'])
|
||||
ws = os.environ['WS_ABS']
|
||||
agent = os.environ['AGENT']
|
||||
uuid = os.environ['UUID']
|
||||
row_str = os.environ.get('ROW_JSON', '')
|
||||
row = json.loads(row_str) if row_str else {}
|
||||
mode = os.environ.get('MODE', 'discover')
|
||||
if verify_session_uuid(ws, agent, uuid, row, mode=mode):
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# verify_tui_viewport <session_name> <agent> <workspace>
|
||||
#
|
||||
# Viewport matching verification:
|
||||
# | Return | Meaning | Action Required |
|
||||
# |---|---|---|
|
||||
# | 0 | Viewport matches workspace | Pin UUID, last_visible_status='pinned', drift class C |
|
||||
# | 1 | Viewport mismatch | Do not pin, log C-warn, last_visible_status unchanged |
|
||||
# | 2 | Viewport check unavailable | Pin UUID via stage 1-3 only, log C-degraded |
|
||||
#
|
||||
# Returns:
|
||||
# 0 = verified match
|
||||
# 1 = mismatch
|
||||
# 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 base; base="$(basename "$abs")"
|
||||
|
||||
if ! _herdr has-session -t "$session_name" >/dev/null 2>&1; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
local pane_content
|
||||
pane_content=$(_pane_capture "$session_name" 2>/dev/null || true)
|
||||
if [ -z "$pane_content" ]; then
|
||||
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
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# find_workspace_uuid <workspace> <agent>
|
||||
#
|
||||
@@ -890,7 +1055,7 @@ PYEOF
|
||||
find_workspace_uuid() {
|
||||
local workspace="$1" agent="$2" session_name="${3:-}"
|
||||
local abs; abs="$(cd "$workspace" 2>/dev/null && pwd)" || abs="$workspace"
|
||||
MAM_STATE_JSON="$(load_state_json)" WS_ABS="$abs" AGENT="$agent" TARGET_SESSION="$session_name" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
|
||||
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
|
||||
|
||||
ws = os.environ['WS_ABS']
|
||||
@@ -899,49 +1064,15 @@ home = os.environ['HOME_DIR']
|
||||
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
||||
target = os.environ.get('TARGET_SESSION', '')
|
||||
|
||||
exec(os.environ['MAM_VERIFY_PY'])
|
||||
|
||||
OWN_KEY = {'claude': 'claude_session_id_own', 'agy': 'agy_conversation_id_own',
|
||||
'hermes': 'hermes_conversation_id_own', 'cline': 'cline_conversation_id_own'}
|
||||
|
||||
|
||||
def iso_root_of(s):
|
||||
iso = s.get('isolation')
|
||||
return iso.get('root') if isinstance(iso, dict) else None
|
||||
|
||||
|
||||
def jsonl_exists(uuid, iso=None):
|
||||
base = f"{iso}/projects" if iso else claude_project_dir
|
||||
key = ws.replace('/', '-').replace('_', '-')
|
||||
return os.path.exists(f"{base}/{key}/{uuid}.jsonl")
|
||||
|
||||
|
||||
def db_exists(uuid, iso=None):
|
||||
base = f"{iso}/.gemini/antigravity-cli/conversations" if iso else f"{home}/.gemini/antigravity-cli/conversations"
|
||||
return os.path.exists(f"{base}/{uuid}.db")
|
||||
|
||||
|
||||
def hermes_exists(uuid, iso=None):
|
||||
hdb = f"{iso}/.hermes/state.db" if iso else f"{home}/.hermes/state.db"
|
||||
if not os.path.exists(hdb):
|
||||
return False
|
||||
try:
|
||||
conn = sqlite3.connect(hdb)
|
||||
r = conn.execute("SELECT 1 FROM sessions WHERE id=?", (uuid,)).fetchone()
|
||||
conn.close()
|
||||
return r is not None
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def cline_exists(uuid, iso=None):
|
||||
base = f"{iso}/sessions" if iso else f"{home}/.cline/data/sessions"
|
||||
return os.path.exists(f"{base}/{uuid}/{uuid}.json")
|
||||
|
||||
|
||||
def own_exists(a, uuid, iso=None):
|
||||
return {'claude': jsonl_exists, 'agy': db_exists,
|
||||
'hermes': hermes_exists, 'cline': cline_exists}[a](uuid, iso)
|
||||
|
||||
|
||||
# Ingest the merged state dictionary from stdin pipeline
|
||||
try:
|
||||
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
|
||||
@@ -958,51 +1089,38 @@ for s_item in d.get('herdr_sessions', []):
|
||||
if val:
|
||||
running_ids.add(val)
|
||||
|
||||
|
||||
def emit(u):
|
||||
if u in running_ids:
|
||||
return
|
||||
print(u)
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
# Fetch all sessions matching this workspace
|
||||
sessions = []
|
||||
for s in d.get('herdr_sessions', []):
|
||||
if isinstance(s, dict) and (s.get('pane') or {}).get('cwd') == ws:
|
||||
sessions.append(s)
|
||||
|
||||
|
||||
# T5: target-session mode — resolve strictly within the named row's scope.
|
||||
# An isolated row's conversation lives ONLY in its isolation root (which holds
|
||||
# at most one conversation), so we never fall through to the global tiers —
|
||||
# those would guess another role's conversation (the C3 mtime bug).
|
||||
if target:
|
||||
for s in sessions:
|
||||
if s.get('name') != target:
|
||||
continue
|
||||
iso = iso_root_of(s)
|
||||
cand = s.get(OWN_KEY.get(agent, ''), None)
|
||||
if cand and own_exists(agent, cand, iso):
|
||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
||||
emit(cand)
|
||||
if iso:
|
||||
key = ws.replace('/', '-').replace('_', '-')
|
||||
key = workspace_key(ws)
|
||||
if agent == 'claude':
|
||||
for j in sorted(glob.glob(f"{iso}/projects/{key}/*.jsonl"), key=os.path.getmtime, reverse=True):
|
||||
sid = None
|
||||
try:
|
||||
with open(j) as f:
|
||||
first = f.readline().strip()
|
||||
if first:
|
||||
sid = json.loads(first).get('sessionId')
|
||||
except Exception:
|
||||
sid = None
|
||||
cand = sid or os.path.basename(j)[:-6]
|
||||
if cand:
|
||||
cand = os.path.basename(j)[:-6]
|
||||
if cand and verify_session_uuid(ws, agent, cand, s):
|
||||
emit(cand)
|
||||
elif agent == 'agy':
|
||||
for j in sorted(glob.glob(f"{iso}/.gemini/antigravity-cli/conversations/*.db"), key=os.path.getmtime, reverse=True):
|
||||
emit(os.path.basename(j)[:-3])
|
||||
cand = os.path.basename(j)[:-3]
|
||||
if cand and verify_session_uuid(ws, agent, cand, s):
|
||||
emit(cand)
|
||||
elif agent == 'hermes':
|
||||
hdb = f"{iso}/.hermes/state.db"
|
||||
if os.path.exists(hdb):
|
||||
@@ -1011,55 +1129,46 @@ if target:
|
||||
r = conn.execute("SELECT id FROM sessions WHERE cwd=? ORDER BY started_at DESC LIMIT 1", (ws,)).fetchone()
|
||||
conn.close()
|
||||
if r:
|
||||
emit(r[0])
|
||||
cand = r[0]
|
||||
if verify_session_uuid(ws, agent, cand, s):
|
||||
emit(cand)
|
||||
except Exception:
|
||||
pass
|
||||
elif agent == 'cline':
|
||||
for folder in sorted(glob.glob(f"{iso}/sessions/*"), key=os.path.getmtime, reverse=True):
|
||||
fn = os.path.basename(folder)
|
||||
if os.path.exists(f"{folder}/{fn}.json"):
|
||||
emit(fn)
|
||||
# isolated row: nothing found inside its exclusive root -> no guess
|
||||
if verify_session_uuid(ws, agent, fn, s):
|
||||
emit(fn)
|
||||
print('')
|
||||
raise SystemExit(0)
|
||||
# target row absent or legacy (non-isolated): fall through to legacy tiers
|
||||
|
||||
for s in sessions:
|
||||
name = s.get('name', '')
|
||||
iso = iso_root_of(s)
|
||||
if agent == 'claude' and name.endswith('-creator-claude'):
|
||||
cand = s.get('claude_session_id_own')
|
||||
if cand and jsonl_exists(cand, iso):
|
||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
||||
emit(cand)
|
||||
if agent == 'agy' and name.endswith('-creator-agy'):
|
||||
cand = s.get('agy_conversation_id_own')
|
||||
if cand and db_exists(cand, iso):
|
||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
||||
emit(cand)
|
||||
if agent == 'hermes' and name.endswith('-creator-hermes'):
|
||||
cand = s.get('hermes_conversation_id_own')
|
||||
if cand and hermes_exists(cand, iso):
|
||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
||||
emit(cand)
|
||||
if agent == 'cline' and name.endswith('-creator-cline'):
|
||||
cand = s.get('cline_conversation_id_own')
|
||||
if cand and cline_exists(cand, iso):
|
||||
if cand and verify_session_uuid(ws, agent, cand, s, mode="revalidate"):
|
||||
emit(cand)
|
||||
|
||||
# 2) disk scan scoped to THIS workspace
|
||||
if agent == 'claude':
|
||||
key = ws.replace('/', '-').replace('_', '-')
|
||||
key = workspace_key(ws)
|
||||
proj = f"{claude_project_dir}/{key}"
|
||||
if os.path.isdir(proj):
|
||||
for j in sorted(glob.glob(f"{proj}/*.jsonl"), key=os.path.getmtime, reverse=True):
|
||||
sid = None
|
||||
try:
|
||||
with open(j) as f:
|
||||
first = f.readline().strip()
|
||||
if first:
|
||||
sid = json.loads(first).get('sessionId')
|
||||
except Exception:
|
||||
sid = None
|
||||
cand = sid or os.path.basename(j)[:-6]
|
||||
if cand and jsonl_exists(cand):
|
||||
cand = os.path.basename(j)[:-6]
|
||||
if cand and verify_session_uuid(ws, agent, cand):
|
||||
emit(cand)
|
||||
elif agent == 'agy':
|
||||
lc = f"{home}/.gemini/antigravity-cli/cache/last_conversations.json"
|
||||
@@ -1069,7 +1178,7 @@ elif agent == 'agy':
|
||||
cand = json.load(open(lc)).get(ws)
|
||||
except Exception:
|
||||
cand = None
|
||||
if cand and db_exists(cand):
|
||||
if cand and verify_session_uuid(ws, agent, cand):
|
||||
emit(cand)
|
||||
elif agent == 'hermes':
|
||||
hdb = f"{home}/.hermes/state.db"
|
||||
@@ -1083,7 +1192,7 @@ elif agent == 'hermes':
|
||||
cand = r[0]
|
||||
except Exception:
|
||||
cand = None
|
||||
if cand:
|
||||
if cand and verify_session_uuid(ws, agent, cand):
|
||||
emit(cand)
|
||||
elif agent == 'cline':
|
||||
sessions_dir = f"{home}/.cline/data/sessions"
|
||||
@@ -1097,17 +1206,10 @@ elif agent == 'cline':
|
||||
candidates.append(json_file)
|
||||
candidates.sort(key=os.path.getmtime, reverse=True)
|
||||
for j in candidates:
|
||||
try:
|
||||
with open(j) as f:
|
||||
sdata = json.load(f)
|
||||
if sdata.get('cwd') == ws or sdata.get('workspace_root') == ws:
|
||||
sid = sdata.get('session_id')
|
||||
if sid:
|
||||
emit(sid)
|
||||
except Exception:
|
||||
pass
|
||||
cand = os.path.basename(j)[:-5]
|
||||
if cand and verify_session_uuid(ws, agent, cand):
|
||||
emit(cand)
|
||||
|
||||
# 3) agent_identities cache, ONLY when its project_cwd == this workspace
|
||||
ai = {}
|
||||
try:
|
||||
if os.path.exists(db_path):
|
||||
@@ -1127,19 +1229,19 @@ ai_agent = ai.get(agent) or {}
|
||||
if ai_agent.get('project_cwd') == ws:
|
||||
if agent == 'claude':
|
||||
cand = ai_agent.get('session_id')
|
||||
if cand and jsonl_exists(cand):
|
||||
if cand and verify_session_uuid(ws, agent, cand, mode="revalidate"):
|
||||
emit(cand)
|
||||
elif agent == 'agy':
|
||||
cand = ai.get('conversation_id')
|
||||
if cand and db_exists(cand):
|
||||
if cand and verify_session_uuid(ws, agent, cand, mode="revalidate"):
|
||||
emit(cand)
|
||||
elif agent == 'hermes':
|
||||
cand = ai_agent.get('session_id') or ai.get('conversation_id')
|
||||
if cand and hermes_exists(cand):
|
||||
if cand and verify_session_uuid(ws, agent, cand, mode="revalidate"):
|
||||
emit(cand)
|
||||
elif agent == 'cline':
|
||||
cand = ai_agent.get('session_id') or ai.get('conversation_id')
|
||||
if cand and cline_exists(cand):
|
||||
if cand and verify_session_uuid(ws, agent, cand, mode="revalidate"):
|
||||
emit(cand)
|
||||
|
||||
print('')
|
||||
|
||||
Reference in New Issue
Block a user