diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d14799..477b12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and submodules without requiring directory changes. Commands, configuration, drivers, dashboard behavior, runtime resource naming, and `Tools: swarm` commit trailers are unchanged. +- **Fix: dashboard `p` restarts an exited post-process.** The + dashboard blocked `p` whenever a post-process container existed, + so an exited one stranded the restart path even after `s`. It now + blocks only a running container and notes that an exited one will + be replaced. `p` also passes the dashboard's `SWARM_CONFIG` to + `launch.sh post-process`, so non-default configs are honoured. ## 0.22.0 — 2026-06-08 diff --git a/USAGE.md b/USAGE.md index b3400f5..db4246d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -356,7 +356,7 @@ do not count toward numbered-agent completion. | `P` | Post-process logs (the `P` row), if it exists. | | `h` | Harvest results. | | `s` | Stop numbered agents and post-process. | -| `p` | Start post-process after confirmation. | +| `p` | Start post-process after confirmation; replaces an exited run. | The Model column appends the agent's reasoning effort as a parenthesised letter: `(h)` high, `(m)` medium, `(l)` low, @@ -472,7 +472,7 @@ Add to `swarm.json`: } ``` -Trigger via `[P]` in the dashboard, `./launch.sh post-process`, +Trigger via `[p]` in the dashboard, `./launch.sh post-process`, or automatically via `./launch.sh wait` after numbered agents have already been started. `./launch.sh wait` does not launch numbered agents. diff --git a/dashboard.sh b/dashboard.sh index 69f106a..989a407 100755 --- a/dashboard.sh +++ b/dashboard.sh @@ -350,6 +350,12 @@ post_process_container_exists() { [ "$_pp_state" != "not found" ] && [ "$_pp_state" != "none" ] } +post_process_container_running() { + local _pp_state + _pp_state=$(container_state "${IMAGE_NAME}-post") + [ "$_pp_state" = "running" ] +} + interactive_container_names() { docker ps -a --format '{{.Names}}' 2>/dev/null \ | grep -E "^${IMAGE_NAME}-interactive-" \ @@ -770,7 +776,7 @@ draw() { printf " ${DIM}[h]${RESET} harvest" # shellcheck disable=SC2059 printf " ${DIM}[s]${RESET} stop all" - if post_process_configured && ! post_process_container_exists; then + if post_process_configured && ! post_process_container_running; then # shellcheck disable=SC2059 printf " ${DIM}[p]${RESET} post-process" fi @@ -838,7 +844,7 @@ while true; do continue fi _pp_name="${IMAGE_NAME}-post" - if post_process_container_exists; then + if post_process_container_running; then echo "(post-processing is already running -- press P for logs)" echo "Stop it first with s before starting a new run." echo "" @@ -846,6 +852,9 @@ while true; do enter_alt_screen continue fi + if post_process_container_exists; then + echo "(previous post-process container will be replaced)" + fi _pp_prompt="Start post-processing now? [y/N] " read -r -p "$_pp_prompt" _pp_confirm || _pp_confirm="" case "$_pp_confirm" in @@ -863,7 +872,8 @@ while true; do docker stop "${IMAGE_NAME}-${i}" 2>/dev/null || true done echo "--- Starting post-processing ---" - "$SWARM_DIR/launch.sh" post-process || \ + SWARM_CONFIG="$CONFIG_FILE" \ + "$SWARM_DIR/launch.sh" post-process || \ echo "(post-processing failed)" echo "" read -rp "Press Enter to return to dashboard..." _ diff --git a/tests/test_dashboard.sh b/tests/test_dashboard.sh index b618e30..e2b2286 100755 --- a/tests/test_dashboard.sh +++ b/tests/test_dashboard.sh @@ -692,9 +692,31 @@ assert_eq "leading blank inspect fallback becomes not found" "not found" \ "$(normalize_docker_state $'\nnot found')" assert_eq "leading blank inspect state keeps real state" "running" \ "$(normalize_docker_state $'\nrunning')" -assert_eq "dashboard uses normalized container state" "4" \ +assert_eq "dashboard uses normalized container state" "5" \ "$(grep -cF 'container_state "' "$DASHBOARD_FILE")" +# Post-process container predicates, loaded from dashboard.sh with +# container_state stubbed. Only a running container blocks p. +eval "$(sed -n '/^post_process_container_exists()/,/^}/p; + /^post_process_container_running()/,/^}/p' "$DASHBOARD_FILE")" +# Invoked by the eval'd predicates above. +# shellcheck disable=SC2329 +container_state() { printf '%s' "$_stub_state"; } +IMAGE_NAME="test-swarm" +for _stub_state in running exited created paused "not found" none; do + _want_exists=true + case "$_stub_state" in + "not found"|none) _want_exists=false ;; + esac + _want_running=false + [ "$_stub_state" = "running" ] && _want_running=true + assert_eq "post-process exists when ${_stub_state}" "$_want_exists" \ + "$(post_process_container_exists && echo true || echo false)" + assert_eq "post-process running when ${_stub_state}" "$_want_running" \ + "$(post_process_container_running && echo true || echo false)" +done +unset -f container_state + pp_lower_case=$(awk ' /^[[:space:]]*p\)/ { p = 1 } p { print } @@ -733,12 +755,22 @@ assert_eq "lowercase p asks for confirmation" "true" \ assert_eq "lowercase p can launch post-process" "1" \ "$(printf '%s\n' "$pp_lower_case" \ | grep -cF '"$SWARM_DIR/launch.sh" post-process' || true)" +assert_eq "lowercase p preserves dashboard config" "1" \ + "$(printf '%s\n' "$pp_lower_case" \ + | grep -cF 'SWARM_CONFIG="$CONFIG_FILE"' || true)" assert_eq "lowercase p has cancellation path" "1" \ "$(printf '%s\n' "$pp_lower_case" \ | grep -cF 'post-processing not started' || true)" assert_eq "lowercase p refuses to replace running post-process" "1" \ "$(printf '%s\n' "$pp_lower_case" \ | grep -cF 'post-processing is already running' || true)" +assert_eq "lowercase p blocks only running post-process" "1" \ + "$(printf '%s\n' "$pp_lower_case" \ + | grep -cF 'if post_process_container_running; then' || true)" +assert_eq "lowercase p allows exited post-process replacement" "1" \ + "$(printf '%s\n' "$pp_lower_case" \ + | grep -cF 'previous post-process container will be replaced' \ + || true)" assert_eq "lowercase p has no replacement prompt" "0" \ "$(printf '%s\n' "$pp_lower_case" \ | grep -cF 'Replace existing' || true)" @@ -751,10 +783,14 @@ assert_eq "footer merges P into the logs hint" "1" \ assert_eq "footer offers lowercase p to start post-process" "1" \ "$(printf '%s\n' "$help_bar" \ | grep -cF '[p]' || true)" -assert_eq "footer guards start hint on missing container" "1" \ +assert_eq "footer guards start hint on running container" "1" \ "$(printf '%s\n' "$help_bar" \ - | grep -cF 'post_process_configured && ! post_process_container_exists' \ + | grep -cF \ + 'post_process_configured && ! post_process_container_running' \ || true)" +assert_eq "footer keeps logs hint for exited container" "1" \ + "$(printf '%s\n' "$help_bar" \ + | grep -cF 'if post_process_container_exists; then' || true)" assert_eq "s stops post-process container" "1" \ "$(printf '%s\n' "$s_case" \ | grep -cF 'docker stop "${IMAGE_NAME}-post"' || true)"