Files
bootstrap/tools/orchestrator/mission-status.sh
Jason Woltje 5ba531e2d0 feat: r0 coordinator tooling for orchestrator protocol
Implements the manual coordinator workflow for multi-session agent
orchestration. Agents stop after one milestone (confirmed limitation);
these tools let the human coordinator check status, generate continuation
prompts, and chain sessions together.

New:
- tools/orchestrator/ — 5 scripts + shared library (_lib.sh)
  - mission-init.sh: initialize mission with milestones and state files
  - mission-status.sh: dashboard showing milestones, tasks, sessions
  - session-status.sh: check if agent is running/stale/dead
  - continue-prompt.sh: generate paste-ready continuation prompt
  - session-resume.sh: crash recovery with dirty state detection
- guides/ORCHESTRATOR-PROTOCOL.md: agent-facing mission lifecycle guide
- templates/docs/: mission manifest, scratchpad, continuation templates
- templates/repo/.mosaic/orchestrator/mission.json: state file template

Modified:
- bin/mosaic: add 'coord' subcommand + resume advisory on launch
- AGENTS.md: conditional loading for protocol guide + rule 37
- bin/mosaic-doctor: checks for new coordinator files
- session hooks: mission detection on start, cleanup on end

Usage: mosaic coord init|mission|status|continue|resume

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:22:50 -06:00

182 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
#
# mission-status.sh — Show mission progress dashboard
#
# Usage:
# mission-status.sh [--project <path>] [--format table|json|markdown]
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: mission-status.sh [--project <path>] [--format table|json|markdown]"
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
_require_jq
require_mission "$PROJECT"
# ─── Load data ───────────────────────────────────────────────────────────────
mission="$(load_mission "$PROJECT")"
mission_name="$(echo "$mission" | jq -r '.name')"
mission_id="$(echo "$mission" | jq -r '.mission_id')"
mission_status="$(echo "$mission" | jq -r '.status')"
version="$(echo "$mission" | jq -r '.milestone_version // "—"')"
created_at="$(echo "$mission" | jq -r '.created_at // "—"')"
session_count="$(echo "$mission" | jq '.sessions | length')"
milestone_count="$(echo "$mission" | jq '.milestones | length')"
completed_milestones="$(echo "$mission" | jq '[.milestones[] | select(.status == "completed")] | length')"
# Task counts
task_counts="$(count_tasks_md "$PROJECT")"
tasks_total="$(echo "$task_counts" | jq '.total')"
tasks_done="$(echo "$task_counts" | jq '.done')"
tasks_inprog="$(echo "$task_counts" | jq '.in_progress')"
tasks_pending="$(echo "$task_counts" | jq '.pending')"
tasks_blocked="$(echo "$task_counts" | jq '.blocked')"
tasks_failed="$(echo "$task_counts" | jq '.failed')"
# Next task
next_task="$(find_next_task "$PROJECT")"
# ─── JSON output ─────────────────────────────────────────────────────────────
if [[ "$FORMAT" == "json" ]]; then
echo "$mission" | jq \
--argjson tasks "$task_counts" \
--arg next "$next_task" \
'. + {task_counts: $tasks, next_task: $next}'
exit 0
fi
# ─── Progress bar ────────────────────────────────────────────────────────────
progress_bar() {
local done=$1
local total=$2
local width=30
if (( total == 0 )); then
printf "[%${width}s]" ""
return
fi
local filled=$(( (done * width) / total ))
local empty=$(( width - filled ))
local bar=""
for (( i=0; i<filled; i++ )); do bar+="="; done
if (( empty > 0 && filled > 0 )); then
bar+=">"
empty=$(( empty - 1 ))
fi
for (( i=0; i<empty; i++ )); do bar+="."; done
printf "[%s]" "$bar"
}
# ─── Table / Markdown output ────────────────────────────────────────────────
# Header
echo ""
echo "=================================================="
echo -e " ${C_BOLD}Mission: $mission_name${C_RESET}"
echo -e " Status: ${C_CYAN}$mission_status${C_RESET} Version: $version"
echo -e " Started: ${created_at:0:10} Sessions: $session_count"
echo "=================================================="
echo ""
# Milestones
echo -e "${C_BOLD}Milestones:${C_RESET}"
for i in $(seq 0 $(( milestone_count - 1 ))); do
ms_id="$(echo "$mission" | jq -r ".milestones[$i].id")"
ms_name="$(echo "$mission" | jq -r ".milestones[$i].name")"
ms_status="$(echo "$mission" | jq -r ".milestones[$i].status")"
ms_issue="$(echo "$mission" | jq -r ".milestones[$i].issue_ref // \"\"")"
case "$ms_status" in
completed) icon="${C_GREEN}[x]${C_RESET}" ;;
in-progress) icon="${C_YELLOW}[>]${C_RESET}" ;;
blocked) icon="${C_RED}[!]${C_RESET}" ;;
*) icon="${C_DIM}[ ]${C_RESET}" ;;
esac
issue_str=""
[[ -n "$ms_issue" ]] && issue_str="$ms_issue"
printf " %b %-40s %s\n" "$icon" "$ms_name" "$issue_str"
done
echo ""
# Tasks progress
pct=0
(( tasks_total > 0 )) && pct=$(( (tasks_done * 100) / tasks_total ))
echo -e "${C_BOLD}Tasks:${C_RESET} $(progress_bar "$tasks_done" "$tasks_total") ${tasks_done}/${tasks_total} (${pct}%)"
echo -e " done: ${C_GREEN}$tasks_done${C_RESET} in-progress: ${C_YELLOW}$tasks_inprog${C_RESET} pending: $tasks_pending blocked: ${C_RED}$tasks_blocked${C_RESET} failed: ${C_RED}$tasks_failed${C_RESET}"
echo ""
# Session history (last 5)
if (( session_count > 0 )); then
echo -e "${C_BOLD}Recent Sessions:${C_RESET}"
start_idx=$(( session_count > 5 ? session_count - 5 : 0 ))
for i in $(seq "$start_idx" $(( session_count - 1 ))); do
s_id="$(echo "$mission" | jq -r ".sessions[$i].session_id")"
s_rt="$(echo "$mission" | jq -r ".sessions[$i].runtime // \"—\"")"
s_start="$(echo "$mission" | jq -r ".sessions[$i].started_at // \"\"")"
s_end="$(echo "$mission" | jq -r ".sessions[$i].ended_at // \"\"")"
s_reason="$(echo "$mission" | jq -r ".sessions[$i].ended_reason // \"—\"")"
s_last="$(echo "$mission" | jq -r ".sessions[$i].last_task_id // \"—\"")"
duration_str="—"
if [[ -n "$s_start" && -n "$s_end" && "$s_end" != "" ]]; then
s_epoch="$(iso_to_epoch "$s_start")"
e_epoch="$(iso_to_epoch "$s_end")"
if (( e_epoch > 0 && s_epoch > 0 )); then
duration_str="$(format_duration $(( e_epoch - s_epoch )))"
fi
fi
printf " %-10s %-8s %-10s %-18s → %s\n" "$s_id" "$s_rt" "$duration_str" "$s_reason" "$s_last"
done
echo ""
fi
# Current session check
lock_data=""
if lock_data="$(session_lock_read "$PROJECT" 2>/dev/null)"; then
lock_pid="$(echo "$lock_data" | jq -r '.pid // 0')"
lock_rt="$(echo "$lock_data" | jq -r '.runtime // "unknown"')"
lock_start="$(echo "$lock_data" | jq -r '.started_at // ""')"
if is_pid_alive "$lock_pid"; then
dur=0
if [[ -n "$lock_start" ]]; then
dur=$(( $(epoch_now) - $(iso_to_epoch "$lock_start") ))
fi
echo -e "${C_GREEN}Current: running ($lock_rt, PID $lock_pid, $(format_duration "$dur"))${C_RESET}"
else
echo -e "${C_RED}Stale session lock: $lock_rt (PID $lock_pid, not running)${C_RESET}"
echo " Run: mosaic coord resume --clean-lock"
fi
else
echo -e "${C_DIM}No active session.${C_RESET}"
fi
[[ -n "$next_task" ]] && echo -e "Next unblocked task: ${C_CYAN}$next_task${C_RESET}"
echo ""