From b5f5162c3addaf5be1be5d6e3c4c01c7e3dc1308 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 2 Apr 2026 06:47:42 -0700 Subject: [PATCH] Rename SessionConfig to SessionOptions and McpServerConfig to McpServerSettings Aligns naming with the convention that "Config" is for file-level configuration while "Options" and "Settings" describe runtime parameters. Also applies rustfmt formatting fixes in web_auth.rs. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker/entrypoint.ts | 4 +- lib/crates/fabro-agent/src/cli.rs | 16 ++-- lib/crates/fabro-agent/src/config.rs | 18 ++-- lib/crates/fabro-agent/src/docker_sandbox.rs | 2 +- lib/crates/fabro-agent/src/lib.rs | 10 +- lib/crates/fabro-agent/src/mcp_integration.rs | 6 +- .../fabro-agent/src/profiles/anthropic.rs | 6 +- lib/crates/fabro-agent/src/profiles/gemini.rs | 4 +- lib/crates/fabro-agent/src/profiles/openai.rs | 4 +- lib/crates/fabro-agent/src/sandbox.rs | 4 +- lib/crates/fabro-agent/src/session.rs | 68 +++++++------- lib/crates/fabro-agent/src/subagent.rs | 4 +- lib/crates/fabro-agent/src/test_support.rs | 10 +- lib/crates/fabro-agent/src/tool_execution.rs | 34 +++---- lib/crates/fabro-agent/src/tools.rs | 10 +- lib/crates/fabro-agent/src/truncation.rs | 12 +-- lib/crates/fabro-agent/src/v4a_patch.rs | 11 ++- .../fabro-agent/tests/it/parity_matrix.rs | 30 +++--- lib/crates/fabro-cli/src/commands/exec.rs | 4 +- .../fabro-cli/src/commands/preflight.rs | 6 +- lib/crates/fabro-config/src/config.rs | 10 +- lib/crates/fabro-config/src/server.rs | 6 +- lib/crates/fabro-devcontainer/src/compose.rs | 10 +- lib/crates/fabro-devcontainer/src/lib.rs | 8 +- lib/crates/fabro-devcontainer/src/types.rs | 4 +- lib/crates/fabro-github/tests/integration.rs | 4 +- lib/crates/fabro-hooks/src/bridge.rs | 12 +-- lib/crates/fabro-hooks/src/config.rs | 2 +- lib/crates/fabro-hooks/src/executor.rs | 2 +- lib/crates/fabro-hooks/src/lib.rs | 2 +- lib/crates/fabro-hooks/src/runner.rs | 32 +++---- lib/crates/fabro-llm/src/generate.rs | 12 +-- lib/crates/fabro-llm/src/providers/gemini.rs | 6 +- lib/crates/fabro-llm/src/types.rs | 4 +- lib/crates/fabro-mcp/src/client.rs | 4 +- lib/crates/fabro-mcp/src/config.rs | 2 +- .../fabro-mcp/src/connection_manager.rs | 6 +- .../fabro-mcp/tests/stdio_integration.rs | 6 +- lib/crates/fabro-retro/src/retro_agent.rs | 8 +- lib/crates/fabro-sandbox/src/docker.rs | 12 +-- lib/crates/fabro-sandbox/src/lib.rs | 4 +- lib/crates/fabro-sandbox/src/reconnect.rs | 6 +- lib/crates/fabro-sandbox/src/sandbox_spec.rs | 6 +- lib/crates/fabro-sandbox/src/worktree.rs | 24 ++--- lib/crates/fabro-server/build.rs | 4 +- lib/crates/fabro-server/src/server.rs | 17 ++-- lib/crates/fabro-server/src/static_files.rs | 3 +- lib/crates/fabro-server/src/web_auth.rs | 92 ++++++++++++++----- lib/crates/fabro-server/tests/it/api.rs | 4 +- lib/crates/fabro-slack/src/config.rs | 14 +-- lib/crates/fabro-test/src/lib.rs | 2 +- lib/crates/fabro-tracker/src/lib.rs | 2 +- lib/crates/fabro-tracker/src/linear.rs | 16 ++-- lib/crates/fabro-types/src/settings/hook.rs | 4 +- lib/crates/fabro-types/src/settings/mcp.rs | 24 ++--- lib/crates/fabro-types/src/settings/mod.rs | 4 +- .../fabro-workflow/src/devcontainer_bridge.rs | 10 +- .../fabro-workflow/src/handler/llm/api.rs | 16 ++-- .../fabro-workflow/src/handler/parallel.rs | 4 +- .../fabro-workflow/src/operations/start.rs | 6 +- .../src/pipeline/execute/tests.rs | 8 +- .../fabro-workflow/src/pipeline/initialize.rs | 8 +- .../fabro-workflow/src/pipeline/types.rs | 6 +- .../fabro-workflow/src/transforms/import.rs | 8 +- .../tests/it/daytona_integration.rs | 4 +- .../fabro-workflow/tests/it/integration.rs | 16 ++-- test/twin/github/src/fixtures.rs | 13 +-- test/twin/github/src/handlers/app.rs | 10 +- test/twin/github/src/handlers/branches.rs | 6 +- test/twin/github/src/handlers/graphql.rs | 4 +- .../twin/github/src/handlers/installations.rs | 12 +-- test/twin/github/src/handlers/pulls.rs | 4 +- test/twin/github/src/state.rs | 14 +-- test/twin/openai/src/openai/models.rs | 4 +- .../twin/openai/tests/live_openai_contract.rs | 50 +++++----- 75 files changed, 448 insertions(+), 396 deletions(-) diff --git a/docker/entrypoint.ts b/docker/entrypoint.ts index 8300800c5..6824a8ad7 100644 --- a/docker/entrypoint.ts +++ b/docker/entrypoint.ts @@ -1,11 +1,11 @@ const service = process.argv[2]; -type ServiceConfig = { +type ServiceOptions = { command: string[]; cwd?: string; }; -const services: Record = { +const services: Record = { api: { command: ["fabro", "serve", "--host", "0.0.0.0"], }, diff --git a/lib/crates/fabro-agent/src/cli.rs b/lib/crates/fabro-agent/src/cli.rs index b5fdb06a7..80583a5d2 100644 --- a/lib/crates/fabro-agent/src/cli.rs +++ b/lib/crates/fabro-agent/src/cli.rs @@ -4,7 +4,7 @@ use crate::tools::WebFetchSummarizer; use crate::truncation; use crate::{ AgentEvent, AgentProfile, AnthropicProfile, GeminiProfile, LocalSandbox, OpenAiProfile, - Sandbox, Session, SessionConfig, Turn, + Sandbox, Session, SessionOptions, Turn, subagent::{SessionFactory, SubAgentManager}, }; use clap::{Args, Parser}; @@ -13,7 +13,7 @@ use fabro_llm::error::SdkError; use fabro_llm::middleware::{Middleware, NextFn, NextStreamFn}; use fabro_llm::provider::StreamEventStream; use fabro_llm::types::{Request, Response}; -use fabro_mcp::config::McpServerConfig; +use fabro_mcp::config::McpServerSettings; use fabro_model::{Catalog, ModelRef, Provider}; use fabro_util::terminal::Styles; use std::io::{IsTerminal, Write}; @@ -355,7 +355,7 @@ impl Middleware for VerboseMiddleware { pub async fn run_with_args( args: AgentArgs, - mcp_servers: Vec, + mcp_servers: Vec, ) -> anyhow::Result<()> { run_with_args_and_client(args, None, mcp_servers).await } @@ -364,7 +364,7 @@ pub async fn run_with_args( pub async fn run_with_args_and_client( args: AgentArgs, llm_client: Option, - mcp_servers: Vec, + mcp_servers: Vec, ) -> anyhow::Result<()> { // Resolve color support once, leak to get 'static lifetime for use across threads let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); @@ -420,11 +420,11 @@ pub async fn run_with_args_and_client( let tool_approval = build_tool_approval(permissions, is_interactive, styles); let tool_hooks: Arc = Arc::new(ToolApprovalAdapter(tool_approval)); - let config = SessionConfig { + let config = SessionOptions { tool_hooks: Some(tool_hooks.clone()), skill_dirs: args.skills_dir.map(|d| vec![d]), mcp_servers, - ..SessionConfig::default() + ..SessionOptions::default() }; // Register subagent tools @@ -464,9 +464,9 @@ pub async fn run_with_args_and_client( factory_client.clone(), child_profile, Arc::clone(&factory_env), - SessionConfig { + SessionOptions { tool_hooks: factory_hooks.clone(), - ..SessionConfig::default() + ..SessionOptions::default() }, None, ) diff --git a/lib/crates/fabro-agent/src/config.rs b/lib/crates/fabro-agent/src/config.rs index af80f7e25..c468b81bc 100644 --- a/lib/crates/fabro-agent/src/config.rs +++ b/lib/crates/fabro-agent/src/config.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::Duration; use fabro_llm::types::ReasoningEffort; -use fabro_mcp::config::McpServerConfig; +use fabro_mcp::config::McpServerSettings; /// Callback invoked before each tool execution. Return `Ok(())` to allow, /// `Err(message)` to deny with the given message. @@ -59,7 +59,7 @@ impl ToolHookCallback for ToolApprovalAdapter { } #[derive(Clone)] -pub struct SessionConfig { +pub struct SessionOptions { pub max_turns: usize, pub max_tool_rounds_per_input: usize, pub default_command_timeout_ms: u64, @@ -84,15 +84,15 @@ pub struct SessionConfig { /// Skill directories. `None` = use convention defaults, `Some(dirs)` = use these instead. pub skill_dirs: Option>, /// MCP server configurations to connect to on session startup. - pub mcp_servers: Vec, + pub mcp_servers: Vec, /// Wall-clock timeout for the entire `process_input` call. /// When set, the session's cancel token is triggered after this duration. pub wall_clock_timeout: Option, } -impl std::fmt::Debug for SessionConfig { +impl std::fmt::Debug for SessionOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SessionConfig") + f.debug_struct("SessionOptions") .field("max_turns", &self.max_turns) .field("max_tool_rounds_per_input", &self.max_tool_rounds_per_input) .field( @@ -127,7 +127,7 @@ impl std::fmt::Debug for SessionConfig { } } -impl Default for SessionConfig { +impl Default for SessionOptions { fn default() -> Self { Self { max_turns: 0, @@ -161,7 +161,7 @@ mod tests { #[test] fn default_config_values() { - let config = SessionConfig::default(); + let config = SessionOptions::default(); assert_eq!(config.max_turns, 0); assert_eq!(config.max_tool_rounds_per_input, 0); assert_eq!(config.default_command_timeout_ms, 10_000); @@ -179,7 +179,7 @@ mod tests { #[test] fn default_config_has_compaction_enabled() { - let config = SessionConfig::default(); + let config = SessionOptions::default(); assert!(config.enable_context_compaction); assert_eq!(config.compaction_threshold_percent, 80); assert_eq!(config.compaction_preserve_turns, 6); @@ -187,7 +187,7 @@ mod tests { #[test] fn config_with_custom_values() { - let config = SessionConfig { + let config = SessionOptions { max_turns: 50, reasoning_effort: Some(ReasoningEffort::High), ..Default::default() diff --git a/lib/crates/fabro-agent/src/docker_sandbox.rs b/lib/crates/fabro-agent/src/docker_sandbox.rs index d7635f528..2c3f4f257 100644 --- a/lib/crates/fabro-agent/src/docker_sandbox.rs +++ b/lib/crates/fabro-agent/src/docker_sandbox.rs @@ -1,2 +1,2 @@ // Re-export from fabro-sandbox -pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxConfig}; +pub use fabro_sandbox::docker::{DockerSandbox, DockerSandboxOptions}; diff --git a/lib/crates/fabro-agent/src/lib.rs b/lib/crates/fabro-agent/src/lib.rs index 67ab43544..ac6ebede1 100644 --- a/lib/crates/fabro-agent/src/lib.rs +++ b/lib/crates/fabro-agent/src/lib.rs @@ -27,12 +27,12 @@ pub mod types; pub mod v4a_patch; pub use agent_profile::AgentProfile; -pub use config::{SessionConfig, ToolApprovalAdapter, ToolHookCallback, ToolHookDecision}; +pub use config::{SessionOptions, ToolApprovalAdapter, ToolHookCallback, ToolHookDecision}; #[cfg(feature = "docker")] -pub use docker_sandbox::{DockerSandbox, DockerSandboxConfig}; +pub use docker_sandbox::{DockerSandbox, DockerSandboxOptions}; pub use error::{AbortReason, AgentError}; pub use event::EventEmitter; -pub use fabro_mcp::config::McpServerConfig; +pub use fabro_mcp::config::McpServerSettings; pub use history::History; pub use local_sandbox::LocalSandbox; pub use loop_detection::detect_loop; @@ -40,8 +40,8 @@ pub use memory::discover_memory; pub use profiles::{AnthropicProfile, EnvContext, GeminiProfile, OpenAiProfile}; pub use read_before_write_sandbox::ReadBeforeWriteSandbox; pub use sandbox::{ - DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig, - WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote, + DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeEvent, + WorktreeEventCallback, WorktreeOptions, WorktreeSandbox, format_lines_numbered, shell_quote, }; pub use session::Session; pub use skills::Skill; diff --git a/lib/crates/fabro-agent/src/mcp_integration.rs b/lib/crates/fabro-agent/src/mcp_integration.rs index 33c97d173..ab2cbaed2 100644 --- a/lib/crates/fabro-agent/src/mcp_integration.rs +++ b/lib/crates/fabro-agent/src/mcp_integration.rs @@ -46,15 +46,15 @@ mod tests { use crate::tool_registry::ToolContext; use std::collections::HashMap; - use fabro_mcp::config::{McpServerConfig, McpTransport}; + use fabro_mcp::config::{McpServerSettings, McpTransport}; use tokio_util::sync::CancellationToken; - fn test_server_config() -> McpServerConfig { + fn test_server_config() -> McpServerSettings { let test_server = format!( "{}/../fabro-mcp/tests/test_mcp_server.py", env!("CARGO_MANIFEST_DIR") ); - McpServerConfig { + McpServerSettings { name: "test-echo".into(), transport: McpTransport::Stdio { command: vec!["python3".into(), test_server], diff --git a/lib/crates/fabro-agent/src/profiles/anthropic.rs b/lib/crates/fabro-agent/src/profiles/anthropic.rs index 3f44aa01c..80995a6be 100644 --- a/lib/crates/fabro-agent/src/profiles/anthropic.rs +++ b/lib/crates/fabro-agent/src/profiles/anthropic.rs @@ -1,5 +1,5 @@ use crate::agent_profile::AgentProfile; -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::profiles::BaseProfile; use crate::profiles::assemble_system_prompt; use crate::sandbox::Sandbox; @@ -25,9 +25,9 @@ impl AnthropicProfile { model: impl Into, summarizer: Option, ) -> Self { - let config = SessionConfig { + let config = SessionOptions { default_command_timeout_ms: 120_000, - ..SessionConfig::default() + ..SessionOptions::default() }; let mut registry = ToolRegistry::new(); diff --git a/lib/crates/fabro-agent/src/profiles/gemini.rs b/lib/crates/fabro-agent/src/profiles/gemini.rs index 91691d402..f137ee98d 100644 --- a/lib/crates/fabro-agent/src/profiles/gemini.rs +++ b/lib/crates/fabro-agent/src/profiles/gemini.rs @@ -1,5 +1,5 @@ use crate::agent_profile::AgentProfile; -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::profiles::BaseProfile; use crate::profiles::assemble_system_prompt; use crate::sandbox::Sandbox; @@ -28,7 +28,7 @@ impl GeminiProfile { model: impl Into, summarizer: Option, ) -> Self { - let config = SessionConfig::default(); + let config = SessionOptions::default(); let mut registry = ToolRegistry::new(); register_core_tools(&mut registry, &config, summarizer); diff --git a/lib/crates/fabro-agent/src/profiles/openai.rs b/lib/crates/fabro-agent/src/profiles/openai.rs index c3f71f2a7..27fbc286b 100644 --- a/lib/crates/fabro-agent/src/profiles/openai.rs +++ b/lib/crates/fabro-agent/src/profiles/openai.rs @@ -1,5 +1,5 @@ use crate::agent_profile::AgentProfile; -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::profiles::BaseProfile; use crate::profiles::assemble_system_prompt; use crate::sandbox::Sandbox; @@ -26,7 +26,7 @@ impl OpenAiProfile { model: impl Into, summarizer: Option, ) -> Self { - let config = SessionConfig::default(); + let config = SessionOptions::default(); let mut registry = ToolRegistry::new(); register_core_tools(&mut registry, &config, summarizer); diff --git a/lib/crates/fabro-agent/src/sandbox.rs b/lib/crates/fabro-agent/src/sandbox.rs index 9efecd005..234d28325 100644 --- a/lib/crates/fabro-agent/src/sandbox.rs +++ b/lib/crates/fabro-agent/src/sandbox.rs @@ -1,7 +1,7 @@ // Re-export all sandbox types from fabro-sandbox. pub use fabro_sandbox::{ - DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig, - WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote, + DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeEvent, + WorktreeEventCallback, WorktreeOptions, WorktreeSandbox, format_lines_numbered, shell_quote, }; // Re-export the delegate_sandbox! macro at crate root so existing diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index e0b5925b6..e7e74d73b 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -1,6 +1,6 @@ use crate::agent_profile::AgentProfile; use crate::compaction::{check_context_usage, compact_context}; -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::error::{AbortReason, AgentError}; use crate::event::EventEmitter; use crate::file_tracker::FileTracker; @@ -24,7 +24,7 @@ use fabro_llm::retry; use fabro_llm::types::{ ContentPart, Message, ReasoningEffort, Request, RetryPolicy, StreamEvent, ToolChoice, }; -use fabro_mcp::config::{McpServerConfig, McpTransport}; +use fabro_mcp::config::{McpServerSettings, McpTransport}; use fabro_mcp::connection_manager::McpConnectionManager; use futures::StreamExt; use std::collections::{HashMap, VecDeque}; @@ -37,7 +37,7 @@ use tracing::{debug, info, warn}; pub struct Session { id: String, - config: SessionConfig, + config: SessionOptions, history: History, event_emitter: EventEmitter, state: SessionState, @@ -63,7 +63,7 @@ impl Session { llm_client: Client, provider_profile: Arc, sandbox: Arc, - config: SessionConfig, + config: SessionOptions, subagent_manager: Option>>, ) -> Self { Self { @@ -203,7 +203,7 @@ impl Session { /// Resolve `McpTransport::Sandbox` configs by starting the MCP server inside the /// sandbox and rewriting the transport to `Http` with the sandbox's preview URL. - async fn resolve_sandbox_mcp_servers(&self) -> Vec { + async fn resolve_sandbox_mcp_servers(&self) -> Vec { let mut resolved = Vec::with_capacity(self.config.mcp_servers.len()); for config in &self.config.mcp_servers { @@ -217,7 +217,7 @@ impl Session { url = %url, "Sandbox MCP server started, connecting via HTTP" ); - resolved.push(McpServerConfig { + resolved.push(McpServerSettings { name: config.name.clone(), transport: McpTransport::Http { url, headers }, startup_timeout_secs: config.startup_timeout_secs, @@ -1118,7 +1118,7 @@ mod tests { client, profile, env, - SessionConfig::default(), + SessionOptions::default(), subagent_manager, ) } @@ -1185,7 +1185,7 @@ mod tests { tool_call_response("echo", "call_3", serde_json::json!({"text": "c"})), ]; - let config = SessionConfig { + let config = SessionOptions { max_tool_rounds_per_input: 2, enable_loop_detection: false, ..Default::default() @@ -1208,7 +1208,7 @@ mod tests { text_response("should not reach"), ]; - let config = SessionConfig { + let config = SessionOptions { max_turns: 3, ..Default::default() }; @@ -1398,7 +1398,7 @@ mod tests { text_response("Done"), ]; - let config = SessionConfig { + let config = SessionOptions { enable_loop_detection: true, loop_detection_window: 3, ..Default::default() @@ -1435,7 +1435,7 @@ mod tests { tool_call_response("echo", "call_2", serde_json::json!({"text": "b"})), ]; - let config = SessionConfig { + let config = SessionOptions { enable_loop_detection: false, ..Default::default() }; @@ -1488,7 +1488,7 @@ mod tests { let client = make_client(provider).await; let profile = Arc::new(TestProfile::with_tools(registry)); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { enable_loop_detection: false, ..Default::default() }; @@ -1523,7 +1523,7 @@ mod tests { let client = make_client(error_provider).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); let result = session.process_input("Hello").await; assert!(result.is_err()); @@ -1602,7 +1602,7 @@ mod tests { let client = make_client(provider).await; let profile = Arc::new(TestProfile::with_tools(registry)); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); let mut rx = session.subscribe(); session.process_input("Use echo three times").await.unwrap(); @@ -1653,7 +1653,7 @@ mod tests { let registry = ToolRegistry::new(); let profile = Arc::new(TestProfile::with_context_window(registry, 100)); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); let mut rx = session.subscribe(); session.process_input(&large_input).await.unwrap(); @@ -1675,7 +1675,7 @@ mod tests { let client = make_client(provider as Arc).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); // Default reasoning_effort is None session.set_reasoning_effort(Some(ReasoningEffort::High)); @@ -1698,7 +1698,7 @@ mod tests { // Large context window so short input stays well under 80% let profile = Arc::new(TestProfile::with_context_window(registry, 200_000)); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); let mut rx = session.subscribe(); session.process_input("Hi").await.unwrap(); @@ -1827,7 +1827,7 @@ mod tests { let client = make_client(provider as Arc).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { user_instructions: Some("Always use TDD".into()), ..Default::default() }; @@ -1855,7 +1855,7 @@ mod tests { let client = make_client(provider as Arc).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); // Intentionally skip initialize(): system prompt remains empty. session.process_input("test").await.unwrap(); @@ -1887,7 +1887,7 @@ mod tests { text_response("OK after denial"), ]; - let config = SessionConfig { + let config = SessionOptions { tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| { Err("denied by policy".to_string()) })))), @@ -1928,7 +1928,7 @@ mod tests { text_response("Done"), ]; - let config = SessionConfig { + let config = SessionOptions { tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| { Ok(()) })))), @@ -1964,7 +1964,7 @@ mod tests { text_response("Done"), ]; - let config = SessionConfig { + let config = SessionOptions { tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new( move |name, args| { *captured_clone.lock().unwrap() = Some((name.to_string(), args.clone())); @@ -1995,7 +1995,7 @@ mod tests { text_response("Done"), ]; - let config = SessionConfig { + let config = SessionOptions { tool_hooks: None, ..Default::default() }; @@ -2026,7 +2026,7 @@ mod tests { text_response("Done"), ]; - let config = SessionConfig { + let config = SessionOptions { tool_hooks: Some(Arc::new(ToolApprovalAdapter(Arc::new(|_name, _args| { Err("not allowed".to_string()) })))), @@ -2087,7 +2087,7 @@ mod tests { let client = make_client(provider as Arc).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let mut session = Session::new(client, profile, env, SessionConfig::default(), None); + let mut session = Session::new(client, profile, env, SessionOptions::default(), None); let result = session.process_input("Hello").await; assert!(matches!( @@ -2260,7 +2260,7 @@ mod tests { let registry = ToolRegistry::new(); let profile = Arc::new(TestProfile::with_context_window(registry, 100)); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { enable_context_compaction: true, compaction_preserve_turns: 1, ..Default::default() @@ -2303,7 +2303,7 @@ mod tests { let registry = ToolRegistry::new(); let profile = Arc::new(TestProfile::with_context_window(registry, 100)); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { enable_context_compaction: false, ..Default::default() }; @@ -2387,7 +2387,7 @@ mod tests { let registry = ToolRegistry::new(); let profile = Arc::new(TestProfile::with_context_window(registry, 100)); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { enable_context_compaction: true, compaction_preserve_turns: 1, ..Default::default() @@ -2491,7 +2491,7 @@ mod tests { // Tiny context window to force compaction let profile = Arc::new(TestProfile::with_context_window(registry, 100)); let env = Arc::new(MockSandbox::default()); - let config = SessionConfig { + let config = SessionOptions { enable_context_compaction: true, compaction_preserve_turns: 1, ..Default::default() @@ -2555,15 +2555,15 @@ mod tests { #[tokio::test] async fn mcp_end_to_end_tool_call() { - use fabro_mcp::config::{McpServerConfig, McpTransport}; + use fabro_mcp::config::{McpServerSettings, McpTransport}; use std::collections::HashMap; let test_server = format!( "{}/../fabro-mcp/tests/test_mcp_server.py", env!("CARGO_MANIFEST_DIR") ); - let config = SessionConfig { - mcp_servers: vec![McpServerConfig { + let config = SessionOptions { + mcp_servers: vec![McpServerSettings { name: "test-echo".into(), transport: McpTransport::Stdio { command: vec!["python3".into(), test_server], @@ -2696,7 +2696,7 @@ mod tests { text_response("Should not reach this"), ]; - let config = SessionConfig { + let config = SessionOptions { wall_clock_timeout: Some(std::time::Duration::from_millis(10)), enable_loop_detection: false, ..Default::default() @@ -2719,7 +2719,7 @@ mod tests { async fn wall_clock_timeout_does_not_fire_when_session_completes_in_time() { let responses = vec![text_response("Fast response")]; - let config = SessionConfig { + let config = SessionOptions { wall_clock_timeout: Some(std::time::Duration::from_secs(10)), ..Default::default() }; diff --git a/lib/crates/fabro-agent/src/subagent.rs b/lib/crates/fabro-agent/src/subagent.rs index ec3c1a171..8bc428ff6 100644 --- a/lib/crates/fabro-agent/src/subagent.rs +++ b/lib/crates/fabro-agent/src/subagent.rs @@ -456,7 +456,7 @@ pub fn make_close_agent_tool(manager: Arc>) -> Regis #[cfg(test)] mod tests { use super::*; - use crate::config::SessionConfig; + use crate::config::SessionOptions; use crate::test_support::*; use fabro_llm::provider::ProviderAdapter; use fabro_llm::types::Role; @@ -491,7 +491,7 @@ mod tests { let client = make_client(provider as Arc).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - let session = Session::new(client, profile, env, SessionConfig::default(), None); + let session = Session::new(client, profile, env, SessionOptions::default(), None); let agent_id = manager.spawn(session, "Do something".into(), 0).unwrap(); let _ = manager.wait(&agent_id).await.unwrap(); diff --git a/lib/crates/fabro-agent/src/test_support.rs b/lib/crates/fabro-agent/src/test_support.rs index 95d2f832a..b13a914fe 100644 --- a/lib/crates/fabro-agent/src/test_support.rs +++ b/lib/crates/fabro-agent/src/test_support.rs @@ -1,7 +1,7 @@ pub use fabro_sandbox::test_support::{MockSandbox, MutableMockSandbox}; use crate::agent_profile::AgentProfile; -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::profiles::EnvContext; use crate::sandbox::*; use crate::session::Session; @@ -197,7 +197,7 @@ pub async fn make_session(responses: Vec) -> Session { let client = make_client(provider).await; let profile = Arc::new(TestProfile::new()); let env = Arc::new(MockSandbox::default()); - Session::new(client, profile, env, SessionConfig::default(), None) + Session::new(client, profile, env, SessionOptions::default(), None) } pub async fn make_session_with_tools(responses: Vec, registry: ToolRegistry) -> Session { @@ -205,10 +205,10 @@ pub async fn make_session_with_tools(responses: Vec, registry: ToolReg let client = make_client(provider).await; let profile = Arc::new(TestProfile::with_tools(registry)); let env = Arc::new(MockSandbox::default()); - Session::new(client, profile, env, SessionConfig::default(), None) + Session::new(client, profile, env, SessionOptions::default(), None) } -pub async fn make_session_with_config(responses: Vec, config: SessionConfig) -> Session { +pub async fn make_session_with_config(responses: Vec, config: SessionOptions) -> Session { let provider = Arc::new(MockLlmProvider::new(responses)); let client = make_client(provider).await; let profile = Arc::new(TestProfile::new()); @@ -219,7 +219,7 @@ pub async fn make_session_with_config(responses: Vec, config: SessionC pub async fn make_session_with_tools_and_config( responses: Vec, registry: ToolRegistry, - config: SessionConfig, + config: SessionOptions, ) -> Session { let provider = Arc::new(MockLlmProvider::new(responses)); let client = make_client(provider).await; diff --git a/lib/crates/fabro-agent/src/tool_execution.rs b/lib/crates/fabro-agent/src/tool_execution.rs index b79184625..6bf3c41b8 100644 --- a/lib/crates/fabro-agent/src/tool_execution.rs +++ b/lib/crates/fabro-agent/src/tool_execution.rs @@ -1,4 +1,4 @@ -use crate::config::{SessionConfig, ToolHookCallback, ToolHookDecision}; +use crate::config::{SessionOptions, ToolHookCallback, ToolHookDecision}; use crate::event::EventEmitter; use crate::sandbox::Sandbox; use crate::tool_registry::{RegisteredTool, ToolContext, ToolRegistry}; @@ -20,7 +20,7 @@ pub async fn execute_tool_calls( env: Arc, tool_hooks: Option<&Arc>, cancel_token: &CancellationToken, - config: &SessionConfig, + config: &SessionOptions, emitter: &EventEmitter, session_id: &str, tool_env: Option<&HashMap>, @@ -61,7 +61,7 @@ async fn execute_tool_calls_sequential( env: Arc, tool_hooks: Option<&Arc>, cancel_token: &CancellationToken, - config: &SessionConfig, + config: &SessionOptions, emitter: &EventEmitter, session_id: &str, tool_env: Option<&HashMap>, @@ -97,7 +97,7 @@ async fn execute_tool_calls_parallel( env: Arc, tool_hooks: Option<&Arc>, cancel_token: &CancellationToken, - config: &SessionConfig, + config: &SessionOptions, emitter: &EventEmitter, session_id: &str, tool_env: Option<&HashMap>, @@ -144,7 +144,7 @@ pub async fn execute_and_emit_one_tool( env: Arc, tool_hooks: Option<&Arc>, cancel_token: CancellationToken, - config: &SessionConfig, + config: &SessionOptions, emitter: &EventEmitter, session_id: &str, tool_env: Option<&HashMap>, @@ -171,7 +171,7 @@ async fn execute_and_emit_one_tool_with_lookup( env: Arc, tool_hooks: Option<&Arc>, cancel_token: CancellationToken, - config: &SessionConfig, + config: &SessionOptions, emitter: &EventEmitter, session_id: &str, tool_env: Option<&HashMap>, @@ -294,7 +294,7 @@ async fn execute_one_tool( fn truncate_tool_result( result: &ToolResult, tool_name: &str, - config: &SessionConfig, + config: &SessionOptions, ) -> ToolResult { let truncated_content = match &result.content { serde_json::Value::String(s) => { @@ -461,7 +461,7 @@ mod tests { let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"})); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, @@ -491,7 +491,7 @@ mod tests { let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"})); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, @@ -521,7 +521,7 @@ mod tests { let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"})); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); execute_and_emit_one_tool( &tc, @@ -556,7 +556,7 @@ mod tests { let tc = make_tool_call("fail_tool", "call_1", serde_json::json!({})); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); execute_and_emit_one_tool( &tc, @@ -588,7 +588,7 @@ mod tests { let tc = make_tool_call("echo", "call_1", serde_json::json!({"text": "hello"})); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, @@ -628,7 +628,7 @@ mod tests { serde_json::json!({"file_path": "a.ts", "content": "new"}), ); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, @@ -655,7 +655,7 @@ mod tests { let sandbox = make_guarded_sandbox(HashMap::from([("a.ts".into(), "content".into())])); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); // First read the file let read_tc = make_tool_call( @@ -707,7 +707,7 @@ mod tests { let sandbox = make_guarded_sandbox(HashMap::from([("a.ts".into(), "content".into())])); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); // Grep matching a.ts let grep_tc = make_tool_call("grep", "call_1", serde_json::json!({"pattern": "content"})); @@ -759,7 +759,7 @@ mod tests { serde_json::json!({"file_path": "a.ts", "old_string": "content", "new_string": "updated"}), ); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, @@ -790,7 +790,7 @@ mod tests { serde_json::json!({"file_path": "new.ts", "content": "hello"}), ); let emitter = EventEmitter::new(); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = execute_and_emit_one_tool( &tc, diff --git a/lib/crates/fabro-agent/src/tools.rs b/lib/crates/fabro-agent/src/tools.rs index a78c92bf3..43c808956 100644 --- a/lib/crates/fabro-agent/src/tools.rs +++ b/lib/crates/fabro-agent/src/tools.rs @@ -1,4 +1,4 @@ -use crate::config::SessionConfig; +use crate::config::SessionOptions; use crate::sandbox::GrepOptions; use crate::tool_registry::{RegisteredTool, ToolRegistry}; use fabro_llm::client::Client; @@ -44,11 +44,11 @@ fn html_to_markdown(text: &str) -> String { /// `shell`, `grep`, `glob`, `web_search`, and `web_fetch`. /// /// The shell tool uses `config` to set its default and max timeouts. Pass a custom -/// `SessionConfig` (e.g. with a longer `default_command_timeout_ms`) for providers +/// `SessionOptions` (e.g. with a longer `default_command_timeout_ms`) for providers /// that need non-default shell behavior. pub fn register_core_tools( registry: &mut ToolRegistry, - config: &SessionConfig, + config: &SessionOptions, summarizer: Option, ) { registry.register(make_read_file_tool()); @@ -190,11 +190,11 @@ pub fn make_edit_file_tool() -> RegisteredTool { #[must_use] pub fn make_shell_tool() -> RegisteredTool { - make_shell_tool_with_config(&SessionConfig::default()) + make_shell_tool_with_config(&SessionOptions::default()) } #[must_use] -pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool { +pub fn make_shell_tool_with_config(config: &SessionOptions) -> RegisteredTool { let default_timeout = config.default_command_timeout_ms; let max_timeout = config.max_command_timeout_ms; RegisteredTool { diff --git a/lib/crates/fabro-agent/src/truncation.rs b/lib/crates/fabro-agent/src/truncation.rs index 8ed70ec7c..78cfa1152 100644 --- a/lib/crates/fabro-agent/src/truncation.rs +++ b/lib/crates/fabro-agent/src/truncation.rs @@ -1,4 +1,4 @@ -use crate::config::SessionConfig; +use crate::config::SessionOptions; /// Round a byte index down to the nearest UTF-8 char boundary. /// Stable equivalent of `str::floor_char_boundary` (nightly-only). @@ -99,7 +99,7 @@ pub fn truncate_lines(output: &str, max_lines: usize) -> String { } #[must_use] -pub fn truncate_tool_output(output: &str, tool_name: &str, config: &SessionConfig) -> String { +pub fn truncate_tool_output(output: &str, tool_name: &str, config: &SessionOptions) -> String { let mode = default_truncation_mode(tool_name); // Char truncation first @@ -180,7 +180,7 @@ mod tests { // Create an output that is large in chars and many lines let long_line = "x".repeat(50_000); let output = format!("{long_line}\n{long_line}"); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = truncate_tool_output(&output, "shell", &config); // Should have been char-truncated first (30k limit for shell) assert!(result.len() < output.len()); @@ -189,7 +189,7 @@ mod tests { #[test] fn config_override_char_limit() { let output = "x".repeat(5000); - let mut config = SessionConfig::default(); + let mut config = SessionOptions::default(); config.tool_output_limits.insert("my_tool".into(), 100); let result = truncate_tool_output(&output, "my_tool", &config); assert!(result.len() < output.len()); @@ -200,7 +200,7 @@ mod tests { fn config_override_line_limit() { let lines: Vec = (1..=100).map(|i| format!("line {i}")).collect(); let output = lines.join("\n"); - let mut config = SessionConfig::default(); + let mut config = SessionOptions::default(); config.tool_line_limits.insert("my_tool".into(), 10); let result = truncate_tool_output(&output, "my_tool", &config); assert!(result.contains("lines omitted")); @@ -209,7 +209,7 @@ mod tests { #[test] fn unknown_tool_no_truncation() { let output = "x".repeat(200); - let config = SessionConfig::default(); + let config = SessionOptions::default(); let result = truncate_tool_output(&output, "unknown_tool", &config); assert_eq!(result, output); } diff --git a/lib/crates/fabro-agent/src/v4a_patch.rs b/lib/crates/fabro-agent/src/v4a_patch.rs index 167c6a7ba..ee772f8d2 100644 --- a/lib/crates/fabro-agent/src/v4a_patch.rs +++ b/lib/crates/fabro-agent/src/v4a_patch.rs @@ -1401,7 +1401,7 @@ def gamma(): #[tokio::test] async fn e2e_through_tool_executor() { - use crate::config::SessionConfig; + use crate::config::SessionOptions; use crate::session::Session; use crate::test_support::{ MockLlmProvider, TestProfile, make_client, text_response, tool_call_response, @@ -1451,8 +1451,13 @@ def farewell(name): let provider = Arc::new(MockLlmProvider::new(responses)); let client = make_client(provider).await; let profile = Arc::new(TestProfile::with_tools(registry)); - let mut session = - Session::new(client, profile, env.clone(), SessionConfig::default(), None); + let mut session = Session::new( + client, + profile, + env.clone(), + SessionOptions::default(), + None, + ); session.initialize().await; session .process_input("Update the greeting functions") diff --git a/lib/crates/fabro-agent/tests/it/parity_matrix.rs b/lib/crates/fabro-agent/tests/it/parity_matrix.rs index 5a3381979..b6df501d6 100644 --- a/lib/crates/fabro-agent/tests/it/parity_matrix.rs +++ b/lib/crates/fabro-agent/tests/it/parity_matrix.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use fabro_agent::subagent::SessionFactory; use fabro_agent::{ AgentProfile, AnthropicProfile, GeminiProfile, LocalSandbox, OpenAiProfile, Session, - SessionConfig, SubAgentManager, WebFetchSummarizer, + SessionOptions, SubAgentManager, WebFetchSummarizer, }; use fabro_llm::client::Client; use fabro_llm::provider::{Provider, ProviderAdapter}; @@ -16,7 +16,7 @@ use fabro_test::{TwinScenario, TwinScenarios, TwinToolCall, twin_openai}; use tokio::sync::Mutex as AsyncMutex; #[derive(Clone)] -struct OpenAiTwinConfig { +struct OpenAiTwinOptions { base_url: String, api_key: String, } @@ -70,7 +70,7 @@ async fn make_session( provider: Provider, model: &str, cwd: &Path, - twin: Option, + twin: Option, ) -> Session { let client = make_client(provider, twin.as_ref()).await; let mut profile = build_profile(provider, model, &client); @@ -110,16 +110,16 @@ async fn make_session( factory_client.clone(), sub_profile, sub_env, - SessionConfig::default(), + SessionOptions::default(), None, ) }); profile.register_subagent_tools(manager, factory, 0); let profile: Arc = Arc::from(profile); - let config = SessionConfig { + let config = SessionOptions { max_turns: 20, - ..SessionConfig::default() + ..SessionOptions::default() }; Session::new(client, profile, env, config, None) } @@ -128,8 +128,8 @@ async fn make_session_with_config( provider: Provider, model: &str, cwd: &Path, - config: SessionConfig, - twin: Option, + config: SessionOptions, + twin: Option, ) -> Session { let client = make_client(provider, twin.as_ref()).await; let profile: Arc = Arc::from(build_profile(provider, model, &client)); @@ -137,7 +137,7 @@ async fn make_session_with_config( Session::new(client, profile, env, config, None) } -async fn make_client(provider: Provider, twin: Option<&OpenAiTwinConfig>) -> Client { +async fn make_client(provider: Provider, twin: Option<&OpenAiTwinOptions>) -> Client { if provider == Provider::OpenAi && fabro_test::TestMode::from_env().is_twin() { return make_twin_client(twin.expect("openai twin config should be provided")).await; } @@ -145,7 +145,7 @@ async fn make_client(provider: Provider, twin: Option<&OpenAiTwinConfig>) -> Cli Client::from_env().await.expect("Client::from_env failed") } -async fn make_twin_client(twin: &OpenAiTwinConfig) -> Client { +async fn make_twin_client(twin: &OpenAiTwinOptions) -> Client { let adapter: Arc = Arc::new(OpenAiAdapter::new(twin.api_key.clone()).with_base_url(twin.base_url.clone())); let mut providers: HashMap> = HashMap::new(); @@ -174,7 +174,7 @@ macro_rules! openai_twin_provider_test { async fn []() { let tmp = tempfile::tempdir().expect("failed to create tempdir"); let (base_url, api_key) = fabro_test::e2e_openai!(); - let twin = OpenAiTwinConfig { base_url, api_key }; + let twin = OpenAiTwinOptions { base_url, api_key }; if fabro_test::TestMode::from_env().is_twin() { load_openai_twin_scenario(stringify!($scenario), &twin.api_key, tmp.path()) .await; @@ -650,10 +650,10 @@ macro_rules! reasoning_effort_tests { #[fabro_macros::e2e_test($(live($key)),+)] async fn $test_name() { let tmp = tempfile::tempdir().expect("failed to create tempdir"); - let config = SessionConfig { + let config = SessionOptions { max_turns: 20, reasoning_effort: Some(fabro_llm::types::ReasoningEffort::Low), - ..SessionConfig::default() + ..SessionOptions::default() }; let mut session = make_session_with_config($provider, $model, tmp.path(), config, None).await; @@ -728,10 +728,10 @@ macro_rules! loop_detection_tests { #[fabro_macros::e2e_test($(live($key)),+)] async fn $test_name() { let tmp = tempfile::tempdir().expect("failed to create tempdir"); - let config = SessionConfig { + let config = SessionOptions { max_turns: 20, loop_detection_window: 3, - ..SessionConfig::default() + ..SessionOptions::default() }; let mut session = make_session_with_config($provider, $model, tmp.path(), config, None).await; diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index cd962bc21..d7433b61d 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -3,7 +3,7 @@ use anyhow::Result; use fabro_agent::cli::run_with_args_and_client; use fabro_agent::cli::{AgentArgs, OutputFormat, run_with_args}; use fabro_config::mcp::McpServerEntry; -use fabro_mcp::config::McpServerConfig; +use fabro_mcp::config::McpServerSettings; use crate::args::GlobalArgs; use crate::user_config; @@ -28,7 +28,7 @@ pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result globals.server_url.as_deref(), &cli_settings, ); - let mcp_servers: Vec = cli_settings + let mcp_servers: Vec = cli_settings .mcp_servers .into_iter() .map(|(name, entry): (String, McpServerEntry)| entry.into_config(name)) diff --git a/lib/crates/fabro-cli/src/commands/preflight.rs b/lib/crates/fabro-cli/src/commands/preflight.rs index b5e1d9af4..8b141609e 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -8,7 +8,7 @@ use fabro_graphviz::graph::{Graph, is_llm_handler_type}; use fabro_llm::client::Client as LlmClient; use fabro_model::{Catalog, Provider}; use fabro_sandbox::daytona::{DaytonaConfig, detect_repo_info}; -use fabro_sandbox::{DockerSandboxConfig, Sandbox, SandboxProvider, SandboxSpec}; +use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec}; use fabro_util::check_report::CheckReport; use fabro_util::terminal::Styles; use fabro_workflow::git::{GitSyncStatus, sync_status}; @@ -274,9 +274,9 @@ async fn run_preflight( .await .map_err(|e| e.to_string()), SandboxProvider::Docker => SandboxSpec::Docker { - config: DockerSandboxConfig { + config: DockerSandboxOptions { host_working_directory: working_directory.to_string_lossy().to_string(), - ..DockerSandboxConfig::default() + ..DockerSandboxOptions::default() }, } .build(None) diff --git a/lib/crates/fabro-config/src/config.rs b/lib/crates/fabro-config/src/config.rs index 381bda5a2..56c6f46c2 100644 --- a/lib/crates/fabro-config/src/config.rs +++ b/lib/crates/fabro-config/src/config.rs @@ -4,14 +4,14 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::combine::Combine; -use crate::hook::{HookConfig, HookDefinition}; +use crate::hook::{HookDefinition, HookSettings}; use crate::mcp::McpServerEntry; use crate::project::{self, ProjectConfig}; use crate::run::{ AssetsConfig, CheckpointConfig, GitHubConfig, LlmConfig, PullRequestConfig, SetupConfig, }; use crate::sandbox::SandboxConfig; -use crate::server::{self, ApiConfig, Features, GitConfig, LogConfig, WebConfig}; +use crate::server::{self, ApiConfig, FeaturesConfig, GitConfig, LogConfig, WebConfig}; use crate::settings::FabroSettings; use crate::user::{self, ExecConfig, ExecutionMode, ServerConfig}; @@ -118,7 +118,7 @@ pub struct ConfigLayer { pub api: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub features: Option, + pub features: Option, // --- Shared fields --- #[serde(default, skip_serializing_if = "Option::is_none")] @@ -139,8 +139,8 @@ impl Combine for ConfigLayer { } else if other.hooks.is_empty() { self.hooks } else { - HookConfig { hooks: other.hooks } - .merge(HookConfig { hooks: self.hooks }) + HookSettings { hooks: other.hooks } + .merge(HookSettings { hooks: self.hooks }) .hooks }; diff --git a/lib/crates/fabro-config/src/server.rs b/lib/crates/fabro-config/src/server.rs index eb6039b38..92bfa2700 100644 --- a/lib/crates/fabro-config/src/server.rs +++ b/lib/crates/fabro-config/src/server.rs @@ -153,14 +153,14 @@ impl From for WebSettings { } #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize, crate::Combine)] -pub struct Features { +pub struct FeaturesConfig { pub session_sandboxes: Option, /// Experimental: enable automatic retro generation after workflow runs. pub retros: Option, } -impl From for FeaturesSettings { - fn from(value: Features) -> Self { +impl From for FeaturesSettings { + fn from(value: FeaturesConfig) -> Self { Self { session_sandboxes: value.session_sandboxes.unwrap_or(false), retros: value.retros.unwrap_or(false), diff --git a/lib/crates/fabro-devcontainer/src/compose.rs b/lib/crates/fabro-devcontainer/src/compose.rs index a7ccbfc60..d23115ab5 100644 --- a/lib/crates/fabro-devcontainer/src/compose.rs +++ b/lib/crates/fabro-devcontainer/src/compose.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; /// Extracted configuration from a Docker Compose service. #[derive(Debug, Clone, Default)] -pub(crate) struct ComposeServiceConfig { +pub(crate) struct ComposeServiceSpec { pub image: Option, pub build: Option, pub ports: Vec, @@ -22,7 +22,7 @@ pub(crate) struct ComposeBuild { pub(crate) fn parse_compose( compose_path: &Path, service_name: &str, -) -> Result { +) -> Result { let contents = std::fs::read_to_string(compose_path) .map_err(|e| format!("failed to read compose file: {e}"))?; @@ -48,7 +48,7 @@ pub(crate) fn parse_compose( .and_then(|v| v.as_str()) .map(String::from); - Ok(ComposeServiceConfig { + Ok(ComposeServiceSpec { image, build, ports, @@ -158,8 +158,8 @@ fn parse_environment(service: &serde_yaml::Value) -> HashMap { pub(crate) fn parse_compose_multi( compose_paths: &[PathBuf], service_name: &str, -) -> Result { - let mut merged = ComposeServiceConfig::default(); +) -> Result { + let mut merged = ComposeServiceSpec::default(); let mut found_service = false; for path in compose_paths { diff --git a/lib/crates/fabro-devcontainer/src/lib.rs b/lib/crates/fabro-devcontainer/src/lib.rs index ee4c9dd49..17952f2a6 100644 --- a/lib/crates/fabro-devcontainer/src/lib.rs +++ b/lib/crates/fabro-devcontainer/src/lib.rs @@ -23,7 +23,7 @@ pub enum Command { /// Parsed and resolved devcontainer configuration — everything needed to create a sandbox. #[derive(Debug, Clone)] -pub struct DevcontainerConfig { +pub struct DevcontainerSpec { /// Generated Dockerfile content pub dockerfile: String, /// Directory for docker build context @@ -159,7 +159,7 @@ pub struct DevcontainerResolver; impl DevcontainerResolver { /// path: repo root (or explicit .devcontainer/ path) - pub async fn resolve(path: &Path) -> Result { + pub async fn resolve(path: &Path) -> Result { let (json_path, devcontainer) = Self::find_and_parse(path)?; let repo_root = Self::repo_root_from_json_path(&json_path, path); let base_dir = json_path.parent().unwrap_or(path); @@ -246,7 +246,7 @@ impl DevcontainerResolver { check_no_build_context_copies(&dockerfile)?; - return Ok(DevcontainerConfig { + return Ok(DevcontainerSpec { dockerfile, build_context: compose_base_dir.to_path_buf(), build_args: HashMap::new(), @@ -384,7 +384,7 @@ impl DevcontainerResolver { post_start_commands.push(Self::convert_lifecycle_command(cmd)); } - Ok(DevcontainerConfig { + Ok(DevcontainerSpec { dockerfile: dockerfile_content, build_context, build_args, diff --git a/lib/crates/fabro-devcontainer/src/types.rs b/lib/crates/fabro-devcontainer/src/types.rs index 1bea0f751..e02881780 100644 --- a/lib/crates/fabro-devcontainer/src/types.rs +++ b/lib/crates/fabro-devcontainer/src/types.rs @@ -9,7 +9,7 @@ pub struct DevcontainerJson { pub image: Option, /// Dockerfile build config - pub build: Option, + pub build: Option, /// Docker Compose file path(s) (compose mode) pub docker_compose_file: Option, @@ -63,7 +63,7 @@ pub struct DevcontainerJson { /// Build configuration for Dockerfile mode. #[derive(Debug, Clone, Deserialize)] -pub struct BuildConfig { +pub struct BuildSpec { /// Path to Dockerfile (relative to devcontainer.json) pub dockerfile: Option, diff --git a/lib/crates/fabro-github/tests/integration.rs b/lib/crates/fabro-github/tests/integration.rs index f26d83971..effbc5f9e 100644 --- a/lib/crates/fabro-github/tests/integration.rs +++ b/lib/crates/fabro-github/tests/integration.rs @@ -3,7 +3,7 @@ use fabro_github::{ create_installation_access_token_for_pr, create_pull_request, enable_auto_merge, get_pull_request, merge_pull_request, resolve_authenticated_url, sign_app_jwt, }; -use fabro_test::{GitHubAppConfig, GitHubAppState, TwinGitHub}; +use fabro_test::{GitHubAppOptions, GitHubAppState, TwinGitHub}; const TEST_RSA_KEY: &str = include_str!("../src/testdata/rsa_private.pem"); @@ -16,7 +16,7 @@ fn github_credentials() -> GitHubAppCredentials { fn standard_app_state() -> GitHubAppState { let mut state = GitHubAppState::new(); - state.register_app(GitHubAppConfig { + state.register_app(GitHubAppOptions { app_id: "42".into(), slug: "test-app".into(), owner_login: "acme".into(), diff --git a/lib/crates/fabro-hooks/src/bridge.rs b/lib/crates/fabro-hooks/src/bridge.rs index 50aa286be..ae4476d9f 100644 --- a/lib/crates/fabro-hooks/src/bridge.rs +++ b/lib/crates/fabro-hooks/src/bridge.rs @@ -73,7 +73,7 @@ impl ToolHookCallback for WorkflowToolHookCallback { #[cfg(test)] mod tests { use super::*; - use crate::config::{HookConfig, HookDefinition}; + use crate::config::{HookDefinition, HookSettings}; use crate::executor::HookExecutor; use crate::types::{HookContext, HookResult}; use fabro_types::fixtures; @@ -143,7 +143,7 @@ mod tests { captured_contexts: captured.clone(), decision: HookDecision::Proceed, }); - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::PreToolUse)], }; let runner = Arc::new(HookRunner::with_executor(config, executor)); @@ -174,7 +174,7 @@ mod tests { reason: Some("forbidden".into()), }, }); - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::PreToolUse)], }; let runner = Arc::new(HookRunner::with_executor(config, executor)); @@ -196,7 +196,7 @@ mod tests { captured_contexts: Arc::new(Mutex::new(Vec::new())), decision: HookDecision::Proceed, }); - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::PreToolUse)], }; let runner = Arc::new(HookRunner::with_executor(config, executor)); @@ -214,7 +214,7 @@ mod tests { captured_contexts: captured.clone(), decision: HookDecision::Proceed, }); - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::PostToolUse)], }; let runner = Arc::new(HookRunner::with_executor(config, executor)); @@ -243,7 +243,7 @@ mod tests { captured_contexts: captured.clone(), decision: HookDecision::Proceed, }); - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::PostToolUseFailure)], }; let runner = Arc::new(HookRunner::with_executor(config, executor)); diff --git a/lib/crates/fabro-hooks/src/config.rs b/lib/crates/fabro-hooks/src/config.rs index 93ebea090..72f52ca45 100644 --- a/lib/crates/fabro-hooks/src/config.rs +++ b/lib/crates/fabro-hooks/src/config.rs @@ -1 +1 @@ -pub use fabro_config::hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode}; +pub use fabro_config::hook::{HookDefinition, HookEvent, HookSettings, HookType, TlsMode}; diff --git a/lib/crates/fabro-hooks/src/executor.rs b/lib/crates/fabro-hooks/src/executor.rs index fdca2747d..de73cf7a9 100644 --- a/lib/crates/fabro-hooks/src/executor.rs +++ b/lib/crates/fabro-hooks/src/executor.rs @@ -313,7 +313,7 @@ impl HookExecutorImpl { } }; - let config = fabro_agent::SessionConfig::default(); + let config = fabro_agent::SessionOptions::default(); let mut registry = fabro_agent::ToolRegistry::new(); fabro_agent::register_core_tools(&mut registry, &config, None); let tool_defs = registry.definitions(); diff --git a/lib/crates/fabro-hooks/src/lib.rs b/lib/crates/fabro-hooks/src/lib.rs index 76c85a393..379297b12 100644 --- a/lib/crates/fabro-hooks/src/lib.rs +++ b/lib/crates/fabro-hooks/src/lib.rs @@ -5,6 +5,6 @@ pub mod runner; pub mod types; pub use bridge::WorkflowToolHookCallback; -pub use config::{HookConfig, HookDefinition, HookType, TlsMode}; +pub use config::{HookDefinition, HookSettings, HookType, TlsMode}; pub use runner::HookRunner; pub use types::{HookContext, HookDecision, HookEvent}; diff --git a/lib/crates/fabro-hooks/src/runner.rs b/lib/crates/fabro-hooks/src/runner.rs index e56aee971..29771f7ca 100644 --- a/lib/crates/fabro-hooks/src/runner.rs +++ b/lib/crates/fabro-hooks/src/runner.rs @@ -4,13 +4,13 @@ use std::sync::Arc; use fabro_agent::Sandbox; -use crate::config::{HookConfig, HookDefinition}; +use crate::config::{HookDefinition, HookSettings}; use crate::executor::{HookExecutor, HookExecutorImpl}; use crate::types::{HookContext, HookDecision}; /// Central orchestrator: filters matching hooks, executes them, merges decisions. pub struct HookRunner { - config: HookConfig, + config: HookSettings, executor: Arc, /// Pre-compiled regexes keyed by matcher pattern string. compiled_matchers: HashMap, @@ -18,7 +18,7 @@ pub struct HookRunner { impl HookRunner { #[must_use] - pub fn new(config: HookConfig) -> Self { + pub fn new(config: HookSettings) -> Self { let compiled_matchers = Self::compile_matchers(&config); Self { config, @@ -29,7 +29,7 @@ impl HookRunner { /// Create a HookRunner with a custom executor (for testing). #[cfg(test)] - pub fn with_executor(config: HookConfig, executor: Arc) -> Self { + pub fn with_executor(config: HookSettings, executor: Arc) -> Self { let compiled_matchers = Self::compile_matchers(&config); Self { config, @@ -38,7 +38,7 @@ impl HookRunner { } } - fn compile_matchers(config: &HookConfig) -> HashMap { + fn compile_matchers(config: &HookSettings) -> HashMap { let mut map = HashMap::new(); for hook in &config.hooks { if let Some(ref pattern) = hook.matcher { @@ -210,7 +210,7 @@ impl HookRunner { #[cfg(test)] mod tests { use super::*; - use crate::config::HookConfig; + use crate::config::HookSettings; use crate::types::{HookContext, HookEvent, HookResult}; use fabro_types::fixtures; @@ -260,7 +260,7 @@ mod tests { #[tokio::test] async fn no_hooks_returns_proceed() { - let runner = HookRunner::new(HookConfig::default()); + let runner = HookRunner::new(HookSettings::default()); let ctx = make_context(HookEvent::RunStart); let sandbox = make_sandbox(); let decision = runner.run(&ctx, sandbox.clone(), None).await; @@ -269,7 +269,7 @@ mod tests { #[tokio::test] async fn filters_by_event() { - let config = HookConfig { + let config = HookSettings { hooks: vec![ make_hook(HookEvent::RunStart, "a"), make_hook(HookEvent::StageStart, "b"), @@ -291,7 +291,7 @@ mod tests { async fn matcher_filters_by_node_id() { let mut hook = make_hook(HookEvent::StageStart, "filtered"); hook.matcher = Some("agent".into()); - let config = HookConfig { hooks: vec![hook] }; + let config = HookSettings { hooks: vec![hook] }; let runner = HookRunner::with_executor( config, Arc::new(MockExecutor { @@ -318,7 +318,7 @@ mod tests { async fn matcher_filters_by_handler_type() { let mut hook = make_hook(HookEvent::StageStart, "filtered"); hook.matcher = Some("^agent$".into()); - let config = HookConfig { hooks: vec![hook] }; + let config = HookSettings { hooks: vec![hook] }; let runner = HookRunner::with_executor( config, Arc::new(MockExecutor { @@ -339,7 +339,7 @@ mod tests { async fn matcher_filters_by_tool_name() { let mut hook = make_hook(HookEvent::PreToolUse, "tool-filter"); hook.matcher = Some("shell".into()); - let config = HookConfig { hooks: vec![hook] }; + let config = HookSettings { hooks: vec![hook] }; let runner = HookRunner::with_executor( config, Arc::new(MockExecutor { @@ -360,7 +360,7 @@ mod tests { #[tokio::test] async fn blocking_hook_block_decision() { - let config = HookConfig { + let config = HookSettings { hooks: vec![make_hook(HookEvent::RunStart, "blocker")], }; let runner = HookRunner::with_executor( @@ -381,7 +381,7 @@ mod tests { async fn blocking_hook_skip_decision() { let mut hook = make_hook(HookEvent::StageStart, "skipper"); hook.blocking = Some(true); - let config = HookConfig { hooks: vec![hook] }; + let config = HookSettings { hooks: vec![hook] }; let runner = HookRunner::with_executor( config, Arc::new(MockExecutor { @@ -400,7 +400,7 @@ mod tests { async fn non_blocking_hook_doesnt_block() { let mut hook = make_hook(HookEvent::StageComplete, "observer"); hook.blocking = Some(false); - let config = HookConfig { hooks: vec![hook] }; + let config = HookSettings { hooks: vec![hook] }; let runner = HookRunner::with_executor( config, Arc::new(MockExecutor { @@ -418,7 +418,7 @@ mod tests { #[tokio::test] async fn executor_integration_success() { - let config = HookConfig { + let config = HookSettings { hooks: vec![{ let mut h = make_hook(HookEvent::RunStart, "echo-hook"); h.command = Some("exit 0".into()); @@ -434,7 +434,7 @@ mod tests { #[tokio::test] async fn executor_integration_block() { - let config = HookConfig { + let config = HookSettings { hooks: vec![{ let mut h = make_hook(HookEvent::RunStart, "fail-hook"); h.command = Some("exit 1".into()); diff --git a/lib/crates/fabro-llm/src/generate.rs b/lib/crates/fabro-llm/src/generate.rs index 0ddc4c362..3f1aec46a 100644 --- a/lib/crates/fabro-llm/src/generate.rs +++ b/lib/crates/fabro-llm/src/generate.rs @@ -5,7 +5,7 @@ use crate::retry::retry; use crate::tools::{RepairToolCallFn, Tool, execute_all_tools_with_repair}; use crate::types::{ FinishReason, GenerateResult, Message, ObjectStreamEvent, ReasoningEffort, Request, Response, - ResponseFormat, ResponseFormatType, RetryPolicy, StepResult, StreamEvent, TimeoutConfig, + ResponseFormat, ResponseFormatType, RetryPolicy, StepResult, StreamEvent, TimeoutOptions, ToolCall, ToolChoice, ToolDefinition, Usage, }; use fabro_util::backoff::BackoffPolicy; @@ -303,7 +303,7 @@ pub struct GenerateParams { pub provider_options: Option, pub metadata: Option>, pub max_retries: u32, - pub timeout: Option, + pub timeout: Option, pub client: Option>, /// Cancellation token to abort generation (Section 4.8). pub abort_signal: Option, @@ -445,7 +445,7 @@ impl GenerateParams { } #[must_use] - pub const fn timeout(mut self, timeout: TimeoutConfig) -> Self { + pub const fn timeout(mut self, timeout: TimeoutOptions) -> Self { self.timeout = Some(timeout); self } @@ -1558,7 +1558,7 @@ mod tests { #[test] fn generate_params_timeout_builder() { - let params = GenerateParams::new("test-model").timeout(TimeoutConfig { + let params = GenerateParams::new("test-model").timeout(TimeoutOptions { total: Some(30.0), per_step: Some(10.0), }); @@ -2504,7 +2504,7 @@ mod tests { |_args, _ctx| async { Ok(serde_json::json!("72F")) }, )]) .max_tool_rounds(1) - .timeout(TimeoutConfig { + .timeout(TimeoutOptions { total: None, per_step: Some(0.01), // 10ms timeout, provider takes 5s }) @@ -2635,7 +2635,7 @@ mod tests { |_args, _ctx| async { Ok(serde_json::json!("72F")) }, )]) .max_tool_rounds(5) - .timeout(TimeoutConfig { + .timeout(TimeoutOptions { total: Some(0.05), // 50ms total timeout per_step: None, }) diff --git a/lib/crates/fabro-llm/src/providers/gemini.rs b/lib/crates/fabro-llm/src/providers/gemini.rs index 3b8feb10c..7fafe92f7 100644 --- a/lib/crates/fabro-llm/src/providers/gemini.rs +++ b/lib/crates/fabro-llm/src/providers/gemini.rs @@ -62,7 +62,7 @@ struct ApiRequest { #[serde(skip_serializing_if = "Option::is_none")] system_instruction: Option, #[serde(skip_serializing_if = "Option::is_none")] - generation_config: Option, + generation_config: Option, #[serde(skip_serializing_if = "Option::is_none")] tools: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -82,7 +82,7 @@ struct SystemInstruction { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] -struct GenerationConfig { +struct GenerationOptions { #[serde(skip_serializing_if = "Option::is_none")] temperature: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -421,7 +421,7 @@ fn build_api_request(request: &Request) -> serde_json::Value { .as_ref() .map_or((None, None), translate_response_format); - let generation_config = GenerationConfig { + let generation_config = GenerationOptions { temperature: request.temperature, max_output_tokens: request.max_tokens, top_p: request.top_p, diff --git a/lib/crates/fabro-llm/src/types.rs b/lib/crates/fabro-llm/src/types.rs index c42a1f9da..739c305e0 100644 --- a/lib/crates/fabro-llm/src/types.rs +++ b/lib/crates/fabro-llm/src/types.rs @@ -696,12 +696,12 @@ pub use fabro_model::{Model, ModelCosts, ModelFeatures, ModelLimits}; // --- 4.7 Timeouts --- #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TimeoutConfig { +pub struct TimeoutOptions { pub total: Option, pub per_step: Option, } -impl From for TimeoutConfig { +impl From for TimeoutOptions { fn from(total: f64) -> Self { Self { total: Some(total), diff --git a/lib/crates/fabro-mcp/src/client.rs b/lib/crates/fabro-mcp/src/client.rs index 1848c9aae..a89c9329f 100644 --- a/lib/crates/fabro-mcp/src/client.rs +++ b/lib/crates/fabro-mcp/src/client.rs @@ -15,7 +15,7 @@ use tokio::time; use tracing::{debug, error, info, warn}; use crate::client_handler::LoggingClientHandler; -use crate::config::{McpServerConfig, McpTransport}; +use crate::config::{McpServerSettings, McpTransport}; enum ClientState { /// Transport created but handshake not yet performed. @@ -37,7 +37,7 @@ pub struct McpClient { impl McpClient { /// Create a new MCP client from config. Does not connect yet — call `initialize()`. - pub fn new(config: &McpServerConfig) -> Result { + pub fn new(config: &McpServerSettings) -> Result { let transport = match &config.transport { McpTransport::Stdio { command, env } => { let (program, args) = command.split_first().ok_or_else(|| { diff --git a/lib/crates/fabro-mcp/src/config.rs b/lib/crates/fabro-mcp/src/config.rs index 7e89a84c7..38da06fcb 100644 --- a/lib/crates/fabro-mcp/src/config.rs +++ b/lib/crates/fabro-mcp/src/config.rs @@ -1,4 +1,4 @@ pub use fabro_config::mcp::{ - McpServerConfig, McpServerEntry, McpTransport, default_startup_timeout_secs, + McpServerEntry, McpServerSettings, McpTransport, default_startup_timeout_secs, default_tool_timeout_secs, }; diff --git a/lib/crates/fabro-mcp/src/connection_manager.rs b/lib/crates/fabro-mcp/src/connection_manager.rs index 920f9ddc0..3d696da94 100644 --- a/lib/crates/fabro-mcp/src/connection_manager.rs +++ b/lib/crates/fabro-mcp/src/connection_manager.rs @@ -7,7 +7,7 @@ use rmcp::model::{CallToolResult, RawContent}; use tracing::{error, info}; use crate::client::McpClient; -use crate::config::McpServerConfig; +use crate::config::McpServerSettings; const MCP_TOOL_NAME_DELIMITER: &str = "__"; @@ -102,7 +102,7 @@ impl McpConnectionManager { /// Returns a list of `(server_name, result)` for each server. pub async fn start_servers( &mut self, - configs: &[McpServerConfig], + configs: &[McpServerSettings], ) -> Vec<(String, Result)> { let mut results = Vec::new(); @@ -122,7 +122,7 @@ impl McpConnectionManager { results } - async fn start_one_server(&mut self, config: &McpServerConfig) -> Result { + async fn start_one_server(&mut self, config: &McpServerSettings) -> Result { let client = McpClient::new(config)?; client.initialize(config.startup_timeout()).await?; let tools = client.list_tools().await?; diff --git a/lib/crates/fabro-mcp/tests/stdio_integration.rs b/lib/crates/fabro-mcp/tests/stdio_integration.rs index 8821966b8..626c102ac 100644 --- a/lib/crates/fabro-mcp/tests/stdio_integration.rs +++ b/lib/crates/fabro-mcp/tests/stdio_integration.rs @@ -4,12 +4,12 @@ use std::time::Duration; use fabro_mcp::connection_manager::McpConnectionManager; use fabro_mcp::client::McpClient; -use fabro_mcp::config::{McpServerConfig, McpTransport}; +use fabro_mcp::config::{McpServerSettings, McpTransport}; use fabro_mcp::connection_manager::call_result_to_string; -fn test_server_config() -> McpServerConfig { +fn test_server_config() -> McpServerSettings { let test_server = format!("{}/tests/test_mcp_server.py", env!("CARGO_MANIFEST_DIR")); - McpServerConfig { + McpServerSettings { name: "test-echo".into(), transport: McpTransport::Stdio { command: vec!["python3".into(), test_server], diff --git a/lib/crates/fabro-retro/src/retro_agent.rs b/lib/crates/fabro-retro/src/retro_agent.rs index fdf91b7cb..2ecacdae0 100644 --- a/lib/crates/fabro-retro/src/retro_agent.rs +++ b/lib/crates/fabro-retro/src/retro_agent.rs @@ -4,8 +4,8 @@ use std::time::Duration; use fabro_agent::tool_registry::RegisteredTool; use fabro_agent::{ - AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, SessionConfig, - SessionEvent, Turn, + AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, SessionEvent, + SessionOptions, Turn, }; use fabro_llm::client::Client; use fabro_llm::provider::Provider; @@ -159,14 +159,14 @@ pub async fn run_retro_agent( let profile: Arc = Arc::from(profile); - let config = SessionConfig { + let config = SessionOptions { max_tool_rounds_per_input: 20, wall_clock_timeout: Some(Duration::from_secs(180)), // Disable features not needed for retro analysis enable_context_compaction: false, skill_dirs: Some(vec![]), user_instructions: Some(RETRO_SYSTEM_PROMPT.to_string()), - ..SessionConfig::default() + ..SessionOptions::default() }; let mut session = Session::new( diff --git a/lib/crates/fabro-sandbox/src/docker.rs b/lib/crates/fabro-sandbox/src/docker.rs index a27846d78..e3b903948 100644 --- a/lib/crates/fabro-sandbox/src/docker.rs +++ b/lib/crates/fabro-sandbox/src/docker.rs @@ -21,7 +21,7 @@ use tokio::time; use tokio_util::sync::CancellationToken; /// Configuration for a Docker-based sandbox. -pub struct DockerSandboxConfig { +pub struct DockerSandboxOptions { /// Docker image to use. Default: `"fabro-agent:latest"`. pub image: String, /// Host directory to bind-mount into the container. @@ -42,7 +42,7 @@ pub struct DockerSandboxConfig { pub env_vars: Vec, } -impl Default for DockerSandboxConfig { +impl Default for DockerSandboxOptions { fn default() -> Self { Self { image: "fabro-agent:latest".to_string(), @@ -64,7 +64,7 @@ impl Default for DockerSandboxConfig { /// operations, commands, grep, and glob execute inside the container via `docker exec`. pub struct DockerSandbox { docker: Docker, - config: DockerSandboxConfig, + config: DockerSandboxOptions, container_id: OnceCell, cached_platform: std::sync::OnceLock, cached_os_version: std::sync::OnceLock, @@ -77,7 +77,7 @@ impl DockerSandbox { /// /// Validates Docker daemon connectivity but does NOT create a container. /// Call `initialize()` to create and start the container. - pub fn new(config: DockerSandboxConfig) -> Result { + pub fn new(config: DockerSandboxOptions) -> Result { let docker = Docker::connect_with_local_defaults() .map_err(|e| format!("Failed to connect to Docker daemon: {e}"))?; Ok(Self { @@ -791,8 +791,8 @@ mod tests { Docker::connect_with_local_defaults().expect("Docker not available — skipping") } - fn test_config(host_dir: &str) -> DockerSandboxConfig { - DockerSandboxConfig { + fn test_config(host_dir: &str) -> DockerSandboxOptions { + DockerSandboxOptions { host_working_directory: host_dir.to_string(), auto_pull: false, ..Default::default() diff --git a/lib/crates/fabro-sandbox/src/lib.rs b/lib/crates/fabro-sandbox/src/lib.rs index 4cd3d9d7f..e647ce2ed 100644 --- a/lib/crates/fabro-sandbox/src/lib.rs +++ b/lib/crates/fabro-sandbox/src/lib.rs @@ -32,12 +32,12 @@ pub use read_guard::ReadBeforeWriteSandbox; pub use sandbox_provider::SandboxProvider; -pub use worktree::{WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox}; +pub use worktree::{WorktreeEvent, WorktreeEventCallback, WorktreeOptions, WorktreeSandbox}; pub use local::LocalSandbox; #[cfg(feature = "docker")] -pub use docker::{DockerSandbox, DockerSandboxConfig}; +pub use docker::{DockerSandbox, DockerSandboxOptions}; pub use sandbox_record::{SandboxRecord, SandboxRecordExt}; diff --git a/lib/crates/fabro-sandbox/src/reconnect.rs b/lib/crates/fabro-sandbox/src/reconnect.rs index d991a2934..04613370b 100644 --- a/lib/crates/fabro-sandbox/src/reconnect.rs +++ b/lib/crates/fabro-sandbox/src/reconnect.rs @@ -6,7 +6,7 @@ use anyhow::{Context, Result, bail}; #[cfg(feature = "daytona")] use crate::daytona::DaytonaSandbox; #[cfg(feature = "docker")] -use crate::docker::{DockerSandbox, DockerSandboxConfig}; +use crate::docker::{DockerSandbox, DockerSandboxOptions}; use crate::local::LocalSandbox; use crate::sandbox_record::SandboxRecord; @@ -30,10 +30,10 @@ pub async fn reconnect(record: &SandboxRecord) -> Result .as_deref() .unwrap_or("/workspace"); - let config = DockerSandboxConfig { + let config = DockerSandboxOptions { host_working_directory: host_dir.to_string(), container_mount_point: mount_point.to_string(), - ..DockerSandboxConfig::default() + ..DockerSandboxOptions::default() }; let sandbox = DockerSandbox::new(config) .map_err(|e| anyhow::anyhow!("Failed to create Docker sandbox: {e}"))?; diff --git a/lib/crates/fabro-sandbox/src/sandbox_spec.rs b/lib/crates/fabro-sandbox/src/sandbox_spec.rs index 334c533a5..c05f7aa56 100644 --- a/lib/crates/fabro-sandbox/src/sandbox_spec.rs +++ b/lib/crates/fabro-sandbox/src/sandbox_spec.rs @@ -12,7 +12,7 @@ use crate::{Sandbox, SandboxEventCallback}; #[cfg(feature = "daytona")] use crate::daytona::{DaytonaConfig, DaytonaSandbox, DaytonaSnapshotConfig}; #[cfg(feature = "docker")] -use crate::docker::{DockerSandbox, DockerSandboxConfig}; +use crate::docker::{DockerSandbox, DockerSandboxOptions}; use crate::local::LocalSandbox; #[cfg(feature = "daytona")] @@ -25,7 +25,7 @@ pub enum SandboxSpec { }, #[cfg(feature = "docker")] Docker { - config: DockerSandboxConfig, + config: DockerSandboxOptions, }, #[cfg(feature = "daytona")] Daytona { @@ -157,7 +157,7 @@ impl SandboxSpec { } #[cfg(feature = "docker")] Self::Docker { config } => { - let mut sandbox = DockerSandbox::new(DockerSandboxConfig { + let mut sandbox = DockerSandbox::new(DockerSandboxOptions { image: config.image.clone(), host_working_directory: config.host_working_directory.clone(), container_mount_point: config.container_mount_point.clone(), diff --git a/lib/crates/fabro-sandbox/src/worktree.rs b/lib/crates/fabro-sandbox/src/worktree.rs index 198bd1e0a..825760a29 100644 --- a/lib/crates/fabro-sandbox/src/worktree.rs +++ b/lib/crates/fabro-sandbox/src/worktree.rs @@ -24,7 +24,7 @@ pub enum WorktreeEvent { pub type WorktreeEventCallback = Arc; /// Configuration for a `WorktreeSandbox`. -pub struct WorktreeConfig { +pub struct WorktreeOptions { pub branch_name: String, pub base_sha: String, pub worktree_path: String, @@ -39,14 +39,14 @@ pub struct WorktreeConfig { /// The inner sandbox's lifecycle is managed separately by the caller. pub struct WorktreeSandbox { inner: Arc, - config: WorktreeConfig, + config: WorktreeOptions, event_callback: Option, initialized: std::sync::atomic::AtomicBool, } impl WorktreeSandbox { /// Create a new `WorktreeSandbox` wrapping `inner` with the given configuration. - pub fn new(inner: Arc, config: WorktreeConfig) -> Self { + pub fn new(inner: Arc, config: WorktreeOptions) -> Self { Self { inner, config, @@ -357,8 +357,8 @@ mod tests { use crate::test_support::MockSandbox; use std::sync::Mutex; - fn make_config(wt_path: &str) -> WorktreeConfig { - WorktreeConfig { + fn make_config(wt_path: &str) -> WorktreeOptions { + WorktreeOptions { branch_name: "fabro/run/test-branch".to_string(), base_sha: "abc123def456".to_string(), worktree_path: wt_path.to_string(), @@ -366,8 +366,8 @@ mod tests { } } - fn make_config_skip(wt_path: &str) -> WorktreeConfig { - WorktreeConfig { + fn make_config_skip(wt_path: &str) -> WorktreeOptions { + WorktreeOptions { branch_name: "fabro/run/test-branch".to_string(), base_sha: "abc123def456".to_string(), worktree_path: wt_path.to_string(), @@ -432,7 +432,7 @@ mod tests { #[tokio::test] async fn initialize_uses_shell_quoted_values_in_commands() { let (inner, mock) = make_mock(); - let config = WorktreeConfig { + let config = WorktreeOptions { branch_name: "fabro/run/my-branch".to_string(), base_sha: "deadbeef".to_string(), worktree_path: "/tmp/my worktree".to_string(), // path with space @@ -651,7 +651,7 @@ mod tests { std::fs::write(worktree.join("marker.txt"), "UNIQUE_WORKTREE_MARKER").unwrap(); let inner: Arc = Arc::new(LocalSandbox::new(original.clone())); - let config = WorktreeConfig { + let config = WorktreeOptions { branch_name: "test-branch".into(), base_sha: "abc123".into(), worktree_path: worktree.to_string_lossy().to_string(), @@ -692,7 +692,7 @@ mod tests { std::fs::write(worktree.join("worktree_only.txt"), "content").unwrap(); let inner: Arc = Arc::new(LocalSandbox::new(original.clone())); - let config = WorktreeConfig { + let config = WorktreeOptions { branch_name: "test-branch".into(), base_sha: "abc123".into(), worktree_path: worktree.to_string_lossy().to_string(), @@ -726,7 +726,7 @@ mod tests { std::fs::write(worktree.join("only_in_worktree.txt"), "worktree content").unwrap(); let inner: Arc = Arc::new(LocalSandbox::new(original.clone())); - let config = WorktreeConfig { + let config = WorktreeOptions { branch_name: "test-branch".into(), base_sha: "abc123".into(), worktree_path: worktree.to_string_lossy().to_string(), @@ -752,7 +752,7 @@ mod tests { #[test] fn accessors_return_config_values() { let (inner, _mock) = make_mock(); - let config = WorktreeConfig { + let config = WorktreeOptions { branch_name: "my-branch".to_string(), base_sha: "sha123".to_string(), worktree_path: "/path/to/wt".to_string(), diff --git a/lib/crates/fabro-server/build.rs b/lib/crates/fabro-server/build.rs index 64d3d33c8..f2afdaa16 100644 --- a/lib/crates/fabro-server/build.rs +++ b/lib/crates/fabro-server/build.rs @@ -13,8 +13,8 @@ fn main() { return; } - let web_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("../../../apps/fabro-web"); + let web_dir = + PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../../apps/fabro-web"); let status = Command::new("bun") .args(["run", "build"]) diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 5d261ea83..a7506a4ec 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -41,9 +41,9 @@ use tracing::{error, info}; use crate::demo; use crate::error::ApiError; use crate::jwt_auth::{AuthMode, AuthenticatedService}; -use crate::static_files; use crate::sessions as sessions_mod; use crate::sessions::{SessionStore, new_session_store}; +use crate::static_files; use crate::web_auth; use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer}; use fabro_workflow::context::Context; @@ -174,20 +174,23 @@ pub fn build_router(state: Arc, auth_mode: AuthMode) -> Router { Router::new() .route("/health", get(health)) - .layer(middleware::from_fn_with_state(middleware_state, cookie_and_demo_middleware)) + .layer(middleware::from_fn_with_state( + middleware_state, + cookie_and_demo_middleware, + )) .fallback_service(service_fn(move |req: axum_extract::Request| { let dispatch = dispatch.clone(); async move { let path = req.uri().path().to_string(); if path.starts_with("/api/v1/") || path.starts_with("/auth/") || path == "/health" { dispatch.oneshot(req).await - } else if matches!(req.method(), &axum::http::Method::GET | &axum::http::Method::HEAD) - { + } else if matches!( + req.method(), + &axum::http::Method::GET | &axum::http::Method::HEAD + ) { Ok::<_, std::convert::Infallible>(static_files::serve(&path).await) } else { - Ok::<_, std::convert::Infallible>( - StatusCode::NOT_FOUND.into_response(), - ) + Ok::<_, std::convert::Infallible>(StatusCode::NOT_FOUND.into_response()) } } })) diff --git a/lib/crates/fabro-server/src/static_files.rs b/lib/crates/fabro-server/src/static_files.rs index 5c2653315..36422a432 100644 --- a/lib/crates/fabro-server/src/static_files.rs +++ b/lib/crates/fabro-server/src/static_files.rs @@ -73,7 +73,8 @@ fn asset_response(path: &str, bytes: Vec) -> Response { *response.status_mut() = StatusCode::OK; response.headers_mut().insert( header::CONTENT_TYPE, - HeaderValue::from_str(mime.as_ref()).unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream")), + HeaderValue::from_str(mime.as_ref()) + .unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream")), ); response.headers_mut().insert( header::CACHE_CONTROL, diff --git a/lib/crates/fabro-server/src/web_auth.rs b/lib/crates/fabro-server/src/web_auth.rs index 38083954a..def4d6424 100644 --- a/lib/crates/fabro-server/src/web_auth.rs +++ b/lib/crates/fabro-server/src/web_auth.rs @@ -116,7 +116,10 @@ pub fn api_routes() -> Router> { pub fn parse_cookie_header(headers: &HeaderMap) -> CookieJar { let mut jar = CookieJar::new(); - if let Some(raw) = headers.get(header::COOKIE).and_then(|value| value.to_str().ok()) { + if let Some(raw) = headers + .get(header::COOKIE) + .and_then(|value| value.to_str().ok()) + { for part in raw.split(';') { if let Ok(cookie) = Cookie::parse(part.trim().to_string()) { jar.add_original(cookie.into_owned()); @@ -159,7 +162,11 @@ fn features_json(settings: &FabroSettings) -> serde_json::Value { } async fn login_github(State(state): State>) -> Response { - let settings = state.settings.read().expect("settings lock poisoned").clone(); + let settings = state + .settings + .read() + .expect("settings lock poisoned") + .clone(); let Some(client_id) = settings.client_id().map(str::to_string) else { return json_response( StatusCode::CONFLICT, @@ -167,7 +174,10 @@ async fn login_github(State(state): State>) -> Response { ); }; let Some(web_url) = settings.web.as_ref().map(|web| web.url.clone()) else { - return json_response(StatusCode::CONFLICT, json!({"error": "web.url is not configured"})); + return json_response( + StatusCode::CONFLICT, + json!({"error": "web.url is not configured"}), + ); }; let state_token = format!("fabro-{}", ulid::Ulid::new()); @@ -207,9 +217,15 @@ async fn callback_github( json!({"error": "SESSION_SECRET is not configured"}), ); }; - let settings = state.settings.read().expect("settings lock poisoned").clone(); + let settings = state + .settings + .read() + .expect("settings lock poisoned") + .clone(); let cookie_jar = parse_cookie_header(&headers); - let stored_state = cookie_jar.get(OAUTH_STATE_COOKIE_NAME).map(|cookie| cookie.value()); + let stored_state = cookie_jar + .get(OAUTH_STATE_COOKIE_NAME) + .map(|cookie| cookie.value()); if stored_state != Some(params.state.as_str()) { return Redirect::to("/login").into_response(); } @@ -240,7 +256,10 @@ async fn callback_github( ("client_id", client_id.as_str()), ("client_secret", client_secret.as_str()), ("code", params.code.as_str()), - ("redirect_uri", format!("{web_url}/auth/callback/github").as_str()), + ( + "redirect_uri", + format!("{web_url}/auth/callback/github").as_str(), + ), ("state", params.state.as_str()), ]) .send() @@ -279,7 +298,8 @@ async fn callback_github( .send() .await { - Ok(response) if response.status().is_success() => match response.json::().await { + Ok(response) if response.status().is_success() => match response.json::().await + { Ok(profile) => profile, Err(_) => { return json_response( @@ -321,7 +341,8 @@ async fn callback_github( .as_ref() .map(|web| web.auth.allowed_usernames.clone()) .unwrap_or_default(); - if !allowed_usernames.is_empty() && !allowed_usernames.iter().any(|user| user == &profile.login) { + if !allowed_usernames.is_empty() && !allowed_usernames.iter().any(|user| user == &profile.login) + { return Redirect::to("/login?error=unauthorized").into_response(); } @@ -343,13 +364,16 @@ async fn callback_github( let mut jar = CookieJar::new(); jar.private_mut(&session_key).add( - Cookie::build((SESSION_COOKIE_NAME, serde_json::to_string(&session).unwrap_or_default())) - .path("/") - .http_only(true) - .same_site(SameSite::Lax) - .secure(false) - .max_age(Duration::days(30)) - .build(), + Cookie::build(( + SESSION_COOKIE_NAME, + serde_json::to_string(&session).unwrap_or_default(), + )) + .path("/") + .http_only(true) + .same_site(SameSite::Lax) + .secure(false) + .max_age(Duration::days(30)) + .build(), ); jar.add( Cookie::build((OAUTH_STATE_COOKIE_NAME, "")) @@ -387,7 +411,11 @@ async fn auth_me(State(state): State>, headers: HeaderMap) -> Resp return json_response(StatusCode::UNAUTHORIZED, json!({"error": "Unauthorized"})); }; - let settings = state.settings.read().expect("settings lock poisoned").clone(); + let settings = state + .settings + .read() + .expect("settings lock poisoned") + .clone(); let demo_mode = parse_cookie_header(&headers) .get("fabro-demo") .map(|cookie| cookie.value() == "1") @@ -408,7 +436,11 @@ async fn auth_me(State(state): State>, headers: HeaderMap) -> Resp } async fn setup_status(State(state): State>) -> Response { - let settings = state.settings.read().expect("settings lock poisoned").clone(); + let settings = state + .settings + .read() + .expect("settings lock poisoned") + .clone(); let configured = settings .git .as_ref() @@ -476,7 +508,11 @@ async fn setup_register( .unwrap_or_else(|_| PathBuf::from(".")) .join(".env"); - let mut settings = state.settings.read().expect("settings lock poisoned").clone(); + let mut settings = state + .settings + .read() + .expect("settings lock poisoned") + .clone(); let mut git = settings.git.clone().unwrap_or_default(); git.provider = GitProvider::Github; git.app_id = Some(data.id.to_string()); @@ -549,12 +585,13 @@ fn build_server_toml(settings: &FabroSettings, git: &GitSettings) -> String { "auth".to_string(), toml::Value::Table({ let mut auth = toml::Table::new(); - auth.insert("provider".to_string(), toml::Value::String("github".to_string())); + auth.insert( + "provider".to_string(), + toml::Value::String("github".to_string()), + ); auth.insert( "allowed_usernames".to_string(), - toml::Value::Array( - allowed.into_iter().map(toml::Value::String).collect(), - ), + toml::Value::Array(allowed.into_iter().map(toml::Value::String).collect()), ); auth }), @@ -587,7 +624,10 @@ fn build_server_toml(settings: &FabroSettings, git: &GitSettings) -> String { "git".to_string(), toml::Value::Table({ let mut git_table = toml::Table::new(); - git_table.insert("provider".to_string(), toml::Value::String("github".to_string())); + git_table.insert( + "provider".to_string(), + toml::Value::String("github".to_string()), + ); git_table.insert( "app_id".to_string(), toml::Value::String(git.app_id.clone().unwrap_or_default()), @@ -614,7 +654,11 @@ fn write_env_file(path: &PathBuf, updates: &BTreeMap) -> std::io merged.insert(key.trim().to_string(), value.to_string()); } } - merged.extend(updates.iter().map(|(key, value)| (key.clone(), value.clone()))); + merged.extend( + updates + .iter() + .map(|(key, value)| (key.clone(), value.clone())), + ); let body = merged .into_iter() .map(|(key, value)| format!("{key}={value}")) diff --git a/lib/crates/fabro-server/tests/it/api.rs b/lib/crates/fabro-server/tests/it/api.rs index cf79e27e8..5f167aee1 100644 --- a/lib/crates/fabro-server/tests/it/api.rs +++ b/lib/crates/fabro-server/tests/it/api.rs @@ -993,9 +993,7 @@ mod route_prefixes { fabro_server::jwt_auth::AuthMode::Disabled, ); - let cases = [ - (Method::POST, "/completions"), - ]; + let cases = [(Method::POST, "/completions")]; for (method, path) in cases { let req = Request::builder() diff --git a/lib/crates/fabro-slack/src/config.rs b/lib/crates/fabro-slack/src/config.rs index 3fec335bb..d1eb565ed 100644 --- a/lib/crates/fabro-slack/src/config.rs +++ b/lib/crates/fabro-slack/src/config.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Debug, Clone, Default, Deserialize, PartialEq)] -pub struct SlackConfig { +pub struct SlackOptions { pub default_channel: Option, } @@ -24,13 +24,13 @@ pub fn resolve_credentials() -> Option { }) } -pub struct SlackRuntimeConfig { - pub config: SlackConfig, +pub struct SlackRuntimeOptions { + pub config: SlackOptions, pub credentials: SlackCredentials, } -impl SlackRuntimeConfig { - pub fn new(config: SlackConfig, credentials: SlackCredentials) -> Self { +impl SlackRuntimeOptions { + pub fn new(config: SlackOptions, credentials: SlackCredentials) -> Self { Self { config, credentials, @@ -44,14 +44,14 @@ mod tests { #[test] fn parse_empty_toml_defaults() { - let config: SlackConfig = toml::from_str("").unwrap(); + let config: SlackOptions = toml::from_str("").unwrap(); assert_eq!(config.default_channel, None); } #[test] fn parse_with_channel() { let toml_str = r##"default_channel = "#arc-reviews""##; - let config: SlackConfig = toml::from_str(toml_str).unwrap(); + let config: SlackOptions = toml::from_str(toml_str).unwrap(); assert_eq!(config.default_channel.as_deref(), Some("#arc-reviews")); } diff --git a/lib/crates/fabro-test/src/lib.rs b/lib/crates/fabro-test/src/lib.rs index 9a44e5137..7ce68fcbf 100644 --- a/lib/crates/fabro-test/src/lib.rs +++ b/lib/crates/fabro-test/src/lib.rs @@ -491,7 +491,7 @@ use tokio::net::TcpListener as TokioTcpListener; use tokio::sync::OnceCell; use tokio::time; pub use twin_github::AppState as GitHubAppState; -pub use twin_github::state::AppConfig as GitHubAppConfig; +pub use twin_github::state::AppOptions as GitHubAppOptions; use twin_openai::config::Config as TwinConfig; /// A shared twin-openai server instance. diff --git a/lib/crates/fabro-tracker/src/lib.rs b/lib/crates/fabro-tracker/src/lib.rs index 6912f3cff..db38b0a90 100644 --- a/lib/crates/fabro-tracker/src/lib.rs +++ b/lib/crates/fabro-tracker/src/lib.rs @@ -4,7 +4,7 @@ pub mod github; pub mod linear; pub use github::GitHubTracker; -pub use linear::{LINEAR_API_ENDPOINT, LinearConfig, LinearTracker}; +pub use linear::{LINEAR_API_ENDPOINT, LinearOptions, LinearTracker}; /// Shared GraphQL execution used by both provider modules. /// diff --git a/lib/crates/fabro-tracker/src/linear.rs b/lib/crates/fabro-tracker/src/linear.rs index cf9d0f97b..12f22a877 100644 --- a/lib/crates/fabro-tracker/src/linear.rs +++ b/lib/crates/fabro-tracker/src/linear.rs @@ -10,12 +10,12 @@ pub const LINEAR_API_ENDPOINT: &str = "https://api.linear.app/graphql"; const BLOCKS_RELATION_TYPE: &str = "blocks"; #[derive(Clone, Debug)] -pub struct LinearConfig { +pub struct LinearOptions { pub api_key: String, pub endpoint: String, } -impl LinearConfig { +impl LinearOptions { pub fn new(api_key: String) -> Self { Self { api_key, @@ -129,7 +129,7 @@ fn normalize_issue(node: &Value) -> Result { async fn execute_graphql( client: &reqwest::Client, - config: &LinearConfig, + config: &LinearOptions, query: &str, variables: Value, ) -> Result { @@ -154,13 +154,13 @@ fn extract_issues(response: &Value) -> Result, String> { /// A `Tracker` implementation backed by Linear. pub struct LinearTracker { - config: LinearConfig, + config: LinearOptions, client: reqwest::Client, project_slug: String, } impl LinearTracker { - pub fn new(config: LinearConfig, client: reqwest::Client, project_slug: String) -> Self { + pub fn new(config: LinearOptions, client: reqwest::Client, project_slug: String) -> Self { Self { config, client, @@ -356,8 +356,8 @@ impl Tracker for LinearTracker { mod tests { use super::*; - fn mock_config(server_url: &str) -> LinearConfig { - LinearConfig { + fn mock_config(server_url: &str) -> LinearOptions { + LinearOptions { api_key: "lin_api_test123".to_string(), endpoint: format!("{server_url}/graphql"), } @@ -1070,7 +1070,7 @@ mod tests { #[tokio::test] async fn fetch_issues_by_ids_empty() { - let config = LinearConfig::new("unused".to_string()); + let config = LinearOptions::new("unused".to_string()); let tracker = LinearTracker::new(config, reqwest::Client::new(), "proj".to_string()); let issues = tracker.fetch_issues_by_ids(&[]).await.unwrap(); assert!(issues.is_empty()); diff --git a/lib/crates/fabro-types/src/settings/hook.rs b/lib/crates/fabro-types/src/settings/hook.rs index aeb8af0ac..69309b9c5 100644 --- a/lib/crates/fabro-types/src/settings/hook.rs +++ b/lib/crates/fabro-types/src/settings/hook.rs @@ -191,12 +191,12 @@ impl HookDefinition { /// Top-level hook configuration: a list of hook definitions. #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] -pub struct HookConfig { +pub struct HookSettings { #[serde(default)] pub hooks: Vec, } -impl HookConfig { +impl HookSettings { /// Merge with another config. Concatenates lists; on name collisions, `other` wins. #[must_use] pub fn merge(self, other: Self) -> Self { diff --git a/lib/crates/fabro-types/src/settings/mcp.rs b/lib/crates/fabro-types/src/settings/mcp.rs index 7623443a8..63d3bf5ac 100644 --- a/lib/crates/fabro-types/src/settings/mcp.rs +++ b/lib/crates/fabro-types/src/settings/mcp.rs @@ -14,7 +14,7 @@ pub fn default_tool_timeout_secs() -> u64 { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct McpServerConfig { +pub struct McpServerSettings { pub name: String, pub transport: McpTransport, #[serde(default = "default_startup_timeout_secs")] @@ -23,7 +23,7 @@ pub struct McpServerConfig { pub tool_timeout_secs: u64, } -impl McpServerConfig { +impl McpServerSettings { #[must_use] pub fn startup_timeout(&self) -> Duration { Duration::from_secs(self.startup_timeout_secs) @@ -67,7 +67,7 @@ impl Combine for McpTransport { /// MCP server entry as it appears in TOML config files (without a `name` field). /// -/// Converted to [`McpServerConfig`] via [`McpServerEntry::into_config`]. +/// Converted to [`McpServerSettings`] via [`McpServerEntry::into_config`]. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct McpServerEntry { #[serde(flatten)] @@ -79,8 +79,8 @@ pub struct McpServerEntry { } impl McpServerEntry { - pub fn into_config(self, name: String) -> McpServerConfig { - McpServerConfig { + pub fn into_config(self, name: String) -> McpServerSettings { + McpServerSettings { name, transport: self.transport, startup_timeout_secs: self.startup_timeout_secs, @@ -102,7 +102,7 @@ mod tests { #[test] fn stdio_config_construction() { - let config = McpServerConfig { + let config = McpServerSettings { name: "test-server".into(), transport: McpTransport::Stdio { command: vec![ @@ -122,7 +122,7 @@ mod tests { #[test] fn http_config_construction() { - let config = McpServerConfig { + let config = McpServerSettings { name: "remote-server".into(), transport: McpTransport::Http { url: "https://example.com/mcp".into(), @@ -138,7 +138,7 @@ mod tests { #[test] fn serde_round_trip_stdio() { - let config = McpServerConfig { + let config = McpServerSettings { name: "fs".into(), transport: McpTransport::Stdio { command: vec!["node".into(), "server.js".into()], @@ -148,7 +148,7 @@ mod tests { tool_timeout_secs: 90, }; let json = serde_json::to_string(&config).unwrap(); - let deserialized: McpServerConfig = serde_json::from_str(&json).unwrap(); + let deserialized: McpServerSettings = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.name, "fs"); assert_eq!(deserialized.startup_timeout_secs, 15); assert_eq!(deserialized.tool_timeout_secs, 90); @@ -159,7 +159,7 @@ mod tests { #[test] fn serde_round_trip_http() { - let config = McpServerConfig { + let config = McpServerSettings { name: "remote".into(), transport: McpTransport::Http { url: "https://mcp.example.com".into(), @@ -169,7 +169,7 @@ mod tests { tool_timeout_secs: 60, }; let json = serde_json::to_string(&config).unwrap(); - let deserialized: McpServerConfig = serde_json::from_str(&json).unwrap(); + let deserialized: McpServerSettings = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.name, "remote"); assert!( matches!(deserialized.transport, McpTransport::Http { url, .. } if url == "https://mcp.example.com") @@ -179,7 +179,7 @@ mod tests { #[test] fn serde_defaults_applied() { let json = r#"{"name":"minimal","transport":{"type":"stdio","command":["echo"]}}"#; - let config: McpServerConfig = serde_json::from_str(json).unwrap(); + let config: McpServerSettings = serde_json::from_str(json).unwrap(); assert_eq!(config.startup_timeout_secs, 10); assert_eq!(config.tool_timeout_secs, 60); } diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index 4e84dac11..de622095b 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -11,9 +11,9 @@ pub mod sandbox; pub mod server; pub mod user; -pub use hook::{HookConfig, HookDefinition, HookEvent, HookType, TlsMode}; +pub use hook::{HookDefinition, HookEvent, HookSettings, HookType, TlsMode}; pub use mcp::{ - McpServerConfig, McpServerEntry, McpTransport, default_startup_timeout_secs, + McpServerEntry, McpServerSettings, McpTransport, default_startup_timeout_secs, default_tool_timeout_secs, }; pub use project::ProjectSettings; diff --git a/lib/crates/fabro-workflow/src/devcontainer_bridge.rs b/lib/crates/fabro-workflow/src/devcontainer_bridge.rs index 2c1cfeab9..a76d655f7 100644 --- a/lib/crates/fabro-workflow/src/devcontainer_bridge.rs +++ b/lib/crates/fabro-workflow/src/devcontainer_bridge.rs @@ -2,7 +2,7 @@ use std::time::Instant; use sha2::{Digest, Sha256}; -use fabro_devcontainer::DevcontainerConfig; +use fabro_devcontainer::DevcontainerSpec; use crate::event::{EventEmitter, WorkflowRunEvent}; use fabro_agent::sandbox::Sandbox; @@ -16,8 +16,8 @@ pub fn snapshot_name_for_dockerfile(dockerfile: &str) -> String { format!("devcontainer-{}", &hex[..12]) } -/// Map a `DevcontainerConfig` to a `DaytonaSnapshotConfig`. -pub fn devcontainer_to_snapshot_config(dc: &DevcontainerConfig) -> DaytonaSnapshotConfig { +/// Map a `DevcontainerSpec` to a `DaytonaSnapshotConfig`. +pub fn devcontainer_to_snapshot_config(dc: &DevcontainerSpec) -> DaytonaSnapshotConfig { DaytonaSnapshotConfig { name: snapshot_name_for_dockerfile(&dc.dockerfile), dockerfile: Some(DockerfileSource::Inline(dc.dockerfile.clone())), @@ -460,8 +460,8 @@ mod tests { assert_eq!(captured.len(), 2); } - fn test_devcontainer_config(dockerfile: &str) -> DevcontainerConfig { - DevcontainerConfig { + fn test_devcontainer_config(dockerfile: &str) -> DevcontainerSpec { + DevcontainerSpec { dockerfile: dockerfile.to_string(), build_context: std::path::PathBuf::from("."), build_args: HashMap::new(), diff --git a/lib/crates/fabro-workflow/src/handler/llm/api.rs b/lib/crates/fabro-workflow/src/handler/llm/api.rs index f5323215d..066d06164 100644 --- a/lib/crates/fabro-workflow/src/handler/llm/api.rs +++ b/lib/crates/fabro-workflow/src/handler/llm/api.rs @@ -5,12 +5,12 @@ use async_trait::async_trait; use fabro_agent::{ AgentEvent, AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, - SessionConfig, Turn, + SessionOptions, Turn, subagent::{SessionFactory, SubAgentManager}, }; use fabro_llm::client::Client; use fabro_llm::types::{Message, Request, Usage}; -use fabro_mcp::config::McpServerConfig; +use fabro_mcp::config::McpServerSettings; use fabro_model::FallbackTarget; use fabro_model::Provider; use tokio::fs; @@ -120,7 +120,7 @@ pub struct AgentApiBackend { fallback_chain: Vec, sessions: Mutex>, env: HashMap, - mcp_servers: Vec, + mcp_servers: Vec, } impl AgentApiBackend { @@ -143,7 +143,7 @@ impl AgentApiBackend { } #[must_use] - pub fn with_mcp_servers(mut self, servers: Vec) -> Self { + pub fn with_mcp_servers(mut self, servers: Vec) -> Self { self.mcp_servers = servers; self } @@ -178,7 +178,7 @@ impl AgentApiBackend { sandbox: &Arc, env: &HashMap, tool_hooks: Option>, - mcp_servers: Vec, + mcp_servers: Vec, ) -> Result { let client = Client::from_env() .await @@ -186,13 +186,13 @@ impl AgentApiBackend { let mut profile = build_profile(model, provider); - let config = SessionConfig { + let config = SessionOptions { max_tokens: node.max_tokens(), reasoning_effort: node.reasoning_effort().parse().ok(), speed: node.speed().map(String::from), tool_hooks, mcp_servers, - ..SessionConfig::default() + ..SessionOptions::default() }; let manager = Arc::new(TokioMutex::new(SubAgentManager::new( @@ -222,7 +222,7 @@ impl AgentApiBackend { factory_client.clone(), child_profile, Arc::clone(&factory_env), - SessionConfig::default(), + SessionOptions::default(), None, ); if !factory_tool_env.is_empty() { diff --git a/lib/crates/fabro-workflow/src/handler/parallel.rs b/lib/crates/fabro-workflow/src/handler/parallel.rs index 176df2bc8..8ddc1b9e6 100644 --- a/lib/crates/fabro-workflow/src/handler/parallel.rs +++ b/lib/crates/fabro-workflow/src/handler/parallel.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::Instant; use async_trait::async_trait; -use fabro_agent::{Sandbox, WorktreeConfig, WorktreeSandbox}; +use fabro_agent::{Sandbox, WorktreeOptions, WorktreeSandbox}; use fabro_store::NodeVisitRef; use fabro_types::RunId; use tokio::sync::Semaphore; @@ -231,7 +231,7 @@ impl Handler for ParallelHandler { tracing::debug!(branch = %branch_name, path = %wt_path_str, "Creating worktree for parallel branch"); // Set up worktree via WorktreeSandbox - let wt_config = WorktreeConfig { + let wt_config = WorktreeOptions { branch_name: branch_name.clone(), base_sha: bsha.clone(), worktree_path: wt_path_str.clone(), diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index 1d4d75b17..3582de8fe 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -46,7 +46,7 @@ struct RunSession { interviewer: Arc, on_node: crate::OnNodeCallback, lifecycle: LifecycleOptions, - hooks: fabro_hooks::HookConfig, + hooks: fabro_hooks::HookSettings, sandbox_env: SandboxEnvSpec, devcontainer: Option, seed_context: Option, @@ -293,7 +293,7 @@ impl RunSession { working_directory: working_directory.clone(), }, SandboxProvider::Docker => SandboxSpec::Docker { - config: fabro_agent::DockerSandboxConfig { + config: fabro_agent::DockerSandboxOptions { host_working_directory: working_directory.to_string_lossy().to_string(), ..Default::default() }, @@ -349,7 +349,7 @@ impl RunSession { setup_command_timeout_ms: settings.setup_timeout_ms().unwrap_or(300_000), devcontainer_phases: Vec::new(), }, - hooks: fabro_hooks::HookConfig { + hooks: fabro_hooks::HookSettings { hooks: settings.hooks.clone(), }, sandbox_env, diff --git a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs index f1d88b02d..3317ac213 100644 --- a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs +++ b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs @@ -11,7 +11,7 @@ use chrono::Utc; use fabro_agent::Sandbox; use fabro_config::FabroSettings; use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; -use fabro_hooks::HookConfig; +use fabro_hooks::HookSettings; use fabro_interview::AutoApproveInterviewer; use fabro_sandbox::SandboxSpec; use fabro_store::InMemoryStore; @@ -200,7 +200,7 @@ async fn execute_test_run_with_options( devcontainer_phases: vec![], }, run_options, - hooks: HookConfig { hooks: vec![] }, + hooks: HookSettings { hooks: vec![] }, sandbox_env: SandboxEnvSpec { devcontainer_env: HashMap::new(), toml_env: HashMap::new(), @@ -251,7 +251,7 @@ async fn execute_runs_start_to_exit_and_returns_final_context() { devcontainer_phases: vec![], }, run_options: test_run_options(&run_dir, "run-test"), - hooks: HookConfig { hooks: vec![] }, + hooks: HookSettings { hooks: vec![] }, sandbox_env: SandboxEnvSpec { devcontainer_env: HashMap::new(), toml_env: HashMap::new(), @@ -314,7 +314,7 @@ async fn run_with_lifecycle( interviewer: Arc::new(AutoApproveInterviewer), lifecycle, run_options, - hooks: HookConfig { hooks: vec![] }, + hooks: HookSettings { hooks: vec![] }, sandbox_env: SandboxEnvSpec { devcontainer_env: HashMap::new(), toml_env: HashMap::new(), diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs index 909bca736..f6a5f7b7a 100644 --- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs @@ -8,7 +8,7 @@ use fabro_graphviz::graph; use fabro_hooks::{HookContext, HookDecision, HookEvent, HookRunner}; use fabro_llm::client::Client; use fabro_sandbox::{ - ReadBeforeWriteSandbox, SandboxEventCallback, SandboxSpec, WorkdirStrategy, WorktreeConfig, + ReadBeforeWriteSandbox, SandboxEventCallback, SandboxSpec, WorkdirStrategy, WorktreeOptions, WorktreeSandbox, }; use shlex::try_quote; @@ -441,7 +441,7 @@ pub async fn initialize( .map_err(|e| FabroError::engine(e.to_string()))?; let mut worktree = WorktreeSandbox::new( inner, - WorktreeConfig { + WorktreeOptions { branch_name: plan.branch_name.clone(), base_sha: plan.base_sha.clone(), worktree_path: plan.worktree_path.to_string_lossy().into_owned(), @@ -792,7 +792,7 @@ mod tests { devcontainer_phases: vec![], }, run_options: test_settings(&run_dir), - hooks: fabro_hooks::HookConfig { hooks: vec![] }, + hooks: fabro_hooks::HookSettings { hooks: vec![] }, sandbox_env: SandboxEnvSpec { devcontainer_env: HashMap::new(), toml_env: HashMap::from([("TEST_KEY".to_string(), "value".to_string())]), @@ -867,7 +867,7 @@ mod tests { devcontainer_phases: vec![], }, run_options: test_settings(&run_dir), - hooks: fabro_hooks::HookConfig { hooks: vec![] }, + hooks: fabro_hooks::HookSettings { hooks: vec![] }, sandbox_env: SandboxEnvSpec { devcontainer_env: HashMap::new(), toml_env: HashMap::new(), diff --git a/lib/crates/fabro-workflow/src/pipeline/types.rs b/lib/crates/fabro-workflow/src/pipeline/types.rs index 5bd31ab5c..567ccc0c4 100644 --- a/lib/crates/fabro-workflow/src/pipeline/types.rs +++ b/lib/crates/fabro-workflow/src/pipeline/types.rs @@ -8,7 +8,7 @@ use fabro_graphviz::graph::Graph; use fabro_hooks::HookRunner; use fabro_interview::Interviewer; use fabro_llm::Provider; -use fabro_mcp::config::McpServerConfig; +use fabro_mcp::config::McpServerSettings; use fabro_model::FallbackTarget; use fabro_sandbox::SandboxSpec; use fabro_store::RunStore; @@ -207,7 +207,7 @@ pub struct LlmSpec { pub model: String, pub provider: Provider, pub fallback_chain: Vec, - pub mcp_servers: Vec, + pub mcp_servers: Vec, pub dry_run: bool, } @@ -235,7 +235,7 @@ pub struct InitOptions { pub interviewer: Arc, pub lifecycle: LifecycleOptions, pub run_options: RunOptions, - pub hooks: fabro_hooks::HookConfig, + pub hooks: fabro_hooks::HookSettings, pub sandbox_env: SandboxEnvSpec, pub devcontainer: Option, pub git: Option, diff --git a/lib/crates/fabro-workflow/src/transforms/import.rs b/lib/crates/fabro-workflow/src/transforms/import.rs index 0fd9fe2d8..c1144003b 100644 --- a/lib/crates/fabro-workflow/src/transforms/import.rs +++ b/lib/crates/fabro-workflow/src/transforms/import.rs @@ -11,7 +11,7 @@ pub struct ImportTransform { fallback_dir: Option, } -struct PlaceholderConfig { +struct PlaceholderOptions { default_attrs: HashMap, class_names: Vec, normalized_class: String, @@ -194,7 +194,7 @@ impl ImportTransform { graph: &mut Graph, placeholder_id: &str, resolved_path: &Path, - placeholder: &PlaceholderConfig, + placeholder: &PlaceholderOptions, prepared: PreparedImport, ) -> Result<(), String> { if graph @@ -341,7 +341,7 @@ impl ImportTransform { fn placeholder_config( graph: &Graph, placeholder_id: &str, - ) -> Result { + ) -> Result { let node = graph .nodes .get(placeholder_id) @@ -378,7 +378,7 @@ impl ImportTransform { )); } - Ok(PlaceholderConfig { + Ok(PlaceholderOptions { default_attrs, class_names, normalized_class: Self::normalize_class_name(placeholder_id), diff --git a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs index 8f63f2e71..a42f796d0 100644 --- a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs +++ b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs @@ -2010,7 +2010,7 @@ async fn daytona_playwright_mcp_sandbox_transport() { // 2. Start the Playwright MCP server via the sandbox transport resolution path let mcp_port = 3100u16; - let mcp_config = fabro_mcp::config::McpServerConfig { + let mcp_config = fabro_mcp::config::McpServerSettings { name: "playwright".into(), transport: fabro_mcp::config::McpTransport::Sandbox { command: vec![ @@ -2077,7 +2077,7 @@ async fn daytona_playwright_mcp_sandbox_transport() { }; eprintln!("Preview URL: {url}"); - fabro_mcp::config::McpServerConfig { + fabro_mcp::config::McpServerSettings { name: mcp_config.name.clone(), transport: fabro_mcp::config::McpTransport::Http { url, headers }, startup_timeout_secs: mcp_config.startup_timeout_secs, diff --git a/lib/crates/fabro-workflow/tests/it/integration.rs b/lib/crates/fabro-workflow/tests/it/integration.rs index 80159b514..ce21c5ccc 100644 --- a/lib/crates/fabro-workflow/tests/it/integration.rs +++ b/lib/crates/fabro-workflow/tests/it/integration.rs @@ -7157,7 +7157,7 @@ fn subgraph_without_label_no_class_derived() { // --------------------------------------------------------------------------- fn hook_runner_from_defs(hooks: Vec) -> Arc { - Arc::new(fabro_hooks::HookRunner::new(fabro_hooks::HookConfig { + Arc::new(fabro_hooks::HookRunner::new(fabro_hooks::HookSettings { hooks, })) } @@ -7798,9 +7798,9 @@ async fn hook_stage_start_exit_2_blocks() { #[tokio::test] async fn hook_config_merge_concatenates() { - use fabro_hooks::{HookConfig, HookDefinition, HookEvent}; + use fabro_hooks::{HookDefinition, HookEvent, HookSettings}; - let server_hooks = HookConfig { + let server_hooks = HookSettings { hooks: vec![HookDefinition { name: Some("server-hook".into()), event: HookEvent::RunStart, @@ -7812,7 +7812,7 @@ async fn hook_config_merge_concatenates() { sandbox: Some(false), }], }; - let run_hooks = HookConfig { + let run_hooks = HookSettings { hooks: vec![HookDefinition { name: Some("run-hook".into()), event: HookEvent::StageComplete, @@ -7833,9 +7833,9 @@ async fn hook_config_merge_concatenates() { #[tokio::test] async fn hook_config_merge_run_overrides_by_name() { - use fabro_hooks::{HookConfig, HookDefinition, HookEvent}; + use fabro_hooks::{HookDefinition, HookEvent, HookSettings}; - let server_hooks = HookConfig { + let server_hooks = HookSettings { hooks: vec![HookDefinition { name: Some("shared".into()), event: HookEvent::RunStart, @@ -7847,7 +7847,7 @@ async fn hook_config_merge_run_overrides_by_name() { sandbox: Some(false), }], }; - let run_hooks = HookConfig { + let run_hooks = HookSettings { hooks: vec![HookDefinition { name: Some("shared".into()), event: HookEvent::RunStart, @@ -12574,7 +12574,7 @@ async fn asset_collection_docker_sandbox() { let host_dir = tempfile::tempdir().unwrap(); let run_dir = tempfile::tempdir().unwrap(); - let config = fabro_agent::DockerSandboxConfig { + let config = fabro_agent::DockerSandboxOptions { host_working_directory: host_dir.path().to_str().unwrap().to_string(), auto_pull: false, ..Default::default() diff --git a/test/twin/github/src/fixtures.rs b/test/twin/github/src/fixtures.rs index c86753e98..312bc376c 100644 --- a/test/twin/github/src/fixtures.rs +++ b/test/twin/github/src/fixtures.rs @@ -6,8 +6,9 @@ use std::path::Path; use serde::Deserialize; use crate::state::{ - AppConfig, AppState, Comment, Installation, IssueContent, ManifestConversion, OwnerType, - Project, ProjectItem, PullRequest, Release, Repository, StatusOption, TokenInfo, WebhookConfig, + AppOptions, AppState, Comment, Installation, IssueContent, ManifestConversion, OwnerType, + Project, ProjectItem, PullRequest, Release, Repository, StatusOption, TokenInfo, + WebhookOptions, }; #[derive(Debug, Clone, Deserialize, Default)] @@ -31,7 +32,7 @@ pub struct FixtureState { #[serde(default)] pub comments: Vec, #[serde(default)] - pub webhook_config: FixtureWebhookConfig, + pub webhook_config: FixtureWebhookOptions, pub next_installation_id: Option, pub next_pr_number: Option, pub viewer_id: Option, @@ -161,7 +162,7 @@ pub struct FixtureComment { } #[derive(Debug, Clone, Deserialize, Default)] -pub struct FixtureWebhookConfig { +pub struct FixtureWebhookOptions { pub url: Option, pub content_type: Option, } @@ -180,7 +181,7 @@ impl FixtureState { for app in self.apps { let app_id = app.app_id.clone(); - let config = AppConfig { + let config = AppOptions { app_id: app.app_id, slug: app.slug, owner_login: app.owner_login, @@ -328,7 +329,7 @@ impl FixtureState { }) .collect(); - state.webhook_config = WebhookConfig { + state.webhook_config = WebhookOptions { url: self.webhook_config.url, content_type: self.webhook_config.content_type, }; diff --git a/test/twin/github/src/handlers/app.rs b/test/twin/github/src/handlers/app.rs index ce165b03b..95b65a170 100644 --- a/test/twin/github/src/handlers/app.rs +++ b/test/twin/github/src/handlers/app.rs @@ -112,7 +112,7 @@ pub async fn patch_webhook_config( #[cfg(test)] mod tests { use crate::server::TestServer; - use crate::state::{AppConfig, AppState}; + use crate::state::{AppOptions, AppState}; fn test_rsa_key() -> String { use std::process::Command; @@ -155,7 +155,7 @@ mod tests { async fn get_app_returns_app_info() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), @@ -187,7 +187,7 @@ mod tests { async fn get_app_rejects_invalid_jwt() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), @@ -213,7 +213,7 @@ mod tests { async fn get_apps_slug_public() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), @@ -240,7 +240,7 @@ mod tests { async fn get_apps_slug_private_returns_404() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "12345".to_string(), slug: "private-app".to_string(), owner_login: "my-org".to_string(), diff --git a/test/twin/github/src/handlers/branches.rs b/test/twin/github/src/handlers/branches.rs index 9cc7f3e26..d4beadab9 100644 --- a/test/twin/github/src/handlers/branches.rs +++ b/test/twin/github/src/handlers/branches.rs @@ -92,7 +92,7 @@ pub async fn get_branch( #[cfg(test)] mod tests { use crate::server::TestServer; - use crate::state::{AppConfig, AppState}; + use crate::state::{AppOptions, AppState}; fn test_rsa_key() -> String { use std::process::Command; @@ -169,7 +169,7 @@ mod tests { async fn branch_exists_returns_200() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), @@ -212,7 +212,7 @@ mod tests { async fn branch_not_found_returns_404() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), diff --git a/test/twin/github/src/handlers/graphql.rs b/test/twin/github/src/handlers/graphql.rs index 1ae8d4a08..f165ec05d 100644 --- a/test/twin/github/src/handlers/graphql.rs +++ b/test/twin/github/src/handlers/graphql.rs @@ -613,7 +613,7 @@ fn extract_unquoted_value(s: &str, key: &str) -> Option { #[cfg(test)] mod tests { use crate::server::TestServer; - use crate::state::{AppConfig, AppState, PullRequest}; + use crate::state::{AppOptions, AppState, PullRequest}; fn test_rsa_key() -> String { use std::process::Command; @@ -697,7 +697,7 @@ mod tests { state: &mut AppState, pem: &str, ) -> (TestServer, reqwest::Client, String) { - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), diff --git a/test/twin/github/src/handlers/installations.rs b/test/twin/github/src/handlers/installations.rs index 5d75fe981..5e3f88d2f 100644 --- a/test/twin/github/src/handlers/installations.rs +++ b/test/twin/github/src/handlers/installations.rs @@ -177,7 +177,7 @@ pub async fn create_access_token( #[cfg(test)] mod tests { use crate::server::TestServer; - use crate::state::{AppConfig, AppState}; + use crate::state::{AppOptions, AppState}; fn test_rsa_key() -> String { use std::process::Command; @@ -220,7 +220,7 @@ mod tests { async fn get_installation_returns_id() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), @@ -252,7 +252,7 @@ mod tests { async fn get_installation_returns_404_when_not_installed() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), @@ -280,7 +280,7 @@ mod tests { async fn get_installation_returns_403_when_suspended() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), @@ -308,7 +308,7 @@ mod tests { async fn create_access_token_returns_201() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), @@ -346,7 +346,7 @@ mod tests { async fn create_access_token_returns_422_for_unauthorized_repo() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), diff --git a/test/twin/github/src/handlers/pulls.rs b/test/twin/github/src/handlers/pulls.rs index eb3aac4ef..76f50563d 100644 --- a/test/twin/github/src/handlers/pulls.rs +++ b/test/twin/github/src/handlers/pulls.rs @@ -302,7 +302,7 @@ fn pr_to_json(pr: &PullRequest) -> serde_json::Value { #[cfg(test)] mod tests { use crate::server::TestServer; - use crate::state::{AppConfig, AppState}; + use crate::state::{AppOptions, AppState}; fn test_rsa_key() -> String { use std::process::Command; @@ -379,7 +379,7 @@ mod tests { state: &mut AppState, pem: &str, ) -> (TestServer, reqwest::Client, String) { - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), diff --git a/test/twin/github/src/state.rs b/test/twin/github/src/state.rs index 60b148236..307716701 100644 --- a/test/twin/github/src/state.rs +++ b/test/twin/github/src/state.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; /// Configuration for a registered GitHub App (user-facing input). #[derive(Debug, Clone)] -pub struct AppConfig { +pub struct AppOptions { pub app_id: String, pub slug: String, pub owner_login: String, @@ -15,7 +15,7 @@ pub struct AppConfig { /// Internal enriched app config with derived public key. #[derive(Debug, Clone)] pub struct RegisteredApp { - pub config: AppConfig, + pub config: AppOptions, /// Derived from `private_key_pem` during `register_app`. Used for JWT verification. pub public_key_pem: String, } @@ -140,7 +140,7 @@ pub struct Comment { /// Stores webhook configuration. #[derive(Debug, Clone, Default)] -pub struct WebhookConfig { +pub struct WebhookOptions { pub url: Option, pub content_type: Option, } @@ -234,7 +234,7 @@ pub struct AppState { pub releases: HashMap<(String, String), Release>, pub manifest_conversions: HashMap, pub comments: Vec, - pub webhook_config: WebhookConfig, + pub webhook_config: WebhookOptions, pub next_installation_id: u64, pub next_pr_number: u64, pub viewer_id: String, @@ -279,14 +279,14 @@ impl AppState { releases: HashMap::new(), manifest_conversions: HashMap::new(), comments: Vec::new(), - webhook_config: WebhookConfig::default(), + webhook_config: WebhookOptions::default(), next_installation_id: 1, next_pr_number: 1, viewer_id: "U_fakeviewer".to_string(), } } - pub fn register_app(&mut self, config: AppConfig) { + pub fn register_app(&mut self, config: AppOptions) { let public_key_pem = derive_public_key_pem(&config.private_key_pem); let app_id = config.app_id.clone(); self.apps.insert( @@ -477,7 +477,7 @@ mod tests { fn can_register_app() { let pem = test_rsa_key(); let mut state = AppState::new(); - state.register_app(AppConfig { + state.register_app(AppOptions { app_id: "12345".to_string(), slug: "test-app".to_string(), owner_login: "test-owner".to_string(), diff --git a/test/twin/openai/src/openai/models.rs b/test/twin/openai/src/openai/models.rs index 349d07362..391e01684 100644 --- a/test/twin/openai/src/openai/models.rs +++ b/test/twin/openai/src/openai/models.rs @@ -16,7 +16,7 @@ pub struct ResponsesRequest { pub stop: Option, pub previous_response_id: Option, pub reasoning: Option, - pub text: Option, + pub text: Option, pub tools: Option>, pub tool_choice: Option, /// Catch-all for fields the twin doesn't use (temperature, top_p, etc.) @@ -319,7 +319,7 @@ fn validate_input_content_part(part: &ContentPart) -> Result<(), OpenAiError> { #[derive(Clone, Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct TextConfig { +pub struct TextOptions { pub format: Option, } diff --git a/test/twin/openai/tests/live_openai_contract.rs b/test/twin/openai/tests/live_openai_contract.rs index 327993254..a2daf1124 100644 --- a/test/twin/openai/tests/live_openai_contract.rs +++ b/test/twin/openai/tests/live_openai_contract.rs @@ -21,7 +21,7 @@ const RESPONSES_STREAM_MILESTONES: &[&str] = &[ ]; #[derive(Clone)] -struct LiveConfig { +struct LiveOptions { api: common::ApiClient, model: String, } @@ -51,7 +51,7 @@ struct ToolCallObservation { #[tokio::test] #[ignore = "requires OPENAI_API_KEY and outbound network"] async fn live_openai_contract_smoke_suite() { - let Some(config) = LiveConfig::from_env().expect("live config should load") else { + let Some(config) = LiveOptions::from_env().expect("live config should load") else { eprintln!("skipping live OpenAI smoke suite because OPENAI_API_KEY is not set"); return; }; @@ -203,7 +203,7 @@ async fn live_openai_contract_smoke_suite() { ); } -impl LiveConfig { +impl LiveOptions { fn from_env() -> Result> { let Some(api_key) = non_empty_env("OPENAI_API_KEY") else { return Ok(None); @@ -232,7 +232,7 @@ fn collect_failure(failures: &mut Vec, name: &str, result: Result<()>) { } } -async fn probe_responses_availability(config: &LiveConfig) -> Result { +async fn probe_responses_availability(config: &LiveOptions) -> Result { probe_surface_availability( &config.api, "/v1/responses", @@ -246,7 +246,7 @@ async fn probe_responses_availability(config: &LiveConfig) -> Result Result { +async fn probe_chat_availability(config: &LiveOptions) -> Result { probe_surface_availability( &config.api, "/v1/chat/completions", @@ -284,7 +284,7 @@ async fn probe_surface_availability( ); } -async fn compare_responses_non_stream_text(config: &LiveConfig) -> Result<()> { +async fn compare_responses_non_stream_text(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -319,7 +319,7 @@ async fn compare_responses_non_stream_text(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_stream_text(config: &LiveConfig) -> Result<()> { +async fn compare_responses_stream_text(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -347,7 +347,7 @@ async fn compare_responses_stream_text(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_structured_output(config: &LiveConfig) -> Result<()> { +async fn compare_responses_structured_output(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let schema = smoke_schema(); let request = json!({ @@ -386,7 +386,7 @@ async fn compare_responses_structured_output(config: &LiveConfig) -> Result<()> Ok(()) } -async fn compare_responses_stream_structured_output(config: &LiveConfig) -> Result<()> { +async fn compare_responses_stream_structured_output(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let schema = smoke_schema(); let request = json!({ @@ -436,7 +436,7 @@ async fn compare_responses_stream_structured_output(config: &LiveConfig) -> Resu Ok(()) } -async fn compare_responses_tool_call(config: &LiveConfig) -> Result<()> { +async fn compare_responses_tool_call(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_tool_scenario(&server, "responses", &config.model, false).await; @@ -494,7 +494,7 @@ async fn compare_responses_tool_call(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_stream_tool_call(config: &LiveConfig) -> Result<()> { +async fn compare_responses_stream_tool_call(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_tool_scenario(&server, "responses", &config.model, true).await; @@ -553,7 +553,7 @@ async fn compare_responses_stream_tool_call(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_continuation(config: &LiveConfig) -> Result<()> { +async fn compare_responses_continuation(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_responses_continuation_scenarios(&server, &config.model).await; @@ -656,7 +656,7 @@ async fn compare_responses_continuation(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_stream_continuation(config: &LiveConfig) -> Result<()> { +async fn compare_responses_stream_continuation(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_responses_continuation_scenarios(&server, &config.model).await; @@ -746,7 +746,7 @@ async fn compare_responses_stream_continuation(config: &LiveConfig) -> Result<() Ok(()) } -async fn compare_responses_image_input(config: &LiveConfig) -> Result<()> { +async fn compare_responses_image_input(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -782,7 +782,7 @@ async fn compare_responses_image_input(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_responses_stream_image_input(config: &LiveConfig) -> Result<()> { +async fn compare_responses_stream_image_input(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -821,7 +821,7 @@ async fn compare_responses_stream_image_input(config: &LiveConfig) -> Result<()> Ok(()) } -async fn compare_responses_tool_choice_none(config: &LiveConfig) -> Result<()> { +async fn compare_responses_tool_choice_none(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -867,7 +867,7 @@ async fn compare_responses_tool_choice_none(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_non_stream_text(config: &LiveConfig) -> Result<()> { +async fn compare_chat_non_stream_text(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -902,7 +902,7 @@ async fn compare_chat_non_stream_text(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_stream_text(config: &LiveConfig) -> Result<()> { +async fn compare_chat_stream_text(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -925,7 +925,7 @@ async fn compare_chat_stream_text(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_structured_output(config: &LiveConfig) -> Result<()> { +async fn compare_chat_structured_output(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let schema = smoke_schema(); let request = json!({ @@ -964,7 +964,7 @@ async fn compare_chat_structured_output(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_stream_structured_output(config: &LiveConfig) -> Result<()> { +async fn compare_chat_stream_structured_output(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let schema = smoke_schema(); let request = json!({ @@ -1009,7 +1009,7 @@ async fn compare_chat_stream_structured_output(config: &LiveConfig) -> Result<() Ok(()) } -async fn compare_chat_tool_call(config: &LiveConfig) -> Result<()> { +async fn compare_chat_tool_call(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_tool_scenario(&server, "chat.completions", &config.model, false).await; @@ -1065,7 +1065,7 @@ async fn compare_chat_tool_call(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_stream_tool_call(config: &LiveConfig) -> Result<()> { +async fn compare_chat_stream_tool_call(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; enqueue_tool_scenario(&server, "chat.completions", &config.model, true).await; @@ -1109,7 +1109,7 @@ async fn compare_chat_stream_tool_call(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_image_input(config: &LiveConfig) -> Result<()> { +async fn compare_chat_image_input(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -1145,7 +1145,7 @@ async fn compare_chat_image_input(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_stream_image_input(config: &LiveConfig) -> Result<()> { +async fn compare_chat_stream_image_input(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model, @@ -1179,7 +1179,7 @@ async fn compare_chat_stream_image_input(config: &LiveConfig) -> Result<()> { Ok(()) } -async fn compare_chat_tool_choice_none(config: &LiveConfig) -> Result<()> { +async fn compare_chat_tool_choice_none(config: &LiveOptions) -> Result<()> { let server = common::spawn_server().await?; let request = json!({ "model": config.model,