diff --git a/CHANGELOG.md b/CHANGELOG.md index a752cd7ca48..cf6af3717c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai ### Fixes - CLI/doctor: run bundled plugin runtime-dependency repairs through the async npm installer with spinner/line progress and heartbeat updates, so long `openclaw doctor --fix` installs no longer look hung in TTY or piped output. Fixes #72775. Thanks @dfpalhano. +- Agents/fallback: classify internal live-session model switch conflicts as unknown fallback failures instead of provider overloads, preventing local vLLM endpoints from receiving misleading overloaded cooldowns. Refs #63229. Thanks @clawdia-lobster. - Control UI: keep session-specific assistant identity loads authoritative after WebSocket connect, so non-main agent chat sessions do not show the main agent name in the header after bootstrap refreshes. Fixes #72776. Thanks @rockytian-top. - Agents/Qwen: preserve exact custom `modelstudio` provider configs with foreign `api` owners so explicit OpenAI-compatible Model Studio endpoints no longer get normalized into the bundled Qwen plugin path. Fixes #64483. Thanks @FiredMosquito831. - Media-understanding/audio: migrate deprecated `{input}` placeholders in legacy `audio.transcription.command` configs to `{{MediaPath}}`, so custom audio transcribers no longer receive the literal placeholder after doctor repair. Fixes #72760. Thanks @krisfanue3-hash. diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index f0bca92435e..6e621b3039b 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -686,6 +686,7 @@ describe("runWithModelFallback", () => { // Should NOT be a LiveSessionModelSwitchError — the outer retry loop must // not restart with the conflicting model. expect(err).not.toBeInstanceOf(LiveSessionModelSwitchError); + expect((err as { reason?: string }).reason).toBe("unknown"); expect(run).toHaveBeenCalledTimes(1); }); @@ -774,6 +775,7 @@ describe("runWithModelFallback", () => { expect(result.result).toBe("ok"); expect(result.provider).toBe("anthropic"); expect(result.model).toBe("claude-haiku-3-5"); + expect(result.attempts[0]?.reason).toBe("unknown"); expect(run.mock.calls).toEqual([ ["openai", "gpt-4.1-mini"], ["anthropic", "claude-haiku-3-5"], diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index 40c3d1c5ebb..ebc3fb7b22e 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -936,7 +936,8 @@ export async function runWithModelFallback(params: { // LiveSessionModelSwitchError during fallback may point at a later // candidate that is already the active live-session selection. Jump // there directly. Stale same/earlier targets remain a known failover - // so the outer runner cannot loop on the conflicting model. + // so the outer runner cannot loop on the conflicting model, but they + // are not provider overloads. if (err instanceof LiveSessionModelSwitchError) { const liveSwitchTargetIndex = findLiveSessionModelSwitchRedirectIndex({ error: err, @@ -950,7 +951,7 @@ export async function runWithModelFallback(params: { const switchMsg = err.message; const switchNormalized = new FailoverError(switchMsg, { - reason: "overloaded", + reason: "unknown", provider: candidate.provider, model: candidate.model, });