From 95aa7595f8e7529727ad800cc5a613175641af24 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 12 Feb 2026 19:09:15 +0900 Subject: [PATCH] feat: include subagent in task_metadata when resuming sessions When delegate-task resumes a session via session_id, the response task_metadata now includes a subagent field identifying which agent was running in the resumed session. This allows the parent agent to know what type of subagent it is continuing. - sync-continuation: uses resumeAgent extracted from session messages - background-continuation: uses task.agent from BackgroundTask object - Gracefully omits subagent when agent info is unavailable --- .../background-continuation.test.ts | 95 +++++++++++++++ .../delegate-task/background-continuation.ts | 2 +- .../delegate-task/sync-continuation.test.ts | 108 ++++++++++++++++++ src/tools/delegate-task/sync-continuation.ts | 2 +- 4 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/tools/delegate-task/background-continuation.test.ts diff --git a/src/tools/delegate-task/background-continuation.test.ts b/src/tools/delegate-task/background-continuation.test.ts new file mode 100644 index 000000000..f97c2d143 --- /dev/null +++ b/src/tools/delegate-task/background-continuation.test.ts @@ -0,0 +1,95 @@ +const { describe, test, expect, mock } = require("bun:test") + +describe("executeBackgroundContinuation - subagent metadata", () => { + test("includes subagent in task_metadata when task has agent", async () => { + //#given - mock manager.resume returning task with agent info + const mockManager = { + resume: async () => ({ + id: "bg_task_001", + description: "oracle consultation", + agent: "oracle", + status: "running", + sessionID: "ses_resumed_123", + }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-456", + metadata: mock(() => Promise.resolve()), + } + + const mockExecutorCtx = { + manager: mockManager, + } + + const parentContext = { + sessionID: "parent-session", + messageID: "msg-parent", + agent: "sisyphus", + } + + const args = { + session_id: "ses_resumed_123", + prompt: "continue working", + description: "resume oracle", + load_skills: [], + run_in_background: true, + } + + //#when - executeBackgroundContinuation completes + const { executeBackgroundContinuation } = require("./background-continuation") + const result = await executeBackgroundContinuation(args, mockCtx, mockExecutorCtx, parentContext) + + //#then - task_metadata should contain subagent field + expect(result).toContain("") + expect(result).toContain("subagent: oracle") + expect(result).toContain("session_id: ses_resumed_123") + }) + + test("omits subagent from task_metadata when task agent is undefined", async () => { + //#given - mock manager.resume returning task without agent + const mockManager = { + resume: async () => ({ + id: "bg_task_002", + description: "unknown task", + agent: undefined, + status: "running", + sessionID: "ses_resumed_456", + }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-789", + metadata: mock(() => Promise.resolve()), + } + + const mockExecutorCtx = { + manager: mockManager, + } + + const parentContext = { + sessionID: "parent-session", + messageID: "msg-parent", + agent: "sisyphus", + } + + const args = { + session_id: "ses_resumed_456", + prompt: "continue", + description: "resume task", + load_skills: [], + run_in_background: true, + } + + //#when - executeBackgroundContinuation completes without agent + const { executeBackgroundContinuation } = require("./background-continuation") + const result = await executeBackgroundContinuation(args, mockCtx, mockExecutorCtx, parentContext) + + //#then - task_metadata should NOT contain subagent field + expect(result).toContain("") + expect(result).toContain("session_id: ses_resumed_456") + expect(result).not.toContain("subagent:") + }) +}) diff --git a/src/tools/delegate-task/background-continuation.ts b/src/tools/delegate-task/background-continuation.ts index 02ee25a15..71e209705 100644 --- a/src/tools/delegate-task/background-continuation.ts +++ b/src/tools/delegate-task/background-continuation.ts @@ -50,7 +50,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress. session_id: ${task.sessionID} -` +${task.agent ? `subagent: ${task.agent}\n` : ""}` } catch (error) { return formatDetailedError(error, { operation: "Continue background task", diff --git a/src/tools/delegate-task/sync-continuation.test.ts b/src/tools/delegate-task/sync-continuation.test.ts index 0f218519e..d3ff6d05b 100644 --- a/src/tools/delegate-task/sync-continuation.test.ts +++ b/src/tools/delegate-task/sync-continuation.test.ts @@ -356,4 +356,112 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { expect(addTaskCalls.length).toBe(0) expect(removeTaskCalls.length).toBe(0) }) + + test("includes subagent in task_metadata when agent info is present in session messages", async () => { + //#given - mock session messages with agent info on the last assistant message + const mockClient = { + session: { + messages: async () => ({ + data: [ + { info: { id: "msg_001", role: "user", time: { created: 1000 }, agent: "oracle" } }, + { + info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn", agent: "oracle", providerID: "openai", modelID: "gpt-5.2" }, + parts: [{ type: "text", text: "Response" }], + }, + ], + }), + promptAsync: async () => ({}), + status: async () => ({ + data: { ses_test: { type: "idle" } }, + }), + }, + } + + const { executeSyncContinuation } = require("./sync-continuation") + + const deps = { + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "Result" }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-123", + metadata: () => {}, + } + + const mockExecutorCtx = { + client: mockClient, + } + + const args = { + session_id: "ses_test_12345678", + prompt: "continue working", + description: "resume oracle task", + load_skills: [], + run_in_background: false, + } + + //#when - executeSyncContinuation completes with agent info in messages + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + + //#then - task_metadata should contain subagent field with the agent name + expect(result).toContain("") + expect(result).toContain("subagent: oracle") + expect(result).toContain("session_id: ses_test_12345678") + }) + + test("omits subagent from task_metadata when no agent info in session messages", async () => { + //#given - mock session messages without any agent info + const mockClient = { + session: { + messages: async () => ({ + data: [ + { info: { id: "msg_001", role: "user", time: { created: 1000 } } }, + { + info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn" }, + parts: [{ type: "text", text: "Response" }], + }, + ], + }), + promptAsync: async () => ({}), + status: async () => ({ + data: { ses_test: { type: "idle" } }, + }), + }, + } + + const { executeSyncContinuation } = require("./sync-continuation") + + const deps = { + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "Result" }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-123", + metadata: () => {}, + } + + const mockExecutorCtx = { + client: mockClient, + } + + const args = { + session_id: "ses_test_12345678", + prompt: "continue working", + description: "resume task", + load_skills: [], + run_in_background: false, + } + + //#when - executeSyncContinuation completes without agent info + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + + //#then - task_metadata should NOT contain subagent field + expect(result).toContain("") + expect(result).toContain("session_id: ses_test_12345678") + expect(result).not.toContain("subagent:") + }) }) diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index 2b811438a..95f6baec8 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -128,7 +128,7 @@ ${result.textContent || "(No text output)"} session_id: ${args.session_id} -` +${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}` } finally { if (toastManager) { toastManager.removeTask(taskId)