From acb6eabcfb12b8f79fa2aaccd2cfe85589f9bd98 Mon Sep 17 00:00:00 2001 From: earayu Date: Mon, 25 May 2026 12:49:14 +0800 Subject: [PATCH 1/2] feat(desktop): bundle ApeMind MCP placeholder + seed default recipes on first launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three pieces for the ApeCloud distribution layer: 1. forge.config.ts: add `default-recipes` to extraResource so the directory ships inside the packaged app's Resources path. 2. main.ts seedDefaultRecipes(): on app startup, read recipe YAMLs from the bundled `default-recipes/` directory (packaged: process.resourcesPath; dev: relative to __dirname) and copy them to ~/.config/goose/recipes/. Same-name files are skipped (never overwrite user edits). This intentionally uses goose's existing user-recipe directory so the reader logic in goose-rs and the Desktop UI need no changes — upstream-merge-friendly. 3. bundled-extensions.json: add an `apemind` entry of type streamable_http, enabled=false, uri="" — appears in the extensions list with description prompting the user to configure URL + token before enabling. URL/token intentionally not hardcoded. Recipe YAML content authored separately in #18 (placed at ui/desktop/default-recipes/). 5-cat compliance: category 3 (默认配置 / bundled extension config) + category 5 (打包分发 / forge extraResource + first-copy logic). Zero changes to Rust crates or recipe reader logic. Signed-off-by: earayu --- ui/desktop/forge.config.ts | 2 +- .../extensions/bundled-extensions.json | 11 ++++++++ ui/desktop/src/main.ts | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ui/desktop/forge.config.ts b/ui/desktop/forge.config.ts index 4c3a77401896..b0c2b69c1838 100644 --- a/ui/desktop/forge.config.ts +++ b/ui/desktop/forge.config.ts @@ -7,7 +7,7 @@ const isLinuxVulkanBuild = process.env.GOOSE_DESKTOP_LINUX_VARIANT === 'vulkan'; let cfg = { asar: true, executableName: 'Goose', - extraResource: ['src/bin', 'src/images'], + extraResource: ['src/bin', 'src/images', 'default-recipes'], icon: 'src/images/icon', // Windows specific configuration win32: { diff --git a/ui/desktop/src/components/settings/extensions/bundled-extensions.json b/ui/desktop/src/components/settings/extensions/bundled-extensions.json index c843abb2c6b4..1b7d1b7d3e0b 100644 --- a/ui/desktop/src/components/settings/extensions/bundled-extensions.json +++ b/ui/desktop/src/components/settings/extensions/bundled-extensions.json @@ -52,5 +52,16 @@ "type": "builtin", "env_keys": [], "bundled": true + }, + { + "id": "apemind", + "name": "apemind", + "display_name": "ApeMind", + "description": "ApeMind 知识图谱 / RAG 服务(启用前请在扩展设置里填写 URL 和 token)", + "enabled": false, + "type": "streamable_http", + "uri": "", + "timeout": 300, + "bundled": true } ] diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index c5fd9b0cf002..87c03b37f20d 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -175,6 +175,25 @@ function translateMenuLabels(items: MenuItem[]): void { const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json'); const STARTUP_LOGS_DIR = path.join(app.getPath('userData'), 'logs', 'startup'); +async function seedDefaultRecipes(): Promise { + const sourceRoot = app.isPackaged + ? path.join(process.resourcesPath, 'default-recipes') + : path.join(__dirname, '..', 'default-recipes'); + const destDir = path.join(os.homedir(), '.config', 'goose', 'recipes'); + + if (!fsSync.existsSync(sourceRoot)) return; + + await fs.mkdir(destDir, { recursive: true }); + const entries = await fs.readdir(sourceRoot); + for (const entry of entries) { + if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue; + const destPath = path.join(destDir, entry); + if (fsSync.existsSync(destPath)) continue; + await fs.copyFile(path.join(sourceRoot, entry), destPath); + log.info(`[seedDefaultRecipes] seeded ${entry} → ${destPath}`); + } +} + function getSettings(): Settings { if (fsSync.existsSync(SETTINGS_FILE)) { let stored: Partial; @@ -2079,6 +2098,12 @@ const registerGlobalShortcuts = () => { async function appMain() { await configureProxy(); + try { + await seedDefaultRecipes(); + } catch (err) { + log.warn('[seedDefaultRecipes] failed:', err); + } + // Ensure Windows shims are available before any MCP processes are spawned await ensureWinShims(); From 1287fa8190d47f7a67804b188022ad61bd838d4a Mon Sep 17 00:00:00 2001 From: earayu Date: Mon, 25 May 2026 12:56:01 +0800 Subject: [PATCH 2/2] fix(desktop): wire Authorization header for bundled streamable_http ApeMind MCP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per earayu2 #鹅岛 msg 792dda6a: - ApeMind MCP placeholder URL: `https://your-apemind.example.com/mcp` - Auth header placeholder: `Authorization: Bearer your-api-key-here` Two pieces: 1. `bundled-extensions.json` apemind entry: add `uri` + `headers.Authorization` placeholder values so the user only has to swap `your-api-key-here` (and adjust the URL if needed) when enabling. 2. `bundled-extensions.ts` BundledExtension type + streamable_http loader case: extend to propagate `headers` (and `envs`/`env_keys`) into the actual extension config. Without this, the JSON `headers` field is silently dropped during sync, so the auth would never reach the runtime. 5-cat compliance: still category 3 (默认配置 / bundled extension config). The loader extension is a 1-line TS plumbing pass-through, not a change to the goose-rs extension runtime — minimal rebase surface upstream-side. Signed-off-by: earayu --- .../components/settings/extensions/bundled-extensions.json | 7 +++++-- .../components/settings/extensions/bundled-extensions.ts | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/desktop/src/components/settings/extensions/bundled-extensions.json b/ui/desktop/src/components/settings/extensions/bundled-extensions.json index 1b7d1b7d3e0b..80120d8241ce 100644 --- a/ui/desktop/src/components/settings/extensions/bundled-extensions.json +++ b/ui/desktop/src/components/settings/extensions/bundled-extensions.json @@ -57,10 +57,13 @@ "id": "apemind", "name": "apemind", "display_name": "ApeMind", - "description": "ApeMind 知识图谱 / RAG 服务(启用前请在扩展设置里填写 URL 和 token)", + "description": "ApeMind 知识图谱 / RAG 服务(启用前请在扩展设置里替换 URL 与 Authorization Bearer token)", "enabled": false, "type": "streamable_http", - "uri": "", + "uri": "https://your-apemind.example.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key-here" + }, "timeout": 300, "bundled": true } diff --git a/ui/desktop/src/components/settings/extensions/bundled-extensions.ts b/ui/desktop/src/components/settings/extensions/bundled-extensions.ts index 332d1dfbad8e..6feb2b4c90a8 100644 --- a/ui/desktop/src/components/settings/extensions/bundled-extensions.ts +++ b/ui/desktop/src/components/settings/extensions/bundled-extensions.ts @@ -17,6 +17,7 @@ type BundledExtension = { uri?: string; envs?: { [key: string]: string }; env_keys?: Array; + headers?: { [key: string]: string }; timeout?: number; allow_configure?: boolean; }; @@ -116,6 +117,9 @@ export async function syncBundledExtensions( description: bundledExt.description, timeout: bundledExt.timeout, uri: bundledExt.uri || '', + envs: bundledExt.envs, + env_keys: bundledExt.env_keys || [], + headers: bundledExt.headers, bundled: true, }; }