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:
95
src/tools/delegate-task/background-continuation.test.ts
Normal file
95
src/tools/delegate-task/background-continuation.test.ts
Normal 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:")
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
@@ -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:")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user