#!/usr/bin/env bash # orc_onboard.sh — Register/remove orchestrator session UUID in agent-sessions.yaml (O-4) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" # shellcheck disable=SC1091 source "$REPO_ROOT/.agents/skills/lib.sh" MODE="add" TARGET_UUID="" AUTODETECT=true DRY_RUN=false usage() { cat <<'EOF' Usage: orc_onboard.sh [OPTIONS] Options: --uuid Register specified orchestrator session UUID --remove Remove specified UUID from orchestrator_uuids list --list List all registered orchestrator UUIDs --no-autodetect Disable process ancestry auto-detection when --uuid is omitted --dry-run Preview changes without updating state registry EOF exit 2 } while [ $# -gt 0 ]; do case "$1" in --uuid) if [ $# -lt 2 ] || [ -z "$2" ]; then usage; fi TARGET_UUID="$2" MODE="add" shift 2 ;; --remove) if [ $# -lt 2 ] || [ -z "$2" ]; then usage; fi TARGET_UUID="$2" MODE="remove" shift 2 ;; --list) MODE="list" shift ;; --no-autodetect) AUTODETECT=false shift ;; --dry-run) DRY_RUN=true shift ;; -h|--help) usage ;; *) usage ;; esac done is_valid_id() { local val="$1" local uuid_re='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' local cline_re='^[0-9]{10,}_[0-9A-Za-z]+$' if [[ "$val" =~ $uuid_re ]] || [[ "$val" =~ $cline_re ]]; then return 0 fi return 1 } detect_nearest_agent() { if [ "${MAM_AUTODETECT_FORCE_FAIL:-0}" = "1" ]; then return 3 fi local curr=$$ local agent_family="" local cmd_line="" local detected_id="" while [ -n "$curr" ] && [ "$curr" -gt 1 ]; do cmd_line=$(ps -p "$curr" -o command= 2>/dev/null || true) if [ -z "$cmd_line" ]; then break fi # Extract tokens before any leading hyphen option local tokens=() for tok in $cmd_line; do if [[ "$tok" =~ ^- ]]; then break fi tokens+=("$tok") done # Check for agent family match in path tokens local match="" for tok in "${tokens[@]}"; do local base base="$(basename "$tok")" case "$base" in claude|agy|hermes|cline) match="$base" break ;; esac done if [ -n "$match" ]; then agent_family="$match" # 1. Parse CLI argv for flags (handles both space and equals form) case "$agent_family" in claude) detected_id=$(echo "$cmd_line" | grep -oE '(-r|--session-id)[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^(-r|--session-id)[[:space:]=]+//' || true) ;; agy) detected_id=$(echo "$cmd_line" | grep -oE '--conversation[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^--conversation[[:space:]=]+//' || true) ;; cline) detected_id=$(echo "$cmd_line" | grep -oE '(--id|--session-id)[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^(--id|--session-id)[[:space:]=]+//' || true) ;; esac if [ -n "$detected_id" ] && is_valid_id "$detected_id"; then echo "$detected_id" return 0 fi # 2. Check family-matched environment variable local env_var="" case "$agent_family" in claude) env_var="${CLAUDE_CODE_SESSION_ID:-}" ;; agy) env_var="${ANTIGRAVITY_CONVERSATION_ID:-}" ;; hermes) env_var="${HERMES_SESSION_ID:-}" ;; cline) env_var="${CLINE_SESSION_ID:-}" ;; esac if [ -n "$env_var" ] && is_valid_id "$env_var"; then echo "$env_var" return 0 fi # Stop at nearest agent ancestor break fi curr=$(ps -p "$curr" -o ppid= 2>/dev/null | tr -d ' ' || true) done return 3 } export PYTHONPATH="$REPO_ROOT/.agents/skills:${PYTHONPATH:-}" if [ "$MODE" = "list" ]; then python3 -c ' import sys, json try: d = json.loads(sys.stdin.read() or "{}") except Exception: d = {} uuids = d.get("orchestrator_uuids", []) for u in uuids: print(u) ' <<< "$(load_state_json)" exit 0 fi if [ -z "$TARGET_UUID" ]; then if [ "$AUTODETECT" = true ]; then TARGET_UUID=$(detect_nearest_agent || true) fi if [ -z "$TARGET_UUID" ]; then echo "ERROR: Could not detect orchestrator session ID." >&2 exit 3 fi fi if ! is_valid_id "$TARGET_UUID"; then echo "ERROR: Invalid UUID or session ID format '$TARGET_UUID'." >&2 exit 2 fi if [ "$MODE" = "add" ]; then # Check if running sub-agent session row currently owns this UUID check_owner_rc=0 python3 -c ' import sys, json try: d = json.loads(sys.stdin.read() or "{}") except Exception: d = {} target = sys.argv[1] for s in d.get("herdr_sessions", []): if s.get("status") == "running": for k in ["claude_session_id_own", "agy_conversation_id_own", "hermes_conversation_id_own", "cline_conversation_id_own"]: if s.get(k) == target: sys.exit(1) sys.exit(0) ' "$TARGET_UUID" <<< "$(load_state_json)" || check_owner_rc=$? if [ "$check_owner_rc" -eq 1 ]; then echo "ERROR: ID '$TARGET_UUID' is currently owned by a running session." >&2 exit 1 fi fi if [ "$DRY_RUN" = true ]; then echo "[dry-run] would $MODE orchestrator uuid: $TARGET_UUID" exit 0 fi MAM_TARGET_UUID="$TARGET_UUID" MAM_OP_MODE="$MODE" atomic_dump_yaml "$AGENT_SESSIONS_YAML" <<'PYEOF' import os, sys, json target = os.environ["MAM_TARGET_UUID"] op = os.environ["MAM_OP_MODE"] uuids = d.get("orchestrator_uuids", []) if not isinstance(uuids, list): uuids = [] if op == "add": if target not in uuids: uuids.append(target) elif op == "remove": uuids = [u for u in uuids if u != target] d["orchestrator_uuids"] = uuids PYEOF echo "Successfully ${MODE}ed orchestrator UUID: $TARGET_UUID"