#!/usr/bin/env bash # status.sh — multi-agent-status 의 부속 스크립트 (READ-ONLY) # 한 번 호출로 현재 agent 세션 상태표를 출력. 부수효과 없음. # reconcile.sh --dry-run 을 재사용해 drift 를 계산하고 (P1-E), YAML/디스크에서 # 보강한 표를 그린다. YAML 을 절대 수정하지 않는다. # # Usage: bash status.sh [--json] set -euo pipefail source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh" RECONCILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/agent-sessions-monitor/scripts/reconcile.sh" JSON=0 [ "${1:-}" = "--json" ] && JSON=1 [ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found. Run multi-agent-create first." >&2; exit 1; } # 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 DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF' import os, json, glob import yaml yaml_path = os.environ['YAML_PATH'] home = os.environ['HOME_DIR'] drift = json.loads(os.environ['DRIFT_JSON']) with open(yaml_path) as f: d = yaml.safe_load(f) or {} 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"{home}/.claude/projects/{key}/{u}.jsonl") else 'MISSING' key = cwd.replace('/', '-').replace('_', '-') return 'scan' if glob.glob(f"{home}/.claude/projects/{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 '?' sessions = d.get('tmux_sessions', []) print(f"agent-sessions status — {drift['timestamp']} (tmux_confirmed={drift['tmux_confirmed']})") print("=" * 116) print(f"{'NAME':<44} {'SERVER':<12} {'YAML':<10} {'TMUX':<6} {'CMD':<6} {'RESUME':<8} DRIFT") print("-" * 116) if not sessions: print("(no sessions registered)") for s in sessions: name = s.get('name', '?') server = s.get('tmux_server') or 'default' status = s.get('status', '?') tmux = 'alive' if f"{name}|{server}" in alive else 'dead' cmd = (s.get('pane') or {}).get('cmd', '?') res = resume_on_disk(s) drs = ','.join(drift_by_name.get(name, [])) or '-' print(f"{name:<44} {server:<12} {status:<10} {tmux:<6} {cmd:<6} {res:<8} {drs}") # drifts not tied to a registered row (e.g. class B unregistered, class D cache) known = {s.get('name') for s in sessions} extra = [dr for dr in drift.get('drifts', []) if dr['name'] not in known] if extra: print("-" * 116) for dr in extra: print(f" [{dr['class']}] {dr['msg']}") print("=" * 116) print(f"alive tmux: {sorted(alive)}") PYEOF