diff --git a/openclaw/CHANGELOG.md b/openclaw/CHANGELOG.md index bc9742379..f020c5555 100644 --- a/openclaw/CHANGELOG.md +++ b/openclaw/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in this file. +## [1.0.5] - 2026-04-07 + +### Fixed +- **Init interactive choice bug**: Fixed number selection in `openclaw mem0 init` — entering 1/2/3 now correctly selects the corresponding option (was broken by readline prefill concatenating with user input) +- **OSS pgvector crash** ([#4727]): Fixed "Client has already been connected" cascade when using pgvector in OSS mode. The warmup call swallowed errors leaving a half-initialized pg client; concurrent recall/capture then all hit `client.connect()` on the same client. Fix: let warmup errors propagate (so `initPromise` resets and retries with a fresh Memory + fresh pg client) and build fresh config objects per attempt instead of mutating shared state. + +### Removed +- **`orgId` / `projectId` config parameters**: Removed from config schema, CLI (`config show/get/set`), init display, and providers. The API key is project-scoped, so separate org/project IDs are unnecessary and could cause access errors if mismatched. +- **`enableGraph` config parameter**: Removed from all config surfaces, providers, backend, and tools. Graph memory is being deprecated — removing the flag avoids unnecessary exposure. + ## [1.0.4] - 2026-04-04 ### Added diff --git a/openclaw/backend/base.ts b/openclaw/backend/base.ts index d34d6ee3b..6a52a3922 100644 --- a/openclaw/backend/base.ts +++ b/openclaw/backend/base.ts @@ -14,7 +14,6 @@ export interface AddOptions { infer?: boolean; expires?: string; categories?: string[]; - enableGraph?: boolean; } export interface SearchOptions { @@ -28,7 +27,6 @@ export interface SearchOptions { keyword?: boolean; filters?: Record; fields?: string[]; - enableGraph?: boolean; } export interface ListOptions { @@ -41,7 +39,6 @@ export interface ListOptions { category?: string; after?: string; before?: string; - enableGraph?: boolean; } export interface DeleteOptions { diff --git a/openclaw/backend/platform.ts b/openclaw/backend/platform.ts index 66865c2cb..481de5873 100644 --- a/openclaw/backend/platform.ts +++ b/openclaw/backend/platform.ts @@ -111,7 +111,6 @@ export class PlatformBackend implements Backend { if (opts.infer === false) payload.infer = false; if (opts.expires) payload.expiration_date = opts.expires; if (opts.categories) payload.categories = opts.categories; - if (opts.enableGraph) payload.enable_graph = true; return (await this._request("POST", "/v1/memories/", { json: payload, @@ -171,7 +170,6 @@ export class PlatformBackend implements Backend { if (opts.rerank) payload.rerank = true; if (opts.keyword) payload.keyword_search = true; if (opts.fields) payload.fields = opts.fields; - if (opts.enableGraph) payload.enable_graph = true; const result = (await this._request("POST", "/v2/memories/search/", { json: payload, @@ -222,7 +220,6 @@ export class PlatformBackend implements Backend { extraFilters: Object.keys(extra).length > 0 ? extra : undefined, }); if (apiFilters) payload.filters = apiFilters; - if (opts.enableGraph) payload.enable_graph = true; const result = (await this._request("POST", "/v2/memories/", { json: payload, diff --git a/openclaw/cli/commands.ts b/openclaw/cli/commands.ts index 015279e3a..603f5a002 100644 --- a/openclaw/cli/commands.ts +++ b/openclaw/cli/commands.ts @@ -384,10 +384,6 @@ export function registerCliCommands( console.log(` User ID: ${existingAuth.userId}`); if (existingAuth.mode) console.log(` Mode: ${existingAuth.mode}`); - if (existingAuth.orgId) - console.log(` Org ID: ${existingAuth.orgId}`); - if (existingAuth.projectId) - console.log(` Project: ${existingAuth.projectId}`); console.log(""); // Validate existing key before asking @@ -408,7 +404,7 @@ export function registerCliCommands( } const reuse = await promptInput( - " Keep existing configuration? (Y/n): ", + " Keep existing configuration? (y/n): ", ); if ( reuse === "" || @@ -432,7 +428,7 @@ export function registerCliCommands( console.log(" 2. Enter API key manually"); console.log(" 3. Open-source mode (self-hosted)\n"); - const choice = (await promptInput(" Choice: ", "1")) || "1"; + const choice = (await promptInput(" Choice (1/2/3): ")) || "1"; if (choice === "1") { // --- Email interactive flow --- @@ -952,9 +948,6 @@ export function registerCliCommands( email: "userEmail", base_url: "baseUrl", user_id: "userId", - org_id: "orgId", - project_id: "projectId", - enable_graph: "enableGraph", auto_recall: "autoRecall", auto_capture: "autoCapture", top_k: "topK", @@ -969,6 +962,8 @@ export function registerCliCommands( vector_host: "oss.vectorStore.config.host", vector_port: "oss.vectorStore.config.port", collection_name: "oss.vectorStore.config.collectionName", + vector_db_name: "oss.vectorStore.config.dbname", + vector_db_user: "oss.vectorStore.config.user", vector_db_path: "oss.vectorStore.config.dbPath", history_db_path: "oss.historyDbPath", disable_history: "oss.disableHistory", @@ -979,7 +974,6 @@ export function registerCliCommands( // Boolean config fields — coerce "true"/"1"/"yes" on set const BOOLEAN_KEYS = new Set([ - "enableGraph", "autoRecall", "autoCapture", "oss.disableHistory", @@ -1009,11 +1003,8 @@ export function registerCliCommands( apiKey: auth.apiKey ?? cfg.apiKey, baseUrl: auth.baseUrl ?? cfg.baseUrl ?? "https://api.mem0.ai", userId: auth.userId ?? cfg.userId, - orgId: auth.orgId ?? cfg.orgId, - projectId: auth.projectId ?? cfg.projectId, mode: auth.mode ?? cfg.mode, userEmail: auth.userEmail, - enableGraph: cfg.enableGraph, autoRecall: cfg.autoRecall, autoCapture: cfg.autoCapture, topK: cfg.topK, @@ -1055,9 +1046,6 @@ export function registerCliCommands( entries.push( ["api_key", "apiKey"], ["email", "userEmail"], - ["org_id", "orgId"], - ["project_id", "projectId"], - ["enable_graph", "enableGraph"], ); } else { entries.push( @@ -1238,6 +1226,10 @@ export function registerCliCommands( .description("List recent background events") .action(async () => { try { + if (!backend || cfg.mode === "open-source") { + console.log("Event tracking is only available in platform mode."); + return; + } const results = await backend.listEvents(); if (!results.length) { console.log("No events found."); @@ -1282,6 +1274,10 @@ export function registerCliCommands( .argument("", "Event ID to check") .action(async (eventId: string) => { try { + if (!backend || cfg.mode === "open-source") { + console.log("Event tracking is only available in platform mode."); + return; + } const ev = await backend.getEvent(eventId); const status = String(ev.status ?? "—"); diff --git a/openclaw/cli/config-file.ts b/openclaw/cli/config-file.ts index 060b96d0d..86287f644 100644 --- a/openclaw/cli/config-file.ts +++ b/openclaw/cli/config-file.ts @@ -29,11 +29,8 @@ export interface PluginAuthConfig { apiKey?: string; baseUrl?: string; userId?: string; - orgId?: string; - projectId?: string; userEmail?: string; mode?: string; - enableGraph?: boolean; autoRecall?: boolean; autoCapture?: boolean; topK?: number; @@ -76,11 +73,8 @@ export function readPluginAuth(): PluginAuthConfig { apiKey: (cfg.apiKey ?? cfg.api_key) as string | undefined, baseUrl: (cfg.baseUrl ?? cfg.base_url) as string | undefined, userId: (cfg.userId ?? cfg.user_id) as string | undefined, - orgId: (cfg.orgId ?? cfg.org_id) as string | undefined, - projectId: (cfg.projectId ?? cfg.project_id) as string | undefined, userEmail: (cfg.userEmail ?? cfg.user_email) as string | undefined, mode: cfg.mode as string | undefined, - enableGraph: cfg.enableGraph as boolean | undefined, autoRecall: cfg.autoRecall as boolean | undefined, autoCapture: cfg.autoCapture as boolean | undefined, topK: cfg.topK as number | undefined, diff --git a/openclaw/config.ts b/openclaw/config.ts index 2b16ba2ac..94cb1226c 100644 --- a/openclaw/config.ts +++ b/openclaw/config.ts @@ -19,8 +19,6 @@ import type { Mem0Config, Mem0Mode } from "./types.ts"; export interface FileConfig { apiKey?: string; baseUrl?: string; - orgId?: string; - projectId?: string; } // ============================================================================ @@ -152,14 +150,11 @@ const ALLOWED_KEYS = [ "baseUrl", "userId", "userEmail", - "orgId", - "projectId", "autoCapture", "autoRecall", "customInstructions", "customCategories", "customPrompt", - "enableGraph", "searchThreshold", "topK", "oss", @@ -185,6 +180,15 @@ export const mem0ConfigSchema = { assertAllowedKeys(cfg, ALLOWED_KEYS, "openclaw-mem0 config"); // Only two modes: "platform" (default) or "open-source" + if ( + typeof cfg.mode === "string" && + cfg.mode !== "platform" && + cfg.mode !== "open-source" + ) { + console.warn( + `[mem0] Unknown mode "${cfg.mode}" — expected "platform" or "open-source". Defaulting to "platform".`, + ); + } const mode: Mem0Mode = cfg.mode === "open-source" ? "open-source" : "platform"; @@ -193,15 +197,9 @@ export const mem0ConfigSchema = { typeof cfg.apiKey === "string" ? cfg.apiKey : undefined; let resolvedBaseUrl = typeof cfg.baseUrl === "string" ? cfg.baseUrl : undefined; - let resolvedOrgId = typeof cfg.orgId === "string" ? cfg.orgId : undefined; - let resolvedProjectId = - typeof cfg.projectId === "string" ? cfg.projectId : undefined; if (mode === "platform" && !resolvedApiKey && fileConfig) { if (fileConfig.apiKey) resolvedApiKey = fileConfig.apiKey; if (fileConfig.baseUrl) resolvedBaseUrl = fileConfig.baseUrl; - if (!resolvedOrgId && fileConfig.orgId) resolvedOrgId = fileConfig.orgId; - if (!resolvedProjectId && fileConfig.projectId) - resolvedProjectId = fileConfig.projectId; } // Platform mode requires apiKey — but don't throw on missing config. @@ -228,8 +226,6 @@ export const mem0ConfigSchema = { return "default"; } })(), - orgId: resolvedOrgId, - projectId: resolvedProjectId, autoCapture: cfg.autoCapture !== false, autoRecall: cfg.autoRecall !== false, customInstructions: @@ -246,7 +242,6 @@ export const mem0ConfigSchema = { typeof cfg.customPrompt === "string" ? cfg.customPrompt : DEFAULT_CUSTOM_INSTRUCTIONS, - enableGraph: cfg.enableGraph === true, searchThreshold: typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.5, topK: typeof cfg.topK === "number" ? cfg.topK : 5, diff --git a/openclaw/index.ts b/openclaw/index.ts index 32e3648cb..8eddd37fb 100644 --- a/openclaw/index.ts +++ b/openclaw/index.ts @@ -103,15 +103,21 @@ const memoryPlugin = definePluginEntry({ const fileConfig: FileConfig = { apiKey: pluginAuth.apiKey, baseUrl: pluginAuth.baseUrl, - orgId: pluginAuth.orgId, - projectId: pluginAuth.projectId, }; const cfg = mem0ConfigSchema.parse(api.pluginConfig, fileConfig); // Telemetry context bound to this plugin instance's config - const telemetryCtx = { apiKey: cfg.apiKey, mode: cfg.mode, skillsActive: false }; + const telemetryCtx = { + apiKey: cfg.apiKey, + mode: cfg.mode, + skillsActive: false, + }; const _captureEvent = (event: string, props?: Record) => { - try { captureEvent(event, props, telemetryCtx); } catch { /* silently swallow */ } + try { + captureEvent(event, props, telemetryCtx); + } catch { + /* silently swallow */ + } }; if (cfg.needsSetup) { @@ -182,7 +188,7 @@ const memoryPlugin = definePluginEntry({ }); api.logger.info( - `openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, graph: ${cfg.enableGraph}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`, + `openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`, ); // Helper: build add options @@ -197,7 +203,6 @@ const memoryPlugin = definePluginEntry({ }; if (runId) opts.run_id = runId; if (cfg.mode === "platform") { - opts.enable_graph = cfg.enableGraph; opts.output_format = "v1.1"; } return opts; @@ -242,7 +247,10 @@ const memoryPlugin = definePluginEntry({ getCurrentSessionId: () => currentSessionId, skillsActive, captureToolEvent: (toolName: string, props: Record) => { - _captureEvent(`openclaw.tool.${toolName}`, { tool_name: toolName, ...props }); + _captureEvent(`openclaw.tool.${toolName}`, { + tool_name: toolName, + ...props, + }); }, }; registerAllTools(toolDeps); @@ -328,7 +336,10 @@ function registerHooks( getStateDir: () => string | undefined; }, skillsActive: boolean = false, - _captureEvent: (event: string, props?: Record) => void = () => {}, + _captureEvent: ( + event: string, + props?: Record, + ) => void = () => {}, ) { // ======================================================================== // SKILLS MODE: Agentic memory via before_prompt_build @@ -355,16 +366,17 @@ function registerHooks( return; } - // Skip recall for system/bootstrap prompts. These are OpenClaw internal - // commands (/new, /reset) that contain system instructions, not user queries. - // Sending them to mem0 search wastes API calls and returns noise. const promptLower = event.prompt.toLowerCase(); + const isChannelSystemEvent = /^system(?:\s*\(untrusted\))?:\s*\[/i.test( + event.prompt, + ); const isSystemPrompt = - promptLower.includes("a new session was started") || - promptLower.includes("session startup sequence") || - promptLower.includes("/new or /reset") || - promptLower.startsWith("system:") || - promptLower.startsWith("run your session"); + !isChannelSystemEvent && + (promptLower.includes("a new session was started") || + promptLower.includes("session startup sequence") || + promptLower.includes("/new or /reset") || + promptLower.startsWith("system:") || + promptLower.startsWith("run your session")); if (isSystemPrompt) { api.logger.info( "openclaw-mem0: skills-mode skipping recall for system/bootstrap prompt", @@ -472,7 +484,10 @@ function registerHooks( "\n"; // Track which session triggered dream (session-keyed, not global) dreamSessionId = sessionId; - _captureEvent("openclaw.hook.dream", { phase: "triggered", memory_count: memCount }); + _captureEvent("openclaw.hook.dream", { + phase: "triggered", + memory_count: memCount, + }); api.logger.info( `openclaw-mem0: auto-dream triggered (${memCount} memories, gate passed)`, ); @@ -545,7 +560,10 @@ function registerHooks( if (writeToolUsed) { releaseDreamLock(stateDir); recordDreamCompletion(stateDir); - _captureEvent("openclaw.hook.dream", { phase: "completed", write_tools_used: true }); + _captureEvent("openclaw.hook.dream", { + phase: "completed", + write_tools_used: true, + }); api.logger.info( "openclaw-mem0: auto-dream completed (verified write tool usage), lock released", ); @@ -599,14 +617,17 @@ function registerHooks( return; } - // Skip recall for system/bootstrap prompts to save API calls const promptLower = event.prompt.toLowerCase(); + const isChannelSystemEvent = /^system(?:\s*\(untrusted\))?:\s*\[/i.test( + event.prompt, + ); const isSystemPrompt = - promptLower.includes("a new session was started") || - promptLower.includes("session startup sequence") || - promptLower.includes("/new or /reset") || - promptLower.startsWith("system:") || - promptLower.startsWith("run your session"); + !isChannelSystemEvent && + (promptLower.includes("a new session was started") || + promptLower.includes("session startup sequence") || + promptLower.includes("/new or /reset") || + promptLower.startsWith("system:") || + promptLower.startsWith("run your session")); if (isSystemPrompt) { api.logger.info( "openclaw-mem0: skipping recall for system/bootstrap prompt", @@ -775,11 +796,17 @@ function registerHooks( // Update shared state for tools (best-effort — tools don't have ctx) if (sessionId) session.setCurrentSessionId(sessionId); - const MEMORY_MUTATE_TOOLS = new Set(["memory_add", "memory_update", "memory_delete"]); + const MEMORY_MUTATE_TOOLS = new Set([ + "memory_add", + "memory_update", + "memory_delete", + ]); const agentUsedMemoryTool = event.messages.some((msg: any) => { - if (msg?.role !== "assistant" || !Array.isArray(msg?.content)) return false; + if (msg?.role !== "assistant" || !Array.isArray(msg?.content)) + return false; return msg.content.some( - (block: any) => block?.type === "tool_use" && MEMORY_MUTATE_TOOLS.has(block.name), + (block: any) => + block?.type === "tool_use" && MEMORY_MUTATE_TOOLS.has(block.name), ); }); if (agentUsedMemoryTool) { @@ -848,7 +875,10 @@ function registerHooks( if (!textContent) continue; } // Strip OpenClaw sender metadata prefix (prevents storing TUI identity as memory) - if (textContent.includes("Sender") && textContent.includes("untrusted metadata")) { + if ( + textContent.includes("Sender") && + textContent.includes("untrusted metadata") + ) { textContent = textContent .replace( /Sender\s*\(untrusted metadata\):\s*```json[\s\S]*?```\s*/gi, diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json index 70f56d6f1..1f1abff2e 100644 --- a/openclaw/openclaw.plugin.json +++ b/openclaw/openclaw.plugin.json @@ -2,6 +2,7 @@ "id": "openclaw-mem0", "name": "Memory (Mem0)", "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source", + "version": "1.0.5", "kind": "memory", "skills": ["skills"], "contracts": { diff --git a/openclaw/package.json b/openclaw/package.json index 125141bce..ff38848cc 100644 --- a/openclaw/package.json +++ b/openclaw/package.json @@ -1,6 +1,6 @@ { "name": "@mem0/openclaw-mem0", - "version": "1.0.4", + "version": "1.0.5", "type": "module", "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source", "license": "Apache-2.0", @@ -42,12 +42,12 @@ "./dist/index.js" ], "compat": { - "pluginApi": ">=2026.3.24-beta.2", - "minGatewayVersion": "2026.3.24-beta.2" + "pluginApi": ">=2026.3.28 <=2026.4.2", + "minGatewayVersion": "2026.3.28" }, "build": { - "openclawVersion": "2026.3.24-beta.2", - "pluginSdkVersion": "2026.3.24-beta.2" + "openclawVersion": "2026.4.1", + "pluginSdkVersion": "2026.4.1" } }, "devDependencies": { diff --git a/openclaw/providers.ts b/openclaw/providers.ts index 22a56226c..565554a44 100644 --- a/openclaw/providers.ts +++ b/openclaw/providers.ts @@ -82,8 +82,6 @@ class PlatformProvider implements Mem0Provider { constructor( private readonly apiKey: string, private readonly baseUrl?: string, - private readonly orgId?: string, - private readonly projectId?: string, ) {} private async ensureClient(): Promise { @@ -101,14 +99,10 @@ class PlatformProvider implements Mem0Provider { const opts: { apiKey: string; host?: string; - organizationId?: string; - projectId?: string; } = { apiKey: this.apiKey, }; if (this.baseUrl) opts.host = this.baseUrl; - if (this.orgId) opts.organizationId = this.orgId; - if (this.projectId) opts.projectId = this.projectId; this.client = new MemoryClient(opts); } @@ -123,7 +117,6 @@ class PlatformProvider implements Mem0Provider { opts.custom_instructions = options.custom_instructions; if (options.custom_categories) opts.custom_categories = options.custom_categories; - if (options.enable_graph) opts.enable_graph = options.enable_graph; if (options.output_format) opts.output_format = options.output_format; if (options.source) opts.source = options.source; // Agentic harness: direct storage bypass @@ -208,9 +201,7 @@ class PlatformProvider implements Mem0Provider { await this.client.deleteAll({ user_id: userId }); } - async history( - memoryId: string, - ): Promise< + async history(memoryId: string): Promise< Array<{ id: string; old_memory: string; @@ -249,23 +240,29 @@ class OSSProvider implements Mem0Provider { return this.initPromise; } - private async _init(): Promise { - const { Memory } = await import("mem0ai/oss"); - + private _buildConfig(disableHistory = false): Record { const config: Record = { version: "v1.1" }; - const defaultEmbedder = { provider: "openai", config: { model: "text-embedding-3-small" } }; + const defaultEmbedder = { + provider: "openai", + config: { model: "text-embedding-3-small" }, + }; const defaultLlm = { provider: "openai", config: { model: "gpt-5.4" } }; - // Helper: strip empty-string values so they don't clobber defaults const stripEmpty = (obj: Record) => { const out = { ...obj }; - for (const k of Object.keys(out)) { if (out[k] === "") delete out[k]; } + for (const k of Object.keys(out)) { + if (out[k] === "") delete out[k]; + } return out; }; if (this.ossConfig?.embedder) { const ec = stripEmpty(this.ossConfig.embedder.config ?? {}); + if (ec.host && !ec.url) { + ec.url = ec.host; + delete ec.host; + } config.embedder = { provider: this.ossConfig.embedder.provider || defaultEmbedder.provider, config: { ...defaultEmbedder.config, ...ec }, @@ -276,6 +273,10 @@ class OSSProvider implements Mem0Provider { if (this.ossConfig?.llm) { const lc = stripEmpty(this.ossConfig.llm.config ?? {}); + if (lc.host && !lc.url) { + lc.url = lc.host; + delete lc.host; + } config.llm = { provider: this.ossConfig.llm.provider || defaultLlm.provider, config: { ...defaultLlm.config, ...lc }, @@ -285,7 +286,7 @@ class OSSProvider implements Mem0Provider { } if (this.ossConfig?.vectorStore) - config.vectorStore = this.ossConfig.vectorStore; + config.vectorStore = { ...this.ossConfig.vectorStore }; if (this.ossConfig?.historyDbPath) { const dbPath = this.resolvePath @@ -294,42 +295,61 @@ class OSSProvider implements Mem0Provider { config.historyDbPath = dbPath; } - if (this.ossConfig?.disableHistory) { + if (disableHistory || this.ossConfig?.disableHistory) { config.disableHistory = true; } if (this.customPrompt) config.customPrompt = this.customPrompt; + return config; + } + private async _init(): Promise { + const mod = await import("mem0ai/oss"); + const Memory = mod.Memory; + for (const cls of ["PGVector", "RedisDB", "Qdrant"]) { + const VectorCls = (mod as any)[cls]; + if (!VectorCls || VectorCls.prototype.__patched) continue; + const origInit = VectorCls.prototype.initialize; + VectorCls.prototype.initialize = function (this: any) { + if (!this.config?.embeddingModelDims && this.config?.dimension) { + this.config.embeddingModelDims = this.config.dimension; + } + // Qdrant reads this.dimension directly + if (!this.dimension && this.config?.dimension) { + this.dimension = this.config.dimension; + } + // Skip premature constructor call when dimensions unknown + const dims = this.config?.embeddingModelDims ?? this.dimension; + if (!dims) return Promise.resolve(); + // Run the real initialize only once + if (!this._initializePromise) { + this._initializePromise = origInit.call(this); + } + return this._initializePromise; + }; + VectorCls.prototype.__patched = true; + } + + let mem: any; try { - this.memory = new Memory(config); + mem = new Memory(this._buildConfig()); } catch (err) { - // If initialization fails (e.g. native SQLite binding resolution under - // jiti), retry with history disabled — the history DB is the most common - // source of native-binding failures and is not required for core - // memory operations. - if (!config.disableHistory) { + // If constructor fails (e.g. native SQLite binding under jiti/Docker), + // retry with a FRESH config that has history disabled. + if (!this.ossConfig?.disableHistory) { console.warn( "[mem0] Memory initialization failed, retrying with history disabled:", err instanceof Error ? err.message : err, ); - config.disableHistory = true; - this.memory = new Memory(config); + mem = new Memory(this._buildConfig(true)); } else { throw err; } } - // Force the SDK's internal auto-initialization to complete now. - // Without this, concurrent method calls (e.g. auto-recall + search) - // both trigger _autoInitialize() simultaneously, causing PGVector's - // pg client to call connect() twice → "Client has already been - // connected" crash. (#4638) - try { - await this.memory.getAll({ userId: "__mem0_warmup__" }); - } catch { - // Warmup errors are non-fatal — the SDK may still work for - // subsequent calls once its internal state settles. - } + await mem.getAll({ userId: "__mem0_warmup__" }); + + this.memory = mem; } async add( @@ -423,9 +443,7 @@ class OSSProvider implements Mem0Provider { await this.memory.deleteAll({ userId }); } - async history( - memoryId: string, - ): Promise< + async history(memoryId: string): Promise< Array<{ id: string; old_memory: string; @@ -438,8 +456,12 @@ class OSSProvider implements Mem0Provider { try { const result = await this.memory.history(memoryId); return Array.isArray(result) ? result : []; - } catch { - // OSS may not support history depending on config + } catch (err) { + // OSS may not support history depending on config (e.g. disableHistory) + console.warn( + "[mem0] OSS history() failed:", + err instanceof Error ? err.message : err, + ); return []; } } @@ -459,7 +481,7 @@ export function createProvider( ); } - return new PlatformProvider(cfg.apiKey!, cfg.baseUrl, cfg.orgId, cfg.projectId); + return new PlatformProvider(cfg.apiKey!, cfg.baseUrl); } // ============================================================================ @@ -489,7 +511,6 @@ export function providerToBackend( ...(opts.immutable && { immutable: true }), ...(opts.infer === false && { infer: false }), ...(opts.expires && { expiration_date: opts.expires }), - ...(opts.enableGraph && { enable_graph: true }), }, ); return result as unknown as Record; diff --git a/openclaw/skills/memory-dream/SKILL.md b/openclaw/skills/memory-dream/SKILL.md index 651bdcd83..9224eafea 100644 --- a/openclaw/skills/memory-dream/SKILL.md +++ b/openclaw/skills/memory-dream/SKILL.md @@ -7,7 +7,7 @@ description: > Also triggers automatically after sufficient activity (configurable). user-invocable: true metadata: - {"openclaw": {"emoji": "💤"}} + {"openclaw": {"emoji": "💤", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}} --- # Memory Consolidation diff --git a/openclaw/skills/memory-triage/SKILL.md b/openclaw/skills/memory-triage/SKILL.md index 7f77bc652..102c1d17e 100644 --- a/openclaw/skills/memory-triage/SKILL.md +++ b/openclaw/skills/memory-triage/SKILL.md @@ -8,7 +8,7 @@ description: > Read this skill at the start of every session. user-invocable: false metadata: - {"openclaw": {"always": true, "emoji": "🧠"}} + {"openclaw": {"always": true, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}} --- # Memory Protocol diff --git a/openclaw/sqlite-resilience.test.ts b/openclaw/sqlite-resilience.test.ts index 3506d2a64..026e0b94d 100644 --- a/openclaw/sqlite-resilience.test.ts +++ b/openclaw/sqlite-resilience.test.ts @@ -4,9 +4,18 @@ * 2. initPromise poisoning fix (retry after failure) * 3. Graceful SQLite fallback in OSSProvider */ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { mem0ConfigSchema, createProvider } from "./index.ts"; +/** Stub vector-store classes required by OSSProvider._init's patching loop. */ +function vectorStubs() { + return { + PGVector: class { initialize() { return Promise.resolve(); } }, + RedisDB: class { initialize() { return Promise.resolve(); } }, + Qdrant: class { initialize() { return Promise.resolve(); } }, + }; +} + // --------------------------------------------------------------------------- // 1. Config: disableHistory passthrough // --------------------------------------------------------------------------- @@ -60,6 +69,7 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => { beforeEach(() => { capturedConfig = undefined; memoryCallCount = 0; + vi.resetModules(); vi.doMock("mem0ai/oss", () => ({ Memory: class MockMemory { @@ -81,9 +91,14 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => { } async delete() {} }, + ...vectorStubs(), })); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("passes disableHistory: true to Memory when configured", async () => { const { createProvider } = await import("./index.ts"); const cfg = mem0ConfigSchema.parse({ @@ -130,6 +145,7 @@ describe("OSSProvider — initPromise retry after failure", () => { beforeEach(() => { callCount = 0; + vi.resetModules(); vi.doMock("mem0ai/oss", () => ({ Memory: class MockMemory { @@ -154,9 +170,14 @@ describe("OSSProvider — initPromise retry after failure", () => { } async delete() {} }, + ...vectorStubs(), })); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("retries initialization after a transient failure", async () => { const { createProvider } = await import("./index.ts"); const cfg = mem0ConfigSchema.parse({ @@ -184,14 +205,21 @@ describe("OSSProvider — initPromise retry after failure", () => { // --------------------------------------------------------------------------- describe("OSSProvider — graceful SQLite fallback", () => { let capturedConfigs: Record[]; + /** When set, the mock Memory constructor always throws with this message. */ + let forceConstructorError: string | null; beforeEach(() => { capturedConfigs = []; + forceConstructorError = null; + vi.resetModules(); vi.doMock("mem0ai/oss", () => ({ Memory: class MockMemory { constructor(config: Record) { capturedConfigs.push({ ...config }); + if (forceConstructorError) { + throw new Error(forceConstructorError); + } if (!config.disableHistory) { throw new Error("Could not locate the bindings file"); } @@ -211,9 +239,14 @@ describe("OSSProvider — graceful SQLite fallback", () => { } async delete() {} }, + ...vectorStubs(), })); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("retries with disableHistory: true when initial construction fails", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const { createProvider } = await import("./index.ts"); @@ -242,14 +275,8 @@ describe("OSSProvider — graceful SQLite fallback", () => { }); it("does not retry when disableHistory is already true", async () => { - vi.doMock("mem0ai/oss", () => ({ - Memory: class MockMemory { - constructor(config: Record) { - // Fail even with disableHistory (e.g. vector store issue) - throw new Error("vector store connection refused"); - } - }, - })); + // Force the constructor to always throw, regardless of disableHistory + forceConstructorError = "vector store connection refused"; const { createProvider } = await import("./index.ts"); const cfg = mem0ConfigSchema.parse({ @@ -274,6 +301,7 @@ describe("PlatformProvider — initPromise retry after failure", () => { beforeEach(() => { callCount = 0; + vi.resetModules(); vi.doMock("mem0ai", () => ({ default: class MockMemoryClient { @@ -300,6 +328,10 @@ describe("PlatformProvider — initPromise retry after failure", () => { })); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("retries initialization after a transient failure", async () => { const { createProvider } = await import("./index.ts"); const cfg = mem0ConfigSchema.parse({ @@ -320,3 +352,349 @@ describe("PlatformProvider — initPromise retry after failure", () => { expect(callCount).toBe(2); }); }); + +// --------------------------------------------------------------------------- +// 6. OSSProvider: _buildConfig covers all branches +// --------------------------------------------------------------------------- +describe("OSSProvider — _buildConfig branch coverage", () => { + let capturedConfig: Record | undefined; + + beforeEach(() => { + capturedConfig = undefined; + vi.resetModules(); + + vi.doMock("mem0ai/oss", () => ({ + Memory: class MockMemory { + constructor(config: Record) { + capturedConfig = { ...config }; + } + async search() { return { results: [] }; } + async get() { return {}; } + async getAll() { return []; } + async add() { return { results: [] }; } + async delete() {} + }, + ...vectorStubs(), + })); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("builds config with custom embedder, llm, vectorStore, and historyDbPath", async () => { + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { + embedder: { provider: "openai", config: { apiKey: "sk-e", model: "text-embedding-3-small" } }, + llm: { provider: "openai", config: { apiKey: "sk-l", model: "gpt-4" } }, + vectorStore: { provider: "qdrant", config: { host: "localhost", port: 6333 } }, + historyDbPath: "/tmp/history.db", + disableHistory: true, + }, + }); + const api = { resolvePath: (p: string) => `/resolved${p}` } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + expect(capturedConfig).toBeDefined(); + expect(capturedConfig!.embedder).toEqual({ + provider: "openai", + config: { model: "text-embedding-3-small", apiKey: "sk-e" }, + }); + expect(capturedConfig!.llm).toEqual({ + provider: "openai", + config: expect.objectContaining({ model: "gpt-4", apiKey: "sk-l" }), + }); + expect(capturedConfig!.vectorStore).toEqual({ provider: "qdrant", config: { host: "localhost", port: 6333 } }); + expect(capturedConfig!.historyDbPath).toBe("/resolved/tmp/history.db"); + expect(capturedConfig!.disableHistory).toBe(true); + }); + + it("strips empty-string values from embedder and llm config", async () => { + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { + embedder: { provider: "openai", config: { apiKey: "", model: "custom-model" } }, + llm: { provider: "openai", config: { apiKey: "", model: "" } }, + disableHistory: true, + }, + }); + const api = { resolvePath: (p: string) => p } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + expect(capturedConfig).toBeDefined(); + // Empty apiKey should be stripped, leaving only the non-empty model + const embedderCfg = (capturedConfig!.embedder as any).config; + expect(embedderCfg.apiKey).toBeUndefined(); + expect(embedderCfg.model).toBe("custom-model"); + // Both empty keys in llm should be stripped, defaults applied + const llmCfg = (capturedConfig!.llm as any).config; + expect(llmCfg.apiKey).toBeUndefined(); + }); + + it("falls back to default provider when embedder/llm provider is empty", async () => { + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { + embedder: { provider: "", config: { apiKey: "sk-e" } }, + llm: { provider: "", config: { apiKey: "sk-l" } }, + disableHistory: true, + }, + }); + const api = { resolvePath: (p: string) => p } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + expect(capturedConfig).toBeDefined(); + // Empty provider should fall back to "openai" default + expect((capturedConfig!.embedder as any).provider).toBe("openai"); + expect((capturedConfig!.llm as any).provider).toBe("openai"); + }); +}); + +// --------------------------------------------------------------------------- +// 7. OSSProvider: vector store dimension patching +// --------------------------------------------------------------------------- +describe("OSSProvider — vector store dimension patching", () => { + let capturedModule: any; + + beforeEach(() => { + vi.resetModules(); + + vi.doMock("mem0ai/oss", () => { + const mod = { + Memory: class MockMemory { + constructor() {} + async search() { return { results: [] }; } + async get() { return {}; } + async getAll() { return []; } + async add() { return { results: [] }; } + async delete() {} + }, + PGVector: class { + config: any; + dimension: any; + _initializePromise: any; + initialize() { return Promise.resolve("pg-initialized"); } + }, + RedisDB: class { + config: any; + _initializePromise: any; + initialize() { return Promise.resolve("redis-initialized"); } + }, + Qdrant: class { + config: any; + dimension: any; + _initializePromise: any; + initialize() { return Promise.resolve("qdrant-initialized"); } + }, + }; + capturedModule = mod; + return mod; + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + async function triggerInit() { + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { disableHistory: true }, + }); + const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any); + await provider.search("test", { user_id: "u1" }); + } + + it("copies config.dimension to embeddingModelDims and this.dimension", async () => { + await triggerInit(); + + const pg = new capturedModule.PGVector(); + pg.config = { dimension: 1536 }; + await pg.initialize(); + + expect(pg.config.embeddingModelDims).toBe(1536); + expect(pg.dimension).toBe(1536); + }); + + it("returns resolved promise when no dimensions are known", async () => { + await triggerInit(); + + const pg = new capturedModule.PGVector(); + pg.config = {}; + const result = await pg.initialize(); + expect(result).toBeUndefined(); + }); + + it("runs original initialize only once via cached promise", async () => { + await triggerInit(); + + const q = new capturedModule.Qdrant(); + q.config = { dimension: 768 }; + + const first = await q.initialize(); + const second = await q.initialize(); + expect(first).toBe("qdrant-initialized"); + expect(second).toBe("qdrant-initialized"); + expect(q._initializePromise).toBeDefined(); + }); + + it("skips missing vector store classes without crashing", async () => { + // Override with a mock that omits PGVector entirely + vi.resetModules(); + vi.doMock("mem0ai/oss", () => ({ + Memory: class { + constructor() {} + async search() { return { results: [] }; } + async get() { return {}; } + async getAll() { return []; } + async add() { return { results: [] }; } + async delete() {} + }, + PGVector: undefined, // explicitly absent — tests the !VectorCls guard + RedisDB: class { initialize() { return Promise.resolve(); } }, + Qdrant: class { initialize() { return Promise.resolve(); } }, + })); + + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { disableHistory: true }, + }); + const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any); + + // Should not throw even though PGVector is missing + const results = await provider.search("test", { user_id: "u1" }); + expect(results).toBeDefined(); + }); +}); + +// --------------------------------------------------------------------------- +// 8. OSSProvider: history() error handler +// --------------------------------------------------------------------------- +describe("OSSProvider — history error handling", () => { + /** When set, the mock history() throws this value instead of an Error. */ + let historyThrowValue: unknown; + + beforeEach(() => { + historyThrowValue = new Error("history not available"); + vi.resetModules(); + + vi.doMock("mem0ai/oss", () => ({ + Memory: class MockMemory { + constructor() {} + async search() { return { results: [] }; } + async get() { return {}; } + async getAll() { return []; } + async add() { return { results: [] }; } + async delete() {} + async history() { throw historyThrowValue; } + }, + ...vectorStubs(), + })); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns empty array and warns when history() throws an Error", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { disableHistory: true }, + }); + const api = { resolvePath: (p: string) => p } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + const result = await provider.history("mem-123"); + expect(result).toEqual([]); + expect(warnSpy).toHaveBeenCalledWith( + "[mem0] OSS history() failed:", + "history not available", + ); + warnSpy.mockRestore(); + }); + + it("handles non-Error thrown values in history()", async () => { + historyThrowValue = "raw string error"; + + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { disableHistory: true }, + }); + const api = { resolvePath: (p: string) => p } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + const result = await provider.history("mem-456"); + expect(result).toEqual([]); + expect(warnSpy).toHaveBeenCalledWith( + "[mem0] OSS history() failed:", + "raw string error", + ); + warnSpy.mockRestore(); + }); +}); + +// --------------------------------------------------------------------------- +// 9. OSSProvider: customPrompt passthrough +// --------------------------------------------------------------------------- +describe("OSSProvider — customPrompt passthrough", () => { + let capturedConfig: Record | undefined; + + beforeEach(() => { + capturedConfig = undefined; + vi.resetModules(); + + vi.doMock("mem0ai/oss", () => ({ + Memory: class MockMemory { + constructor(config: Record) { + capturedConfig = { ...config }; + } + async search() { return { results: [] }; } + async get() { return {}; } + async getAll() { return []; } + async add() { return { results: [] }; } + async delete() {} + }, + ...vectorStubs(), + })); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("passes customPrompt to Memory config when provided", async () => { + const { createProvider } = await import("./index.ts"); + const cfg = mem0ConfigSchema.parse({ + mode: "open-source", + oss: { disableHistory: true }, + customPrompt: "Extract only user preferences.", + }); + const api = { resolvePath: (p: string) => p } as any; + const provider = createProvider(cfg, api); + + await provider.search("test", { user_id: "u1" }); + + expect(capturedConfig).toBeDefined(); + expect(capturedConfig!.customPrompt).toBe("Extract only user preferences."); + }); +}); diff --git a/openclaw/tests/cli-commands.test.ts b/openclaw/tests/cli-commands.test.ts index 9233f54d2..04d832b55 100644 --- a/openclaw/tests/cli-commands.test.ts +++ b/openclaw/tests/cli-commands.test.ts @@ -175,7 +175,6 @@ function createMockCfg() { apiKey: "m0-test-key-1234", baseUrl: "https://api.mem0.ai", topK: 5, - enableGraph: false, autoCapture: true, autoRecall: true, searchThreshold: 0.5, @@ -967,7 +966,7 @@ describe("registerCliCommands", () => { const configCmd = findCommand(mem0, "config")!; const getCmd = findCommand(configCmd, "get")!; - getCmd._action!("org_id"); + getCmd._action!("email"); expect(consoleSpy.log).toHaveBeenCalledWith("(not set)"); }); @@ -1033,18 +1032,6 @@ describe("registerCliCommands", () => { ); }); - it("coerces 'true' to boolean for boolean keys", () => { - const { mem0 } = setup(); - const configCmd = findCommand(mem0, "config")!; - const setCmd = findCommand(configCmd, "set")!; - - setCmd._action!("enable_graph", "true"); - - expect(writePluginAuth).toHaveBeenCalledWith( - expect.objectContaining({ enableGraph: true }), - ); - }); - it("coerces 'false' to boolean false for boolean keys", () => { const { mem0 } = setup(); const configCmd = findCommand(mem0, "config")!; @@ -1069,18 +1056,6 @@ describe("registerCliCommands", () => { ); }); - it("coerces 'yes' to boolean true for boolean keys", () => { - const { mem0 } = setup(); - const configCmd = findCommand(mem0, "config")!; - const setCmd = findCommand(configCmd, "set")!; - - setCmd._action!("enable_graph", "yes"); - - expect(writePluginAuth).toHaveBeenCalledWith( - expect.objectContaining({ enableGraph: true }), - ); - }); - it("coerces integer string for integer keys", () => { const { mem0 } = setup(); const configCmd = findCommand(mem0, "config")!; @@ -1412,5 +1387,73 @@ describe("registerCliCommands", () => { expect.stringContaining("Failed to get event"), ); }); + + it("event list returns early in open-source mode", async () => { + const provider = createMockProvider(); + const cfg = { ...createMockCfg(), mode: "open-source" as const }; + const mockApi = { + registerCli: vi.fn((cb: any) => { + const root = createMockCommand("root"); + cb({ program: root }); + const mem0 = findCommand(root, "mem0")!; + const eventCmd = findCommand(mem0, "event")!; + const listCmd = findCommand(eventCmd, "list")!; + listCmd._action!(); + }), + logger: { info: vi.fn(), warn: vi.fn() }, + } as any; + + registerCliCommands( + mockApi, + null as any, + provider as any, + cfg as any, + vi.fn().mockReturnValue("testuser"), + vi.fn((id: string) => `testuser:agent:${id}`), + vi.fn().mockReturnValue({ user_id: "testuser", top_k: 5 }), + vi.fn().mockReturnValue(undefined), + ); + + // Wait for async action + await new Promise((r) => setTimeout(r, 10)); + + expect(consoleSpy.log).toHaveBeenCalledWith( + "Event tracking is only available in platform mode.", + ); + }); + + it("event status returns early in open-source mode", async () => { + const provider = createMockProvider(); + const cfg = { ...createMockCfg(), mode: "open-source" as const }; + const mockApi = { + registerCli: vi.fn((cb: any) => { + const root = createMockCommand("root"); + cb({ program: root }); + const mem0 = findCommand(root, "mem0")!; + const eventCmd = findCommand(mem0, "event")!; + const statusCmd = findCommand(eventCmd, "status")!; + statusCmd._action!("evt-123"); + }), + logger: { info: vi.fn(), warn: vi.fn() }, + } as any; + + registerCliCommands( + mockApi, + null as any, + provider as any, + cfg as any, + vi.fn().mockReturnValue("testuser"), + vi.fn((id: string) => `testuser:agent:${id}`), + vi.fn().mockReturnValue({ user_id: "testuser", top_k: 5 }), + vi.fn().mockReturnValue(undefined), + ); + + // Wait for async action + await new Promise((r) => setTimeout(r, 10)); + + expect(consoleSpy.log).toHaveBeenCalledWith( + "Event tracking is only available in platform mode.", + ); + }); }); }); diff --git a/openclaw/tests/config-file.test.ts b/openclaw/tests/config-file.test.ts index 127a92415..f2d535757 100644 --- a/openclaw/tests/config-file.test.ts +++ b/openclaw/tests/config-file.test.ts @@ -69,10 +69,7 @@ describe("readPluginAuth", () => { apiKey: "sk-test-123", baseUrl: "https://custom.api.com", userId: "user-1", - orgId: "org-1", - projectId: "proj-1", mode: "platform", - enableGraph: true, autoRecall: true, autoCapture: false, topK: 10, @@ -87,17 +84,14 @@ describe("readPluginAuth", () => { apiKey: "sk-test-123", baseUrl: "https://custom.api.com", userId: "user-1", - orgId: "org-1", - projectId: "proj-1", mode: "platform", - enableGraph: true, autoRecall: true, autoCapture: false, topK: 10, }); }); - it("handles snake_case aliases (api_key, base_url, user_id, org_id, project_id)", () => { + it("handles snake_case aliases (api_key, base_url, user_id)", () => { setConfigFile({ plugins: { entries: { @@ -107,8 +101,6 @@ describe("readPluginAuth", () => { api_key: "sk-snake", base_url: "https://snake.api.com", user_id: "user-snake", - org_id: "org-snake", - project_id: "proj-snake", }, }, }, @@ -119,8 +111,6 @@ describe("readPluginAuth", () => { expect(auth.apiKey).toBe("sk-snake"); expect(auth.baseUrl).toBe("https://snake.api.com"); expect(auth.userId).toBe("user-snake"); - expect(auth.orgId).toBe("org-snake"); - expect(auth.projectId).toBe("proj-snake"); }); it("returns empty object when JSON is invalid", () => { diff --git a/openclaw/tests/config.test.ts b/openclaw/tests/config.test.ts index 188209311..7b420213a 100644 --- a/openclaw/tests/config.test.ts +++ b/openclaw/tests/config.test.ts @@ -64,11 +64,6 @@ describe("mem0ConfigSchema.parse() — defaults", () => { expect(cfg.searchThreshold).toBe(0.5); }); - it("enableGraph defaults to false", () => { - const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" }); - expect(cfg.enableGraph).toBe(false); - }); - it("customInstructions defaults to DEFAULT_CUSTOM_INSTRUCTIONS", () => { const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" }); expect(cfg.customInstructions).toBe(DEFAULT_CUSTOM_INSTRUCTIONS); @@ -274,14 +269,6 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => { expect(cfg.autoRecall).toBe(false); }); - it("enableGraph can be set to true", () => { - const cfg = mem0ConfigSchema.parse({ - apiKey: "k", - enableGraph: true, - }); - expect(cfg.enableGraph).toBe(true); - }); - it("custom topK is used when provided", () => { const cfg = mem0ConfigSchema.parse({ apiKey: "k", topK: 20 }); expect(cfg.topK).toBe(20); @@ -330,18 +317,6 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => { expect(cfg.baseUrl).toBe("https://custom.api.com"); }); - it("orgId is passed through when provided", () => { - const cfg = mem0ConfigSchema.parse({ apiKey: "k", orgId: "org-123" }); - expect(cfg.orgId).toBe("org-123"); - }); - - it("projectId is passed through when provided", () => { - const cfg = mem0ConfigSchema.parse({ - apiKey: "k", - projectId: "proj-456", - }); - expect(cfg.projectId).toBe("proj-456"); - }); }); // --------------------------------------------------------------------------- @@ -359,22 +334,23 @@ describe("mem0ConfigSchema.parse() — oss config", () => { historyDbPath: "/tmp/history.db", disableHistory: false, }; - const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: ossConfig }); + const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: ossConfig }); + expect(cfg.mode).toBe("open-source"); expect(cfg.oss).toEqual(ossConfig); }); it("ignores oss when it is not a plain object", () => { - const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: "not-an-object" }); + const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: "not-an-object" }); expect(cfg.oss).toBeUndefined(); }); it("ignores oss when it is an array", () => { - const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: [1, 2, 3] }); + const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: [1, 2, 3] }); expect(cfg.oss).toBeUndefined(); }); it("ignores oss when it is null", () => { - const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: null }); + const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: null }); expect(cfg.oss).toBeUndefined(); }); }); @@ -388,7 +364,6 @@ describe("mem0ConfigSchema.parse() — skills config", () => { triage: { enabled: true, importanceThreshold: 3, - enableGraph: false, credentialPatterns: ["sk-", "ghp_"], }, recall: { diff --git a/openclaw/tests/providers.test.ts b/openclaw/tests/providers.test.ts index c4cc314ad..f586272e1 100644 --- a/openclaw/tests/providers.test.ts +++ b/openclaw/tests/providers.test.ts @@ -127,7 +127,6 @@ describe("providerToBackend — add", () => { immutable: true, infer: false, expires: "2027-01-01", - enableGraph: true, }); expect(provider.add).toHaveBeenCalledWith( @@ -139,7 +138,6 @@ describe("providerToBackend — add", () => { immutable: true, infer: false, expiration_date: "2027-01-01", - enable_graph: true, }), ); }); diff --git a/openclaw/tests/tools.test.ts b/openclaw/tests/tools.test.ts index 9515dbf50..bbddc29ce 100644 --- a/openclaw/tests/tools.test.ts +++ b/openclaw/tests/tools.test.ts @@ -32,7 +32,6 @@ function createMockToolDeps(overrides = {}): ToolDeps { mode: "platform", userId: "testuser", topK: 5, - enableGraph: false, autoCapture: true, autoRecall: true, searchThreshold: 0.5, diff --git a/openclaw/tools/memory-add.ts b/openclaw/tools/memory-add.ts index ae5af952f..46e2f20b2 100644 --- a/openclaw/tools/memory-add.ts +++ b/openclaw/tools/memory-add.ts @@ -67,7 +67,6 @@ export function createMemoryAddTool(deps: ToolDeps) { if (runId) addOpts.run_id = runId; if (cfg.mode === "platform") { addOpts.output_format = "v1.1"; - if (cfg.enableGraph || cfg.skills?.triage?.enableGraph) addOpts.enable_graph = true; } const result = await provider.add([{ role: "user", content: allFacts.join("\n") }], addOpts); diff --git a/openclaw/types.ts b/openclaw/types.ts index e9c32e793..2e375739f 100644 --- a/openclaw/types.ts +++ b/openclaw/types.ts @@ -9,11 +9,8 @@ export type Mem0Config = { // Platform-specific apiKey?: string; baseUrl?: string; - orgId?: string; - projectId?: string; customInstructions: string; customCategories: Record; - enableGraph: boolean; // OSS-specific customPrompt?: string; oss?: { @@ -40,7 +37,6 @@ export interface AddOptions { run_id?: string; custom_instructions?: string; custom_categories?: Array>; - enable_graph?: boolean; output_format?: string; source?: string; // Agentic harness additions @@ -79,7 +75,6 @@ export interface SkillsConfig { triage?: { enabled?: boolean; importanceThreshold?: number; - enableGraph?: boolean; credentialPatterns?: string[]; }; recall?: {