@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.
Files changed (51) hide show
  1. package/dist/capture/index.d.ts.map +1 -1
  2. package/dist/capture/index.js +41 -1
  3. package/dist/capture/index.js.map +1 -1
  4. package/dist/embedding/index.d.ts.map +1 -1
  5. package/dist/embedding/index.js +20 -7
  6. package/dist/embedding/index.js.map +1 -1
  7. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  8. package/dist/ingest/providers/anthropic.js +28 -13
  9. package/dist/ingest/providers/anthropic.js.map +1 -1
  10. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  11. package/dist/ingest/providers/bedrock.js +28 -13
  12. package/dist/ingest/providers/bedrock.js.map +1 -1
  13. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  14. package/dist/ingest/providers/gemini.js +28 -13
  15. package/dist/ingest/providers/gemini.js.map +1 -1
  16. package/dist/ingest/providers/index.d.ts +19 -0
  17. package/dist/ingest/providers/index.d.ts.map +1 -1
  18. package/dist/ingest/providers/index.js +98 -10
  19. package/dist/ingest/providers/index.js.map +1 -1
  20. package/dist/ingest/providers/openai.d.ts.map +1 -1
  21. package/dist/ingest/providers/openai.js +28 -13
  22. package/dist/ingest/providers/openai.js.map +1 -1
  23. package/dist/ingest/worker.d.ts.map +1 -1
  24. package/dist/ingest/worker.js +8 -14
  25. package/dist/ingest/worker.js.map +1 -1
  26. package/dist/storage/sqlite.d.ts +14 -0
  27. package/dist/storage/sqlite.d.ts.map +1 -1
  28. package/dist/storage/sqlite.js +42 -0
  29. package/dist/storage/sqlite.js.map +1 -1
  30. package/dist/viewer/html.d.ts +1 -1
  31. package/dist/viewer/html.d.ts.map +1 -1
  32. package/dist/viewer/html.js +113 -0
  33. package/dist/viewer/html.js.map +1 -1
  34. package/dist/viewer/server.d.ts +3 -0
  35. package/dist/viewer/server.d.ts.map +1 -1
  36. package/dist/viewer/server.js +104 -21
  37. package/dist/viewer/server.js.map +1 -1
  38. package/index.ts +38 -85
  39. package/package.json +1 -1
  40. package/scripts/postinstall.cjs +16 -3
  41. package/src/capture/index.ts +56 -1
  42. package/src/embedding/index.ts +13 -7
  43. package/src/ingest/providers/anthropic.ts +28 -13
  44. package/src/ingest/providers/bedrock.ts +28 -13
  45. package/src/ingest/providers/gemini.ts +28 -13
  46. package/src/ingest/providers/index.ts +112 -9
  47. package/src/ingest/providers/openai.ts +28 -13
  48. package/src/ingest/worker.ts +8 -15
  49. package/src/storage/sqlite.ts +49 -0
  50. package/src/viewer/html.ts +113 -0
  51. package/src/viewer/server.ts +101 -20
@@ -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<void> {
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
- return;
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
- return;
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-lite"]?.config ??
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-lite"]?.config ??
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, strongCfg);
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 * 1000,
1635
- updatedAt: row.updated_at * 1000,
1715
+ createdAt: normalizeTimestamp(row.updated_at),
1716
+ updatedAt: normalizeTimestamp(row.updated_at),
1636
1717
  };
1637
1718
 
1638
1719
  this.store.insertChunk(chunk);