refactor: migrate skills/ directory to .agents/skills/
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# status.sh — tmux-agent-orchestrate-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)/tmux-agent-orchestrate-monitor/scripts/reconcile.sh"
|
||||
|
||||
JSON=0
|
||||
[ "${1:-}" = "--json" ] && JSON=1
|
||||
|
||||
[ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found. Run tmux-agent-orchestrate-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
|
||||
|
||||
# Project root (parent of .agents/) holds the tmux-agent-orchestrate-delegate-job .hermes registry.
|
||||
# Resolved relative to this script — no hardcoded absolute path (review item 6).
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)"
|
||||
|
||||
DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" PROJECT_ROOT="$PROJECT_ROOT" <<'PYEOF'
|
||||
import os, json, glob
|
||||
import yaml
|
||||
|
||||
yaml_path = os.environ['YAML_PATH']
|
||||
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'])
|
||||
|
||||
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||
d = {}
|
||||
import sqlite3
|
||||
try:
|
||||
if os.path.exists(db_path):
|
||||
conn = sqlite3.connect(db_path, timeout=10.0)
|
||||
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
|
||||
if row: d = json.loads(row[0])
|
||||
|
||||
try:
|
||||
db_sessions = []
|
||||
cursor = conn.execute('SELECT data FROM sessions')
|
||||
for s_row in cursor.fetchall():
|
||||
db_sessions.append(json.loads(s_row[0]))
|
||||
d['tmux_sessions'] = db_sessions
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
conn.close()
|
||||
elif os.path.exists(yaml_path):
|
||||
with open(yaml_path) as f:
|
||||
d = yaml.safe_load(f) or {}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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', '.')
|
||||
# Candidate locations (review item 6: project-root-relative, no hardcoded abs paths):
|
||||
# 1) cwd-relative registry 2) project-root registry 3) project-root audit log
|
||||
candidates = [
|
||||
os.path.join('.hermes', 'jobs', f"{jid}.json"),
|
||||
os.path.join(project_root, '.hermes', 'jobs', f"{jid}.json"),
|
||||
os.path.join(project_root, '.hermes', '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 = d.get('tmux_sessions', [])
|
||||
print(f"agent-sessions status — {drift['timestamp']} (tmux_confirmed={drift['tmux_confirmed']})")
|
||||
print("=" * 136)
|
||||
print(f"{'NAME':<44} {'SERVER':<12} {'YAML':<10} {'TMUX':<6} {'CMD':<6} {'RESUME':<8} {'JOB_ID':<10} {'JOB_STATUS':<12} DRIFT")
|
||||
print("-" * 136)
|
||||
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)
|
||||
jid, jstatus = get_job_status(s)
|
||||
drs = ','.join(drift_by_name.get(name, [])) or '-'
|
||||
print(f"{name:<44} {server:<12} {status:<10} {tmux:<6} {cmd:<6} {res:<8} {jid:<10} {jstatus:<12} {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("-" * 136)
|
||||
for dr in extra:
|
||||
print(f" [{dr['class']}] {dr['msg']}")
|
||||
print("=" * 136)
|
||||
print(f"alive tmux: {sorted(alive)}")
|
||||
PYEOF
|
||||
Reference in New Issue
Block a user