@icex-labs/openclaw-memory-engine 4.1.1 → 4.1.2

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.
@@ -5,11 +5,15 @@
5
5
  * No reliance on the agent calling tools — memory happens passively.
6
6
  */
7
7
 
8
- import { appendRecord } from "./archival.js";
8
+ import { loadArchival, appendRecord } from "./archival.js";
9
9
  import { indexEmbedding } from "./embedding.js";
10
10
  import { extractTriples, addTriple } from "./graph.js";
11
11
  import { resolveWorkspace } from "./paths.js";
12
12
 
13
+ /** Recent capture cache to prevent duplicates within short windows. */
14
+ const recentCaptures = new Map(); // content_hash → timestamp
15
+ const DEDUP_WINDOW_MS = 60_000; // 60 seconds
16
+
13
17
  // Minimum message length to consider for fact extraction
14
18
  const MIN_LENGTH = 20;
15
19
 
@@ -64,10 +68,39 @@ function isHighValue(content) {
64
68
  export function captureMessage(ws, content, source = "auto-capture") {
65
69
  if (!shouldCapture(content)) return null;
66
70
 
71
+ // Dedup: skip if same content captured in last 60s
72
+ const contentHash = content.slice(0, 100).toLowerCase().replace(/\s+/g, " ");
73
+ const now = Date.now();
74
+ if (recentCaptures.has(contentHash) && now - recentCaptures.get(contentHash) < DEDUP_WINDOW_MS) {
75
+ return null;
76
+ }
77
+ recentCaptures.set(contentHash, now);
78
+
79
+ // Clean old entries from dedup cache
80
+ if (recentCaptures.size > 200) {
81
+ for (const [key, ts] of recentCaptures) {
82
+ if (now - ts > DEDUP_WINDOW_MS) recentCaptures.delete(key);
83
+ }
84
+ }
85
+
86
+ // Also check against existing archival (keyword overlap)
87
+ const existing = loadArchival(ws);
88
+ const contentLower = content.toLowerCase();
89
+ const contentWords = new Set(contentLower.split(/\s+/).filter((w) => w.length > 2));
90
+ if (contentWords.size > 0) {
91
+ for (let i = existing.length - 1; i >= Math.max(0, existing.length - 50); i--) {
92
+ const ex = (existing[i].content || "").toLowerCase();
93
+ const exWords = new Set(ex.split(/\s+/).filter((w) => w.length > 2));
94
+ let overlap = 0;
95
+ for (const w of contentWords) { if (exWords.has(w)) overlap++; }
96
+ if (overlap / contentWords.size > 0.7) return null; // too similar to recent record
97
+ }
98
+ }
99
+
67
100
  const importance = isHighValue(content) ? 7 : 4;
68
101
  const entity = inferEntity(content);
69
102
 
70
- // Trim very long messages to first 500 chars
103
+ // Trim very long messages
71
104
  const trimmed = content.length > 500 ? content.slice(0, 497) + "..." : content;
72
105
 
73
106
  const record = appendRecord(ws, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icex-labs/openclaw-memory-engine",
3
- "version": "4.1.1",
3
+ "version": "4.1.2",
4
4
  "description": "MemGPT-style hierarchical memory plugin for OpenClaw — core memory block + archival storage with semantic search",
5
5
  "type": "module",
6
6
  "main": "index.js",