From 3537b545089a4a63603e485d4e7b6de09edbc2a1 Mon Sep 17 00:00:00 2001 From: Bo Wu Date: Thu, 23 Jul 2026 17:44:18 -0700 Subject: [PATCH] Add Codex UI swarm dashboard --- README.md | 2 + bin/subagent.sh | 23 ++++- bin/watch.sh | 222 ++++++++++++++++++++++++++++++++++++++++ docs/getting-started.md | 22 ++++ launch.sh | 17 ++- tests/run.sh | 44 ++++++++ 6 files changed, 324 insertions(+), 6 deletions(-) create mode 100755 bin/watch.sh diff --git a/README.md b/README.md index 97e5f2b..3053f71 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,8 @@ contracts and workflows: `validation-run`, and `validation-lease-acquire`; - **Verifier Workflow**, its compact contract ledger, and `MULTIAGENT_VERIFIER_MAX_ITERATIONS=3`; +- Codex UI dashboard watching through `bin/watch.sh`, backed by tmux pane logs + under `.multiagent/logs`, blocked-agent state, and workflow DAG nodes; - preflight checks that prevent a scaffold, shim, or proxy behavior from being mistaken for the target production system. diff --git a/bin/subagent.sh b/bin/subagent.sh index e27cf27..1e05427 100755 --- a/bin/subagent.sh +++ b/bin/subagent.sh @@ -4,6 +4,7 @@ set -euo pipefail SESSION="${MULTIAGENT_SESSION:-multiagent}" ROOT="${MULTIAGENT_ROOT:-$(pwd)}" STATE_DIR="${MULTIAGENT_STATE_DIR:-$ROOT/.multiagent}" +LOG_DIR="${MULTIAGENT_LOG_DIR:-$STATE_DIR/logs}" POLICY_FILE="${MULTIAGENT_WRITE_POLICY:-$ROOT/docs/write-policy.paths}" CODEX_BIN="${CODEX_BIN:-codex}" CLAUDE_BIN="${CLAUDE_BIN:-claude}" @@ -90,6 +91,14 @@ require_cmd() { command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" } +pipe_log() { + local window="$1" + local log_file="$LOG_DIR/$window.log" + mkdir -p "$LOG_DIR" + touch "$log_file" + tmux pipe-pane -o -t "$SESSION:$window" "cat >> $(printf '%q' "$log_file")" +} + normalize_cli() { case "$1" in codex|claude) @@ -1296,12 +1305,13 @@ spawn_subagent() { local dir dir="$(subagent_dir "$name")" - mkdir -p "$dir" + mkdir -p "$dir" "$LOG_DIR" cat >"$dir/meta.env" <>"$dir/transcript.log" fi - printf -v command "cd %q && export MULTIAGENT_SESSION=%q MULTIAGENT_ROOT=%q MULTIAGENT_STATE_DIR=%q MULTIAGENT_WRITE_POLICY=%q MULTIAGENT_SUBAGENT_NAME=%q MULTIAGENT_HELPER=%q WORKER_CLI=%q SUBAGENT_CLI=%q VERIFIER_CLI=%q CODEX_BIN=%q CLAUDE_BIN=%q MULTIAGENT_CODEX_EXEC=%q PATH=%q && %s; rc=\$?; printf '\\nfinal status: codex exec exited rc=%%s\\n' \$rc; sleep infinity" \ - "$ROOT" "$SESSION" "$ROOT" "$STATE_DIR" "$POLICY_FILE" "$name" "$MULTIAGENT_HELPER" "$WORKER_CLI" "$cli" "$VERIFIER_CLI" "$CODEX_BIN" "$CLAUDE_BIN" "${MULTIAGENT_CODEX_EXEC:-0}" "$PATH" "$(build_cli_command "$cli" "$ROOT" "$prompt_file" "$output_file")" + printf -v command "cd %q && export MULTIAGENT_SESSION=%q MULTIAGENT_ROOT=%q MULTIAGENT_STATE_DIR=%q MULTIAGENT_LOG_DIR=%q MULTIAGENT_WRITE_POLICY=%q MULTIAGENT_SUBAGENT_NAME=%q MULTIAGENT_HELPER=%q WORKER_CLI=%q SUBAGENT_CLI=%q VERIFIER_CLI=%q CODEX_BIN=%q CLAUDE_BIN=%q MULTIAGENT_CODEX_EXEC=%q PATH=%q && %s; rc=\$?; printf '\\nfinal status: codex exec exited rc=%%s\\n' \$rc; sleep infinity" \ + "$ROOT" "$SESSION" "$ROOT" "$STATE_DIR" "$LOG_DIR" "$POLICY_FILE" "$name" "$MULTIAGENT_HELPER" "$WORKER_CLI" "$cli" "$VERIFIER_CLI" "$CODEX_BIN" "$CLAUDE_BIN" "${MULTIAGENT_CODEX_EXEC:-0}" "$PATH" "$(build_cli_command "$cli" "$ROOT" "$prompt_file" "$output_file")" tmux new-window -d -t "$SESSION" -n "$name" "$command" + pipe_log "$name" set_status "$name" "running" if [[ -f "$(assignment_meta_file "$name")" ]]; then set_assignment_status "$name" "running" @@ -1580,6 +1591,7 @@ restore_subagent() { } >>"$dir/transcript.log" set_status "$name" "restoring" + mkdir -p "$LOG_DIR" local prompt_file output_file prompt_file="" output_file="$dir/last-message.txt" @@ -1587,9 +1599,10 @@ restore_subagent() { prompt_file="$dir/restore-instruction.txt" printf '%s\n' "$instruction" >"$prompt_file" fi - printf -v command "cd %q && export MULTIAGENT_SESSION=%q MULTIAGENT_ROOT=%q MULTIAGENT_STATE_DIR=%q MULTIAGENT_WRITE_POLICY=%q MULTIAGENT_SUBAGENT_NAME=%q MULTIAGENT_HELPER=%q MULTIAGENT_SUBAGENT_RESTORED=1 WORKER_CLI=%q SUBAGENT_CLI=%q VERIFIER_CLI=%q CODEX_BIN=%q CLAUDE_BIN=%q MULTIAGENT_CODEX_EXEC=%q PATH=%q && %s; rc=\$?; printf '\\nfinal status: codex exec exited rc=%%s\\n' \$rc; sleep infinity" \ - "$ROOT" "$SESSION" "$ROOT" "$STATE_DIR" "$POLICY_FILE" "$name" "$MULTIAGENT_HELPER" "$WORKER_CLI" "$cli" "$VERIFIER_CLI" "$CODEX_BIN" "$CLAUDE_BIN" "${MULTIAGENT_CODEX_EXEC:-0}" "$PATH" "$(build_cli_command "$cli" "$ROOT" "$prompt_file" "$output_file")" + printf -v command "cd %q && export MULTIAGENT_SESSION=%q MULTIAGENT_ROOT=%q MULTIAGENT_STATE_DIR=%q MULTIAGENT_LOG_DIR=%q MULTIAGENT_WRITE_POLICY=%q MULTIAGENT_SUBAGENT_NAME=%q MULTIAGENT_HELPER=%q MULTIAGENT_SUBAGENT_RESTORED=1 WORKER_CLI=%q SUBAGENT_CLI=%q VERIFIER_CLI=%q CODEX_BIN=%q CLAUDE_BIN=%q MULTIAGENT_CODEX_EXEC=%q PATH=%q && %s; rc=\$?; printf '\\nfinal status: codex exec exited rc=%%s\\n' \$rc; sleep infinity" \ + "$ROOT" "$SESSION" "$ROOT" "$STATE_DIR" "$LOG_DIR" "$POLICY_FILE" "$name" "$MULTIAGENT_HELPER" "$WORKER_CLI" "$cli" "$VERIFIER_CLI" "$CODEX_BIN" "$CLAUDE_BIN" "${MULTIAGENT_CODEX_EXEC:-0}" "$PATH" "$(build_cli_command "$cli" "$ROOT" "$prompt_file" "$output_file")" tmux new-window -d -t "$SESSION" -n "$name" "$command" + pipe_log "$name" set_status "$name" "running" if ! [[ "${MULTIAGENT_CODEX_EXEC:-0}" == "1" && "$cli" == "codex" ]]; then deliver_instruction "$name" "$instruction" diff --git a/bin/watch.sh b/bin/watch.sh new file mode 100755 index 0000000..2436b2a --- /dev/null +++ b/bin/watch.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash +set -euo pipefail + +SESSION="${MULTIAGENT_SESSION:-multiagent}" +ROOT="${MULTIAGENT_ROOT:-$(pwd)}" +STATE_DIR="${MULTIAGENT_STATE_DIR:-$ROOT/.multiagent}" +LOG_DIR="${MULTIAGENT_LOG_DIR:-$STATE_DIR/logs}" +INTERVAL="${MULTIAGENT_WATCH_INTERVAL:-5}" +LOG_LINES="${MULTIAGENT_WATCH_LOG_LINES:-40}" +ONCE=0 + +usage() { + cat <<'USAGE' +Usage: bin/watch.sh [--once] [--interval SECONDS] [--log-lines N] + +Renders a compact multiagent dashboard for Codex desktop. It combines +assignment/subagent status, workflow DAG summaries, blocked nodes, and the +orchestrator pane log written by launch.sh. + +Environment: + MULTIAGENT_SESSION tmux session name, default: multiagent + MULTIAGENT_ROOT project root, default: current directory + MULTIAGENT_STATE_DIR state root, default: $MULTIAGENT_ROOT/.multiagent + MULTIAGENT_LOG_DIR pane log directory, default: $STATE_DIR/logs + MULTIAGENT_WATCH_INTERVAL refresh interval, default: 5 + MULTIAGENT_WATCH_LOG_LINES orchestrator tail lines, default: 40 +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --once) + ONCE=1 + shift + ;; + --interval) + INTERVAL="${2:-}" + shift 2 + ;; + --log-lines) + LOG_LINES="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "watch: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if ! [[ "$INTERVAL" =~ ^[1-9][0-9]*$ ]]; then + echo "watch: --interval must be a positive integer" >&2 + exit 2 +fi +if ! [[ "$LOG_LINES" =~ ^[0-9]+$ ]]; then + echo "watch: --log-lines must be a non-negative integer" >&2 + exit 2 +fi + +status_text() { + if "$ROOT/bin/status.sh" 2>/dev/null; then + return 0 + fi + printf 'TYPE\tNAME\tSTATUS\tWINDOW\tLAST_PROGRESS\tSTATE_DIR\tROLE\tDECISION_ID\tPLAN_ID\tWORKFLOW_ID\tNODE_ID\n' +} + +render_status_summary() { + awk -F'\t' ' + NR == 1 { next } + $3 != "" { count[$3]++; seen=1 } + END { + if (!seen) { + print "none\t0" + next + } + for (status in count) { + print status "\t" count[status] + } + } + ' | sort +} + +render_workers() { + awk -F'\t' ' + NR == 1 { next } + { + seen=1 + progress=$5 + if (length(progress) > 90) { + progress=substr(progress, 1, 87) "..." + } + printf "%-9s %-28s %-10s %-7s %s\n", $1, $2, $3, $4, progress + } + END { + if (!seen) { + print "none" + } + } + ' +} + +render_blocked_agents() { + awk -F'\t' ' + NR == 1 { next } + tolower($3) ~ /blocked|delivery-blocked/ { + seen=1 + progress=$5 + if (length(progress) > 110) { + progress=substr(progress, 1, 107) "..." + } + printf "%-28s %-16s %s\n", $2, $3, progress + } + END { + if (!seen) { + print "none" + } + } + ' +} + +render_dag_summary() { + local base="$STATE_DIR/workflows" + if [[ ! -d "$base" ]]; then + printf 'No workflows found.\n' + return + fi + + local any=0 dir workflow nodes + for dir in "$base"/*; do + [[ -d "$dir" ]] || continue + workflow="$(basename "$dir")" + nodes="$dir/nodes.tsv" + [[ -f "$nodes" ]] || continue + any=1 + printf '%s\n' "$workflow" + awk -F'\t' ' + NR == 1 { next } + $7 != "" { count[$7]++ } + END { + for (status in count) { + printf " %-8s %s\n", status, count[status] + } + } + ' "$nodes" | sort + done + [[ "$any" -eq 1 ]] || printf 'No workflows found.\n' +} + +render_blocked_dag_nodes() { + local base="$STATE_DIR/workflows" + if [[ ! -d "$base" ]]; then + printf 'none\n' + return + fi + + local any=0 dir workflow nodes + for dir in "$base"/*; do + [[ -d "$dir" ]] || continue + workflow="$(basename "$dir")" + nodes="$dir/nodes.tsv" + [[ -f "$nodes" ]] || continue + while IFS=$'\t' read -r node_id agent assignment_id role branch owned_paths status decision_id plan_id added_at; do + [[ "$node_id" != "node_id" ]] || continue + if [[ "$status" == "blocked" || "$status" == "failed" ]]; then + any=1 + printf '%s\t%s\t%s\t%s\n' "$workflow" "$node_id" "$status" "$agent" + fi + done <"$nodes" + done + [[ "$any" -eq 1 ]] || printf 'none\n' +} + +render_once() { + local now status_snapshot orchestrator_log + now="$(date '+%Y-%m-%d %H:%M:%S')" + status_snapshot="$(status_text)" + orchestrator_log="$LOG_DIR/orchestrator.log" + + printf 'Multiagent Dashboard\n' + printf 'Session: %s Root: %s\n' "$SESSION" "$ROOT" + printf 'State: %s\n' "$STATE_DIR" + printf 'Logs: %s\n' "$LOG_DIR" + printf 'Updated: %s\n\n' "$now" + + printf 'Agent Status Summary\n' + printf '%s\n' "$status_snapshot" | render_status_summary + + printf '\nAgents\n' + printf '%s\n' "$status_snapshot" | render_workers + + printf '\nBlocked Agents\n' + printf '%s\n' "$status_snapshot" | render_blocked_agents + + printf '\nDAG Summary\n' + render_dag_summary + + printf '\nBlocked DAG Nodes\n' + render_blocked_dag_nodes + + printf '\nOrchestrator Tail\n' + if [[ -f "$orchestrator_log" && "$LOG_LINES" -gt 0 ]]; then + tail -n "$LOG_LINES" "$orchestrator_log" + elif [[ "$LOG_LINES" -eq 0 ]]; then + printf '(disabled)\n' + else + printf 'No orchestrator log yet. Start with ./launch.sh or pipe the pane manually with tmux pipe-pane.\n' + fi +} + +while true; do + if [[ "$ONCE" -eq 0 ]]; then + printf '\033[H\033[2J' + fi + render_once + [[ "$ONCE" -eq 0 ]] || break + sleep "$INTERVAL" +done diff --git a/docs/getting-started.md b/docs/getting-started.md index 83a0fc0..c68e3d7 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -606,6 +606,28 @@ worker windows, polls open named subagents, refreshes subagent state, and prints a table with agent type, name, status, window state, latest progress line, and state directory. +For a live Codex desktop view, use the dashboard watcher: + +```bash +bin/watch.sh +``` + +`launch.sh` pipes the orchestrator tmux pane into +`$MULTIAGENT_STATE_DIR/logs/orchestrator.log`. Named subagents spawned or +restored through `bin/subagent.sh` are piped into +`$MULTIAGENT_STATE_DIR/logs/NAME.log`. The watcher renders a compact dashboard +from those logs, `bin/status.sh`, assignment metadata, and workflow DAG state so +the Codex UI can continuously show the orchestrator tail, status counts, blocked +agents, DAG summaries, and blocked DAG nodes. + +Useful watcher options: + +```bash +bin/watch.sh --once +bin/watch.sh --interval 2 --log-lines 80 +MULTIAGENT_LOG_DIR=/tmp/swarm-logs bin/watch.sh +``` + ## Organizational Learning Workflow The orchestrator supports exploration/exploitation/reflection cycles for complex decisions requiring multiple approaches. diff --git a/launch.sh b/launch.sh index c724df2..c141258 100755 --- a/launch.sh +++ b/launch.sh @@ -38,6 +38,7 @@ Environment: MULTIAGENT_ROOT Default project root, default: launcher directory MULTIAGENT_RESUME Launch mode exported by this script: 0 clean, 1 resume MULTIAGENT_STATE_DIR Persisted subagent state, default: $MULTIAGENT_ROOT/.multiagent + MULTIAGENT_LOG_DIR tmux pane logs, default: $MULTIAGENT_STATE_DIR/logs MULTIAGENT_WRITE_POLICY Repo write policy, default: $MULTIAGENT_ROOT/docs/write-policy.paths MULTIAGENT_VERIFIER_MAX_ITERATIONS Verifier follow-up loop cap, default: 3 MULTIAGENT_PROMPT Orchestrator prompt, default: /orchestrator_prompt.md @@ -87,6 +88,7 @@ while [[ $# -gt 0 ]]; do done STATE_DIR="${MULTIAGENT_STATE_DIR:-$ROOT/.multiagent}" +LOG_DIR="${MULTIAGENT_LOG_DIR:-$STATE_DIR/logs}" POLICY_FILE="${MULTIAGENT_WRITE_POLICY:-$ROOT/docs/write-policy.paths}" require_cmd() { @@ -104,6 +106,14 @@ require_python_runtime() { fi } +pipe_log() { + local window="$1" + local log_file="$LOG_DIR/$window.log" + mkdir -p "$LOG_DIR" + touch "$log_file" + tmux pipe-pane -o -t "$SESSION:$window" "cat >> $(printf '%q' "$log_file")" +} + normalize_cli() { case "$1" in codex|claude) @@ -190,6 +200,7 @@ export MULTIAGENT_PROMPT="$PROMPT_FILE" export MULTIAGENT_PROMPT_MODULE_ROOT="$PROMPT_MODULE_ROOT" export MULTIAGENT_REQUIRE_HASH_BOUND_VERIFIER="${MULTIAGENT_REQUIRE_HASH_BOUND_VERIFIER:-1}" export MULTIAGENT_STATE_DIR="$STATE_DIR" +export MULTIAGENT_LOG_DIR="$LOG_DIR" export MULTIAGENT_WRITE_POLICY="$POLICY_FILE" export MULTIAGENT_VERIFIER_MAX_ITERATIONS="$VERIFIER_MAX_ITERATIONS" export ORCHESTRATOR_CLI @@ -202,7 +213,7 @@ export MULTIAGENT_CODEX_EXEC="${MULTIAGENT_CODEX_EXEC:-0}" export MULTIAGENT_EXTRA_PATH="${MULTIAGENT_EXTRA_PATH:-}" export PATH -mkdir -p "$STATE_DIR/subagents" "$STATE_DIR/assignments" "$STATE_DIR/worktrees" +mkdir -p "$STATE_DIR/subagents" "$STATE_DIR/assignments" "$STATE_DIR/worktrees" "$LOG_DIR" "$SCRIPT_DIR/bin/write-policy.sh" init if [[ "$RESUME" -eq 1 ]]; then RESUME_LABEL="resume" @@ -221,6 +232,7 @@ ORCHESTRATOR_BOOTSTRAP_SCRIPT="$STATE_DIR/orchestrator-bootstrap.sh" printf 'export MULTIAGENT_PROMPT_MODULE_ROOT=%q\n' "$PROMPT_MODULE_ROOT" printf 'export MULTIAGENT_REQUIRE_HASH_BOUND_VERIFIER=%q\n' "${MULTIAGENT_REQUIRE_HASH_BOUND_VERIFIER:-1}" printf 'export MULTIAGENT_STATE_DIR=%q\n' "$STATE_DIR" + printf 'export MULTIAGENT_LOG_DIR=%q\n' "$LOG_DIR" printf 'export MULTIAGENT_WRITE_POLICY=%q\n' "$POLICY_FILE" printf 'export MULTIAGENT_VERIFIER_MAX_ITERATIONS=%q\n' "$VERIFIER_MAX_ITERATIONS" printf 'export ORCHESTRATOR_CLI=%q\n' "$ORCHESTRATOR_CLI" @@ -240,11 +252,14 @@ chmod 700 "$ORCHESTRATOR_BOOTSTRAP_SCRIPT" tmux new-session -d -s "$SESSION" -n orchestrator "bash $(printf '%q' "$ORCHESTRATOR_BOOTSTRAP_SCRIPT")" tmux select-window -t "$SESSION:orchestrator" +pipe_log orchestrator echo "Started tmux session: $SESSION" echo "Attach with: tmux attach -t $SESSION" echo "Resume mode: $RESUME" echo "Subagent state: $STATE_DIR" +echo "Logs: $LOG_DIR" +echo "Dashboard: MULTIAGENT_SESSION=$(printf '%q' "$SESSION") MULTIAGENT_ROOT=$(printf '%q' "$ROOT") $SCRIPT_DIR/bin/watch.sh" echo "Verifier max iterations: $VERIFIER_MAX_ITERATIONS" echo "Worker CLI: $WORKER_CLI" echo "Subagent CLI: $SUBAGENT_CLI" diff --git a/tests/run.sh b/tests/run.sh index 5ddfa8b..5bea8db 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -99,6 +99,26 @@ case "$cmd" in select-window) printf 'select-window %s\n' "${1:-}" >>"$log_file" ;; + pipe-pane) + target="" + pipe_command="" + while [[ $# -gt 0 ]]; do + case "$1" in + -o) + shift + ;; + -t) + target="$2" + shift 2 + ;; + *) + pipe_command="$1" + shift + ;; + esac + done + printf 'pipe-pane %s %s\n' "$target" "$pipe_command" >>"$log_file" + ;; capture-pane) target="" while [[ $# -gt 0 ]]; do @@ -227,10 +247,14 @@ assert_file_contains "$TMPDIR/launch.out" "Worker CLI: claude" assert_file_contains "$TMPDIR/launch.out" "Subagent CLI: claude" assert_file_contains "$TMPDIR/launch.out" "Verifier CLI: codex" assert_file_contains "$TMPDIR/launch.out" "Default write root: $LAUNCH_TARGET" +assert_file_contains "$TMPDIR/launch.out" "Logs: $LAUNCH_STATE/logs" +assert_file_contains "$TMPDIR/launch.out" "Dashboard: MULTIAGENT_SESSION=launch-cross-repo MULTIAGENT_ROOT=$LAUNCH_TARGET $ROOT/bin/watch.sh" LAUNCH_BOOTSTRAP="$LAUNCH_STATE/orchestrator-bootstrap.sh" assert_file_contains "$MOCK_TMUX_LOG" "$(printf '%q' "$LAUNCH_BOOTSTRAP")" +assert_file_contains "$MOCK_TMUX_LOG" "pipe-pane launch-cross-repo:orchestrator cat >> $LAUNCH_STATE/logs/orchestrator.log" assert_file_contains "$LAUNCH_BOOTSTRAP" "--cd $LAUNCH_TARGET" assert_file_contains "$LAUNCH_BOOTSTRAP" "export MULTIAGENT_RESUME=0" +assert_file_contains "$LAUNCH_BOOTSTRAP" "export MULTIAGENT_LOG_DIR=$LAUNCH_STATE/logs" assert_file_contains "$LAUNCH_BOOTSTRAP" "export MULTIAGENT_VERIFIER_MAX_ITERATIONS=3" assert_file_contains "$LAUNCH_BOOTSTRAP" "export WORKER_CLI=claude" assert_file_contains "$LAUNCH_BOOTSTRAP" "export SUBAGENT_CLI=claude" @@ -5169,8 +5193,10 @@ assert_file_contains "$MOCK_TMUX_WINDOWS" "subagent-watch" assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-watch/status" "running" assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-watch/current.txt" "Claude prompt ready" assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-watch/meta.env" "write_policy=$MULTIAGENT_WRITE_POLICY" +assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-watch/meta.env" "log_file=$MULTIAGENT_STATE_DIR/logs/subagent-watch.log" assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-watch/meta.env" "cli=claude" assert_file_contains "$MOCK_TMUX_LOG" "new-window -d test-session subagent-watch" +assert_file_contains "$MOCK_TMUX_LOG" "pipe-pane test-session:subagent-watch cat >> $MULTIAGENT_STATE_DIR/logs/subagent-watch.log" watch_spawn_line="$(grep -F "new-window -d test-session subagent-watch " "$MOCK_TMUX_LOG")" [[ "$watch_spawn_line" == *"--dangerously-skip-permissions"* ]] if [[ "$watch_spawn_line" == *"--cd"* || "$watch_spawn_line" == *"--no-alt-screen"* ]]; then @@ -5403,6 +5429,23 @@ mkdir -p "$MULTIAGENT_STATE_DIR/subagents/subagent-blocked" printf 'running\n' >"$MULTIAGENT_STATE_DIR/subagents/subagent-blocked/status" printf 'Blocked: need input from orchestrator\n' >"$MULTIAGENT_STATE_DIR/subagents/subagent-blocked/current.txt" +mkdir -p "$MULTIAGENT_STATE_DIR/logs" "$MULTIAGENT_STATE_DIR/workflows/dashboard-flow" +printf 'orchestrator event: routed worker-01-docs\n' >"$MULTIAGENT_STATE_DIR/logs/orchestrator.log" +cat >"$MULTIAGENT_STATE_DIR/workflows/dashboard-flow/nodes.tsv" <<'EOF' +node_id agent assignment_id role branch owned_paths status decision_id plan_id added_at +impl worker-impl A-impl exploitation feature/docs README.md blocked DEC-1 PLAN-1 2026-01-01T00:00:00Z +docs worker-docs A-docs exploitation feature/docs docs/ running DEC-1 PLAN-1 2026-01-01T00:00:01Z +EOF +watch_output="$("$ROOT/bin/watch.sh" --once --log-lines 5)" +[[ "$watch_output" == *"Multiagent Dashboard"* ]] +[[ "$watch_output" == *"Agent Status Summary"* ]] +[[ "$watch_output" == *"Blocked Agents"* ]] +[[ "$watch_output" == *"subagent-blocked"* ]] +[[ "$watch_output" == *"DAG Summary"* ]] +[[ "$watch_output" == *"dashboard-flow"* ]] +[[ "$watch_output" == *$'dashboard-flow\timpl\tblocked\tworker-impl'* ]] +[[ "$watch_output" == *"orchestrator event: routed worker-01-docs"* ]] + mkdir -p "$MULTIAGENT_STATE_DIR/subagents/subagent-prompt-only" printf 'missing\n' >"$MULTIAGENT_STATE_DIR/subagents/subagent-prompt-only/status" printf 'If blocked, stop and state what you need. Do not finish with only a plan while /app has no materialized source diff.\n' >"$MULTIAGENT_STATE_DIR/subagents/subagent-prompt-only/current.txt" @@ -5454,6 +5497,7 @@ assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-restore/transcrip assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-restore/transcript.log" "Previous progress: halfway through recovery work" assert_file_contains "$MULTIAGENT_STATE_DIR/subagents/subagent-restore/instruction.txt" "You are a restored long-running subagent." assert_file_contains "$MOCK_TMUX_LOG" "send-key test-session:subagent-restore Read and follow the assignment in $MULTIAGENT_STATE_DIR/subagents/subagent-restore/instruction.txt" +assert_file_contains "$MOCK_TMUX_LOG" "pipe-pane test-session:subagent-restore cat >> $MULTIAGENT_STATE_DIR/logs/subagent-restore.log" claude_restore_line="$(grep -F "new-window -d test-session subagent-restore " "$MOCK_TMUX_LOG")" [[ "$claude_restore_line" == *"--dangerously-skip-permissions"* ]] if [[ "$claude_restore_line" == *"--cd"* || "$claude_restore_line" == *"--no-alt-screen"* ]]; then