From 79e9fd82c56e4f36d39afffe67c61671a80d7322 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 9 Jan 2026 15:53:36 +0900 Subject: [PATCH] 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 --- src/features/background-agent/manager.test.ts | 92 +++++++++++++++++++ src/features/background-agent/manager.ts | 28 ++++-- src/tools/background-task/tools.ts | 2 +- 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index e340af68b..6d2f61e7e 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -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 { + const body: Record = { + 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 +} diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 392d6775a..ccc7ddc63 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -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 }) diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts index 9dd39447b..1f9169378 100644 --- a/src/tools/background-task/tools.ts +++ b/src/tools/background-task/tools.ts @@ -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?.({