


























@@ -1038,6 +1038,83 @@ describe("generateAndAppendDreamNarrative", () => {
10381038expectLogIncludes(logger.info, "dreaming cleanup scrubbed");
10391039});
104010401041+it("reclaims an aged dreaming row whose transcript still exists (failed deleteSession)", async () => {
1042+const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
1043+const stateDir = await createTempWorkspace("openclaw-dreaming-state-");
1044+const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
1045+await fs.mkdir(sessionsDir, { recursive: true });
1046+const storePath = path.join(sessionsDir, "sessions.json");
1047+// Orphan: a completed dreaming row whose deleteSession previously threw, so
1048+// BOTH the store row and its transcript still exist (issue #88322).
1049+const orphanTranscript = path.join(sessionsDir, "orphan-dreaming.jsonl");
1050+// A second dreaming row whose transcript is fresh (a live/just-started run)
1051+// must be preserved.
1052+const liveTranscript = path.join(sessionsDir, "live-dreaming.jsonl");
1053+await fs.writeFile(
1054+storePath,
1055+`${JSON.stringify({
1056+ "agent:main:dreaming-narrative-deep-orphan": {
1057+ sessionId: "orphan-dreaming",
1058+ },
1059+ "agent:main:dreaming-narrative-deep-live": {
1060+ sessionId: "live-dreaming",
1061+ },
1062+ "agent:main:kept-session": {
1063+ sessionId: "still-live",
1064+ },
1065+ })}\n`,
1066+"utf-8",
1067+);
1068+await fs.writeFile(orphanTranscript, '{"runId":"dreaming-narrative-deep-orphan"}\n', "utf-8");
1069+await fs.writeFile(liveTranscript, '{"runId":"dreaming-narrative-deep-live"}\n', "utf-8");
1070+await fs.writeFile(path.join(sessionsDir, "still-live.jsonl"), "{}\n", "utf-8");
1071+// Age the orphan transcript past the 5-minute orphan threshold; keep the
1072+// live transcript fresh.
1073+const aged = new Date(Date.now() - 600_000);
1074+await fs.utimes(orphanTranscript, aged, aged);
1075+1076+vi.spyOn(runtimeConfigSnapshotModule, "getRuntimeConfig").mockReturnValue({
1077+session: {},
1078+} as never);
1079+vi.spyOn(sessionStoreRuntimeModule, "resolveStorePath").mockImplementation(((
1080+_store: string | undefined,
1081+{ agentId }: { agentId: string },
1082+) => {
1083+expect(agentId).toBe("main");
1084+return storePath;
1085+}) as typeof sessionStoreRuntimeModule.resolveStorePath);
1086+vi.spyOn(memoryCoreHostRuntimeCoreModule, "resolveStateDir").mockReturnValue(stateDir);
1087+1088+const subagent = createMockSubagent("A forgotten endpoint hummed in the dark.");
1089+const logger = createMockLogger();
1090+1091+await generateAndAppendDreamNarrative({
1092+ subagent,
1093+ workspaceDir,
1094+data: { phase: "light", snippets: ["memory fragment"] },
1095+ logger,
1096+});
1097+1098+const updatedStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
1099+string,
1100+unknown
1101+>;
1102+// The aged orphan dreaming row is reclaimed even though its transcript existed.
1103+expect(updatedStore).not.toHaveProperty("agent:main:dreaming-narrative-deep-orphan");
1104+// The fresh dreaming row and the non-dreaming row survive.
1105+expect(updatedStore).toHaveProperty("agent:main:dreaming-narrative-deep-live");
1106+expect(updatedStore).toHaveProperty("agent:main:kept-session");
1107+1108+const sessionFiles = await fs.readdir(sessionsDir);
1109+// The orphan transcript is archived; the live transcript stays.
1110+expect(
1111+sessionFiles.filter((file) => file.startsWith("orphan-dreaming.jsonl.deleted.")),
1112+).not.toEqual([]);
1113+expect(sessionFiles).not.toContain("orphan-dreaming.jsonl");
1114+expect(sessionFiles).toContain("live-dreaming.jsonl");
1115+expectLogIncludes(logger.info, "dreaming cleanup scrubbed");
1116+});
1117+10411118it("isolates narrative sessions across workspaces even at the same timestamp", async () => {
10421119const firstWorkspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
10431120const secondWorkspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。