fix(background-agent): retry with fallback agent on Agent not found error
When a model/mode switch happens while a background task is in-flight, the oh-my-openagent agent registry can be rebuilt without custom agents (e.g., Sisyphus-Junior). The SDK then rejects the promptAsync call with "Agent not found", killing the task. This adds retry logic: when promptAsync fails with "Agent not found", retry with the "general" agent (always available in opencode). The original prompt, model, and skill content are preserved — only the agent routing changes. Fixes both the spawner (startTask/resumeTask) and manager (inline launch) code paths. Also improves the error message detection in manager.ts to recognize "Agent not found" alongside the existing "agent.name"/"undefined" checks. Related: #2052, #2875, #2882 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,13 @@ import { getTaskToastManager } from "../task-toast-manager"
|
||||
import { isInsideTmux } from "../../shared/tmux"
|
||||
import type { ConcurrencyManager } from "./concurrency"
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
export interface SpawnerContext {
|
||||
client: OpencodeClient
|
||||
directory: string
|
||||
@@ -138,22 +145,42 @@ export async function startTask(
|
||||
|
||||
applySessionPromptParams(sessionID, input.model)
|
||||
|
||||
const promptBody = {
|
||||
agent: input.agent,
|
||||
...(launchModel ? { model: launchModel } : {}),
|
||||
...(launchVariant ? { variant: launchVariant } : {}),
|
||||
system: input.skillContent,
|
||||
tools: {
|
||||
task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
...getAgentToolRestrictions(input.agent),
|
||||
},
|
||||
parts: [createInternalAgentTextPart(input.prompt)],
|
||||
}
|
||||
|
||||
promptWithModelSuggestionRetry(client, {
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
agent: input.agent,
|
||||
...(launchModel ? { model: launchModel } : {}),
|
||||
...(launchVariant ? { variant: launchVariant } : {}),
|
||||
system: input.skillContent,
|
||||
tools: {
|
||||
task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
...getAgentToolRestrictions(input.agent),
|
||||
},
|
||||
parts: [createInternalAgentTextPart(input.prompt)],
|
||||
},
|
||||
}).catch((error) => {
|
||||
body: promptBody,
|
||||
}).catch(async (error) => {
|
||||
if (isAgentNotFoundError(error) && input.agent !== FALLBACK_AGENT) {
|
||||
log("[background-agent] Agent not found, retrying with fallback agent", {
|
||||
original: input.agent,
|
||||
fallback: FALLBACK_AGENT,
|
||||
taskId: task.id,
|
||||
})
|
||||
try {
|
||||
await promptWithModelSuggestionRetry(client, {
|
||||
path: { id: sessionID },
|
||||
body: { ...promptBody, agent: FALLBACK_AGENT },
|
||||
})
|
||||
return
|
||||
} catch (retryError) {
|
||||
log("[background-agent] Fallback agent also failed:", retryError)
|
||||
onTaskError(task, retryError instanceof Error ? retryError : new Error(String(retryError)))
|
||||
return
|
||||
}
|
||||
}
|
||||
log("[background-agent] promptAsync error:", error)
|
||||
onTaskError(task, error instanceof Error ? error : new Error(String(error)))
|
||||
})
|
||||
@@ -228,21 +255,41 @@ export async function resumeTask(
|
||||
|
||||
applySessionPromptParams(task.sessionID, task.model)
|
||||
|
||||
const resumeBody = {
|
||||
agent: task.agent,
|
||||
...(resumeModel ? { model: resumeModel } : {}),
|
||||
...(resumeVariant ? { variant: resumeVariant } : {}),
|
||||
tools: {
|
||||
task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
...getAgentToolRestrictions(task.agent),
|
||||
},
|
||||
parts: [createInternalAgentTextPart(input.prompt)],
|
||||
}
|
||||
|
||||
client.session.promptAsync({
|
||||
path: { id: task.sessionID },
|
||||
body: {
|
||||
agent: task.agent,
|
||||
...(resumeModel ? { model: resumeModel } : {}),
|
||||
...(resumeVariant ? { variant: resumeVariant } : {}),
|
||||
tools: {
|
||||
task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
...getAgentToolRestrictions(task.agent),
|
||||
},
|
||||
parts: [createInternalAgentTextPart(input.prompt)],
|
||||
},
|
||||
}).catch((error) => {
|
||||
body: resumeBody,
|
||||
}).catch(async (error) => {
|
||||
if (isAgentNotFoundError(error) && task.agent !== FALLBACK_AGENT) {
|
||||
log("[background-agent] Resume agent not found, retrying with fallback agent", {
|
||||
original: task.agent,
|
||||
fallback: FALLBACK_AGENT,
|
||||
taskId: task.id,
|
||||
})
|
||||
try {
|
||||
await client.session.promptAsync({
|
||||
path: { id: task.sessionID! },
|
||||
body: { ...resumeBody, agent: FALLBACK_AGENT },
|
||||
})
|
||||
return
|
||||
} catch (retryError) {
|
||||
log("[background-agent] Resume fallback agent also failed:", retryError)
|
||||
onTaskError(task, retryError instanceof Error ? retryError : new Error(String(retryError)))
|
||||
return
|
||||
}
|
||||
}
|
||||
log("[background-agent] resume prompt error:", error)
|
||||
onTaskError(task, error instanceof Error ? error : new Error(String(error)))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user