diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 42291dfd565a..5f89c9c5d4e0 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -634,6 +634,7 @@ impl Agent { /// Load extensions from session into the agent /// Skips extensions that are already loaded + /// Uses the session's working_dir for extension initialization pub async fn load_extensions_from_session( self: &Arc, session: &Session, @@ -651,11 +652,15 @@ impl Agent { } }; + // Capture the session's working_dir to pass to extensions + let working_dir = session.working_dir.clone(); + let extension_futures = enabled_configs .into_iter() .map(|config| { let config_clone = config.clone(); let agent_ref = self.clone(); + let working_dir_clone = working_dir.clone(); async move { let name = config_clone.name().to_string(); @@ -674,7 +679,10 @@ impl Agent { }; } - match agent_ref.add_extension(config_clone).await { + match agent_ref + .add_extension_with_working_dir(config_clone, Some(working_dir_clone)) + .await + { Ok(_) => ExtensionLoadResult { name, success: true, @@ -698,6 +706,14 @@ impl Agent { } pub async fn add_extension(&self, extension: ExtensionConfig) -> ExtensionResult<()> { + self.add_extension_with_working_dir(extension, None).await + } + + pub async fn add_extension_with_working_dir( + &self, + extension: ExtensionConfig, + working_dir: Option, + ) -> ExtensionResult<()> { match &extension { ExtensionConfig::Frontend { tools, @@ -726,7 +742,7 @@ impl Agent { } _ => { self.extension_manager - .add_extension(extension.clone()) + .add_extension_with_working_dir(extension.clone(), working_dir) .await?; } } diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 27cc65060ec4..15ef6fcd2cbf 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -466,13 +466,6 @@ impl ExtensionManager { &self.context } - /// Resolve the working directory for an extension. - /// Falls back to current_dir when working_dir is not available. - async fn resolve_working_dir(&self) -> PathBuf { - // Fall back to current_dir - working_dir is passed through the call chain from session - std::env::current_dir().unwrap_or_default() - } - pub async fn supports_resources(&self) -> bool { self.extensions .lock() @@ -481,7 +474,14 @@ impl ExtensionManager { .any(|ext| ext.supports_resources()) } - pub async fn add_extension(self: &Arc, config: ExtensionConfig) -> ExtensionResult<()> { + /// Add an extension with an optional working directory. + /// If working_dir is None, falls back to current_dir. + #[allow(clippy::too_many_lines)] + pub async fn add_extension_with_working_dir( + self: &Arc, + config: ExtensionConfig, + working_dir: Option, + ) -> ExtensionResult<()> { let config_name = config.key().to_string(); let sanitized_name = normalize(&config_name); @@ -489,8 +489,9 @@ impl ExtensionManager { return Ok(()); } - // Resolve working_dir: session > current_dir - let effective_working_dir = self.resolve_working_dir().await; + // Resolve working_dir: explicit > current_dir + let effective_working_dir = + working_dir.unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); let mut temp_dir = None; @@ -555,6 +556,17 @@ impl ExtensionManager { .ok_or_else(|| { ExtensionError::ConfigError(format!("Unknown builtin extension: {}", name)) })?; + + // Set GOOSE_WORKING_DIR in the current process for builtin extensions + // since they run in-process and read from std::env::var + if effective_working_dir.exists() && effective_working_dir.is_dir() { + std::env::set_var("GOOSE_WORKING_DIR", &effective_working_dir); + tracing::info!( + "Set GOOSE_WORKING_DIR for builtin extension: {:?}", + effective_working_dir + ); + } + let (server_read, client_write) = tokio::io::duplex(65536); let (client_read, server_write) = tokio::io::duplex(65536); (def.spawn_server)(server_read, server_write); diff --git a/crates/goose/src/agents/extension_manager_extension.rs b/crates/goose/src/agents/extension_manager_extension.rs index 9a54e0d81307..377c592d3559 100644 --- a/crates/goose/src/agents/extension_manager_extension.rs +++ b/crates/goose/src/agents/extension_manager_extension.rs @@ -211,7 +211,7 @@ impl ExtensionManagerClient { }; extension_manager - .add_extension(config) + .add_extension_with_working_dir(config, None) .await .map(|_| { vec![Content::text(format!( diff --git a/crates/goose/tests/mcp_integration_test.rs b/crates/goose/tests/mcp_integration_test.rs index 88bb3d323e19..71fae27d8e3f 100644 --- a/crates/goose/tests/mcp_integration_test.rs +++ b/crates/goose/tests/mcp_integration_test.rs @@ -264,7 +264,9 @@ async fn test_replayed_session( #[allow(clippy::redundant_closure_call)] let result = (async || -> Result<(), Box> { - extension_manager.add_extension(extension_config).await?; + extension_manager + .add_extension_with_working_dir(extension_config, None) + .await?; let mut results = Vec::new(); for tool_call in tool_calls { let tool_call = CallToolRequestParam {