fix(runtime-fallback): keep pending retry state on gate skip
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import { createAutoRetryHelpers } from "./auto-retry"
|
||||
import { createFallbackState } from "./fallback-state"
|
||||
import type { HookDeps, RuntimeFallbackPluginInput } from "./types"
|
||||
|
||||
function createContext(promptCalls: { count: number }): RuntimeFallbackPluginInput {
|
||||
const session = {
|
||||
abort: async () => ({}),
|
||||
messages: async () => ({
|
||||
data: [
|
||||
{
|
||||
info: { role: "user" },
|
||||
parts: [{ type: "text", text: "retry this" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
promptAsync: async () => {
|
||||
promptCalls.count += 1
|
||||
return {}
|
||||
},
|
||||
status: async () => ({ data: { "session-auto-retry": { type: "busy" } } }),
|
||||
}
|
||||
return {
|
||||
client: {
|
||||
session,
|
||||
tui: {
|
||||
showToast: async () => ({}),
|
||||
},
|
||||
},
|
||||
directory: "/test/dir",
|
||||
}
|
||||
}
|
||||
|
||||
function createDeps(promptCalls: { count: number }): HookDeps {
|
||||
return {
|
||||
ctx: createContext(promptCalls),
|
||||
config: {
|
||||
enabled: true,
|
||||
retry_on_errors: [429, 503, 529],
|
||||
max_fallback_attempts: 3,
|
||||
cooldown_seconds: 60,
|
||||
timeout_seconds: 0,
|
||||
notify_on_fallback: false,
|
||||
},
|
||||
options: undefined,
|
||||
pluginConfig: undefined,
|
||||
sessionStates: new Map(),
|
||||
sessionLastAccess: new Map(),
|
||||
sessionRetryInFlight: new Set(),
|
||||
sessionAwaitingFallbackResult: new Set(),
|
||||
sessionFallbackTimeouts: new Map(),
|
||||
sessionStatusRetryKeys: new Map(),
|
||||
internallyAbortedSessions: new Set(),
|
||||
}
|
||||
}
|
||||
|
||||
describe("createAutoRetryHelpers", () => {
|
||||
test("#given an existing fallback result is pending #when a new fallback retry is skipped by the prompt gate #then the previous pending state is preserved", async () => {
|
||||
// given
|
||||
const promptCalls = { count: 0 }
|
||||
const deps = createDeps(promptCalls)
|
||||
const helpers = createAutoRetryHelpers(deps)
|
||||
const sessionID = "session-auto-retry"
|
||||
const state = createFallbackState("anthropic/claude-opus-4-7")
|
||||
state.pendingFallbackModel = "openai/gpt-5.4"
|
||||
deps.sessionStates.set(sessionID, state)
|
||||
deps.sessionAwaitingFallbackResult.add(sessionID)
|
||||
|
||||
// when
|
||||
await helpers.autoRetryWithFallback(sessionID, "google/gemini-2.5-pro", undefined, "session.status")
|
||||
|
||||
// then
|
||||
expect(promptCalls.count).toBe(0)
|
||||
expect(deps.sessionAwaitingFallbackResult.has(sessionID)).toBe(true)
|
||||
expect(state.pendingFallbackModel).toBe("openai/gpt-5.4")
|
||||
})
|
||||
})
|
||||
@@ -137,6 +137,8 @@ export function createAutoRetryHelpers(deps: HookDeps) {
|
||||
return
|
||||
}
|
||||
|
||||
const hadAwaitingFallbackResult = sessionAwaitingFallbackResult.has(sessionID)
|
||||
const previousPendingFallbackModel = sessionStates.get(sessionID)?.pendingFallbackModel
|
||||
sessionRetryInFlight.add(sessionID)
|
||||
let retryDispatched = false
|
||||
try {
|
||||
@@ -154,8 +156,10 @@ export function createAutoRetryHelpers(deps: HookDeps) {
|
||||
|
||||
const retryAgent = resolvedAgent ?? getSessionAgent(sessionID)
|
||||
const launchAgent = resolveRegisteredAgentName(retryAgent)
|
||||
sessionAwaitingFallbackResult.add(sessionID)
|
||||
scheduleSessionFallbackTimeout(sessionID, retryAgent)
|
||||
if (!hadAwaitingFallbackResult) {
|
||||
sessionAwaitingFallbackResult.add(sessionID)
|
||||
scheduleSessionFallbackTimeout(sessionID, retryAgent)
|
||||
}
|
||||
|
||||
const promptResult = await dispatchInternalPrompt({
|
||||
mode: "async",
|
||||
@@ -185,6 +189,10 @@ export function createAutoRetryHelpers(deps: HookDeps) {
|
||||
})
|
||||
return
|
||||
}
|
||||
sessionAwaitingFallbackResult.add(sessionID)
|
||||
if (hadAwaitingFallbackResult) {
|
||||
scheduleSessionFallbackTimeout(sessionID, retryAgent)
|
||||
}
|
||||
retryDispatched = true
|
||||
} else {
|
||||
log(`[${HOOK_NAME}] No user message found for auto-retry (${source})`, { sessionID })
|
||||
@@ -194,11 +202,19 @@ export function createAutoRetryHelpers(deps: HookDeps) {
|
||||
} finally {
|
||||
sessionRetryInFlight.delete(sessionID)
|
||||
if (!retryDispatched) {
|
||||
sessionAwaitingFallbackResult.delete(sessionID)
|
||||
clearSessionFallbackTimeout(sessionID)
|
||||
if (hadAwaitingFallbackResult) {
|
||||
sessionAwaitingFallbackResult.add(sessionID)
|
||||
} else {
|
||||
sessionAwaitingFallbackResult.delete(sessionID)
|
||||
clearSessionFallbackTimeout(sessionID)
|
||||
}
|
||||
const state = sessionStates.get(sessionID)
|
||||
if (state?.pendingFallbackModel) {
|
||||
state.pendingFallbackModel = undefined
|
||||
if (state) {
|
||||
if (hadAwaitingFallbackResult) {
|
||||
state.pendingFallbackModel = previousPendingFallbackModel
|
||||
} else if (state.pendingFallbackModel) {
|
||||
state.pendingFallbackModel = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user