@memtensor/memos-local-openclaw-plugin 0.3.10 → 0.3.12
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/README.md +13 -27
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +32 -20
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +58 -28
- package/dist/viewer/server.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.cjs +64 -9
- package/src/viewer/html.ts +32 -20
- package/src/viewer/server.ts +58 -30
package/scripts/postinstall.cjs
CHANGED
|
@@ -18,6 +18,64 @@ function warn(msg) { console.log(`${YELLOW}[memos-local]${RESET} ${msg}`); }
|
|
|
18
18
|
function ok(msg) { console.log(`${GREEN}[memos-local]${RESET} ${msg}`); }
|
|
19
19
|
function fail(msg) { console.log(`${RED}[memos-local]${RESET} ${msg}`); }
|
|
20
20
|
|
|
21
|
+
const pluginDir = path.resolve(__dirname, "..");
|
|
22
|
+
|
|
23
|
+
console.log(`
|
|
24
|
+
${CYAN}${BOLD}┌──────────────────────────────────────────────────┐
|
|
25
|
+
│ MemOS Local Memory — postinstall │
|
|
26
|
+
└──────────────────────────────────────────────────┘${RESET}
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
log(`Plugin dir: ${DIM}${pluginDir}${RESET}`);
|
|
30
|
+
log(`Node: ${process.version} Platform: ${process.platform}-${process.arch}`);
|
|
31
|
+
|
|
32
|
+
/* ═══════════════════════════════════════════════════════════
|
|
33
|
+
* Phase 0: Ensure all dependencies are installed
|
|
34
|
+
* ═══════════════════════════════════════════════════════════ */
|
|
35
|
+
|
|
36
|
+
function ensureDependencies() {
|
|
37
|
+
const coreDeps = ["@sinclair/typebox", "uuid", "posthog-node", "@huggingface/transformers"];
|
|
38
|
+
const missing = [];
|
|
39
|
+
for (const dep of coreDeps) {
|
|
40
|
+
try {
|
|
41
|
+
require.resolve(dep, { paths: [pluginDir] });
|
|
42
|
+
} catch {
|
|
43
|
+
missing.push(dep);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (missing.length === 0) {
|
|
48
|
+
ok("All dependencies present.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
warn(`Missing dependencies: ${missing.join(", ")}`);
|
|
53
|
+
log("Running: npm install --omit=dev (this may take a moment)...");
|
|
54
|
+
|
|
55
|
+
const startMs = Date.now();
|
|
56
|
+
const result = spawnSync("npm", ["install", "--omit=dev"], {
|
|
57
|
+
cwd: pluginDir,
|
|
58
|
+
stdio: "inherit",
|
|
59
|
+
shell: true,
|
|
60
|
+
timeout: 120_000,
|
|
61
|
+
});
|
|
62
|
+
const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
|
|
63
|
+
|
|
64
|
+
if (result.status === 0) {
|
|
65
|
+
ok(`Dependencies installed (${elapsed}s).`);
|
|
66
|
+
} else {
|
|
67
|
+
fail(`npm install exited with code ${result.status} (${elapsed}s).`);
|
|
68
|
+
warn("Some features may not work. Try running manually:");
|
|
69
|
+
warn(` cd ${pluginDir} && npm install --omit=dev`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
ensureDependencies();
|
|
75
|
+
} catch (e) {
|
|
76
|
+
warn(`Dependency check skipped: ${e.message}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
21
79
|
/* ═══════════════════════════════════════════════════════════
|
|
22
80
|
* Phase 1: Clean up legacy plugin versions
|
|
23
81
|
* ═══════════════════════════════════════════════════════════ */
|
|
@@ -52,7 +110,6 @@ function cleanupLegacy() {
|
|
|
52
110
|
}
|
|
53
111
|
}
|
|
54
112
|
|
|
55
|
-
// Clean up openclaw.json config — migrate old plugin entries
|
|
56
113
|
const cfgPath = path.join(ocHome, "openclaw.json");
|
|
57
114
|
if (fs.existsSync(cfgPath)) {
|
|
58
115
|
try {
|
|
@@ -67,9 +124,8 @@ function cleanupLegacy() {
|
|
|
67
124
|
if (entries[oldKey]) {
|
|
68
125
|
const oldEntry = entries[oldKey];
|
|
69
126
|
if (!entries["memos-local-openclaw-plugin"]) {
|
|
70
|
-
// Migrate: copy old config to new key
|
|
71
127
|
entries["memos-local-openclaw-plugin"] = oldEntry;
|
|
72
|
-
log(`Migrated config: ${DIM}${oldKey}${RESET}
|
|
128
|
+
log(`Migrated config: ${DIM}${oldKey}${RESET} -> ${GREEN}memos-local-openclaw-plugin${RESET}`);
|
|
73
129
|
}
|
|
74
130
|
delete entries[oldKey];
|
|
75
131
|
cfgChanged = true;
|
|
@@ -77,7 +133,6 @@ function cleanupLegacy() {
|
|
|
77
133
|
}
|
|
78
134
|
}
|
|
79
135
|
|
|
80
|
-
// Fix source path if it points to old directory names
|
|
81
136
|
const newEntry = entries["memos-local-openclaw-plugin"];
|
|
82
137
|
if (newEntry && typeof newEntry.source === "string") {
|
|
83
138
|
const oldSource = newEntry.source;
|
|
@@ -86,14 +141,13 @@ function cleanupLegacy() {
|
|
|
86
141
|
.replace(/memos-lite-openclaw-plugin/g, "memos-local-openclaw-plugin")
|
|
87
142
|
.replace(/memos-lite/g, "memos-local");
|
|
88
143
|
if (newEntry.source !== oldSource) {
|
|
89
|
-
log(`Updated source path: ${DIM}${oldSource}${RESET}
|
|
144
|
+
log(`Updated source path: ${DIM}${oldSource}${RESET} -> ${GREEN}${newEntry.source}${RESET}`);
|
|
90
145
|
cfgChanged = true;
|
|
91
146
|
}
|
|
92
147
|
}
|
|
93
148
|
}
|
|
94
149
|
|
|
95
150
|
if (cfgChanged) {
|
|
96
|
-
// Write back with backup
|
|
97
151
|
const backup = cfgPath + ".bak-" + Date.now();
|
|
98
152
|
fs.copyFileSync(cfgPath, backup);
|
|
99
153
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
@@ -127,18 +181,18 @@ log("Checking better-sqlite3 native module...");
|
|
|
127
181
|
try {
|
|
128
182
|
require("better-sqlite3");
|
|
129
183
|
ok("better-sqlite3 is ready.");
|
|
184
|
+
console.log(`\n${GREEN}${BOLD}[memos-local] Setup complete!${RESET} Restart the gateway: ${CYAN}openclaw gateway stop && openclaw gateway start${RESET}\n`);
|
|
130
185
|
process.exit(0);
|
|
131
186
|
} catch (_) {
|
|
132
187
|
warn("better-sqlite3 native bindings not found, attempting rebuild...");
|
|
133
188
|
}
|
|
134
189
|
|
|
135
|
-
log(`Node: ${process.version} Platform: ${process.platform}-${process.arch}`);
|
|
136
190
|
log("Running: npm rebuild better-sqlite3 (this may take 30-60 seconds)...");
|
|
137
191
|
|
|
138
192
|
const startMs = Date.now();
|
|
139
193
|
|
|
140
194
|
const result = spawnSync("npm", ["rebuild", "better-sqlite3"], {
|
|
141
|
-
cwd:
|
|
195
|
+
cwd: pluginDir,
|
|
142
196
|
stdio: "inherit",
|
|
143
197
|
shell: true,
|
|
144
198
|
timeout: 180_000,
|
|
@@ -151,6 +205,7 @@ if (result.status === 0) {
|
|
|
151
205
|
delete require.cache[require.resolve("better-sqlite3")];
|
|
152
206
|
require("better-sqlite3");
|
|
153
207
|
ok(`better-sqlite3 rebuilt successfully (${elapsed}s).`);
|
|
208
|
+
console.log(`\n${GREEN}${BOLD}[memos-local] Setup complete!${RESET} Restart the gateway: ${CYAN}openclaw gateway stop && openclaw gateway start${RESET}\n`);
|
|
154
209
|
process.exit(0);
|
|
155
210
|
} catch (_) {
|
|
156
211
|
fail(`Rebuild completed but module still cannot load (${elapsed}s).`);
|
|
@@ -159,7 +214,6 @@ if (result.status === 0) {
|
|
|
159
214
|
fail(`Rebuild failed with exit code ${result.status} (${elapsed}s).`);
|
|
160
215
|
}
|
|
161
216
|
|
|
162
|
-
const pluginDir = path.resolve(__dirname, "..");
|
|
163
217
|
console.log(`
|
|
164
218
|
${YELLOW}${BOLD}╔══════════════════════════════════════════════════════════════╗
|
|
165
219
|
║ better-sqlite3 native module build failed ║
|
|
@@ -176,6 +230,7 @@ ${YELLOW}║${RESET}
|
|
|
176
230
|
${YELLOW}║${RESET} Then retry: ${YELLOW}║${RESET}
|
|
177
231
|
${YELLOW}║${RESET} ${GREEN}cd ${pluginDir}${RESET}
|
|
178
232
|
${YELLOW}║${RESET} ${GREEN}npm rebuild better-sqlite3${RESET} ${YELLOW}║${RESET}
|
|
233
|
+
${YELLOW}║${RESET} ${GREEN}openclaw gateway stop && openclaw gateway start${RESET} ${YELLOW}║${RESET}
|
|
179
234
|
${YELLOW}${BOLD}╚══════════════════════════════════════════════════════════════╝${RESET}
|
|
180
235
|
`);
|
|
181
236
|
|
package/src/viewer/html.ts
CHANGED
|
@@ -2868,17 +2868,22 @@ async function loadAll(){
|
|
|
2868
2868
|
}
|
|
2869
2869
|
|
|
2870
2870
|
async function loadStats(){
|
|
2871
|
-
|
|
2872
|
-
|
|
2871
|
+
let d;
|
|
2872
|
+
try{
|
|
2873
|
+
const r=await fetch('/api/stats');
|
|
2874
|
+
d=await r.json();
|
|
2875
|
+
}catch(e){ d={}; }
|
|
2876
|
+
if(!d||typeof d!=='object') d={};
|
|
2877
|
+
const tm=d.totalMemories||0;
|
|
2873
2878
|
const dedupB=d.dedupBreakdown||{};
|
|
2874
|
-
const activeCount=dedupB.active||
|
|
2879
|
+
const activeCount=dedupB.active||tm;
|
|
2875
2880
|
const inactiveCount=(dedupB.duplicate||0)+(dedupB.merged||0);
|
|
2876
|
-
document.getElementById('statTotal').textContent=
|
|
2881
|
+
document.getElementById('statTotal').textContent=tm;
|
|
2877
2882
|
if(inactiveCount>0){
|
|
2878
2883
|
document.getElementById('statTotal').title=activeCount+' '+t('stat.active')+', '+inactiveCount+' '+t('stat.deduped');
|
|
2879
2884
|
}
|
|
2880
|
-
document.getElementById('statSessions').textContent=d.totalSessions;
|
|
2881
|
-
document.getElementById('statEmbeddings').textContent=d.totalEmbeddings;
|
|
2885
|
+
document.getElementById('statSessions').textContent=d.totalSessions||0;
|
|
2886
|
+
document.getElementById('statEmbeddings').textContent=d.totalEmbeddings||0;
|
|
2882
2887
|
let days=0;
|
|
2883
2888
|
if(d.timeRange&&d.timeRange.earliest!=null&&d.timeRange.latest!=null){
|
|
2884
2889
|
let e=Number(d.timeRange.earliest), l=Number(d.timeRange.latest);
|
|
@@ -2900,7 +2905,7 @@ async function loadStats(){
|
|
|
2900
2905
|
}
|
|
2901
2906
|
|
|
2902
2907
|
const sl=document.getElementById('sessionList');
|
|
2903
|
-
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+
|
|
2908
|
+
sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+tm+'</span></div>';
|
|
2904
2909
|
(d.sessions||[]).forEach(s=>{
|
|
2905
2910
|
const isActive=activeSession===s.session_key;
|
|
2906
2911
|
const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
|
|
@@ -2927,16 +2932,23 @@ async function loadMemories(page){
|
|
|
2927
2932
|
if(page) currentPage=page;
|
|
2928
2933
|
const list=document.getElementById('memoryList');
|
|
2929
2934
|
list.innerHTML='<div class="spinner"></div>';
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2935
|
+
try{
|
|
2936
|
+
const p=getFilterParams();
|
|
2937
|
+
p.set('limit',PAGE_SIZE);
|
|
2938
|
+
p.set('page',currentPage);
|
|
2939
|
+
const r=await fetch('/api/memories?'+p.toString());
|
|
2940
|
+
const d=await r.json();
|
|
2941
|
+
totalPages=d.totalPages||1;
|
|
2942
|
+
totalCount=d.total||0;
|
|
2943
|
+
document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
|
|
2944
|
+
renderMemories(d.memories||[]);
|
|
2945
|
+
renderPagination();
|
|
2946
|
+
}catch(e){
|
|
2947
|
+
list.innerHTML='';
|
|
2948
|
+
totalPages=1;totalCount=0;
|
|
2949
|
+
renderMemories([]);
|
|
2950
|
+
renderPagination();
|
|
2951
|
+
}
|
|
2940
2952
|
}
|
|
2941
2953
|
|
|
2942
2954
|
async function doSearch(q){
|
|
@@ -3261,11 +3273,11 @@ async function migrateScan(){
|
|
|
3261
3273
|
|
|
3262
3274
|
try{
|
|
3263
3275
|
const r=await fetch('/api/migrate/scan');
|
|
3264
|
-
|
|
3265
|
-
|
|
3276
|
+
const d=await r.json().catch(()=>({}));
|
|
3277
|
+
if(d.error && !d.sqliteFiles) throw new Error(d.error);
|
|
3266
3278
|
migrateScanData=d;
|
|
3267
3279
|
|
|
3268
|
-
const files=d.sqliteFiles
|
|
3280
|
+
const files=Array.isArray(d.sqliteFiles)?d.sqliteFiles:[];
|
|
3269
3281
|
const sess=d.sessions||{count:0,messages:0};
|
|
3270
3282
|
const sqliteTotal=files.reduce((s,f)=>s+f.chunks,0);
|
|
3271
3283
|
document.getElementById('migrateSqliteCount').textContent=sqliteTotal;
|
package/src/viewer/server.ts
CHANGED
|
@@ -447,36 +447,55 @@ export class ViewerServer {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
private serveStats(res: http.ServerResponse): void {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
let dedupBreakdown: Record<string, number> = {};
|
|
450
|
+
const emptyStats = {
|
|
451
|
+
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
452
|
+
embeddingProvider: this.embedder?.provider ?? "none",
|
|
453
|
+
roleBreakdown: {}, kindBreakdown: {}, dedupBreakdown: {},
|
|
454
|
+
timeRange: { earliest: null, latest: null },
|
|
455
|
+
sessions: [],
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
if (!this.store || !(this.store as any).db) {
|
|
459
|
+
this.jsonResponse(res, emptyStats);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
465
463
|
try {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
const db = (this.store as any).db;
|
|
465
|
+
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
|
|
466
|
+
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
|
|
467
|
+
const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
|
|
468
|
+
const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks").get() as any;
|
|
469
|
+
let embCount = 0;
|
|
470
|
+
try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
|
|
471
|
+
const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
|
|
472
|
+
const sessionList = db.prepare(
|
|
473
|
+
"SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks GROUP BY session_key ORDER BY latest DESC",
|
|
474
|
+
).all() as any[];
|
|
475
|
+
|
|
476
|
+
let skillCount = 0;
|
|
477
|
+
try { skillCount = (db.prepare("SELECT COUNT(*) as count FROM skills").get() as any).count; } catch { /* table may not exist yet */ }
|
|
478
|
+
|
|
479
|
+
let dedupBreakdown: Record<string, number> = {};
|
|
480
|
+
try {
|
|
481
|
+
const dedupRows = db.prepare("SELECT dedup_status, COUNT(*) as count FROM chunks GROUP BY dedup_status").all() as any[];
|
|
482
|
+
dedupBreakdown = Object.fromEntries(dedupRows.map((d: any) => [d.dedup_status ?? "active", d.count]));
|
|
483
|
+
} catch { /* column may not exist yet */ }
|
|
469
484
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
485
|
+
this.jsonResponse(res, {
|
|
486
|
+
totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
|
|
487
|
+
totalSkills: skillCount,
|
|
488
|
+
embeddingProvider: this.embedder.provider,
|
|
489
|
+
roleBreakdown: Object.fromEntries(roles.map((r: any) => [r.role, r.count])),
|
|
490
|
+
kindBreakdown: Object.fromEntries(kinds.map((k: any) => [k.kind, k.count])),
|
|
491
|
+
dedupBreakdown,
|
|
492
|
+
timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
|
|
493
|
+
sessions: sessionList,
|
|
494
|
+
});
|
|
495
|
+
} catch (e) {
|
|
496
|
+
this.log.warn(`stats error: ${e}`);
|
|
497
|
+
this.jsonResponse(res, emptyStats);
|
|
498
|
+
}
|
|
480
499
|
}
|
|
481
500
|
|
|
482
501
|
private async serveSearch(_req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<void> {
|
|
@@ -943,8 +962,17 @@ export class ViewerServer {
|
|
|
943
962
|
});
|
|
944
963
|
} catch (e) {
|
|
945
964
|
this.log.warn(`migrate/scan error: ${e}`);
|
|
946
|
-
|
|
947
|
-
|
|
965
|
+
this.jsonResponse(res, {
|
|
966
|
+
sqliteFiles: [],
|
|
967
|
+
sessions: { count: 0, messages: 0 },
|
|
968
|
+
totalItems: 0,
|
|
969
|
+
configReady: false,
|
|
970
|
+
hasEmbedding: false,
|
|
971
|
+
hasSummarizer: false,
|
|
972
|
+
hasImportedData: false,
|
|
973
|
+
importedSessionCount: 0,
|
|
974
|
+
error: String(e),
|
|
975
|
+
});
|
|
948
976
|
}
|
|
949
977
|
}
|
|
950
978
|
|