diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfe10c9943..01abad8d741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai - Matrix/runtime: resolve the verification/bootstrap runtime from a distinct packaged Matrix entry so global npm installs stop failing on crypto bootstrap with missing-module or recursive runtime alias errors. (#59249) Thanks @gumadeiras. - Matrix/streaming: preserve ordered block flushes before tool, message, and agent boundaries, add explicit `channels.matrix.blockStreaming` opt-in so Matrix `streaming: "off"` stays final-only by default, and move MiniMax plain-text final handling into the MiniMax provider runtime instead of the shared core heuristic. (#59266) thanks @gumadeiras - Gateway/agents: fix stale run-context TTL cleanup so the new maintenance sweep compiles and resets orphaned run sequence state correctly. (#52731) thanks @artwalker +- Memory/lancedb: accept `dreaming` config when `memory-lancedb` owns the memory slot so Dreaming surfaces can read slot-owner settings without schema rejection. (#63874) Thanks @mbelinky. ## 2026.4.9 diff --git a/extensions/memory-lancedb/config.test.ts b/extensions/memory-lancedb/config.test.ts new file mode 100644 index 00000000000..4174c793cf1 --- /dev/null +++ b/extensions/memory-lancedb/config.test.ts @@ -0,0 +1,64 @@ +import fs from "node:fs"; +import { describe, expect, it } from "vitest"; +import { validateJsonSchemaValue } from "../../src/plugins/schema-validator.js"; +import { memoryConfigSchema } from "./config.js"; + +const manifest = JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf-8"), +) as { configSchema: Record }; + +describe("memory-lancedb config", () => { + it("accepts dreaming in the manifest schema and preserves it in runtime parsing", () => { + const manifestResult = validateJsonSchemaValue({ + schema: manifest.configSchema, + cacheKey: "memory-lancedb.manifest.dreaming", + value: { + embedding: { + apiKey: "sk-test", + }, + dreaming: { + enabled: true, + }, + }, + }); + + const parsed = memoryConfigSchema.parse({ + embedding: { + apiKey: "sk-test", + }, + dreaming: { + enabled: true, + }, + }); + + expect(manifestResult.ok).toBe(true); + expect(parsed.dreaming).toEqual({ + enabled: true, + }); + }); + + it("still rejects unrelated unknown top-level config keys", () => { + expect(() => { + memoryConfigSchema.parse({ + embedding: { + apiKey: "sk-test", + }, + dreaming: { + enabled: true, + }, + unexpected: true, + }); + }).toThrow("memory config has unknown keys: unexpected"); + }); + + it("rejects non-object dreaming values in runtime parsing", () => { + expect(() => { + memoryConfigSchema.parse({ + embedding: { + apiKey: "sk-test", + }, + dreaming: true, + }); + }).toThrow("dreaming config must be an object"); + }); +}); diff --git a/extensions/memory-lancedb/config.ts b/extensions/memory-lancedb/config.ts index 9b71e3992d0..29ae376a54f 100644 --- a/extensions/memory-lancedb/config.ts +++ b/extensions/memory-lancedb/config.ts @@ -10,6 +10,7 @@ export type MemoryConfig = { baseUrl?: string; dimensions?: number; }; + dreaming?: Record; dbPath?: string; autoCapture?: boolean; autoRecall?: boolean; @@ -97,7 +98,7 @@ export const memoryConfigSchema = { const cfg = value as Record; assertAllowedKeys( cfg, - ["embedding", "dbPath", "autoCapture", "autoRecall", "captureMaxChars"], + ["embedding", "dreaming", "dbPath", "autoCapture", "autoRecall", "captureMaxChars"], "memory config", ); @@ -118,6 +119,15 @@ export const memoryConfigSchema = { throw new Error("captureMaxChars must be between 100 and 10000"); } + const dreaming = + typeof cfg.dreaming === "undefined" + ? undefined + : cfg.dreaming && typeof cfg.dreaming === "object" && !Array.isArray(cfg.dreaming) + ? (cfg.dreaming as Record) + : (() => { + throw new Error("dreaming config must be an object"); + })(); + return { embedding: { provider: "openai", @@ -127,6 +137,7 @@ export const memoryConfigSchema = { typeof embedding.baseUrl === "string" ? resolveEnvVars(embedding.baseUrl) : undefined, dimensions: typeof embedding.dimensions === "number" ? embedding.dimensions : undefined, }, + dreaming, dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH, autoCapture: cfg.autoCapture === true, autoRecall: cfg.autoRecall !== false, diff --git a/extensions/memory-lancedb/openclaw.plugin.json b/extensions/memory-lancedb/openclaw.plugin.json index 754407380b5..b19e3f3a50f 100644 --- a/extensions/memory-lancedb/openclaw.plugin.json +++ b/extensions/memory-lancedb/openclaw.plugin.json @@ -38,6 +38,10 @@ "label": "Auto-Recall", "help": "Automatically inject relevant memories into context" }, + "dreaming": { + "label": "Dreaming", + "help": "Optional dreaming config consumed when this plugin owns the memory slot" + }, "captureMaxChars": { "label": "Capture Max Chars", "help": "Maximum message length eligible for auto-capture", @@ -77,6 +81,9 @@ "autoRecall": { "type": "boolean" }, + "dreaming": { + "type": "object" + }, "captureMaxChars": { "type": "number", "minimum": 100, diff --git a/src/memory-host-sdk/dreaming.test.ts b/src/memory-host-sdk/dreaming.test.ts index b5f2c9cbb3d..bbf51f45471 100644 --- a/src/memory-host-sdk/dreaming.test.ts +++ b/src/memory-host-sdk/dreaming.test.ts @@ -156,6 +156,9 @@ describe("memory dreaming host helpers", () => { "America/Los_Angeles", ), ).toBe(true); + }); + + it("resolves the configured memory-slot plugin id", () => { expect( resolveMemoryDreamingPluginId({ plugins: { @@ -165,6 +168,9 @@ describe("memory dreaming host helpers", () => { }, } as OpenClawConfig), ).toBe("memos-local-openclaw-plugin"); + }); + + it("reads dreaming config from the configured memory-slot owner", () => { expect( resolveMemoryDreamingPluginConfig({ plugins: { @@ -187,6 +193,36 @@ describe("memory dreaming host helpers", () => { enabled: true, }, }); + }); + + it("reads dreaming config from memory-lancedb when it owns the memory slot", () => { + expect( + resolveMemoryDreamingPluginConfig({ + plugins: { + slots: { + memory: "memory-lancedb", + }, + entries: { + "memory-lancedb": { + config: { + dreaming: { + enabled: true, + frequency: "0 */6 * * *", + }, + }, + }, + }, + }, + } as OpenClawConfig), + ).toEqual({ + dreaming: { + enabled: true, + frequency: "0 */6 * * *", + }, + }); + }); + + it("falls back to memory-core when no memory slot override is configured", () => { expect( resolveMemoryDreamingPluginConfig({ plugins: {