@memtensor/memos-local-openclaw-plugin 1.0.2-beta.2 → 1.0.2-beta.4
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 +28 -13
- 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 +28 -13
- 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 +28 -13
- 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 +28 -13
- 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/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 +113 -0
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +3 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +104 -21
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +38 -85
- package/package.json +1 -1
- package/scripts/postinstall.cjs +16 -3
- package/src/capture/index.ts +56 -1
- package/src/embedding/index.ts +13 -7
- package/src/ingest/providers/anthropic.ts +28 -13
- package/src/ingest/providers/bedrock.ts +28 -13
- package/src/ingest/providers/gemini.ts +28 -13
- package/src/ingest/providers/index.ts +112 -9
- package/src/ingest/providers/openai.ts +28 -13
- package/src/ingest/worker.ts +8 -15
- package/src/storage/sqlite.ts +49 -0
- package/src/viewer/html.ts +113 -0
- package/src/viewer/server.ts +101 -20
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;
|
|
@@ -93,11 +98,28 @@ export class ViewerServer {
|
|
|
93
98
|
this.server.listen(this.port, "127.0.0.1", () => {
|
|
94
99
|
const addr = this.server!.address();
|
|
95
100
|
const actualPort = typeof addr === "object" && addr ? addr.port : this.port;
|
|
101
|
+
this.autoCleanupPolluted();
|
|
96
102
|
resolve(`http://127.0.0.1:${actualPort}`);
|
|
97
103
|
});
|
|
98
104
|
});
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
private autoCleanupPolluted(): void {
|
|
108
|
+
try {
|
|
109
|
+
const polluted = this.store.findPollutedUserChunks();
|
|
110
|
+
let deleted = 0;
|
|
111
|
+
for (const { id } of polluted) {
|
|
112
|
+
if (this.store.deleteChunk(id)) deleted++;
|
|
113
|
+
}
|
|
114
|
+
const fixed = this.store.fixMixedUserChunks();
|
|
115
|
+
if (deleted > 0 || fixed > 0) {
|
|
116
|
+
this.log.info(`Auto-cleanup: removed ${deleted} polluted chunks, fixed ${fixed} mixed user+assistant chunks`);
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
this.log.warn(`Auto-cleanup failed: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
101
123
|
stop(): void {
|
|
102
124
|
this.server?.close();
|
|
103
125
|
this.server = null;
|
|
@@ -216,9 +238,11 @@ export class ViewerServer {
|
|
|
216
238
|
else if (p === "/api/config" && req.method === "GET") this.serveConfig(res);
|
|
217
239
|
else if (p === "/api/config" && req.method === "PUT") this.handleSaveConfig(req, res);
|
|
218
240
|
else if (p === "/api/test-model" && req.method === "POST") this.handleTestModel(req, res);
|
|
241
|
+
else if (p === "/api/model-health" && req.method === "GET") this.serveModelHealth(res);
|
|
219
242
|
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
220
243
|
else if (p === "/api/update-check" && req.method === "GET") this.handleUpdateCheck(res);
|
|
221
244
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
245
|
+
else if (p === "/api/cleanup-polluted" && req.method === "POST") this.handleCleanupPolluted(res);
|
|
222
246
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
223
247
|
else if (p === "/api/migrate/start" && req.method === "POST") this.handleMigrateStart(req, res);
|
|
224
248
|
else if (p === "/api/migrate/status" && req.method === "GET") this.handleMigrateStatus(res);
|
|
@@ -485,7 +509,15 @@ export class ViewerServer {
|
|
|
485
509
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
|
|
486
510
|
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
|
|
487
511
|
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;
|
|
512
|
+
const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE dedup_status = 'active'").get() as any;
|
|
513
|
+
const MIN_VALID_TS = 1704067200000; // 2024-01-01
|
|
514
|
+
if (timeRange.earliest != null && timeRange.earliest < MIN_VALID_TS) {
|
|
515
|
+
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;
|
|
516
|
+
timeRange.earliest = timeRange.earliest?.v ?? null;
|
|
517
|
+
}
|
|
518
|
+
if (timeRange.latest != null && timeRange.latest < MIN_VALID_TS) {
|
|
519
|
+
timeRange.latest = null;
|
|
520
|
+
}
|
|
489
521
|
let embCount = 0;
|
|
490
522
|
try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
|
|
491
523
|
const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
|
|
@@ -970,11 +1002,13 @@ export class ViewerServer {
|
|
|
970
1002
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
971
1003
|
const entries = raw?.plugins?.entries ?? {};
|
|
972
1004
|
const pluginEntry = entries["memos-local-openclaw-plugin"]?.config
|
|
1005
|
+
?? entries["memos-local"]?.config
|
|
973
1006
|
?? entries["memos-lite-openclaw-plugin"]?.config
|
|
974
1007
|
?? entries["memos-lite"]?.config
|
|
975
1008
|
?? {};
|
|
976
1009
|
const result: Record<string, unknown> = { ...pluginEntry };
|
|
977
1010
|
const topEntry = entries["memos-local-openclaw-plugin"]
|
|
1011
|
+
?? entries["memos-local"]
|
|
978
1012
|
?? entries["memos-lite-openclaw-plugin"]
|
|
979
1013
|
?? entries["memos-lite"]
|
|
980
1014
|
?? {};
|
|
@@ -1003,6 +1037,7 @@ export class ViewerServer {
|
|
|
1003
1037
|
if (!plugins.entries) plugins.entries = {};
|
|
1004
1038
|
const entries = plugins.entries as Record<string, unknown>;
|
|
1005
1039
|
const entryKey = entries["memos-local-openclaw-plugin"] ? "memos-local-openclaw-plugin"
|
|
1040
|
+
: entries["memos-local"] ? "memos-local"
|
|
1006
1041
|
: entries["memos-lite-openclaw-plugin"] ? "memos-lite-openclaw-plugin"
|
|
1007
1042
|
: entries["memos-lite"] ? "memos-lite"
|
|
1008
1043
|
: "memos-local-openclaw-plugin";
|
|
@@ -1038,8 +1073,8 @@ export class ViewerServer {
|
|
|
1038
1073
|
return;
|
|
1039
1074
|
}
|
|
1040
1075
|
if (type === "embedding") {
|
|
1041
|
-
await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1042
|
-
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}
|
|
1076
|
+
const dims = await this.testEmbeddingModel(provider, model, endpoint, apiKey);
|
|
1077
|
+
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}`, dimensions: dims });
|
|
1043
1078
|
} else {
|
|
1044
1079
|
await this.testChatModel(provider, model, endpoint, apiKey);
|
|
1045
1080
|
this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
|
|
@@ -1052,6 +1087,10 @@ export class ViewerServer {
|
|
|
1052
1087
|
});
|
|
1053
1088
|
}
|
|
1054
1089
|
|
|
1090
|
+
private serveModelHealth(res: http.ServerResponse): void {
|
|
1091
|
+
this.jsonResponse(res, { models: modelHealth.getAll() });
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1055
1094
|
private serveFallbackModel(res: http.ServerResponse): void {
|
|
1056
1095
|
try {
|
|
1057
1096
|
const cfgPath = this.getOpenClawConfigPath();
|
|
@@ -1131,9 +1170,9 @@ export class ViewerServer {
|
|
|
1131
1170
|
}
|
|
1132
1171
|
}
|
|
1133
1172
|
|
|
1134
|
-
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<
|
|
1173
|
+
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<number | undefined> {
|
|
1135
1174
|
if (provider === "local") {
|
|
1136
|
-
return;
|
|
1175
|
+
return 384;
|
|
1137
1176
|
}
|
|
1138
1177
|
const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1139
1178
|
const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
|
|
@@ -1146,39 +1185,59 @@ export class ViewerServer {
|
|
|
1146
1185
|
const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
|
|
1147
1186
|
method: "POST",
|
|
1148
1187
|
headers,
|
|
1149
|
-
body: JSON.stringify({ texts: ["test"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1188
|
+
body: JSON.stringify({ texts: ["test embedding vector"], model: model || "embed-english-v3.0", input_type: "search_query", embedding_types: ["float"] }),
|
|
1150
1189
|
signal: AbortSignal.timeout(15_000),
|
|
1151
1190
|
});
|
|
1152
1191
|
if (!resp.ok) {
|
|
1153
1192
|
const txt = await resp.text();
|
|
1154
1193
|
throw new Error(`Cohere embed ${resp.status}: ${txt}`);
|
|
1155
1194
|
}
|
|
1156
|
-
|
|
1195
|
+
const json = await resp.json() as any;
|
|
1196
|
+
const vecs = json?.embeddings?.float;
|
|
1197
|
+
if (!Array.isArray(vecs) || vecs.length === 0 || !Array.isArray(vecs[0]) || vecs[0].length === 0) {
|
|
1198
|
+
throw new Error("Cohere returned empty embedding vector");
|
|
1199
|
+
}
|
|
1200
|
+
return vecs[0].length;
|
|
1157
1201
|
}
|
|
1158
1202
|
if (provider === "gemini") {
|
|
1159
1203
|
const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
|
|
1160
1204
|
const resp = await fetch(url, {
|
|
1161
1205
|
method: "POST",
|
|
1162
1206
|
headers: { "Content-Type": "application/json" },
|
|
1163
|
-
body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
|
|
1207
|
+
body: JSON.stringify({ content: { parts: [{ text: "test embedding vector" }] } }),
|
|
1164
1208
|
signal: AbortSignal.timeout(15_000),
|
|
1165
1209
|
});
|
|
1166
1210
|
if (!resp.ok) {
|
|
1167
1211
|
const txt = await resp.text();
|
|
1168
1212
|
throw new Error(`Gemini embed ${resp.status}: ${txt}`);
|
|
1169
1213
|
}
|
|
1170
|
-
|
|
1214
|
+
const json = await resp.json() as any;
|
|
1215
|
+
const vec = json?.embedding?.values;
|
|
1216
|
+
if (!Array.isArray(vec) || vec.length === 0) {
|
|
1217
|
+
throw new Error("Gemini returned empty embedding vector");
|
|
1218
|
+
}
|
|
1219
|
+
return vec.length;
|
|
1171
1220
|
}
|
|
1172
1221
|
const resp = await fetch(embUrl, {
|
|
1173
1222
|
method: "POST",
|
|
1174
1223
|
headers,
|
|
1175
|
-
body: JSON.stringify({ input: ["test"], model: model || "text-embedding-3-small" }),
|
|
1224
|
+
body: JSON.stringify({ input: ["test embedding vector"], model: model || "text-embedding-3-small" }),
|
|
1176
1225
|
signal: AbortSignal.timeout(15_000),
|
|
1177
1226
|
});
|
|
1178
1227
|
if (!resp.ok) {
|
|
1179
1228
|
const txt = await resp.text();
|
|
1180
1229
|
throw new Error(`${resp.status}: ${txt}`);
|
|
1181
1230
|
}
|
|
1231
|
+
const json = await resp.json() as any;
|
|
1232
|
+
const data = json?.data;
|
|
1233
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
1234
|
+
throw new Error("API returned no embedding data");
|
|
1235
|
+
}
|
|
1236
|
+
const vec = data[0]?.embedding;
|
|
1237
|
+
if (!Array.isArray(vec) || vec.length === 0) {
|
|
1238
|
+
throw new Error(`API returned empty embedding vector (got ${JSON.stringify(vec)?.slice(0, 100)})`);
|
|
1239
|
+
}
|
|
1240
|
+
return vec.length;
|
|
1182
1241
|
}
|
|
1183
1242
|
|
|
1184
1243
|
private async testChatModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
|
|
@@ -1253,6 +1312,28 @@ export class ViewerServer {
|
|
|
1253
1312
|
return path.join(home, ".openclaw");
|
|
1254
1313
|
}
|
|
1255
1314
|
|
|
1315
|
+
private handleCleanupPolluted(res: http.ServerResponse): void {
|
|
1316
|
+
try {
|
|
1317
|
+
const polluted = this.store.findPollutedUserChunks();
|
|
1318
|
+
let deleted = 0;
|
|
1319
|
+
for (const { id, reason } of polluted) {
|
|
1320
|
+
if (this.store.deleteChunk(id)) {
|
|
1321
|
+
deleted++;
|
|
1322
|
+
this.log.info(`Cleaned polluted chunk ${id}: ${reason}`);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
const fixed = this.store.fixMixedUserChunks();
|
|
1326
|
+
this.log.info(`Cleanup: removed ${deleted} polluted, fixed ${fixed} mixed chunks`);
|
|
1327
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1328
|
+
res.end(JSON.stringify({ deleted, fixed, total: polluted.length }));
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1331
|
+
this.log.error(`handleCleanupPolluted error: ${msg}`);
|
|
1332
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1333
|
+
res.end(JSON.stringify({ error: msg }));
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1256
1337
|
private handleMigrateScan(res: http.ServerResponse): void {
|
|
1257
1338
|
try {
|
|
1258
1339
|
const ocHome = this.getOpenClawHome();
|
|
@@ -1311,8 +1392,9 @@ export class ViewerServer {
|
|
|
1311
1392
|
try {
|
|
1312
1393
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
1313
1394
|
const pluginCfg = raw?.plugins?.entries?.["memos-local-openclaw-plugin"]?.config ??
|
|
1314
|
-
raw?.plugins?.entries?.["memos-
|
|
1315
|
-
raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
|
|
1395
|
+
raw?.plugins?.entries?.["memos-local"]?.config ??
|
|
1396
|
+
raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
|
|
1397
|
+
raw?.plugins?.entries?.["memos-lite"]?.config ?? {};
|
|
1316
1398
|
const emb = pluginCfg.embedding;
|
|
1317
1399
|
hasEmbedding = !!(emb && emb.provider);
|
|
1318
1400
|
const sum = pluginCfg.summarizer;
|
|
@@ -1495,17 +1577,16 @@ export class ViewerServer {
|
|
|
1495
1577
|
|
|
1496
1578
|
const cfgPath = this.getOpenClawConfigPath();
|
|
1497
1579
|
let summarizerCfg: any;
|
|
1498
|
-
let strongCfg: any;
|
|
1499
1580
|
try {
|
|
1500
1581
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
1501
1582
|
const pluginCfg = raw?.plugins?.entries?.["memos-local-openclaw-plugin"]?.config ??
|
|
1502
|
-
raw?.plugins?.entries?.["memos-
|
|
1503
|
-
raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
|
|
1583
|
+
raw?.plugins?.entries?.["memos-local"]?.config ??
|
|
1584
|
+
raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
|
|
1585
|
+
raw?.plugins?.entries?.["memos-lite"]?.config ?? {};
|
|
1504
1586
|
summarizerCfg = pluginCfg.summarizer;
|
|
1505
|
-
strongCfg = pluginCfg.skillEvolution?.summarizer;
|
|
1506
1587
|
} catch { /* no config */ }
|
|
1507
1588
|
|
|
1508
|
-
const summarizer = new Summarizer(summarizerCfg, this.log
|
|
1589
|
+
const summarizer = new Summarizer(summarizerCfg, this.log);
|
|
1509
1590
|
|
|
1510
1591
|
// Phase 1: Import SQLite memory chunks
|
|
1511
1592
|
if (importSqlite) {
|
|
@@ -1631,8 +1712,8 @@ export class ViewerServer {
|
|
|
1631
1712
|
mergeCount: 0,
|
|
1632
1713
|
lastHitAt: null,
|
|
1633
1714
|
mergeHistory: "[]",
|
|
1634
|
-
createdAt: row.updated_at
|
|
1635
|
-
updatedAt: row.updated_at
|
|
1715
|
+
createdAt: normalizeTimestamp(row.updated_at),
|
|
1716
|
+
updatedAt: normalizeTimestamp(row.updated_at),
|
|
1636
1717
|
};
|
|
1637
1718
|
|
|
1638
1719
|
this.store.insertChunk(chunk);
|