Fix model fallback across main/background/sync agents

This commit is contained in:
VespianRex
2026-02-19 04:41:00 +02:00
parent 400e821723
commit ff78a7f46a
44 changed files with 2500 additions and 605 deletions
+62
View File
@@ -7,6 +7,7 @@ import {
createSessionRecoveryHook,
createSessionNotification,
createThinkModeHook,
createModelFallbackHook,
createAnthropicContextWindowLimitRecoveryHook,
createAutoUpdateCheckerHook,
createAgentUsageReminderHook,
@@ -30,6 +31,7 @@ import {
detectExternalNotificationPlugin,
getNotificationConflictWarning,
log,
normalizeSDKResponse,
} from "../../shared"
import { safeCreateHook } from "../../shared/safe-create-hook"
import { sessionExists } from "../../tools"
@@ -40,6 +42,7 @@ export type SessionHooks = {
sessionRecovery: ReturnType<typeof createSessionRecoveryHook> | null
sessionNotification: ReturnType<typeof createSessionNotification> | null
thinkMode: ReturnType<typeof createThinkModeHook> | null
modelFallback: ReturnType<typeof createModelFallbackHook> | null
anthropicContextWindowLimitRecovery: ReturnType<typeof createAnthropicContextWindowLimitRecoveryHook> | null
autoUpdateChecker: ReturnType<typeof createAutoUpdateCheckerHook> | null
agentUsageReminder: ReturnType<typeof createAgentUsageReminderHook> | null
@@ -102,6 +105,64 @@ export function createSessionHooks(args: {
? safeHook("think-mode", () => createThinkModeHook())
: null
const fallbackTitleState = new Map<string, { baseTitle?: string; lastKey?: string }>()
const updateFallbackTitle = async (input: {
sessionID: string
providerID: string
modelID: string
variant?: string
}) => {
const key = `${input.providerID}/${input.modelID}${input.variant ? `:${input.variant}` : ""}`
const existing = fallbackTitleState.get(input.sessionID) ?? {}
if (existing.lastKey === key) return
if (!existing.baseTitle) {
const sessionResp = await ctx.client.session.get({ path: { id: input.sessionID } }).catch(() => null)
const sessionInfo = sessionResp
? normalizeSDKResponse(sessionResp, null as { title?: string } | null, { preferResponseOnMissingData: true })
: null
const rawTitle = sessionInfo?.title
if (typeof rawTitle === "string" && rawTitle.length > 0) {
existing.baseTitle = rawTitle.replace(/\s*\[fallback:[^\]]+\]$/i, "").trim()
} else {
existing.baseTitle = "Session"
}
}
const variantLabel = input.variant ? ` ${input.variant}` : ""
const newTitle = `${existing.baseTitle} [fallback: ${input.providerID}/${input.modelID}${variantLabel}]`
await ctx.client.session
.update({
path: { id: input.sessionID },
body: { title: newTitle },
query: { directory: ctx.directory },
})
.catch(() => {})
existing.lastKey = key
fallbackTitleState.set(input.sessionID, existing)
}
// Model fallback hook - always enabled (no feature flag)
// This handles automatic model switching when model errors occur
const modelFallback = safeHook("model-fallback", () =>
createModelFallbackHook({
toast: async ({ title, message, variant, duration }) => {
await ctx.client.tui
.showToast({
body: {
title,
message,
variant: variant ?? "warning",
duration: duration ?? 5000,
},
})
.catch(() => {})
},
onApplied: updateFallbackTitle,
}))
const anthropicContextWindowLimitRecovery = isHookEnabled("anthropic-context-window-limit-recovery")
? safeHook("anthropic-context-window-limit-recovery", () =>
createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental: pluginConfig.experimental }))
@@ -181,6 +242,7 @@ export function createSessionHooks(args: {
sessionRecovery,
sessionNotification,
thinkMode,
modelFallback,
anthropicContextWindowLimitRecovery,
autoUpdateChecker,
agentUsageReminder,
@@ -5,6 +5,7 @@ import {
createClaudeCodeHooksHook,
createKeywordDetectorHook,
createThinkingBlockValidatorHook,
createBeastModeSystemHook,
} from "../../hooks"
import {
contextCollector,
@@ -17,6 +18,7 @@ export type TransformHooks = {
keywordDetector: ReturnType<typeof createKeywordDetectorHook> | null
contextInjectorMessagesTransform: ReturnType<typeof createContextInjectorMessagesTransformHook>
thinkingBlockValidator: ReturnType<typeof createThinkingBlockValidatorHook> | null
beastModeSystem: ReturnType<typeof createBeastModeSystemHook> | null
}
export function createTransformHooks(args: {
@@ -56,10 +58,19 @@ export function createTransformHooks(args: {
)
: null
const beastModeSystem = isHookEnabled("beast-mode-system")
? safeCreateHook(
"beast-mode-system",
() => createBeastModeSystemHook(),
{ enabled: safeHookEnabled },
)
: null
return {
claudeCodeHooks,
keywordDetector,
contextInjectorMessagesTransform,
thinkingBlockValidator,
beastModeSystem,
}
}