@ztorchan/openclaw-mem0 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1058 @@
1
+ // index.ts
2
+ import { Type } from "@sinclair/typebox";
3
+ var PlatformProvider = class {
4
+ constructor(apiKey, orgId, projectId) {
5
+ this.apiKey = apiKey;
6
+ this.orgId = orgId;
7
+ this.projectId = projectId;
8
+ }
9
+ client;
10
+ // MemoryClient from mem0ai
11
+ initPromise = null;
12
+ async ensureClient() {
13
+ if (this.client) return;
14
+ if (this.initPromise) return this.initPromise;
15
+ this.initPromise = this._init();
16
+ return this.initPromise;
17
+ }
18
+ async _init() {
19
+ const { default: MemoryClient } = await import("@ztorchan/mem0ai");
20
+ const opts = { apiKey: this.apiKey };
21
+ if (this.orgId) opts.org_id = this.orgId;
22
+ if (this.projectId) opts.project_id = this.projectId;
23
+ this.client = new MemoryClient(opts);
24
+ }
25
+ async add(messages, options) {
26
+ await this.ensureClient();
27
+ const opts = { user_id: options.user_id };
28
+ if (options.run_id) opts.run_id = options.run_id;
29
+ if (options.custom_instructions)
30
+ opts.custom_instructions = options.custom_instructions;
31
+ if (options.custom_categories)
32
+ opts.custom_categories = options.custom_categories;
33
+ if (options.enable_graph) opts.enable_graph = options.enable_graph;
34
+ if (options.output_format) opts.output_format = options.output_format;
35
+ if (options.source) opts.source = options.source;
36
+ const result = await this.client.add(messages, opts);
37
+ return normalizeAddResult(result);
38
+ }
39
+ async search(query, options) {
40
+ await this.ensureClient();
41
+ const filters = { user_id: options.user_id };
42
+ if (options.run_id) filters.run_id = options.run_id;
43
+ const opts = {
44
+ api_version: "v2",
45
+ filters
46
+ };
47
+ if (options.top_k != null) opts.top_k = options.top_k;
48
+ if (options.threshold != null) opts.threshold = options.threshold;
49
+ if (options.keyword_search != null) opts.keyword_search = options.keyword_search;
50
+ if (options.reranking != null) opts.rerank = options.reranking;
51
+ const results = await this.client.search(query, opts);
52
+ return normalizeSearchResults(results);
53
+ }
54
+ async get(memoryId) {
55
+ await this.ensureClient();
56
+ const result = await this.client.get(memoryId);
57
+ return normalizeMemoryItem(result);
58
+ }
59
+ async getAll(options) {
60
+ await this.ensureClient();
61
+ const opts = { user_id: options.user_id };
62
+ if (options.run_id) opts.run_id = options.run_id;
63
+ if (options.page_size != null) opts.page_size = options.page_size;
64
+ if (options.source) opts.source = options.source;
65
+ const results = await this.client.getAll(opts);
66
+ if (Array.isArray(results)) return results.map(normalizeMemoryItem);
67
+ if (results?.results && Array.isArray(results.results))
68
+ return results.results.map(normalizeMemoryItem);
69
+ return [];
70
+ }
71
+ async delete(memoryId) {
72
+ await this.ensureClient();
73
+ await this.client.delete(memoryId);
74
+ }
75
+ };
76
+ var OSSProvider = class {
77
+ constructor(ossConfig, customPrompt, resolvePath) {
78
+ this.ossConfig = ossConfig;
79
+ this.customPrompt = customPrompt;
80
+ this.resolvePath = resolvePath;
81
+ }
82
+ memory;
83
+ // Memory from mem0ai/oss
84
+ initPromise = null;
85
+ async ensureMemory() {
86
+ if (this.memory) return;
87
+ if (this.initPromise) return this.initPromise;
88
+ this.initPromise = this._init();
89
+ return this.initPromise;
90
+ }
91
+ async _init() {
92
+ const { Memory } = await import("@ztorchan/mem0ai/oss");
93
+ const config = { version: "v1.1" };
94
+ if (this.ossConfig?.embedder) config.embedder = this.ossConfig.embedder;
95
+ if (this.ossConfig?.vectorStore)
96
+ config.vectorStore = this.ossConfig.vectorStore;
97
+ if (this.ossConfig?.llm) config.llm = this.ossConfig.llm;
98
+ if (this.ossConfig?.historyDbPath) {
99
+ const dbPath = this.resolvePath ? this.resolvePath(this.ossConfig.historyDbPath) : this.ossConfig.historyDbPath;
100
+ config.historyDbPath = dbPath;
101
+ }
102
+ if (this.customPrompt) config.customPrompt = this.customPrompt;
103
+ this.memory = new Memory(config);
104
+ }
105
+ async add(messages, options) {
106
+ await this.ensureMemory();
107
+ const addOpts = { userId: options.user_id };
108
+ if (options.run_id) addOpts.runId = options.run_id;
109
+ if (options.source) addOpts.source = options.source;
110
+ const result = await this.memory.add(messages, addOpts);
111
+ return normalizeAddResult(result);
112
+ }
113
+ async search(query, options) {
114
+ await this.ensureMemory();
115
+ const opts = { userId: options.user_id };
116
+ if (options.run_id) opts.runId = options.run_id;
117
+ if (options.limit != null) opts.limit = options.limit;
118
+ else if (options.top_k != null) opts.limit = options.top_k;
119
+ if (options.keyword_search != null) opts.keyword_search = options.keyword_search;
120
+ if (options.reranking != null) opts.reranking = options.reranking;
121
+ if (options.source) opts.source = options.source;
122
+ if (options.threshold != null) opts.threshold = options.threshold;
123
+ const results = await this.memory.search(query, opts);
124
+ const normalized = normalizeSearchResults(results);
125
+ if (options.threshold != null) {
126
+ return normalized.filter((item) => (item.score ?? 0) >= options.threshold);
127
+ }
128
+ return normalized;
129
+ }
130
+ async get(memoryId) {
131
+ await this.ensureMemory();
132
+ const result = await this.memory.get(memoryId);
133
+ return normalizeMemoryItem(result);
134
+ }
135
+ async getAll(options) {
136
+ await this.ensureMemory();
137
+ const getAllOpts = { userId: options.user_id };
138
+ if (options.run_id) getAllOpts.runId = options.run_id;
139
+ if (options.source) getAllOpts.source = options.source;
140
+ const results = await this.memory.getAll(getAllOpts);
141
+ if (Array.isArray(results)) return results.map(normalizeMemoryItem);
142
+ if (results?.results && Array.isArray(results.results))
143
+ return results.results.map(normalizeMemoryItem);
144
+ return [];
145
+ }
146
+ async delete(memoryId) {
147
+ await this.ensureMemory();
148
+ await this.memory.delete(memoryId);
149
+ }
150
+ };
151
+ function normalizeMemoryItem(raw) {
152
+ return {
153
+ id: raw.id ?? raw.memory_id ?? "",
154
+ memory: raw.memory ?? raw.text ?? raw.content ?? "",
155
+ // Handle both platform (user_id, created_at) and OSS (userId, createdAt) field names
156
+ user_id: raw.user_id ?? raw.userId,
157
+ score: raw.score,
158
+ categories: raw.categories,
159
+ metadata: raw.metadata,
160
+ created_at: raw.created_at ?? raw.createdAt,
161
+ updated_at: raw.updated_at ?? raw.updatedAt
162
+ };
163
+ }
164
+ function normalizeSearchResults(raw) {
165
+ if (Array.isArray(raw)) return raw.map(normalizeMemoryItem);
166
+ if (raw?.results && Array.isArray(raw.results))
167
+ return raw.results.map(normalizeMemoryItem);
168
+ return [];
169
+ }
170
+ function normalizeAddResult(raw) {
171
+ if (raw?.results && Array.isArray(raw.results)) {
172
+ return {
173
+ results: raw.results.map((r) => ({
174
+ id: r.id ?? r.memory_id ?? "",
175
+ memory: r.memory ?? r.text ?? "",
176
+ // Platform API may return PENDING status (async processing)
177
+ // OSS stores event in metadata.event
178
+ event: r.event ?? r.metadata?.event ?? (r.status === "PENDING" ? "ADD" : "ADD")
179
+ }))
180
+ };
181
+ }
182
+ if (Array.isArray(raw)) {
183
+ return {
184
+ results: raw.map((r) => ({
185
+ id: r.id ?? r.memory_id ?? "",
186
+ memory: r.memory ?? r.text ?? "",
187
+ event: r.event ?? r.metadata?.event ?? (r.status === "PENDING" ? "ADD" : "ADD")
188
+ }))
189
+ };
190
+ }
191
+ return { results: [] };
192
+ }
193
+ function resolveEnvVars(value) {
194
+ return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
195
+ const envValue = process.env[envVar];
196
+ if (!envValue) {
197
+ throw new Error(`Environment variable ${envVar} is not set`);
198
+ }
199
+ return envValue;
200
+ });
201
+ }
202
+ function resolveEnvVarsDeep(obj) {
203
+ const result = {};
204
+ for (const [key, value] of Object.entries(obj)) {
205
+ if (typeof value === "string") {
206
+ result[key] = resolveEnvVars(value);
207
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
208
+ result[key] = resolveEnvVarsDeep(value);
209
+ } else {
210
+ result[key] = value;
211
+ }
212
+ }
213
+ return result;
214
+ }
215
+ var DEFAULT_CUSTOM_INSTRUCTIONS = `Your Task: Extract and maintain a structured, evolving profile of the user from their conversations with an AI assistant. Capture information that would help the assistant provide personalized, context-aware responses in future interactions.
216
+
217
+ Information to Extract:
218
+
219
+ 1. Identity & Demographics:
220
+ - Name, age, location, timezone, language preferences
221
+ - Occupation, employer, job role, industry
222
+ - Education background
223
+
224
+ 2. Preferences & Opinions:
225
+ - Communication style preferences (formal/casual, verbose/concise)
226
+ - Tool and technology preferences (languages, frameworks, editors, OS)
227
+ - Content preferences (topics of interest, learning style)
228
+ - Strong opinions or values they've expressed
229
+ - Likes and dislikes they've explicitly stated
230
+
231
+ 3. Goals & Projects:
232
+ - Current projects they're working on (name, description, status)
233
+ - Short-term and long-term goals
234
+ - Deadlines and milestones mentioned
235
+ - Problems they're actively trying to solve
236
+
237
+ 4. Technical Context:
238
+ - Tech stack and tools they use
239
+ - Skill level in different areas (beginner/intermediate/expert)
240
+ - Development environment and setup details
241
+ - Recurring technical challenges
242
+
243
+ 5. Relationships & People:
244
+ - Names and roles of people they mention (colleagues, family, friends)
245
+ - Team structure and dynamics
246
+ - Key contacts and their relevance
247
+
248
+ 6. Decisions & Lessons:
249
+ - Important decisions made and their reasoning
250
+ - Lessons learned from past experiences
251
+ - Strategies that worked or failed
252
+ - Changed opinions or updated beliefs
253
+
254
+ 7. Routines & Habits:
255
+ - Daily routines and schedules mentioned
256
+ - Work patterns (when they're productive, how they organize work)
257
+ - Health and wellness habits if voluntarily shared
258
+
259
+ 8. Life Events:
260
+ - Significant events (new job, moving, milestones)
261
+ - Upcoming events or plans
262
+ - Changes in circumstances
263
+
264
+ Guidelines:
265
+ - Store memories as clear, self-contained statements (each memory should make sense on its own)
266
+ - Use third person: "User prefers..." not "I prefer..."
267
+ - Include temporal context when relevant: "As of [date], user is working on..."
268
+ - When information updates, UPDATE the existing memory rather than creating duplicates
269
+ - Merge related facts into single coherent memories when possible
270
+ - Preserve specificity: "User uses Next.js 14 with App Router" is better than "User uses React"
271
+ - Capture the WHY behind preferences when stated: "User prefers Vim because of keyboard-driven workflow"
272
+
273
+ Exclude:
274
+ - Passwords, API keys, tokens, or any authentication credentials
275
+ - Exact financial amounts (account balances, salaries) unless the user explicitly asks to remember them
276
+ - Temporary or ephemeral information (one-time questions, debugging sessions with no lasting insight)
277
+ - Generic small talk with no informational content
278
+ - The assistant's own responses unless they contain a commitment or promise to the user
279
+ - Raw code snippets (capture the intent/decision, not the code itself)
280
+ - Information the user explicitly asks not to remember`;
281
+ var DEFAULT_CUSTOM_CATEGORIES = {
282
+ identity: "Personal identity information: name, age, location, timezone, occupation, employer, education, demographics",
283
+ preferences: "Explicitly stated likes, dislikes, preferences, opinions, and values across any domain",
284
+ goals: "Current and future goals, aspirations, objectives, targets the user is working toward",
285
+ projects: "Specific projects, initiatives, or endeavors the user is working on, including status and details",
286
+ technical: "Technical skills, tools, tech stack, development environment, programming languages, frameworks",
287
+ decisions: "Important decisions made, reasoning behind choices, strategy changes, and their outcomes",
288
+ relationships: "People mentioned by the user: colleagues, family, friends, their roles and relevance",
289
+ routines: "Daily habits, work patterns, schedules, productivity routines, health and wellness habits",
290
+ life_events: "Significant life events, milestones, transitions, upcoming plans and changes",
291
+ lessons: "Lessons learned, insights gained, mistakes acknowledged, changed opinions or beliefs",
292
+ work: "Work-related context: job responsibilities, workplace dynamics, career progression, professional challenges",
293
+ health: "Health-related information voluntarily shared: conditions, medications, fitness, wellness goals"
294
+ };
295
+ var ALLOWED_KEYS = [
296
+ "mode",
297
+ "apiKey",
298
+ "userId",
299
+ "orgId",
300
+ "projectId",
301
+ "autoCapture",
302
+ "autoRecall",
303
+ "customInstructions",
304
+ "customCategories",
305
+ "customPrompt",
306
+ "enableGraph",
307
+ "searchThreshold",
308
+ "topK",
309
+ "oss"
310
+ ];
311
+ function assertAllowedKeys(value, allowed, label) {
312
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
313
+ if (unknown.length === 0) return;
314
+ throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
315
+ }
316
+ var mem0ConfigSchema = {
317
+ parse(value) {
318
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
319
+ throw new Error("openclaw-mem0 config required");
320
+ }
321
+ const cfg = value;
322
+ assertAllowedKeys(cfg, ALLOWED_KEYS, "openclaw-mem0 config");
323
+ const mode = cfg.mode === "oss" || cfg.mode === "open-source" ? "open-source" : "platform";
324
+ if (mode === "platform") {
325
+ if (typeof cfg.apiKey !== "string" || !cfg.apiKey) {
326
+ throw new Error(
327
+ 'apiKey is required for platform mode (set mode: "open-source" for self-hosted)'
328
+ );
329
+ }
330
+ }
331
+ let ossConfig;
332
+ if (cfg.oss && typeof cfg.oss === "object" && !Array.isArray(cfg.oss)) {
333
+ ossConfig = resolveEnvVarsDeep(
334
+ cfg.oss
335
+ );
336
+ }
337
+ return {
338
+ mode,
339
+ apiKey: typeof cfg.apiKey === "string" ? resolveEnvVars(cfg.apiKey) : void 0,
340
+ userId: typeof cfg.userId === "string" && cfg.userId ? cfg.userId : "default",
341
+ orgId: typeof cfg.orgId === "string" ? cfg.orgId : void 0,
342
+ projectId: typeof cfg.projectId === "string" ? cfg.projectId : void 0,
343
+ autoCapture: cfg.autoCapture !== false,
344
+ autoRecall: cfg.autoRecall !== false,
345
+ customInstructions: typeof cfg.customInstructions === "string" ? cfg.customInstructions : DEFAULT_CUSTOM_INSTRUCTIONS,
346
+ customCategories: cfg.customCategories && typeof cfg.customCategories === "object" && !Array.isArray(cfg.customCategories) ? cfg.customCategories : DEFAULT_CUSTOM_CATEGORIES,
347
+ customPrompt: typeof cfg.customPrompt === "string" ? cfg.customPrompt : DEFAULT_CUSTOM_INSTRUCTIONS,
348
+ enableGraph: cfg.enableGraph === true,
349
+ searchThreshold: typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.5,
350
+ topK: typeof cfg.topK === "number" ? cfg.topK : 5,
351
+ oss: ossConfig
352
+ };
353
+ }
354
+ };
355
+ function createProvider(cfg, api) {
356
+ if (cfg.mode === "open-source") {
357
+ return new OSSProvider(
358
+ cfg.oss,
359
+ cfg.customPrompt,
360
+ (p) => api.resolvePath(p)
361
+ );
362
+ }
363
+ return new PlatformProvider(cfg.apiKey, cfg.orgId, cfg.projectId);
364
+ }
365
+ function categoriesToArray(cats) {
366
+ return Object.entries(cats).map(([key, value]) => ({ [key]: value }));
367
+ }
368
+ function extractAgentId(sessionKey) {
369
+ if (!sessionKey) return void 0;
370
+ const match = sessionKey.match(/^agent:([^:]+):/);
371
+ const agentId = match?.[1];
372
+ if (!agentId || agentId === "main") return void 0;
373
+ return agentId;
374
+ }
375
+ function effectiveUserId(baseUserId, sessionKey) {
376
+ const agentId = extractAgentId(sessionKey);
377
+ return agentId ? `${baseUserId}:agent:${agentId}` : baseUserId;
378
+ }
379
+ function agentUserId(baseUserId, agentId) {
380
+ return `${baseUserId}:agent:${agentId}`;
381
+ }
382
+ function resolveUserId(baseUserId, opts, currentSessionId) {
383
+ if (opts.agentId) return agentUserId(baseUserId, opts.agentId);
384
+ if (opts.userId) return opts.userId;
385
+ return effectiveUserId(baseUserId, currentSessionId);
386
+ }
387
+ var memoryPlugin = {
388
+ id: "openclaw-mem0",
389
+ name: "Memory (Mem0)",
390
+ description: "Mem0 memory backend \u2014 Mem0 platform or self-hosted open-source",
391
+ kind: "memory",
392
+ configSchema: mem0ConfigSchema,
393
+ register(api) {
394
+ const cfg = mem0ConfigSchema.parse(api.pluginConfig);
395
+ const provider = createProvider(cfg, api);
396
+ let currentSessionId;
397
+ const _effectiveUserId = (sessionKey) => effectiveUserId(cfg.userId, sessionKey);
398
+ const _agentUserId = (id) => agentUserId(cfg.userId, id);
399
+ const _resolveUserId = (opts) => resolveUserId(cfg.userId, opts, currentSessionId);
400
+ api.logger.info(
401
+ `openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, graph: ${cfg.enableGraph}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture})`
402
+ );
403
+ function buildAddOptions(userIdOverride, runId, sessionKey) {
404
+ const opts = {
405
+ user_id: userIdOverride || _effectiveUserId(sessionKey),
406
+ source: "OPENCLAW"
407
+ };
408
+ if (runId) opts.run_id = runId;
409
+ if (cfg.mode === "platform") {
410
+ opts.custom_instructions = cfg.customInstructions;
411
+ opts.custom_categories = categoriesToArray(cfg.customCategories);
412
+ opts.enable_graph = cfg.enableGraph;
413
+ opts.output_format = "v1.1";
414
+ }
415
+ return opts;
416
+ }
417
+ function buildSearchOptions(userIdOverride, limit, runId, sessionKey) {
418
+ const opts = {
419
+ user_id: userIdOverride || _effectiveUserId(sessionKey),
420
+ top_k: limit ?? cfg.topK,
421
+ limit: limit ?? cfg.topK,
422
+ threshold: cfg.searchThreshold,
423
+ keyword_search: true,
424
+ reranking: true,
425
+ source: "OPENCLAW"
426
+ };
427
+ if (runId) opts.run_id = runId;
428
+ return opts;
429
+ }
430
+ api.registerTool(
431
+ {
432
+ name: "memory_search",
433
+ label: "Memory Search",
434
+ description: "Search through long-term memories stored in Mem0. Use when you need context about user preferences, past decisions, or previously discussed topics.",
435
+ parameters: Type.Object({
436
+ query: Type.String({ description: "Search query" }),
437
+ limit: Type.Optional(
438
+ Type.Number({
439
+ description: `Max results (default: ${cfg.topK})`
440
+ })
441
+ ),
442
+ userId: Type.Optional(
443
+ Type.String({
444
+ description: "User ID to scope search (default: configured userId)"
445
+ })
446
+ ),
447
+ agentId: Type.Optional(
448
+ Type.String({
449
+ description: 'Agent ID to search memories for a specific agent (e.g. "researcher"). Overrides userId.'
450
+ })
451
+ ),
452
+ scope: Type.Optional(
453
+ Type.Union([
454
+ Type.Literal("session"),
455
+ Type.Literal("long-term"),
456
+ Type.Literal("all")
457
+ ], {
458
+ description: 'Memory scope: "session" (current session only), "long-term" (user-scoped only), or "all" (both). Default: "all"'
459
+ })
460
+ )
461
+ }),
462
+ async execute(_toolCallId, params) {
463
+ const { query, limit, userId, agentId, scope = "all" } = params;
464
+ try {
465
+ let results = [];
466
+ const uid = _resolveUserId({ agentId, userId });
467
+ if (scope === "session") {
468
+ if (currentSessionId) {
469
+ results = await provider.search(
470
+ query,
471
+ buildSearchOptions(uid, limit, currentSessionId)
472
+ );
473
+ }
474
+ } else if (scope === "long-term") {
475
+ results = await provider.search(
476
+ query,
477
+ buildSearchOptions(uid, limit)
478
+ );
479
+ } else {
480
+ const longTermResults = await provider.search(
481
+ query,
482
+ buildSearchOptions(uid, limit)
483
+ );
484
+ let sessionResults = [];
485
+ if (currentSessionId) {
486
+ sessionResults = await provider.search(
487
+ query,
488
+ buildSearchOptions(uid, limit, currentSessionId)
489
+ );
490
+ }
491
+ const seen = new Set(longTermResults.map((r) => r.id));
492
+ results = [
493
+ ...longTermResults,
494
+ ...sessionResults.filter((r) => !seen.has(r.id))
495
+ ];
496
+ }
497
+ if (!results || results.length === 0) {
498
+ return {
499
+ content: [
500
+ { type: "text", text: "No relevant memories found." }
501
+ ],
502
+ details: { count: 0 }
503
+ };
504
+ }
505
+ const text = results.map(
506
+ (r, i) => `${i + 1}. ${r.memory} (score: ${((r.score ?? 0) * 100).toFixed(0)}%, id: ${r.id})`
507
+ ).join("\n");
508
+ const sanitized = results.map((r) => ({
509
+ id: r.id,
510
+ memory: r.memory,
511
+ score: r.score,
512
+ categories: r.categories,
513
+ created_at: r.created_at
514
+ }));
515
+ return {
516
+ content: [
517
+ {
518
+ type: "text",
519
+ text: `Found ${results.length} memories:
520
+
521
+ ${text}`
522
+ }
523
+ ],
524
+ details: { count: results.length, memories: sanitized }
525
+ };
526
+ } catch (err) {
527
+ return {
528
+ content: [
529
+ {
530
+ type: "text",
531
+ text: `Memory search failed: ${String(err)}`
532
+ }
533
+ ],
534
+ details: { error: String(err) }
535
+ };
536
+ }
537
+ }
538
+ },
539
+ { name: "memory_search" }
540
+ );
541
+ api.registerTool(
542
+ {
543
+ name: "memory_store",
544
+ label: "Memory Store",
545
+ description: "Save important information in long-term memory via Mem0. Use for preferences, facts, decisions, and anything worth remembering.",
546
+ parameters: Type.Object({
547
+ text: Type.String({ description: "Information to remember" }),
548
+ userId: Type.Optional(
549
+ Type.String({
550
+ description: "User ID to scope this memory"
551
+ })
552
+ ),
553
+ agentId: Type.Optional(
554
+ Type.String({
555
+ description: `Agent ID to store memory under a specific agent's namespace (e.g. "researcher"). Overrides userId.`
556
+ })
557
+ ),
558
+ metadata: Type.Optional(
559
+ Type.Record(Type.String(), Type.Unknown(), {
560
+ description: "Optional metadata to attach to this memory"
561
+ })
562
+ ),
563
+ longTerm: Type.Optional(
564
+ Type.Boolean({
565
+ description: "Store as long-term (user-scoped) memory. Default: true. Set to false for session-scoped memory."
566
+ })
567
+ )
568
+ }),
569
+ async execute(_toolCallId, params) {
570
+ const { text, userId, agentId, longTerm = true } = params;
571
+ try {
572
+ const uid = _resolveUserId({ agentId, userId });
573
+ const runId = !longTerm && currentSessionId ? currentSessionId : void 0;
574
+ const result = await provider.add(
575
+ [{ role: "user", content: text }],
576
+ buildAddOptions(uid, runId, currentSessionId)
577
+ );
578
+ const added = result.results?.filter((r) => r.event === "ADD") ?? [];
579
+ const updated = result.results?.filter((r) => r.event === "UPDATE") ?? [];
580
+ const summary = [];
581
+ if (added.length > 0)
582
+ summary.push(
583
+ `${added.length} new memor${added.length === 1 ? "y" : "ies"} added`
584
+ );
585
+ if (updated.length > 0)
586
+ summary.push(
587
+ `${updated.length} memor${updated.length === 1 ? "y" : "ies"} updated`
588
+ );
589
+ if (summary.length === 0)
590
+ summary.push("No new memories extracted");
591
+ return {
592
+ content: [
593
+ {
594
+ type: "text",
595
+ text: `Stored: ${summary.join(", ")}. ${result.results?.map((r) => `[${r.event}] ${r.memory}`).join("; ") ?? ""}`
596
+ }
597
+ ],
598
+ details: {
599
+ action: "stored",
600
+ results: result.results
601
+ }
602
+ };
603
+ } catch (err) {
604
+ return {
605
+ content: [
606
+ {
607
+ type: "text",
608
+ text: `Memory store failed: ${String(err)}`
609
+ }
610
+ ],
611
+ details: { error: String(err) }
612
+ };
613
+ }
614
+ }
615
+ },
616
+ { name: "memory_store" }
617
+ );
618
+ api.registerTool(
619
+ {
620
+ name: "memory_get",
621
+ label: "Memory Get",
622
+ description: "Retrieve a specific memory by its ID from Mem0.",
623
+ parameters: Type.Object({
624
+ memoryId: Type.String({ description: "The memory ID to retrieve" })
625
+ }),
626
+ async execute(_toolCallId, params) {
627
+ const { memoryId } = params;
628
+ try {
629
+ const memory = await provider.get(memoryId);
630
+ return {
631
+ content: [
632
+ {
633
+ type: "text",
634
+ text: `Memory ${memory.id}:
635
+ ${memory.memory}
636
+
637
+ Created: ${memory.created_at ?? "unknown"}
638
+ Updated: ${memory.updated_at ?? "unknown"}`
639
+ }
640
+ ],
641
+ details: { memory }
642
+ };
643
+ } catch (err) {
644
+ return {
645
+ content: [
646
+ {
647
+ type: "text",
648
+ text: `Memory get failed: ${String(err)}`
649
+ }
650
+ ],
651
+ details: { error: String(err) }
652
+ };
653
+ }
654
+ }
655
+ },
656
+ { name: "memory_get" }
657
+ );
658
+ api.registerTool(
659
+ {
660
+ name: "memory_list",
661
+ label: "Memory List",
662
+ description: "List all stored memories for a user or agent. Use this when you want to see everything that's been remembered, rather than searching for something specific.",
663
+ parameters: Type.Object({
664
+ userId: Type.Optional(
665
+ Type.String({
666
+ description: "User ID to list memories for (default: configured userId)"
667
+ })
668
+ ),
669
+ agentId: Type.Optional(
670
+ Type.String({
671
+ description: 'Agent ID to list memories for a specific agent (e.g. "researcher"). Overrides userId.'
672
+ })
673
+ ),
674
+ scope: Type.Optional(
675
+ Type.Union([
676
+ Type.Literal("session"),
677
+ Type.Literal("long-term"),
678
+ Type.Literal("all")
679
+ ], {
680
+ description: 'Memory scope: "session" (current session only), "long-term" (user-scoped only), or "all" (both). Default: "all"'
681
+ })
682
+ )
683
+ }),
684
+ async execute(_toolCallId, params) {
685
+ const { userId, agentId, scope = "all" } = params;
686
+ try {
687
+ let memories = [];
688
+ const uid = _resolveUserId({ agentId, userId });
689
+ if (scope === "session") {
690
+ if (currentSessionId) {
691
+ memories = await provider.getAll({
692
+ user_id: uid,
693
+ run_id: currentSessionId,
694
+ source: "OPENCLAW"
695
+ });
696
+ }
697
+ } else if (scope === "long-term") {
698
+ memories = await provider.getAll({ user_id: uid, source: "OPENCLAW" });
699
+ } else {
700
+ const longTerm = await provider.getAll({ user_id: uid, source: "OPENCLAW" });
701
+ let session = [];
702
+ if (currentSessionId) {
703
+ session = await provider.getAll({
704
+ user_id: uid,
705
+ run_id: currentSessionId,
706
+ source: "OPENCLAW"
707
+ });
708
+ }
709
+ const seen = new Set(longTerm.map((r) => r.id));
710
+ memories = [
711
+ ...longTerm,
712
+ ...session.filter((r) => !seen.has(r.id))
713
+ ];
714
+ }
715
+ if (!memories || memories.length === 0) {
716
+ return {
717
+ content: [
718
+ { type: "text", text: "No memories stored yet." }
719
+ ],
720
+ details: { count: 0 }
721
+ };
722
+ }
723
+ const text = memories.map(
724
+ (r, i) => `${i + 1}. ${r.memory} (id: ${r.id})`
725
+ ).join("\n");
726
+ const sanitized = memories.map((r) => ({
727
+ id: r.id,
728
+ memory: r.memory,
729
+ categories: r.categories,
730
+ created_at: r.created_at
731
+ }));
732
+ return {
733
+ content: [
734
+ {
735
+ type: "text",
736
+ text: `${memories.length} memories:
737
+
738
+ ${text}`
739
+ }
740
+ ],
741
+ details: { count: memories.length, memories: sanitized }
742
+ };
743
+ } catch (err) {
744
+ return {
745
+ content: [
746
+ {
747
+ type: "text",
748
+ text: `Memory list failed: ${String(err)}`
749
+ }
750
+ ],
751
+ details: { error: String(err) }
752
+ };
753
+ }
754
+ }
755
+ },
756
+ { name: "memory_list" }
757
+ );
758
+ api.registerTool(
759
+ {
760
+ name: "memory_forget",
761
+ label: "Memory Forget",
762
+ description: "Delete memories from Mem0. Provide a specific memoryId to delete directly, or a query to search and delete matching memories. Supports agent-scoped deletion. GDPR-compliant.",
763
+ parameters: Type.Object({
764
+ query: Type.Optional(
765
+ Type.String({
766
+ description: "Search query to find memory to delete"
767
+ })
768
+ ),
769
+ memoryId: Type.Optional(
770
+ Type.String({ description: "Specific memory ID to delete" })
771
+ ),
772
+ agentId: Type.Optional(
773
+ Type.String({
774
+ description: `Agent ID to scope deletion to a specific agent's memories (e.g. "researcher").`
775
+ })
776
+ )
777
+ }),
778
+ async execute(_toolCallId, params) {
779
+ const { query, memoryId, agentId } = params;
780
+ try {
781
+ if (memoryId) {
782
+ await provider.delete(memoryId);
783
+ return {
784
+ content: [
785
+ { type: "text", text: `Memory ${memoryId} forgotten.` }
786
+ ],
787
+ details: { action: "deleted", id: memoryId }
788
+ };
789
+ }
790
+ if (query) {
791
+ const uid = _resolveUserId({ agentId });
792
+ const results = await provider.search(
793
+ query,
794
+ buildSearchOptions(uid, 5)
795
+ );
796
+ if (!results || results.length === 0) {
797
+ return {
798
+ content: [
799
+ { type: "text", text: "No matching memories found." }
800
+ ],
801
+ details: { found: 0 }
802
+ };
803
+ }
804
+ if (results.length === 1 || (results[0].score ?? 0) > 0.9) {
805
+ await provider.delete(results[0].id);
806
+ return {
807
+ content: [
808
+ {
809
+ type: "text",
810
+ text: `Forgotten: "${results[0].memory}"`
811
+ }
812
+ ],
813
+ details: { action: "deleted", id: results[0].id }
814
+ };
815
+ }
816
+ const list = results.map(
817
+ (r) => `- [${r.id}] ${r.memory.slice(0, 80)}${r.memory.length > 80 ? "..." : ""} (score: ${((r.score ?? 0) * 100).toFixed(0)}%)`
818
+ ).join("\n");
819
+ const candidates = results.map((r) => ({
820
+ id: r.id,
821
+ memory: r.memory,
822
+ score: r.score
823
+ }));
824
+ return {
825
+ content: [
826
+ {
827
+ type: "text",
828
+ text: `Found ${results.length} candidates. Specify memoryId to delete:
829
+ ${list}`
830
+ }
831
+ ],
832
+ details: { action: "candidates", candidates }
833
+ };
834
+ }
835
+ return {
836
+ content: [
837
+ { type: "text", text: "Provide a query or memoryId." }
838
+ ],
839
+ details: { error: "missing_param" }
840
+ };
841
+ } catch (err) {
842
+ return {
843
+ content: [
844
+ {
845
+ type: "text",
846
+ text: `Memory forget failed: ${String(err)}`
847
+ }
848
+ ],
849
+ details: { error: String(err) }
850
+ };
851
+ }
852
+ }
853
+ },
854
+ { name: "memory_forget" }
855
+ );
856
+ api.registerCli(
857
+ ({ program }) => {
858
+ const mem0 = program.command("mem0").description("Mem0 memory plugin commands");
859
+ mem0.command("search").description("Search memories in Mem0").argument("<query>", "Search query").option("--limit <n>", "Max results", String(cfg.topK)).option("--scope <scope>", 'Memory scope: "session", "long-term", or "all"', "all").option("--agent <agentId>", "Search a specific agent's memory namespace").action(async (query, opts) => {
860
+ try {
861
+ const limit = parseInt(opts.limit, 10);
862
+ const scope = opts.scope;
863
+ const uid = opts.agent ? _agentUserId(opts.agent) : _effectiveUserId(currentSessionId);
864
+ let allResults = [];
865
+ if (scope === "session" || scope === "all") {
866
+ if (currentSessionId) {
867
+ const sessionResults = await provider.search(
868
+ query,
869
+ buildSearchOptions(uid, limit, currentSessionId)
870
+ );
871
+ if (sessionResults?.length) {
872
+ allResults.push(...sessionResults.map((r) => ({ ...r, _scope: "session" })));
873
+ }
874
+ } else if (scope === "session") {
875
+ console.log("No active session ID available for session-scoped search.");
876
+ return;
877
+ }
878
+ }
879
+ if (scope === "long-term" || scope === "all") {
880
+ const longTermResults = await provider.search(
881
+ query,
882
+ buildSearchOptions(uid, limit)
883
+ );
884
+ if (longTermResults?.length) {
885
+ allResults.push(...longTermResults.map((r) => ({ ...r, _scope: "long-term" })));
886
+ }
887
+ }
888
+ if (scope === "all") {
889
+ const seen = /* @__PURE__ */ new Set();
890
+ allResults = allResults.filter((r) => {
891
+ if (seen.has(r.id)) return false;
892
+ seen.add(r.id);
893
+ return true;
894
+ });
895
+ }
896
+ if (!allResults.length) {
897
+ console.log("No memories found.");
898
+ return;
899
+ }
900
+ const output = allResults.map((r) => ({
901
+ id: r.id,
902
+ memory: r.memory,
903
+ score: r.score,
904
+ scope: r._scope,
905
+ categories: r.categories,
906
+ created_at: r.created_at
907
+ }));
908
+ console.log(JSON.stringify(output, null, 2));
909
+ } catch (err) {
910
+ console.error(`Search failed: ${String(err)}`);
911
+ }
912
+ });
913
+ mem0.command("stats").description("Show memory statistics from Mem0").option("--agent <agentId>", "Show stats for a specific agent").action(async (opts) => {
914
+ try {
915
+ const uid = opts.agent ? _agentUserId(opts.agent) : cfg.userId;
916
+ const memories = await provider.getAll({
917
+ user_id: uid,
918
+ source: "OPENCLAW"
919
+ });
920
+ console.log(`Mode: ${cfg.mode}`);
921
+ console.log(`User: ${uid}${opts.agent ? ` (agent: ${opts.agent})` : ""}`);
922
+ console.log(
923
+ `Total memories: ${Array.isArray(memories) ? memories.length : "unknown"}`
924
+ );
925
+ console.log(`Graph enabled: ${cfg.enableGraph}`);
926
+ console.log(
927
+ `Auto-recall: ${cfg.autoRecall}, Auto-capture: ${cfg.autoCapture}`
928
+ );
929
+ } catch (err) {
930
+ console.error(`Stats failed: ${String(err)}`);
931
+ }
932
+ });
933
+ },
934
+ { commands: ["mem0"] }
935
+ );
936
+ if (cfg.autoRecall) {
937
+ api.on("before_agent_start", async (event, ctx) => {
938
+ if (!event.prompt || event.prompt.length < 5) return;
939
+ const sessionId = ctx?.sessionKey ?? void 0;
940
+ if (sessionId) currentSessionId = sessionId;
941
+ try {
942
+ const longTermResults = await provider.search(
943
+ event.prompt,
944
+ buildSearchOptions(void 0, void 0, void 0, sessionId)
945
+ );
946
+ let sessionResults = [];
947
+ if (currentSessionId) {
948
+ sessionResults = await provider.search(
949
+ event.prompt,
950
+ buildSearchOptions(void 0, void 0, currentSessionId, sessionId)
951
+ );
952
+ }
953
+ const longTermIds = new Set(longTermResults.map((r) => r.id));
954
+ const uniqueSessionResults = sessionResults.filter(
955
+ (r) => !longTermIds.has(r.id)
956
+ );
957
+ if (longTermResults.length === 0 && uniqueSessionResults.length === 0) return;
958
+ let memoryContext = "";
959
+ if (longTermResults.length > 0) {
960
+ memoryContext += longTermResults.map(
961
+ (r) => `- ${r.memory}${r.categories?.length ? ` [${r.categories.join(", ")}]` : ""}`
962
+ ).join("\n");
963
+ }
964
+ if (uniqueSessionResults.length > 0) {
965
+ if (memoryContext) memoryContext += "\n";
966
+ memoryContext += "\nSession memories:\n";
967
+ memoryContext += uniqueSessionResults.map((r) => `- ${r.memory}`).join("\n");
968
+ }
969
+ const totalCount = longTermResults.length + uniqueSessionResults.length;
970
+ api.logger.info(
971
+ `openclaw-mem0: injecting ${totalCount} memories into context (${longTermResults.length} long-term, ${uniqueSessionResults.length} session)`
972
+ );
973
+ return {
974
+ prependContext: `<relevant-memories>
975
+ The following memories may be relevant to this conversation:
976
+ ${memoryContext}
977
+ </relevant-memories>`
978
+ };
979
+ } catch (err) {
980
+ api.logger.warn(`openclaw-mem0: recall failed: ${String(err)}`);
981
+ }
982
+ });
983
+ }
984
+ if (cfg.autoCapture) {
985
+ api.on("agent_end", async (event, ctx) => {
986
+ if (!event.success || !event.messages || event.messages.length === 0) {
987
+ return;
988
+ }
989
+ const sessionId = ctx?.sessionKey ?? void 0;
990
+ if (sessionId) currentSessionId = sessionId;
991
+ try {
992
+ const recentMessages = event.messages.slice(-10);
993
+ const formattedMessages = [];
994
+ for (const msg of recentMessages) {
995
+ if (!msg || typeof msg !== "object") continue;
996
+ const msgObj = msg;
997
+ const role = msgObj.role;
998
+ if (role !== "user" && role !== "assistant") continue;
999
+ let textContent = "";
1000
+ const content = msgObj.content;
1001
+ if (typeof content === "string") {
1002
+ textContent = content;
1003
+ } else if (Array.isArray(content)) {
1004
+ for (const block of content) {
1005
+ if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
1006
+ textContent += (textContent ? "\n" : "") + block.text;
1007
+ }
1008
+ }
1009
+ }
1010
+ if (!textContent) continue;
1011
+ if (textContent.includes("<relevant-memories>")) {
1012
+ textContent = textContent.replace(/<relevant-memories>[\s\S]*?<\/relevant-memories>\s*/g, "").trim();
1013
+ if (!textContent) continue;
1014
+ }
1015
+ formattedMessages.push({
1016
+ role,
1017
+ content: textContent
1018
+ });
1019
+ }
1020
+ if (formattedMessages.length === 0) return;
1021
+ const addOpts = buildAddOptions(void 0, currentSessionId, sessionId);
1022
+ const result = await provider.add(
1023
+ formattedMessages,
1024
+ addOpts
1025
+ );
1026
+ const capturedCount = result.results?.length ?? 0;
1027
+ if (capturedCount > 0) {
1028
+ api.logger.info(
1029
+ `openclaw-mem0: auto-captured ${capturedCount} memories`
1030
+ );
1031
+ }
1032
+ } catch (err) {
1033
+ api.logger.warn(`openclaw-mem0: capture failed: ${String(err)}`);
1034
+ }
1035
+ });
1036
+ }
1037
+ api.registerService({
1038
+ id: "openclaw-mem0",
1039
+ start: () => {
1040
+ api.logger.info(
1041
+ `openclaw-mem0: initialized (mode: ${cfg.mode}, user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture})`
1042
+ );
1043
+ },
1044
+ stop: () => {
1045
+ api.logger.info("openclaw-mem0: stopped");
1046
+ }
1047
+ });
1048
+ }
1049
+ };
1050
+ var index_default = memoryPlugin;
1051
+ export {
1052
+ agentUserId,
1053
+ index_default as default,
1054
+ effectiveUserId,
1055
+ extractAgentId,
1056
+ resolveUserId
1057
+ };
1058
+ //# sourceMappingURL=index.js.map