From fc15c58715f4eafbd258abaeb0e90a715bd67f60 Mon Sep 17 00:00:00 2001 From: xiayu Date: Tue, 23 Jun 2026 07:16:14 +0800 Subject: [PATCH] fix(memory-core): report active dreaming phases in status (#93113) * fix(memory-core): report active dreaming phases in status * fix(memory-core): repair active dreaming status phases --------- Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> --- extensions/memory-core/src/cli.runtime.ts | 24 ++- extensions/memory-core/src/cli.test.ts | 200 ++++++++++++++++++++++ 2 files changed, 218 insertions(+), 6 deletions(-) diff --git a/extensions/memory-core/src/cli.runtime.ts b/extensions/memory-core/src/cli.runtime.ts index 62555129670..077618c7928 100644 --- a/extensions/memory-core/src/cli.runtime.ts +++ b/extensions/memory-core/src/cli.runtime.ts @@ -6,6 +6,7 @@ import path from "node:path"; import type { MemoryEmbeddingProbeResult } from "openclaw/plugin-sdk/memory-core-host-engine-storage"; import { resolveMemoryDreamingConfig, + resolveMemoryLightDreamingConfig, resolveMemoryRemDreamingConfig, } from "openclaw/plugin-sdk/memory-core-host-status"; import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; @@ -223,12 +224,23 @@ async function createHistoricalRemHarnessWorkspace(params: { function formatDreamingSummary(cfg: OpenClawConfig): string { const pluginConfig = resolveMemoryPluginConfig(cfg); - const dreaming = resolveShortTermPromotionDreamingConfig({ pluginConfig, cfg }); - if (!dreaming.enabled) { - return "off"; - } - const timezone = dreaming.timezone ? ` (${dreaming.timezone})` : ""; - return `${dreaming.cron}${timezone} · limit=${dreaming.limit} · minScore=${dreaming.minScore} · minRecallCount=${dreaming.minRecallCount} · minUniqueQueries=${dreaming.minUniqueQueries} · recencyHalfLifeDays=${dreaming.recencyHalfLifeDays} · maxAgeDays=${dreaming.maxAgeDays ?? "none"} · maxPromotedSnippetTokens=${dreaming.maxPromotedSnippetTokens}`; + const light = resolveMemoryLightDreamingConfig({ pluginConfig, cfg }); + const deep = resolveShortTermPromotionDreamingConfig({ pluginConfig, cfg }); + const rem = resolveMemoryRemDreamingConfig({ pluginConfig, cfg }); + const timezone = deep.timezone ?? light.timezone ?? rem.timezone; + const formatCron = (cron: string) => (timezone ? `${cron} (${timezone})` : cron); + const lightSummary = light.enabled + ? `light=${formatCron(light.cron)} · limit=${light.limit} · lookbackDays=${light.lookbackDays}` + : null; + const remSummary = rem.enabled + ? `rem=${formatCron(rem.cron)} · limit=${rem.limit} · lookbackDays=${rem.lookbackDays} · minPatternStrength=${rem.minPatternStrength}` + : null; + const hasLighterPhase = light.enabled || rem.enabled; + const deepLabel = hasLighterPhase ? "deep=" : ""; + const deepDetails = `${formatCron(deep.cron)} · limit=${deep.limit} · minScore=${deep.minScore} · minRecallCount=${deep.minRecallCount} · minUniqueQueries=${deep.minUniqueQueries} · recencyHalfLifeDays=${deep.recencyHalfLifeDays} · maxAgeDays=${deep.maxAgeDays ?? "none"} · maxPromotedSnippetTokens=${deep.maxPromotedSnippetTokens}`; + const deepSummary = deep.enabled ? `${deepLabel}${deepDetails}` : null; + const phases = [lightSummary, remSummary, deepSummary].filter(Boolean); + return phases.length > 0 ? phases.join(" · ") : "off"; } function formatAuditCounts(audit: ShortTermAuditSummary): string { diff --git a/extensions/memory-core/src/cli.test.ts b/extensions/memory-core/src/cli.test.ts index f37b6471c97..48365994fed 100644 --- a/extensions/memory-core/src/cli.test.ts +++ b/extensions/memory-core/src/cli.test.ts @@ -746,6 +746,206 @@ describe("memory cli", () => { }); }); + it("reports light-only dreaming as active during status", async () => { + getRuntimeConfig.mockReturnValue({ + plugins: { + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + frequency: "5 * * * *", + timezone: "UTC", + phases: { + light: { + enabled: true, + limit: 4, + lookbackDays: 2, + }, + deep: { + enabled: false, + }, + rem: { + enabled: false, + }, + }, + }, + }, + }, + }, + }, + }); + const close = vi.fn(async () => {}); + mockManager({ + probeVectorAvailability: vi.fn(async () => true), + status: () => makeMemoryStatus(), + close, + }); + + const log = spyRuntimeLogs(defaultRuntime); + await runMemoryCli(["status"]); + + expectLogged(log, "Dreaming: light=5 * * * * (UTC) · limit=4 · lookbackDays=2"); + expect(close).toHaveBeenCalled(); + }); + + it("reports rem-only dreaming as active during status", async () => { + getRuntimeConfig.mockReturnValue({ + plugins: { + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + frequency: "0 6 * * 0", + timezone: "UTC", + phases: { + light: { + enabled: false, + }, + deep: { + enabled: false, + }, + rem: { + enabled: true, + limit: 3, + lookbackDays: 9, + minPatternStrength: 0.81, + }, + }, + }, + }, + }, + }, + }, + }); + const close = vi.fn(async () => {}); + mockManager({ + probeVectorAvailability: vi.fn(async () => true), + status: () => makeMemoryStatus(), + close, + }); + + const log = spyRuntimeLogs(defaultRuntime); + await runMemoryCli(["status"]); + + expectLogged( + log, + "Dreaming: rem=0 6 * * 0 (UTC) · limit=3 · lookbackDays=9 · minPatternStrength=0.81", + ); + expect(close).toHaveBeenCalled(); + }); + + it("labels deep dreaming when multiple phases are active during status", async () => { + getRuntimeConfig.mockReturnValue({ + plugins: { + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + frequency: "15 2 * * *", + timezone: "UTC", + phases: { + light: { + enabled: true, + limit: 5, + lookbackDays: 1, + }, + deep: { + enabled: true, + limit: 7, + minScore: 0.72, + minRecallCount: 4, + minUniqueQueries: 2, + recencyHalfLifeDays: 10, + maxAgeDays: 45, + maxPromotedSnippetTokens: 512, + }, + rem: { + enabled: true, + limit: 2, + lookbackDays: 14, + minPatternStrength: 0.67, + }, + }, + }, + }, + }, + }, + }, + }); + const close = vi.fn(async () => {}); + mockManager({ + probeVectorAvailability: vi.fn(async () => true), + status: () => makeMemoryStatus(), + close, + }); + + const log = spyRuntimeLogs(defaultRuntime); + await runMemoryCli(["status"]); + + expectLogged(log, "Dreaming: light=15 2 * * * (UTC) · limit=5 · lookbackDays=1"); + expectLogged(log, "rem=15 2 * * * (UTC) · limit=2 · lookbackDays=14 · minPatternStrength=0.67"); + expectLogged(log, "deep=15 2 * * * (UTC) · limit=7 · minScore=0.72"); + expectLogged(log, "minRecallCount=4"); + expectLogged(log, "maxPromotedSnippetTokens=512"); + expect(close).toHaveBeenCalled(); + }); + + it("preserves deep dreaming diagnostics during status", async () => { + getRuntimeConfig.mockReturnValue({ + plugins: { + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + frequency: "0 4 * * *", + timezone: "UTC", + phases: { + light: { + enabled: false, + }, + deep: { + enabled: true, + limit: 6, + minScore: 0.88, + minRecallCount: 5, + minUniqueQueries: 3, + recencyHalfLifeDays: 12, + maxAgeDays: 30, + maxPromotedSnippetTokens: 640, + }, + rem: { + enabled: false, + }, + }, + }, + }, + }, + }, + }, + }); + const close = vi.fn(async () => {}); + mockManager({ + probeVectorAvailability: vi.fn(async () => true), + status: () => makeMemoryStatus(), + close, + }); + + const log = spyRuntimeLogs(defaultRuntime); + await runMemoryCli(["status"]); + + expectLogged(log, "Dreaming: 0 4 * * * (UTC) · limit=6 · minScore=0.88"); + expectLogged(log, "minRecallCount=5"); + expectLogged(log, "minUniqueQueries=3"); + expectLogged(log, "recencyHalfLifeDays=12"); + expectLogged(log, "maxAgeDays=30"); + expectLogged(log, "maxPromotedSnippetTokens=640"); + expect(close).toHaveBeenCalled(); + }); + it("repairs invalid recall metadata and stale locks with status --fix", async () => { await withTempWorkspace(async (workspaceDir) => { await shortTermTesting.writeRawRecallStore(workspaceDir, {