mr-memory 2.11.7 → 2.12.0

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.
Files changed (3) hide show
  1. package/index.ts +20 -13
  2. package/package.json +1 -1
  3. package/upload.ts +73 -1
package/index.ts CHANGED
@@ -276,11 +276,17 @@ const memoryRouterPlugin = {
276
276
  .map(b => b.text!)
277
277
  .join("\n");
278
278
  }
279
- // Strip old memory injections to prevent stacking
280
- if (text) contextPayload.push({ role: m.role, content: m.role === "user" ? stripOldMemory(text) : text });
279
+ if (!text) continue;
280
+ // Sanitize ALL roles: strip memory injections, envelope metadata, system noise
281
+ const cleaned = sanitizeForIngest(text);
282
+ if (!cleaned || isSystemNoise(cleaned)) continue;
283
+ contextPayload.push({ role: m.role, content: cleaned });
281
284
  }
282
285
  }
283
- contextPayload.push({ role: "user", content: stripOldMemory(prompt) });
286
+ const cleanedPrompt = sanitizeForIngest(prompt);
287
+ if (cleanedPrompt && !isSystemNoise(cleanedPrompt)) {
288
+ contextPayload.push({ role: "user", content: cleanedPrompt });
289
+ }
284
290
 
285
291
 
286
292
  const res = await fetch(`${endpoint}/v1/memory/prepare`, {
@@ -359,9 +365,8 @@ const memoryRouterPlugin = {
359
365
  contextPayload.push({ role: "system", content: fullContext });
360
366
  }
361
367
 
362
- // Add conversation history
368
+ // Add conversation history (sanitized — strip noise, metadata, old memory injections)
363
369
  if (event.messages && Array.isArray(event.messages)) {
364
- let skipped = 0;
365
370
  for (const msg of event.messages) {
366
371
  const m = msg as { role?: string; content?: unknown };
367
372
  if (!m.role) continue;
@@ -377,17 +382,19 @@ const memoryRouterPlugin = {
377
382
  .join("\n");
378
383
  }
379
384
 
380
- if (text) {
381
- // Strip old memory injections to prevent stacking
382
- contextPayload.push({ role: m.role, content: m.role === "user" ? stripOldMemory(text) : text });
383
- } else {
384
- skipped++;
385
- }
385
+ if (!text) continue;
386
+ // Sanitize ALL roles: strip memory injections, envelope metadata, system noise
387
+ const cleaned = sanitizeForIngest(text);
388
+ if (!cleaned || isSystemNoise(cleaned)) continue;
389
+ contextPayload.push({ role: m.role, content: cleaned });
386
390
  }
387
391
  }
388
392
 
389
- // Add current user prompt (strip any residual memory tags)
390
- contextPayload.push({ role: "user", content: stripOldMemory(prompt) });
393
+ // Add current user prompt (sanitized)
394
+ const cleanedPrompt = sanitizeForIngest(prompt);
395
+ if (cleanedPrompt && !isSystemNoise(cleanedPrompt)) {
396
+ contextPayload.push({ role: "user", content: cleanedPrompt });
397
+ }
391
398
 
392
399
  // 4. Call /v1/memory/prepare
393
400
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "2.11.7",
3
+ "version": "2.12.0",
4
4
  "description": "MemoryRouter persistent memory plugin for OpenClaw — your AI remembers every conversation",
5
5
  "type": "module",
6
6
  "files": [
package/upload.ts CHANGED
@@ -5,6 +5,71 @@
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
7
 
8
+ // ── Sanitization utilities (shared with index.ts ingest logic)
9
+ // These MUST stay in sync with the patterns in index.ts
10
+
11
+ const MEMORY_TAG_RE = /<mr-memory>[\s\S]*?<\/mr-memory>\s*/g;
12
+ const LEGACY_TAG_RE = /<memory_context>[\s\S]*?<\/memory_context>\s*(?:The above are retrieved memories|IMPORTANT: The above block contains retrieved memories)[^\n]*\n*/g;
13
+ function stripOldMemory(text: string): string {
14
+ return text.replace(MEMORY_TAG_RE, "").replace(LEGACY_TAG_RE, "").trim();
15
+ }
16
+
17
+ const ENVELOPE_METADATA_RE = /Conversation info \(untrusted metadata\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
18
+ const SENDER_METADATA_RE = /Sender \(untrusted metadata\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
19
+ const REPLY_CONTEXT_RE = /Replied message \(untrusted, for context\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
20
+ const MEMORY_INSTRUCTION_RE = /The above are retrieved memories from past conversations[^\n]*\n*/g;
21
+ const IMPORTANT_MEMORY_RE = /IMPORTANT: The above (?:are retrieved memories|block contains retrieved memories)[^\n]*\n*/g;
22
+
23
+ const MEDIA_ATTACHED_RE = /\[media attached[^\]]*\]/gi;
24
+ const MEDIA_TAGS_RE = /<media:(?:audio|image|video|document)>/gi;
25
+ const MEDIA_INSTRUCTION_RE = /To send an image back,[\s\S]*?Keep caption in the text body\./gi;
26
+ const MEDIA_INBOUND_PATH_RE = /\/Users\/[^\s]*\/media\/inbound\/[^\s)\]"]*/gi;
27
+ const IMAGE_DATA_RE = /\[image data removed[^\]]*\]/gi;
28
+ function stripMediaReferences(text: string): string {
29
+ return text
30
+ .replace(MEDIA_INSTRUCTION_RE, "")
31
+ .replace(MEDIA_ATTACHED_RE, "[media reference removed]")
32
+ .replace(MEDIA_TAGS_RE, "")
33
+ .replace(MEDIA_INBOUND_PATH_RE, "[media reference removed]")
34
+ .replace(IMAGE_DATA_RE, "")
35
+ .replace(/\n{3,}/g, "\n\n");
36
+ }
37
+
38
+ function sanitizeForUpload(text: string): string {
39
+ let cleaned = stripOldMemory(text);
40
+ cleaned = stripMediaReferences(cleaned);
41
+ cleaned = cleaned
42
+ .replace(ENVELOPE_METADATA_RE, "")
43
+ .replace(SENDER_METADATA_RE, "")
44
+ .replace(REPLY_CONTEXT_RE, "")
45
+ .replace(MEMORY_INSTRUCTION_RE, "")
46
+ .replace(IMPORTANT_MEMORY_RE, "");
47
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
48
+ return cleaned;
49
+ }
50
+
51
+ const SYSTEM_NOISE_PATTERNS = [
52
+ /^Read HEARTBEAT\.md if it exists/,
53
+ /^HEARTBEAT_OK\s*$/,
54
+ /^Pre-compaction memory flush\./,
55
+ /⚠️ Post-Compaction Audit:/,
56
+ /^NO_REPLY\s*$/,
57
+ /^Note: The previous agent run was aborted/,
58
+ /^\[Queued messages while agent was busy\]/,
59
+ /^\[media reference removed\]\s*$/,
60
+ /^System: \[\d{4}-\d{2}-\d{2}/,
61
+ /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [A-Z]{2,4}\] ⚠️/,
62
+ /^Store durable memories now/,
63
+ /^Current time: .+\(America\//,
64
+ /^A new session was started via \/new or \/reset/,
65
+ ];
66
+
67
+ function isSystemNoise(text: string): boolean {
68
+ const trimmed = text.trim();
69
+ if (!trimmed) return true;
70
+ return SYSTEM_NOISE_PATTERNS.some(pattern => pattern.test(trimmed));
71
+ }
72
+
8
73
  type MemoryLine = {
9
74
  content: string;
10
75
  role: "user" | "assistant";
@@ -114,6 +179,11 @@ async function sessionToJsonl(filePath: string): Promise<MemoryLine[]> {
114
179
 
115
180
  if (!text || text.trim().length < 20) continue;
116
181
 
182
+ // Sanitize: strip memory injections, envelope metadata, media refs, system noise
183
+ text = sanitizeForUpload(text);
184
+ if (!text || text.trim().length < 20) continue;
185
+ if (isSystemNoise(text)) continue;
186
+
117
187
  let timestamp: number;
118
188
  if (typeof parsed.timestamp === "string") {
119
189
  timestamp = new Date(parsed.timestamp).getTime();
@@ -341,8 +411,10 @@ export async function runUpload(params: {
341
411
  );
342
412
 
343
413
  const result = (await response.json()) as {
344
- stats?: { stored?: number; failed?: number; inputItems?: number };
414
+ stats?: { stored?: number; failed?: number; inputItems?: number; path?: string; diagnostic?: string };
345
415
  errors?: string[];
416
+ path?: string;
417
+ diagnostic?: string;
346
418
  };
347
419
 
348
420
  const batchStored = result.stats?.stored ?? result.stats?.inputItems ?? batch.length;