From 5a1afb00f78ed614d91230710b1142899479bbf8 Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sat, 5 Sep 2026 09:45:23 +0800 Subject: [PATCH] fix: reuse Runtime after native inventory load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native capability loading previously cold-started a temporary Runtime and disposed it immediately, so Linux ARM users paid the OpenCode startup cost again on their first request. Native inventory loading now uses the cached execution Runtime, preserving normal idle eviction and lifecycle cleanup. Release note: 优化 Linux ARM 上切换 OpenCode 后的响应速度;原生能力清单与后续对话现在复用同一 Runtime,避免重复冷启动。 --- .../deepseek-harness/technical-design.md | 2 +- .../agent/selected-runtime-manager.test.ts | 43 ++++++++++++++----- src/main/agent/selected-runtime-manager.ts | 17 +++----- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/docs/features/deepseek-harness/technical-design.md b/docs/features/deepseek-harness/technical-design.md index fd622ad1..7b344f8a 100644 --- a/docs/features/deepseek-harness/technical-design.md +++ b/docs/features/deepseek-harness/technical-design.md @@ -338,7 +338,7 @@ GoodBuddy conversationId -> Harness sessionId + process generation ### 9.4 释放与退出 -- 原生能力清单通过一次性 Runtime 探测,取得有界快照后立即 dispose,不得因浏览设置或切换项目把 Host 缓存在执行 Runtime 池中。 +- 原生能力清单由 `SelectedRuntimeManager` 缓存的执行 Runtime 提供,与随后的对话请求共用同一实例,不再为清单单独启动并销毁一次 Host;该实例遵循执行缓存的空闲淘汰、设置变更 reset 和退出 dispose 规则。 - 删除或释放对话时调用 `goodbuddy/session/release`。 - Runtime dispose 时先拒绝新请求,再取消所有 Session。 - Harness Control Plane 完成 Agent、工具和会话清理,Host 完成 Cordis Fiber 与子进程的反向清理。 diff --git a/src/main/agent/selected-runtime-manager.test.ts b/src/main/agent/selected-runtime-manager.test.ts index 26cbcf22..4a836669 100644 --- a/src/main/agent/selected-runtime-manager.test.ts +++ b/src/main/agent/selected-runtime-manager.test.ts @@ -318,13 +318,9 @@ describe('SelectedRuntimeManager', () => { await manager.dispose() }) - it('disposes a native-inventory runtime without caching it', async () => { - const inspected = runtime() - const cached = runtime() - const create = vi - .fn() - .mockResolvedValueOnce(inspected.value) - .mockResolvedValueOnce(cached.value) + it('serves the native inventory from the cached execution runtime', async () => { + const shared = runtime() + const create = vi.fn(async () => shared.value) const manager = new SelectedRuntimeManager(create) const selection = { provider: 'opencode' as const } @@ -337,15 +333,42 @@ describe('SelectedRuntimeManager', () => { provider: 'opencode', inventoryStatus: 'available' }) - expect(inspected.getNativeSnapshot).toHaveBeenCalledOnce() - expect(inspected.dispose).toHaveBeenCalledOnce() + expect(shared.getNativeSnapshot).toHaveBeenCalledOnce() + expect(shared.dispose).not.toHaveBeenCalled() - await manager.getRuntime( + const controller = await manager.getRuntime( selection, executionSpace('local:project-one') ) + expect(create).toHaveBeenCalledOnce() + await expect(controller.getStatus()).resolves.toMatchObject({ + available: true + }) + + await manager.getNativeSnapshot( + selection, + executionSpace('local:project-two') + ) expect(create).toHaveBeenCalledTimes(2) await manager.dispose() + expect(shared.dispose).toHaveBeenCalled() + }) + + it('rejects the native inventory when the runtime lacks it', async () => { + const plain = runtime() + const withoutInventory: AgentRuntime = { ...plain.value } + delete withoutInventory.getNativeSnapshot + const manager = new SelectedRuntimeManager( + vi.fn(async () => withoutInventory) + ) + + await expect( + manager.getNativeSnapshot({ + provider: 'model', + profileId: '00000000-0000-4000-8000-000000000001' + }) + ).rejects.toThrow('当前 Runtime 不支持原生能力清单') + await manager.dispose() }) it('waits for a pending connection-test runtime during shutdown', async () => { diff --git a/src/main/agent/selected-runtime-manager.ts b/src/main/agent/selected-runtime-manager.ts index f37aef4e..c8ed5c7e 100644 --- a/src/main/agent/selected-runtime-manager.ts +++ b/src/main/agent/selected-runtime-manager.ts @@ -355,18 +355,11 @@ export class SelectedRuntimeManager implements SelectedRuntimeResolver { selection: AgentRuntimeSelection, executionSpace?: ExecutionSpaceDescriptor ): Promise { - const runtime = await this.createRuntime(selection, executionSpace) - try { - if (this.disposed) { - throw new Error('Agent Runtime 正在关闭') - } - if (!runtime.getNativeSnapshot) { - throw new Error('当前 Runtime 不支持原生能力清单') - } - return await runtime.getNativeSnapshot() - } finally { - await runtime.dispose() - } + // The inventory runs on the same cached Runtime that later serves the + // conversation, so a slow cold start (embedded OpenCode on Linux ARM) + // is paid once instead of once per inventory and again per request. + const controller = await this.getRuntime(selection, executionSpace) + return controller.getNativeSnapshot() } private async startRetiring(