fix(runtime-fallback): address cubic AI review issues
- Add normalizeFallbackModels helper to centralize string/array normalization (P3) - Export RuntimeFallbackConfig and FallbackModels types from config/index.ts - Fix agent detection regex to use word boundaries for sessionID matching - Improve tests to verify actual fallback switching logic (not just log paths) - Add SessionCategoryRegistry cleanup in executeSyncTask on completion/error (P2) - All 24 runtime-fallback tests pass, 115 delegate-task tests pass
This commit is contained in:
@@ -32,4 +32,5 @@ export type {
|
||||
SisyphusConfig,
|
||||
SisyphusTasksConfig,
|
||||
RuntimeFallbackConfig,
|
||||
FallbackModels,
|
||||
} from "./schema"
|
||||
|
||||
@@ -522,9 +522,22 @@ describe("runtime-fallback", () => {
|
||||
})
|
||||
|
||||
describe("fallback models configuration", () => {
|
||||
function createMockPluginConfigWithAgentFallback(agentName: string, fallbackModels: string[]): OhMyOpenCodeConfig {
|
||||
return {
|
||||
agents: {
|
||||
[agentName]: {
|
||||
fallback_models: fallbackModels,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test("should use agent-level fallback_models", async () => {
|
||||
const input = createMockPluginInput()
|
||||
const hook = createRuntimeFallbackHook(input, { config: createMockConfig() })
|
||||
const hook = createRuntimeFallbackHook(input, {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("oracle", ["openai/gpt-5.2", "google/gemini-3-pro"]),
|
||||
})
|
||||
const sessionID = "test-agent-fallback"
|
||||
|
||||
//#given - agent with custom fallback models
|
||||
@@ -543,13 +556,17 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
//#then - should use oracle's fallback models
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("No fallback models configured") || c.msg.includes("Fallback triggered"))
|
||||
//#then - should prepare fallback to openai/gpt-5.2
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "anthropic/claude-opus-4-5", to: "openai/gpt-5.2" })
|
||||
})
|
||||
|
||||
test("should detect agent from sessionID pattern", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), { config: createMockConfig() })
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("sisyphus", ["openai/gpt-5.2"]),
|
||||
})
|
||||
const sessionID = "sisyphus-session-123"
|
||||
|
||||
await hook.event({
|
||||
@@ -566,8 +583,10 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
const errorLog = logCalls.find((c) => c.msg.includes("session.error received"))
|
||||
expect(errorLog?.data).toMatchObject({ sessionID })
|
||||
//#then - should detect sisyphus from sessionID and use its fallback
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.2" })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -115,7 +115,26 @@ function getFallbackModelsForSession(
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
const sessionAgentMatch = sessionID.match(/\b(sisyphus|oracle|librarian|explore|prometheus|atlas|metis|momus|hephaestus|sisyphus-junior|build|plan|multimodal-looker)\b/i)
|
||||
const AGENT_NAMES = [
|
||||
"sisyphus",
|
||||
"oracle",
|
||||
"librarian",
|
||||
"explore",
|
||||
"prometheus",
|
||||
"atlas",
|
||||
"metis",
|
||||
"momus",
|
||||
"hephaestus",
|
||||
"sisyphus-junior",
|
||||
"build",
|
||||
"plan",
|
||||
"multimodal-looker",
|
||||
]
|
||||
const agentPattern = new RegExp(
|
||||
`(?:^|[^a-zA-Z0-9_-])(${AGENT_NAMES.map((a) => a.replace(/-/g, "\\-")).join("|")})(?:$|[^a-zA-Z0-9_-])`,
|
||||
"i",
|
||||
)
|
||||
const sessionAgentMatch = sessionID.match(agentPattern)
|
||||
if (sessionAgentMatch) {
|
||||
const detectedAgent = sessionAgentMatch[1].toLowerCase()
|
||||
const result = tryGetFallbackFromAgent(detectedAgent)
|
||||
|
||||
@@ -73,3 +73,13 @@ export function resolveModelWithFallback(
|
||||
variant: resolved.variant,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes fallback_models config (which can be string or string[]) to string[]
|
||||
* Centralized helper to avoid duplicated normalization logic
|
||||
*/
|
||||
export function normalizeFallbackModels(models: string | string[] | undefined): string[] | undefined {
|
||||
if (!models) return undefined
|
||||
if (typeof models === "string") return [models]
|
||||
return models
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ session_id: ${sessionID}
|
||||
} finally {
|
||||
if (syncSessionID) {
|
||||
subagentSessions.delete(syncSessionID)
|
||||
SessionCategoryRegistry.remove(syncSessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user