feat(ui): complete M1 Milestone - read-only Dashboard and Detail Pane with status.sh integration

This commit is contained in:
2026-07-16 21:09:31 +09:00
parent 7d22774d76
commit 2eb85866b3
22 changed files with 2014 additions and 399 deletions
@@ -19,15 +19,113 @@ JSON=0
# read-only drift snapshot — reconcile.sh --dry-run (no side effects)
DRIFT_JSON="$(bash "$RECONCILE" --once --emit-diff --dry-run)"
if [ "$JSON" = "1" ]; then
printf '%s\n' "$DRIFT_JSON"
exit 0
fi
# Project root (parent of .agents/) holds the multi-agent-mux-delegate-job .mam registry.
# Resolved relative to this script — no hardcoded absolute path (review item 6).
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)"
if [ "$JSON" = "1" ]; then
# D8: --json historically only carried reconcile.sh's drift subset (timestamp/
# yaml_path/tmux_sessions_alive/tmux_confirmed/drifts/actions), not the enriched
# per-row fields (RESUME/JOB_ID/JOB_STATUS/CMD/attach_command/pane.cwd/...) that
# only this script's text-mode block computed. Fixed additively below via a
# 'sessions_detail' key — every existing key is passed through untouched, so
# any consumer of the pre-fix schema keeps working unmodified.
MAM_STATE_JSON="$(load_state_json)" DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" PROJECT_ROOT="$PROJECT_ROOT" <<'PYEOF'
import os, json, glob
home = os.environ['HOME_DIR']
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
drift = json.loads(os.environ['DRIFT_JSON'])
try:
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
except Exception:
d = {}
alive = set(drift.get('tmux_sessions_alive', []))
drift_by_name = {}
for dr in drift.get('drifts', []):
drift_by_name.setdefault(dr['name'], []).append(dr['class'])
def resume_on_disk(s):
# workspace-SCOPED check only — per-row own id, never a global identity (P0-C)
name = s.get('name', '')
cwd = (s.get('pane') or {}).get('cwd', '')
if name.endswith('-creator-claude'):
u = s.get('claude_session_id_own')
if u:
key = cwd.replace('/', '-').replace('_', '-')
return 'yes' if os.path.exists(f"{claude_project_dir}/{key}/{u}.jsonl") else 'MISSING'
key = cwd.replace('/', '-').replace('_', '-')
return 'scan' if glob.glob(f"{claude_project_dir}/{key}/*.jsonl") else 'no'
if name.endswith('-creator-agy'):
u = s.get('agy_conversation_id_own')
if u:
return 'yes' if os.path.exists(f"{home}/.gemini/antigravity-cli/conversations/{u}.db") else 'MISSING'
return 'no'
return '?'
def get_job_status(s):
jid = s.get('delegate_job_id')
if not jid:
return ('-', '-')
project_root = os.environ.get('PROJECT_ROOT', '.')
candidates = [
os.path.join('.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.mam', 'delegate_job_logs', jid, 'status.json'),
]
for path in candidates:
if os.path.exists(path):
try:
with open(path) as jf:
job_data = json.load(jf)
return (jid, job_data.get('status', 'unknown'))
except Exception:
pass
return (jid, 'unknown')
sessions_detail = []
for s in d.get('tmux_sessions', []):
name = s.get('name', '?')
server = s.get('tmux_server') or 'default'
jid, jstatus = get_job_status(s)
pane = s.get('pane') or {}
sessions_detail.append({
# Fields named/typed to match the reviewed D8 contract
# (.mam/jobs/40bdce88/claude-reports/report-final.md §3.1) exactly —
# mam_core maps this straight onto its Session/Pane/Drift models.
'name': name,
'server': server,
'status': s.get('status', '?'),
'tmux_alive': f"{name}|{server}" in alive,
'cmd': pane.get('cmd'),
'role': s.get('role'),
'resume_state': resume_on_disk(s),
'job_id': jid,
'job_status': jstatus,
'pane_cwd': pane.get('cwd'),
'attach_command': s.get('attach_command'),
'drift_classes': drift_by_name.get(name, []),
# Additive beyond the D8 example — needed by the M1 Detail Pane
# (pane pid / full launch command / start command / last banner text).
'pane_pid': pane.get('pid'),
'cmd_full': pane.get('cmd_full'),
'start_command': s.get('start_command'),
'last_visible_status': s.get('last_visible_status'),
})
drift['sessions_detail'] = sessions_detail
print(json.dumps(drift, ensure_ascii=False))
PYEOF
exit 0
fi
MAM_STATE_JSON="$(load_state_json)" DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" PROJECT_ROOT="$PROJECT_ROOT" <<'PYEOF'
import os, sys, json, glob