From 39fef2042b5f4ab1169690f7886cc1a849833d85 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 21:52:40 +0900 Subject: [PATCH] fix(background-agent): resolve parent wake agent aliases --- src/features/background-agent/manager.test.ts | 47 ++++++++++++++++++- src/features/background-agent/manager.ts | 26 ++++++---- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 823da5435..c03580fb2 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -7,7 +7,7 @@ import { getSessionPromptParams, clearSessionPromptParams } from "../../shared/s import { tmpdir } from "node:os" import type { PluginInput } from "@opencode-ai/plugin" import * as sharedModule from "../../shared" -import { _resetForTesting as resetClaudeCodeSessionState, subagentSessions } from "../claude-code-session-state" +import { _resetForTesting as resetClaudeCodeSessionState, registerAgentName, subagentSessions } from "../claude-code-session-state" import type { BackgroundTask, ResumeInput } from "./types" import { MIN_IDLE_TIME_MS } from "./constants" import { BackgroundManager } from "./manager" @@ -5204,6 +5204,51 @@ describe("BackgroundManager.handleEvent - session.error", () => { manager.shutdown() }) + test("pins the registered parent agent alias before dispatching a deferred parent wake", async () => { + //#given + resetClaudeCodeSessionState() + registerAgentName("\u200B\u200B\u200B\u200BAtlas - Plan Executor") + const promptCalls: Array<{ path: { id: string }; body: Record }> = [] + const client = { + session: { + status: async () => ({ data: { "parent-session-alias": { type: "idle" } } }), + promptAsync: async (args: { path: { id: string }; body: Record }) => { + promptCalls.push(args) + return {} + }, + abort: async () => ({}), + }, + } + const manager = new BackgroundManager({ pluginContext: createPluginInput(client) }) + const managerInternals = cast<{ + queuePendingParentWake: ( + sessionID: string, + notification: string, + promptContext: Record, + shouldReply: boolean, + delayMs?: number, + ) => void + flushPendingParentWake: (sessionID: string) => Promise + }>(manager) + + //#when + managerInternals.queuePendingParentWake( + "parent-session-alias", + "done", + { agent: "atlas" }, + true, + 0, + ) + await managerInternals.flushPendingParentWake("parent-session-alias") + + //#then + expect(promptCalls).toHaveLength(1) + expect(promptCalls[0]?.body.agent).toBe("\u200B\u200B\u200B\u200BAtlas - Plan Executor") + + manager.shutdown() + resetClaudeCodeSessionState() + }) + test("does not requeue dispatched parent wake when session.error arrives before accepted history is visible", async () => { //#given const promptCalls: Array<{ path: { id: string }; body: Record }> = [] diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 95bba7fc7..84611c578 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -37,7 +37,7 @@ import { type QueueItem, } from "./constants" -import { subagentSessions } from "../claude-code-session-state" +import { resolveRegisteredAgentName, subagentSessions } from "../claude-code-session-state" import { getTaskToastManager } from "../task-toast-manager" import { formatDuration } from "./duration-formatter" import { @@ -1385,13 +1385,20 @@ The fallback retry session is now created and can be inspected directly. this.observedOutputSessions.add(sessionID) } - private cloneParentWake(wake: PendingParentWake): PendingParentWake { + private resolveParentWakePromptContext(promptContext: ParentWakePromptContext): ParentWakePromptContext { + const resolvedAgent = resolveRegisteredAgentName(promptContext.agent) return { - promptContext: { - ...wake.promptContext, - ...(wake.promptContext.model ? { model: { ...wake.promptContext.model } } : {}), - ...(wake.promptContext.tools ? { tools: { ...wake.promptContext.tools } } : {}), - }, + ...promptContext, + ...(resolvedAgent ? { agent: resolvedAgent } : {}), + ...(promptContext.model ? { model: { ...promptContext.model } } : {}), + ...(promptContext.tools ? { tools: { ...promptContext.tools } } : {}), + } + } + + private cloneParentWake(wake: PendingParentWake): PendingParentWake { + const promptContext = this.resolveParentWakePromptContext(wake.promptContext) + return { + promptContext, notifications: [...wake.notifications], shouldReply: wake.shouldReply, ...(wake.dispatchedAt !== undefined ? { dispatchedAt: wake.dispatchedAt } : {}), @@ -2692,14 +2699,15 @@ The task was re-queued on a fallback model after a retryable failure. shouldReply: boolean, delayMs?: number, ): void { + const resolvedPromptContext = this.resolveParentWakePromptContext(promptContext) const pendingWake = this.pendingParentWakes.get(sessionID) if (pendingWake) { pendingWake.notifications.push(notification) - pendingWake.promptContext = promptContext + pendingWake.promptContext = resolvedPromptContext pendingWake.shouldReply = pendingWake.shouldReply || shouldReply } else { this.pendingParentWakes.set(sessionID, { - promptContext, + promptContext: resolvedPromptContext, notifications: [notification], shouldReply, })