Serve the local sandbox through the driver Host provider

SandboxSpec::Local and run reconnect now build a DriverSandbox over the
sandbox-driver Host provider instead of fabro's own LocalSandbox, which
is deleted. fabro_sandbox::local_sandbox designates the working
directory (created when missing, never removed), creates the Host handle
in a per-process registry, and learns the platform up front. A local
sandbox reports no provider id: it is its directory, which the run
record already carries, so reconnect rebuilds the handle over that
directory rather than by id.

The credential filter for explicit environment variables and the Bash
readiness probe now come from the exec layer and the driver's activate
helper. Test call sites move to the async constructor; test factories
that must stay synchronous share the parent session's sandbox handle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-09 16:09:45 -06:00
parent ef69883fdd
commit ba3adb92c7
No known key found for this signature in database
36 changed files with 663 additions and 2550 deletions

View file

@ -1824,13 +1824,15 @@ reasoning = false
]);
}
#[test]
fn ask_fabro_prompt_lists_effective_tools_without_denied_tools() {
#[tokio::test]
async fn ask_fabro_prompt_lists_effective_tools_without_denied_tools() {
let registry = ask_fabro_test_registry();
let policy = build_ask_fabro_tool_access_policy();
let prompt = build_ask_fabro_system_prompt(
&fabro_agent::LocalSandbox::new(std::env::current_dir().unwrap()),
&fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
&fabro_agent::EnvContext::default(),
&[],
None,
@ -1874,8 +1876,8 @@ reasoning = false
assert!(prompt.contains("Use workspace file tools only when the question asks"));
}
#[test]
fn ask_fabro_prompt_keeps_tool_descriptions_inert() {
#[tokio::test]
async fn ask_fabro_prompt_keeps_tool_descriptions_inert() {
let mut registry = ToolRegistry::new();
let mut tool = stub_tool("read_file");
tool.definition.description = "{{ inputs.env_block }}".to_string();
@ -1883,7 +1885,9 @@ reasoning = false
let policy = build_ask_fabro_tool_access_policy();
let prompt = build_ask_fabro_system_prompt(
&fabro_agent::LocalSandbox::new(std::env::current_dir().unwrap()),
&fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
&fabro_agent::EnvContext::default(),
&[],
None,
@ -2030,9 +2034,11 @@ reasoning = false
tool_exposure_mode: ToolExposureMode::AutoApprovedOnly,
..SessionOptions::default()
};
let sandbox: Arc<dyn fabro_agent::Sandbox> = Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap(),
));
let sandbox: Arc<dyn fabro_agent::Sandbox> = Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
);
for tool_name in denied_tools {
let result = fabro_agent::tool_execution::execute_and_emit_one_tool(

View file

@ -10,7 +10,7 @@ use fabro_acp::{
run_acp_turn,
};
use fabro_sandbox::test_support::{MockSandbox, MockStdioProcess};
use fabro_sandbox::{LocalSandbox, Sandbox, shell_quote};
use fabro_sandbox::{Sandbox, local_sandbox, shell_quote};
use fabro_types::SteeringMessage;
use fabro_util::error::collect_chain;
use tokio::fs::{read_to_string, write};
@ -104,7 +104,11 @@ async fn session_lifecycle_initializes_sends_prompt_and_aggregates_text() {
let raw_command = format!("python3 {}", shell_quote(&script_path.to_string_lossy()));
let command = AcpProcessSpec::from_command_attr(&raw_command).expect("parse ACP command");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempdir.path().to_path_buf())
.await
.expect("local sandbox should be created"),
);
let result = run_acp_turn(AcpRunRequest {
command,
@ -145,7 +149,11 @@ async fn steering_sends_followup_session_prompt_over_acp() {
let raw_command = format!("python3 {}", shell_quote(&script_path.to_string_lossy()));
let command = AcpProcessSpec::from_command_attr(&raw_command).expect("parse ACP command");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempdir.path().to_path_buf())
.await
.expect("local sandbox should be created"),
);
let control_handle = AcpControlHandle::new();
let handle_for_activity = control_handle.clone();
let queued = Arc::new(AtomicBool::new(false));
@ -208,7 +216,11 @@ async fn interrupt_then_steer_sends_cancel_then_followup_session_prompt_over_acp
let raw_command = format!("python3 {}", shell_quote(&script_path.to_string_lossy()));
let command = AcpProcessSpec::from_command_attr(&raw_command).expect("parse ACP command");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempdir.path().to_path_buf())
.await
.expect("local sandbox should be created"),
);
let control_handle = AcpControlHandle::new();
let handle_for_activity = control_handle.clone();
let queued = Arc::new(AtomicBool::new(false));
@ -283,7 +295,11 @@ async fn inline_interrupt_terminates_agent_that_ignores_cancel() {
let raw_command = format!("python3 {}", shell_quote(&script_path.to_string_lossy()));
let command = AcpProcessSpec::from_command_attr(&raw_command).expect("parse ACP command");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempdir.path().to_path_buf())
.await
.expect("local sandbox should be created"),
);
let control_handle = AcpControlHandle::new();
let handle_for_activity = control_handle.clone();
let interrupted = Arc::new(AtomicBool::new(false));
@ -670,7 +686,11 @@ async fn run_fake_agent_with_activity(
.expect("write fake ACP agent");
let raw_command = format!("python3 {}", shell_quote(&script_path.to_string_lossy()));
let command = AcpProcessSpec::from_command_attr(&raw_command).expect("parse ACP command");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempdir.to_path_buf())
.await
.expect("local sandbox should be created"),
);
env.entry("LC_ALL".to_string())
.or_insert_with(|| "C".to_string());

View file

@ -509,7 +509,7 @@ mod tests {
use tokio_util::sync::CancellationToken;
use super::*;
use crate::LocalSandbox;
use crate::local_sandbox;
use crate::test_support::MutableMockSandbox;
use crate::tool_registry::ToolContext;
@ -906,7 +906,7 @@ mod tests {
fs::write(&path, "fn hello() {\n println!(\"old\");\n}\n")
.await
.unwrap();
let env = LocalSandbox::new(dir.path().to_path_buf());
let env = local_sandbox(dir.path().to_path_buf()).await.unwrap();
let patch = "\
*** Begin Patch
*** Update File: src/lib.rs
@ -1044,7 +1044,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("insert_only.txt");
fs::write(&path, "alpha\nomega\n").await.unwrap();
let env = LocalSandbox::new(dir.path().to_path_buf());
let env = local_sandbox(dir.path().to_path_buf()).await.unwrap();
let patch = "\
*** Begin Patch
*** Update File: insert_only.txt

View file

@ -33,8 +33,8 @@ use crate::subagent::{SessionFactory, SubAgentSupervisor};
use crate::tool_permissions::{is_auto_approved, tool_category};
use crate::tools::WebFetchSummarizer;
use crate::{
AgentEvent, AgentProfile, AgentProfileBuilder, LocalSandbox, Message, Sandbox, Session,
SessionOptions, SessionShutdownReason,
AgentEvent, AgentProfile, AgentProfileBuilder, Message, Sandbox, Session, SessionOptions,
SessionShutdownReason, local_sandbox,
};
#[expect(
@ -557,7 +557,11 @@ pub async fn run_with_args_and_client_and_catalog(
// Build sandbox
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cwd_str = cwd.to_string_lossy().to_string();
let env: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(cwd));
let env: Arc<dyn Sandbox> = Arc::new(
local_sandbox(cwd)
.await
.context("failed to create the local sandbox")?,
);
// Build tool approval callback
let permissions = args.permissions.unwrap_or(PermissionLevel::ReadWrite);

View file

@ -45,7 +45,7 @@ pub use event::Emitter;
pub use fabro_mcp::config::McpServerSettings;
pub use fabro_types::SteeringMessage;
pub use history::History;
pub use local_sandbox::LocalSandbox;
pub use local_sandbox::{DriverSandbox, local_sandbox};
pub use loop_detection::detect_loop;
pub use memory::{MemoryDocument, discover_memory};
pub use native_tool::{NativeTool, ToolVocabulary};

View file

@ -1,2 +1,3 @@
// Re-export from fabro-sandbox
pub use fabro_sandbox::local::LocalSandbox;
//! The host-backed sandbox fabro calls `local`, re-exported from
//! fabro-sandbox so agent consumers construct it without a second import.
pub use fabro_sandbox::{DriverSandbox, local_sandbox};

View file

@ -644,7 +644,7 @@ mod tests {
ToolAccess, ToolAccessPolicy, ToolExposureMode, ToolHookCallback, ToolHookDecision,
};
use crate::event::Emitter;
use crate::local_sandbox::LocalSandbox;
use crate::local_sandbox;
use crate::question_tools::{
AgentQuestion, AgentQuestionAnswer, AgentQuestionAnswerStatus, AgentQuestionRuntime,
AgentToolRuntime, register_question_tools,
@ -775,7 +775,11 @@ mod tests {
&tool_calls,
true,
&registry,
Arc::new(LocalSandbox::new(std::env::current_dir().unwrap())),
Arc::new(
local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
),
None,
&CancellationToken::new(),
&SessionOptions::default(),
@ -823,7 +827,11 @@ mod tests {
&tool_calls,
true,
&registry,
Arc::new(LocalSandbox::new(std::env::current_dir().unwrap())),
Arc::new(
local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
),
None,
&CancellationToken::new(),
&SessionOptions::default(),
@ -889,8 +897,12 @@ mod tests {
}
}
fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(LocalSandbox::new(std::env::current_dir().unwrap()))
async fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
)
}
#[tokio::test]
@ -910,7 +922,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
Some(&hooks),
CancellationToken::new(),
&config,
@ -941,7 +953,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
Some(&hooks),
CancellationToken::new(),
&config,
@ -969,7 +981,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
None,
CancellationToken::new(),
&SessionOptions::default(),
@ -1034,7 +1046,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
None,
CancellationToken::new(),
&SessionOptions::default(),
@ -1129,7 +1141,7 @@ mod tests {
execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
Some(&hooks),
CancellationToken::new(),
&config,
@ -1165,7 +1177,7 @@ mod tests {
execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
Some(&hooks),
CancellationToken::new(),
&config,
@ -1198,7 +1210,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
None,
CancellationToken::new(),
&config,
@ -1247,7 +1259,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
None,
CancellationToken::new(),
&config,
@ -1302,7 +1314,7 @@ mod tests {
let result = execute_and_emit_one_tool(
&tc,
&registry,
make_sandbox(),
make_sandbox().await,
None,
CancellationToken::new(),
&config,

View file

@ -748,13 +748,12 @@ mod tests {
use super::*;
use crate::config::{NativeToolOptions, SessionOptions, ToolSecrets};
use crate::event::{Emitter, SessionBoundEmitter};
use crate::local_sandbox::LocalSandbox;
use crate::sandbox::*;
use crate::test_support::MockSandbox;
use crate::tool_registry::ToolContext;
use crate::truncation;
use crate::types::SessionEvent;
use crate::web_search::make_web_search_tool_with_api_key;
use crate::{local_sandbox, truncation};
#[test]
fn core_tool_descriptions_include_actionable_guidance() {
@ -1432,9 +1431,11 @@ mod tests {
#[tokio::test]
async fn shell_reports_real_local_process_outcome() {
let tool = make_shell_tool();
let env: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(
std::env::current_dir().expect("current dir"),
));
let env: Arc<dyn Sandbox> = Arc::new(
local_sandbox(std::env::current_dir().expect("current dir"))
.await
.unwrap(),
);
let emitter = Emitter::new();
let mut receiver = emitter.subscribe();

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use fabro_agent::{AgentProfile, LocalSandbox, OpenAiProfile, Session, SessionOptions};
use fabro_agent::{AgentProfile, OpenAiProfile, Session, SessionOptions, local_sandbox};
use fabro_llm::client::Client;
use fabro_llm::provider::ProviderAdapter;
use fabro_llm::providers::OpenAiAdapter;
@ -23,7 +23,7 @@ async fn openai_twin_compaction_preserves_tool_call_pairs() {
load_compaction_scenarios(&api_key).await;
let mut session = make_openai_session(tmp.path(), base_url, api_key);
let mut session = make_openai_session(tmp.path(), base_url, api_key).await;
session.initialize().await.unwrap();
let result = session
@ -44,14 +44,18 @@ async fn openai_twin_compaction_preserves_tool_call_pairs() {
);
}
fn make_openai_session(cwd: &Path, base_url: String, api_key: String) -> Session {
async fn make_openai_session(cwd: &Path, base_url: String, api_key: String) -> Session {
let adapter: Arc<dyn ProviderAdapter> =
Arc::new(OpenAiAdapter::new(api_key).with_base_url(base_url));
let mut providers = HashMap::new();
providers.insert(ProviderId::OPENAI.to_string(), adapter);
let client = Client::new(providers, Some(ProviderId::OPENAI.to_string()), Vec::new());
let profile: Arc<dyn AgentProfile> = Arc::new(OpenAiProfile::new(MODEL));
let sandbox = Arc::new(LocalSandbox::new(cwd.to_path_buf()));
let sandbox = Arc::new(
local_sandbox(cwd.to_path_buf())
.await
.expect("local sandbox should be created"),
);
let options = SessionOptions {
enable_context_compaction: true,
compaction_threshold_percent: 80,

View file

@ -10,8 +10,8 @@ use std::sync::Arc;
use fabro_agent::subagent::SessionFactory;
use fabro_agent::{
AgentEvent, AgentProfile, AgentProfileBuilder, LocalSandbox, OpenAiProfile, Session,
SessionOptions, SubAgentSupervisor, ToolSecrets, WebFetchSummarizer,
AgentEvent, AgentProfile, AgentProfileBuilder, OpenAiProfile, Session, SessionOptions,
SubAgentSupervisor, ToolSecrets, WebFetchSummarizer, local_sandbox,
};
use fabro_auth::EnvCredentialSource;
use fabro_llm::client::Client;
@ -82,21 +82,25 @@ async fn make_session(
let client = make_client(&provider, twin.as_ref()).await;
let profile_builder = profile_builder(&provider, model, &client, tool_secrets);
let mut profile = profile_builder.build();
let env = Arc::new(LocalSandbox::new(cwd.to_path_buf()));
let env: Arc<dyn fabro_agent::Sandbox> = Arc::new(
local_sandbox(cwd.to_path_buf())
.await
.expect("local sandbox should be created"),
);
// Register subagent tools so spawn_agent / wait / send_input / close_agent are
// available
// available. Subagents share the parent's sandbox: same directory, same
// host, and a session factory is synchronous.
let supervisor = SubAgentSupervisor::new(3);
let factory_client = client.clone();
let factory_cwd = cwd.to_path_buf();
let factory_env = Arc::clone(&env);
let factory_profile_builder = profile_builder;
let factory: SessionFactory = Arc::new(move || {
let sub_profile: Arc<dyn AgentProfile> = Arc::from(factory_profile_builder.build());
let sub_env = Arc::new(LocalSandbox::new(factory_cwd.clone()));
Session::new(
factory_client.clone(),
sub_profile,
sub_env,
Arc::clone(&factory_env),
SessionOptions::default(),
None,
)
@ -123,7 +127,11 @@ async fn make_session_with_config(
let client = make_client(&provider, twin.as_ref()).await;
let profile: Arc<dyn AgentProfile> =
Arc::from(profile_builder(&provider, model, &client, ToolSecrets::default()).build());
let env = Arc::new(LocalSandbox::new(cwd.to_path_buf()));
let env = Arc::new(
local_sandbox(cwd.to_path_buf())
.await
.expect("local sandbox should be created"),
);
Session::new(client, profile, env, config, None)
}
@ -158,7 +166,7 @@ fn make_openai_compatible_twin_client(provider: &Provider, twin: &OpenAiTwinOpti
Client::new(providers, Some(provider_name), Vec::new())
}
fn make_openai_compatible_twin_session(
async fn make_openai_compatible_twin_session(
provider: Provider,
model: &str,
cwd: &Path,
@ -182,7 +190,11 @@ fn make_openai_compatible_twin_session(
);
let profile: Arc<dyn AgentProfile> =
Arc::new(OpenAiProfile::new(model).with_route(provider, catalog));
let env = Arc::new(LocalSandbox::new(cwd.to_path_buf()));
let env = Arc::new(
local_sandbox(cwd.to_path_buf())
.await
.expect("local sandbox should be created"),
);
Session::new(client, profile, env, config, None)
}
@ -342,7 +354,8 @@ async fn openai_compatible_twin_uses_json_edit_file_tool() {
tmp.path(),
SessionOptions::default(),
&twin,
);
)
.await;
session.initialize().await.unwrap();
let mut rx = session.subscribe();

View file

@ -129,10 +129,12 @@ mod tests {
}
}
fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap(),
))
async fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
)
}
fn make_bridge(
@ -162,7 +164,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let bridge = make_bridge(runner, sandbox, HookExecutionContext::default());
bridge
@ -194,7 +196,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let bridge = make_bridge(runner, sandbox, HookExecutionContext::default());
let decision = bridge.pre_tool_use("shell", &serde_json::json!({})).await;
@ -214,7 +216,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let bridge = make_bridge(runner, sandbox, HookExecutionContext::default());
let decision = bridge.pre_tool_use("shell", &serde_json::json!({})).await;
@ -233,7 +235,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PostToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let bridge = make_bridge(runner, sandbox, HookExecutionContext::default());
bridge
@ -263,7 +265,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PostToolUseFailure)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let bridge = make_bridge(runner, sandbox, HookExecutionContext::default());
bridge
@ -294,7 +296,7 @@ mod tests {
hooks: vec![make_hook(HookEvent::PreToolUse)],
};
let runner = Arc::new(HookRunner::with_executor(config, executor));
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let hook_execution_context = HookExecutionContext {
host_source_dir: Some(PathBuf::from("/host/source")),
sandbox_work_dir: Some(PathBuf::from("/supplied/sandbox")),

View file

@ -740,10 +740,12 @@ mod tests {
HookContext::new(HookEvent::StageStart, fixtures::RUN_1, "test-wf".into())
}
fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap(),
))
async fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
)
}
fn test_llm_source() -> Arc<dyn CredentialSource> {
@ -833,7 +835,7 @@ mod tests {
let executor = HookExecutorImpl;
let def = make_definition("exit 0");
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -854,7 +856,7 @@ mod tests {
let executor = HookExecutorImpl;
let def = make_definition("exit 1");
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -874,7 +876,7 @@ mod tests {
let executor = HookExecutorImpl;
let def = make_definition("exit 2");
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -894,7 +896,7 @@ mod tests {
let executor = HookExecutorImpl;
let def = make_definition(r#"echo '{"decision": "skip", "reason": "test skip"}'"#);
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -918,7 +920,7 @@ mod tests {
let def = make_definition("echo $ARC_EVENT:$ARC_RUN_ID:$ARC_WORKFLOW");
let mut ctx = make_context();
ctx.node_id = Some("plan".into());
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -947,7 +949,7 @@ mod tests {
sandbox: Some(false),
};
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -1433,7 +1435,7 @@ mod tests {
sandbox: Some(false),
};
let ctx = make_context();
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let source = test_llm_source();
let result = executor
.execute(
@ -1453,7 +1455,7 @@ mod tests {
#[tokio::test]
async fn command_hook_missing_env_blocks() {
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = HookExecutorImpl::execute_command(
&make_definition("echo {{ env.MISSING_HOOK_VALUE }}"),
&interp("echo {{ env.MISSING_HOOK_VALUE }}"),
@ -1502,7 +1504,7 @@ mod tests {
None,
Some(1),
&make_context(),
make_sandbox(),
make_sandbox().await,
test_llm_source().as_ref(),
test_catalog(),
)

View file

@ -267,10 +267,12 @@ mod tests {
}
}
fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap(),
))
async fn make_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
)
}
fn make_context(event: HookEvent) -> HookContext {
@ -302,7 +304,7 @@ mod tests {
async fn no_hooks_returns_proceed() {
let runner = HookRunner::new(HookSettings::default(), test_llm_source(), test_catalog());
let ctx = make_context(HookEvent::RunStart);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;
@ -414,7 +416,7 @@ mod tests {
}),
);
let ctx = make_context(HookEvent::RunStart);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;
@ -435,7 +437,7 @@ mod tests {
}),
);
let ctx = make_context(HookEvent::StageStart);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;
@ -456,7 +458,7 @@ mod tests {
}),
);
let ctx = make_context(HookEvent::StageComplete);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;
@ -475,7 +477,7 @@ mod tests {
};
let runner = HookRunner::new(config, test_llm_source(), test_catalog());
let ctx = make_context(HookEvent::RunStart);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;
@ -493,7 +495,7 @@ mod tests {
};
let runner = HookRunner::new(config, test_llm_source(), test_catalog());
let ctx = make_context(HookEvent::RunStart);
let sandbox = make_sandbox();
let sandbox = make_sandbox().await;
let decision = runner
.run(&ctx, sandbox.clone(), HookExecutionContext::default())
.await;

View file

@ -1,7 +1,7 @@
use std::path::Path;
use std::sync::Arc;
use fabro_agent::{LocalSandbox, Sandbox};
use fabro_agent::{Sandbox, local_sandbox};
use fabro_auth::{CredentialSource, test_support};
use fabro_hooks::{
HookContext, HookDecision, HookDefinition, HookEvent, HookExecutionContext, HookRunner,
@ -19,10 +19,12 @@ fn test_catalog() -> Arc<Catalog> {
Arc::new(Catalog::from_builtin().expect("default catalog should build"))
}
fn local_sandbox() -> Arc<dyn Sandbox> {
Arc::new(LocalSandbox::new(
std::env::current_dir().expect("test process should have a cwd"),
))
async fn test_sandbox() -> Arc<dyn Sandbox> {
Arc::new(
local_sandbox(std::env::current_dir().expect("test process should have a cwd"))
.await
.expect("local sandbox should be created"),
)
}
#[tokio::test]
@ -62,7 +64,7 @@ async fn host_command_hook_uses_host_workdir_not_sandbox_workdir() {
);
let decision = runner
.run(&context, local_sandbox(), HookExecutionContext {
.run(&context, test_sandbox().await, HookExecutionContext {
host_source_dir: Some(host_work_dir.clone()),
sandbox_work_dir: Some(container_only_work_dir.to_path_buf()),
})

View file

@ -12,18 +12,46 @@
//! results relative to a caller-declared base).
use std::collections::HashMap;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
use async_trait::async_trait;
use fabro_types::SandboxProviderKind;
use sandbox_driver::{
FileKind, LifecycleTimers, Sandbox as DriverHandle, SandboxState, Search as _, WaitOptions,
FileKind, LifecycleTimers, Sandbox as DriverHandle, SandboxProvider as _, SandboxSource,
SandboxSpec as DriverSpec, SandboxState, Search as _, WaitOptions,
};
use sandbox_driver_host::HostProvider;
use tokio::fs;
use tokio_util::sync::CancellationToken;
use crate::RetryPlan;
/// A sandbox on the worker host at `working_directory`, the fabro `local`
/// kind, served by the driver's in-process Host provider.
///
/// The directory is designated: the sandbox uses it in place and never
/// removes it. It is created when missing so a run can point at a fresh
/// scratch path. The registry lives in a per-process temporary root, so a
/// later process rebuilds the handle by calling this again with the
/// persisted working directory rather than by id.
pub async fn local_sandbox(working_directory: impl Into<PathBuf>) -> crate::Result<DriverSandbox> {
let working_directory: PathBuf = working_directory.into();
fs::create_dir_all(&working_directory)
.await
.map_err(|error| crate::Error::context("Failed to create working directory", error))?;
let provider = HostProvider::new();
let spec = DriverSpec::new(SandboxSource::HostDirectory)
.working_directory(working_directory.display().to_string());
let handle = provider
.create(&spec, None)
.await
.map_err(|error| crate::Error::context("Failed to create local sandbox", error))?;
let sandbox = DriverSandbox::new(SandboxProviderKind::LOCAL, handle);
sandbox.learn_platform().await?;
Ok(sandbox)
}
use crate::exec::{ExplicitEnvPolicy, SandboxExec};
use crate::sandbox::{
self, DirEntry, ExecResult, ExecStreamingRequest, ExecStreamingResult, GrepOptions, PushError,
@ -107,6 +135,12 @@ impl DriverSandbox {
/// platform. Shared by initialize and start.
async fn make_ready(&self) -> crate::Result<()> {
sandbox_driver::activate(self.handle.as_ref(), &WaitOptions::default()).await?;
self.learn_platform().await
}
/// Ask the sandbox for its platform once; `platform` and `os_version`
/// report `unknown` until this has run.
async fn learn_platform(&self) -> crate::Result<()> {
if self.platform.get().is_none() {
let info = self.handle.platform_info().await?;
let platform = fabro_platform_name(&info.os).to_string();
@ -355,7 +389,7 @@ impl Sandbox for DriverSandbox {
Ok(()) => self.emit(SandboxEvent::Ready {
provider: self.provider_name(),
duration_ms,
name: Some(self.handle.id().to_string()),
name: Some(self.sandbox_info()).filter(|name| !name.is_empty()),
cpu: None,
memory: None,
url: None,
@ -484,8 +518,15 @@ impl Sandbox for DriverSandbox {
)
}
/// The provider's id for this sandbox, or empty for `local`: a local
/// sandbox is its working directory, which the run record already
/// carries, and its Host registry id does not outlive the process.
fn sandbox_info(&self) -> String {
self.handle.id().to_string()
if self.kind.is_local() {
String::new()
} else {
self.handle.id().to_string()
}
}
async fn set_autostop_interval(&self, minutes: i32) -> crate::Result<()> {
@ -788,8 +829,12 @@ mod tests {
assert!(f.sandbox.os_version().starts_with(expected));
assert_eq!(
f.sandbox.sandbox_info(),
f.sandbox.handle().id().to_string()
"",
"local sandboxes are identified by directory"
);
let isolated =
DriverSandbox::new(SandboxProviderKind::DOCKER, Arc::clone(f.sandbox.handle()));
assert_eq!(isolated.sandbox_info(), f.sandbox.handle().id().to_string());
f.sandbox.stop().await.unwrap();
f.sandbox.activate().await.unwrap();
@ -831,6 +876,22 @@ mod tests {
}));
}
#[tokio::test]
async fn local_sandbox_designates_the_directory_and_knows_its_platform() {
let dir = tempfile::tempdir().unwrap();
let workspace = dir.path().join("fresh");
let sandbox = local_sandbox(&workspace).await.unwrap();
assert!(workspace.is_dir(), "a missing working directory is created");
assert_eq!(sandbox.kind(), &SandboxProviderKind::LOCAL);
assert_ne!(sandbox.platform(), "unknown");
assert_eq!(
Path::new(sandbox.working_directory()),
workspace.canonicalize().unwrap()
);
sandbox.cleanup().await.unwrap();
assert!(workspace.is_dir());
}
#[tokio::test]
async fn preview_urls_come_from_the_access_facet() {
let f = fixture().await;

View file

@ -29,8 +29,6 @@ pub mod reconnect;
pub mod terminal;
pub mod local;
#[cfg(feature = "docker")]
pub mod docker;
@ -43,7 +41,7 @@ pub mod test_support;
pub use details::sandbox_details;
#[cfg(feature = "docker")]
pub use docker::{DockerSandbox, DockerSandboxOptions};
pub use driver_sandbox::DriverSandbox;
pub use driver_sandbox::{DriverSandbox, local_sandbox};
pub use error::{Error, Result, default_redacted_output_tail, display_for_log};
pub use exec::{ExplicitEnvPolicy, SandboxExec, is_sensitive_env_var};
pub use fabro_github::token_source::{
@ -53,7 +51,6 @@ pub use fabro_types::{RunSandboxInstance, SandboxProviderKind};
pub use git_retry::{
CredentialContext, GitRetryReason, RetryPlan, classify_failure, retry_git_operation,
};
pub use local::LocalSandbox;
#[cfg(feature = "daytona")]
pub use provider::daytona::DaytonaSandboxProvider;
#[cfg(feature = "docker")]

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@ use crate::SandboxEventCallback;
use crate::daytona::DaytonaSandbox;
#[cfg(feature = "docker")]
use crate::docker::DockerSandbox;
use crate::local::LocalSandbox;
use crate::driver_sandbox::local_sandbox;
/// Reconnect to a sandbox from a saved record.
///
@ -54,8 +54,13 @@ pub async fn reconnect_for_run_with_callback(
) -> Result<Box<dyn crate::Sandbox>> {
let runtime = &record.runtime;
match record.provider.bundled() {
// A local sandbox is its working directory: rebuilding the handle
// over that directory is the reconnect. The per-process Host
// registry holds no state worth attaching to.
Some(BundledProvider::Local) => {
let mut sandbox = LocalSandbox::new(PathBuf::from(&runtime.working_directory));
let mut sandbox = local_sandbox(PathBuf::from(&runtime.working_directory))
.await
.context("Failed to reconnect local sandbox")?;
if let Some(callback) = event_callback {
sandbox.set_event_callback(callback);
}

View file

@ -1,7 +1,6 @@
use std::path::PathBuf;
use std::sync::Arc;
#[cfg(feature = "docker")]
use anyhow::Context as _;
#[cfg(any(feature = "docker", feature = "daytona"))]
use fabro_github::GitHubCredentials;
@ -17,7 +16,7 @@ use crate::clone_source;
use crate::daytona::{self, DaytonaConfig, DaytonaSandbox};
#[cfg(feature = "docker")]
use crate::docker::{self, DockerSandbox, DockerSandboxOptions};
use crate::local::LocalSandbox;
use crate::driver_sandbox::local_sandbox;
use crate::{Sandbox, SandboxEventCallback};
/// Options for sandbox initialization and construction.
@ -185,17 +184,15 @@ impl SandboxSpec {
}
}
#[allow(
clippy::unused_async,
reason = "Only Daytona construction awaits; local and Docker builds share the async API."
)]
pub async fn build(
&self,
event_callback: Option<SandboxEventCallback>,
) -> Result<Arc<dyn Sandbox>, anyhow::Error> {
match self {
Self::Local { working_directory } => {
let mut sandbox = LocalSandbox::new(working_directory.clone());
let mut sandbox = local_sandbox(working_directory.clone())
.await
.context("Failed to create local sandbox")?;
if let Some(callback) = event_callback {
sandbox.set_event_callback(callback);
}

View file

@ -7,7 +7,7 @@
//! - Docker file reads and content search: fabro's `DockerSandbox` (archive API
//! reads, `docker exec` grep) against the driver `DockerProvider` (archive
//! API reads, exec-derived search) in-process.
//! - Host tool calls: fabro's `LocalSandbox` against the driver `HostProvider`
//! - Host tool calls: fabro's local sandbox against the driver `HostProvider`
//! in-process, to confirm no regression on the path every local run takes.
//! - The wire: the driver Host and Docker providers served over the JSON-RPC
//! protocol on an in-process duplex pipe, to size the budget for running a
@ -37,7 +37,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use bollard::Docker;
use fabro_sandbox::{DockerSandbox, DockerSandboxOptions, LocalSandbox, Sandbox as FabroSandbox};
use fabro_sandbox::{DockerSandbox, DockerSandboxOptions, Sandbox as FabroSandbox, local_sandbox};
use sandbox_driver::{
ExecSpec, GrepOptions, Sandbox as DriverSandbox, SandboxProvider, SandboxSource, SandboxSpec,
Search,
@ -335,12 +335,14 @@ async fn agent_tool_call_latency_through_the_driver() {
let repo = Repository::pack();
let mut rows = Vec::new();
// -- Host, in-process: fabro LocalSandbox vs driver HostProvider.
// -- Host, in-process: fabro local sandbox vs driver HostProvider.
let host_dir = tempfile::tempdir().expect("tempdir");
let local = LocalSandbox::new(host_dir.path().to_path_buf());
let local = local_sandbox(host_dir.path().to_path_buf())
.await
.expect("local sandbox should be created");
local.initialize().await.expect("local init");
unpack_fabro(&local, &repo).await;
rows.extend(bench_fabro("fabro LocalSandbox", &local, &repo).await);
rows.extend(bench_fabro("fabro local sandbox", &local, &repo).await);
let host_provider = Arc::new(HostProvider::new());
let host = host_provider

View file

@ -1550,7 +1550,9 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let run_dir = tmp.path().join("run");
std::fs::create_dir_all(&run_dir).unwrap();
let sandbox = fabro_agent::LocalSandbox::new(tmp.path().to_path_buf());
let sandbox = fabro_agent::local_sandbox(tmp.path().to_path_buf())
.await
.unwrap();
let dataset = serde_json::json!({
"rows": vec![serde_json::json!({"payload": "x".repeat(64)}); 256]
@ -1703,7 +1705,9 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let run_dir = tmp.path().join("run");
std::fs::create_dir_all(&run_dir).unwrap();
let sandbox = fabro_agent::LocalSandbox::new(tmp.path().to_path_buf());
let sandbox = fabro_agent::local_sandbox(tmp.path().to_path_buf())
.await
.unwrap();
let inherited_preamble = "p".repeat(PROMPT_INLINE_VALUE_MAX + 1);
let mut values = HashMap::from([(

View file

@ -541,17 +541,19 @@ mod tests {
}
}
fn sandbox_with_file(path: &str, contents: &str) -> (TempDir, Arc<dyn Sandbox>) {
async fn sandbox_with_file(path: &str, contents: &str) -> (TempDir, Arc<dyn Sandbox>) {
let sandbox_dir = TempDir::new().unwrap();
std::fs::write(sandbox_dir.path().join(path), contents).unwrap();
let sandbox: Arc<dyn Sandbox> = Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
));
let sandbox: Arc<dyn Sandbox> = Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
);
(sandbox_dir, sandbox)
}
async fn execute_with_last_file(path: &str, contents: &str) -> Outcome {
let (_sandbox_dir, sandbox) = sandbox_with_file(path, contents);
let (_sandbox_dir, sandbox) = sandbox_with_file(path, contents).await;
let handler = AgentHandler::new(Some(Box::new(LastFileBackend {
path: path.to_string(),
@ -574,7 +576,7 @@ mod tests {
path: &str,
contents: &str,
) -> Result<ValidatedStructuredOutput, StructuredOutputError> {
let (_sandbox_dir, sandbox) = sandbox_with_file(path, contents);
let (_sandbox_dir, sandbox) = sandbox_with_file(path, contents).await;
validate_agent_output_sources(
&OutputSchemaKind::Routing,
@ -707,12 +709,11 @@ mod tests {
let tmp = TempDir::new().unwrap();
let mut services = EngineServices::test_default();
services.run =
services
.run
.with_sandbox(std::sync::Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
)));
services.run = services.run.with_sandbox(std::sync::Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
));
let outcome = handler
.execute(&node, &context, &graph, tmp.path(), &services)
@ -760,12 +761,11 @@ mod tests {
let tmp = TempDir::new().unwrap();
let mut services = EngineServices::test_default();
services.run =
services
.run
.with_sandbox(std::sync::Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
)));
services.run = services.run.with_sandbox(std::sync::Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
));
let outcome = handler
.execute(&node, &context, &graph, tmp.path(), &services)
@ -873,12 +873,11 @@ All checks passed.
let tmp = TempDir::new().unwrap();
let mut services = EngineServices::test_default();
services.run =
services
.run
.with_sandbox(std::sync::Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
)));
services.run = services.run.with_sandbox(std::sync::Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
));
let outcome = handler
.execute(&node, &context, &graph, tmp.path(), &services)
@ -972,12 +971,11 @@ All checks passed.
let tmp = TempDir::new().unwrap();
let mut services = EngineServices::test_default();
services.run =
services
.run
.with_sandbox(std::sync::Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
)));
services.run = services.run.with_sandbox(std::sync::Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
));
let outcome = handler
.execute(&node, &context, &graph, tmp.path(), &services)

View file

@ -647,8 +647,8 @@ mod tests {
use fabro_acp::test_support::fake_acp_agent_script;
use fabro_acp::{AcpError, AcpProcessExit};
use fabro_agent::{
LocalSandbox, RefreshOutcome, RemoteCredentialAction, Sandbox, TokenProvenance,
TokenSnapshot, shell_quote,
RefreshOutcome, RemoteCredentialAction, Sandbox, TokenProvenance, TokenSnapshot,
local_sandbox, shell_quote,
};
use fabro_graphviz::graph::{AttrValue, Node};
use fabro_sandbox::test_support::MockSandbox;
@ -1040,7 +1040,8 @@ mod tests {
"ACP_MODE".to_string(),
"write_file".to_string(),
)]));
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let emitter = Arc::new(Emitter::default());
let context = Context::new();
let result = backend
@ -1088,7 +1089,8 @@ mod tests {
);
let backend = AgentAcpBackend::new();
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let emitter = Arc::new(Emitter::default());
let context = Context::new();
let result = backend
@ -1159,7 +1161,8 @@ mod tests {
"steer".to_string(),
)]))
.with_steering_hub(steering_hub);
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let context = Context::new();
let result = backend
.run(CodergenRunRequest {
@ -1206,7 +1209,8 @@ mod tests {
"ACP_MODE".to_string(),
"write_file".to_string(),
)]));
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let emitter = Arc::new(Emitter::default());
let context = Context::new();
let result = backend
@ -1295,7 +1299,8 @@ mod tests {
"ACP_STOP_REASON".to_string(),
"cancelled".to_string(),
)]));
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let emitter = Arc::new(Emitter::default());
let context = Context::new();
let result = backend
@ -1343,7 +1348,8 @@ mod tests {
.insert("acp.config".to_string(), AttrValue::String(raw_command));
let backend = AgentAcpBackend::new();
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(tempdir.path().to_path_buf()));
let sandbox: Arc<dyn Sandbox> =
Arc::new(local_sandbox(tempdir.path().to_path_buf()).await.unwrap());
let emitter = Arc::new(Emitter::default());
let events = Arc::new(Mutex::new(Vec::new()));
emitter.on_event({

View file

@ -1880,7 +1880,7 @@ mod tests {
use chrono::TimeZone;
use fabro_agent::subagent::SessionFactory;
use fabro_agent::{AgentProfile, LocalSandbox, ToolRegistry};
use fabro_agent::{AgentProfile, ToolRegistry, local_sandbox};
use fabro_api::types;
use fabro_auth::{VaultCredentialSource, test_support as auth_test_support};
use fabro_llm::provider::{ProviderAdapter, StreamEventStream};
@ -2369,7 +2369,7 @@ reasoning = false
"start": false
}]
}),
tool_context(),
tool_context().await,
)
.await
.expect("create tool should succeed");
@ -2395,7 +2395,7 @@ reasoning = false
"workflow": "child.fabro"
}]
}),
tool_context(),
tool_context().await,
)
.await
.expect("create tool should succeed");
@ -2423,7 +2423,7 @@ reasoning = false
"start": false
}]
}),
tool_context(),
tool_context().await,
)
.await
.expect_err("conflicting parent should be rejected");
@ -2448,7 +2448,7 @@ reasoning = false
"start": false
}]
}),
tool_context(),
tool_context().await,
)
.await
.expect("create should succeed");
@ -2461,7 +2461,7 @@ reasoning = false
"run_ids": [child_run_id().to_string()],
"timeout_seconds": 0
}),
tool_context(),
tool_context().await,
)
.await
.expect("gather should succeed");
@ -2475,7 +2475,7 @@ reasoning = false
"run_id": child_run_id().to_string(),
"first": 5
}),
tool_context(),
tool_context().await,
)
.await
.expect("events should succeed");
@ -2502,7 +2502,7 @@ reasoning = false
"run_id": child_run_id().to_string(),
"action": action
}),
tool_context(),
tool_context().await,
)
.await
.expect_err("workflow agents must not approve or deny runs");
@ -2533,7 +2533,7 @@ reasoning = false
"action": "status",
"run_id": child_run_id().to_string()
}),
tool_context(),
tool_context().await,
)
.await
.expect("pair status should succeed");
@ -2563,9 +2563,9 @@ reasoning = false
(services, backend)
}
fn tool_context() -> ToolContext {
async fn tool_context() -> ToolContext {
ToolContext {
env: Arc::new(LocalSandbox::new(PathBuf::from("."))),
env: Arc::new(local_sandbox(PathBuf::from(".")).await.unwrap()),
cancel: CancellationToken::new(),
tool_env_provider: None,
session_id: None,
@ -3069,7 +3069,7 @@ reasoning = false
.await
.unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let mut session = backend
.create_session_with_plan(&node, &sandbox, Some(hooks))
@ -3609,7 +3609,7 @@ enabled = true
let emitter = Arc::new(Emitter::new(fabro_types::RunId::new()));
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.one_shot(OneShotRequest {
@ -3675,7 +3675,7 @@ enabled = true
let emitter = Arc::new(Emitter::new(fabro_types::RunId::new()));
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.one_shot(OneShotRequest {
@ -3736,7 +3736,7 @@ enabled = true
let emitter = Arc::new(Emitter::new(fabro_types::RunId::new()));
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.run(CodergenRunRequest {
@ -3808,7 +3808,7 @@ enabled = true
let emitter = Arc::new(Emitter::new(fabro_types::RunId::new()));
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.run(CodergenRunRequest {
@ -3879,7 +3879,7 @@ enabled = true
let emitter = Arc::new(Emitter::new(fabro_types::RunId::new()));
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.run(CodergenRunRequest {
@ -3953,7 +3953,7 @@ enabled = true
});
let workspace = tempfile::tempdir().unwrap();
let sandbox: Arc<dyn fabro_agent::Sandbox> =
Arc::new(LocalSandbox::new(workspace.path().to_path_buf()));
Arc::new(local_sandbox(workspace.path().to_path_buf()).await.unwrap());
let result = backend
.run(CodergenRunRequest {
@ -4017,9 +4017,11 @@ enabled = true
let session = Session::new(
client,
Arc::new(ShutdownTestProfile::new()),
Arc::new(fabro_agent::LocalSandbox::new(
tempfile::tempdir().unwrap().path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(tempfile::tempdir().unwrap().path().to_path_buf())
.await
.unwrap(),
),
SessionOptions::default(),
None,
);
@ -4060,9 +4062,11 @@ enabled = true
let mut session = Session::new(
client,
Arc::new(ShutdownTestProfile::new()),
Arc::new(LocalSandbox::new(
tempfile::tempdir().unwrap().path().to_path_buf(),
)),
Arc::new(
local_sandbox(tempfile::tempdir().unwrap().path().to_path_buf())
.await
.unwrap(),
),
SessionOptions::default(),
None,
);

View file

@ -79,7 +79,7 @@ mod tests {
use std::sync::Arc;
use async_trait::async_trait;
use fabro_agent::{LocalSandbox, Sandbox};
use fabro_agent::{Sandbox, local_sandbox};
use fabro_graphviz::graph::{AttrValue, Node};
use fabro_model::{ReasoningEffort, Speed};
use tokio_util::sync::CancellationToken;
@ -114,9 +114,11 @@ mod tests {
#[tokio::test]
async fn router_routes_one_shot_to_api_by_default() {
let node = Node::new("test");
let sandbox: Arc<dyn Sandbox> = Arc::new(LocalSandbox::new(
tempfile::tempdir().unwrap().path().to_path_buf(),
));
let sandbox: Arc<dyn Sandbox> = Arc::new(
local_sandbox(tempfile::tempdir().unwrap().path().to_path_buf())
.await
.unwrap(),
);
let context = Context::new();
let router = BackendRouter::new(Box::new(StubBackend), AgentAcpBackend::new());
let emitter = Arc::new(Emitter::default());

View file

@ -1828,9 +1828,11 @@ mod tests {
services.run = services
.run
.with_run_store(run_store.into())
.with_sandbox(Arc::new(fabro_agent::LocalSandbox::new(
run_dir.path().to_path_buf(),
)));
.with_sandbox(Arc::new(
fabro_agent::local_sandbox(run_dir.path().to_path_buf())
.await
.unwrap(),
));
let (node, graph) = for_each_graph("context.items", 2);
let context = test_context();
let oversized_payload = "x".repeat(65 * 1024);
@ -2161,9 +2163,11 @@ mod tests {
services.run = services
.run
.with_run_store(run_store.into())
.with_sandbox(Arc::new(fabro_agent::LocalSandbox::new(
sandbox_dir.path().to_path_buf(),
)));
.with_sandbox(Arc::new(
fabro_agent::local_sandbox(sandbox_dir.path().to_path_buf())
.await
.unwrap(),
));
let (node, graph) = for_each_graph("items", 1);
let context = test_context();
context.set("items", serde_json::json!(format_blob_ref(&blob_hash)));

View file

@ -720,9 +720,11 @@ reasoning = false
let mut services = make_services();
services.run = services
.run
.with_sandbox(Arc::new(fabro_agent::LocalSandbox::new(
workspace.path().to_path_buf(),
)))
.with_sandbox(Arc::new(
fabro_agent::local_sandbox(workspace.path().to_path_buf())
.await
.unwrap(),
))
.with_catalog_context(
Arc::clone(&catalog),
fabro_model::ProviderId::new("acme"),
@ -795,9 +797,11 @@ reasoning = false
let mut services = make_services();
services.run = services
.run
.with_sandbox(Arc::new(fabro_agent::LocalSandbox::new(
workspace.path().to_path_buf(),
)))
.with_sandbox(Arc::new(
fabro_agent::local_sandbox(workspace.path().to_path_buf())
.await
.unwrap(),
))
.with_catalog_context(
Arc::clone(&catalog),
fabro_model::ProviderId::new("acme"),

View file

@ -468,8 +468,11 @@ mod tests {
None,
));
let run_store = store.create_run(&fixtures::RUN_1).await.unwrap();
let sandbox: Arc<dyn Sandbox> =
Arc::new(fabro_agent::LocalSandbox::new(run_dir.to_path_buf()));
let sandbox: Arc<dyn Sandbox> = Arc::new(
fabro_agent::local_sandbox(run_dir.to_path_buf())
.await
.unwrap(),
);
FidelityLifecycle::new(
graph.0.clone(),
sandbox,

View file

@ -808,7 +808,7 @@ mod tests {
events
}
fn git_lifecycle(
async fn git_lifecycle(
repo: &Path,
emitter: Arc<Emitter>,
run_store: RunStoreHandle,
@ -828,9 +828,10 @@ mod tests {
metadata_runtime,
metadata_writer,
)
.await
}
fn git_lifecycle_with_writer(
async fn git_lifecycle_with_writer(
repo: &Path,
emitter: Arc<Emitter>,
run_store: RunStoreHandle,
@ -840,7 +841,11 @@ mod tests {
) -> GitLifecycle {
GitLifecycle {
stage_executions: StageExecutionTracker::default(),
sandbox: Arc::new(fabro_agent::LocalSandbox::new(repo.to_path_buf())),
sandbox: Arc::new(
fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap(),
),
emitter,
run_id: fixtures::RUN_1,
run_store,
@ -879,7 +884,8 @@ mod tests {
handle,
run_options(repo_dir.path(), branch),
Arc::new(RunMetadataRuntime::new()),
);
)
.await;
let graph = workflow_graph();
let state = ExecutionState::new(&graph).unwrap();
@ -916,7 +922,8 @@ mod tests {
RunStoreHandle::new(Arc::new(FailingStateStore)),
run_options(repo_dir.path(), branch),
Arc::new(RunMetadataRuntime::new()),
);
)
.await;
let graph = workflow_graph();
let state = ExecutionState::new(&graph).unwrap();
@ -975,7 +982,8 @@ mod tests {
run_options(repo_dir.path(), branch),
Arc::clone(&runtime),
Some(metadata_writer),
);
)
.await;
let graph = workflow_graph();
let state = ExecutionState::new(&graph).unwrap();
@ -1014,7 +1022,8 @@ mod tests {
RunStoreHandle::new(Arc::new(FailingStateStore)),
run_options(repo_dir.path(), branch),
Arc::new(RunMetadataRuntime::new()),
);
)
.await;
let graph = workflow_graph();
let node = graph.get_node("build").unwrap();
let mut state = ExecutionState::new(&graph).unwrap();
@ -1064,7 +1073,8 @@ mod tests {
RunStoreHandle::local(run_store),
run_options(repo_dir.path(), branch),
Arc::new(RunMetadataRuntime::new()),
);
)
.await;
let graph = workflow_graph();
let node = graph.get_node("build").unwrap();
let mut state = ExecutionState::new(&graph).unwrap();
@ -1132,7 +1142,8 @@ mod tests {
Arc::new(options),
Arc::new(RunMetadataRuntime::new()),
None,
);
)
.await;
let graph = workflow_graph();
let node = graph.get_node("build").unwrap();
let mut state = ExecutionState::new(&graph).unwrap();
@ -1206,7 +1217,8 @@ mod tests {
Arc::new(options),
Arc::new(RunMetadataRuntime::new()),
None,
);
)
.await;
let graph = workflow_graph();
let node = graph.get_node("build").unwrap();
let mut state = ExecutionState::new(&graph).unwrap();
@ -1249,7 +1261,8 @@ mod tests {
RunStoreHandle::local(run_store(fixtures::RUN_1).await),
run_options(repo_dir.path(), "fabro/metadata/run"),
runtime,
);
)
.await;
let graph = workflow_graph();
let state = ExecutionState::new(&graph).unwrap();
@ -1271,7 +1284,8 @@ mod tests {
RunStoreHandle::new(Arc::new(FailingStateStore)),
run_options(repo_dir.path(), "fabro/metadata/run"),
runtime,
);
)
.await;
let graph = workflow_graph();
let state = ExecutionState::new(&graph).unwrap();
@ -1293,7 +1307,9 @@ mod tests {
.await
.unwrap();
let finalize_sandbox: Arc<dyn fabro_agent::Sandbox> = Arc::new(
fabro_agent::LocalSandbox::new(repo_dir.path().to_path_buf()),
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
);
let finalize_locations = crate::services::RunLocations::for_sandbox(
None,

View file

@ -40,10 +40,12 @@ use crate::records::RunSpec;
use crate::run_options::{GitCheckpointOptions, LifecycleOptions, RunOptions, SetupCommand};
use crate::test_support::run_graph;
fn local_env() -> Arc<dyn Sandbox> {
Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
))
async fn local_env() -> Arc<dyn Sandbox> {
Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
.await
.unwrap(),
)
}
fn simple_graph() -> Graph {
@ -874,7 +876,7 @@ async fn execute_runs_simple_workflow() {
let outcome = run_graph(
make_registry(),
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&simple_graph(),
&test_run_options(dir.path(), "test-run"),
)
@ -972,7 +974,7 @@ async fn execute_emits_events() {
run_graph(
make_registry(),
Arc::new(emitter),
local_env(),
local_env().await,
&simple_graph(),
&test_run_options(dir.path(), "test-run"),
)
@ -988,7 +990,7 @@ async fn execute_error_when_no_start_node() {
let result = run_graph(
make_registry(),
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&Graph::new("empty"),
&test_run_options(dir.path(), "test-run"),
)
@ -1264,7 +1266,7 @@ async fn execute_cancelled_mid_run() {
let result = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&g,
&run_options,
)
@ -1313,7 +1315,7 @@ async fn max_node_visits_errors_on_cycle() {
let result = run_graph(
make_registry(),
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&g,
&test_run_options(dir.path(), "test-run"),
)
@ -1348,7 +1350,7 @@ async fn panic_handler_returns_panic_message() {
let result = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&g,
&test_run_options(dir.path(), "test-run"),
)
@ -1369,7 +1371,7 @@ async fn loop_circuit_breaker_aborts_on_repeated_failure() {
let result = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&looping_fail_graph(),
&test_run_options(dir.path(), "test-run"),
)
@ -1418,7 +1420,7 @@ async fn stall_watchdog_triggers_on_hung_handler() {
let result = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&g,
&test_run_options(dir.path(), "test-run"),
)
@ -1477,7 +1479,7 @@ async fn stall_watchdog_suspends_while_run_waits_for_human_input() {
let outcome = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&graph,
&test_run_options(dir.path(), "test-run"),
)
@ -1505,7 +1507,7 @@ async fn node_timeout_excludes_human_input_wait() {
let outcome = run_graph(
registry,
test_emitter_arc("test-run"),
local_env(),
local_env().await,
&graph,
&test_run_options(dir.path(), "test-run"),
)
@ -1570,7 +1572,7 @@ async fn retry_emits_stage_started_per_attempt() {
let outcome = run_graph(
registry,
Arc::new(emitter),
local_env(),
local_env().await,
&g,
&test_run_options(dir.path(), "retry-events-test"),
)
@ -1610,7 +1612,7 @@ async fn run_with_lifecycle_emits_initialize_and_setup_events() {
let outcome = run_with_lifecycle(
make_registry(),
Arc::new(emitter),
local_env(),
local_env().await,
&simple_graph(),
test_run_options(dir.path(), "order-test"),
test_lifecycle(vec!["echo ok"]),

View file

@ -1018,9 +1018,11 @@ mod tests {
let emitter = Arc::new(Emitter::new(test_run_id()));
let store_logger = StoreProgressLogger::new(run_store.clone());
store_logger.register(&emitter);
let sandbox: Arc<dyn fabro_agent::Sandbox> = Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap(),
));
let sandbox: Arc<dyn fabro_agent::Sandbox> = Arc::new(
fabro_agent::local_sandbox(std::env::current_dir().unwrap())
.await
.unwrap(),
);
let locations =
crate::services::RunLocations::for_sandbox(None, sandbox.as_ref(), run_dir.clone());
let services = RunServices::new(
@ -1085,9 +1087,11 @@ mod tests {
let services = test_services(
handle,
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
Some(RunMetadataWriterHandle::new_for_test_repo(
repo_dir.path(),
@ -1122,9 +1126,11 @@ mod tests {
let services = test_services(
RunStoreHandle::new(Arc::new(FailingStateStore)),
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
Some(RunMetadataWriterHandle::new_for_test_repo(
repo_dir.path(),
@ -1174,9 +1180,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(run_store),
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
runtime,
Some(RunMetadataWriterHandle::new_for_test_repo(
repo_dir.path(),
@ -1211,9 +1219,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(run_store),
Arc::clone(&emitter),
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
Some(RunMetadataWriterHandle::new_for_test_repo(
repo_dir.path(),
@ -1405,9 +1415,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(seeded_run_store().await),
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
None,
);
@ -1470,9 +1482,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(seeded_run_store().await),
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
None,
);
@ -1528,9 +1542,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(seeded_run_store().await),
emitter,
Arc::new(fabro_agent::LocalSandbox::new(
repo_dir.path().to_path_buf(),
)),
Arc::new(
fabro_agent::local_sandbox(repo_dir.path().to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
None,
);
@ -1682,7 +1698,11 @@ mod tests {
let services = test_services(
RunStoreHandle::local(run_store),
Arc::clone(&emitter),
Arc::new(fabro_agent::LocalSandbox::new(repo.to_path_buf())),
Arc::new(
fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap(),
),
Arc::new(RunMetadataRuntime::new()),
None,
);

View file

@ -1184,9 +1184,14 @@ mod tests {
.await
.expect("a forked run should materialize a fresh sandbox before resuming");
// The Host provider reports the designated directory canonically
// (macOS resolves `/var` to `/private/var`).
let expected = workspace
.canonicalize()
.expect("materialized workspace should exist");
assert_eq!(
initialized.engine.run.sandbox.working_directory(),
workspace.to_string_lossy().as_ref()
expected.to_string_lossy().as_ref()
);
}

View file

@ -1318,7 +1318,9 @@ mod tests {
std::fs::create_dir_all(repo.join(".venv/lib")).unwrap();
std::fs::write(repo.join(".venv/lib/site.py"), "venv").unwrap();
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let author = crate::git::GitAuthor::default();
// Call git_checkpoint with empty user excludes — built-in excludes should still
@ -1417,7 +1419,9 @@ mod tests {
std::fs::remove_file(repo.join("drop.txt")).unwrap();
let head = git_commit_all(repo, "change");
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let entries = list_changed_files_raw(&sandbox, &base, &head)
.await
.unwrap();
@ -1456,7 +1460,9 @@ mod tests {
std::fs::write(repo.join("new.txt"), &content).unwrap();
let head = git_commit_all(repo, "rename");
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let entries = list_changed_files_raw(&sandbox, &base, &head)
.await
.unwrap();
@ -1500,7 +1506,9 @@ mod tests {
std::fs::write(repo.join("logo.png"), png).unwrap();
let head = git_commit_all(repo, "change");
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let stats = list_diff_numstat(&sandbox, &base, &head).await.unwrap();
assert!(
@ -1544,7 +1552,9 @@ mod tests {
sha_by_name.insert(path.to_string(), sha.to_string());
}
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let shas = vec![sha_by_name["a.txt"].clone(), sha_by_name["b.txt"].clone()];
let metas = stream_blob_metadata(&sandbox, &shas).await.unwrap();
assert_eq!(metas.len(), 2);
@ -1580,7 +1590,9 @@ mod tests {
sha_by_name.insert(path.to_string(), sha.to_string());
}
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let shas = vec![sha_by_name["a.txt"].clone(), sha_by_name["big.txt"].clone()];
// size_cap = 100 bytes — "hello\n" (6) stays, 200-byte blob truncates.
@ -1598,7 +1610,9 @@ mod tests {
std::fs::write(repo.join("x"), "x").unwrap();
git_commit_all(repo, "seed");
let sandbox = fabro_agent::LocalSandbox::new(repo.to_path_buf());
let sandbox = fabro_agent::local_sandbox(repo.to_path_buf())
.await
.unwrap();
let err =
list_changed_files_raw(&sandbox, "0000000000000000000000000000000000000000", "HEAD")
.await

View file

@ -291,23 +291,28 @@ impl EngineServices {
Duration::from_millis(1),
None,
));
let run_store = std::thread::spawn(move || {
let (run_store, sandbox) = std::thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("test runtime should initialize")
.block_on(async {
store
let run_store = store
.create_run(&fabro_types::RunId::new())
.await
.expect("slate-backed test run store should initialize")
.expect("slate-backed test run store should initialize");
let sandbox: Arc<dyn Sandbox> = Arc::new(
fabro_agent::local_sandbox(
std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
)
.await
.expect("local sandbox should be created"),
);
(run_store, sandbox)
})
})
.join()
.expect("test run store thread should join");
let sandbox: Arc<dyn Sandbox> = Arc::new(fabro_agent::LocalSandbox::new(
std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
));
let locations = RunLocations::for_sandbox(None, sandbox.as_ref(), PathBuf::from("."));
Self {

View file

@ -114,8 +114,12 @@ fn list_branch(repo_dir: &Path, branch: &str) -> String {
String::from_utf8(output.stdout).expect("git branch --list output should be UTF-8")
}
fn local_env(repo: &Path) -> Arc<dyn Sandbox> {
Arc::new(fabro_agent::LocalSandbox::new(repo.to_path_buf()))
async fn local_env(repo: &Path) -> Arc<dyn Sandbox> {
Arc::new(
fabro_agent::local_sandbox(repo.to_path_buf())
.await
.expect("local sandbox should be created"),
)
}
fn simple_graph() -> Graph {
@ -304,7 +308,7 @@ async fn git_checkpoint_skips_start_node() {
Box::pin(run_graph(
make_registry(),
Arc::new(emitter),
local_env(repo),
local_env(repo).await,
&g,
&run_options,
))
@ -334,7 +338,7 @@ async fn git_checkpoint_skips_start_node() {
/// inaccessible (as it is for Docker/Daytona) and the sandbox exposes a
/// runtime directory outside the checkout.
struct RemoteRuntimeSandbox {
inner: fabro_agent::LocalSandbox,
inner: fabro_agent::DriverSandbox,
hidden_path: String,
runtime_directory: String,
}
@ -479,7 +483,9 @@ async fn remote_prompt_demotion_stays_outside_checkout_and_survives_checkpoint()
std::fs::create_dir_all(&run_dir).unwrap();
let sandbox = RemoteRuntimeSandbox {
inner: fabro_agent::LocalSandbox::new(repo_dir.clone()),
inner: fabro_agent::local_sandbox(repo_dir.clone())
.await
.expect("local sandbox should be created"),
hidden_path: run_dir.to_string_lossy().to_string(),
runtime_directory: runtime_dir.to_string_lossy().to_string(),
};

File diff suppressed because it is too large Load diff