From 90f0971f4fc85550011895d56b5c5919acc9b324 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 18:06:22 +0900 Subject: [PATCH] fix(background-agent): handle idle status events --- src/features/background-agent/manager.test.ts | 61 +++++++++++++++++++ src/features/background-agent/manager.ts | 9 ++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 6c92398ad..9249af41a 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -5023,6 +5023,67 @@ describe("BackgroundManager.handleEvent - session.error", () => { manager.shutdown() }) + test("completes task on session.status idle after todo-continuation finishes", async () => { + //#given + const sessionID = "ses-status-idle-after-todo-continuation" + const client = { + session: { + prompt: async () => ({}), + promptAsync: async () => ({}), + abort: async () => ({}), + messages: async () => ({ + data: [ + { + info: { role: "assistant" }, + parts: [{ type: "text", text: "final verified result" }], + }, + ], + }), + todo: async () => ({ data: [] }), + }, + } + + const manager = new BackgroundManager({ pluginContext: createPluginInput(client) }) + stubNotifyParentSession(manager) + mockVerifySessionExists(manager, true) + + const task = createMockTask({ + id: "task-status-idle-after-todo-continuation", + sessionId: sessionID, + parentSessionId: "parent-session", + parentMessageId: "msg-status-idle", + description: "task that finished after todo-continuation", + agent: "explore", + status: "running", + startedAt: new Date(Date.now() - (MIN_IDLE_TIME_MS + 10)), + }) + getTaskMap(manager).set(task.id, task) + + manager.handleEvent({ + type: "todo.updated", + properties: { + sessionID, + todos: [{ id: "todo-1", content: "compile result", status: "completed", priority: "high" }], + }, + }) + + //#when + manager.handleEvent({ + type: "session.status", + properties: { + sessionID, + status: { type: "idle" }, + }, + }) + await flushBackgroundNotifications() + + //#then + expect(task.status).toBe("completed") + expect(task.completedAt).toBeDefined() + + manager.shutdown() + }) + test("retry path releases current concurrency slot and prefers current provider in fallback entry", async () => { //#given const manager = createBackgroundManager() diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 071a33cd9..d1fad5ca4 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -1531,7 +1531,14 @@ The fallback retry session is now created and can be inspected directly. if (event.type === "session.status") { const sessionID = props?.sessionID as string | undefined const status = props?.status as { type?: string; message?: string } | undefined - if (!sessionID || status?.type !== "retry") return + if (!sessionID || !status?.type) return + + if (status.type === "idle") { + this.handleEvent({ type: "session.idle", properties: { sessionID } }) + return + } + + if (status.type !== "retry") return const resolved = this.resolveTaskAttemptBySession(sessionID) if (!resolved?.isCurrent) return