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) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-25 11:38:34 -04:00
parent 1dfcc699c6
commit 2203bb8d9e
No known key found for this signature in database
7 changed files with 32 additions and 20 deletions

View file

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

View file

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

View file

@ -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"))?;

View file

@ -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<SandboxProvider> 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);

View file

@ -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")]

View file

@ -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::<SandboxProvider>().unwrap(),
SandboxProvider::Local
);
#[cfg(feature = "exedev")]
{
assert_eq!(
"exe".parse::<SandboxProvider>().unwrap(),
SandboxProvider::Exe
);
assert_eq!(
"EXE".parse::<SandboxProvider>().unwrap(),
SandboxProvider::Exe
);
}
assert_eq!(
"exe".parse::<SandboxProvider>().unwrap(),
SandboxProvider::Exe
);
assert_eq!(
"EXE".parse::<SandboxProvider>().unwrap(),
SandboxProvider::Exe
);
assert_eq!(
"ssh".parse::<SandboxProvider>().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");
}

View file

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