fix(background-agent): preserve parent agent context in completion notifications
When parentAgent is undefined, omit the agent field entirely from session.prompt body instead of passing undefined. This prevents the OpenCode SDK from falling back to defaultAgent(), which would change the parent session's agent context. Changes: - manager.ts: Build prompt body conditionally, only include agent/model when defined - background-task/tools.ts: Use ctx.agent as primary source for parentAgent (consistent with sisyphus-task) - registerExternalTask: Add parentAgent parameter support - Added tests for agent context preservation scenarios
This commit is contained in:
@@ -674,3 +674,95 @@ describe("LaunchInput.skillContent", () => {
|
||||
expect(input.skillContent).toBe("You are a playwright expert")
|
||||
})
|
||||
})
|
||||
|
||||
describe("BackgroundManager.notifyParentSession - agent context preservation", () => {
|
||||
test("should not pass agent field when parentAgent is undefined", async () => {
|
||||
// #given
|
||||
const task: BackgroundTask = {
|
||||
id: "task-no-agent",
|
||||
sessionID: "session-child",
|
||||
parentSessionID: "session-parent",
|
||||
parentMessageID: "msg-parent",
|
||||
description: "task without agent context",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
parentAgent: undefined,
|
||||
parentModel: { providerID: "anthropic", modelID: "claude-opus" },
|
||||
}
|
||||
|
||||
// #when
|
||||
const promptBody = buildNotificationPromptBody(task)
|
||||
|
||||
// #then
|
||||
expect("agent" in promptBody).toBe(false)
|
||||
expect(promptBody.model).toEqual({ providerID: "anthropic", modelID: "claude-opus" })
|
||||
})
|
||||
|
||||
test("should include agent field when parentAgent is defined", async () => {
|
||||
// #given
|
||||
const task: BackgroundTask = {
|
||||
id: "task-with-agent",
|
||||
sessionID: "session-child",
|
||||
parentSessionID: "session-parent",
|
||||
parentMessageID: "msg-parent",
|
||||
description: "task with agent context",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
parentAgent: "Sisyphus",
|
||||
parentModel: { providerID: "anthropic", modelID: "claude-opus" },
|
||||
}
|
||||
|
||||
// #when
|
||||
const promptBody = buildNotificationPromptBody(task)
|
||||
|
||||
// #then
|
||||
expect(promptBody.agent).toBe("Sisyphus")
|
||||
})
|
||||
|
||||
test("should not pass model field when parentModel is undefined", async () => {
|
||||
// #given
|
||||
const task: BackgroundTask = {
|
||||
id: "task-no-model",
|
||||
sessionID: "session-child",
|
||||
parentSessionID: "session-parent",
|
||||
parentMessageID: "msg-parent",
|
||||
description: "task without model context",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
parentAgent: "Sisyphus",
|
||||
parentModel: undefined,
|
||||
}
|
||||
|
||||
// #when
|
||||
const promptBody = buildNotificationPromptBody(task)
|
||||
|
||||
// #then
|
||||
expect("model" in promptBody).toBe(false)
|
||||
expect(promptBody.agent).toBe("Sisyphus")
|
||||
})
|
||||
})
|
||||
|
||||
function buildNotificationPromptBody(task: BackgroundTask): Record<string, unknown> {
|
||||
const body: Record<string, unknown> = {
|
||||
parts: [{ type: "text", text: `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished.` }],
|
||||
}
|
||||
|
||||
if (task.parentAgent !== undefined) {
|
||||
body.agent = task.parentAgent
|
||||
}
|
||||
|
||||
if (task.parentModel?.providerID && task.parentModel?.modelID) {
|
||||
body.model = { providerID: task.parentModel.providerID, modelID: task.parentModel.modelID }
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ export class BackgroundManager {
|
||||
parentSessionID: string
|
||||
description: string
|
||||
agent?: string
|
||||
parentAgent?: string
|
||||
}): BackgroundTask {
|
||||
const task: BackgroundTask = {
|
||||
id: input.taskId,
|
||||
@@ -214,6 +215,7 @@ export class BackgroundManager {
|
||||
toolCalls: 0,
|
||||
lastUpdate: new Date(),
|
||||
},
|
||||
parentAgent: input.parentAgent,
|
||||
}
|
||||
|
||||
this.tasks.set(task.id, task)
|
||||
@@ -440,19 +442,25 @@ export class BackgroundManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// Use only parentModel/parentAgent - don't fallback to prevMessage
|
||||
// This prevents accidentally changing parent session's model/agent
|
||||
const modelField = task.parentModel?.providerID && task.parentModel?.modelID
|
||||
? { providerID: task.parentModel.providerID, modelID: task.parentModel.modelID }
|
||||
: undefined
|
||||
const body: {
|
||||
agent?: string
|
||||
model?: { providerID: string; modelID: string }
|
||||
parts: Array<{ type: "text"; text: string }>
|
||||
} = {
|
||||
parts: [{ type: "text", text: message }],
|
||||
}
|
||||
|
||||
if (task.parentAgent !== undefined) {
|
||||
body.agent = task.parentAgent
|
||||
}
|
||||
|
||||
if (task.parentModel?.providerID && task.parentModel?.modelID) {
|
||||
body.model = { providerID: task.parentModel.providerID, modelID: task.parentModel.modelID }
|
||||
}
|
||||
|
||||
await this.client.session.prompt({
|
||||
path: { id: task.parentSessionID },
|
||||
body: {
|
||||
agent: task.parentAgent,
|
||||
model: modelField,
|
||||
parts: [{ type: "text", text: message }],
|
||||
},
|
||||
body,
|
||||
query: { directory: this.directory },
|
||||
})
|
||||
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID })
|
||||
|
||||
@@ -74,7 +74,7 @@ export function createBackgroundTask(manager: BackgroundManager): ToolDefinition
|
||||
parentSessionID: ctx.sessionID,
|
||||
parentMessageID: ctx.messageID,
|
||||
parentModel,
|
||||
parentAgent: prevMessage?.agent,
|
||||
parentAgent: ctx.agent ?? prevMessage?.agent,
|
||||
})
|
||||
|
||||
ctx.metadata?.({
|
||||
|
||||
Reference in New Issue
Block a user