mr-memory 2.9.12 → 2.10.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 (2) hide show
  1. package/index.ts +90 -9
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -15,11 +15,21 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
15
15
 
16
16
  const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
17
17
 
18
- /** Strip media-attached references so OpenClaw's image detection doesn't
19
- * load old screenshots/photos from file paths embedded in memories. */
20
- const MEDIA_ATTACHED_RE = /\[media attached:\s*[^\]]*\]/gi;
18
+ /** Strip media-attached references and OpenClaw media instruction text so
19
+ * old screenshots/photos/audio don't get stored and re-injected forever. */
20
+ const MEDIA_ATTACHED_RE = /\[media attached[^\]]*\]/gi;
21
+ const MEDIA_TAGS_RE = /<media:(?:audio|image|video|document)>/gi;
22
+ const MEDIA_INSTRUCTION_RE = /To send an image back,[\s\S]*?Keep caption in the text body\./gi;
23
+ const MEDIA_INBOUND_PATH_RE = /\/Users\/[^\s]*\/media\/inbound\/[^\s)\]"]*/gi;
24
+ const IMAGE_DATA_RE = /\[image data removed[^\]]*\]/gi;
21
25
  function stripMediaReferences(text: string): string {
22
- return text.replace(MEDIA_ATTACHED_RE, "[media reference removed]");
26
+ return text
27
+ .replace(MEDIA_INSTRUCTION_RE, "")
28
+ .replace(MEDIA_ATTACHED_RE, "[media reference removed]")
29
+ .replace(MEDIA_TAGS_RE, "")
30
+ .replace(MEDIA_INBOUND_PATH_RE, "[media reference removed]")
31
+ .replace(IMAGE_DATA_RE, "")
32
+ .replace(/\n{3,}/g, "\n\n");
23
33
  }
24
34
 
25
35
  /** Wrap raw memory context in XML tags with a strong instruction */
@@ -38,6 +48,63 @@ function stripOldMemory(text: string): string {
38
48
  return text.replace(MEMORY_TAG_RE, "").replace(LEGACY_TAG_RE, "").trim();
39
49
  }
40
50
 
51
+ // ── Ingest sanitization: strip system noise so only real conversations get stored
52
+ // Patterns that indicate system-generated messages (not user conversations)
53
+ const SYSTEM_NOISE_PATTERNS = [
54
+ // Heartbeat prompts and responses
55
+ /^Read HEARTBEAT\.md if it exists/,
56
+ /^HEARTBEAT_OK\s*$/,
57
+ // Pre-compaction flush prompts
58
+ /^Pre-compaction memory flush\./,
59
+ // Post-compaction audit warnings
60
+ /⚠️ Post-Compaction Audit:/,
61
+ // Silent reply ack
62
+ /^NO_REPLY\s*$/,
63
+ // Agent abort notices
64
+ /^Note: The previous agent run was aborted/,
65
+ // Queued message headers
66
+ /^\[Queued messages while agent was busy\]/,
67
+ // Media reference placeholders
68
+ /^\[media reference removed\]\s*$/,
69
+ // System event timestamps (cron fires, exec failures, etc.)
70
+ /^System: \[\d{4}-\d{2}-\d{2}/,
71
+ /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [A-Z]{2,4}\] ⚠️/,
72
+ // Memory flush with date pattern
73
+ /^Store durable memories now/,
74
+ // Current time system lines (injected by OpenClaw into system events)
75
+ /^Current time: .+\(America\//,
76
+ ];
77
+
78
+ /** Strip OpenClaw envelope metadata from user messages */
79
+ const ENVELOPE_METADATA_RE = /Conversation info \(untrusted metadata\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
80
+ const SENDER_METADATA_RE = /Sender \(untrusted metadata\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
81
+ const REPLY_CONTEXT_RE = /Replied message \(untrusted, for context\):\s*```json\s*\{[^}]*\}\s*```\s*/g;
82
+ const MEMORY_INSTRUCTION_RE = /The above are retrieved memories from past conversations[^\n]*\n*/g;
83
+ const IMPORTANT_MEMORY_RE = /IMPORTANT: The above (?:are retrieved memories|block contains retrieved memories)[^\n]*\n*/g;
84
+
85
+ /** Sanitize text before storing in memory — removes system noise and envelope metadata */
86
+ function sanitizeForIngest(text: string): string {
87
+ // 1. Strip our own memory injection wrapper (already exists as stripOldMemory)
88
+ let cleaned = stripOldMemory(text);
89
+ // 2. Strip envelope metadata blocks
90
+ cleaned = cleaned
91
+ .replace(ENVELOPE_METADATA_RE, "")
92
+ .replace(SENDER_METADATA_RE, "")
93
+ .replace(REPLY_CONTEXT_RE, "")
94
+ .replace(MEMORY_INSTRUCTION_RE, "")
95
+ .replace(IMPORTANT_MEMORY_RE, "");
96
+ // 3. Collapse excessive whitespace from removals
97
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
98
+ return cleaned;
99
+ }
100
+
101
+ /** Check if a message is system noise that should not be stored */
102
+ function isSystemNoise(text: string): boolean {
103
+ const trimmed = text.trim();
104
+ if (!trimmed) return true;
105
+ return SYSTEM_NOISE_PATTERNS.some(pattern => pattern.test(trimmed));
106
+ }
107
+
41
108
  // Workspace files OpenClaw loads into the system prompt
42
109
  const WORKSPACE_FILES = [
43
110
  "IDENTITY.md", "USER.md", "MEMORY.md", "HEARTBEAT.md",
@@ -403,11 +470,17 @@ const memoryRouterPlugin = {
403
470
 
404
471
  const toStore: Array<{ role: string; content: string }> = [];
405
472
 
406
- // Add the user message
473
+ // Add the user message (sanitized)
407
474
  if (lastUserIdx >= 0) {
408
475
  const userMsg = msgs[lastUserIdx] as { content?: unknown };
409
- const userText = extractText(userMsg.content);
410
- if (userText) toStore.push({ role: "user", content: stripMediaReferences(userText) });
476
+ let userText = extractText(userMsg.content);
477
+ if (userText) {
478
+ // Sanitize: strip memory injections, envelope metadata, and system noise
479
+ userText = sanitizeForIngest(stripMediaReferences(userText));
480
+ if (userText && !isSystemNoise(userText)) {
481
+ toStore.push({ role: "user", content: userText });
482
+ }
483
+ }
411
484
  }
412
485
 
413
486
  // Collect ALL assistant messages after the last user message
@@ -421,10 +494,18 @@ const memoryRouterPlugin = {
421
494
  }
422
495
  if (assistantParts.length > 0) {
423
496
  // Strip media refs so image paths don't get stored and re-injected
424
- toStore.push({ role: "assistant", content: stripMediaReferences(assistantParts.join("\n\n")) });
497
+ let assistantText = stripMediaReferences(assistantParts.join("\n\n"));
498
+ // Skip storing system noise responses (HEARTBEAT_OK, NO_REPLY, etc.)
499
+ if (!isSystemNoise(assistantText)) {
500
+ toStore.push({ role: "assistant", content: assistantText });
501
+ }
425
502
  }
426
503
 
427
- if (toStore.length === 0) return;
504
+ // Skip if nothing meaningful to store (both messages were system noise)
505
+ if (toStore.length === 0) {
506
+ log("memoryrouter: skipped ingest — system noise filtered out");
507
+ return;
508
+ }
428
509
 
429
510
  // Await the fetch so OpenClaw's runVoidHook keeps the event loop alive.
430
511
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "2.9.12",
3
+ "version": "2.10.0",
4
4
  "description": "MemoryRouter persistent memory plugin for OpenClaw — your AI remembers every conversation",
5
5
  "type": "module",
6
6
  "files": [