diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index e9328d00..44811241 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -416,6 +416,16 @@ pub struct BashRunArgs { /// `timed_out` when the limit is exceeded. #[serde(default)] pub timeout_secs: Option, + /// Optional inline wait: if set, `bash_run` polls for up to + /// `wait_seconds` (capped at 30) before returning. When the task + /// finishes within the window the full status is returned immediately + /// — no separate `bash_status` call needed. When the timeout expires + /// before the command finishes, the task keeps running in the + /// background and the usual `task started: id=` response is + /// returned. Useful for fast commands (< 5 s) that would otherwise + /// force an unnecessary round-trip. + #[serde(default)] + pub wait_seconds: Option, } /// MCP tool args for `bash_status`. @@ -827,15 +837,43 @@ impl AgentServer { do NOT wait inline. When the command finishes, the harness fires a wake with \ `from: \"bash-task-\"` and the exit code + last stdout lines in the body; \ handle it on a future turn. Use `bash_status` to poll the task status within \ - the same turn if needed. `timeout_secs` defaults to 180." + the same turn if needed. `timeout_secs` defaults to 180. \ + Pass `wait_seconds` (capped at 30) to wait inline for fast commands: when the \ + task finishes within the window the full status is returned immediately and no \ + wake is fired; when the timeout expires the task keeps running and the normal \ + `task started: id=` response is returned." )] async fn bash_run(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); run_tool_envelope("bash_run", log, async move { - match crate::bash_runner::submit_task(args.cmd, args.timeout_secs) { - Ok(id) => format!("task started: id={id}"), - Err(e) => format!("bash_run failed: {e:#}"), + let id = match crate::bash_runner::submit_task(args.cmd, args.timeout_secs) { + Ok(id) => id, + Err(e) => return format!("bash_run failed: {e:#}"), + }; + // Inline wait: poll until done or deadline, whichever comes first. + if let Some(wait) = args.wait_seconds { + const MAX_WAIT_SECS: u64 = 30; + const POLL_MS: u64 = 100; + let deadline = + tokio::time::Instant::now() + + std::time::Duration::from_secs(wait.min(MAX_WAIT_SECS)); + loop { + tokio::time::sleep(std::time::Duration::from_millis(POLL_MS)).await; + if let Some(task) = crate::bash_runner::read_task(&id) { + use crate::bash_runner::TaskStatus; + if matches!( + task.status, + TaskStatus::Done | TaskStatus::TimedOut | TaskStatus::Interrupted + ) { + return format_bash_status(&id); + } + } + if tokio::time::Instant::now() >= deadline { + break; + } + } } + format!("task started: id={id}") }) .await }