From 848cc5e0ce0b34cebec6dcf2589f0e65c88f5dc5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 5 Apr 2026 17:56:15 +0100 Subject: [PATCH] refactor(memory-core): remove legacy dreaming host helpers --- src/memory-host-sdk/dreaming.test.ts | 162 --------------- src/memory-host-sdk/dreaming.ts | 300 --------------------------- 2 files changed, 462 deletions(-) delete mode 100644 src/memory-host-sdk/dreaming.test.ts delete mode 100644 src/memory-host-sdk/dreaming.ts diff --git a/src/memory-host-sdk/dreaming.test.ts b/src/memory-host-sdk/dreaming.test.ts deleted file mode 100644 index d82618de605..00000000000 --- a/src/memory-host-sdk/dreaming.test.ts +++ /dev/null @@ -1,162 +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 } | undefined>(() => ({ - enabled: true, - })), -); - -vi.mock("../agents/agent-scope.js", () => ({ - resolveDefaultAgentId, - resolveAgentWorkspaceDir, -})); - -vi.mock("../agents/memory-search.js", () => ({ - resolveMemorySearchConfig, -})); - -import { - formatMemoryDreamingDay, - isSameMemoryDreamingDay, - resolveMemoryCorePluginConfig, - resolveMemoryDreamingConfig, - resolveMemoryDreamingWorkspaces, -} from "./dreaming.js"; - -describe("memory dreaming host helpers", () => { - it("prefers cron over legacy frequency and normalizes string settings", () => { - const resolved = resolveMemoryDreamingConfig({ - pluginConfig: { - dreaming: { - mode: "deep", - cron: "0 */4 * * *", - frequency: "0 */12 * * *", - timezone: "Europe/London", - limit: "5", - minScore: "0.9", - minRecallCount: "4", - minUniqueQueries: "2", - recencyHalfLifeDays: "21", - maxAgeDays: "30", - verboseLogging: "true", - }, - }, - }); - - expect(resolved).toEqual({ - mode: "deep", - enabled: true, - cron: "0 */4 * * *", - timezone: "Europe/London", - limit: 5, - minScore: 0.9, - minRecallCount: 4, - minUniqueQueries: 2, - recencyHalfLifeDays: 21, - maxAgeDays: 30, - verboseLogging: true, - }); - }); - - it("falls back to cfg timezone and core defaults when mode is off", () => { - const cfg = { - agents: { - defaults: { - userTimezone: "America/Los_Angeles", - }, - }, - } as OpenClawConfig; - - const resolved = resolveMemoryDreamingConfig({ - pluginConfig: { - dreaming: { - mode: "off", - }, - }, - cfg, - }); - - expect(resolved.enabled).toBe(false); - expect(resolved.cron).toBe("0 3 * * *"); - expect(resolved.timezone).toBe("America/Los_Angeles"); - expect(resolved.limit).toBe(10); - expect(resolved.minScore).toBe(0.75); - expect(resolved.recencyHalfLifeDays).toBe(14); - expect(resolved.maxAgeDays).toBeUndefined(); - }); - - it("dedupes shared workspaces and skips agents without memory search", () => { - resolveMemorySearchConfig.mockImplementation((_cfg: OpenClawConfig, agentId: string) => - agentId === "beta" ? undefined : { 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(resolveMemoryDreamingWorkspaces(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(resolveMemoryDreamingWorkspaces(cfg)).toEqual([ - { - workspaceDir: "/workspace/fallback", - agentIds: ["fallback"], - }, - ]); - - expect( - formatMemoryDreamingDay(Date.parse("2026-04-02T06:30:00.000Z"), "America/Los_Angeles"), - ).toBe("2026-04-01"); - expect( - isSameMemoryDreamingDay( - 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: { - dreaming: { - mode: "core", - }, - }, - }, - }, - }, - } as OpenClawConfig), - ).toEqual({ - dreaming: { - mode: "core", - }, - }); - }); -}); diff --git a/src/memory-host-sdk/dreaming.ts b/src/memory-host-sdk/dreaming.ts deleted file mode 100644 index fb7f429607b..00000000000 --- a/src/memory-host-sdk/dreaming.ts +++ /dev/null @@ -1,300 +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_DREAMING_CRON_EXPR = "0 3 * * *"; -export const DEFAULT_MEMORY_DREAMING_LIMIT = 10; -export const DEFAULT_MEMORY_DREAMING_MIN_SCORE = 0.75; -export const DEFAULT_MEMORY_DREAMING_MIN_RECALL_COUNT = 3; -export const DEFAULT_MEMORY_DREAMING_MIN_UNIQUE_QUERIES = 2; -export const DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS = 14; -export const DEFAULT_MEMORY_DREAMING_MODE = "off"; -export const DEFAULT_MEMORY_DREAMING_PRESET = "core"; - -export type MemoryDreamingPreset = "core" | "deep" | "rem"; -export type MemoryDreamingMode = MemoryDreamingPreset | "off"; - -export type MemoryDreamingConfig = { - mode: MemoryDreamingMode; - enabled: boolean; - cron: string; - timezone?: string; - limit: number; - minScore: number; - minRecallCount: number; - minUniqueQueries: number; - recencyHalfLifeDays: number; - maxAgeDays?: number; - verboseLogging: boolean; -}; - -export type MemoryDreamingWorkspace = { - workspaceDir: string; - agentIds: string[]; -}; - -export const MEMORY_DREAMING_PRESET_DEFAULTS: Record< - MemoryDreamingPreset, - { - cron: string; - limit: number; - minScore: number; - minRecallCount: number; - minUniqueQueries: number; - recencyHalfLifeDays: number; - } -> = { - core: { - cron: DEFAULT_MEMORY_DREAMING_CRON_EXPR, - limit: DEFAULT_MEMORY_DREAMING_LIMIT, - minScore: DEFAULT_MEMORY_DREAMING_MIN_SCORE, - minRecallCount: DEFAULT_MEMORY_DREAMING_MIN_RECALL_COUNT, - minUniqueQueries: DEFAULT_MEMORY_DREAMING_MIN_UNIQUE_QUERIES, - recencyHalfLifeDays: DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS, - }, - deep: { - cron: "0 */12 * * *", - limit: DEFAULT_MEMORY_DREAMING_LIMIT, - minScore: 0.8, - minRecallCount: 3, - minUniqueQueries: 3, - recencyHalfLifeDays: DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS, - }, - rem: { - cron: "0 */6 * * *", - limit: DEFAULT_MEMORY_DREAMING_LIMIT, - minScore: 0.85, - minRecallCount: 4, - minUniqueQueries: 3, - recencyHalfLifeDays: DEFAULT_MEMORY_DREAMING_RECENCY_HALF_LIFE_DAYS, - }, -}; - -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 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 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 normalizePathForComparison(input: string): string { - const normalized = path.resolve(input); - if (process.platform === "win32") { - return normalized.toLowerCase(); - } - return 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 normalizeMemoryDreamingMode(value: unknown): MemoryDreamingMode { - const normalized = normalizeTrimmedString(value)?.toLowerCase(); - if ( - normalized === "off" || - normalized === "core" || - normalized === "deep" || - normalized === "rem" - ) { - return normalized; - } - return DEFAULT_MEMORY_DREAMING_MODE; -} - -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 resolveMemoryDreamingConfig(params: { - pluginConfig?: Record; - cfg?: OpenClawConfig; -}): MemoryDreamingConfig { - const dreaming = asRecord(params.pluginConfig?.dreaming); - const mode = normalizeMemoryDreamingMode(dreaming?.mode); - const enabled = mode !== "off"; - const preset: MemoryDreamingPreset = mode === "off" ? DEFAULT_MEMORY_DREAMING_PRESET : mode; - const defaults = MEMORY_DREAMING_PRESET_DEFAULTS[preset]; - const timezone = - normalizeTrimmedString(dreaming?.timezone) ?? - normalizeTrimmedString(params.cfg?.agents?.defaults?.userTimezone); - const maxAgeDays = normalizeOptionalPositiveInt(dreaming?.maxAgeDays); - return { - mode, - enabled, - cron: - normalizeTrimmedString(dreaming?.cron) ?? - normalizeTrimmedString(dreaming?.frequency) ?? - defaults.cron, - ...(timezone ? { timezone } : {}), - limit: normalizeNonNegativeInt(dreaming?.limit, defaults.limit), - minScore: normalizeScore(dreaming?.minScore, defaults.minScore), - minRecallCount: normalizeNonNegativeInt(dreaming?.minRecallCount, defaults.minRecallCount), - minUniqueQueries: normalizeNonNegativeInt( - dreaming?.minUniqueQueries, - defaults.minUniqueQueries, - ), - recencyHalfLifeDays: normalizeNonNegativeInt( - dreaming?.recencyHalfLifeDays, - defaults.recencyHalfLifeDays, - ), - ...(typeof maxAgeDays === "number" ? { maxAgeDays } : {}), - verboseLogging: normalizeBoolean(dreaming?.verboseLogging, false), - }; -} - -export function formatMemoryDreamingDay(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 isSameMemoryDreamingDay( - firstEpochMs: number, - secondEpochMs: number, - timezone?: string, -): boolean { - return ( - formatMemoryDreamingDay(firstEpochMs, timezone) === - formatMemoryDreamingDay(secondEpochMs, timezone) - ); -} - -export function resolveMemoryDreamingWorkspaces(cfg: OpenClawConfig): MemoryDreamingWorkspace[] { - 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()]; -}