mirror of https://github.com/openclaw/openclaw.git
fix(agents): stop transient live-switch mismatches
This commit is contained in:
parent
e01ca8cfc6
commit
bd89e07baa
|
|
@ -118,6 +118,7 @@ Docs: https://docs.openclaw.ai
|
|||
- iMessage: stop leaking inline `[[reply_to:...]]` tags into delivered text by sending `reply_to` as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.
|
||||
- CLI/plugins: make routed commands use the same auto-enabled bundled-channel snapshot as gateway startup, so configured bundled channels like Slack load without requiring a prior config rewrite. (#54809) Thanks @neeravmakwana.
|
||||
- CLI/message send: write manual `openclaw message send` deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.
|
||||
- Agents/live switch: stop transient cron and subagent model overrides from being misread as persisted live-session switches, so isolated runs no longer fail with `LiveSessionModelSwitchError`. Thanks @vincentkoc.
|
||||
- CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider
|
||||
- Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so `/status` shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.
|
||||
- OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.
|
||||
|
|
|
|||
|
|
@ -90,6 +90,34 @@ describe("live model switch", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("prefers persisted runtime model fields ahead of session overrides", async () => {
|
||||
state.loadSessionStoreMock.mockReturnValue({
|
||||
main: {
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-6",
|
||||
modelProvider: "anthropic",
|
||||
model: "claude-sonnet-4-6",
|
||||
},
|
||||
});
|
||||
|
||||
const { resolveLiveSessionModelSelection } = await loadModule();
|
||||
|
||||
expect(
|
||||
resolveLiveSessionModelSelection({
|
||||
cfg: { session: { store: "/tmp/custom-store.json" } },
|
||||
sessionKey: "main",
|
||||
agentId: "reply",
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.4",
|
||||
}),
|
||||
).toEqual({
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-6",
|
||||
authProfileId: undefined,
|
||||
authProfileIdSource: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("queues a live switch only when an active run was aborted", async () => {
|
||||
state.abortEmbeddedPiRunMock.mockReturnValue(true);
|
||||
|
||||
|
|
@ -126,4 +154,21 @@ describe("live model switch", () => {
|
|||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not track persisted live selection when the run started on a transient model override", async () => {
|
||||
const { shouldTrackPersistedLiveSessionModelSelection } = await loadModule();
|
||||
|
||||
expect(
|
||||
shouldTrackPersistedLiveSessionModelSelection(
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-haiku-4-5",
|
||||
},
|
||||
{
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-6",
|
||||
},
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ export function resolveLiveSessionModelSelection(params: {
|
|||
agentId,
|
||||
});
|
||||
const entry = loadSessionStore(storePath, { skipCache: true })[sessionKey];
|
||||
const provider = entry?.providerOverride?.trim() || defaultModelRef.provider;
|
||||
const model = entry?.modelOverride?.trim() || defaultModelRef.model;
|
||||
const runtimeProvider = entry?.modelProvider?.trim();
|
||||
const runtimeModel = entry?.model?.trim();
|
||||
const provider = runtimeProvider || entry?.providerOverride?.trim() || defaultModelRef.provider;
|
||||
const model = runtimeModel || entry?.modelOverride?.trim() || defaultModelRef.model;
|
||||
const authProfileId = entry?.authProfileOverride?.trim() || undefined;
|
||||
return {
|
||||
provider,
|
||||
|
|
@ -101,3 +103,15 @@ export function hasDifferentLiveSessionModelSelection(
|
|||
next.authProfileIdSource
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldTrackPersistedLiveSessionModelSelection(
|
||||
current: {
|
||||
provider: string;
|
||||
model: string;
|
||||
authProfileId?: string;
|
||||
authProfileIdSource?: string;
|
||||
},
|
||||
persisted: LiveSessionModelSelection | null | undefined,
|
||||
): boolean {
|
||||
return !hasDifferentLiveSessionModelSelection(current, persisted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
hasDifferentLiveSessionModelSelection,
|
||||
LiveSessionModelSwitchError,
|
||||
resolveLiveSessionModelSelection,
|
||||
shouldTrackPersistedLiveSessionModelSelection,
|
||||
consumeLiveSessionModelSwitch,
|
||||
} from "../live-model-switch.js";
|
||||
import {
|
||||
|
|
@ -242,6 +243,10 @@ export async function runEmbeddedPiAgent(
|
|||
defaultProvider: provider,
|
||||
defaultModel: modelId,
|
||||
});
|
||||
const shouldTrackPersistedLiveSelection = shouldTrackPersistedLiveSessionModelSelection(
|
||||
resolveCurrentLiveSelection(),
|
||||
resolvePersistedLiveSelection(),
|
||||
);
|
||||
const {
|
||||
advanceAuthProfile,
|
||||
initializeAuthProfile,
|
||||
|
|
@ -449,7 +454,9 @@ export async function runEmbeddedPiAgent(
|
|||
};
|
||||
}
|
||||
runLoopIterations += 1;
|
||||
const nextSelection = resolvePersistedLiveSelection();
|
||||
const nextSelection = shouldTrackPersistedLiveSelection
|
||||
? resolvePersistedLiveSelection()
|
||||
: null;
|
||||
if (hasDifferentLiveSessionModelSelection(resolveCurrentLiveSelection(), nextSelection)) {
|
||||
log.info(
|
||||
`live session model switch detected before attempt for ${params.sessionId}: ${provider}/${modelId} -> ${nextSelection.provider}/${nextSelection.model}`,
|
||||
|
|
@ -605,9 +612,10 @@ export async function runEmbeddedPiAgent(
|
|||
}
|
||||
const failedOrAbortedAttempt =
|
||||
aborted || Boolean(promptError) || Boolean(assistantErrorText) || timedOut;
|
||||
const persistedSelection = failedOrAbortedAttempt
|
||||
? resolvePersistedLiveSelection()
|
||||
: null;
|
||||
const persistedSelection =
|
||||
failedOrAbortedAttempt && shouldTrackPersistedLiveSelection
|
||||
? resolvePersistedLiveSelection()
|
||||
: null;
|
||||
if (
|
||||
failedOrAbortedAttempt &&
|
||||
canRestartForLiveSwitch &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue