diff --git a/docs/4-implementation/agent-tools/bash.md b/docs/4-implementation/agent-tools/bash.md index 2074ab29a..57d3b576f 100644 --- a/docs/4-implementation/agent-tools/bash.md +++ b/docs/4-implementation/agent-tools/bash.md @@ -99,7 +99,8 @@ log line, and a task never waits on the queue. `$SWARM.term..bash.`, each with the `Swarm-Stream` header (`stdout` or `stderr`). A chunk never splits a character, and one larger than the queue's payload limit goes out in pieces. When the process exits, the daemon - publishes what the files still hold before it writes the final status. + queues what the files still hold before it writes the final status, and the + publish task sends queued output ahead of a status. ## Relationship to the `execution` permission diff --git a/hive-bash-mcp/src/swarm_term.rs b/hive-bash-mcp/src/swarm_term.rs index c7e80bf6c..0d79cf742 100644 --- a/hive-bash-mcp/src/swarm_term.rs +++ b/hive-bash-mcp/src/swarm_term.rs @@ -203,13 +203,10 @@ async fn run( let period = Duration::from_secs(bash_task::HEARTBEAT_S.unsigned_abs()); let mut beat = tokio::time::interval_at(tokio::time::Instant::now() + period, period); loop { + // Output first: a task's last chunks are queued before its final status, + // and taking queued output ahead of a status keeps that order on the wire. tokio::select! { - Some(event) = status_rx.recv() => { - if let Event::Status { task, status } = event { - apply(&mut live, &task, &status); - publish_status(&mut queue, &target, &task, &status).await; - } - } + biased; event = output_rx.recv() => { let Some(Event::Output { task, stream, text }) = event else { return }; let missed = dropped.swap(0, Ordering::Relaxed); @@ -218,6 +215,12 @@ async fn run( } publish_output(&mut queue, &target, &task, stream, &text).await; } + Some(event) = status_rx.recv() => { + if let Event::Status { task, status } = event { + apply(&mut live, &task, &status); + publish_status(&mut queue, &target, &task, &status).await; + } + } _ = beat.tick() => { for (task, status) in refresh(&mut live, unix_now()) { publish_status(&mut queue, &target, &task, &status).await;