@memtensor/memos-local-openclaw-plugin 1.0.2-beta.3 → 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 (50) 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 +92 -14
  37. package/dist/viewer/server.js.map +1 -1
  38. package/index.ts +38 -85
  39. package/package.json +1 -1
  40. package/src/capture/index.ts +56 -1
  41. package/src/embedding/index.ts +13 -7
  42. package/src/ingest/providers/anthropic.ts +28 -13
  43. package/src/ingest/providers/bedrock.ts +28 -13
  44. package/src/ingest/providers/gemini.ts +28 -13
  45. package/src/ingest/providers/index.ts +112 -9
  46. package/src/ingest/providers/openai.ts +28 -13
  47. package/src/ingest/worker.ts +8 -15
  48. package/src/storage/sqlite.ts +49 -0
  49. package/src/viewer/html.ts +113 -0
  50. package/src/viewer/server.ts +92 -16
@@ -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[];
@@ -1041,8 +1073,8 @@ export class ViewerServer {
1041
1073
  return;
1042
1074
  }
1043
1075
  if (type === "embedding") {
1044
- await this.testEmbeddingModel(provider, model, endpoint, apiKey);
1045
- 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 });
1046
1078
  } else {
1047
1079
  await this.testChatModel(provider, model, endpoint, apiKey);
1048
1080
  this.jsonResponse(res, { ok: true, detail: `${provider}/${model}` });
@@ -1055,6 +1087,10 @@ export class ViewerServer {
1055
1087
  });
1056
1088
  }
1057
1089
 
1090
+ private serveModelHealth(res: http.ServerResponse): void {
1091
+ this.jsonResponse(res, { models: modelHealth.getAll() });
1092
+ }
1093
+
1058
1094
  private serveFallbackModel(res: http.ServerResponse): void {
1059
1095
  try {
1060
1096
  const cfgPath = this.getOpenClawConfigPath();
@@ -1134,9 +1170,9 @@ export class ViewerServer {
1134
1170
  }
1135
1171
  }
1136
1172
 
1137
- 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> {
1138
1174
  if (provider === "local") {
1139
- return;
1175
+ return 384;
1140
1176
  }
1141
1177
  const baseUrl = (endpoint || "https://api.openai.com/v1").replace(/\/+$/, "");
1142
1178
  const embUrl = baseUrl.endsWith("/embeddings") ? baseUrl : `${baseUrl}/embeddings`;
@@ -1149,39 +1185,59 @@ export class ViewerServer {
1149
1185
  const resp = await fetch(baseUrl.replace(/\/v\d+.*/, "/v2/embed"), {
1150
1186
  method: "POST",
1151
1187
  headers,
1152
- 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"] }),
1153
1189
  signal: AbortSignal.timeout(15_000),
1154
1190
  });
1155
1191
  if (!resp.ok) {
1156
1192
  const txt = await resp.text();
1157
1193
  throw new Error(`Cohere embed ${resp.status}: ${txt}`);
1158
1194
  }
1159
- 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;
1160
1201
  }
1161
1202
  if (provider === "gemini") {
1162
1203
  const url = `https://generativelanguage.googleapis.com/v1/models/${model || "text-embedding-004"}:embedContent?key=${apiKey}`;
1163
1204
  const resp = await fetch(url, {
1164
1205
  method: "POST",
1165
1206
  headers: { "Content-Type": "application/json" },
1166
- body: JSON.stringify({ content: { parts: [{ text: "test" }] } }),
1207
+ body: JSON.stringify({ content: { parts: [{ text: "test embedding vector" }] } }),
1167
1208
  signal: AbortSignal.timeout(15_000),
1168
1209
  });
1169
1210
  if (!resp.ok) {
1170
1211
  const txt = await resp.text();
1171
1212
  throw new Error(`Gemini embed ${resp.status}: ${txt}`);
1172
1213
  }
1173
- 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;
1174
1220
  }
1175
1221
  const resp = await fetch(embUrl, {
1176
1222
  method: "POST",
1177
1223
  headers,
1178
- 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" }),
1179
1225
  signal: AbortSignal.timeout(15_000),
1180
1226
  });
1181
1227
  if (!resp.ok) {
1182
1228
  const txt = await resp.text();
1183
1229
  throw new Error(`${resp.status}: ${txt}`);
1184
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;
1185
1241
  }
1186
1242
 
1187
1243
  private async testChatModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
@@ -1256,6 +1312,28 @@ export class ViewerServer {
1256
1312
  return path.join(home, ".openclaw");
1257
1313
  }
1258
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
+
1259
1337
  private handleMigrateScan(res: http.ServerResponse): void {
1260
1338
  try {
1261
1339
  const ocHome = this.getOpenClawHome();
@@ -1499,7 +1577,6 @@ export class ViewerServer {
1499
1577
 
1500
1578
  const cfgPath = this.getOpenClawConfigPath();
1501
1579
  let summarizerCfg: any;
1502
- let strongCfg: any;
1503
1580
  try {
1504
1581
  const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
1505
1582
  const pluginCfg = raw?.plugins?.entries?.["memos-local-openclaw-plugin"]?.config ??
@@ -1507,10 +1584,9 @@ export class ViewerServer {
1507
1584
  raw?.plugins?.entries?.["memos-lite-openclaw-plugin"]?.config ??
1508
1585
  raw?.plugins?.entries?.["memos-lite"]?.config ?? {};
1509
1586
  summarizerCfg = pluginCfg.summarizer;
1510
- strongCfg = pluginCfg.skillEvolution?.summarizer;
1511
1587
  } catch { /* no config */ }
1512
1588
 
1513
- const summarizer = new Summarizer(summarizerCfg, this.log, strongCfg);
1589
+ const summarizer = new Summarizer(summarizerCfg, this.log);
1514
1590
 
1515
1591
  // Phase 1: Import SQLite memory chunks
1516
1592
  if (importSqlite) {
@@ -1636,8 +1712,8 @@ export class ViewerServer {
1636
1712
  mergeCount: 0,
1637
1713
  lastHitAt: null,
1638
1714
  mergeHistory: "[]",
1639
- createdAt: row.updated_at * 1000,
1640
- updatedAt: row.updated_at * 1000,
1715
+ createdAt: normalizeTimestamp(row.updated_at),
1716
+ updatedAt: normalizeTimestamp(row.updated_at),
1641
1717
  };
1642
1718
 
1643
1719
  this.store.insertChunk(chunk);