惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
雷峰网
雷峰网
小众软件
小众软件
博客园 - 【当耐特】
J
Java Code Geeks
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
H
Help Net Security
博客园_首页
P
Proofpoint News Feed
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
N
Netflix TechBlog - Medium
爱范儿
爱范儿
MyScale Blog
MyScale Blog
Blog — PlanetScale
Blog — PlanetScale
The Cloudflare Blog
MongoDB | Blog
MongoDB | Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(agents): preserve session model after heartbeat runs ...
zhangguiping · 2026-05-02 · via Recent Commits to openclaw:main

@@ -877,6 +877,248 @@ describe("updateSessionStoreAfterAgentRun", () => {

877877

expect(sessionStore[sessionKey]?.lastInteractionAt).toBeGreaterThan(lastInteractionAt);

878878

});

879879

});

880+881+

it("preserves runtime model and contextTokens when preserveRuntimeModel is true (heartbeat bleed fix)", async () => {

882+

await withTempSessionStore(async ({ storePath }) => {

883+

const cfg = {} as OpenClawConfig;

884+

const sessionKey = "agent:main:explicit:test-heartbeat-bleed";

885+

const sessionId = "test-heartbeat-bleed-session";

886+

const sessionStore: Record<string, SessionEntry> = {

887+

[sessionKey]: {

888+

sessionId,

889+

updatedAt: 1,

890+

modelProvider: "anthropic",

891+

model: "claude-opus-4-6",

892+

contextTokens: 1_000_000,

893+

},

894+

};

895+

await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));

896+897+

// Heartbeat turn uses a different model

898+

const result: EmbeddedPiRunResult = {

899+

meta: {

900+

durationMs: 500,

901+

agentMeta: {

902+

sessionId,

903+

provider: "ollama",

904+

model: "llama3.2:1b",

905+

contextTokens: 128_000,

906+

},

907+

},

908+

};

909+910+

await updateSessionStoreAfterAgentRun({

911+

cfg,

912+

sessionId,

913+

sessionKey,

914+

storePath,

915+

sessionStore,

916+

defaultProvider: "anthropic",

917+

defaultModel: "claude-opus-4-6",

918+

result,

919+

preserveRuntimeModel: true,

920+

});

921+922+

// Runtime model and contextTokens should be preserved from the original entry

923+

expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");

924+

expect(sessionStore[sessionKey]?.modelProvider).toBe("anthropic");

925+

expect(sessionStore[sessionKey]?.contextTokens).toBe(1_000_000);

926+927+

const persisted = loadSessionStore(storePath);

928+

expect(persisted[sessionKey]?.model).toBe("claude-opus-4-6");

929+

expect(persisted[sessionKey]?.modelProvider).toBe("anthropic");

930+

expect(persisted[sessionKey]?.contextTokens).toBe(1_000_000);

931+

});

932+

});

933+934+

it("leaves contextTokens unset when entry has prior model but no contextTokens (heartbeat bleed guard)", async () => {

935+

await withTempSessionStore(async ({ storePath }) => {

936+

const cfg = {} as OpenClawConfig;

937+

const sessionKey = "agent:main:explicit:test-heartbeat-no-context-tokens";

938+

const sessionId = "test-heartbeat-no-context-tokens-session";

939+

const sessionStore: Record<string, SessionEntry> = {

940+

[sessionKey]: {

941+

sessionId,

942+

updatedAt: 1,

943+

modelProvider: "anthropic",

944+

model: "claude-opus-4-6",

945+

// contextTokens intentionally missing — older session without cached context

946+

},

947+

};

948+

await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));

949+950+

// Heartbeat turn uses a different, smaller model

951+

const result: EmbeddedPiRunResult = {

952+

meta: {

953+

durationMs: 500,

954+

agentMeta: {

955+

sessionId,

956+

provider: "ollama",

957+

model: "llama3.2:1b",

958+

contextTokens: 128_000,

959+

},

960+

},

961+

};

962+963+

await updateSessionStoreAfterAgentRun({

964+

cfg,

965+

sessionId,

966+

sessionKey,

967+

storePath,

968+

sessionStore,

969+

defaultProvider: "anthropic",

970+

defaultModel: "claude-opus-4-6",

971+

result,

972+

preserveRuntimeModel: true,

973+

});

974+975+

// Runtime model should be preserved

976+

expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");

977+

expect(sessionStore[sessionKey]?.modelProvider).toBe("anthropic");

978+

// contextTokens should NOT bleed from the heartbeat run's smaller window

979+

expect(sessionStore[sessionKey]?.contextTokens).toBeUndefined();

980+

});

981+

});

982+983+

it("does not set runtime model when preserveRuntimeModel is true and entry has no prior runtime model", async () => {

984+

await withTempSessionStore(async ({ storePath }) => {

985+

const cfg = {} as OpenClawConfig;

986+

const sessionKey = "agent:main:explicit:test-heartbeat-new-session";

987+

const sessionId = "test-heartbeat-new-session-id";

988+

const sessionStore: Record<string, SessionEntry> = {

989+

[sessionKey]: {

990+

sessionId,

991+

updatedAt: 1,

992+

},

993+

};

994+

await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));

995+996+

const result: EmbeddedPiRunResult = {

997+

meta: {

998+

durationMs: 500,

999+

agentMeta: {

1000+

sessionId,

1001+

provider: "ollama",

1002+

model: "llama3.2:1b",

1003+

contextTokens: 128_000,

1004+

},

1005+

},

1006+

};

1007+1008+

await updateSessionStoreAfterAgentRun({

1009+

cfg,

1010+

sessionId,

1011+

sessionKey,

1012+

storePath,

1013+

sessionStore,

1014+

defaultProvider: "ollama",

1015+

defaultModel: "llama3.2:1b",

1016+

result,

1017+

preserveRuntimeModel: true,

1018+

});

1019+1020+

// Heartbeat should NOT establish initial model state on an empty session

1021+

expect(sessionStore[sessionKey]?.model).toBeUndefined();

1022+

expect(sessionStore[sessionKey]?.modelProvider).toBeUndefined();

1023+

expect(sessionStore[sessionKey]?.contextTokens).toBeUndefined();

1024+

});

1025+

});

1026+1027+

it("preserves model without borrowing heartbeat provider when entry has model but no modelProvider", async () => {

1028+

await withTempSessionStore(async ({ storePath }) => {

1029+

const cfg = {} as OpenClawConfig;

1030+

const sessionKey = "agent:main:explicit:test-heartbeat-model-no-provider";

1031+

const sessionId = "test-heartbeat-model-no-provider-session";

1032+

const sessionStore: Record<string, SessionEntry> = {

1033+

[sessionKey]: {

1034+

sessionId,

1035+

updatedAt: 1,

1036+

model: "claude-opus-4-6",

1037+

// modelProvider intentionally missing

1038+

},

1039+

};

1040+

await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));

1041+1042+

// Heartbeat turn uses a different provider

1043+

const result: EmbeddedPiRunResult = {

1044+

meta: {

1045+

durationMs: 500,

1046+

agentMeta: {

1047+

sessionId,

1048+

provider: "ollama",

1049+

model: "llama3.2:1b",

1050+

contextTokens: 128_000,

1051+

},

1052+

},

1053+

};

1054+1055+

await updateSessionStoreAfterAgentRun({

1056+

cfg,

1057+

sessionId,

1058+

sessionKey,

1059+

storePath,

1060+

sessionStore,

1061+

defaultProvider: "anthropic",

1062+

defaultModel: "claude-opus-4-6",

1063+

result,

1064+

preserveRuntimeModel: true,

1065+

});

1066+1067+

// Model preserved, provider NOT borrowed from heartbeat

1068+

expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");

1069+

expect(sessionStore[sessionKey]?.modelProvider).toBeUndefined();

1070+1071+

const persisted = loadSessionStore(storePath);

1072+

expect(persisted[sessionKey]?.model).toBe("claude-opus-4-6");

1073+

expect(persisted[sessionKey]?.modelProvider).toBeUndefined();

1074+

});

1075+

});

1076+1077+

it("overwrites runtime model when preserveRuntimeModel is false (default behavior)", async () => {

1078+

await withTempSessionStore(async ({ storePath }) => {

1079+

const cfg = {} as OpenClawConfig;

1080+

const sessionKey = "agent:main:explicit:test-normal-overwrite";

1081+

const sessionId = "test-normal-overwrite-session";

1082+

const sessionStore: Record<string, SessionEntry> = {

1083+

[sessionKey]: {

1084+

sessionId,

1085+

updatedAt: 1,

1086+

modelProvider: "anthropic",

1087+

model: "claude-opus-4-6",

1088+

contextTokens: 1_000_000,

1089+

},

1090+

};

1091+

await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));

1092+1093+

const result: EmbeddedPiRunResult = {

1094+

meta: {

1095+

durationMs: 500,

1096+

agentMeta: {

1097+

sessionId,

1098+

provider: "openai",

1099+

model: "gpt-5.4",

1100+

contextTokens: 400_000,

1101+

},

1102+

},

1103+

};

1104+1105+

await updateSessionStoreAfterAgentRun({

1106+

cfg,

1107+

sessionId,

1108+

sessionKey,

1109+

storePath,

1110+

sessionStore,

1111+

defaultProvider: "openai",

1112+

defaultModel: "gpt-5.4",

1113+

result,

1114+

});

1115+1116+

// Normal turn: runtime model is updated

1117+

expect(sessionStore[sessionKey]?.model).toBe("gpt-5.4");

1118+

expect(sessionStore[sessionKey]?.modelProvider).toBe("openai");

1119+

expect(sessionStore[sessionKey]?.contextTokens).toBe(400_000);

1120+

});

1121+

});

8801122

});

88111238821124

describe("clearCliSessionInStore", () => {