fix: preserve terminal session lifecycle state

This commit is contained in:
Peter Steinberger
2026-05-02 20:41:12 +01:00
parent 4a7e60d05b
commit 7621208d4e
3 changed files with 69 additions and 1 deletions

View File

@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- 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.
- 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.
- 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.
- 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.

View File

@@ -388,6 +388,63 @@ describe("updateSessionStoreAfterAgentRun", () => {
});
});
it("preserves terminal lifecycle state when caller has a stale running snapshot", async () => {
await withTempSessionStore(async ({ storePath }) => {
const cfg = {} as OpenClawConfig;
const sessionKey = "agent:main:explicit:test-lifecycle-preserve";
const sessionId = "test-lifecycle-preserve-session";
const terminalEntry: SessionEntry = {
sessionId,
updatedAt: 2_000,
status: "done",
startedAt: 1_000,
endedAt: 1_900,
runtimeMs: 900,
};
await fs.writeFile(storePath, JSON.stringify({ [sessionKey]: terminalEntry }, null, 2));
const staleInMemory: Record<string, SessionEntry> = {
[sessionKey]: {
sessionId,
updatedAt: 1_100,
status: "running",
startedAt: 1_000,
},
};
await updateSessionStoreAfterAgentRun({
cfg,
sessionId,
sessionKey,
storePath,
sessionStore: staleInMemory,
defaultProvider: "openai",
defaultModel: "gpt-5.4",
result: {
payloads: [],
meta: {
aborted: false,
agentMeta: {
provider: "openai",
model: "gpt-5.4",
},
},
} as never,
});
const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey];
expect(persisted).toMatchObject({
status: "done",
startedAt: 1_000,
endedAt: 1_900,
runtimeMs: 900,
modelProvider: "openai",
model: "gpt-5.4",
});
expect(staleInMemory[sessionKey]?.status).toBe("done");
});
});
it("persists latest systemPromptReport for downstream warning dedupe", async () => {
await withTempSessionStore(async ({ storePath }) => {
const sessionKey = "agent:codex:report:test-system-prompt-report";

View File

@@ -36,6 +36,15 @@ function resolvePositiveInteger(value: number | undefined): number | undefined {
return Math.floor(value);
}
function removeLifecycleStateFromMetadataPatch(entry: SessionEntry): SessionEntry {
const next = { ...entry };
delete next.status;
delete next.startedAt;
delete next.endedAt;
delete next.runtimeMs;
return next;
}
export async function updateSessionStoreAfterAgentRun(params: {
cfg: OpenClawConfig;
contextTokensOverride?: number;
@@ -218,8 +227,9 @@ export async function updateSessionStoreAfterAgentRun(params: {
if (compactionsThisRun > 0) {
next.compactionCount = (entry.compactionCount ?? 0) + compactionsThisRun;
}
const metadataPatch = removeLifecycleStateFromMetadataPatch(next);
const persisted = await updateSessionStore(storePath, (store) => {
const merged = mergeSessionEntry(store[sessionKey], next);
const merged = mergeSessionEntry(store[sessionKey], metadataPatch);
store[sessionKey] = merged;
return merged;
});