From 817c04b6640fb20fc5085f9d9f45e4a53370760a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 5 Aug 2026 10:48:43 +0200 Subject: [PATCH] fix(vscode): prevent Agent Manager overview timeouts --- .changeset/quiet-worktrees-list.md | 5 ++ .../src/agent-manager/orchestration-bridge.ts | 5 +- ...agent-manager-orchestration-bridge.test.ts | 53 ++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .changeset/quiet-worktrees-list.md diff --git a/.changeset/quiet-worktrees-list.md b/.changeset/quiet-worktrees-list.md new file mode 100644 index 00000000000..71bd8445ece --- /dev/null +++ b/.changeset/quiet-worktrees-list.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Prevent Agent Manager overview requests from timing out while refreshing Git statistics across many worktrees. diff --git a/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts b/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts index cb2fa2da09d..0ad1e60b014 100644 --- a/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts @@ -257,7 +257,10 @@ export class AgentManagerOrchestrationBridge { if (this.disposed || active.cancelled) return const client = this.connection.getClient() if (request.operation === "overview") { - const stats = await this.options.stats(true) + // Git stats are refreshed by the poller independently. A forced refresh + // here can spawn one diff/ahead-behind pair per worktree and exceed the + // host request timeout before the overview can return its IDs. + const stats = await this.options.stats() if (this.disposed || active.cancelled) return const result = await overview({ client, diff --git a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts index 2c3f845e349..2b8b4203a0d 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts @@ -37,6 +37,7 @@ describe("AgentManagerOrchestrationBridge", () => { const replies: unknown[] = [] const rejections: unknown[] = [] const lists = new Map() + const statsCalls: Array = [] const handlers: { event?: (event: SSEPayload, directory?: string) => void state?: (state: "connecting" | "connected" | "disconnected" | "error") => void @@ -54,6 +55,12 @@ describe("AgentManagerOrchestrationBridge", () => { status: mock(async () => ({ data: {} })), promptAsync, }, + permission: { + list: mock(async () => ({ data: [] })), + }, + question: { + list: mock(async () => ({ data: [] })), + }, kilocode: { agentManager: { list: mock(async ({ directory }: { directory?: string }) => { @@ -96,7 +103,11 @@ describe("AgentManagerOrchestrationBridge", () => { root: () => root, ready: async () => state, state: () => state, - stats: async () => ({ worktrees: [] }), + stats: async (refresh) => { + statsCalls.push(refresh) + if (refresh) return new Promise(() => undefined) + return { worktrees: [] } + }, prs: () => new Map(), push, managed: (id) => managed.has(id), @@ -108,7 +119,21 @@ describe("AgentManagerOrchestrationBridge", () => { { id: `event-${value.id}`, type: "kilocode.agent_manager.requested", properties: value } as SSEPayload, directory, ) - return { bridge, client, close, handlers, lists, managed, promptAsync, push, rejections, replies, request, status } + return { + bridge, + client, + close, + handlers, + lists, + managed, + promptAsync, + push, + rejections, + replies, + request, + statsCalls, + status, + } } const request: AgentManagerRequest = { @@ -233,6 +258,30 @@ describe("AgentManagerOrchestrationBridge", () => { test.bridge.dispose() }) + it("returns an overview without waiting for a forced git refresh", async () => { + const test = harness() + test.request({ + id: "amr_overview", + sessionID: "ses_caller", + operation: "overview", + }) + await waitFor(() => test.replies.length === 1) + + expect(test.statsCalls).toEqual([undefined]) + expect(test.replies[0]).toEqual({ + requestID: "amr_overview", + directory: root, + result: { + operation: "overview", + overview: expect.objectContaining({ + ungrouped: [expect.objectContaining({ id: expect.any(String) })], + sections: [], + }), + }, + }) + test.bridge.dispose() + }) + it("stops a live panel session before it is persisted", async () => { const test = harness() test.managed.add("ses_live")