From 2203bb8d9e8129d63302680f6feecb87e73d697e Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Wed, 25 Mar 2026 11:38:34 -0400 Subject: [PATCH] refactor(sandbox): move SandboxProvider from fabro-workflows to fabro-sandbox The enum had zero internal usage in fabro-workflows and naturally belongs in fabro-sandbox alongside the sandbox implementations. Removed cfg gates from the Exe variant (it's just a tag) and added non-exedev fallback arms in fabro-cli to handle feature unification from fabro-api. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crates/fabro-api/src/serve.rs | 2 +- lib/crates/fabro-cli/src/commands/create.rs | 2 +- lib/crates/fabro-cli/src/commands/resume.rs | 10 +++++++- lib/crates/fabro-cli/src/commands/run.rs | 10 +++++++- lib/crates/fabro-sandbox/src/lib.rs | 4 ++++ .../src/sandbox_provider.rs | 23 +++++++------------ lib/crates/fabro-workflows/src/lib.rs | 1 - 7 files changed, 32 insertions(+), 20 deletions(-) rename lib/crates/{fabro-workflows => fabro-sandbox}/src/sandbox_provider.rs (85%) diff --git a/lib/crates/fabro-api/src/serve.rs b/lib/crates/fabro-api/src/serve.rs index 08cb3f49a..fb6055c8a 100644 --- a/lib/crates/fabro-api/src/serve.rs +++ b/lib/crates/fabro-api/src/serve.rs @@ -15,9 +15,9 @@ use crate::jwt_auth::{AuthMode, AuthStrategy}; use crate::server::build_router; use crate::tls::ClientAuth; use fabro_interview::Interviewer; +use fabro_sandbox::SandboxProvider; use fabro_workflows::handler::default_registry; use fabro_workflows::handler::llm::AgentApiBackend; -use fabro_workflows::sandbox_provider::SandboxProvider; #[derive(Args)] pub struct ServeArgs { diff --git a/lib/crates/fabro-cli/src/commands/create.rs b/lib/crates/fabro-cli/src/commands/create.rs index eecc56265..3bf033a8a 100644 --- a/lib/crates/fabro-cli/src/commands/create.rs +++ b/lib/crates/fabro-cli/src/commands/create.rs @@ -2,8 +2,8 @@ use std::path::PathBuf; use chrono::Utc; use fabro_config::config::FabroConfig; +use fabro_sandbox::SandboxProvider; use fabro_workflows::records::RunRecord; -use fabro_workflows::sandbox_provider::SandboxProvider; use super::run::{ cached_graph_path, default_run_dir, prepare_workflow, write_run_config_snapshot, RunArgs, diff --git a/lib/crates/fabro-cli/src/commands/resume.rs b/lib/crates/fabro-cli/src/commands/resume.rs index 9fdbe9844..c9e21f44b 100644 --- a/lib/crates/fabro-cli/src/commands/resume.rs +++ b/lib/crates/fabro-cli/src/commands/resume.rs @@ -9,6 +9,7 @@ use fabro_agent::{DockerSandbox, DockerSandboxConfig, Sandbox, WorktreeConfig, W use fabro_config::config::FabroConfig; use fabro_interview::{AutoApproveInterviewer, ConsoleInterviewer, Interviewer}; use fabro_model::{Catalog, Provider}; +use fabro_sandbox::SandboxProvider; use fabro_util::terminal::Styles; use fabro_workflows::event::{EventEmitter, RunNoticeLevel}; use fabro_workflows::handler::llm::{AgentApiBackend, AgentCliBackend, BackendRouter}; @@ -22,7 +23,6 @@ use fabro_workflows::pipeline::{ use fabro_workflows::records::Checkpoint; use fabro_workflows::records::RunRecord; use fabro_workflows::run_settings::{GitCheckpointSettings, LifecycleConfig, RunSettings}; -use fabro_workflows::sandbox_provider::SandboxProvider; use super::detached_support::{DetachedRunBootstrapGuard, DetachedRunCompletionGuard}; use super::run::{ @@ -409,6 +409,10 @@ async fn prepare_from_checkpoint( })); Arc::new(env) } + #[cfg(not(feature = "exedev"))] + SandboxProvider::Exe => { + anyhow::bail!("exe sandbox requires the exedev feature"); + } SandboxProvider::Ssh => { let config = resolve_ssh_config(run_cfg.as_ref(), run_defaults) .ok_or_else(|| anyhow::anyhow!("--sandbox ssh requires [sandbox.ssh] config"))?; @@ -828,6 +832,10 @@ async fn prepare_from_branch( })); (Arc::new(env), None) } + #[cfg(not(feature = "exedev"))] + SandboxProvider::Exe => { + anyhow::bail!("exe sandbox requires the exedev feature"); + } SandboxProvider::Ssh => { let config = resolve_ssh_config(run_cfg.as_ref(), run_defaults) .ok_or_else(|| anyhow::anyhow!("--sandbox ssh requires [sandbox.ssh] config"))?; diff --git a/lib/crates/fabro-cli/src/commands/run.rs b/lib/crates/fabro-cli/src/commands/run.rs index 541b748cc..79508351a 100644 --- a/lib/crates/fabro-cli/src/commands/run.rs +++ b/lib/crates/fabro-cli/src/commands/run.rs @@ -14,6 +14,7 @@ use fabro_config::config::FabroConfig; use fabro_config::{project as project_config, run as run_config, sandbox as sandbox_config}; use fabro_interview::{AutoApproveInterviewer, ConsoleInterviewer, FileInterviewer, Interviewer}; use fabro_model::{Catalog, FallbackTarget, Provider}; +use fabro_sandbox::SandboxProvider; use fabro_util::terminal::Styles; use fabro_workflows::devcontainer_bridge; use fabro_workflows::event::{EventEmitter, RunNoticeLevel, WorkflowRunEvent}; @@ -30,7 +31,6 @@ use fabro_workflows::pipeline::{ }; use fabro_workflows::records::Checkpoint; use fabro_workflows::run_settings::{GitCheckpointSettings, LifecycleConfig, RunSettings}; -use fabro_workflows::sandbox_provider::SandboxProvider; use indicatif::HumanDuration; use std::time::Duration; use tracing::debug; @@ -72,6 +72,8 @@ impl From for CliSandboxProvider { SandboxProvider::Daytona => Self::Daytona, #[cfg(feature = "exedev")] SandboxProvider::Exe => Self::Exe, + #[cfg(not(feature = "exedev"))] + SandboxProvider::Exe => Self::Local, SandboxProvider::Ssh => Self::Ssh, } } @@ -1485,6 +1487,10 @@ async fn run_command_impl( })); Arc::new(env) } + #[cfg(not(feature = "exedev"))] + SandboxProvider::Exe => { + bail!("exe sandbox requires the exedev feature"); + } SandboxProvider::Ssh => { let config = ssh_config .clone() @@ -2201,6 +2207,8 @@ async fn run_preflight( Err(e) => Err(format!("exe.dev SSH connection failed: {e}")), } } + #[cfg(not(feature = "exedev"))] + SandboxProvider::Exe => Err("exe sandbox requires the exedev feature".to_string()), SandboxProvider::Ssh => match ssh_config { Some(config) => { let clone_params = resolve_ssh_clone_params(&original_cwd); diff --git a/lib/crates/fabro-sandbox/src/lib.rs b/lib/crates/fabro-sandbox/src/lib.rs index a1d31fb07..b0e414e20 100644 --- a/lib/crates/fabro-sandbox/src/lib.rs +++ b/lib/crates/fabro-sandbox/src/lib.rs @@ -2,6 +2,8 @@ pub mod sandbox; pub mod read_guard; +pub mod sandbox_provider; + pub mod worktree; #[cfg(feature = "ssh")] @@ -35,6 +37,8 @@ pub use sandbox::{ pub use read_guard::ReadBeforeWriteSandbox; +pub use sandbox_provider::SandboxProvider; + pub use worktree::{WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox}; #[cfg(feature = "local")] diff --git a/lib/crates/fabro-workflows/src/sandbox_provider.rs b/lib/crates/fabro-sandbox/src/sandbox_provider.rs similarity index 85% rename from lib/crates/fabro-workflows/src/sandbox_provider.rs rename to lib/crates/fabro-sandbox/src/sandbox_provider.rs index 6205e2224..6e3ae6169 100644 --- a/lib/crates/fabro-workflows/src/sandbox_provider.rs +++ b/lib/crates/fabro-sandbox/src/sandbox_provider.rs @@ -12,7 +12,6 @@ pub enum SandboxProvider { /// Run tools inside a Daytona cloud sandbox Daytona, /// Run tools inside an exe.dev VM - #[cfg(feature = "exedev")] Exe, /// Run tools on a user-provided SSH host Ssh, @@ -26,7 +25,6 @@ impl fmt::Display for SandboxProvider { Self::Local => write!(f, "local"), Self::Docker => write!(f, "docker"), Self::Daytona => write!(f, "daytona"), - #[cfg(feature = "exedev")] Self::Exe => write!(f, "exe"), Self::Ssh => write!(f, "ssh"), } @@ -41,7 +39,6 @@ impl FromStr for SandboxProvider { "local" => Ok(Self::Local), "docker" => Ok(Self::Docker), "daytona" => Ok(Self::Daytona), - #[cfg(feature = "exedev")] "exe" => Ok(Self::Exe), "ssh" => Ok(Self::Ssh), other => Err(format!("unknown sandbox provider: {other}")), @@ -76,17 +73,14 @@ mod tests { "LOCAL".parse::().unwrap(), SandboxProvider::Local ); - #[cfg(feature = "exedev")] - { - assert_eq!( - "exe".parse::().unwrap(), - SandboxProvider::Exe - ); - assert_eq!( - "EXE".parse::().unwrap(), - SandboxProvider::Exe - ); - } + assert_eq!( + "exe".parse::().unwrap(), + SandboxProvider::Exe + ); + assert_eq!( + "EXE".parse::().unwrap(), + SandboxProvider::Exe + ); assert_eq!( "ssh".parse::().unwrap(), SandboxProvider::Ssh @@ -103,7 +97,6 @@ mod tests { assert_eq!(SandboxProvider::Local.to_string(), "local"); assert_eq!(SandboxProvider::Docker.to_string(), "docker"); assert_eq!(SandboxProvider::Daytona.to_string(), "daytona"); - #[cfg(feature = "exedev")] assert_eq!(SandboxProvider::Exe.to_string(), "exe"); assert_eq!(SandboxProvider::Ssh.to_string(), "ssh"); } diff --git a/lib/crates/fabro-workflows/src/lib.rs b/lib/crates/fabro-workflows/src/lib.rs index 3ddeb7602..40d02fcad 100644 --- a/lib/crates/fabro-workflows/src/lib.rs +++ b/lib/crates/fabro-workflows/src/lib.rs @@ -112,7 +112,6 @@ pub mod run_lookup; pub mod run_settings; pub mod run_status; pub mod sandbox_git; -pub mod sandbox_provider; pub mod sandbox_reconnect; #[doc(hidden)] pub mod test_support;