@icex-labs/openclaw-memory-engine 3.3.2 → 3.4.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.
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * migrate-legacy.mjs — Import existing file-based memory into archival.jsonl
4
+ *
5
+ * Scans workspace for: MEMORY.md, memory/*.md, memory/weekly/*.md, memory/topics/*.md
6
+ * Extracts facts, deduplicates, and appends to memory/archival.jsonl.
7
+ *
8
+ * Usage: node migrate-legacy.mjs [workspace_path]
9
+ */
10
+
11
+ import { readFileSync, appendFileSync, existsSync, readdirSync } from "node:fs";
12
+ import { join, basename } from "node:path";
13
+
14
+ const WS = process.argv[2] || process.env.OPENCLAW_WORKSPACE || join(process.env.HOME || "/tmp", ".openclaw", "workspace");
15
+ const ARCHIVAL = join(WS, "memory", "archival.jsonl");
16
+
17
+ console.log(`🧠 Legacy memory migration`);
18
+ console.log(` Workspace: ${WS}`);
19
+ console.log(` Archival: ${ARCHIVAL}`);
20
+ console.log(``);
21
+
22
+ // Load existing archival for dedup
23
+ const existingContent = new Set();
24
+ if (existsSync(ARCHIVAL)) {
25
+ for (const line of readFileSync(ARCHIVAL, "utf-8").trim().split("\n").filter(Boolean)) {
26
+ try { existingContent.add(JSON.parse(line).content?.toLowerCase()); } catch {}
27
+ }
28
+ }
29
+ console.log(`Existing archival: ${existingContent.size} records`);
30
+
31
+ // Generic entity inference (no personal data)
32
+ const ENTITY_PATTERNS = [
33
+ [/\b(IBKR|Interactive Brokers|NAV|portfolio|投资|HELOC|mortgage|finance)/i, "finance"],
34
+ [/\b(immigration|PR|IRCC|CBSA|visa|律师|lawyer|petition)/i, "immigration"],
35
+ [/\b(quant|trading|backtest|signal|portfolio|Sharpe)/i, "trading"],
36
+ [/\b(doctor|医生|hospital|health|medication|药|体检|clinic)/i, "health"],
37
+ [/\b(car|vehicle|SUV|sedan|truck)\b/i, "vehicles"],
38
+ [/\b(k3d|ArgoCD|Helm|kubectl|GitOps|cluster|deploy|CI|CD)/i, "infrastructure"],
39
+ [/\b(OpenClaw|gateway|plugin|session|agent|memory|compaction)/i, "openclaw"],
40
+ [/\b(Discord|Telegram|Slack|bot|channel)/i, "messaging"],
41
+ [/\b(school|university|college|学校|education)/i, "education"],
42
+ [/\b(house|home|property|rent|房)/i, "property"],
43
+ [/\b(lawyer|legal|court|lawsuit|案|诉)/i, "legal"],
44
+ ];
45
+
46
+ function inferEntity(text) {
47
+ for (const [pat, name] of ENTITY_PATTERNS) {
48
+ if (pat.test(text)) return name;
49
+ }
50
+ return "general";
51
+ }
52
+
53
+ function extractFacts(text) {
54
+ const facts = [];
55
+ for (const line of text.split(/\n/).map((l) => l.trim()).filter(Boolean)) {
56
+ if (line.startsWith("#") || line.length < 15) continue;
57
+ if (/^(##|===|---|\*\*\*|```|>|\|)/.test(line)) continue;
58
+ const sentences = line.split(/(?<=[。.!!??;;])\s*/).filter(Boolean);
59
+ for (const s of sentences) {
60
+ const clean = s.replace(/^[-*•]\s*/, "").replace(/^\d+\.\s*/, "").trim();
61
+ if (clean.length >= 15 && clean.length <= 500) facts.push(clean);
62
+ }
63
+ }
64
+ return facts;
65
+ }
66
+
67
+ // Collect all legacy files
68
+ const files = [];
69
+
70
+ // MEMORY.md
71
+ const memoryMd = join(WS, "MEMORY.md");
72
+ if (existsSync(memoryMd)) files.push({ path: memoryMd, tag: "long-term" });
73
+
74
+ // memory/*.md (daily logs)
75
+ const memDir = join(WS, "memory");
76
+ if (existsSync(memDir)) {
77
+ for (const f of readdirSync(memDir).filter((f) => /\.md$/.test(f) && f !== ".abstract")) {
78
+ files.push({ path: join(memDir, f), tag: "daily" });
79
+ }
80
+ }
81
+
82
+ // memory/weekly/*.md
83
+ const weeklyDir = join(WS, "memory", "weekly");
84
+ if (existsSync(weeklyDir)) {
85
+ for (const f of readdirSync(weeklyDir).filter((f) => f.endsWith(".md"))) {
86
+ files.push({ path: join(weeklyDir, f), tag: "weekly" });
87
+ }
88
+ }
89
+
90
+ // memory/topics/*.md
91
+ const topicDir = join(WS, "memory", "topics");
92
+ if (existsSync(topicDir)) {
93
+ for (const f of readdirSync(topicDir).filter((f) => f.endsWith(".md"))) {
94
+ files.push({ path: join(topicDir, f), tag: "topic" });
95
+ }
96
+ }
97
+
98
+ if (files.length === 0) {
99
+ console.log("\nNo legacy memory files found. Nothing to migrate.");
100
+ process.exit(0);
101
+ }
102
+
103
+ console.log(`Found ${files.length} files to scan\n`);
104
+
105
+ let inserted = 0;
106
+ let skipped = 0;
107
+
108
+ for (const { path, tag } of files) {
109
+ const content = readFileSync(path, "utf-8");
110
+ const facts = extractFacts(content);
111
+ let fileInserted = 0;
112
+
113
+ for (const fact of facts) {
114
+ const factLower = fact.toLowerCase();
115
+
116
+ // Exact dedup
117
+ if (existingContent.has(factLower)) {
118
+ skipped++;
119
+ continue;
120
+ }
121
+
122
+ // Keyword overlap dedup (>75% overlap = skip)
123
+ let isDupe = false;
124
+ const factWords = new Set(factLower.split(/\s+/).filter((w) => w.length > 2));
125
+ if (factWords.size > 0) {
126
+ for (const ex of existingContent) {
127
+ const exWords = new Set(ex.split(/\s+/).filter((w) => w.length > 2));
128
+ let overlap = 0;
129
+ for (const w of factWords) {
130
+ if (exWords.has(w)) overlap++;
131
+ }
132
+ if (overlap / factWords.size > 0.75) {
133
+ isDupe = true;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ if (isDupe) {
139
+ skipped++;
140
+ continue;
141
+ }
142
+
143
+ const record = {
144
+ id: `arch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
145
+ ts: new Date().toISOString(),
146
+ last_accessed: null,
147
+ access_count: 0,
148
+ importance: 5,
149
+ content: fact,
150
+ entity: inferEntity(fact),
151
+ tags: [tag],
152
+ source: "migration",
153
+ };
154
+
155
+ appendFileSync(ARCHIVAL, JSON.stringify(record) + "\n", "utf-8");
156
+ existingContent.add(factLower);
157
+ inserted++;
158
+ fileInserted++;
159
+ }
160
+
161
+ if (fileInserted > 0) console.log(` ${basename(path)}: +${fileInserted} facts`);
162
+ }
163
+
164
+ console.log(`\n✅ Migration complete: ${inserted} facts imported, ${skipped} skipped (duplicates)`);
165
+ console.log(`Total archival: ${existingContent.size} records`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icex-labs/openclaw-memory-engine",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
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",
package/setup.sh CHANGED
@@ -106,6 +106,40 @@ else
106
106
  echo "⏭️ archival.jsonl already exists ($lines records)"
107
107
  fi
108
108
 
109
+ # --- 3b. Migrate legacy memory files into archival ---
110
+ if command -v node &>/dev/null && [ -f "$PLUGIN_DIR/extras/migrate-legacy.mjs" ]; then
111
+ # Check if there are legacy files to migrate
112
+ legacy_count=0
113
+ [ -f "$WORKSPACE/MEMORY.md" ] && legacy_count=$((legacy_count + 1))
114
+ legacy_count=$((legacy_count + $(ls "$MEMORY_DIR"/*.md 2>/dev/null | wc -l | tr -d ' ')))
115
+ legacy_count=$((legacy_count + $(ls "$MEMORY_DIR"/weekly/*.md 2>/dev/null | wc -l | tr -d ' ')))
116
+ legacy_count=$((legacy_count + $(ls "$MEMORY_DIR"/topics/*.md 2>/dev/null | wc -l | tr -d ' ')))
117
+
118
+ archival_count=$(wc -l < "$MEMORY_DIR/archival.jsonl" 2>/dev/null | tr -d ' ' || echo "0")
119
+
120
+ if [ "$legacy_count" -gt 0 ] && [ "$archival_count" -lt 10 ]; then
121
+ echo ""
122
+ echo "📦 Found $legacy_count legacy memory files (MEMORY.md, daily logs, weekly summaries, topics)."
123
+ if $NON_INTERACTIVE; then
124
+ echo " Migrating automatically..."
125
+ node "$PLUGIN_DIR/extras/migrate-legacy.mjs" "$WORKSPACE" 2>&1 | tail -3
126
+ else
127
+ printf " Migrate into archival memory? [Y/n]: "
128
+ read -r migrate_answer
129
+ if [ "${migrate_answer:-Y}" != "n" ] && [ "${migrate_answer:-Y}" != "N" ]; then
130
+ node "$PLUGIN_DIR/extras/migrate-legacy.mjs" "$WORKSPACE" 2>&1 | tail -5
131
+ else
132
+ echo "⏭️ Skipping migration. Run manually later: node $PLUGIN_DIR/extras/migrate-legacy.mjs $WORKSPACE"
133
+ fi
134
+ fi
135
+ echo ""
136
+ else
137
+ if [ "$archival_count" -gt 10 ]; then
138
+ echo "⏭️ Archival already has $archival_count records, skipping migration"
139
+ fi
140
+ fi
141
+ fi
142
+
109
143
  # --- 4. Install memory-maintenance.sh ---
110
144
  SCRIPTS_DIR="$WORKSPACE/scripts"
111
145
  mkdir -p "$SCRIPTS_DIR"