diff --git a/src/features/background-agent/spawner.test.ts b/src/features/background-agent/spawner.test.ts index 34a70873c..d3896e55f 100644 --- a/src/features/background-agent/spawner.test.ts +++ b/src/features/background-agent/spawner.test.ts @@ -144,11 +144,13 @@ describe("background-agent spawner agent-not-found fallback", () => { test("calls onTaskError if fallback agent also fails", async () => { //#given + let callCount = 0 const client = { session: { get: async () => ({ data: { directory: "/tmp/test" } }), create: async () => ({ data: { id: "session-fallback" } }), promptAsync: async () => { + callCount++ throw new Error('Agent not found: "Sisyphus-Junior". Available agents: build, explore, general, plan') }, }, @@ -188,8 +190,135 @@ describe("background-agent spawner agent-not-found fallback", () => { await new Promise(resolve => setTimeout(resolve, 50)) //#then + // Verify retry was attempted (2 calls: original + fallback) + expect(callCount).toBe(2) expect(onTaskError).toHaveBeenCalled() }) + + test("retries on agent.name/undefined error variant", async () => { + //#given + const promptCalls: any[] = [] + let callCount = 0 + + const client = { + session: { + get: async () => ({ data: { directory: "/tmp/test" } }), + create: async () => ({ data: { id: "session-fallback" } }), + promptAsync: async (args: any) => { + callCount++ + promptCalls.push({ body: { ...args.body } }) + if (callCount === 1) { + throw new Error("Cannot read properties of undefined (reading 'agent.name')") + } + return { data: {} } + }, + }, + } as any + + const onTaskError = mock(() => {}) + + const task = createTask({ + description: "Test task", + prompt: "Do work", + agent: "Sisyphus-Junior", + parentSessionID: "ses_parent", + parentMessageID: "msg_parent", + }) + + const item = { + task, + input: { + description: task.description, + prompt: task.prompt, + agent: task.agent, + parentSessionID: task.parentSessionID, + parentMessageID: task.parentMessageID, + parentModel: task.parentModel, + parentAgent: task.parentAgent, + model: task.model, + }, + } + + const ctx = { + client, + directory: "/tmp/test", + concurrencyManager: { release: () => {} }, + tmuxEnabled: false, + onTaskError, + } + + //#when + await startTask(item as any, ctx as any) + await new Promise(resolve => setTimeout(resolve, 50)) + + //#then + expect(promptCalls).toHaveLength(2) + expect(promptCalls[0].body.agent).toBe("Sisyphus-Junior") + expect(promptCalls[1].body.agent).toBe("general") + expect(onTaskError).not.toHaveBeenCalled() + }) + + test("detects agent error from plain object with message field", async () => { + //#given + const promptCalls: any[] = [] + let callCount = 0 + + const client = { + session: { + get: async () => ({ data: { directory: "/tmp/test" } }), + create: async () => ({ data: { id: "session-fallback" } }), + promptAsync: async (args: any) => { + callCount++ + promptCalls.push({ body: { ...args.body } }) + if (callCount === 1) { + throw { message: 'Agent not found: "Custom-Agent"', name: "UnknownError" } + } + return { data: {} } + }, + }, + } as any + + const onTaskError = mock(() => {}) + + const task = createTask({ + description: "Test task", + prompt: "Do work", + agent: "Custom-Agent", + parentSessionID: "ses_parent", + parentMessageID: "msg_parent", + }) + + const item = { + task, + input: { + description: task.description, + prompt: task.prompt, + agent: task.agent, + parentSessionID: task.parentSessionID, + parentMessageID: task.parentMessageID, + parentModel: task.parentModel, + parentAgent: task.parentAgent, + model: task.model, + }, + } + + const ctx = { + client, + directory: "/tmp/test", + concurrencyManager: { release: () => {} }, + tmuxEnabled: false, + onTaskError, + } + + //#when + await startTask(item as any, ctx as any) + await new Promise(resolve => setTimeout(resolve, 50)) + + //#then + expect(promptCalls).toHaveLength(2) + expect(promptCalls[1].body.agent).toBe("general") + expect(onTaskError).not.toHaveBeenCalled() + }) }) describe("background-agent spawner fallback model promotion", () => { diff --git a/src/features/background-agent/spawner.ts b/src/features/background-agent/spawner.ts index c412e7c3a..1ae9f078d 100644 --- a/src/features/background-agent/spawner.ts +++ b/src/features/background-agent/spawner.ts @@ -11,8 +11,19 @@ import type { ConcurrencyManager } from "./concurrency" export const FALLBACK_AGENT = "general" export function isAgentNotFoundError(error: unknown): boolean { - const message = error instanceof Error ? error.message : String(error) - return message.includes("Agent not found") + const message = + typeof error === "string" + ? error + : error instanceof Error + ? error.message + : typeof error === "object" && error !== null && typeof (error as { message?: unknown }).message === "string" + ? (error as { message: string }).message + : String(error) + return ( + message.includes("Agent not found") || + message.includes("agent.name") || + (message.includes("agent") && message.includes("undefined")) + ) } export function buildFallbackBody(