feat(#1104): surface full output file path in bash_status when truncated

This commit is contained in:
damocles 2026-06-03 10:54:37 +02:00 committed by mara
commit 60047eb01d
2 changed files with 37 additions and 2 deletions

View file

@ -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)
// ---------------------------------------------------------------------------

View file

@ -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
}