Merge pull request #1981 from VespianRex/fix/fallback-sync-model-ui
Fix model fallback retries for main, background, and sync subagents + show runtime fallback model in task UI
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { CallOmoAgentArgs } from "./types"
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { subagentSessions } from "../../features/claude-code-session-state"
|
||||
import { subagentSessions, syncSubagentSessions } from "../../features/claude-code-session-state"
|
||||
import { log } from "../../shared"
|
||||
|
||||
export async function createOrGetSession(
|
||||
@@ -64,6 +64,7 @@ Original error: ${createResult.error}`)
|
||||
const sessionID = createResult.data.id
|
||||
log(`[call_omo_agent] Created session: ${sessionID}`)
|
||||
subagentSessions.add(sessionID)
|
||||
syncSubagentSessions.add(sessionID)
|
||||
return { sessionID, isNew: true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { log } from "../../shared"
|
||||
import { resolveSessionDirectory } from "../../shared"
|
||||
import { subagentSessions } from "../../features/claude-code-session-state"
|
||||
import { subagentSessions, syncSubagentSessions } from "../../features/claude-code-session-state"
|
||||
import type { CallOmoAgentArgs } from "./types"
|
||||
import type { ToolContextWithMetadata } from "./tool-context-with-metadata"
|
||||
|
||||
@@ -69,5 +69,6 @@ Original error: ${createResult.error}`,
|
||||
const sessionID = createResult.data.id
|
||||
log(`[call_omo_agent] Created session: ${sessionID}`)
|
||||
subagentSessions.add(sessionID)
|
||||
syncSubagentSessions.add(sessionID)
|
||||
return { ok: true, sessionID }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
|
||||
import type { ExecutorContext, ParentContext } from "./executor-types"
|
||||
import type { FallbackEntry } from "../../shared/model-requirements"
|
||||
import { getTimingConfig } from "./timing"
|
||||
import { storeToolMetadata } from "../../features/tool-metadata-store"
|
||||
import { formatDetailedError } from "./error-formatting"
|
||||
@@ -12,7 +13,8 @@ export async function executeBackgroundTask(
|
||||
parentContext: ParentContext,
|
||||
agentToUse: string,
|
||||
categoryModel: { providerID: string; modelID: string; variant?: string } | undefined,
|
||||
systemContent: string | undefined
|
||||
systemContent: string | undefined,
|
||||
fallbackChain?: FallbackEntry[],
|
||||
): Promise<string> {
|
||||
const { manager } = executorCtx
|
||||
|
||||
@@ -27,6 +29,7 @@ export async function executeBackgroundTask(
|
||||
parentAgent: parentContext.agent,
|
||||
parentTools: getSessionTools(parentContext.sessionID),
|
||||
model: categoryModel,
|
||||
fallbackChain,
|
||||
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
||||
skillContent: systemContent,
|
||||
category: args.category,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ModelFallbackInfo } from "../../features/task-toast-manager/types"
|
||||
import type { DelegateTaskArgs } from "./types"
|
||||
import type { ExecutorContext } from "./executor-types"
|
||||
import type { FallbackEntry } from "../../shared/model-requirements"
|
||||
import { mergeCategories } from "../../shared/merge-categories"
|
||||
import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent"
|
||||
import { resolveCategoryConfig } from "./categories"
|
||||
@@ -16,6 +17,7 @@ export interface CategoryResolutionResult {
|
||||
modelInfo: ModelFallbackInfo | undefined
|
||||
actualModel: string | undefined
|
||||
isUnstableAgent: boolean
|
||||
fallbackChain?: FallbackEntry[] // For runtime retry on model errors
|
||||
error?: string
|
||||
}
|
||||
|
||||
@@ -177,5 +179,6 @@ Available categories: ${categoryNames.join(", ")}`,
|
||||
modelInfo,
|
||||
actualModel,
|
||||
isUnstableAgent,
|
||||
fallbackChain: requirement?.fallbackChain,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getAgentDisplayName, getAgentConfigKey } from "../../shared/agent-displ
|
||||
import { normalizeSDKResponse } from "../../shared"
|
||||
import { log } from "../../shared/logger"
|
||||
import { getAvailableModelsForDelegateTask } from "./available-models"
|
||||
import type { FallbackEntry } from "../../shared/model-requirements"
|
||||
import { resolveModelForDelegateTask } from "./model-selection"
|
||||
|
||||
export async function resolveSubagentExecution(
|
||||
@@ -15,7 +16,7 @@ export async function resolveSubagentExecution(
|
||||
executorCtx: ExecutorContext,
|
||||
parentAgent: string | undefined,
|
||||
categoryExamples: string
|
||||
): Promise<{ agentToUse: string; categoryModel: { providerID: string; modelID: string; variant?: string } | undefined; error?: string }> {
|
||||
): Promise<{ agentToUse: string; categoryModel: { providerID: string; modelID: string; variant?: string } | undefined; fallbackChain?: FallbackEntry[]; error?: string }> {
|
||||
const { client, agentOverrides } = executorCtx
|
||||
|
||||
if (!args.subagent_type?.trim()) {
|
||||
@@ -46,6 +47,7 @@ Create the work plan directly - that's your job as the planning agent.`,
|
||||
|
||||
let agentToUse = agentName
|
||||
let categoryModel: { providerID: string; modelID: string; variant?: string } | undefined
|
||||
let fallbackChain: FallbackEntry[] | undefined = undefined
|
||||
|
||||
try {
|
||||
const agentsResult = await client.app.agents()
|
||||
@@ -92,6 +94,7 @@ Create the work plan directly - that's your job as the planning agent.`,
|
||||
const agentOverride = agentOverrides?.[agentConfigKey as keyof typeof agentOverrides]
|
||||
?? (agentOverrides ? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentConfigKey)?.[1] : undefined)
|
||||
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentConfigKey]
|
||||
fallbackChain = agentRequirement?.fallbackChain
|
||||
|
||||
if (agentOverride?.model || agentRequirement || matchedAgent.model) {
|
||||
const availableModels = await getAvailableModelsForDelegateTask(client)
|
||||
@@ -135,5 +138,5 @@ Create the work plan directly - that's your job as the planning agent.`,
|
||||
}
|
||||
}
|
||||
|
||||
return { agentToUse, categoryModel }
|
||||
return { agentToUse, categoryModel, fallbackChain }
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
//#when - executeSyncTask with fetchSyncResult failing
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined, undefined, deps)
|
||||
}, "test-agent", undefined, undefined, undefined, undefined, deps)
|
||||
|
||||
//#then - should return error and cleanup resources
|
||||
expect(result).toBe("Fetch failed")
|
||||
@@ -150,7 +150,7 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
//#when - executeSyncTask with pollSyncSession failing
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined, undefined, deps)
|
||||
}, "test-agent", undefined, undefined, undefined, undefined, deps)
|
||||
|
||||
//#then - should return error and cleanup resources
|
||||
expect(result).toBe("Poll error")
|
||||
@@ -200,7 +200,7 @@ describe("executeSyncTask - cleanup on error paths", () => {
|
||||
//#when - executeSyncTask completes successfully
|
||||
const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, {
|
||||
sessionID: "parent-session",
|
||||
}, "test-agent", undefined, undefined, undefined, deps)
|
||||
}, "test-agent", undefined, undefined, undefined, undefined, deps)
|
||||
|
||||
//#then - should complete and cleanup resources
|
||||
expect(result).toContain("Task completed")
|
||||
|
||||
@@ -3,11 +3,12 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
|
||||
import type { ExecutorContext, ParentContext } from "./executor-types"
|
||||
import { getTaskToastManager } from "../../features/task-toast-manager"
|
||||
import { storeToolMetadata } from "../../features/tool-metadata-store"
|
||||
import { subagentSessions } from "../../features/claude-code-session-state"
|
||||
import { subagentSessions, syncSubagentSessions, setSessionAgent } from "../../features/claude-code-session-state"
|
||||
import { log } from "../../shared/logger"
|
||||
import { formatDuration } from "./time-formatter"
|
||||
import { formatDetailedError } from "./error-formatting"
|
||||
import { syncTaskDeps, type SyncTaskDeps } from "./sync-task-deps"
|
||||
import { setSessionFallbackChain, clearSessionFallbackChain } from "../../hooks/model-fallback/hook"
|
||||
|
||||
export async function executeSyncTask(
|
||||
args: DelegateTaskArgs,
|
||||
@@ -18,6 +19,7 @@ export async function executeSyncTask(
|
||||
categoryModel: { providerID: string; modelID: string; variant?: string } | undefined,
|
||||
systemContent: string | undefined,
|
||||
modelInfo?: ModelFallbackInfo,
|
||||
fallbackChain?: import("../../shared/model-requirements").FallbackEntry[],
|
||||
deps: SyncTaskDeps = syncTaskDeps
|
||||
): Promise<string> {
|
||||
const { client, directory, onSyncSessionCreated } = executorCtx
|
||||
@@ -40,6 +42,9 @@ export async function executeSyncTask(
|
||||
const sessionID = createSessionResult.sessionID
|
||||
syncSessionID = sessionID
|
||||
subagentSessions.add(sessionID)
|
||||
syncSubagentSessions.add(sessionID)
|
||||
setSessionAgent(sessionID, agentToUse)
|
||||
setSessionFallbackChain(sessionID, fallbackChain)
|
||||
|
||||
if (onSyncSessionCreated) {
|
||||
log("[task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID })
|
||||
@@ -59,6 +64,7 @@ export async function executeSyncTask(
|
||||
if (toastManager) {
|
||||
toastManager.addTask({
|
||||
id: taskId,
|
||||
sessionID,
|
||||
description: args.description,
|
||||
agent: agentToUse,
|
||||
isBackground: false,
|
||||
@@ -145,6 +151,8 @@ session_id: ${sessionID}
|
||||
} finally {
|
||||
if (syncSessionID) {
|
||||
subagentSessions.delete(syncSessionID)
|
||||
syncSubagentSessions.delete(syncSessionID)
|
||||
clearSessionFallbackChain(syncSessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ Prompts MUST be in English.`
|
||||
let modelInfo: import("../../features/task-toast-manager/types").ModelFallbackInfo | undefined
|
||||
let actualModel: string | undefined
|
||||
let isUnstableAgent = false
|
||||
let fallbackChain: import("../../shared/model-requirements").FallbackEntry[] | undefined
|
||||
|
||||
if (args.category) {
|
||||
const resolution = await resolveCategoryExecution(args, options, inheritedModel, systemDefaultModel)
|
||||
@@ -176,6 +177,7 @@ Prompts MUST be in English.`
|
||||
modelInfo = resolution.modelInfo
|
||||
actualModel = resolution.actualModel
|
||||
isUnstableAgent = resolution.isUnstableAgent
|
||||
fallbackChain = resolution.fallbackChain
|
||||
|
||||
const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean
|
||||
|
||||
@@ -206,6 +208,7 @@ Prompts MUST be in English.`
|
||||
}
|
||||
agentToUse = resolution.agentToUse
|
||||
categoryModel = resolution.categoryModel
|
||||
fallbackChain = resolution.fallbackChain
|
||||
}
|
||||
|
||||
const systemContent = buildSystemContent({
|
||||
@@ -217,10 +220,10 @@ Prompts MUST be in English.`
|
||||
})
|
||||
|
||||
if (runInBackground) {
|
||||
return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent)
|
||||
return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain)
|
||||
}
|
||||
|
||||
return executeSyncTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo)
|
||||
return executeSyncTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user