mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
fix(memory-core): add dreaming narrative idempotency
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
appendNarrativeEntry,
|
||||
buildBackfillDiaryEntry,
|
||||
@@ -18,6 +18,10 @@ import { createMemoryCoreTestHarness } from "./test-helpers.js";
|
||||
|
||||
const { createTempWorkspace } = createMemoryCoreTestHarness();
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("buildNarrativePrompt", () => {
|
||||
it("builds a prompt from snippets only", () => {
|
||||
const data: NarrativePhaseData = {
|
||||
@@ -312,6 +316,26 @@ describe("appendNarrativeEntry", () => {
|
||||
// Original content should still be there, after the diary.
|
||||
expect(content).toContain("# Existing");
|
||||
});
|
||||
|
||||
it("keeps existing diary content intact when the atomic replace fails", async () => {
|
||||
const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
|
||||
const dreamsPath = path.join(workspaceDir, "DREAMS.md");
|
||||
await fs.writeFile(dreamsPath, "# Existing\n", "utf-8");
|
||||
const renameError = Object.assign(new Error("replace failed"), { code: "ENOSPC" });
|
||||
const renameSpy = vi.spyOn(fs, "rename").mockRejectedValueOnce(renameError);
|
||||
|
||||
await expect(
|
||||
appendNarrativeEntry({
|
||||
workspaceDir,
|
||||
narrative: "Appended dream.",
|
||||
nowMs: Date.parse("2026-04-05T03:00:00Z"),
|
||||
timezone: "UTC",
|
||||
}),
|
||||
).rejects.toThrow("replace failed");
|
||||
|
||||
expect(renameSpy).toHaveBeenCalledOnce();
|
||||
await expect(fs.readFile(dreamsPath, "utf-8")).resolves.toBe("# Existing\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateAndAppendDreamNarrative", () => {
|
||||
@@ -341,6 +365,8 @@ describe("generateAndAppendDreamNarrative", () => {
|
||||
const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
|
||||
const subagent = createMockSubagent("The repository whispered of forgotten endpoints.");
|
||||
const logger = createMockLogger();
|
||||
const nowMs = Date.parse("2026-04-05T03:00:00Z");
|
||||
const expectedSessionKey = `dreaming-narrative-light-${nowMs}`;
|
||||
|
||||
await generateAndAppendDreamNarrative({
|
||||
subagent,
|
||||
@@ -349,13 +375,15 @@ describe("generateAndAppendDreamNarrative", () => {
|
||||
phase: "light",
|
||||
snippets: ["API endpoints need authentication"],
|
||||
},
|
||||
nowMs: Date.parse("2026-04-05T03:00:00Z"),
|
||||
nowMs,
|
||||
timezone: "UTC",
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(subagent.run).toHaveBeenCalledOnce();
|
||||
expect(subagent.run.mock.calls[0][0]).toMatchObject({
|
||||
idempotencyKey: expectedSessionKey,
|
||||
sessionKey: expectedSessionKey,
|
||||
deliver: false,
|
||||
});
|
||||
expect(subagent.waitForRun).toHaveBeenCalledOnce();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
type SubagentSurface = {
|
||||
run: (params: {
|
||||
idempotencyKey: string;
|
||||
sessionKey: string;
|
||||
message: string;
|
||||
extraSystemPrompt?: string;
|
||||
@@ -409,7 +410,7 @@ export async function appendNarrativeEntry(params: {
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(dreamsPath, updated.endsWith("\n") ? updated : `${updated}\n`, "utf-8");
|
||||
await writeDreamsFileAtomic(dreamsPath, updated.endsWith("\n") ? updated : `${updated}\n`);
|
||||
return dreamsPath;
|
||||
}
|
||||
|
||||
@@ -434,6 +435,7 @@ export async function generateAndAppendDreamNarrative(params: {
|
||||
|
||||
try {
|
||||
const { runId } = await params.subagent.run({
|
||||
idempotencyKey: sessionKey,
|
||||
sessionKey,
|
||||
message,
|
||||
extraSystemPrompt: NARRATIVE_SYSTEM_PROMPT,
|
||||
|
||||
Reference in New Issue
Block a user