



























@@ -21,6 +21,7 @@ import {
2121formatNarrativeDate,
2222formatBackfillDiaryDate,
2323generateAndAppendDreamNarrative,
24+readRecentDreamDiaryEntries,
2425removeBackfillDiaryEntries,
2526runDetachedDreamNarrative,
2627type NarrativePhaseData,
@@ -133,6 +134,19 @@ describe("buildNarrativePrompt", () => {
133134expect(prompt).toContain("snippet-11");
134135expect(prompt).not.toContain("snippet-12");
135136});
137+138+it("includes current sweep and recent diary context", () => {
139+const prompt = buildNarrativePrompt({
140+phase: "light",
141+snippets: ["Later workspace routing notes surfaced."],
142+currentDate: "April 6, 2026, 9:00 AM UTC",
143+recentDiaryEntries: ["The first meeting memory already filled the page."],
144+});
145+expect(prompt).toContain("Diary continuity context");
146+expect(prompt).toContain("Current sweep: April 6, 2026, 9:00 AM UTC");
147+expect(prompt).toContain("The first meeting memory already filled the page.");
148+expect(prompt).toContain("do not replay the same first-day framing");
149+});
136150});
137151138152describe("extractNarrativeText", () => {
@@ -388,6 +402,77 @@ describe("appendNarrativeEntry", () => {
388402expect(secondIdx).toBeLessThan(end);
389403});
390404405+it("reads recent diary entries without timestamps or markers", async () => {
406+const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
407+await appendNarrativeEntry({
408+ workspaceDir,
409+narrative: "The first meeting memory already filled the page.",
410+nowMs: Date.parse("2026-04-04T03:00:00Z"),
411+timezone: "UTC",
412+});
413+await appendNarrativeEntry({
414+ workspaceDir,
415+narrative: "A later routing note flickered in the margins.",
416+nowMs: Date.parse("2026-04-05T03:00:00Z"),
417+timezone: "UTC",
418+});
419+420+await expect(readRecentDreamDiaryEntries({ workspaceDir, limit: 1 })).resolves.toEqual([
421+"A later routing note flickered in the margins.",
422+]);
423+});
424+425+it("skips symlinked DREAMS.md when building recent diary context", async () => {
426+const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
427+const targetPath = path.join(workspaceDir, "target-dreams.md");
428+const dreamsPath = path.join(workspaceDir, "DREAMS.md");
429+const symlinkTargetDiary = "Symlink target diary text must not enter the prompt.";
430+await fs.writeFile(
431+targetPath,
432+[
433+"# Dream Diary",
434+"",
435+"<!-- openclaw:dreaming:diary:start -->",
436+"---",
437+"",
438+"*April 5, 2026, 3:00 AM UTC*",
439+"",
440+symlinkTargetDiary,
441+"",
442+"<!-- openclaw:dreaming:diary:end -->",
443+"",
444+].join("\n"),
445+"utf-8",
446+);
447+await fs.symlink(targetPath, dreamsPath);
448+449+const entries = await readRecentDreamDiaryEntries({ workspaceDir, limit: 3 });
450+expect(entries).toEqual([]);
451+const prompt = buildNarrativePrompt({
452+phase: "light",
453+snippets: ["A fresh routing memory arrived."],
454+recentDiaryEntries: entries,
455+});
456+expect(prompt).not.toContain(symlinkTargetDiary);
457+});
458+459+it("skips non-file DREAMS.md when reading recent diary context", async () => {
460+const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
461+await fs.mkdir(path.join(workspaceDir, "DREAMS.md"));
462+463+await expect(readRecentDreamDiaryEntries({ workspaceDir, limit: 3 })).resolves.toEqual([]);
464+});
465+466+it("treats unreadable DREAMS.md as empty recent diary context", async () => {
467+const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
468+await fs.writeFile(path.join(workspaceDir, "DREAMS.md"), "unreadable", "utf-8");
469+vi.spyOn(fs, "access").mockRejectedValueOnce(
470+Object.assign(new Error("permission denied"), { code: "EACCES" }),
471+);
472+473+await expect(readRecentDreamDiaryEntries({ workspaceDir, limit: 3 })).resolves.toEqual([]);
474+});
475+391476it("prepends diary before existing managed blocks", async () => {
392477const workspaceDir = await createTempWorkspace("openclaw-dreaming-narrative-");
393478const dreamsPath = path.join(workspaceDir, "DREAMS.md");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。