fix: preserve terminal session lifecycle state · openclaw/openclaw@7621208
steipete
·
2026-05-03
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
|
11 | 11 | |
12 | 12 | ### Fixes |
13 | 13 | |
| 14 | +- Agents/sessions: preserve terminal lifecycle state when final run metadata persists from a stale in-memory snapshot, preventing `main` sessions from staying stuck as running after completed or timed-out turns. |
14 | 15 | - Status: show the `openai-codex` OAuth profile for `openai/gpt-*` sessions running through the native Codex runtime instead of reporting auth as unknown. (#76197) Thanks @mbelinky. |
15 | 16 | - Plugins/externalization: keep diagnostics ClawHub packages and persisted bundled-plugin relocation on npm-first install metadata for launch, and omit Discord from the core package now that its external package is published. Thanks @vincentkoc. |
16 | 17 | - Plugins/Codex: allow the official npm Codex plugin to install without the unsafe-install override, keep `/codex` command ownership, and cover the real npm Docker live path through managed `.openclaw/npm` dependencies plus uninstall failure proof. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -388,6 +388,63 @@ describe("updateSessionStoreAfterAgentRun", () => {
|
388 | 388 | }); |
389 | 389 | }); |
390 | 390 | |
| 391 | +it("preserves terminal lifecycle state when caller has a stale running snapshot", async () => { |
| 392 | +await withTempSessionStore(async ({ storePath }) => { |
| 393 | +const cfg = {} as OpenClawConfig; |
| 394 | +const sessionKey = "agent:main:explicit:test-lifecycle-preserve"; |
| 395 | +const sessionId = "test-lifecycle-preserve-session"; |
| 396 | +const terminalEntry: SessionEntry = { |
| 397 | + sessionId, |
| 398 | +updatedAt: 2_000, |
| 399 | +status: "done", |
| 400 | +startedAt: 1_000, |
| 401 | +endedAt: 1_900, |
| 402 | +runtimeMs: 900, |
| 403 | +}; |
| 404 | +await fs.writeFile(storePath, JSON.stringify({ [sessionKey]: terminalEntry }, null, 2)); |
| 405 | + |
| 406 | +const staleInMemory: Record<string, SessionEntry> = { |
| 407 | +[sessionKey]: { |
| 408 | + sessionId, |
| 409 | +updatedAt: 1_100, |
| 410 | +status: "running", |
| 411 | +startedAt: 1_000, |
| 412 | +}, |
| 413 | +}; |
| 414 | + |
| 415 | +await updateSessionStoreAfterAgentRun({ |
| 416 | + cfg, |
| 417 | + sessionId, |
| 418 | + sessionKey, |
| 419 | + storePath, |
| 420 | +sessionStore: staleInMemory, |
| 421 | +defaultProvider: "openai", |
| 422 | +defaultModel: "gpt-5.4", |
| 423 | +result: { |
| 424 | +payloads: [], |
| 425 | +meta: { |
| 426 | +aborted: false, |
| 427 | +agentMeta: { |
| 428 | +provider: "openai", |
| 429 | +model: "gpt-5.4", |
| 430 | +}, |
| 431 | +}, |
| 432 | +} as never, |
| 433 | +}); |
| 434 | + |
| 435 | +const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey]; |
| 436 | +expect(persisted).toMatchObject({ |
| 437 | +status: "done", |
| 438 | +startedAt: 1_000, |
| 439 | +endedAt: 1_900, |
| 440 | +runtimeMs: 900, |
| 441 | +modelProvider: "openai", |
| 442 | +model: "gpt-5.4", |
| 443 | +}); |
| 444 | +expect(staleInMemory[sessionKey]?.status).toBe("done"); |
| 445 | +}); |
| 446 | +}); |
| 447 | + |
391 | 448 | it("persists latest systemPromptReport for downstream warning dedupe", async () => { |
392 | 449 | await withTempSessionStore(async ({ storePath }) => { |
393 | 450 | const sessionKey = "agent:codex:report:test-system-prompt-report"; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -36,6 +36,15 @@ function resolvePositiveInteger(value: number | undefined): number | undefined {
|
36 | 36 | return Math.floor(value); |
37 | 37 | } |
38 | 38 | |
| 39 | +function removeLifecycleStateFromMetadataPatch(entry: SessionEntry): SessionEntry { |
| 40 | +const next = { ...entry }; |
| 41 | +delete next.status; |
| 42 | +delete next.startedAt; |
| 43 | +delete next.endedAt; |
| 44 | +delete next.runtimeMs; |
| 45 | +return next; |
| 46 | +} |
| 47 | + |
39 | 48 | export async function updateSessionStoreAfterAgentRun(params: { |
40 | 49 | cfg: OpenClawConfig; |
41 | 50 | contextTokensOverride?: number; |
@@ -218,8 +227,9 @@ export async function updateSessionStoreAfterAgentRun(params: {
|
218 | 227 | if (compactionsThisRun > 0) { |
219 | 228 | next.compactionCount = (entry.compactionCount ?? 0) + compactionsThisRun; |
220 | 229 | } |
| 230 | +const metadataPatch = removeLifecycleStateFromMetadataPatch(next); |
221 | 231 | const persisted = await updateSessionStore(storePath, (store) => { |
222 | | -const merged = mergeSessionEntry(store[sessionKey], next); |
| 232 | +const merged = mergeSessionEntry(store[sessionKey], metadataPatch); |
223 | 233 | store[sessionKey] = merged; |
224 | 234 | return merged; |
225 | 235 | }); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。