Files
bootstrap/tools/orchestrator/session-status.sh
Jason Woltje abead17e0e feat: add multi-runtime support (coord run, prdy --codex) and next-task capsule
- coord/prdy subcommands now accept --claude/--codex runtime flags
- New `mosaic coord run` generates continuation context and launches
  selected runtime, replacing manual copy/paste workflow
- Next-task capsule (.mosaic/orchestrator/next-task.json) provides
  machine-readable execution context for deterministic session launches
- Codex strict orchestrator profile added to runtime/codex/RUNTIME.md
- Orchestrator protocol updated with between-session run flow
- New smoke-test.sh for orchestration behavior verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:27:09 -06:00

242 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
#
# session-status.sh — Check agent session health
#
# Usage:
# session-status.sh [--project <path>] [--format table|json]
#
# Exit codes:
# 0 = running
# 2 = stale (recently died)
# 3 = dead (no longer running)
# 4 = no session
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/_lib.sh"
# ─── Parse arguments ─────────────────────────────────────────────────────────
PROJECT="."
FORMAT="table"
while [[ $# -gt 0 ]]; do
case "$1" in
--project) PROJECT="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
-h|--help)
echo "Usage: session-status.sh [--project <path>] [--format table|json]"
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
_require_jq
runtime_cmd="$(coord_launch_command)"
run_cmd="$(coord_run_command)"
# ─── Check session lock ─────────────────────────────────────────────────────
lock_data=""
if ! lock_data="$(session_lock_read "$PROJECT")"; then
# No active session — but check if a mission exists
mp="$(mission_path "$PROJECT")"
if [[ -f "$mp" ]]; then
m_status="$(jq -r '.status // "inactive"' "$mp")"
m_name="$(jq -r '.name // "unnamed"' "$mp")"
m_id="$(jq -r '.mission_id // ""' "$mp")"
m_total="$(jq '.milestones | length' "$mp")"
m_done="$(jq '[.milestones[] | select(.status == "completed")] | length' "$mp")"
m_current="$(jq -r '[.milestones[] | select(.status == "active" or .status == "pending")][0].name // "none"' "$mp")"
# Task counts if TASKS.md exists
task_json="$(count_tasks_md "$PROJECT")"
t_total="$(echo "$task_json" | jq '.total')"
t_done="$(echo "$task_json" | jq '.done')"
t_pending="$(echo "$task_json" | jq '.pending')"
t_inprog="$(echo "$task_json" | jq '.in_progress')"
if [[ "$FORMAT" == "json" ]]; then
jq -n \
--arg status "no-session" \
--arg mission_status "$m_status" \
--arg mission_name "$m_name" \
--arg mission_id "$m_id" \
--argjson milestones_total "$m_total" \
--argjson milestones_done "$m_done" \
--argjson tasks_total "$t_total" \
--argjson tasks_done "$t_done" \
'{
status: $status,
mission: {
status: $mission_status,
name: $mission_name,
id: $mission_id,
milestones_total: $milestones_total,
milestones_done: $milestones_done,
tasks_total: $tasks_total,
tasks_done: $tasks_done
}
}'
else
echo ""
echo -e " ${C_DIM}No active agent session.${C_RESET}"
echo ""
# Mission info
case "$m_status" in
active) ms_color="${C_GREEN}ACTIVE${C_RESET}" ;;
paused) ms_color="${C_YELLOW}PAUSED${C_RESET}" ;;
completed) ms_color="${C_CYAN}COMPLETED${C_RESET}" ;;
*) ms_color="${C_DIM}${m_status}${C_RESET}" ;;
esac
echo -e " ${C_BOLD}Mission:${C_RESET} $m_name"
echo -e " ${C_CYAN}Status:${C_RESET} $ms_color"
echo -e " ${C_CYAN}ID:${C_RESET} $m_id"
echo -e " ${C_CYAN}Milestones:${C_RESET} $m_done / $m_total completed"
[[ "$m_current" != "none" ]] && echo -e " ${C_CYAN}Current:${C_RESET} $m_current"
if (( t_total > 0 )); then
echo -e " ${C_CYAN}Tasks:${C_RESET} $t_done / $t_total done ($t_pending pending, $t_inprog in-progress)"
fi
echo ""
if [[ "$m_status" == "active" || "$m_status" == "paused" ]]; then
echo -e " ${C_BOLD}Next steps:${C_RESET}"
echo " $run_cmd Auto-generate context and launch"
echo " mosaic coord continue Generate continuation prompt"
echo " $runtime_cmd Launch agent session"
elif [[ "$m_status" == "completed" ]]; then
echo -e " ${C_DIM}Mission completed. Start a new one with: mosaic coord init${C_RESET}"
else
echo -e " ${C_DIM}Initialize with: mosaic coord init --name \"Mission Name\"${C_RESET}"
fi
echo ""
fi
else
if [[ "$FORMAT" == "json" ]]; then
echo '{"status":"no-session","mission":null}'
else
echo ""
echo -e " ${C_DIM}No active session.${C_RESET}"
echo -e " ${C_DIM}No mission found.${C_RESET}"
echo ""
echo " Initialize with: mosaic coord init --name \"Mission Name\""
echo ""
fi
fi
exit 4
fi
# Parse lock
session_id="$(echo "$lock_data" | jq -r '.session_id // "unknown"')"
runtime="$(echo "$lock_data" | jq -r '.runtime // "unknown"')"
pid="$(echo "$lock_data" | jq -r '.pid // 0')"
started_at="$(echo "$lock_data" | jq -r '.started_at // ""')"
milestone_id="$(echo "$lock_data" | jq -r '.milestone_id // ""')"
# ─── Determine status ───────────────────────────────────────────────────────
status="unknown"
exit_code=1
if is_pid_alive "$pid"; then
status="running"
exit_code=0
else
# PID is dead — check how recently
last_act="$(last_activity_time "$PROJECT")"
now="$(epoch_now)"
age=$(( now - last_act ))
if (( age < STALE_THRESHOLD )); then
status="stale"
exit_code=2
elif (( age < DEAD_THRESHOLD )); then
status="stale"
exit_code=2
else
status="dead"
exit_code=3
fi
fi
# ─── Gather supplementary info ──────────────────────────────────────────────
duration_secs=0
if [[ -n "$started_at" ]]; then
start_epoch="$(iso_to_epoch "$started_at")"
now="$(epoch_now)"
duration_secs=$(( now - start_epoch ))
fi
last_act="$(last_activity_time "$PROJECT")"
# Current milestone from mission.json
current_ms=""
if [[ -f "$(mission_path "$PROJECT")" ]]; then
current_ms="$(current_milestone_id "$PROJECT")"
if [[ -n "$current_ms" ]]; then
ms_name="$(milestone_name "$PROJECT" "$current_ms")"
[[ -n "$ms_name" ]] && current_ms="$current_ms ($ms_name)"
fi
fi
# Next task from TASKS.md
next_task="$(find_next_task "$PROJECT")"
# ─── Output ──────────────────────────────────────────────────────────────────
if [[ "$FORMAT" == "json" ]]; then
jq -n \
--arg status "$status" \
--arg session_id "$session_id" \
--arg runtime "$runtime" \
--arg pid "$pid" \
--arg started_at "$started_at" \
--arg duration "$duration_secs" \
--arg milestone "$current_ms" \
--arg next_task "$next_task" \
--arg last_activity "$last_act" \
'{
status: $status,
session_id: $session_id,
runtime: $runtime,
pid: ($pid | tonumber),
started_at: $started_at,
duration_seconds: ($duration | tonumber),
milestone: $milestone,
next_task: $next_task,
last_activity_epoch: ($last_activity | tonumber)
}'
else
# Color the status
case "$status" in
running) status_color="${C_GREEN}RUNNING${C_RESET}" ;;
stale) status_color="${C_YELLOW}STALE${C_RESET}" ;;
dead) status_color="${C_RED}DEAD${C_RESET}" ;;
*) status_color="$status" ;;
esac
echo ""
echo -e " Session Status: $status_color ($runtime)"
echo -e " ${C_CYAN}Session ID:${C_RESET} $session_id"
echo -e " ${C_CYAN}Started:${C_RESET} $started_at ($(format_duration "$duration_secs"))"
echo -e " ${C_CYAN}PID:${C_RESET} $pid"
[[ -n "$current_ms" ]] && echo -e " ${C_CYAN}Milestone:${C_RESET} $current_ms"
[[ -n "$next_task" ]] && echo -e " ${C_CYAN}Next task:${C_RESET} $next_task"
echo -e " ${C_CYAN}Last activity:${C_RESET} $(format_ago "$last_act")"
echo ""
if [[ "$status" == "stale" || "$status" == "dead" ]]; then
echo -e " ${C_YELLOW}Session is no longer running.${C_RESET}"
echo " Recovery: mosaic coord resume"
echo " Continue: mosaic coord continue"
echo ""
fi
fi
exit "$exit_code"