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
This commit is contained in:
YeonGyu-Kim
2026-02-12 19:09:15 +09:00
parent c6349dc38a
commit 95aa7595f8
4 changed files with 205 additions and 2 deletions

View File

@@ -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("<task_metadata>")
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("<task_metadata>")
expect(result).toContain("session_id: ses_resumed_456")
expect(result).not.toContain("subagent:")
})
})

View File

@@ -50,7 +50,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.
<task_metadata>
session_id: ${task.sessionID}
</task_metadata>`
${task.agent ? `subagent: ${task.agent}\n` : ""}</task_metadata>`
} catch (error) {
return formatDetailedError(error, {
operation: "Continue background task",

View File

@@ -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("<task_metadata>")
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("<task_metadata>")
expect(result).toContain("session_id: ses_test_12345678")
expect(result).not.toContain("subagent:")
})
})

View File

@@ -128,7 +128,7 @@ ${result.textContent || "(No text output)"}
<task_metadata>
session_id: ${args.session_id}
</task_metadata>`
${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}</task_metadata>`
} finally {
if (toastManager) {
toastManager.removeTask(taskId)