From 6da71be2fabd8ddb07168647541a9df50cf20275 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 30 Apr 2026 21:49:50 -0400 Subject: [PATCH] fix(sandbox): forward exec_command_streaming through WorktreeSandbox --- lib/crates/fabro-sandbox/src/sandbox.rs | 9 +++++++ lib/crates/fabro-sandbox/src/worktree.rs | 30 +++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/crates/fabro-sandbox/src/sandbox.rs b/lib/crates/fabro-sandbox/src/sandbox.rs index 5a8558011..d2c26751b 100644 --- a/lib/crates/fabro-sandbox/src/sandbox.rs +++ b/lib/crates/fabro-sandbox/src/sandbox.rs @@ -509,6 +509,15 @@ pub trait Sandbox: Send + Sync { env_vars: Option<&std::collections::HashMap>, cancel_token: Option, ) -> crate::Result; + /// Stream a command's output as it runs. + /// + /// **Production sandboxes must override this.** The default falls back to + /// the non-streaming [`exec_command`](Self::exec_command) and replays its + /// output through `output_callback` at the end, marking + /// `live_streaming: false`. That's the right behavior for test mocks but + /// silently drops live output for any real sandbox that wraps another — + /// decorators in particular must forward to the inner sandbox's streaming + /// implementation rather than relying on this default. async fn exec_command_streaming( &self, command: &str, diff --git a/lib/crates/fabro-sandbox/src/worktree.rs b/lib/crates/fabro-sandbox/src/worktree.rs index 08e5590d6..f412c87d4 100644 --- a/lib/crates/fabro-sandbox/src/worktree.rs +++ b/lib/crates/fabro-sandbox/src/worktree.rs @@ -6,7 +6,10 @@ use async_trait::async_trait; use tokio_util::sync::CancellationToken; use crate::sandbox::fetch_source_run_ref; -use crate::{DirEntry, ExecResult, GitRunInfo, GitSetupIntent, GrepOptions, Sandbox, shell_quote}; +use crate::{ + CommandOutputCallback, DirEntry, ExecResult, ExecStreamingResult, GitRunInfo, GitSetupIntent, + GrepOptions, Sandbox, shell_quote, +}; /// Git command prefix that disables background maintenance. const GIT: &str = "git -c maintenance.auto=0 -c gc.auto=0"; @@ -227,6 +230,31 @@ impl Sandbox for WorktreeSandbox { .await } + /// Stream a command's output, forwarding to the inner sandbox's streaming + /// implementation so live output and `streams_separated` / `live_streaming` + /// flags survive the worktree wrapping. + async fn exec_command_streaming( + &self, + command: &str, + timeout_ms: u64, + working_dir: Option<&str>, + env_vars: Option<&HashMap>, + cancel_token: Option, + output_callback: CommandOutputCallback, + ) -> crate::Result { + let wd = working_dir.unwrap_or(&self.config.worktree_path); + self.inner + .exec_command_streaming( + command, + timeout_ms, + Some(wd), + env_vars, + cancel_token, + output_callback, + ) + .await + } + // --- Delegated methods --- async fn read_file(