diff --git a/Cargo.lock b/Cargo.lock index 18578f0b1640..ee07bf9bb281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2648,6 +2648,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", + "paste", "rand 0.8.5", "regex", "reqwest 0.12.12", @@ -2659,6 +2660,7 @@ dependencies = [ "serde_yaml", "serial_test", "sha2", + "shellexpand", "sqlx", "sys-info", "temp-env", @@ -6129,9 +6131,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shellexpand" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" dependencies = [ "dirs", ] diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index e70471c8909f..f1fad2320ac8 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -105,6 +105,8 @@ sys-info = "0.9" oauth2 = "5.0.0" schemars = { version = "1.0.4", default-features = false, features = ["derive"] } insta = "1.43.2" +paste = "1.0.0" +shellexpand = "3.1.1" [target.'cfg(target_os = "windows")'.dependencies] diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 6d5bf085bb7f..443673c83cf8 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -32,6 +32,7 @@ use super::tool_execution::ToolCallResult; use crate::agents::extension::{Envs, ProcessExit}; use crate::agents::extension_malware_check; use crate::agents::mcp_client::{McpClient, McpClientTrait}; +use crate::config::search_path::search_path_var; use crate::config::{get_all_extensions, Config}; use crate::oauth::oauth_flow; use crate::prompt_template; @@ -182,6 +183,12 @@ async fn child_process_client( command.process_group(0); #[cfg(windows)] command.creation_flags(CREATE_NO_WINDOW_FLAG); + + command.env( + "PATH", + search_path_var().map_err(|e| ExtensionError::ConfigError(format!("{}", e)))?, + ); + let (transport, mut stderr) = TokioChildProcess::builder(command) .stderr(Stdio::piped()) .spawn()?; diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index 3cde60d3b423..3974a4b9a6cc 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -136,6 +136,16 @@ impl Default for Config { } } +macro_rules! declare_param { + ($param_name:ident, $param_type:ty) => { + paste::paste! { + pub fn [](&self) -> Result<$param_type, ConfigError> { + self.get_param(stringify!($param_name)) + } + } + }; +} + impl Config { /// Get the global configuration instance. /// @@ -730,6 +740,8 @@ impl Config { }; Ok(()) } + + declare_param!(GOOSE_SEARCH_PATHS, Vec); } /// Load init-config.yaml from workspace root if it exists. diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index 3ab6f3497ffa..c37a49e66b89 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -4,6 +4,7 @@ mod experiments; pub mod extensions; pub mod paths; pub mod permission; +pub mod search_path; pub mod signup_openrouter; pub mod signup_tetrate; diff --git a/crates/goose/src/config/search_path.rs b/crates/goose/src/config/search_path.rs new file mode 100644 index 000000000000..d500a4bb5167 --- /dev/null +++ b/crates/goose/src/config/search_path.rs @@ -0,0 +1,25 @@ +use std::{env, ffi::OsString, path::PathBuf}; + +use crate::config::{Config, ConfigError}; + +pub fn search_path_var() -> Result { + let paths = Config::global() + .get_goose_search_paths() + .or_else(|err| match err { + ConfigError::NotFound(_) => Ok(vec![]), + err => Err(err), + })? + .into_iter() + .map(|s| PathBuf::from(shellexpand::tilde(&s).as_ref())); + + env::join_paths( + paths.chain( + env::var_os("PATH") + .as_ref() + .map(env::split_paths) + .into_iter() + .flatten(), + ), + ) + .map_err(|e| ConfigError::DeserializeError(format!("{}", e))) +}