From 2cd5c601e57ddccf284e3038a586f0f82e2e9377 Mon Sep 17 00:00:00 2001 From: The-Best-Codes Date: Sun, 18 Jan 2026 17:42:12 -0600 Subject: [PATCH 1/3] fix: remove empty case for platform extensions so they are always enabled on fresh install Signed-off-by: The-Best-Codes --- crates/goose/src/config/extensions.rs | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index 10e9d5dc67bc..4537abbfdf2e 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -54,22 +54,20 @@ fn get_extensions_map() -> IndexMap { } } - if !extensions_map.is_empty() { - for (name, def) in PLATFORM_EXTENSIONS.iter() { - if !extensions_map.contains_key(*name) { - extensions_map.insert( - name.to_string(), - ExtensionEntry { - config: ExtensionConfig::Platform { - name: def.name.to_string(), - description: def.description.to_string(), - bundled: Some(true), - available_tools: Vec::new(), - }, - enabled: def.default_enabled, + for (name, def) in PLATFORM_EXTENSIONS.iter() { + if !extensions_map.contains_key(*name) { + extensions_map.insert( + name.to_string(), + ExtensionEntry { + config: ExtensionConfig::Platform { + name: def.name.to_string(), + description: def.description.to_string(), + bundled: Some(true), + available_tools: Vec::new(), }, - ); - } + enabled: def.default_enabled, + }, + ); } } extensions_map From 1136b5b6aa003dd3b3154beeb21dc2d4efe211b7 Mon Sep 17 00:00:00 2001 From: The-Best-Codes Date: Sun, 18 Jan 2026 17:42:47 -0600 Subject: [PATCH 2/3] fix: sync bundled extensions in desktop gui Signed-off-by: The-Best-Codes --- ui/desktop/src/components/ConfigContext.tsx | 24 ++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index cee6a79b7c13..fee1d45267fc 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -19,6 +19,7 @@ import type { ExtensionQuery, ExtensionConfig, } from '../api'; +import { syncBundledExtensions } from './settings/extensions'; export type { ExtensionConfig } from '../api/types.gen'; @@ -215,11 +216,28 @@ export const ConfigProvider: React.FC = ({ children }) => { setProvidersList([]); } - // Load extensions + // Load extensions and sync bundled extensions try { const extensionsResponse = await apiGetExtensions(); - setExtensionsList(extensionsResponse.data?.extensions || []); - setExtensionWarnings(extensionsResponse.data?.warnings || []); + let extensions = extensionsResponse.data?.extensions || []; + + // Sync bundled extensions (adds any missing bundled extensions to config) + // Use a simple addExtension function that doesn't refresh after each call + const addExtensionForSync = async ( + name: string, + config: ExtensionConfig, + enabled: boolean + ) => { + const query: ExtensionQuery = { name, config, enabled }; + await apiAddExtension({ body: query }); + }; + + await syncBundledExtensions(extensions, addExtensionForSync); + + // Refresh extensions after sync to get the updated list + const updatedExtensionsResponse = await apiGetExtensions(); + setExtensionsList(updatedExtensionsResponse.data?.extensions || []); + setExtensionWarnings(updatedExtensionsResponse.data?.warnings || []); } catch (error) { console.error('Failed to load extensions:', error); } From 91da4738b2c9b850ba7eadc0fc7bfeb48f8e5c69 Mon Sep 17 00:00:00 2001 From: The-Best-Codes Date: Sun, 18 Jan 2026 18:38:24 -0600 Subject: [PATCH 3/3] move bundled extensions to backend Signed-off-by: The-Best-Codes --- crates/goose/src/config/extensions.rs | 76 +++++++++++++++++++++ ui/desktop/src/components/ConfigContext.tsx | 24 +------ 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index 4537abbfdf2e..06922a5c9699 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -2,6 +2,7 @@ use super::base::Config; use crate::agents::extension::PLATFORM_EXTENSIONS; use crate::agents::ExtensionConfig; use indexmap::IndexMap; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; use tracing::warn; @@ -20,6 +21,71 @@ pub struct ExtensionEntry { pub config: ExtensionConfig, } +/// Default bundled extensions (builtin MCP servers) that should be available on fresh installs. +/// These are distinct from PLATFORM_EXTENSIONS which run in-process. +pub static BUNDLED_EXTENSIONS: Lazy> = Lazy::new(|| { + vec![ + ExtensionEntry { + config: ExtensionConfig::Builtin { + name: "developer".to_string(), + display_name: Some("Developer".to_string()), + description: "General development tools useful for software engineering." + .to_string(), + timeout: Some(DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + available_tools: Vec::new(), + }, + enabled: true, + }, + ExtensionEntry { + config: ExtensionConfig::Builtin { + name: "computercontroller".to_string(), + display_name: Some("Computer Controller".to_string()), + description: + "General computer control tools that don't require you to be a developer or engineer." + .to_string(), + timeout: Some(DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + available_tools: Vec::new(), + }, + enabled: false, + }, + ExtensionEntry { + config: ExtensionConfig::Builtin { + name: "autovisualiser".to_string(), + display_name: Some("Auto Visualiser".to_string()), + description: "Data visualization and UI generation tools".to_string(), + timeout: Some(DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + available_tools: Vec::new(), + }, + enabled: false, + }, + ExtensionEntry { + config: ExtensionConfig::Builtin { + name: "memory".to_string(), + display_name: Some("Memory".to_string()), + description: "Teach goose your preferences as you go.".to_string(), + timeout: Some(DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + available_tools: Vec::new(), + }, + enabled: false, + }, + ExtensionEntry { + config: ExtensionConfig::Builtin { + name: "tutorial".to_string(), + display_name: Some("Tutorial".to_string()), + description: "Access interactive tutorials and guides".to_string(), + timeout: Some(DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + available_tools: Vec::new(), + }, + enabled: false, + }, + ] +}); + pub fn name_to_key(name: &str) -> String { name.chars() .filter(|c| !c.is_whitespace()) @@ -54,6 +120,7 @@ fn get_extensions_map() -> IndexMap { } } + // Always add platform extensions if they aren't present (including fresh installs) for (name, def) in PLATFORM_EXTENSIONS.iter() { if !extensions_map.contains_key(*name) { extensions_map.insert( @@ -70,6 +137,15 @@ fn get_extensions_map() -> IndexMap { ); } } + + // Always add bundled extensions if they aren't present (including fresh installs) + for entry in BUNDLED_EXTENSIONS.iter() { + let key = entry.config.key(); + if !extensions_map.contains_key(&key) { + extensions_map.insert(key, entry.clone()); + } + } + extensions_map } diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index fee1d45267fc..cee6a79b7c13 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -19,7 +19,6 @@ import type { ExtensionQuery, ExtensionConfig, } from '../api'; -import { syncBundledExtensions } from './settings/extensions'; export type { ExtensionConfig } from '../api/types.gen'; @@ -216,28 +215,11 @@ export const ConfigProvider: React.FC = ({ children }) => { setProvidersList([]); } - // Load extensions and sync bundled extensions + // Load extensions try { const extensionsResponse = await apiGetExtensions(); - let extensions = extensionsResponse.data?.extensions || []; - - // Sync bundled extensions (adds any missing bundled extensions to config) - // Use a simple addExtension function that doesn't refresh after each call - const addExtensionForSync = async ( - name: string, - config: ExtensionConfig, - enabled: boolean - ) => { - const query: ExtensionQuery = { name, config, enabled }; - await apiAddExtension({ body: query }); - }; - - await syncBundledExtensions(extensions, addExtensionForSync); - - // Refresh extensions after sync to get the updated list - const updatedExtensionsResponse = await apiGetExtensions(); - setExtensionsList(updatedExtensionsResponse.data?.extensions || []); - setExtensionWarnings(updatedExtensionsResponse.data?.warnings || []); + setExtensionsList(extensionsResponse.data?.extensions || []); + setExtensionWarnings(extensionsResponse.data?.warnings || []); } catch (error) { console.error('Failed to load extensions:', error); }