diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index e2182ed290c..7bc98e4aa89 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -1,6 +1,7 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { registerMemoryCli } from "./src/cli.js"; -import { registerSleepCommand } from "./src/dreaming-command.js"; +import { registerDreamingCommand } from "./src/dreaming-command.js"; +import { registerMemoryDreamingPhases } from "./src/dreaming-phases.js"; import { registerShortTermPromotionDreaming } from "./src/dreaming.js"; import { buildMemoryFlushPlan, @@ -11,7 +12,6 @@ import { import { registerBuiltInMemoryEmbeddingProviders } from "./src/memory/provider-adapters.js"; import { buildPromptSection } from "./src/prompt-section.js"; import { memoryRuntime } from "./src/runtime-provider.js"; -import { registerMemorySleepPhases } from "./src/sleep.js"; import { createMemoryGetTool, createMemorySearchTool } from "./src/tools.js"; export { buildMemoryFlushPlan, @@ -29,8 +29,8 @@ export default definePluginEntry({ register(api) { registerBuiltInMemoryEmbeddingProviders(api); registerShortTermPromotionDreaming(api); - registerMemorySleepPhases(api); - registerSleepCommand(api); + registerMemoryDreamingPhases(api); + registerDreamingCommand(api); api.registerMemoryPromptSection(buildPromptSection); api.registerMemoryFlushPlan(buildMemoryFlushPlan); api.registerMemoryRuntime(memoryRuntime); diff --git a/extensions/memory-core/openclaw.plugin.json b/extensions/memory-core/openclaw.plugin.json index 559b1a113b6..3bf3146a513 100644 --- a/extensions/memory-core/openclaw.plugin.json +++ b/extensions/memory-core/openclaw.plugin.json @@ -2,82 +2,82 @@ "id": "memory-core", "kind": "memory", "uiHints": { - "sleep.timezone": { - "label": "Sleep Timezone", + "dreaming.timezone": { + "label": "Dreaming Timezone", "placeholder": "America/Los_Angeles", - "help": "IANA timezone used for sleep schedules and day bucketing." + "help": "IANA timezone used for dreaming schedules and day bucketing." }, - "sleep.verboseLogging": { - "label": "Sleep Verbose Logging", + "dreaming.verboseLogging": { + "label": "Dreaming Verbose Logging", "placeholder": "false", - "help": "Emit detailed per-run sleep logs for phase scheduling, ranking, and writes." + "help": "Emit detailed per-run dreaming logs for phase scheduling, ranking, and writes." }, - "sleep.storage.mode": { - "label": "Sleep Storage Mode", + "dreaming.storage.mode": { + "label": "Dreaming Storage Mode", "placeholder": "inline", "help": "Write inline to MEMORY.md and daily notes, to separate reports, or both." }, - "sleep.phases.light.cron": { + "dreaming.phases.light.cron": { "label": "Light Sleep Cron", "placeholder": "0 3 * * *", "help": "Cron cadence for light sleep sorting runs." }, - "sleep.phases.light.lookbackDays": { + "dreaming.phases.light.lookbackDays": { "label": "Light Sleep Lookback Days", "placeholder": "2", "help": "How many prior days light sleep may inspect." }, - "sleep.phases.light.limit": { + "dreaming.phases.light.limit": { "label": "Light Sleep Limit", "placeholder": "100", "help": "Maximum staged light-sleep candidates per run." }, - "sleep.phases.deep.cron": { + "dreaming.phases.deep.cron": { "label": "Deep Sleep Cron", "placeholder": "0 3 * * *", "help": "Cron cadence for deep sleep promotion and recovery runs." }, - "sleep.phases.deep.limit": { + "dreaming.phases.deep.limit": { "label": "Deep Sleep Limit", "placeholder": "10", "help": "Maximum candidates promoted into MEMORY.md per deep-sleep run." }, - "sleep.phases.deep.minScore": { + "dreaming.phases.deep.minScore": { "label": "Deep Sleep Min Score", "placeholder": "0.8", "help": "Minimum weighted rank required for durable promotion." }, - "sleep.phases.deep.minRecallCount": { + "dreaming.phases.deep.minRecallCount": { "label": "Deep Sleep Min Recalls", "placeholder": "3", "help": "Minimum recall count required for durable promotion." }, - "sleep.phases.deep.minUniqueQueries": { + "dreaming.phases.deep.minUniqueQueries": { "label": "Deep Sleep Min Queries", "placeholder": "3", "help": "Minimum unique query count required for durable promotion." }, - "sleep.phases.deep.recencyHalfLifeDays": { + "dreaming.phases.deep.recencyHalfLifeDays": { "label": "Deep Sleep Recency Half-Life Days", "placeholder": "14", "help": "Days for the recency score to decay by half during deep-sleep ranking." }, - "sleep.phases.deep.maxAgeDays": { + "dreaming.phases.deep.maxAgeDays": { "label": "Deep Sleep Max Age Days", "placeholder": "30", "help": "Optional maximum candidate age in days for deep-sleep promotion." }, - "sleep.phases.deep.recovery.lookbackDays": { + "dreaming.phases.deep.recovery.lookbackDays": { "label": "Recovery Lookback Days", "placeholder": "30", "help": "How many prior days deep sleep may scan when memory recovery is triggered." }, - "sleep.phases.rem.cron": { + "dreaming.phases.rem.cron": { "label": "REM Sleep Cron", "placeholder": "0 5 * * 0", "help": "Cron cadence for REM sleep reflection runs." }, - "sleep.phases.rem.lookbackDays": { + "dreaming.phases.rem.lookbackDays": { "label": "REM Sleep Lookback Days", "placeholder": "7", "help": "How many prior days REM sleep may inspect for patterns." @@ -87,7 +87,7 @@ "type": "object", "additionalProperties": false, "properties": { - "sleep": { + "dreaming": { "type": "object", "additionalProperties": false, "properties": { diff --git a/extensions/memory-core/src/cli.runtime.ts b/extensions/memory-core/src/cli.runtime.ts index 3944841b590..ec1b7f13388 100644 --- a/extensions/memory-core/src/cli.runtime.ts +++ b/extensions/memory-core/src/cli.runtime.ts @@ -114,7 +114,7 @@ function resolveMemoryPluginConfig(cfg: OpenClawConfig): Record return asRecord(entry?.config) ?? {}; } -function formatSleepSummary(cfg: OpenClawConfig): string { +function formatDreamingSummary(cfg: OpenClawConfig): string { const pluginConfig = resolveMemoryPluginConfig(cfg); const dreaming = resolveShortTermPromotionDreamingConfig({ pluginConfig, cfg }); if (!dreaming.enabled) { @@ -577,7 +577,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { `${label("Dirty")} ${status.dirty ? warn("yes") : muted("no")}`, `${label("Store")} ${info(storePath)}`, `${label("Workspace")} ${info(workspacePath)}`, - `${label("Sleep")} ${info(formatSleepSummary(cfg))}`, + `${label("Dreaming")} ${info(formatDreamingSummary(cfg))}`, ].filter(Boolean) as string[]; if (embeddingProbe) { const state = embeddingProbe.ok ? "ready" : "unavailable"; diff --git a/extensions/memory-core/src/cli.test.ts b/extensions/memory-core/src/cli.test.ts index bff51a4742c..5c427658018 100644 --- a/extensions/memory-core/src/cli.test.ts +++ b/extensions/memory-core/src/cli.test.ts @@ -367,7 +367,7 @@ describe("memory cli", () => { await runMemoryCli(["status"]); expect(log).toHaveBeenCalledWith(expect.stringContaining("Recall store: 1 entries")); - expect(log).toHaveBeenCalledWith(expect.stringContaining("Sleep: 0 3 * * *")); + expect(log).toHaveBeenCalledWith(expect.stringContaining("Dreaming: 0 3 * * *")); expect(close).toHaveBeenCalled(); }); }); diff --git a/extensions/memory-core/src/dreaming-command.test.ts b/extensions/memory-core/src/dreaming-command.test.ts index a7b3417eebf..3b5fb403c04 100644 --- a/extensions/memory-core/src/dreaming-command.test.ts +++ b/extensions/memory-core/src/dreaming-command.test.ts @@ -4,7 +4,7 @@ import type { } from "openclaw/plugin-sdk/core"; import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core"; import { describe, expect, it, vi } from "vitest"; -import { registerSleepCommand } from "./dreaming-command.js"; +import { registerDreamingCommand } from "./dreaming-command.js"; function asRecord(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { @@ -13,10 +13,10 @@ function asRecord(value: unknown): Record | null { return value as Record; } -function resolveStoredSleep(config: OpenClawConfig): Record { +function resolveStoredDreaming(config: OpenClawConfig): Record { const entry = asRecord(config.plugins?.entries?.["memory-core"]); const pluginConfig = asRecord(entry?.config); - return asRecord(pluginConfig?.sleep) ?? {}; + return asRecord(pluginConfig?.dreaming) ?? {}; } function createHarness(initialConfig: OpenClawConfig = {}) { @@ -39,10 +39,10 @@ function createHarness(initialConfig: OpenClawConfig = {}) { }), } as unknown as OpenClawPluginApi; - registerSleepCommand(api); + registerDreamingCommand(api); if (!command) { - throw new Error("memory-core did not register /sleep"); + throw new Error("memory-core did not register /dreaming"); } return { @@ -56,7 +56,7 @@ function createCommandContext(args?: string): PluginCommandContext { return { channel: "webchat", isAuthorizedSender: true, - commandBody: args ? `/sleep ${args}` : "/sleep", + commandBody: args ? `/dreaming ${args}` : "/dreaming", args, config: {}, requestConversationBinding: async () => ({ status: "error", message: "unsupported" }), @@ -65,20 +65,20 @@ function createCommandContext(args?: string): PluginCommandContext { }; } -describe("memory-core /sleep command", () => { +describe("memory-core /dreaming command", () => { it("registers with a phase-oriented description", () => { const { command } = createHarness(); - expect(command.name).toBe("sleep"); + expect(command.name).toBe("dreaming"); expect(command.acceptsArgs).toBe(true); - expect(command.description).toContain("sleep phases"); + expect(command.description).toContain("dreaming phases"); }); it("shows phase explanations when invoked without args", async () => { const { command } = createHarness(); const result = await command.handler(createCommandContext()); - expect(result.text).toContain("Usage: /sleep status"); - expect(result.text).toContain("Sleep status:"); + expect(result.text).toContain("Usage: /dreaming status"); + expect(result.text).toContain("Dreaming status:"); expect(result.text).toContain("- light: sorts recent memory traces into the daily note."); expect(result.text).toContain( "- deep: promotes durable memories into MEMORY.md and handles recovery when memory is thin.", @@ -88,13 +88,13 @@ describe("memory-core /sleep command", () => { ); }); - it("persists global enablement under plugins.entries.memory-core.config.sleep.enabled", async () => { + it("persists global enablement under plugins.entries.memory-core.config.dreaming.enabled", async () => { const { command, runtime, getRuntimeConfig } = createHarness({ plugins: { entries: { "memory-core": { config: { - sleep: { + dreaming: { phases: { deep: { minScore: 0.9, @@ -110,7 +110,7 @@ describe("memory-core /sleep command", () => { const result = await command.handler(createCommandContext("off")); expect(runtime.config.writeConfigFile).toHaveBeenCalledTimes(1); - expect(resolveStoredSleep(getRuntimeConfig())).toMatchObject({ + expect(resolveStoredDreaming(getRuntimeConfig())).toMatchObject({ enabled: false, phases: { deep: { @@ -118,23 +118,23 @@ describe("memory-core /sleep command", () => { }, }, }); - expect(result.text).toContain("Sleep disabled."); + expect(result.text).toContain("Dreaming disabled."); }); - it("persists phase changes under plugins.entries.memory-core.config.sleep.phases", async () => { + it("persists phase changes under plugins.entries.memory-core.config.dreaming.phases", async () => { const { command, runtime, getRuntimeConfig } = createHarness(); const result = await command.handler(createCommandContext("disable rem")); expect(runtime.config.writeConfigFile).toHaveBeenCalledTimes(1); - expect(resolveStoredSleep(getRuntimeConfig())).toMatchObject({ + expect(resolveStoredDreaming(getRuntimeConfig())).toMatchObject({ phases: { rem: { enabled: false, }, }, }); - expect(result.text).toContain("REM sleep disabled."); + expect(result.text).toContain("REM phase disabled."); }); it("returns status without mutating config", async () => { @@ -143,7 +143,7 @@ describe("memory-core /sleep command", () => { entries: { "memory-core": { config: { - sleep: { + dreaming: { timezone: "America/Los_Angeles", storage: { mode: "both", @@ -164,7 +164,7 @@ describe("memory-core /sleep command", () => { const result = await command.handler(createCommandContext("status")); - expect(result.text).toContain("Sleep status:"); + expect(result.text).toContain("Dreaming status:"); expect(result.text).toContain("- enabled: on (America/Los_Angeles)"); expect(result.text).toContain("- storage: both + reports"); expect(result.text).toContain("recencyHalfLifeDays=21"); @@ -176,7 +176,7 @@ describe("memory-core /sleep command", () => { const { command, runtime } = createHarness(); const result = await command.handler(createCommandContext("unknown-mode")); - expect(result.text).toContain("Usage: /sleep status"); + expect(result.text).toContain("Usage: /dreaming status"); expect(runtime.config.writeConfigFile).not.toHaveBeenCalled(); }); }); diff --git a/extensions/memory-core/src/dreaming-command.ts b/extensions/memory-core/src/dreaming-command.ts index 6e427ec0505..81aa4f565dc 100644 --- a/extensions/memory-core/src/dreaming-command.ts +++ b/extensions/memory-core/src/dreaming-command.ts @@ -1,12 +1,12 @@ import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core"; import { - resolveMemoryLightSleepConfig, - resolveMemoryRemSleepConfig, - resolveMemorySleepConfig, + resolveMemoryLightDreamingConfig, + resolveMemoryRemDreamingConfig, + resolveMemoryDreamingConfig, } from "openclaw/plugin-sdk/memory-core-host-status"; import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js"; -type SleepPhaseName = "light" | "deep" | "rem"; +type DreamingPhaseName = "light" | "deep" | "rem"; function asRecord(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { @@ -15,7 +15,7 @@ function asRecord(value: unknown): Record | null { return value as Record; } -function normalizeSleepPhase(value: unknown): SleepPhaseName | null { +function normalizeDreamingPhase(value: unknown): DreamingPhaseName | null { if (typeof value !== "string") { return null; } @@ -31,16 +31,16 @@ function resolveMemoryCorePluginConfig(cfg: OpenClawConfig): Record { const args = ctx.args?.trim() ?? ""; @@ -169,23 +169,25 @@ export function registerSleepCommand(api: OpenClawPluginApi): void { if (firstToken === "on" || firstToken === "off") { const enabled = firstToken === "on"; - const nextConfig = updateSleepEnabledInConfig(currentConfig, enabled); - await api.runtime.config.writeConfigFile(nextConfig); - return { - text: [`Sleep ${enabled ? "enabled" : "disabled"}.`, "", formatStatus(nextConfig)].join( - "\n", - ), - }; - } - - const phase = normalizeSleepPhase(secondToken); - if ((firstToken === "enable" || firstToken === "disable") && phase) { - const enabled = firstToken === "enable"; - const nextConfig = updateSleepPhaseEnabledInConfig(currentConfig, phase, enabled); + const nextConfig = updateDreamingEnabledInConfig(currentConfig, enabled); await api.runtime.config.writeConfigFile(nextConfig); return { text: [ - `${phase.toUpperCase()} sleep ${enabled ? "enabled" : "disabled"}.`, + `Dreaming ${enabled ? "enabled" : "disabled"}.`, + "", + formatStatus(nextConfig), + ].join("\n"), + }; + } + + const phase = normalizeDreamingPhase(secondToken); + if ((firstToken === "enable" || firstToken === "disable") && phase) { + const enabled = firstToken === "enable"; + const nextConfig = updateDreamingPhaseEnabledInConfig(currentConfig, phase, enabled); + await api.runtime.config.writeConfigFile(nextConfig); + return { + text: [ + `${phase.toUpperCase()} phase ${enabled ? "enabled" : "disabled"}.`, "", formatStatus(nextConfig), ].join("\n"), @@ -196,5 +198,3 @@ export function registerSleepCommand(api: OpenClawPluginApi): void { }, }); } - -export const registerDreamingCommand = registerSleepCommand; diff --git a/extensions/memory-core/src/dreaming.test.ts b/extensions/memory-core/src/dreaming.test.ts index c75c359148f..e28b47fe3ef 100644 --- a/extensions/memory-core/src/dreaming.test.ts +++ b/extensions/memory-core/src/dreaming.test.ts @@ -140,7 +140,7 @@ describe("short-term dreaming config", () => { it("reads explicit dreaming config values", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { timezone: "UTC", verboseLogging: true, phases: { @@ -178,7 +178,7 @@ describe("short-term dreaming config", () => { it("accepts cron alias and numeric string thresholds", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { phases: { deep: { cron: "5 1 * * *", @@ -213,7 +213,7 @@ describe("short-term dreaming config", () => { it("treats blank numeric strings as unset and keeps preset defaults", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { phases: { deep: { limit: " ", @@ -247,7 +247,7 @@ describe("short-term dreaming config", () => { it("accepts limit=0 as an explicit no-op promotion cap", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { phases: { deep: { limit: 0, @@ -262,14 +262,14 @@ describe("short-term dreaming config", () => { it("accepts verboseLogging as a boolean or boolean string", () => { const enabled = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { verboseLogging: true, }, }, }); const disabled = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { verboseLogging: "false", }, }, @@ -282,7 +282,7 @@ describe("short-term dreaming config", () => { it("falls back to defaults when thresholds are negative", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { phases: { deep: { minScore: -0.2, @@ -308,7 +308,7 @@ describe("short-term dreaming config", () => { it("keeps deep sleep disabled when the phase is off", () => { const resolved = resolveShortTermPromotionDreamingConfig({ pluginConfig: { - sleep: { + dreaming: { phases: { deep: { enabled: false, diff --git a/extensions/memory-core/src/dreaming.ts b/extensions/memory-core/src/dreaming.ts index b04d8b81a0b..1fa4e57a6a4 100644 --- a/extensions/memory-core/src/dreaming.ts +++ b/extensions/memory-core/src/dreaming.ts @@ -1,21 +1,21 @@ import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core"; import { - DEFAULT_MEMORY_DEEP_SLEEP_CRON_EXPR as DEFAULT_MEMORY_DREAMING_CRON_EXPR, - DEFAULT_MEMORY_DEEP_SLEEP_LIMIT as DEFAULT_MEMORY_DREAMING_LIMIT, - DEFAULT_MEMORY_DEEP_SLEEP_MIN_RECALL_COUNT as DEFAULT_MEMORY_DREAMING_MIN_RECALL_COUNT, - DEFAULT_MEMORY_DEEP_SLEEP_MIN_SCORE as DEFAULT_MEMORY_DREAMING_MIN_SCORE, - DEFAULT_MEMORY_DEEP_SLEEP_MIN_UNIQUE_QUERIES as DEFAULT_MEMORY_DREAMING_MIN_UNIQUE_QUERIES, - DEFAULT_MEMORY_DEEP_SLEEP_RECENCY_HALF_LIFE_DAYS as DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS, + DEFAULT_MEMORY_DEEP_DREAMING_CRON_EXPR as DEFAULT_MEMORY_DREAMING_CRON_EXPR, + DEFAULT_MEMORY_DEEP_DREAMING_LIMIT as DEFAULT_MEMORY_DREAMING_LIMIT, + DEFAULT_MEMORY_DEEP_DREAMING_MIN_RECALL_COUNT as DEFAULT_MEMORY_DREAMING_MIN_RECALL_COUNT, + DEFAULT_MEMORY_DEEP_DREAMING_MIN_SCORE as DEFAULT_MEMORY_DREAMING_MIN_SCORE, + DEFAULT_MEMORY_DEEP_DREAMING_MIN_UNIQUE_QUERIES as DEFAULT_MEMORY_DREAMING_MIN_UNIQUE_QUERIES, + DEFAULT_MEMORY_DEEP_DREAMING_RECENCY_HALF_LIFE_DAYS as DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS, resolveMemoryCorePluginConfig, - resolveMemoryDeepSleepConfig, - resolveMemorySleepWorkspaces, + resolveMemoryDeepDreamingConfig, + resolveMemoryDreamingWorkspaces, } from "openclaw/plugin-sdk/memory-core-host-status"; +import { writeDeepDreamingReport } from "./dreaming-markdown.js"; import { applyShortTermPromotions, repairShortTermPromotionArtifacts, rankShortTermPromotionCandidates, } from "./short-term-promotion.js"; -import { writeDeepSleepReport } from "./sleep-markdown.js"; const MANAGED_DREAMING_CRON_NAME = "Memory Dreaming Promotion"; const MANAGED_DREAMING_CRON_TAG = "[managed-by=memory-core.short-term-promotion]"; @@ -267,7 +267,7 @@ export function resolveShortTermPromotionDreamingConfig(params: { pluginConfig?: Record; cfg?: OpenClawConfig; }): ShortTermPromotionDreamingConfig { - const resolved = resolveMemoryDeepSleepConfig(params); + const resolved = resolveMemoryDeepDreamingConfig(params); return { enabled: resolved.enabled, cron: resolved.cron, @@ -372,7 +372,7 @@ export async function runShortTermDreamingPromotionIfTriggered(params: { const recencyHalfLifeDays = params.config.recencyHalfLifeDays ?? DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS; const workspaceCandidates = params.cfg - ? resolveMemorySleepWorkspaces(params.cfg).map((entry) => entry.workspaceDir) + ? resolveMemoryDreamingWorkspaces(params.cfg).map((entry) => entry.workspaceDir) : []; const seenWorkspaces = new Set(); const workspaces = workspaceCandidates.filter((workspaceDir) => { @@ -467,7 +467,7 @@ export async function runShortTermDreamingPromotionIfTriggered(params: { `memory-core: dreaming applied details [workspace=${workspaceDir}] ${appliedSummary}`, ); } - await writeDeepSleepReport({ + await writeDeepDreamingReport({ workspaceDir, bodyLines: reportLines, timezone: params.config.timezone, diff --git a/extensions/memory-core/src/short-term-promotion.ts b/extensions/memory-core/src/short-term-promotion.ts index b2ed92c89bb..27a09ffbeab 100644 --- a/extensions/memory-core/src/short-term-promotion.ts +++ b/extensions/memory-core/src/short-term-promotion.ts @@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files"; -import { formatMemorySleepDay } from "openclaw/plugin-sdk/memory-core-host-status"; +import { formatMemoryDreamingDay } from "openclaw/plugin-sdk/memory-core-host-status"; import { deriveConceptTags, MAX_CONCEPT_TAGS, @@ -605,7 +605,7 @@ export async function recordShortTermRecalls(params: { const queryHashes = mergeQueryHashes(existing?.queryHashes ?? [], queryHash); const recallDays = mergeRecentDistinct( existing?.recallDays ?? [], - formatMemorySleepDay(nowMs, params.timezone), + formatMemoryDreamingDay(nowMs, params.timezone), MAX_RECALL_DAYS, ); const conceptTags = deriveConceptTags({ path: normalizedPath, snippet }); @@ -929,7 +929,7 @@ function buildPromotionSection( nowMs: number, timezone?: string, ): string { - const sectionDate = formatMemorySleepDay(nowMs, timezone); + const sectionDate = formatMemoryDreamingDay(nowMs, timezone); const lines = ["", `## Promoted From Short-Term Memory (${sectionDate})`, ""]; for (const candidate of candidates) { diff --git a/extensions/memory-core/src/sleep-markdown.ts b/extensions/memory-core/src/sleep-markdown.ts deleted file mode 100644 index 45e32d2ed82..00000000000 --- a/extensions/memory-core/src/sleep-markdown.ts +++ /dev/null @@ -1,155 +0,0 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { - formatMemorySleepDay, - type MemorySleepPhaseName, - type MemorySleepStorageConfig, -} from "openclaw/plugin-sdk/memory-core-host-status"; - -const DAILY_PHASE_HEADINGS: Record, string> = { - light: "## Light Sleep", - rem: "## REM Sleep", -}; - -const DAILY_PHASE_LABELS: Record, string> = { - light: "light", - rem: "rem", -}; - -function resolvePhaseMarkers(phase: Exclude): { - start: string; - end: string; -} { - const label = DAILY_PHASE_LABELS[phase]; - return { - start: ``, - end: ``, - }; -} - -function withTrailingNewline(content: string): string { - return content.endsWith("\n") ? content : `${content}\n`; -} - -function replaceManagedBlock(params: { - original: string; - heading: string; - startMarker: string; - endMarker: string; - body: string; -}): string { - const managedBlock = `${params.heading}\n${params.startMarker}\n${params.body}\n${params.endMarker}`; - const existingPattern = new RegExp( - `${escapeRegex(params.heading)}\\n${escapeRegex(params.startMarker)}[\\s\\S]*?${escapeRegex(params.endMarker)}`, - "m", - ); - if (existingPattern.test(params.original)) { - return params.original.replace(existingPattern, managedBlock); - } - const trimmed = params.original.trimEnd(); - if (trimmed.length === 0) { - return `${managedBlock}\n`; - } - return `${trimmed}\n\n${managedBlock}\n`; -} - -function escapeRegex(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -function resolveDailyMemoryPath(workspaceDir: string, epochMs: number, timezone?: string): string { - const isoDay = formatMemorySleepDay(epochMs, timezone); - return path.join(workspaceDir, "memory", `${isoDay}.md`); -} - -function resolveSeparateReportPath( - workspaceDir: string, - phase: MemorySleepPhaseName, - epochMs: number, - timezone?: string, -): string { - const isoDay = formatMemorySleepDay(epochMs, timezone); - return path.join(workspaceDir, "memory", "sleep", phase, `${isoDay}.md`); -} - -function shouldWriteInline(storage: MemorySleepStorageConfig): boolean { - return storage.mode === "inline" || storage.mode === "both"; -} - -function shouldWriteSeparate(storage: MemorySleepStorageConfig): boolean { - return storage.mode === "separate" || storage.mode === "both" || storage.separateReports; -} - -export async function writeDailySleepPhaseBlock(params: { - workspaceDir: string; - phase: Exclude; - bodyLines: string[]; - nowMs?: number; - timezone?: string; - storage: MemorySleepStorageConfig; -}): Promise<{ inlinePath?: string; reportPath?: string }> { - const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now(); - const body = params.bodyLines.length > 0 ? params.bodyLines.join("\n") : "- No notable updates."; - let inlinePath: string | undefined; - let reportPath: string | undefined; - - if (shouldWriteInline(params.storage)) { - inlinePath = resolveDailyMemoryPath(params.workspaceDir, nowMs, params.timezone); - await fs.mkdir(path.dirname(inlinePath), { recursive: true }); - const original = await fs.readFile(inlinePath, "utf-8").catch((err: unknown) => { - if ((err as NodeJS.ErrnoException)?.code === "ENOENT") { - return ""; - } - throw err; - }); - const markers = resolvePhaseMarkers(params.phase); - const updated = replaceManagedBlock({ - original, - heading: DAILY_PHASE_HEADINGS[params.phase], - startMarker: markers.start, - endMarker: markers.end, - body, - }); - await fs.writeFile(inlinePath, withTrailingNewline(updated), "utf-8"); - } - - if (shouldWriteSeparate(params.storage)) { - reportPath = resolveSeparateReportPath( - params.workspaceDir, - params.phase, - nowMs, - params.timezone, - ); - await fs.mkdir(path.dirname(reportPath), { recursive: true }); - const report = [ - `# ${params.phase === "light" ? "Light Sleep" : "REM Sleep"}`, - "", - body, - "", - ].join("\n"); - await fs.writeFile(reportPath, report, "utf-8"); - } - - return { - ...(inlinePath ? { inlinePath } : {}), - ...(reportPath ? { reportPath } : {}), - }; -} - -export async function writeDeepSleepReport(params: { - workspaceDir: string; - bodyLines: string[]; - nowMs?: number; - timezone?: string; - storage: MemorySleepStorageConfig; -}): Promise { - if (!shouldWriteSeparate(params.storage)) { - return undefined; - } - const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now(); - const reportPath = resolveSeparateReportPath(params.workspaceDir, "deep", nowMs, params.timezone); - await fs.mkdir(path.dirname(reportPath), { recursive: true }); - const body = params.bodyLines.length > 0 ? params.bodyLines.join("\n") : "- No durable changes."; - await fs.writeFile(reportPath, `# Deep Sleep\n\n${body}\n`, "utf-8"); - return reportPath; -} diff --git a/extensions/memory-core/src/sleep.ts b/extensions/memory-core/src/sleep.ts deleted file mode 100644 index 481621d35c4..00000000000 --- a/extensions/memory-core/src/sleep.ts +++ /dev/null @@ -1,657 +0,0 @@ -import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core"; -import { - resolveMemoryCorePluginConfig, - resolveMemoryLightSleepConfig, - resolveMemoryRemSleepConfig, - resolveMemorySleepWorkspaces, - type MemoryLightSleepConfig, - type MemoryRemSleepConfig, - type MemorySleepPhaseName, -} from "openclaw/plugin-sdk/memory-core-host-status"; -import { readShortTermRecallEntries, type ShortTermRecallEntry } from "./short-term-promotion.js"; -import { writeDailySleepPhaseBlock } from "./sleep-markdown.js"; - -type Logger = Pick; - -type CronSchedule = { kind: "cron"; expr: string; tz?: string }; -type CronPayload = { kind: "systemEvent"; text: string }; -type ManagedCronJobCreate = { - name: string; - description: string; - enabled: boolean; - schedule: CronSchedule; - sessionTarget: "main"; - wakeMode: "next-heartbeat"; - payload: CronPayload; -}; - -type ManagedCronJobPatch = { - name?: string; - description?: string; - enabled?: boolean; - schedule?: CronSchedule; - sessionTarget?: "main"; - wakeMode?: "next-heartbeat"; - payload?: CronPayload; -}; - -type ManagedCronJobLike = { - id: string; - name?: string; - description?: string; - enabled?: boolean; - schedule?: { - kind?: string; - expr?: string; - tz?: string; - }; - sessionTarget?: string; - wakeMode?: string; - payload?: { - kind?: string; - text?: string; - }; - createdAtMs?: number; -}; - -type CronServiceLike = { - list: (opts?: { includeDisabled?: boolean }) => Promise; - add: (input: ManagedCronJobCreate) => Promise; - update: (id: string, patch: ManagedCronJobPatch) => Promise; - remove: (id: string) => Promise<{ removed?: boolean }>; -}; - -const LIGHT_SLEEP_CRON_NAME = "Memory Light Sleep"; -const LIGHT_SLEEP_CRON_TAG = "[managed-by=memory-core.sleep.light]"; -const LIGHT_SLEEP_EVENT_TEXT = "__openclaw_memory_core_light_sleep__"; - -const REM_SLEEP_CRON_NAME = "Memory REM Sleep"; -const REM_SLEEP_CRON_TAG = "[managed-by=memory-core.sleep.rem]"; -const REM_SLEEP_EVENT_TEXT = "__openclaw_memory_core_rem_sleep__"; - -function asRecord(value: unknown): Record | null { - if (!value || typeof value !== "object" || Array.isArray(value)) { - return null; - } - return value as Record; -} - -function normalizeTrimmedString(value: unknown): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : undefined; -} - -function formatErrorMessage(err: unknown): string { - if (err instanceof Error) { - return err.message; - } - return String(err); -} - -function buildCronDescription(params: { - tag: string; - phase: "light" | "rem"; - cron: string; - limit: number; - lookbackDays: number; -}): string { - return `${params.tag} Run ${params.phase} sleep (cron=${params.cron}, limit=${params.limit}, lookbackDays=${params.lookbackDays}).`; -} - -function buildManagedCronJob(params: { - name: string; - tag: string; - payloadText: string; - cron: string; - timezone?: string; - phase: "light" | "rem"; - limit: number; - lookbackDays: number; -}): ManagedCronJobCreate { - return { - name: params.name, - description: buildCronDescription({ - tag: params.tag, - phase: params.phase, - cron: params.cron, - limit: params.limit, - lookbackDays: params.lookbackDays, - }), - enabled: true, - schedule: { - kind: "cron", - expr: params.cron, - ...(params.timezone ? { tz: params.timezone } : {}), - }, - sessionTarget: "main", - wakeMode: "next-heartbeat", - payload: { - kind: "systemEvent", - text: params.payloadText, - }, - }; -} - -function isManagedPhaseJob( - job: ManagedCronJobLike, - params: { - name: string; - tag: string; - payloadText: string; - }, -): boolean { - const description = normalizeTrimmedString(job.description); - if (description?.includes(params.tag)) { - return true; - } - const name = normalizeTrimmedString(job.name); - const payloadText = normalizeTrimmedString(job.payload?.text); - return name === params.name && payloadText === params.payloadText; -} - -function buildManagedPhasePatch( - job: ManagedCronJobLike, - desired: ManagedCronJobCreate, -): ManagedCronJobPatch | null { - const patch: ManagedCronJobPatch = {}; - const scheduleKind = normalizeTrimmedString(job.schedule?.kind)?.toLowerCase(); - const scheduleExpr = normalizeTrimmedString(job.schedule?.expr); - const scheduleTz = normalizeTrimmedString(job.schedule?.tz); - if (normalizeTrimmedString(job.name) !== desired.name) { - patch.name = desired.name; - } - if (normalizeTrimmedString(job.description) !== desired.description) { - patch.description = desired.description; - } - if (job.enabled !== true) { - patch.enabled = true; - } - if ( - scheduleKind !== "cron" || - scheduleExpr !== desired.schedule.expr || - scheduleTz !== desired.schedule.tz - ) { - patch.schedule = desired.schedule; - } - if (normalizeTrimmedString(job.sessionTarget)?.toLowerCase() !== "main") { - patch.sessionTarget = "main"; - } - if (normalizeTrimmedString(job.wakeMode)?.toLowerCase() !== "next-heartbeat") { - patch.wakeMode = "next-heartbeat"; - } - const payloadKind = normalizeTrimmedString(job.payload?.kind)?.toLowerCase(); - const payloadText = normalizeTrimmedString(job.payload?.text); - if (payloadKind !== "systemevent" || payloadText !== desired.payload.text) { - patch.payload = desired.payload; - } - return Object.keys(patch).length > 0 ? patch : null; -} - -function sortManagedJobs(managed: ManagedCronJobLike[]): ManagedCronJobLike[] { - return managed.toSorted((a, b) => { - const aCreated = - typeof a.createdAtMs === "number" && Number.isFinite(a.createdAtMs) - ? a.createdAtMs - : Number.MAX_SAFE_INTEGER; - const bCreated = - typeof b.createdAtMs === "number" && Number.isFinite(b.createdAtMs) - ? b.createdAtMs - : Number.MAX_SAFE_INTEGER; - if (aCreated !== bCreated) { - return aCreated - bCreated; - } - return a.id.localeCompare(b.id); - }); -} - -function resolveCronServiceFromStartupEvent(event: unknown): CronServiceLike | null { - const payload = asRecord(event); - if (!payload || payload.type !== "gateway" || payload.action !== "startup") { - return null; - } - const context = asRecord(payload.context); - const deps = asRecord(context?.deps); - const cronCandidate = context?.cron ?? deps?.cron; - if (!cronCandidate || typeof cronCandidate !== "object") { - return null; - } - const cron = cronCandidate as Partial; - if ( - typeof cron.list !== "function" || - typeof cron.add !== "function" || - typeof cron.update !== "function" || - typeof cron.remove !== "function" - ) { - return null; - } - return cron as CronServiceLike; -} - -async function reconcileManagedPhaseCronJob(params: { - cron: CronServiceLike | null; - desired: ManagedCronJobCreate; - match: { name: string; tag: string; payloadText: string }; - enabled: boolean; - logger: Logger; -}): Promise { - const cron = params.cron; - if (!cron) { - return; - } - const allJobs = await cron.list({ includeDisabled: true }); - const managed = allJobs.filter((job) => isManagedPhaseJob(job, params.match)); - if (!params.enabled) { - for (const job of managed) { - try { - await cron.remove(job.id); - } catch (err) { - params.logger.warn( - `memory-core: failed to remove managed ${params.match.name} cron job ${job.id}: ${formatErrorMessage(err)}`, - ); - } - } - return; - } - - if (managed.length === 0) { - await cron.add(params.desired); - return; - } - - const [primary, ...duplicates] = sortManagedJobs(managed); - for (const duplicate of duplicates) { - try { - await cron.remove(duplicate.id); - } catch (err) { - params.logger.warn( - `memory-core: failed to prune duplicate managed ${params.match.name} cron job ${duplicate.id}: ${formatErrorMessage(err)}`, - ); - } - } - - const patch = buildManagedPhasePatch(primary, params.desired); - if (patch) { - await cron.update(primary.id, patch); - } -} - -function resolveWorkspaces(params: { - cfg?: OpenClawConfig; - fallbackWorkspaceDir?: string; -}): string[] { - const workspaceCandidates = params.cfg - ? resolveMemorySleepWorkspaces(params.cfg).map((entry) => entry.workspaceDir) - : []; - const seen = new Set(); - const workspaces = workspaceCandidates.filter((workspaceDir) => { - if (seen.has(workspaceDir)) { - return false; - } - seen.add(workspaceDir); - return true; - }); - const fallbackWorkspaceDir = normalizeTrimmedString(params.fallbackWorkspaceDir); - if (workspaces.length === 0 && fallbackWorkspaceDir) { - workspaces.push(fallbackWorkspaceDir); - } - return workspaces; -} - -function calculateLookbackCutoffMs(nowMs: number, lookbackDays: number): number { - return nowMs - Math.max(0, lookbackDays) * 24 * 60 * 60 * 1000; -} - -function entryAverageScore(entry: ShortTermRecallEntry): number { - return entry.recallCount > 0 ? Math.max(0, Math.min(1, entry.totalScore / entry.recallCount)) : 0; -} - -function tokenizeSnippet(snippet: string): Set { - return new Set( - snippet - .toLowerCase() - .split(/[^a-z0-9]+/i) - .map((token) => token.trim()) - .filter(Boolean), - ); -} - -function jaccardSimilarity(left: string, right: string): number { - const leftTokens = tokenizeSnippet(left); - const rightTokens = tokenizeSnippet(right); - if (leftTokens.size === 0 || rightTokens.size === 0) { - return left.trim().toLowerCase() === right.trim().toLowerCase() ? 1 : 0; - } - let intersection = 0; - for (const token of leftTokens) { - if (rightTokens.has(token)) { - intersection += 1; - } - } - const union = new Set([...leftTokens, ...rightTokens]).size; - return union > 0 ? intersection / union : 0; -} - -function dedupeEntries(entries: ShortTermRecallEntry[], threshold: number): ShortTermRecallEntry[] { - const deduped: ShortTermRecallEntry[] = []; - for (const entry of entries) { - const duplicate = deduped.find( - (candidate) => - candidate.path === entry.path && - jaccardSimilarity(candidate.snippet, entry.snippet) >= threshold, - ); - if (duplicate) { - if (entry.recallCount > duplicate.recallCount) { - duplicate.recallCount = entry.recallCount; - } - duplicate.totalScore = Math.max(duplicate.totalScore, entry.totalScore); - duplicate.maxScore = Math.max(duplicate.maxScore, entry.maxScore); - duplicate.queryHashes = [...new Set([...duplicate.queryHashes, ...entry.queryHashes])]; - duplicate.recallDays = [ - ...new Set([...duplicate.recallDays, ...entry.recallDays]), - ].toSorted(); - duplicate.conceptTags = [...new Set([...duplicate.conceptTags, ...entry.conceptTags])]; - duplicate.lastRecalledAt = - Date.parse(entry.lastRecalledAt) > Date.parse(duplicate.lastRecalledAt) - ? entry.lastRecalledAt - : duplicate.lastRecalledAt; - continue; - } - deduped.push({ ...entry }); - } - return deduped; -} - -function buildLightSleepBody(entries: ShortTermRecallEntry[]): string[] { - if (entries.length === 0) { - return ["- No notable updates."]; - } - const lines: string[] = []; - for (const entry of entries) { - const snippet = entry.snippet || "(no snippet captured)"; - lines.push(`- Candidate: ${snippet}`); - lines.push(` - confidence: ${entryAverageScore(entry).toFixed(2)}`); - lines.push(` - evidence: ${entry.path}:${entry.startLine}-${entry.endLine}`); - lines.push(` - recalls: ${entry.recallCount}`); - lines.push(` - status: staged`); - } - return lines; -} - -function buildRemSleepBody( - entries: ShortTermRecallEntry[], - limit: number, - minPatternStrength: number, -): string[] { - const tagStats = new Map }>(); - for (const entry of entries) { - for (const tag of entry.conceptTags) { - if (!tag) { - continue; - } - const stat = tagStats.get(tag) ?? { count: 0, evidence: new Set() }; - stat.count += 1; - stat.evidence.add(`${entry.path}:${entry.startLine}-${entry.endLine}`); - tagStats.set(tag, stat); - } - } - - const ranked = [...tagStats.entries()] - .map(([tag, stat]) => { - const strength = Math.min(1, (stat.count / Math.max(1, entries.length)) * 2); - return { tag, strength, stat }; - }) - .filter((entry) => entry.strength >= minPatternStrength) - .toSorted( - (a, b) => - b.strength - a.strength || b.stat.count - a.stat.count || a.tag.localeCompare(b.tag), - ) - .slice(0, limit); - - if (ranked.length === 0) { - return ["- No strong patterns surfaced."]; - } - - const lines: string[] = []; - for (const entry of ranked) { - lines.push(`- Theme: \`${entry.tag}\` kept surfacing across ${entry.stat.count} memories.`); - lines.push(` - confidence: ${entry.strength.toFixed(2)}`); - lines.push(` - evidence: ${[...entry.stat.evidence].slice(0, 3).join(", ")}`); - lines.push(` - note: reflection`); - } - return lines; -} - -async function runLightSleep(params: { - workspaceDir: string; - config: MemoryLightSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }; - logger: Logger; - nowMs?: number; -}): Promise { - const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now(); - const cutoffMs = calculateLookbackCutoffMs(nowMs, params.config.lookbackDays); - const entries = dedupeEntries( - (await readShortTermRecallEntries({ workspaceDir: params.workspaceDir, nowMs })) - .filter((entry) => Date.parse(entry.lastRecalledAt) >= cutoffMs) - .toSorted((a, b) => { - const byTime = Date.parse(b.lastRecalledAt) - Date.parse(a.lastRecalledAt); - if (byTime !== 0) { - return byTime; - } - return b.recallCount - a.recallCount; - }) - .slice(0, params.config.limit), - params.config.dedupeSimilarity, - ); - const bodyLines = buildLightSleepBody(entries.slice(0, params.config.limit)); - await writeDailySleepPhaseBlock({ - workspaceDir: params.workspaceDir, - phase: "light", - bodyLines, - nowMs, - timezone: params.config.timezone, - storage: params.config.storage, - }); - if (params.config.enabled && entries.length > 0 && params.config.storage.mode !== "separate") { - params.logger.info( - `memory-core: light sleep staged ${Math.min(entries.length, params.config.limit)} candidate(s) [workspace=${params.workspaceDir}].`, - ); - } -} - -async function runRemSleep(params: { - workspaceDir: string; - config: MemoryRemSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }; - logger: Logger; - nowMs?: number; -}): Promise { - const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now(); - const cutoffMs = calculateLookbackCutoffMs(nowMs, params.config.lookbackDays); - const entries = ( - await readShortTermRecallEntries({ workspaceDir: params.workspaceDir, nowMs }) - ).filter((entry) => Date.parse(entry.lastRecalledAt) >= cutoffMs); - const bodyLines = buildRemSleepBody( - entries, - params.config.limit, - params.config.minPatternStrength, - ); - await writeDailySleepPhaseBlock({ - workspaceDir: params.workspaceDir, - phase: "rem", - bodyLines, - nowMs, - timezone: params.config.timezone, - storage: params.config.storage, - }); - if (params.config.enabled && entries.length > 0 && params.config.storage.mode !== "separate") { - params.logger.info( - `memory-core: REM sleep wrote reflections from ${entries.length} recent memory trace(s) [workspace=${params.workspaceDir}].`, - ); - } -} - -async function runPhaseIfTriggered(params: { - cleanedBody: string; - trigger?: string; - workspaceDir?: string; - cfg?: OpenClawConfig; - logger: Logger; - phase: "light" | "rem"; - eventText: string; - config: - | (MemoryLightSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }) - | (MemoryRemSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }); -}): Promise<{ handled: true; reason: string } | undefined> { - if (params.trigger !== "heartbeat" || params.cleanedBody.trim() !== params.eventText) { - return undefined; - } - if (!params.config.enabled) { - return { handled: true, reason: `memory-core: ${params.phase} sleep disabled` }; - } - const workspaces = resolveWorkspaces({ - cfg: params.cfg, - fallbackWorkspaceDir: params.workspaceDir, - }); - if (workspaces.length === 0) { - params.logger.warn( - `memory-core: ${params.phase} sleep skipped because no memory workspace is available.`, - ); - return { handled: true, reason: `memory-core: ${params.phase} sleep missing workspace` }; - } - if (params.config.limit === 0) { - params.logger.info(`memory-core: ${params.phase} sleep skipped because limit=0.`); - return { handled: true, reason: `memory-core: ${params.phase} sleep disabled by limit` }; - } - for (const workspaceDir of workspaces) { - try { - if (params.phase === "light") { - await runLightSleep({ - workspaceDir, - config: params.config as MemoryLightSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }, - logger: params.logger, - }); - } else { - await runRemSleep({ - workspaceDir, - config: params.config as MemoryRemSleepConfig & { - timezone?: string; - storage: { mode: "inline" | "separate" | "both"; separateReports: boolean }; - }, - logger: params.logger, - }); - } - } catch (err) { - params.logger.error( - `memory-core: ${params.phase} sleep failed for workspace ${workspaceDir}: ${formatErrorMessage(err)}`, - ); - } - } - return { handled: true, reason: `memory-core: ${params.phase} sleep processed` }; -} - -export function registerMemorySleepPhases(api: OpenClawPluginApi): void { - api.registerHook( - "gateway:startup", - async (event: unknown) => { - const cron = resolveCronServiceFromStartupEvent(event); - const pluginConfig = resolveMemoryCorePluginConfig(api.config) ?? api.pluginConfig; - const light = resolveMemoryLightSleepConfig({ pluginConfig, cfg: api.config }); - const rem = resolveMemoryRemSleepConfig({ pluginConfig, cfg: api.config }); - const lightDesired = buildManagedCronJob({ - name: LIGHT_SLEEP_CRON_NAME, - tag: LIGHT_SLEEP_CRON_TAG, - payloadText: LIGHT_SLEEP_EVENT_TEXT, - cron: light.cron, - timezone: light.timezone, - phase: "light", - limit: light.limit, - lookbackDays: light.lookbackDays, - }); - const remDesired = buildManagedCronJob({ - name: REM_SLEEP_CRON_NAME, - tag: REM_SLEEP_CRON_TAG, - payloadText: REM_SLEEP_EVENT_TEXT, - cron: rem.cron, - timezone: rem.timezone, - phase: "rem", - limit: rem.limit, - lookbackDays: rem.lookbackDays, - }); - try { - await reconcileManagedPhaseCronJob({ - cron, - desired: lightDesired, - match: { - name: LIGHT_SLEEP_CRON_NAME, - tag: LIGHT_SLEEP_CRON_TAG, - payloadText: LIGHT_SLEEP_EVENT_TEXT, - }, - enabled: light.enabled, - logger: api.logger, - }); - await reconcileManagedPhaseCronJob({ - cron, - desired: remDesired, - match: { - name: REM_SLEEP_CRON_NAME, - tag: REM_SLEEP_CRON_TAG, - payloadText: REM_SLEEP_EVENT_TEXT, - }, - enabled: rem.enabled, - logger: api.logger, - }); - } catch (err) { - api.logger.error( - `memory-core: sleep startup reconciliation failed: ${formatErrorMessage(err)}`, - ); - } - }, - { name: "memory-core-sleep-phase-cron" }, - ); - - api.on("before_agent_reply", async (event, ctx) => { - const pluginConfig = resolveMemoryCorePluginConfig(api.config) ?? api.pluginConfig; - const light = resolveMemoryLightSleepConfig({ pluginConfig, cfg: api.config }); - const lightResult = await runPhaseIfTriggered({ - cleanedBody: event.cleanedBody, - trigger: ctx.trigger, - workspaceDir: ctx.workspaceDir, - cfg: api.config, - logger: api.logger, - phase: "light", - eventText: LIGHT_SLEEP_EVENT_TEXT, - config: light, - }); - if (lightResult) { - return lightResult; - } - const rem = resolveMemoryRemSleepConfig({ pluginConfig, cfg: api.config }); - return await runPhaseIfTriggered({ - cleanedBody: event.cleanedBody, - trigger: ctx.trigger, - workspaceDir: ctx.workspaceDir, - cfg: api.config, - logger: api.logger, - phase: "rem", - eventText: REM_SLEEP_EVENT_TEXT, - config: rem, - }); - }); -} diff --git a/extensions/memory-core/src/tools.recall-tracking.test.ts b/extensions/memory-core/src/tools.recall-tracking.test.ts index e340c553c75..9e97daedc4d 100644 --- a/extensions/memory-core/src/tools.recall-tracking.test.ts +++ b/extensions/memory-core/src/tools.recall-tracking.test.ts @@ -137,7 +137,7 @@ describe("memory_search recall tracking", () => { } }); - it("passes the resolved sleep timezone into recall tracking", async () => { + it("passes the resolved dreaming timezone into recall tracking", async () => { setMemorySearchImpl(async () => [ { path: "memory/2026-04-03.md", @@ -161,7 +161,7 @@ describe("memory_search recall tracking", () => { entries: { "memory-core": { config: { - sleep: { + dreaming: { timezone: "Europe/London", }, }, diff --git a/extensions/memory-core/src/tools.ts b/extensions/memory-core/src/tools.ts index fd9d7e1f328..96a65988fa7 100644 --- a/extensions/memory-core/src/tools.ts +++ b/extensions/memory-core/src/tools.ts @@ -8,7 +8,7 @@ import { import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files"; import { resolveMemoryCorePluginConfig, - resolveMemoryDeepSleepConfig, + resolveMemoryDeepDreamingConfig, } from "openclaw/plugin-sdk/memory-core-host-status"; import { recordShortTermRecalls } from "./short-term-promotion.js"; import { @@ -108,7 +108,7 @@ export function createMemorySearchTool(options: { status.backend === "qmd" ? clampResultsByInjectedChars(decorated, resolved.qmd?.limits.maxInjectedChars) : decorated; - const sleepTimezone = resolveMemoryDeepSleepConfig({ + const sleepTimezone = resolveMemoryDeepDreamingConfig({ pluginConfig: resolveMemoryCorePluginConfig(cfg), cfg, }).timezone; diff --git a/src/gateway/server-methods/doctor.test.ts b/src/gateway/server-methods/doctor.test.ts index efb4fc4bfea..d95fbbc8701 100644 --- a/src/gateway/server-methods/doctor.test.ts +++ b/src/gateway/server-methods/doctor.test.ts @@ -105,7 +105,7 @@ describe("doctor.memory.status", () => { agentId: "main", provider: "gemini", embedding: { ok: true }, - sleep: expect.objectContaining({ + dreaming: expect.objectContaining({ enabled: true, shortTermCount: 0, promotedTotal: 0, @@ -240,7 +240,7 @@ describe("doctor.memory.status", () => { entries: { "memory-core": { config: { - sleep: { + dreaming: { phases: { deep: { cron: "0 */4 * * *", @@ -292,7 +292,7 @@ describe("doctor.memory.status", () => { agentId: "main", provider: "gemini", embedding: { ok: true }, - sleep: expect.objectContaining({ + dreaming: expect.objectContaining({ enabled: true, timezone: "America/Los_Angeles", shortTermCount: 1, @@ -347,7 +347,7 @@ describe("doctor.memory.status", () => { entries: { "memory-core": { config: { - sleep: {}, + dreaming: {}, }, }, }, @@ -369,7 +369,7 @@ describe("doctor.memory.status", () => { expect(respond).toHaveBeenCalledWith( true, expect.objectContaining({ - sleep: expect.objectContaining({ + dreaming: expect.objectContaining({ shortTermCount: 0, promotedTotal: 1, storePath, @@ -429,7 +429,7 @@ describe("doctor.memory.status", () => { entries: { "memory-core": { config: { - sleep: {}, + dreaming: {}, }, }, }, @@ -475,10 +475,10 @@ describe("doctor.memory.status", () => { expect(respond).toHaveBeenCalledWith( true, expect.objectContaining({ - sleep: expect.objectContaining({ + dreaming: expect.objectContaining({ shortTermCount: 0, promotedTotal: 0, - storeError: "2 sleep stores had read errors.", + storeError: "2 dreaming stores had read errors.", }), }), undefined, diff --git a/src/gateway/server-methods/doctor.ts b/src/gateway/server-methods/doctor.ts index 817ecfb269e..15e6e23a74a 100644 --- a/src/gateway/server-methods/doctor.ts +++ b/src/gateway/server-methods/doctor.ts @@ -4,42 +4,42 @@ import { resolveDefaultAgentId } from "../../agents/agent-scope.js"; import { loadConfig } from "../../config/config.js"; import type { OpenClawConfig } from "../../config/config.js"; import { - isSameMemorySleepDay, + isSameMemoryDreamingDay, resolveMemoryCorePluginConfig, - resolveMemoryDeepSleepConfig, - resolveMemoryLightSleepConfig, - resolveMemoryRemSleepConfig, - resolveMemorySleepConfig, - resolveMemorySleepWorkspaces, -} from "../../memory-host-sdk/sleep.js"; + resolveMemoryDeepDreamingConfig, + resolveMemoryLightDreamingConfig, + resolveMemoryRemDreamingConfig, + resolveMemoryDreamingConfig, + resolveMemoryDreamingWorkspaces, +} from "../../memory-host-sdk/dreaming.js"; import { getActiveMemorySearchManager } from "../../plugins/memory-runtime.js"; import { formatError } from "../server-utils.js"; import type { GatewayRequestHandlers } from "./types.js"; const SHORT_TERM_STORE_RELATIVE_PATH = path.join("memory", ".dreams", "short-term-recall.json"); -const MANAGED_LIGHT_SLEEP_CRON_NAME = "Memory Light Sleep"; -const MANAGED_LIGHT_SLEEP_CRON_TAG = "[managed-by=memory-core.sleep.light]"; +const MANAGED_LIGHT_SLEEP_CRON_NAME = "Memory Light Dreaming"; +const MANAGED_LIGHT_SLEEP_CRON_TAG = "[managed-by=memory-core.dreaming.light]"; const LIGHT_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_light_sleep__"; const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion"; const MANAGED_DEEP_SLEEP_CRON_TAG = "[managed-by=memory-core.short-term-promotion]"; const DEEP_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_short_term_promotion_dream__"; -const MANAGED_REM_SLEEP_CRON_NAME = "Memory REM Sleep"; -const MANAGED_REM_SLEEP_CRON_TAG = "[managed-by=memory-core.sleep.rem]"; +const MANAGED_REM_SLEEP_CRON_NAME = "Memory REM Dreaming"; +const MANAGED_REM_SLEEP_CRON_TAG = "[managed-by=memory-core.dreaming.rem]"; const REM_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_rem_sleep__"; -type DoctorMemorySleepPhasePayload = { +type DoctorMemoryDreamingPhasePayload = { enabled: boolean; cron: string; managedCronPresent: boolean; nextRunAtMs?: number; }; -type DoctorMemoryLightSleepPayload = DoctorMemorySleepPhasePayload & { +type DoctorMemoryLightDreamingPayload = DoctorMemoryDreamingPhasePayload & { lookbackDays: number; limit: number; }; -type DoctorMemoryDeepSleepPayload = DoctorMemorySleepPhasePayload & { +type DoctorMemoryDeepDreamingPayload = DoctorMemoryDreamingPhasePayload & { minScore: number; minRecallCount: number; minUniqueQueries: number; @@ -48,13 +48,13 @@ type DoctorMemoryDeepSleepPayload = DoctorMemorySleepPhasePayload & { limit: number; }; -type DoctorMemoryRemSleepPayload = DoctorMemorySleepPhasePayload & { +type DoctorMemoryRemDreamingPayload = DoctorMemoryDreamingPhasePayload & { lookbackDays: number; limit: number; minPatternStrength: number; }; -type DoctorMemorySleepPayload = { +type DoctorMemoryDreamingPayload = { enabled: boolean; timezone?: string; verboseLogging: boolean; @@ -67,9 +67,9 @@ type DoctorMemorySleepPayload = { lastPromotedAt?: string; storeError?: string; phases: { - light: DoctorMemoryLightSleepPayload; - deep: DoctorMemoryDeepSleepPayload; - rem: DoctorMemoryRemSleepPayload; + light: DoctorMemoryLightDreamingPayload; + deep: DoctorMemoryDeepDreamingPayload; + rem: DoctorMemoryRemDreamingPayload; }; }; @@ -80,7 +80,7 @@ export type DoctorMemoryStatusPayload = { ok: boolean; error?: string; }; - sleep?: DoctorMemorySleepPayload; + dreaming?: DoctorMemoryDreamingPayload; }; function asRecord(value: unknown): Record | null { @@ -98,10 +98,10 @@ function normalizeTrimmedString(value: unknown): string | undefined { return trimmed.length > 0 ? trimmed : undefined; } -function resolveSleepConfig( +function resolveDreamingConfig( cfg: OpenClawConfig, ): Omit< - DoctorMemorySleepPayload, + DoctorMemoryDreamingPayload, | "shortTermCount" | "promotedTotal" | "promotedToday" @@ -109,19 +109,19 @@ function resolveSleepConfig( | "lastPromotedAt" | "storeError" > { - const resolved = resolveMemorySleepConfig({ + const resolved = resolveMemoryDreamingConfig({ pluginConfig: resolveMemoryCorePluginConfig(cfg), cfg, }); - const light = resolveMemoryLightSleepConfig({ + const light = resolveMemoryLightDreamingConfig({ pluginConfig: resolveMemoryCorePluginConfig(cfg), cfg, }); - const deep = resolveMemoryDeepSleepConfig({ + const deep = resolveMemoryDeepDreamingConfig({ pluginConfig: resolveMemoryCorePluginConfig(cfg), cfg, }); - const rem = resolveMemoryRemSleepConfig({ + const rem = resolveMemoryRemDreamingConfig({ pluginConfig: resolveMemoryCorePluginConfig(cfg), cfg, }); @@ -174,8 +174,8 @@ function isShortTermMemoryPath(filePath: string): boolean { return /^(\d{4})-(\d{2})-(\d{2})\.md$/.test(normalized); } -type SleepStoreStats = Pick< - DoctorMemorySleepPayload, +type DreamingStoreStats = Pick< + DoctorMemoryDreamingPayload, | "shortTermCount" | "promotedTotal" | "promotedToday" @@ -184,11 +184,11 @@ type SleepStoreStats = Pick< | "storeError" >; -async function loadSleepStoreStats( +async function loadDreamingStoreStats( workspaceDir: string, nowMs: number, timezone?: string, -): Promise { +): Promise { const storePath = path.join(workspaceDir, SHORT_TERM_STORE_RELATIVE_PATH); try { const raw = await fs.readFile(storePath, "utf-8"); @@ -218,7 +218,7 @@ async function loadSleepStoreStats( } promotedTotal += 1; const promotedAtMs = Date.parse(promotedAt); - if (Number.isFinite(promotedAtMs) && isSameMemorySleepDay(promotedAtMs, nowMs, timezone)) { + if (Number.isFinite(promotedAtMs) && isSameMemoryDreamingDay(promotedAtMs, nowMs, timezone)) { promotedToday += 1; } if (Number.isFinite(promotedAtMs) && promotedAtMs > latestPromotedAtMs) { @@ -254,7 +254,7 @@ async function loadSleepStoreStats( } } -function mergeSleepStoreStats(stats: SleepStoreStats[]): SleepStoreStats { +function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStats { let shortTermCount = 0; let promotedTotal = 0; let promotedToday = 0; @@ -289,12 +289,12 @@ function mergeSleepStoreStats(stats: SleepStoreStats[]): SleepStoreStats { ...(storeErrors.length === 1 ? { storeError: storeErrors[0] } : storeErrors.length > 1 - ? { storeError: `${storeErrors.length} sleep stores had read errors.` } + ? { storeError: `${storeErrors.length} dreaming stores had read errors.` } : {}), }; } -type ManagedSleepCronStatus = { +type ManagedDreamingCronStatus = { managedCronPresent: boolean; nextRunAtMs?: number; }; @@ -307,7 +307,7 @@ type ManagedCronJobLike = { state?: { nextRunAtMs?: number }; }; -function isManagedSleepJob( +function isManagedDreamingJob( job: ManagedCronJobLike, params: { name: string; tag: string; payloadText: string }, ): boolean { @@ -323,7 +323,7 @@ function isManagedSleepJob( ); } -async function resolveManagedSleepCronStatus(params: { +async function resolveManagedDreamingCronStatus(params: { context: { cron?: { list?: (opts?: { includeDisabled?: boolean }) => Promise }; }; @@ -332,7 +332,7 @@ async function resolveManagedSleepCronStatus(params: { tag: string; payloadText: string; }; -}): Promise { +}): Promise { if (!params.context.cron || typeof params.context.cron.list !== "function") { return { managedCronPresent: false }; } @@ -340,7 +340,7 @@ async function resolveManagedSleepCronStatus(params: { const jobs = await params.context.cron.list({ includeDisabled: true }); const managed = jobs .filter((job): job is ManagedCronJobLike => typeof job === "object" && job !== null) - .filter((job) => isManagedSleepJob(job, params.match)); + .filter((job) => isManagedDreamingJob(job, params.match)); let nextRunAtMs: number | undefined; for (const job of managed) { if (job.enabled !== true) { @@ -363,11 +363,11 @@ async function resolveManagedSleepCronStatus(params: { } } -async function resolveAllManagedSleepCronStatuses(context: { +async function resolveAllManagedDreamingCronStatuses(context: { cron?: { list?: (opts?: { includeDisabled?: boolean }) => Promise }; -}): Promise> { +}): Promise> { return { - light: await resolveManagedSleepCronStatus({ + light: await resolveManagedDreamingCronStatus({ context, match: { name: MANAGED_LIGHT_SLEEP_CRON_NAME, @@ -375,7 +375,7 @@ async function resolveAllManagedSleepCronStatuses(context: { payloadText: LIGHT_SLEEP_SYSTEM_EVENT_TEXT, }, }), - deep: await resolveManagedSleepCronStatus({ + deep: await resolveManagedDreamingCronStatus({ context, match: { name: MANAGED_DEEP_SLEEP_CRON_NAME, @@ -383,7 +383,7 @@ async function resolveAllManagedSleepCronStatuses(context: { payloadText: DEEP_SLEEP_SYSTEM_EVENT_TEXT, }, }), - rem: await resolveManagedSleepCronStatus({ + rem: await resolveManagedDreamingCronStatus({ context, match: { name: MANAGED_REM_SLEEP_CRON_NAME, @@ -422,19 +422,19 @@ export const doctorHandlers: GatewayRequestHandlers = { embedding = { ok: false, error: "memory embeddings unavailable" }; } const nowMs = Date.now(); - const sleepConfig = resolveSleepConfig(cfg); + const dreamingConfig = resolveDreamingConfig(cfg); const workspaceDir = normalizeTrimmedString((status as Record).workspaceDir); - const configuredWorkspaces = resolveMemorySleepWorkspaces(cfg).map( + const configuredWorkspaces = resolveMemoryDreamingWorkspaces(cfg).map( (entry) => entry.workspaceDir, ); const allWorkspaces = configuredWorkspaces.length > 0 ? configuredWorkspaces : workspaceDir ? [workspaceDir] : []; const storeStats = allWorkspaces.length > 0 - ? mergeSleepStoreStats( + ? mergeDreamingStoreStats( await Promise.all( allWorkspaces.map((entry) => - loadSleepStoreStats(entry, nowMs, sleepConfig.timezone), + loadDreamingStoreStats(entry, nowMs, dreamingConfig.timezone), ), ), ) @@ -443,25 +443,25 @@ export const doctorHandlers: GatewayRequestHandlers = { promotedTotal: 0, promotedToday: 0, }; - const cronStatuses = await resolveAllManagedSleepCronStatuses(context); + const cronStatuses = await resolveAllManagedDreamingCronStatuses(context); const payload: DoctorMemoryStatusPayload = { agentId, provider: status.provider, embedding, - sleep: { - ...sleepConfig, + dreaming: { + ...dreamingConfig, ...storeStats, phases: { light: { - ...sleepConfig.phases.light, + ...dreamingConfig.phases.light, ...cronStatuses.light, }, deep: { - ...sleepConfig.phases.deep, + ...dreamingConfig.phases.deep, ...cronStatuses.deep, }, rem: { - ...sleepConfig.phases.rem, + ...dreamingConfig.phases.rem, ...cronStatuses.rem, }, }, diff --git a/src/memory-host-sdk/sleep.test.ts b/src/memory-host-sdk/sleep.test.ts deleted file mode 100644 index 165ce080d79..00000000000 --- a/src/memory-host-sdk/sleep.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/config.js"; - -const resolveDefaultAgentId = vi.hoisted(() => vi.fn(() => "main")); -const resolveAgentWorkspaceDir = vi.hoisted(() => - vi.fn((_cfg: OpenClawConfig, agentId: string) => `/workspace/${agentId}`), -); -const resolveMemorySearchConfig = vi.hoisted(() => - vi.fn<(_cfg: OpenClawConfig, _agentId: string) => { enabled: boolean } | null>(() => ({ - enabled: true, - })), -); - -vi.mock("../agents/agent-scope.js", () => ({ - resolveDefaultAgentId, - resolveAgentWorkspaceDir, -})); - -vi.mock("../agents/memory-search.js", () => ({ - resolveMemorySearchConfig, -})); - -import { - formatMemorySleepDay, - isSameMemorySleepDay, - resolveMemoryCorePluginConfig, - resolveMemorySleepConfig, - resolveMemorySleepWorkspaces, -} from "./sleep.js"; - -describe("memory sleep host helpers", () => { - it("normalizes string settings from the sleep config", () => { - const resolved = resolveMemorySleepConfig({ - pluginConfig: { - sleep: { - enabled: true, - timezone: "Europe/London", - storage: { - mode: "both", - separateReports: true, - }, - phases: { - deep: { - cron: "0 */4 * * *", - limit: "5", - minScore: "0.9", - minRecallCount: "4", - minUniqueQueries: "2", - recencyHalfLifeDays: "21", - maxAgeDays: "30", - }, - }, - }, - }, - }); - - expect(resolved.enabled).toBe(true); - expect(resolved.timezone).toBe("Europe/London"); - expect(resolved.storage).toEqual({ - mode: "both", - separateReports: true, - }); - expect(resolved.phases.deep).toMatchObject({ - cron: "0 */4 * * *", - limit: 5, - minScore: 0.9, - minRecallCount: 4, - minUniqueQueries: 2, - recencyHalfLifeDays: 21, - maxAgeDays: 30, - }); - }); - - it("falls back to cfg timezone and deep defaults", () => { - const cfg = { - agents: { - defaults: { - userTimezone: "America/Los_Angeles", - }, - }, - } as OpenClawConfig; - - const resolved = resolveMemorySleepConfig({ - pluginConfig: {}, - cfg, - }); - - expect(resolved.enabled).toBe(true); - expect(resolved.timezone).toBe("America/Los_Angeles"); - expect(resolved.phases.deep).toMatchObject({ - cron: "0 3 * * *", - limit: 10, - minScore: 0.8, - recencyHalfLifeDays: 14, - maxAgeDays: 30, - }); - }); - - it("dedupes shared workspaces and skips agents without memory search", () => { - resolveMemorySearchConfig.mockImplementation((_cfg: OpenClawConfig, agentId: string) => - agentId === "beta" ? null : { enabled: true }, - ); - resolveAgentWorkspaceDir.mockImplementation((_cfg: OpenClawConfig, agentId: string) => { - if (agentId === "alpha") { - return "/workspace/shared"; - } - if (agentId === "gamma") { - return "/workspace/shared"; - } - return `/workspace/${agentId}`; - }); - - const cfg = { - agents: { - list: [{ id: "alpha" }, { id: "beta" }, { id: "gamma" }], - }, - } as OpenClawConfig; - - expect(resolveMemorySleepWorkspaces(cfg)).toEqual([ - { - workspaceDir: "/workspace/shared", - agentIds: ["alpha", "gamma"], - }, - ]); - }); - - it("uses default agent fallback and timezone-aware day helpers", () => { - resolveDefaultAgentId.mockReturnValue("fallback"); - const cfg = {} as OpenClawConfig; - - expect(resolveMemorySleepWorkspaces(cfg)).toEqual([ - { - workspaceDir: "/workspace/fallback", - agentIds: ["fallback"], - }, - ]); - - expect( - formatMemorySleepDay(Date.parse("2026-04-02T06:30:00.000Z"), "America/Los_Angeles"), - ).toBe("2026-04-01"); - expect( - isSameMemorySleepDay( - Date.parse("2026-04-02T06:30:00.000Z"), - Date.parse("2026-04-02T06:50:00.000Z"), - "America/Los_Angeles", - ), - ).toBe(true); - expect( - resolveMemoryCorePluginConfig({ - plugins: { - entries: { - "memory-core": { - config: { - sleep: { - enabled: true, - }, - }, - }, - }, - }, - } as OpenClawConfig), - ).toEqual({ - sleep: { - enabled: true, - }, - }); - }); -}); diff --git a/src/memory-host-sdk/sleep.ts b/src/memory-host-sdk/sleep.ts deleted file mode 100644 index 5cfe8af702b..00000000000 --- a/src/memory-host-sdk/sleep.ts +++ /dev/null @@ -1,603 +0,0 @@ -import path from "node:path"; -import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; -import { resolveMemorySearchConfig } from "../agents/memory-search.js"; -import type { OpenClawConfig } from "../config/config.js"; - -export const DEFAULT_MEMORY_SLEEP_ENABLED = true; -export const DEFAULT_MEMORY_SLEEP_TIMEZONE = undefined; -export const DEFAULT_MEMORY_SLEEP_VERBOSE_LOGGING = false; -export const DEFAULT_MEMORY_SLEEP_STORAGE_MODE = "inline"; -export const DEFAULT_MEMORY_SLEEP_SEPARATE_REPORTS = false; - -export const DEFAULT_MEMORY_LIGHT_SLEEP_CRON_EXPR = "0 */6 * * *"; -export const DEFAULT_MEMORY_LIGHT_SLEEP_LOOKBACK_DAYS = 2; -export const DEFAULT_MEMORY_LIGHT_SLEEP_LIMIT = 100; -export const DEFAULT_MEMORY_LIGHT_SLEEP_DEDUPE_SIMILARITY = 0.9; - -export const DEFAULT_MEMORY_DEEP_SLEEP_CRON_EXPR = "0 3 * * *"; -export const DEFAULT_MEMORY_DEEP_SLEEP_LIMIT = 10; -export const DEFAULT_MEMORY_DEEP_SLEEP_MIN_SCORE = 0.8; -export const DEFAULT_MEMORY_DEEP_SLEEP_MIN_RECALL_COUNT = 3; -export const DEFAULT_MEMORY_DEEP_SLEEP_MIN_UNIQUE_QUERIES = 3; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECENCY_HALF_LIFE_DAYS = 14; -export const DEFAULT_MEMORY_DEEP_SLEEP_MAX_AGE_DAYS = 30; - -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_ENABLED = true; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_TRIGGER_BELOW_HEALTH = 0.35; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_LOOKBACK_DAYS = 30; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_MAX_CANDIDATES = 20; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_MIN_CONFIDENCE = 0.9; -export const DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_AUTO_WRITE_MIN_CONFIDENCE = 0.97; - -export const DEFAULT_MEMORY_REM_SLEEP_CRON_EXPR = "0 5 * * 0"; -export const DEFAULT_MEMORY_REM_SLEEP_LOOKBACK_DAYS = 7; -export const DEFAULT_MEMORY_REM_SLEEP_LIMIT = 10; -export const DEFAULT_MEMORY_REM_SLEEP_MIN_PATTERN_STRENGTH = 0.75; - -export const DEFAULT_MEMORY_SLEEP_SPEED = "balanced"; -export const DEFAULT_MEMORY_SLEEP_THINKING = "medium"; -export const DEFAULT_MEMORY_SLEEP_BUDGET = "medium"; - -export type MemorySleepSpeed = "fast" | "balanced" | "slow"; -export type MemorySleepThinking = "low" | "medium" | "high"; -export type MemorySleepBudget = "cheap" | "medium" | "expensive"; -export type MemorySleepStorageMode = "inline" | "separate" | "both"; - -export type MemoryLightSleepSource = "daily" | "sessions" | "recall"; -export type MemoryDeepSleepSource = "daily" | "memory" | "sessions" | "logs" | "recall"; -export type MemoryRemSleepSource = "memory" | "daily" | "deep"; - -export type MemorySleepExecutionConfig = { - speed: MemorySleepSpeed; - thinking: MemorySleepThinking; - budget: MemorySleepBudget; - model?: string; - maxOutputTokens?: number; - temperature?: number; - timeoutMs?: number; -}; - -export type MemorySleepStorageConfig = { - mode: MemorySleepStorageMode; - separateReports: boolean; -}; - -export type MemoryLightSleepConfig = { - enabled: boolean; - cron: string; - lookbackDays: number; - limit: number; - dedupeSimilarity: number; - sources: MemoryLightSleepSource[]; - execution: MemorySleepExecutionConfig; -}; - -export type MemoryDeepSleepRecoveryConfig = { - enabled: boolean; - triggerBelowHealth: number; - lookbackDays: number; - maxRecoveredCandidates: number; - minRecoveryConfidence: number; - autoWriteMinConfidence: number; -}; - -export type MemoryDeepSleepConfig = { - enabled: boolean; - cron: string; - limit: number; - minScore: number; - minRecallCount: number; - minUniqueQueries: number; - recencyHalfLifeDays: number; - maxAgeDays?: number; - sources: MemoryDeepSleepSource[]; - recovery: MemoryDeepSleepRecoveryConfig; - execution: MemorySleepExecutionConfig; -}; - -export type MemoryRemSleepConfig = { - enabled: boolean; - cron: string; - lookbackDays: number; - limit: number; - minPatternStrength: number; - sources: MemoryRemSleepSource[]; - execution: MemorySleepExecutionConfig; -}; - -export type MemorySleepPhaseName = "light" | "deep" | "rem"; - -export type MemorySleepConfig = { - enabled: boolean; - timezone?: string; - verboseLogging: boolean; - storage: MemorySleepStorageConfig; - execution: { - defaults: MemorySleepExecutionConfig; - }; - phases: { - light: MemoryLightSleepConfig; - deep: MemoryDeepSleepConfig; - rem: MemoryRemSleepConfig; - }; -}; - -export type MemorySleepWorkspace = { - workspaceDir: string; - agentIds: string[]; -}; - -const DEFAULT_MEMORY_LIGHT_SLEEP_SOURCES: MemoryLightSleepSource[] = [ - "daily", - "sessions", - "recall", -]; -const DEFAULT_MEMORY_DEEP_SLEEP_SOURCES: MemoryDeepSleepSource[] = [ - "daily", - "memory", - "sessions", - "logs", - "recall", -]; -const DEFAULT_MEMORY_REM_SLEEP_SOURCES: MemoryRemSleepSource[] = ["memory", "daily", "deep"]; - -function asRecord(value: unknown): Record | null { - if (!value || typeof value !== "object" || Array.isArray(value)) { - return null; - } - return value as Record; -} - -function normalizeTrimmedString(value: unknown): string | undefined { - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : undefined; -} - -function normalizeNonNegativeInt(value: unknown, fallback: number): number { - if (typeof value === "string" && value.trim().length === 0) { - return fallback; - } - const num = typeof value === "string" ? Number(value.trim()) : Number(value); - if (!Number.isFinite(num)) { - return fallback; - } - const floored = Math.floor(num); - if (floored < 0) { - return fallback; - } - return floored; -} - -function normalizeOptionalPositiveInt(value: unknown): number | undefined { - if (value === undefined || value === null) { - return undefined; - } - if (typeof value === "string" && value.trim().length === 0) { - return undefined; - } - const num = typeof value === "string" ? Number(value.trim()) : Number(value); - if (!Number.isFinite(num)) { - return undefined; - } - const floored = Math.floor(num); - if (floored <= 0) { - return undefined; - } - return floored; -} - -function normalizeBoolean(value: unknown, fallback: boolean): boolean { - if (typeof value === "boolean") { - return value; - } - if (typeof value === "string") { - const normalized = value.trim().toLowerCase(); - if (normalized === "true") { - return true; - } - if (normalized === "false") { - return false; - } - } - return fallback; -} - -function normalizeScore(value: unknown, fallback: number): number { - if (typeof value === "string" && value.trim().length === 0) { - return fallback; - } - const num = typeof value === "string" ? Number(value.trim()) : Number(value); - if (!Number.isFinite(num) || num < 0 || num > 1) { - return fallback; - } - return num; -} - -function normalizeSimilarity(value: unknown, fallback: number): number { - return normalizeScore(value, fallback); -} - -function normalizeStringArray( - value: unknown, - allowed: readonly T[], - fallback: readonly T[], -): T[] { - if (!Array.isArray(value)) { - return [...fallback]; - } - const allowedSet = new Set(allowed); - const normalized: T[] = []; - for (const entry of value) { - const normalizedEntry = normalizeTrimmedString(entry)?.toLowerCase(); - if (!normalizedEntry || !allowedSet.has(normalizedEntry as T)) { - continue; - } - if (!normalized.includes(normalizedEntry as T)) { - normalized.push(normalizedEntry as T); - } - } - return normalized.length > 0 ? normalized : [...fallback]; -} - -function normalizeStorageMode(value: unknown): MemorySleepStorageMode { - const normalized = normalizeTrimmedString(value)?.toLowerCase(); - if (normalized === "inline" || normalized === "separate" || normalized === "both") { - return normalized; - } - return DEFAULT_MEMORY_SLEEP_STORAGE_MODE; -} - -function normalizeSpeed(value: unknown): MemorySleepSpeed | undefined { - const normalized = normalizeTrimmedString(value)?.toLowerCase(); - if (normalized === "fast" || normalized === "balanced" || normalized === "slow") { - return normalized; - } - return undefined; -} - -function normalizeThinking(value: unknown): MemorySleepThinking | undefined { - const normalized = normalizeTrimmedString(value)?.toLowerCase(); - if (normalized === "low" || normalized === "medium" || normalized === "high") { - return normalized; - } - return undefined; -} - -function normalizeBudget(value: unknown): MemorySleepBudget | undefined { - const normalized = normalizeTrimmedString(value)?.toLowerCase(); - if (normalized === "cheap" || normalized === "medium" || normalized === "expensive") { - return normalized; - } - return undefined; -} - -function resolveExecutionConfig( - value: unknown, - fallback: MemorySleepExecutionConfig, -): MemorySleepExecutionConfig { - const record = asRecord(value); - const maxOutputTokens = normalizeOptionalPositiveInt(record?.maxOutputTokens); - const timeoutMs = normalizeOptionalPositiveInt(record?.timeoutMs); - const temperatureRaw = record?.temperature; - const temperature = - typeof temperatureRaw === "number" && Number.isFinite(temperatureRaw) && temperatureRaw >= 0 - ? Math.min(2, temperatureRaw) - : undefined; - - return { - speed: normalizeSpeed(record?.speed) ?? fallback.speed, - thinking: normalizeThinking(record?.thinking) ?? fallback.thinking, - budget: normalizeBudget(record?.budget) ?? fallback.budget, - ...(normalizeTrimmedString(record?.model) - ? { model: normalizeTrimmedString(record?.model) } - : {}), - ...(typeof maxOutputTokens === "number" ? { maxOutputTokens } : {}), - ...(typeof temperature === "number" ? { temperature } : {}), - ...(typeof timeoutMs === "number" ? { timeoutMs } : {}), - }; -} - -function normalizePathForComparison(input: string): string { - const normalized = path.resolve(input); - return process.platform === "win32" ? normalized.toLowerCase() : normalized; -} - -function formatLocalIsoDay(epochMs: number): string { - const date = new Date(epochMs); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -export function resolveMemoryCorePluginConfig( - cfg: OpenClawConfig | Record | undefined, -): Record | undefined { - const root = asRecord(cfg); - const plugins = asRecord(root?.plugins); - const entries = asRecord(plugins?.entries); - const memoryCore = asRecord(entries?.["memory-core"]); - return asRecord(memoryCore?.config) ?? undefined; -} - -export function resolveMemorySleepConfig(params: { - pluginConfig?: Record; - cfg?: OpenClawConfig; -}): MemorySleepConfig { - const sleep = asRecord(params.pluginConfig?.sleep); - const timezone = - normalizeTrimmedString(sleep?.timezone) ?? - normalizeTrimmedString(params.cfg?.agents?.defaults?.userTimezone) ?? - DEFAULT_MEMORY_SLEEP_TIMEZONE; - const storage = asRecord(sleep?.storage); - const execution = asRecord(sleep?.execution); - const phases = asRecord(sleep?.phases); - - const defaultExecution = resolveExecutionConfig(execution?.defaults, { - speed: DEFAULT_MEMORY_SLEEP_SPEED, - thinking: DEFAULT_MEMORY_SLEEP_THINKING, - budget: DEFAULT_MEMORY_SLEEP_BUDGET, - }); - - const light = asRecord(phases?.light); - const deep = asRecord(phases?.deep); - const rem = asRecord(phases?.rem); - const deepRecovery = asRecord(deep?.recovery); - const maxAgeDays = normalizeOptionalPositiveInt(deep?.maxAgeDays); - - return { - enabled: normalizeBoolean(sleep?.enabled, DEFAULT_MEMORY_SLEEP_ENABLED), - ...(timezone ? { timezone } : {}), - verboseLogging: normalizeBoolean(sleep?.verboseLogging, DEFAULT_MEMORY_SLEEP_VERBOSE_LOGGING), - storage: { - mode: normalizeStorageMode(storage?.mode), - separateReports: normalizeBoolean( - storage?.separateReports, - DEFAULT_MEMORY_SLEEP_SEPARATE_REPORTS, - ), - }, - execution: { - defaults: defaultExecution, - }, - phases: { - light: { - enabled: normalizeBoolean(light?.enabled, true), - cron: normalizeTrimmedString(light?.cron) ?? DEFAULT_MEMORY_LIGHT_SLEEP_CRON_EXPR, - lookbackDays: normalizeNonNegativeInt( - light?.lookbackDays, - DEFAULT_MEMORY_LIGHT_SLEEP_LOOKBACK_DAYS, - ), - limit: normalizeNonNegativeInt(light?.limit, DEFAULT_MEMORY_LIGHT_SLEEP_LIMIT), - dedupeSimilarity: normalizeSimilarity( - light?.dedupeSimilarity, - DEFAULT_MEMORY_LIGHT_SLEEP_DEDUPE_SIMILARITY, - ), - sources: normalizeStringArray( - light?.sources, - ["daily", "sessions", "recall"] as const, - DEFAULT_MEMORY_LIGHT_SLEEP_SOURCES, - ), - execution: resolveExecutionConfig(light?.execution, { - ...defaultExecution, - speed: "fast", - thinking: "low", - budget: "cheap", - }), - }, - deep: { - enabled: normalizeBoolean(deep?.enabled, true), - cron: normalizeTrimmedString(deep?.cron) ?? DEFAULT_MEMORY_DEEP_SLEEP_CRON_EXPR, - limit: normalizeNonNegativeInt(deep?.limit, DEFAULT_MEMORY_DEEP_SLEEP_LIMIT), - minScore: normalizeScore(deep?.minScore, DEFAULT_MEMORY_DEEP_SLEEP_MIN_SCORE), - minRecallCount: normalizeNonNegativeInt( - deep?.minRecallCount, - DEFAULT_MEMORY_DEEP_SLEEP_MIN_RECALL_COUNT, - ), - minUniqueQueries: normalizeNonNegativeInt( - deep?.minUniqueQueries, - DEFAULT_MEMORY_DEEP_SLEEP_MIN_UNIQUE_QUERIES, - ), - recencyHalfLifeDays: normalizeNonNegativeInt( - deep?.recencyHalfLifeDays, - DEFAULT_MEMORY_DEEP_SLEEP_RECENCY_HALF_LIFE_DAYS, - ), - ...(typeof maxAgeDays === "number" - ? { maxAgeDays } - : typeof DEFAULT_MEMORY_DEEP_SLEEP_MAX_AGE_DAYS === "number" - ? { maxAgeDays: DEFAULT_MEMORY_DEEP_SLEEP_MAX_AGE_DAYS } - : {}), - sources: normalizeStringArray( - deep?.sources, - ["daily", "memory", "sessions", "logs", "recall"] as const, - DEFAULT_MEMORY_DEEP_SLEEP_SOURCES, - ), - recovery: { - enabled: normalizeBoolean( - deepRecovery?.enabled, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_ENABLED, - ), - triggerBelowHealth: normalizeScore( - deepRecovery?.triggerBelowHealth, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_TRIGGER_BELOW_HEALTH, - ), - lookbackDays: normalizeNonNegativeInt( - deepRecovery?.lookbackDays, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_LOOKBACK_DAYS, - ), - maxRecoveredCandidates: normalizeNonNegativeInt( - deepRecovery?.maxRecoveredCandidates, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_MAX_CANDIDATES, - ), - minRecoveryConfidence: normalizeScore( - deepRecovery?.minRecoveryConfidence, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_MIN_CONFIDENCE, - ), - autoWriteMinConfidence: normalizeScore( - deepRecovery?.autoWriteMinConfidence, - DEFAULT_MEMORY_DEEP_SLEEP_RECOVERY_AUTO_WRITE_MIN_CONFIDENCE, - ), - }, - execution: resolveExecutionConfig(deep?.execution, { - ...defaultExecution, - speed: "balanced", - thinking: "high", - budget: "medium", - }), - }, - rem: { - enabled: normalizeBoolean(rem?.enabled, true), - cron: normalizeTrimmedString(rem?.cron) ?? DEFAULT_MEMORY_REM_SLEEP_CRON_EXPR, - lookbackDays: normalizeNonNegativeInt( - rem?.lookbackDays, - DEFAULT_MEMORY_REM_SLEEP_LOOKBACK_DAYS, - ), - limit: normalizeNonNegativeInt(rem?.limit, DEFAULT_MEMORY_REM_SLEEP_LIMIT), - minPatternStrength: normalizeScore( - rem?.minPatternStrength, - DEFAULT_MEMORY_REM_SLEEP_MIN_PATTERN_STRENGTH, - ), - sources: normalizeStringArray( - rem?.sources, - ["memory", "daily", "deep"] as const, - DEFAULT_MEMORY_REM_SLEEP_SOURCES, - ), - execution: resolveExecutionConfig(rem?.execution, { - ...defaultExecution, - speed: "slow", - thinking: "high", - budget: "expensive", - }), - }, - }, - }; -} - -export function resolveMemoryDeepSleepConfig(params: { - pluginConfig?: Record; - cfg?: OpenClawConfig; -}): MemoryDeepSleepConfig & { - timezone?: string; - verboseLogging: boolean; - storage: MemorySleepStorageConfig; -} { - const resolved = resolveMemorySleepConfig(params); - return { - ...resolved.phases.deep, - enabled: resolved.enabled && resolved.phases.deep.enabled, - ...(resolved.timezone ? { timezone: resolved.timezone } : {}), - verboseLogging: resolved.verboseLogging, - storage: resolved.storage, - }; -} - -export function resolveMemoryLightSleepConfig(params: { - pluginConfig?: Record; - cfg?: OpenClawConfig; -}): MemoryLightSleepConfig & { - timezone?: string; - verboseLogging: boolean; - storage: MemorySleepStorageConfig; -} { - const resolved = resolveMemorySleepConfig(params); - return { - ...resolved.phases.light, - enabled: resolved.enabled && resolved.phases.light.enabled, - ...(resolved.timezone ? { timezone: resolved.timezone } : {}), - verboseLogging: resolved.verboseLogging, - storage: resolved.storage, - }; -} - -export function resolveMemoryRemSleepConfig(params: { - pluginConfig?: Record; - cfg?: OpenClawConfig; -}): MemoryRemSleepConfig & { - timezone?: string; - verboseLogging: boolean; - storage: MemorySleepStorageConfig; -} { - const resolved = resolveMemorySleepConfig(params); - return { - ...resolved.phases.rem, - enabled: resolved.enabled && resolved.phases.rem.enabled, - ...(resolved.timezone ? { timezone: resolved.timezone } : {}), - verboseLogging: resolved.verboseLogging, - storage: resolved.storage, - }; -} - -export function formatMemorySleepDay(epochMs: number, timezone?: string): string { - if (!timezone) { - return formatLocalIsoDay(epochMs); - } - try { - const parts = new Intl.DateTimeFormat("en-CA", { - timeZone: timezone, - year: "numeric", - month: "2-digit", - day: "2-digit", - }).formatToParts(new Date(epochMs)); - const values = new Map(parts.map((part) => [part.type, part.value])); - const year = values.get("year"); - const month = values.get("month"); - const day = values.get("day"); - if (year && month && day) { - return `${year}-${month}-${day}`; - } - } catch { - // Fall back to host-local day for invalid or unsupported timezones. - } - return formatLocalIsoDay(epochMs); -} - -export function isSameMemorySleepDay( - firstEpochMs: number, - secondEpochMs: number, - timezone?: string, -): boolean { - return ( - formatMemorySleepDay(firstEpochMs, timezone) === formatMemorySleepDay(secondEpochMs, timezone) - ); -} - -export function resolveMemorySleepWorkspaces(cfg: OpenClawConfig): MemorySleepWorkspace[] { - const configured = Array.isArray(cfg.agents?.list) ? cfg.agents.list : []; - const agentIds: string[] = []; - const seenAgents = new Set(); - for (const entry of configured) { - if (!entry || typeof entry !== "object" || typeof entry.id !== "string") { - continue; - } - const id = entry.id.trim().toLowerCase(); - if (!id || seenAgents.has(id)) { - continue; - } - seenAgents.add(id); - agentIds.push(id); - } - if (agentIds.length === 0) { - agentIds.push(resolveDefaultAgentId(cfg)); - } - - const byWorkspace = new Map(); - for (const agentId of agentIds) { - if (!resolveMemorySearchConfig(cfg, agentId)) { - continue; - } - const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId)?.trim(); - if (!workspaceDir) { - continue; - } - const key = normalizePathForComparison(workspaceDir); - const existing = byWorkspace.get(key); - if (existing) { - existing.agentIds.push(agentId); - continue; - } - byWorkspace.set(key, { workspaceDir, agentIds: [agentId] }); - } - return [...byWorkspace.values()]; -} diff --git a/src/plugin-sdk/memory-core-host-status.ts b/src/plugin-sdk/memory-core-host-status.ts index d28546b0cb8..a7196314403 100644 --- a/src/plugin-sdk/memory-core-host-status.ts +++ b/src/plugin-sdk/memory-core-host-status.ts @@ -1,2 +1,2 @@ export * from "../memory-host-sdk/status.js"; -export * from "../memory-host-sdk/sleep.js"; +export * from "../memory-host-sdk/dreaming.js"; diff --git a/src/plugin-sdk/memory-core.ts b/src/plugin-sdk/memory-core.ts index 6495cc1f27d..4b6bde5f930 100644 --- a/src/plugin-sdk/memory-core.ts +++ b/src/plugin-sdk/memory-core.ts @@ -48,11 +48,11 @@ export { } from "./memory-core-host-runtime-cli.js"; export { resolveMemoryCorePluginConfig, - formatMemorySleepDay, - isSameMemorySleepDay, - resolveMemoryDeepSleepConfig, - resolveMemorySleepConfig, - resolveMemorySleepWorkspaces, + formatMemoryDreamingDay, + isSameMemoryDreamingDay, + resolveMemoryDeepDreamingConfig, + resolveMemoryDreamingConfig, + resolveMemoryDreamingWorkspaces, } from "./memory-core-host-status.js"; export { listMemoryFiles, diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 93ba2cb00e8..69247f20ca1 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -45,7 +45,7 @@ export const en: TranslationMap = { aiAgents: "AI & Agents", debug: "Debug", logs: "Logs", - dreams: "Dreams", + dreams: "Dreaming", }, subtitles: { agents: "Workspaces, tools, identities.", @@ -66,7 +66,7 @@ export const en: TranslationMap = { aiAgents: "Agents, models, skills, tools, memory, session.", debug: "Snapshots, events, RPC.", logs: "Live gateway logs.", - dreams: "Memory consolidation while sleeping.", + dreams: "Memory dreaming, consolidation, and reflection.", }, overview: { access: { diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index f23a5c4884b..8d43b11bbfa 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -66,9 +66,9 @@ import { } from "./controllers/devices.ts"; import { loadDreamingStatus, - updateSleepEnabled, - updateSleepPhaseEnabled, - type SleepPhaseId, + updateDreamingEnabled, + updateDreamingPhaseEnabled, + type DreamingPhaseId, } from "./controllers/dreaming.ts"; import { loadExecApprovals, @@ -149,16 +149,16 @@ const lazyLogs = createLazy(() => import("./views/logs.ts")); const lazyNodes = createLazy(() => import("./views/nodes.ts")); const lazySessions = createLazy(() => import("./views/sessions.ts")); const lazySkills = createLazy(() => import("./views/skills.ts")); -const lazyDreams = createLazy(() => import("./views/dreams.ts")); -const SLEEP_PHASE_OPTIONS: Array<{ id: SleepPhaseId; label: string; detail: string }> = [ +const lazyDreamingView = createLazy(() => import("./views/dreaming.ts")); +const DREAMING_PHASE_OPTIONS: Array<{ id: DreamingPhaseId; label: string; detail: string }> = [ { id: "light", label: "Light", detail: "sort and stage the day" }, { id: "deep", label: "Deep", detail: "promote durable memory" }, { id: "rem", label: "REM", detail: "surface themes and reflections" }, ]; -function resolveConfiguredSleep(configValue: Record | null): { +function resolveConfiguredDreaming(configValue: Record | null): { enabled: boolean; - phases: Record; + phases: Record; } { if (!configValue) { return { @@ -174,13 +174,13 @@ function resolveConfiguredSleep(configValue: Record | null): { const entries = plugins?.entries as Record | undefined; const memoryCore = entries?.["memory-core"] as Record | undefined; const config = memoryCore?.config as Record | undefined; - const sleep = config?.sleep as Record | undefined; - const phases = sleep?.phases as Record | undefined; + const dreaming = config?.dreaming as Record | undefined; + const phases = dreaming?.phases as Record | undefined; const light = phases?.light as Record | undefined; const deep = phases?.deep as Record | undefined; const rem = phases?.rem as Record | undefined; return { - enabled: typeof sleep?.enabled === "boolean" ? sleep.enabled : true, + enabled: typeof dreaming?.enabled === "boolean" ? dreaming.enabled : true, phases: { light: typeof light?.enabled === "boolean" ? light.enabled : true, deep: typeof deep?.enabled === "boolean" ? deep.enabled : true, @@ -199,8 +199,8 @@ function formatDreamNextCycle(nextRunAtMs: number | undefined): string | null { }); } -function resolveSleepNextCycle( - status: { phases: Record } | null, +function resolveDreamingNextCycle( + status: { phases: Record } | null, ): string | null { if (!status) { return null; @@ -397,17 +397,17 @@ export function renderApp(state: AppViewState) { const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null; const configValue = state.configForm ?? (state.configSnapshot?.config as Record | null); - const configuredSleep = resolveConfiguredSleep(configValue); - const dreamingOn = state.dreamingStatus?.enabled ?? configuredSleep.enabled; - const dreamingNextCycle = resolveSleepNextCycle(state.dreamingStatus); + const configuredDreaming = resolveConfiguredDreaming(configValue); + const dreamingOn = state.dreamingStatus?.enabled ?? configuredDreaming.enabled; + const dreamingNextCycle = resolveDreamingNextCycle(state.dreamingStatus); const dreamingLoading = state.dreamingStatusLoading || state.dreamingModeSaving; const refreshDreamingStatus = () => loadDreamingStatus(state); - const applySleepEnabled = (enabled: boolean) => { + const applyDreamingEnabled = (enabled: boolean) => { if (state.dreamingModeSaving || dreamingOn === enabled) { return; } void (async () => { - const updated = await updateSleepEnabled(state, enabled); + const updated = await updateDreamingEnabled(state, enabled); if (!updated) { return; } @@ -415,17 +415,17 @@ export function renderApp(state: AppViewState) { await loadDreamingStatus(state); })(); }; - const applySleepPhaseEnabled = (phase: SleepPhaseId, enabled: boolean) => { + const applyDreamingPhaseEnabled = (phase: DreamingPhaseId, enabled: boolean) => { if (state.dreamingModeSaving) { return; } const currentEnabled = - state.dreamingStatus?.phases[phase].enabled ?? configuredSleep.phases[phase]; + state.dreamingStatus?.phases[phase].enabled ?? configuredDreaming.phases[phase]; if (currentEnabled === enabled) { return; } void (async () => { - const updated = await updateSleepPhaseEnabled(state, phase, enabled); + const updated = await updateDreamingPhaseEnabled(state, phase, enabled); if (!updated) { return; } @@ -740,23 +740,19 @@ export function renderApp(state: AppViewState) {
- -
- -
- ${props.phases.map( - (phase) => html` -
-
-
-
${phase.label}
-
${phase.detail}
-
- -
-
- ${phase.enabled ? "scheduled" : "off"} - ${phase.nextCycle ? `next ${phase.nextCycle}` : "no next run"} - ${phase.managedCronPresent ? "managed cron" : "cron missing"} -
-
- `, - )} -
- ${props.statusError - ? html`
${props.statusError}
` - : nothing} - - - `; -}