fix(delegate-task): retry sync tasks after runtime session errors

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Choi Kijin / 최 기진 / チョイ キジン
2026-04-28 15:28:20 +09:00
parent a4968a3d1d
commit 613e4a6c12
6 changed files with 268 additions and 37 deletions
+111 -36
View File
@@ -9,9 +9,11 @@ import { SessionCategoryRegistry } from "../../shared/session-category-registry"
import { formatDuration } from "./time-formatter"
import { formatDetailedError } from "./error-formatting"
import { syncTaskDeps, type SyncTaskDeps } from "./sync-task-deps"
import { retrySyncPromptWithFallbacks } from "./sync-task-fallback"
import { getNextSyncFallbackModel, retrySyncPromptWithFallbacks } from "./sync-task-fallback"
import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
import { resolveMetadataModel } from "./resolve-metadata-model"
import { shouldRetryError } from "../../shared/model-error-classifier"
import type { ModelFallbackState } from "../../hooks/model-fallback/hook"
export async function executeSyncTask(
args: DelegateTaskArgs,
@@ -147,44 +149,97 @@ export async function executeSyncTask(
}
let effectiveCategoryModel = categoryModel
let promptError = await deps.sendSyncPrompt(client, {
...syncPromptInput,
categoryModel: effectiveCategoryModel,
})
if (promptError) {
const promptResult = await retrySyncPromptWithFallbacks({
sessionID,
initialError: promptError,
categoryModel: effectiveCategoryModel,
fallbackChain,
sendPrompt: async (fallbackModel) => {
return deps.sendSyncPrompt(client, {
...syncPromptInput,
categoryModel: fallbackModel,
})
},
})
let fallbackState: ModelFallbackState | undefined = effectiveCategoryModel && fallbackChain?.length
? {
providerID: effectiveCategoryModel.providerID,
modelID: effectiveCategoryModel.modelID,
fallbackChain,
attemptCount: 0,
pending: true,
}
: undefined
let activeSessionID = sessionID
promptError = promptResult.promptError
effectiveCategoryModel = promptResult.categoryModel
if (promptError) {
return promptError
}
const cleanupRetrySession = (currentSessionID: string): void => {
subagentSessions.delete(currentSessionID)
syncSubagentSessions.delete(currentSessionID)
executorCtx.modelFallbackControllerAccessor?.clearSessionFallbackChain(currentSessionID)
SessionCategoryRegistry.remove(currentSessionID)
}
try {
const pollError = await deps.pollSyncSession(ctx, client, {
sessionID,
agentToUse,
toastManager,
taskId,
}, syncPollTimeoutMs)
if (pollError) {
return pollError
}
while (true) {
let promptError = await deps.sendSyncPrompt(client, {
...syncPromptInput,
sessionID: activeSessionID,
categoryModel: effectiveCategoryModel,
})
if (promptError) {
const promptResult = await retrySyncPromptWithFallbacks({
sessionID: activeSessionID,
initialError: promptError,
categoryModel: effectiveCategoryModel,
fallbackChain,
sendPrompt: async (fallbackModel) => {
return deps.sendSyncPrompt(client, {
...syncPromptInput,
sessionID: activeSessionID,
categoryModel: fallbackModel,
})
},
})
const result = await deps.fetchSyncResult(client, sessionID)
promptError = promptResult.promptError
effectiveCategoryModel = promptResult.categoryModel
fallbackState = promptResult.fallbackState ?? fallbackState
if (promptError) {
return promptError
}
}
const pollError = await deps.pollSyncSession(ctx, client, {
sessionID: activeSessionID,
agentToUse,
toastManager,
taskId,
}, syncPollTimeoutMs)
if (pollError) {
const nextFallbackModel = shouldRetryError({ message: pollError })
? getNextSyncFallbackModel(activeSessionID, fallbackState)
: null
if (!nextFallbackModel) {
return pollError
}
cleanupRetrySession(activeSessionID)
const retrySessionResult = await deps.createSyncSession(client, {
parentSessionID: parentContext.sessionID,
agentToUse,
description: args.description,
defaultDirectory: directory,
})
if (!retrySessionResult.ok) {
return retrySessionResult.error
}
activeSessionID = retrySessionResult.sessionID
syncSessionID = retrySessionResult.sessionID
subagentSessions.add(activeSessionID)
syncSubagentSessions.add(activeSessionID)
setSessionAgent(activeSessionID, agentToUse)
executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(activeSessionID, fallbackChain)
if (args.category) {
SessionCategoryRegistry.register(activeSessionID, args.category)
}
effectiveCategoryModel = nextFallbackModel
continue
}
const result = await deps.fetchSyncResult(client, activeSessionID)
if (!result.ok) {
return result.error
}
@@ -205,6 +260,25 @@ export async function executeSyncTask(
modelRoutingNote = `\nModel: ${actualModelStr}${args.category ? ` (category: ${args.category})` : ""}`
}
await publishToolMetadata(ctx, {
title: args.description,
metadata: {
prompt: args.prompt,
agent: agentToUse,
category: args.category,
...(args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {}),
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
taskId: activeSessionID,
sessionId: activeSessionID,
sync: true,
spawnDepth: spawnContext.childDepth,
command: args.command,
model: resolveMetadataModel(effectiveCategoryModel, parentContext.model),
},
})
return `Task completed in ${duration}.
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${modelRoutingNote}
@@ -214,11 +288,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod
${result.textContent || "(No text output)"}
${buildTaskMetadataBlock({
sessionId: sessionID,
taskId: sessionID,
sessionId: activeSessionID,
taskId: activeSessionID,
agent: agentToUse,
category: args.category,
})}`
}
} finally {
if (toastManager && taskId !== undefined) {
toastManager.removeTask(taskId)