@memtensor/memos-local-openclaw-plugin 1.0.2-beta.3 → 1.0.2-beta.5
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/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +41 -1
- package/dist/capture/index.js.map +1 -1
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +20 -7
- package/dist/embedding/index.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +39 -25
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +39 -25
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +39 -25
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +19 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +98 -10
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +39 -25
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +8 -14
- package/dist/ingest/worker.js.map +1 -1
- package/dist/skill/bundled-memory-guide.d.ts +1 -1
- package/dist/skill/bundled-memory-guide.d.ts.map +1 -1
- package/dist/skill/bundled-memory-guide.js +9 -0
- package/dist/skill/bundled-memory-guide.js.map +1 -1
- package/dist/storage/sqlite.d.ts +14 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +42 -0
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +276 -51
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +4 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +152 -27
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +38 -85
- package/package.json +2 -1
- package/src/capture/index.ts +56 -1
- package/src/embedding/index.ts +13 -7
- package/src/ingest/providers/anthropic.ts +39 -25
- package/src/ingest/providers/bedrock.ts +39 -25
- package/src/ingest/providers/gemini.ts +39 -25
- package/src/ingest/providers/index.ts +112 -9
- package/src/ingest/providers/openai.ts +39 -25
- package/src/ingest/worker.ts +8 -15
- package/src/skill/bundled-memory-guide.ts +9 -0
- package/src/storage/sqlite.ts +49 -0
- package/src/viewer/html.ts +275 -50
- package/src/viewer/server.ts +143 -32
package/src/viewer/server.ts
CHANGED
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import readline from "node:readline";
|
|
7
7
|
import type { SqliteStore } from "../storage/sqlite";
|
|
8
8
|
import type { Embedder } from "../embedding";
|
|
9
|
-
import { Summarizer } from "../ingest/providers";
|
|
9
|
+
import { Summarizer, modelHealth } from "../ingest/providers";
|
|
10
10
|
import { findTopSimilar } from "../ingest/dedup";
|
|
11
11
|
import { stripInboundMetadata } from "../capture";
|
|
12
12
|
import { vectorSearch } from "../storage/vector";
|
|
@@ -17,6 +17,11 @@ import type { Logger, Chunk, PluginContext } from "../types";
|
|
|
17
17
|
import { viewerHTML } from "./html";
|
|
18
18
|
import { v4 as uuid } from "uuid";
|
|
19
19
|
|
|
20
|
+
function normalizeTimestamp(ts: number): number {
|
|
21
|
+
if (ts < 1e12) return ts * 1000;
|
|
22
|
+
return ts;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export interface ViewerServerOptions {
|
|
21
26
|
store: SqliteStore;
|
|
22
27
|
embedder: Embedder;
|
|
@@ -43,6 +48,14 @@ export class ViewerServer {
|
|
|
43
48
|
private readonly ctx?: PluginContext;
|
|
44
49
|
|
|
45
50
|
private static readonly SESSION_TTL = 24 * 60 * 60 * 1000;
|
|
51
|
+
private static readonly PLUGIN_VERSION: string = (() => {
|
|
52
|
+
try {
|
|
53
|
+
const pkgPath = path.resolve(__dirname, "../../package.json");
|
|
54
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
55
|
+
} catch {
|
|
56
|
+
return "unknown";
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
46
59
|
private resetToken: string;
|
|
47
60
|
private migrationRunning = false;
|
|
48
61
|
private migrationAbort = false;
|
|
@@ -93,11 +106,28 @@ export class ViewerServer {
|
|
|
93
106
|
this.server.listen(this.port, "127.0.0.1", () => {
|
|
94
107
|
const addr = this.server!.address();
|
|
95
108
|
const actualPort = typeof addr === "object" && addr ? addr.port : this.port;
|
|
109
|
+
this.autoCleanupPolluted();
|
|
96
110
|
resolve(`http://127.0.0.1:${actualPort}`);
|
|
97
111
|
});
|
|
98
112
|
});
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
private autoCleanupPolluted(): void {
|
|
116
|
+
try {
|
|
117
|
+
const polluted = this.store.findPollutedUserChunks();
|
|
118
|
+
let deleted = 0;
|
|
119
|
+
for (const { id } of polluted) {
|
|
120
|
+
if (this.store.deleteChunk(id)) deleted++;
|
|
121
|
+
}
|
|
122
|
+
const fixed = this.store.fixMixedUserChunks();
|
|
123
|
+
if (deleted > 0 || fixed > 0) {
|
|
124
|
+
this.log.info(`Auto-cleanup: removed ${deleted} polluted chunks, fixed ${fixed} mixed user+assistant chunks`);
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.log.warn(`Auto-cleanup failed: ${err}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
stop(): void {
|
|
102
132
|
this.server?.close();
|
|
103
133
|
this.server = null;
|
|
@@ -216,9 +246,11 @@ export class ViewerServer {
|
|
|
216
246
|
else if (p === "/api/config" && req.method === "GET") this.serveConfig(res);
|
|
217
247
|
else if (p === "/api/config" && req.method === "PUT") this.handleSaveConfig(req, res);
|
|
218
248
|
else if (p === "/api/test-model" && req.method === "POST") this.handleTestModel(req, res);
|
|
249
|
+
else if (p === "/api/model-health" && req.method === "GET") this.serveModelHealth(res);
|
|
219
250
|
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
220
251
|
else if (p === "/api/update-check" && req.method === "GET") this.handleUpdateCheck(res);
|
|
221
252
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
253
|
+
else if (p === "/api/cleanup-polluted" && req.method === "POST") this.handleCleanupPolluted(res);
|
|
222
254
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
223
255
|
else if (p === "/api/migrate/start" && req.method === "POST") this.handleMigrateStart(req, res);
|
|
224
256
|
else if (p === "/api/migrate/status" && req.method === "GET") this.handleMigrateStatus(res);
|
|
@@ -339,7 +371,7 @@ export class ViewerServer {
|
|
|
339
371
|
|
|
340
372
|
private serveViewer(res: http.ServerResponse): void {
|
|
341
373
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0", "Pragma": "no-cache", "Expires": "0" });
|
|
342
|
-
res.end(viewerHTML);
|
|
374
|
+
res.end(viewerHTML(ViewerServer.PLUGIN_VERSION));
|
|
343
375
|
}
|
|
344
376
|
|
|
345
377
|
// ─── Data APIs ───
|
|
@@ -485,7 +517,15 @@ export class ViewerServer {
|
|
|
485
517
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
|
|
486
518
|
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
|
|
487
519
|
const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
|
|
488
|
-
const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks").get() as any;
|
|
520
|
+
const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE dedup_status = 'active'").get() as any;
|
|
521
|
+
const MIN_VALID_TS = 1704067200000; // 2024-01-01
|
|
522
|
+
if (timeRange.earliest != null && timeRange.earliest < MIN_VALID_TS) {
|
|
523
|
+
timeRange.earliest = db.prepare("SELECT MIN(created_at) as v FROM chunks WHERE dedup_status = 'active' AND created_at >= ?").get(MIN_VALID_TS) as any;
|
|
524
|
+
timeRange.earliest = timeRange.earliest?.v ?? null;
|
|
525
|
+
}
|
|
526
|
+
if (timeRange.latest != null && timeRange.latest < MIN_VALID_TS) {
|
|
527
|
+
timeRange.latest = null;
|
|
528
|
+
}
|
|
489
529
|
let embCount = 0;
|
|
490
530
|
try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
|
|
491
531
|
const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
|
|
@@ -531,44 +571,71 @@ export class ViewerServer {
|
|
|
531
571
|
|
|
532
572
|
const role = url.searchParams.get("role") ?? undefined;
|
|
533
573
|
const kind = url.searchParams.get("kind") ?? undefined;
|
|
574
|
+
const session = url.searchParams.get("session") ?? undefined;
|
|
575
|
+
const owner = url.searchParams.get("owner") ?? undefined;
|
|
534
576
|
const dateFrom = url.searchParams.get("dateFrom") ?? undefined;
|
|
535
577
|
const dateTo = url.searchParams.get("dateTo") ?? undefined;
|
|
536
578
|
|
|
537
579
|
const passesFilter = (r: any): boolean => {
|
|
538
580
|
if (role && r.role !== role) return false;
|
|
539
581
|
if (kind && r.kind !== kind) return false;
|
|
582
|
+
if (session && r.session_key !== session) return false;
|
|
583
|
+
if (owner && r.owner !== owner) return false;
|
|
540
584
|
if (dateFrom && r.created_at < new Date(dateFrom).getTime()) return false;
|
|
541
585
|
if (dateTo && r.created_at > new Date(dateTo).getTime()) return false;
|
|
542
586
|
return true;
|
|
543
587
|
};
|
|
544
588
|
|
|
589
|
+
const ftsFilters: string[] = [];
|
|
590
|
+
const likeFilters: string[] = [];
|
|
591
|
+
const sqlParams: any[] = [];
|
|
592
|
+
if (session) { ftsFilters.push("c.session_key = ?"); likeFilters.push("session_key = ?"); sqlParams.push(session); }
|
|
593
|
+
if (owner) { ftsFilters.push("c.owner = ?"); likeFilters.push("owner = ?"); sqlParams.push(owner); }
|
|
594
|
+
const ftsWhere = ftsFilters.length > 0 ? " AND " + ftsFilters.join(" AND ") : "";
|
|
595
|
+
const likeWhere = likeFilters.length > 0 ? " AND " + likeFilters.join(" AND ") : "";
|
|
596
|
+
|
|
545
597
|
const db = (this.store as any).db;
|
|
546
598
|
let ftsResults: any[] = [];
|
|
547
599
|
try {
|
|
548
600
|
ftsResults = db.prepare(
|
|
549
|
-
|
|
550
|
-
).all(q).filter(passesFilter);
|
|
601
|
+
`SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ?${ftsWhere} ORDER BY rank LIMIT 100`,
|
|
602
|
+
).all(q, ...sqlParams).filter(passesFilter);
|
|
551
603
|
} catch { /* FTS syntax error, fall through */ }
|
|
552
604
|
if (ftsResults.length === 0) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
605
|
+
try {
|
|
606
|
+
ftsResults = db.prepare(
|
|
607
|
+
`SELECT * FROM chunks WHERE (content LIKE ? OR summary LIKE ?)${likeWhere} ORDER BY created_at DESC LIMIT 100`,
|
|
608
|
+
).all(`%${q}%`, `%${q}%`, ...sqlParams).filter(passesFilter);
|
|
609
|
+
} catch (err) {
|
|
610
|
+
this.log.warn(`LIKE search failed: ${err}`);
|
|
611
|
+
}
|
|
556
612
|
}
|
|
557
613
|
|
|
558
614
|
const SEMANTIC_THRESHOLD = 0.64;
|
|
615
|
+
const VECTOR_TIMEOUT_MS = 8000;
|
|
559
616
|
let vectorResults: any[] = [];
|
|
560
617
|
let scoreMap = new Map<string, number>();
|
|
561
618
|
try {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
619
|
+
const vecPromise = (async () => {
|
|
620
|
+
const queryVec = await this.embedder.embedQuery(q);
|
|
621
|
+
return vectorSearch(this.store, queryVec, 40);
|
|
622
|
+
})();
|
|
623
|
+
const hits = await Promise.race([
|
|
624
|
+
vecPromise,
|
|
625
|
+
new Promise<null>((resolve) => setTimeout(() => resolve(null), VECTOR_TIMEOUT_MS)),
|
|
626
|
+
]);
|
|
627
|
+
if (hits) {
|
|
628
|
+
scoreMap = new Map(hits.map(h => [h.chunkId, h.score]));
|
|
629
|
+
const hitIds = new Set(hits.filter(h => h.score >= SEMANTIC_THRESHOLD).map(h => h.chunkId));
|
|
630
|
+
if (hitIds.size > 0) {
|
|
631
|
+
const placeholders = [...hitIds].map(() => "?").join(",");
|
|
632
|
+
const rows = db.prepare(`SELECT * FROM chunks WHERE id IN (${placeholders})${likeWhere}`).all(...hitIds, ...sqlParams).filter(passesFilter);
|
|
633
|
+
rows.forEach((r: any) => { r._vscore = scoreMap.get(r.id) ?? 0; });
|
|
634
|
+
rows.sort((a: any, b: any) => (b._vscore ?? 0) - (a._vscore ?? 0));
|
|
635
|
+
vectorResults = rows;
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
this.log.warn("Vector search timed out, returning FTS results only");
|
|
572
639
|
}
|
|
573
640
|
} catch (err) {
|
|
574
641
|
this.log.warn(`Vector search failed (falling back to FTS only): ${err}`);
|
|
@@ -1041,8 +1108,8 @@ export class ViewerServer {
|
|
|
1041
1108
|
return;
|
|
1042
1109
|
}
|
|
1043
1110
|
if (type === "embedding") {
|
|
1044
|
-
await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1045
|
-
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}
|
|
1111
|
+
const dims = await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1112
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}`, dimensions: dims });
|
|
1046
1113
|
} else {
|
|
1047
1114
|
await this.testChatModel(provider, model, endpoint, apiKey);
|
|
1048
1115
|
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
@@ -1055,6 +1122,10 @@ export class ViewerServer {
|
|
|
1055
1122
|
});
|
|
1056
1123
|
}
|
|
1057
1124
|
|
|
1125
|
+
private serveModelHealth(res: http.ServerResponse): void {
|
|
1126
|
+
this.jsonResponse(res, { models: modelHealth.getAll() });
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1058
1129
|
private serveFallbackModel(res: http.ServerResponse): void {
|
|
1059
1130
|
try {
|
|
1060
1131
|
const cfgPath = this.getOpenClawConfigPath();
|
|
@@ -1134,9 +1205,9 @@ export class ViewerServer {
|
|
|
1134
1205
|
}
|
|
1135
1206
|
}
|
|
1136
1207
|
|
|
1137
|
-
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<
|
|
1208
|
+
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<number | undefined> {
|
|
1138
1209
|
if (provider === "local") {
|
|
1139
|
-
return;
|
|
1210
|
+
return 384;
|
|
1140
1211
|
}
|
|
1141
1212
|
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1142
1213
|
const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
|
|
@@ -1149,39 +1220,59 @@ export class ViewerServer {
|
|
|
1149
1220
|
const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
|
|
1150
1221
|
method: "POST",
|
|
1151
1222
|
headers,
|
|
1152
|
-
body: JSON.stringify({ texts: ["test"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1223
|
+
body: JSON.stringify({ texts: ["test embedding vector"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1153
1224
|
signal: AbortSignal.timeout(15_000),
|
|
1154
1225
|
});
|
|
1155
1226
|
if (!resp.ok) {
|
|
1156
1227
|
const txt = await resp.text();
|
|
1157
1228
|
throw new Error(`Cohere embed ${resp.status}: ${txt}`);
|
|
1158
1229
|
}
|
|
1159
|
-
|
|
1230
|
+
const json = await resp.json() as any;
|
|
1231
|
+
const vecs = json?.embeddings?.float;
|
|
1232
|
+
if (!Array.isArray(vecs) || vecs.length === 0 || !Array.isArray(vecs[0]) || vecs[0].length === 0) {
|
|
1233
|
+
throw new Error("Cohere returned empty embedding vector");
|
|
1234
|
+
}
|
|
1235
|
+
return vecs[0].length;
|
|
1160
1236
|
}
|
|
1161
1237
|
if (provider === "gemini") {
|
|
1162
1238
|
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
|
|
1163
1239
|
const resp = await fetch(url, {
|
|
1164
1240
|
method: "POST",
|
|
1165
1241
|
headers: { "Content-Type": "application/json" },
|
|
1166
|
-
body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
|
|
1242
|
+
body: JSON.stringify({ content: { parts: [{ text: "test embedding vector" }] } }),
|
|
1167
1243
|
signal: AbortSignal.timeout(15_000),
|
|
1168
1244
|
});
|
|
1169
1245
|
if (!resp.ok) {
|
|
1170
1246
|
const txt = await resp.text();
|
|
1171
1247
|
throw new Error(`Gemini embed ${resp.status}: ${txt}`);
|
|
1172
1248
|
}
|
|
1173
|
-
|
|
1249
|
+
const json = await resp.json() as any;
|
|
1250
|
+
const vec = json?.embedding?.values;
|
|
1251
|
+
if (!Array.isArray(vec) || vec.length === 0) {
|
|
1252
|
+
throw new Error("Gemini returned empty embedding vector");
|
|
1253
|
+
}
|
|
1254
|
+
return vec.length;
|
|
1174
1255
|
}
|
|
1175
1256
|
const resp = await fetch(embUrl, {
|
|
1176
1257
|
method: "POST",
|
|
1177
1258
|
headers,
|
|
1178
|
-
body: JSON.stringify({ input: ["test"], model: model || "text-embedding-3-small" }),
|
|
1259
|
+
body: JSON.stringify({ input: ["test embedding vector"], model: model || "text-embedding-3-small" }),
|
|
1179
1260
|
signal: AbortSignal.timeout(15_000),
|
|
1180
1261
|
});
|
|
1181
1262
|
if (!resp.ok) {
|
|
1182
1263
|
const txt = await resp.text();
|
|
1183
1264
|
throw new Error(`${resp.status}: ${txt}`);
|
|
1184
1265
|
}
|
|
1266
|
+
const json = await resp.json() as any;
|
|
1267
|
+
const data = json?.data;
|
|
1268
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
1269
|
+
throw new Error("API returned no embedding data");
|
|
1270
|
+
}
|
|
1271
|
+
const vec = data[0]?.embedding;
|
|
1272
|
+
if (!Array.isArray(vec) || vec.length === 0) {
|
|
1273
|
+
throw new Error(`API returned empty embedding vector (got ${JSON.stringify(vec)?.slice(0, 100)})`);
|
|
1274
|
+
}
|
|
1275
|
+
return vec.length;
|
|
1185
1276
|
}
|
|
1186
1277
|
|
|
1187
1278
|
private async testChatModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
|
|
@@ -1256,6 +1347,28 @@ export class ViewerServer {
|
|
|
1256
1347
|
return path.join(home, ".openclaw");
|
|
1257
1348
|
}
|
|
1258
1349
|
|
|
1350
|
+
private handleCleanupPolluted(res: http.ServerResponse): void {
|
|
1351
|
+
try {
|
|
1352
|
+
const polluted = this.store.findPollutedUserChunks();
|
|
1353
|
+
let deleted = 0;
|
|
1354
|
+
for (const { id, reason } of polluted) {
|
|
1355
|
+
if (this.store.deleteChunk(id)) {
|
|
1356
|
+
deleted++;
|
|
1357
|
+
this.log.info(`Cleaned polluted chunk ${id}: ${reason}`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const fixed = this.store.fixMixedUserChunks();
|
|
1361
|
+
this.log.info(`Cleanup: removed ${deleted} polluted, fixed ${fixed} mixed chunks`);
|
|
1362
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1363
|
+
res.end(JSON.stringify({ deleted, fixed, total: polluted.length }));
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1366
|
+
this.log.error(`handleCleanupPolluted error: ${msg}`);
|
|
1367
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1368
|
+
res.end(JSON.stringify({ error: msg }));
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1259
1372
|
private handleMigrateScan(res: http.ServerResponse): void {
|
|
1260
1373
|
try {
|
|
1261
1374
|
const ocHome = this.getOpenClawHome();
|
|
@@ -1499,7 +1612,6 @@ export class ViewerServer {
|
|
|
1499
1612
|
|
|
1500
1613
|
const cfgPath = this.getOpenClawConfigPath();
|
|
1501
1614
|
let summarizerCfg: any;
|
|
1502
|
-
let strongCfg: any;
|
|
1503
1615
|
try {
|
|
1504
1616
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
1505
1617
|
const pluginCfg = raw?.plugins?.entries?.["memos-local-openclaw-plugin"]?.config ??
|
|
@@ -1507,10 +1619,9 @@ export class ViewerServer {
|
|
|
1507
1619
|
raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
|
|
1508
1620
|
raw?.plugins?.entries?.["memos-lite"]?.config ?? {};
|
|
1509
1621
|
summarizerCfg = pluginCfg.summarizer;
|
|
1510
|
-
strongCfg = pluginCfg.skillEvolution?.summarizer;
|
|
1511
1622
|
} catch { /* no config */ }
|
|
1512
1623
|
|
|
1513
|
-
const summarizer = new Summarizer(summarizerCfg, this.log
|
|
1624
|
+
const summarizer = new Summarizer(summarizerCfg, this.log);
|
|
1514
1625
|
|
|
1515
1626
|
// Phase 1: Import SQLite memory chunks
|
|
1516
1627
|
if (importSqlite) {
|
|
@@ -1636,8 +1747,8 @@ export class ViewerServer {
|
|
|
1636
1747
|
mergeCount: 0,
|
|
1637
1748
|
lastHitAt: null,
|
|
1638
1749
|
mergeHistory: "[]",
|
|
1639
|
-
createdAt: row.updated_at
|
|
1640
|
-
updatedAt: row.updated_at
|
|
1750
|
+
createdAt: normalizeTimestamp(row.updated_at),
|
|
1751
|
+
updatedAt: normalizeTimestamp(row.updated_at),
|
|
1641
1752
|
};
|
|
1642
1753
|
|
|
1643
1754
|
this.store.insertChunk(chunk);
|