From 9f6eb65ad18be80b1d13e066d5832e8b07ca5dfd Mon Sep 17 00:00:00 2001 From: Darren Xu Date: Wed, 17 Jun 2026 09:10:35 -0700 Subject: [PATCH] fix: resolve bundled extensions from discovery (#9759) Signed-off-by: Douwe M Osinga Co-authored-by: Douwe M Osinga --- crates/goose/src/config/extensions.rs | 69 +++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index f36419972..85a596f87 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -127,11 +127,24 @@ where } pub fn get_extension_by_name(name: &str) -> Option { - let extensions = get_extensions_map(); - extensions + get_extension_by_name_with_config(Config::global(), name) +} + +fn get_extension_by_name_with_config(config: &Config, name: &str) -> Option { + let extensions = get_extensions_map_with_config(config); + let key = name_to_key(name); + + if let Some(entry) = extensions .values() .find(|entry| entry.config.name() == name) - .map(|entry| entry.config.clone()) + .or_else(|| extensions.get(&key)) + { + return Some(entry.config.clone()); + } + + get_available_extensions() + .into_iter() + .find(|config| config.name() == name || config.key() == key) } pub fn set_extension(entry: ExtensionEntry) { @@ -444,6 +457,56 @@ extensions: assert!(extensions.contains_key("newextension")); } + #[test] + fn test_get_extension_by_name_falls_back_to_available_builtin() { + fn spawn_builtin(_: tokio::io::DuplexStream, _: tokio::io::DuplexStream) {} + crate::builtin_extension::register_builtin_extension("memory", spawn_builtin); + + let extension = get_extension_by_name("memory").unwrap(); + + assert!(matches!( + extension, + ExtensionConfig::Builtin { ref name, .. } if name == "memory" + )); + } + + #[test] + fn test_get_extension_by_name_resolves_saved_entry_by_key() { + let saved = ExtensionEntry { + enabled: true, + config: ExtensionConfig::Stdio { + name: "My Tool".to_string(), + description: "saved description".to_string(), + cmd: "my-tool".to_string(), + args: Vec::new(), + envs: Default::default(), + env_keys: Vec::new(), + timeout: Some(120), + bundled: None, + available_tools: vec!["run".to_string()], + }, + }; + let key = saved.config.key(); + assert_ne!(key, saved.config.name()); + + let (config, _config_file, _secrets_file) = test_config(""); + set_extension_with_config(&config, saved); + + let resolved = get_extension_by_name_with_config(&config, &key).unwrap(); + + match resolved { + ExtensionConfig::Stdio { + timeout, + available_tools, + .. + } => { + assert_eq!(timeout, Some(120)); + assert_eq!(available_tools, vec!["run".to_string()]); + } + other => panic!("expected stdio, got {other:?}"), + } + } + #[test] fn test_remove_extension_preserves_unparseable_sibling() { let (config, _config_file, _secrets_file) = test_config(