mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix(agents): prevent heartbeat model override from persisting in session state
- Skip setting runtime model when preserveRuntimeModel is true and no prior model exists - Preserve model-only entries without borrowing heartbeat provider to avoid invalid cross-provider pairs
This commit is contained in:
@@ -980,7 +980,7 @@ describe("updateSessionStoreAfterAgentRun", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to run model when preserveRuntimeModel is true but entry has no prior runtime model", async () => {
|
||||
it("does not set runtime model when preserveRuntimeModel is true and entry has no prior runtime model", async () => {
|
||||
await withTempSessionStore(async ({ storePath }) => {
|
||||
const cfg = {} as OpenClawConfig;
|
||||
const sessionKey = "agent:main:explicit:test-heartbeat-new-session";
|
||||
@@ -1017,10 +1017,60 @@ describe("updateSessionStoreAfterAgentRun", () => {
|
||||
preserveRuntimeModel: true,
|
||||
});
|
||||
|
||||
// No prior runtime model, so falls back to the run's model
|
||||
expect(sessionStore[sessionKey]?.model).toBe("llama3.2:1b");
|
||||
expect(sessionStore[sessionKey]?.modelProvider).toBe("ollama");
|
||||
expect(sessionStore[sessionKey]?.contextTokens).toBe(128_000);
|
||||
// Heartbeat should NOT establish initial model state on an empty session
|
||||
expect(sessionStore[sessionKey]?.model).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.modelProvider).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.contextTokens).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves model without borrowing heartbeat provider when entry has model but no modelProvider", async () => {
|
||||
await withTempSessionStore(async ({ storePath }) => {
|
||||
const cfg = {} as OpenClawConfig;
|
||||
const sessionKey = "agent:main:explicit:test-heartbeat-model-no-provider";
|
||||
const sessionId = "test-heartbeat-model-no-provider-session";
|
||||
const sessionStore: Record<string, SessionEntry> = {
|
||||
[sessionKey]: {
|
||||
sessionId,
|
||||
updatedAt: 1,
|
||||
model: "claude-opus-4-6",
|
||||
// modelProvider intentionally missing
|
||||
},
|
||||
};
|
||||
await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));
|
||||
|
||||
// Heartbeat turn uses a different provider
|
||||
const result: EmbeddedPiRunResult = {
|
||||
meta: {
|
||||
durationMs: 500,
|
||||
agentMeta: {
|
||||
sessionId,
|
||||
provider: "ollama",
|
||||
model: "llama3.2:1b",
|
||||
contextTokens: 128_000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateSessionStoreAfterAgentRun({
|
||||
cfg,
|
||||
sessionId,
|
||||
sessionKey,
|
||||
storePath,
|
||||
sessionStore,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModel: "claude-opus-4-6",
|
||||
result,
|
||||
preserveRuntimeModel: true,
|
||||
});
|
||||
|
||||
// Model preserved, provider NOT borrowed from heartbeat
|
||||
expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");
|
||||
expect(sessionStore[sessionKey]?.modelProvider).toBeUndefined();
|
||||
|
||||
const persisted = loadSessionStore(storePath);
|
||||
expect(persisted[sessionKey]?.model).toBe("claude-opus-4-6");
|
||||
expect(persisted[sessionKey]?.modelProvider).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -125,14 +125,19 @@ export async function updateSessionStoreAfterAgentRun(params: {
|
||||
// leave contextTokens unset rather than falling back to the heartbeat
|
||||
// run's context window; status derives it from the preserved model.
|
||||
next.contextTokens = entry.contextTokens;
|
||||
} else {
|
||||
// No prior runtime model: heartbeat establishes initial state.
|
||||
next.contextTokens = entry.contextTokens ?? contextTokens;
|
||||
if (entry.modelProvider) {
|
||||
setSessionRuntimeModel(next, {
|
||||
provider: entry.modelProvider,
|
||||
model: entry.model,
|
||||
});
|
||||
} else {
|
||||
// Retain the model-only entry without borrowing the heartbeat provider
|
||||
// to avoid invalid cross-provider pairs (e.g. ollama/claude-opus-4-6).
|
||||
next.model = entry.model;
|
||||
}
|
||||
}
|
||||
setSessionRuntimeModel(next, {
|
||||
provider: entry.modelProvider ?? providerUsed,
|
||||
model: entry.model ?? modelUsed,
|
||||
});
|
||||
// When there is no prior runtime model, do nothing: a heartbeat turn
|
||||
// should not establish initial model state on an empty session.
|
||||
} else {
|
||||
setSessionRuntimeModel(next, {
|
||||
provider: providerUsed,
|
||||
|
||||
Reference in New Issue
Block a user