Merge pull request #2309 from code-yeongyu/fix/task-tui-session-metadata-sync
fix(task): align background delegate-task output with OpenCode TUI session metadata contract
This commit is contained in:
commit
fc41a389c5
158
src/tools/delegate-task/background-task.test.ts
Normal file
158
src/tools/delegate-task/background-task.test.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
const bunTest = require("bun:test")
|
||||||
|
const describeFn = bunTest.describe
|
||||||
|
const testFn = bunTest.test
|
||||||
|
const expectFn = bunTest.expect
|
||||||
|
const beforeEachFn = bunTest.beforeEach
|
||||||
|
const afterEachFn = bunTest.afterEach
|
||||||
|
|
||||||
|
const { executeBackgroundTask } = require("./background-task")
|
||||||
|
const { __setTimingConfig, __resetTimingConfig } = require("./timing")
|
||||||
|
|
||||||
|
describeFn("executeBackgroundTask output/session metadata compatibility", () => {
|
||||||
|
beforeEachFn(() => {
|
||||||
|
//#given - reduce waiting to keep tests fast
|
||||||
|
__setTimingConfig({
|
||||||
|
WAIT_FOR_SESSION_INTERVAL_MS: 1,
|
||||||
|
WAIT_FOR_SESSION_TIMEOUT_MS: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEachFn(() => {
|
||||||
|
__resetTimingConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
testFn("does not emit synthetic pending session metadata when session id is unresolved", async () => {
|
||||||
|
//#given - launched task without resolved subagent session id
|
||||||
|
const metadataCalls: any[] = []
|
||||||
|
const manager = {
|
||||||
|
launch: async () => ({
|
||||||
|
id: "bg_unresolved",
|
||||||
|
sessionID: undefined,
|
||||||
|
description: "Unresolved session",
|
||||||
|
agent: "explore",
|
||||||
|
status: "running",
|
||||||
|
}),
|
||||||
|
getTask: () => undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await executeBackgroundTask(
|
||||||
|
{
|
||||||
|
description: "Unresolved session",
|
||||||
|
prompt: "check",
|
||||||
|
run_in_background: true,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sessionID: "ses_parent",
|
||||||
|
callID: "call_1",
|
||||||
|
metadata: async (value: any) => metadataCalls.push(value),
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
},
|
||||||
|
{ manager },
|
||||||
|
{ sessionID: "ses_parent", messageID: "msg_1" },
|
||||||
|
"explore",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
//#then - output and metadata should avoid fake session markers
|
||||||
|
expectFn(result).not.toContain("<task_metadata>")
|
||||||
|
expectFn(result).not.toContain("session_id: undefined")
|
||||||
|
expectFn(result).not.toContain("session_id: pending")
|
||||||
|
expectFn(metadataCalls).toHaveLength(1)
|
||||||
|
expectFn("sessionId" in metadataCalls[0].metadata).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
testFn("emits task metadata session_id when real session id is available", async () => {
|
||||||
|
//#given - launched task with resolved subagent session id
|
||||||
|
const metadataCalls: any[] = []
|
||||||
|
const manager = {
|
||||||
|
launch: async () => ({
|
||||||
|
id: "bg_resolved",
|
||||||
|
sessionID: "ses_sub_123",
|
||||||
|
description: "Resolved session",
|
||||||
|
agent: "explore",
|
||||||
|
status: "running",
|
||||||
|
}),
|
||||||
|
getTask: () => ({ sessionID: "ses_sub_123" }),
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await executeBackgroundTask(
|
||||||
|
{
|
||||||
|
description: "Resolved session",
|
||||||
|
prompt: "check",
|
||||||
|
run_in_background: true,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sessionID: "ses_parent",
|
||||||
|
callID: "call_2",
|
||||||
|
metadata: async (value: any) => metadataCalls.push(value),
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
},
|
||||||
|
{ manager },
|
||||||
|
{ sessionID: "ses_parent", messageID: "msg_2" },
|
||||||
|
"explore",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
//#then - output and metadata should include canonical session linkage
|
||||||
|
expectFn(result).toContain("<task_metadata>")
|
||||||
|
expectFn(result).toContain("session_id: ses_sub_123")
|
||||||
|
expectFn(result).toContain("task_id: ses_sub_123")
|
||||||
|
expectFn(result).toContain("background_task_id: bg_resolved")
|
||||||
|
expectFn(result).toContain("Background Task ID: bg_resolved")
|
||||||
|
expectFn(metadataCalls).toHaveLength(1)
|
||||||
|
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_sub_123")
|
||||||
|
})
|
||||||
|
|
||||||
|
testFn("captures late-resolved session id and emits synced metadata", async () => {
|
||||||
|
//#given - background task session id appears after launch via manager polling
|
||||||
|
const metadataCalls: any[] = []
|
||||||
|
let reads = 0
|
||||||
|
const manager = {
|
||||||
|
launch: async () => ({
|
||||||
|
id: "bg_late",
|
||||||
|
sessionID: undefined,
|
||||||
|
description: "Late session",
|
||||||
|
agent: "explore",
|
||||||
|
status: "running",
|
||||||
|
}),
|
||||||
|
getTask: () => {
|
||||||
|
reads += 1
|
||||||
|
return reads >= 2 ? { sessionID: "ses_late_123" } : undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await executeBackgroundTask(
|
||||||
|
{
|
||||||
|
description: "Late session",
|
||||||
|
prompt: "check",
|
||||||
|
run_in_background: true,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sessionID: "ses_parent",
|
||||||
|
callID: "call_3",
|
||||||
|
metadata: async (value: any) => metadataCalls.push(value),
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
},
|
||||||
|
{ manager },
|
||||||
|
{ sessionID: "ses_parent", messageID: "msg_3" },
|
||||||
|
"explore",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
//#then - late session id still propagates to task metadata contract
|
||||||
|
expectFn(result).toContain("session_id: ses_late_123")
|
||||||
|
expectFn(result).toContain("task_id: ses_late_123")
|
||||||
|
expectFn(result).toContain("background_task_id: bg_late")
|
||||||
|
expectFn(metadataCalls).toHaveLength(1)
|
||||||
|
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_late_123")
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -56,37 +56,39 @@ export async function executeBackgroundTask(
|
|||||||
SessionCategoryRegistry.register(sessionId, args.category)
|
SessionCategoryRegistry.register(sessionId, args.category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
prompt: args.prompt,
|
||||||
|
agent: task.agent,
|
||||||
|
category: args.category,
|
||||||
|
load_skills: args.load_skills,
|
||||||
|
description: args.description,
|
||||||
|
run_in_background: args.run_in_background,
|
||||||
|
command: args.command,
|
||||||
|
...(sessionId ? { sessionId } : {}),
|
||||||
|
...(categoryModel ? { model: { providerID: categoryModel.providerID, modelID: categoryModel.modelID } } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
const unstableMeta = {
|
const unstableMeta = {
|
||||||
title: args.description,
|
title: args.description,
|
||||||
metadata: {
|
metadata,
|
||||||
prompt: args.prompt,
|
|
||||||
agent: task.agent,
|
|
||||||
category: args.category,
|
|
||||||
load_skills: args.load_skills,
|
|
||||||
description: args.description,
|
|
||||||
run_in_background: args.run_in_background,
|
|
||||||
sessionId: sessionId ?? "pending",
|
|
||||||
command: args.command,
|
|
||||||
model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
await ctx.metadata?.(unstableMeta)
|
await ctx.metadata?.(unstableMeta)
|
||||||
if (ctx.callID) {
|
if (ctx.callID) {
|
||||||
storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta)
|
storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskMetadataBlock = sessionId
|
||||||
|
? `\n\n<task_metadata>\nsession_id: ${sessionId}\ntask_id: ${sessionId}\nbackground_task_id: ${task.id}\n</task_metadata>`
|
||||||
|
: ""
|
||||||
|
|
||||||
return `Background task launched.
|
return `Background task launched.
|
||||||
|
|
||||||
Task ID: ${task.id}
|
Background Task ID: ${task.id}
|
||||||
Description: ${task.description}
|
Description: ${task.description}
|
||||||
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
||||||
Status: ${task.status}
|
Status: ${task.status}
|
||||||
|
|
||||||
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
|
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.${taskMetadataBlock}`
|
||||||
|
|
||||||
<task_metadata>
|
|
||||||
session_id: ${sessionId}
|
|
||||||
</task_metadata>`
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return formatDetailedError(error, {
|
return formatDetailedError(error, {
|
||||||
operation: "Launch background task",
|
operation: "Launch background task",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user