diff --git a/hive-ag3nt/src/bash_runner.rs b/hive-ag3nt/src/bash_runner.rs index b048a705..2f123450 100644 --- a/hive-ag3nt/src/bash_runner.rs +++ b/hive-ag3nt/src/bash_runner.rs @@ -30,8 +30,10 @@ use tokio::io::AsyncWriteExt; const POLL_INTERVAL: Duration = Duration::from_millis(200); /// Soft cap on stdout/stderr captured in the done JSON summary. -/// Full output always lives in the .out/.err files. -const SUMMARY_BYTES: usize = 4096; +/// Full output always lives in the .out/.err files. Exposed so +/// `format_bash_status` can detect truncation and surface the full-output +/// file path when the on-disk file is larger. +pub const SUMMARY_BYTES: usize = 4096; /// Default timeout for tasks that don't specify one. pub const DEFAULT_TIMEOUT_SECS: u64 = 180; @@ -77,6 +79,20 @@ fn task_err(id: &str) -> PathBuf { tasks_dir().join(format!("{id}.err")) } +/// Path to the full stdout capture file for `id`. +/// Exposed so `format_bash_status` can surface it when output is truncated. +#[must_use] +pub fn task_out_path(id: &str) -> PathBuf { + task_out(id) +} + +/// Path to the full stderr capture file for `id`. +/// Exposed so `format_bash_status` can surface it when output is truncated. +#[must_use] +pub fn task_err_path(id: &str) -> PathBuf { + task_err(id) +} + // --------------------------------------------------------------------------- // Wire types (shared between MCP tool writers and runner readers) // --------------------------------------------------------------------------- diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index c0b0e4cc..e9328d00 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -426,6 +426,10 @@ pub struct BashStatusArgs { } /// Format the result of `bash_status` from a task ID. +/// +/// Includes inline output tails (up to [`crate::bash_runner::SUMMARY_BYTES`]) +/// and, when the full output file is larger than the inline tail, appends +/// the file path so the caller can read the rest with the `Read` tool. #[must_use] fn format_bash_status(id: &str) -> String { use std::fmt::Write as _; @@ -447,16 +451,31 @@ fn format_bash_status(id: &str) -> String { let _ = write!(out, ", took {}s", t - s); } } + + // Inline tail for stdout. + let out_path = crate::bash_runner::task_out_path(id); + let out_file_len = std::fs::metadata(&out_path).map(|m| m.len()).unwrap_or(0); if let Some(ref stdout) = task.stdout_tail { if !stdout.trim().is_empty() { let _ = write!(out, "\n\nstdout:\n```\n{}\n```", stdout.trim()); } } + if out_file_len > crate::bash_runner::SUMMARY_BYTES as u64 { + let _ = write!(out, "\n\nFull stdout lives in `{}`", out_path.display()); + } + + // Inline tail for stderr. + let err_path = crate::bash_runner::task_err_path(id); + let err_file_len = std::fs::metadata(&err_path).map(|m| m.len()).unwrap_or(0); if let Some(ref stderr) = task.stderr_tail { if !stderr.trim().is_empty() { let _ = write!(out, "\n\nstderr:\n```\n{}\n```", stderr.trim()); } } + if err_file_len > crate::bash_runner::SUMMARY_BYTES as u64 { + let _ = write!(out, "\n\nFull stderr lives in `{}`", err_path.display()); + } + out }