@memtensor/memos-local-openclaw-plugin 1.0.2-beta.4 → 1.0.2-beta.6
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.js +52 -8
- package/dist/capture/index.js.map +1 -1
- package/dist/ingest/chunker.d.ts +3 -4
- package/dist/ingest/chunker.d.ts.map +1 -1
- package/dist/ingest/chunker.js +19 -24
- package/dist/ingest/chunker.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts +3 -1
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +90 -51
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts +3 -1
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +90 -51
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts +3 -1
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +88 -51
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +3 -1
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +70 -30
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +3 -1
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +91 -51
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +1 -0
- package/dist/ingest/task-processor.d.ts.map +1 -1
- package/dist/ingest/task-processor.js +33 -9
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +29 -13
- package/dist/ingest/worker.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +19 -14
- package/dist/recall/engine.js.map +1 -1
- package/dist/skill/bundled-memory-guide.d.ts +1 -5
- package/dist/skill/bundled-memory-guide.d.ts.map +1 -1
- package/dist/skill/bundled-memory-guide.js +38 -88
- package/dist/skill/bundled-memory-guide.js.map +1 -1
- package/dist/skill/evaluator.js +1 -1
- package/dist/storage/sqlite.d.ts +1 -2
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +90 -17
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/memory-get.d.ts.map +1 -1
- package/dist/tools/memory-get.js +1 -3
- package/dist/tools/memory-get.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-check.d.ts +21 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +111 -0
- package/dist/update-check.js.map +1 -0
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +608 -234
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +2 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +201 -90
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +206 -198
- package/openclaw.plugin.json +3 -0
- package/package.json +6 -1
- package/scripts/postinstall.cjs +69 -2
- package/skill/memos-memory-guide/SKILL.md +73 -36
- package/src/capture/index.ts +52 -8
- package/src/ingest/chunker.ts +22 -30
- package/src/ingest/providers/anthropic.ts +100 -53
- package/src/ingest/providers/bedrock.ts +101 -53
- package/src/ingest/providers/gemini.ts +100 -53
- package/src/ingest/providers/index.ts +81 -35
- package/src/ingest/providers/openai.ts +101 -53
- package/src/ingest/task-processor.ts +29 -8
- package/src/ingest/worker.ts +31 -13
- package/src/recall/engine.ts +20 -13
- package/src/skill/bundled-memory-guide.ts +5 -87
- package/src/skill/evaluator.ts +1 -1
- package/src/storage/sqlite.ts +93 -21
- package/src/tools/memory-get.ts +1 -4
- package/src/types.ts +2 -9
- package/src/update-check.ts +96 -0
- package/src/viewer/html.ts +607 -233
- package/src/viewer/server.ts +152 -82
package/src/viewer/server.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
3
|
+
import { execSync, exec } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import readline from "node:readline";
|
|
@@ -48,6 +48,14 @@ export class ViewerServer {
|
|
|
48
48
|
private readonly ctx?: PluginContext;
|
|
49
49
|
|
|
50
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
|
+
})();
|
|
51
59
|
private resetToken: string;
|
|
52
60
|
private migrationRunning = false;
|
|
53
61
|
private migrationAbort = false;
|
|
@@ -67,8 +75,8 @@ export class ViewerServer {
|
|
|
67
75
|
|
|
68
76
|
private ppRunning = false;
|
|
69
77
|
private ppAbort = false;
|
|
70
|
-
private ppState: { running: boolean; done: boolean; stopped: boolean; processed: number; total: number; tasksCreated: number; skillsCreated: number; errors: number } =
|
|
71
|
-
{ running: false, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0 };
|
|
78
|
+
private ppState: { running: boolean; done: boolean; stopped: boolean; processed: number; total: number; tasksCreated: number; skillsCreated: number; errors: number; skippedSessions: number; totalSessions: number } =
|
|
79
|
+
{ running: false, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0, skippedSessions: 0, totalSessions: 0 };
|
|
72
80
|
private ppSSEClients: http.ServerResponse[] = [];
|
|
73
81
|
|
|
74
82
|
constructor(opts: ViewerServerOptions) {
|
|
@@ -227,7 +235,6 @@ export class ViewerServer {
|
|
|
227
235
|
else if (p.startsWith("/api/skill/") && req.method === "DELETE") this.handleSkillDelete(res, p);
|
|
228
236
|
else if (p.startsWith("/api/skill/") && req.method === "PUT") this.handleSkillUpdate(req, res, p);
|
|
229
237
|
else if (p.startsWith("/api/skill/") && req.method === "GET") this.serveSkillDetail(res, p);
|
|
230
|
-
else if (p === "/api/memory" && req.method === "POST") this.handleCreate(req, res);
|
|
231
238
|
else if (p.startsWith("/api/memory/") && req.method === "GET") this.serveMemoryDetail(res, p);
|
|
232
239
|
else if (p.startsWith("/api/memory/") && req.method === "PUT") this.handleUpdate(req, res, p);
|
|
233
240
|
else if (p.startsWith("/api/memory/") && req.method === "DELETE") this.handleDelete(res, p);
|
|
@@ -241,6 +248,7 @@ export class ViewerServer {
|
|
|
241
248
|
else if (p === "/api/model-health" && req.method === "GET") this.serveModelHealth(res);
|
|
242
249
|
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
243
250
|
else if (p === "/api/update-check" && req.method === "GET") this.handleUpdateCheck(res);
|
|
251
|
+
else if (p === "/api/update-install" && req.method === "POST") this.handleUpdateInstall(req, res);
|
|
244
252
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
245
253
|
else if (p === "/api/cleanup-polluted" && req.method === "POST") this.handleCleanupPolluted(res);
|
|
246
254
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
@@ -363,7 +371,7 @@ export class ViewerServer {
|
|
|
363
371
|
|
|
364
372
|
private serveViewer(res: http.ServerResponse): void {
|
|
365
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" });
|
|
366
|
-
res.end(viewerHTML);
|
|
374
|
+
res.end(viewerHTML(ViewerServer.PLUGIN_VERSION));
|
|
367
375
|
}
|
|
368
376
|
|
|
369
377
|
// ─── Data APIs ───
|
|
@@ -374,7 +382,6 @@ export class ViewerServer {
|
|
|
374
382
|
const offset = (page - 1) * limit;
|
|
375
383
|
const session = url.searchParams.get("session") ?? undefined;
|
|
376
384
|
const role = url.searchParams.get("role") ?? undefined;
|
|
377
|
-
const kind = url.searchParams.get("kind") ?? undefined;
|
|
378
385
|
const dateFrom = url.searchParams.get("dateFrom") ?? undefined;
|
|
379
386
|
const dateTo = url.searchParams.get("dateTo") ?? undefined;
|
|
380
387
|
const owner = url.searchParams.get("owner") ?? undefined;
|
|
@@ -385,7 +392,6 @@ export class ViewerServer {
|
|
|
385
392
|
const params: any[] = [];
|
|
386
393
|
if (session) { conditions.push("session_key = ?"); params.push(session); }
|
|
387
394
|
if (role) { conditions.push("role = ?"); params.push(role); }
|
|
388
|
-
if (kind) { conditions.push("kind = ?"); params.push(kind); }
|
|
389
395
|
if (owner) { conditions.push("owner = ?"); params.push(owner); }
|
|
390
396
|
if (dateFrom) { conditions.push("created_at >= ?"); params.push(new Date(dateFrom).getTime()); }
|
|
391
397
|
if (dateTo) { conditions.push("created_at <= ?"); params.push(new Date(dateTo).getTime()); }
|
|
@@ -393,9 +399,14 @@ export class ViewerServer {
|
|
|
393
399
|
const where = conditions.length > 0 ? " WHERE " + conditions.join(" AND ") : "";
|
|
394
400
|
const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks" + where).get(...params) as any;
|
|
395
401
|
const rawMemories = db.prepare("SELECT * FROM chunks" + where + ` ORDER BY created_at ${sortBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
402
|
+
const findMergeSources = db.prepare("SELECT id, summary, role FROM chunks WHERE dedup_target = ? AND (dedup_status = 'merged' OR dedup_status = 'duplicate')");
|
|
396
403
|
const memories = rawMemories.map((m: any) => {
|
|
397
404
|
if (m.role === "user" && m.content) {
|
|
398
|
-
|
|
405
|
+
m = { ...m, content: stripInboundMetadata(m.content) };
|
|
406
|
+
}
|
|
407
|
+
if (m.merge_count > 0) {
|
|
408
|
+
const sources = findMergeSources.all(m.id) as Array<{ id: string; summary: string; role: string }>;
|
|
409
|
+
m.merge_sources = sources;
|
|
399
410
|
}
|
|
400
411
|
return m;
|
|
401
412
|
});
|
|
@@ -433,7 +444,7 @@ export class ViewerServer {
|
|
|
433
444
|
id: t.id,
|
|
434
445
|
sessionKey: t.sessionKey,
|
|
435
446
|
title: t.title,
|
|
436
|
-
summary: t.summary
|
|
447
|
+
summary: t.summary ?? "",
|
|
437
448
|
status: t.status,
|
|
438
449
|
startedAt: t.startedAt,
|
|
439
450
|
endedAt: t.endedAt,
|
|
@@ -456,8 +467,7 @@ export class ViewerServer {
|
|
|
456
467
|
|
|
457
468
|
const chunks = this.store.getChunksByTask(taskId);
|
|
458
469
|
const chunkItems = chunks.map((c) => {
|
|
459
|
-
|
|
460
|
-
if (text.length > 500) text = text.slice(0, 497) + "...";
|
|
470
|
+
const text = c.role === "user" ? stripInboundMetadata(c.content) : c.content;
|
|
461
471
|
return { id: c.id, role: c.role, content: text, summary: c.summary, createdAt: c.createdAt };
|
|
462
472
|
});
|
|
463
473
|
|
|
@@ -494,7 +504,7 @@ export class ViewerServer {
|
|
|
494
504
|
const emptyStats = {
|
|
495
505
|
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
496
506
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
497
|
-
|
|
507
|
+
dedupBreakdown: {},
|
|
498
508
|
timeRange: { earliest: null, latest: null },
|
|
499
509
|
sessions: [],
|
|
500
510
|
};
|
|
@@ -508,7 +518,6 @@ export class ViewerServer {
|
|
|
508
518
|
const db = (this.store as any).db;
|
|
509
519
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
|
|
510
520
|
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
|
|
511
|
-
const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
|
|
512
521
|
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
522
|
const MIN_VALID_TS = 1704067200000; // 2024-01-01
|
|
514
523
|
if (timeRange.earliest != null && timeRange.earliest < MIN_VALID_TS) {
|
|
@@ -520,7 +529,6 @@ export class ViewerServer {
|
|
|
520
529
|
}
|
|
521
530
|
let embCount = 0;
|
|
522
531
|
try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
|
|
523
|
-
const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
|
|
524
532
|
const sessionList = db.prepare(
|
|
525
533
|
"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",
|
|
526
534
|
).all() as any[];
|
|
@@ -544,8 +552,6 @@ export class ViewerServer {
|
|
|
544
552
|
totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
|
|
545
553
|
totalSkills: skillCount,
|
|
546
554
|
embeddingProvider: this.embedder.provider,
|
|
547
|
-
roleBreakdown: Object.fromEntries(roles.map((r: any) => [r.role, r.count])),
|
|
548
|
-
kindBreakdown: Object.fromEntries(kinds.map((k: any) => [k.kind, k.count])),
|
|
549
555
|
dedupBreakdown,
|
|
550
556
|
timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
|
|
551
557
|
sessions: sessionList,
|
|
@@ -562,45 +568,70 @@ export class ViewerServer {
|
|
|
562
568
|
if (!q.trim()) { this.jsonResponse(res, { results: [], query: q }); return; }
|
|
563
569
|
|
|
564
570
|
const role = url.searchParams.get("role") ?? undefined;
|
|
565
|
-
const
|
|
571
|
+
const session = url.searchParams.get("session") ?? undefined;
|
|
572
|
+
const owner = url.searchParams.get("owner") ?? undefined;
|
|
566
573
|
const dateFrom = url.searchParams.get("dateFrom") ?? undefined;
|
|
567
574
|
const dateTo = url.searchParams.get("dateTo") ?? undefined;
|
|
568
575
|
|
|
569
576
|
const passesFilter = (r: any): boolean => {
|
|
570
577
|
if (role && r.role !== role) return false;
|
|
571
|
-
if (
|
|
578
|
+
if (session && r.session_key !== session) return false;
|
|
579
|
+
if (owner && r.owner !== owner) return false;
|
|
572
580
|
if (dateFrom && r.created_at < new Date(dateFrom).getTime()) return false;
|
|
573
581
|
if (dateTo && r.created_at > new Date(dateTo).getTime()) return false;
|
|
574
582
|
return true;
|
|
575
583
|
};
|
|
576
584
|
|
|
585
|
+
const ftsFilters: string[] = [];
|
|
586
|
+
const likeFilters: string[] = [];
|
|
587
|
+
const sqlParams: any[] = [];
|
|
588
|
+
if (session) { ftsFilters.push("c.session_key = ?"); likeFilters.push("session_key = ?"); sqlParams.push(session); }
|
|
589
|
+
if (owner) { ftsFilters.push("c.owner = ?"); likeFilters.push("owner = ?"); sqlParams.push(owner); }
|
|
590
|
+
const ftsWhere = ftsFilters.length > 0 ? " AND " + ftsFilters.join(" AND ") : "";
|
|
591
|
+
const likeWhere = likeFilters.length > 0 ? " AND " + likeFilters.join(" AND ") : "";
|
|
592
|
+
|
|
577
593
|
const db = (this.store as any).db;
|
|
578
594
|
let ftsResults: any[] = [];
|
|
579
595
|
try {
|
|
580
596
|
ftsResults = db.prepare(
|
|
581
|
-
|
|
582
|
-
).all(q).filter(passesFilter);
|
|
597
|
+
`SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ?${ftsWhere} ORDER BY rank LIMIT 100`,
|
|
598
|
+
).all(q, ...sqlParams).filter(passesFilter);
|
|
583
599
|
} catch { /* FTS syntax error, fall through */ }
|
|
584
600
|
if (ftsResults.length === 0) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
601
|
+
try {
|
|
602
|
+
ftsResults = db.prepare(
|
|
603
|
+
`SELECT * FROM chunks WHERE (content LIKE ? OR summary LIKE ?)${likeWhere} ORDER BY created_at DESC LIMIT 100`,
|
|
604
|
+
).all(`%${q}%`, `%${q}%`, ...sqlParams).filter(passesFilter);
|
|
605
|
+
} catch (err) {
|
|
606
|
+
this.log.warn(`LIKE search failed: ${err}`);
|
|
607
|
+
}
|
|
588
608
|
}
|
|
589
609
|
|
|
590
610
|
const SEMANTIC_THRESHOLD = 0.64;
|
|
611
|
+
const VECTOR_TIMEOUT_MS = 8000;
|
|
591
612
|
let vectorResults: any[] = [];
|
|
592
613
|
let scoreMap = new Map<string, number>();
|
|
593
614
|
try {
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
615
|
+
const vecPromise = (async () => {
|
|
616
|
+
const queryVec = await this.embedder.embedQuery(q);
|
|
617
|
+
return vectorSearch(this.store, queryVec, 40);
|
|
618
|
+
})();
|
|
619
|
+
const hits = await Promise.race([
|
|
620
|
+
vecPromise,
|
|
621
|
+
new Promise<null>((resolve) => setTimeout(() => resolve(null), VECTOR_TIMEOUT_MS)),
|
|
622
|
+
]);
|
|
623
|
+
if (hits) {
|
|
624
|
+
scoreMap = new Map(hits.map(h => [h.chunkId, h.score]));
|
|
625
|
+
const hitIds = new Set(hits.filter(h => h.score >= SEMANTIC_THRESHOLD).map(h => h.chunkId));
|
|
626
|
+
if (hitIds.size > 0) {
|
|
627
|
+
const placeholders = [...hitIds].map(() => "?").join(",");
|
|
628
|
+
const rows = db.prepare(`SELECT * FROM chunks WHERE id IN (${placeholders})${likeWhere}`).all(...hitIds, ...sqlParams).filter(passesFilter);
|
|
629
|
+
rows.forEach((r: any) => { r._vscore = scoreMap.get(r.id) ?? 0; });
|
|
630
|
+
rows.sort((a: any, b: any) => (b._vscore ?? 0) - (a._vscore ?? 0));
|
|
631
|
+
vectorResults = rows;
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
this.log.warn("Vector search timed out, returning FTS results only");
|
|
604
635
|
}
|
|
605
636
|
} catch (err) {
|
|
606
637
|
this.log.warn(`Vector search failed (falling back to FTS only): ${err}`);
|
|
@@ -885,35 +916,6 @@ export class ViewerServer {
|
|
|
885
916
|
|
|
886
917
|
// ─── CRUD ───
|
|
887
918
|
|
|
888
|
-
private handleCreate(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
889
|
-
this.readBody(req, (body) => {
|
|
890
|
-
try {
|
|
891
|
-
const data = JSON.parse(body);
|
|
892
|
-
if (!data.content || typeof data.content !== "string" || !data.content.trim()) {
|
|
893
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
894
|
-
res.end(JSON.stringify({ error: "content is required and must be a non-empty string" }));
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
const { v4: uuidv4 } = require("uuid");
|
|
898
|
-
const id = uuidv4();
|
|
899
|
-
const now = Date.now();
|
|
900
|
-
this.store.insertChunk({
|
|
901
|
-
id, sessionKey: data.session_key || "manual", turnId: `manual-${now}`, seq: 0,
|
|
902
|
-
role: data.role || "user", content: data.content, kind: data.kind || "paragraph",
|
|
903
|
-
summary: data.summary || data.content.slice(0, 100),
|
|
904
|
-
taskId: null, skillId: null, owner: data.owner || "agent:main",
|
|
905
|
-
dedupStatus: "active", dedupTarget: null, dedupReason: null,
|
|
906
|
-
mergeCount: 0, lastHitAt: null, mergeHistory: "[]",
|
|
907
|
-
createdAt: now, updatedAt: now, embedding: null,
|
|
908
|
-
});
|
|
909
|
-
this.jsonResponse(res, { ok: true, id, message: "Memory created" });
|
|
910
|
-
} catch (err) {
|
|
911
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
912
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
|
|
917
919
|
private serveMemoryDetail(res: http.ServerResponse, urlPath: string): void {
|
|
918
920
|
const chunkId = urlPath.replace("/api/memory/", "");
|
|
919
921
|
const chunk = this.store.getChunk(chunkId);
|
|
@@ -938,7 +940,7 @@ export class ViewerServer {
|
|
|
938
940
|
res.end(JSON.stringify({ error: "content must be a non-empty string" }));
|
|
939
941
|
return;
|
|
940
942
|
}
|
|
941
|
-
const ok = this.store.updateChunk(chunkId, { summary: data.summary, content: data.content, role: data.role,
|
|
943
|
+
const ok = this.store.updateChunk(chunkId, { summary: data.summary, content: data.content, role: data.role, owner: data.owner });
|
|
942
944
|
if (ok) this.jsonResponse(res, { ok: true, message: "Memory updated" });
|
|
943
945
|
else { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); }
|
|
944
946
|
} catch (err) {
|
|
@@ -1149,20 +1151,20 @@ export class ViewerServer {
|
|
|
1149
1151
|
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1150
1152
|
return;
|
|
1151
1153
|
}
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1154
|
+
const { computeUpdateCheck } = await import("../update-check");
|
|
1155
|
+
const result = await computeUpdateCheck(name, current, fetch, 6_000);
|
|
1156
|
+
if (!result) {
|
|
1157
|
+
this.jsonResponse(res, { updateAvailable: false, current, packageName: name });
|
|
1157
1158
|
return;
|
|
1158
1159
|
}
|
|
1159
|
-
const data = await npmResp.json() as { version?: string };
|
|
1160
|
-
const latest = data.version ?? current;
|
|
1161
1160
|
this.jsonResponse(res, {
|
|
1162
|
-
updateAvailable:
|
|
1163
|
-
current,
|
|
1164
|
-
latest,
|
|
1165
|
-
packageName:
|
|
1161
|
+
updateAvailable: result.updateAvailable,
|
|
1162
|
+
current: result.current,
|
|
1163
|
+
latest: result.latest,
|
|
1164
|
+
packageName: result.packageName,
|
|
1165
|
+
channel: result.channel,
|
|
1166
|
+
installCommand: result.installCommand,
|
|
1167
|
+
stableChannel: result.stableChannel,
|
|
1166
1168
|
});
|
|
1167
1169
|
} catch (e) {
|
|
1168
1170
|
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
@@ -1170,6 +1172,47 @@ export class ViewerServer {
|
|
|
1170
1172
|
}
|
|
1171
1173
|
}
|
|
1172
1174
|
|
|
1175
|
+
private handleUpdateInstall(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
1176
|
+
let body = "";
|
|
1177
|
+
req.on("data", (chunk: Buffer) => { body += chunk.toString(); });
|
|
1178
|
+
req.on("end", () => {
|
|
1179
|
+
try {
|
|
1180
|
+
const { packageSpec } = JSON.parse(body);
|
|
1181
|
+
if (!packageSpec || typeof packageSpec !== "string") {
|
|
1182
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1183
|
+
res.end(JSON.stringify({ ok: false, error: "Missing packageSpec" }));
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const allowed = /^@[\w-]+\/[\w.-]+(@[\w.-]+)?$/;
|
|
1187
|
+
if (!allowed.test(packageSpec)) {
|
|
1188
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1189
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid package spec" }));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
this.log.info(`update-install: installing ${packageSpec}...`);
|
|
1193
|
+
exec(`npx openclaw plugins install ${packageSpec}`, { timeout: 120_000 }, (err, stdout, stderr) => {
|
|
1194
|
+
if (err) {
|
|
1195
|
+
this.log.warn(`update-install failed: ${err.message}\n${stderr}`);
|
|
1196
|
+
this.jsonResponse(res, { ok: false, error: stderr || err.message });
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
this.log.info(`update-install success: ${stdout}`);
|
|
1200
|
+
this.jsonResponse(res, { ok: true, output: stdout });
|
|
1201
|
+
this.log.info(`update-install: restarting gateway...`);
|
|
1202
|
+
setTimeout(() => {
|
|
1203
|
+
exec("npx openclaw gateway restart", { timeout: 30_000 }, (restartErr) => {
|
|
1204
|
+
if (restartErr) this.log.warn(`gateway restart failed: ${restartErr.message}`);
|
|
1205
|
+
else this.log.info("gateway restart initiated");
|
|
1206
|
+
});
|
|
1207
|
+
}, 1000);
|
|
1208
|
+
});
|
|
1209
|
+
} catch (e) {
|
|
1210
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1211
|
+
res.end(JSON.stringify({ ok: false, error: String(e) }));
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1173
1216
|
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<number | undefined> {
|
|
1174
1217
|
if (provider === "local") {
|
|
1175
1218
|
return 384;
|
|
@@ -1403,10 +1446,18 @@ export class ViewerServer {
|
|
|
1403
1446
|
}
|
|
1404
1447
|
|
|
1405
1448
|
let importedSessions: string[] = [];
|
|
1449
|
+
let importedChunkCount = 0;
|
|
1406
1450
|
try {
|
|
1407
1451
|
if (this.store) {
|
|
1408
1452
|
importedSessions = this.store.getDistinctSessionKeys()
|
|
1409
1453
|
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
1454
|
+
if (importedSessions.length > 0) {
|
|
1455
|
+
const placeholders = importedSessions.map(() => "?").join(",");
|
|
1456
|
+
const row = (this.store as any).db.prepare(
|
|
1457
|
+
`SELECT COUNT(*) as cnt FROM chunks WHERE session_key IN (${placeholders})`
|
|
1458
|
+
).get(...importedSessions) as { cnt: number };
|
|
1459
|
+
importedChunkCount = row?.cnt ?? 0;
|
|
1460
|
+
}
|
|
1410
1461
|
}
|
|
1411
1462
|
} catch (storeErr) {
|
|
1412
1463
|
this.log.warn(`migrate/scan: store query failed: ${storeErr}`);
|
|
@@ -1421,6 +1472,7 @@ export class ViewerServer {
|
|
|
1421
1472
|
hasSummarizer,
|
|
1422
1473
|
hasImportedData: importedSessions.length > 0,
|
|
1423
1474
|
importedSessionCount: importedSessions.length,
|
|
1475
|
+
importedChunkCount,
|
|
1424
1476
|
});
|
|
1425
1477
|
} catch (e) {
|
|
1426
1478
|
this.log.warn(`migrate/scan error: ${e}`);
|
|
@@ -1552,11 +1604,14 @@ export class ViewerServer {
|
|
|
1552
1604
|
} else {
|
|
1553
1605
|
this.broadcastSSE("done", { ok: true });
|
|
1554
1606
|
}
|
|
1555
|
-
for (const c of this.migrationSSEClients) {
|
|
1556
|
-
try { c.end(); } catch { /* ignore */ }
|
|
1557
|
-
}
|
|
1558
|
-
this.migrationSSEClients = [];
|
|
1559
1607
|
this.migrationAbort = false;
|
|
1608
|
+
const clientsToClose = [...this.migrationSSEClients];
|
|
1609
|
+
this.migrationSSEClients = [];
|
|
1610
|
+
setTimeout(() => {
|
|
1611
|
+
for (const c of clientsToClose) {
|
|
1612
|
+
try { c.end(); } catch { /* ignore */ }
|
|
1613
|
+
}
|
|
1614
|
+
}, 500);
|
|
1560
1615
|
});
|
|
1561
1616
|
});
|
|
1562
1617
|
}
|
|
@@ -1987,7 +2042,7 @@ export class ViewerServer {
|
|
|
1987
2042
|
res.on("close", () => { this.ppSSEClients = this.ppSSEClients.filter(c => c !== res); });
|
|
1988
2043
|
|
|
1989
2044
|
this.ppAbort = false;
|
|
1990
|
-
this.ppState = { running: true, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0 };
|
|
2045
|
+
this.ppState = { running: true, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0, skippedSessions: 0, totalSessions: 0 };
|
|
1991
2046
|
|
|
1992
2047
|
const send = (event: string, data: unknown) => {
|
|
1993
2048
|
this.broadcastPPSSE(event, data);
|
|
@@ -2004,9 +2059,12 @@ export class ViewerServer {
|
|
|
2004
2059
|
} else {
|
|
2005
2060
|
this.broadcastPPSSE("done", { ...this.ppState });
|
|
2006
2061
|
}
|
|
2007
|
-
for (const c of this.ppSSEClients) { try { c.end(); } catch { /* */ } }
|
|
2008
|
-
this.ppSSEClients = [];
|
|
2009
2062
|
this.ppAbort = false;
|
|
2063
|
+
const ppClientsToClose = [...this.ppSSEClients];
|
|
2064
|
+
this.ppSSEClients = [];
|
|
2065
|
+
setTimeout(() => {
|
|
2066
|
+
for (const c of ppClientsToClose) { try { c.end(); } catch { /* */ } }
|
|
2067
|
+
}, 500);
|
|
2010
2068
|
});
|
|
2011
2069
|
});
|
|
2012
2070
|
}
|
|
@@ -2038,7 +2096,13 @@ export class ViewerServer {
|
|
|
2038
2096
|
}
|
|
2039
2097
|
|
|
2040
2098
|
private handlePostprocessStatus(res: http.ServerResponse): void {
|
|
2041
|
-
|
|
2099
|
+
let existingTasks = 0;
|
|
2100
|
+
let existingSkills = 0;
|
|
2101
|
+
try {
|
|
2102
|
+
existingTasks = (this.store as any).db.prepare("SELECT COUNT(*) as c FROM tasks").get()?.c ?? 0;
|
|
2103
|
+
existingSkills = this.store.countSkills("active");
|
|
2104
|
+
} catch { /* */ }
|
|
2105
|
+
this.jsonResponse(res, { ...this.ppState, existingTasks, existingSkills });
|
|
2042
2106
|
}
|
|
2043
2107
|
|
|
2044
2108
|
private broadcastPPSSE(event: string, data: unknown): void {
|
|
@@ -2088,12 +2152,18 @@ export class ViewerServer {
|
|
|
2088
2152
|
}
|
|
2089
2153
|
|
|
2090
2154
|
this.ppState.total = pendingItems.length;
|
|
2155
|
+
this.ppState.skippedSessions = skippedCount;
|
|
2156
|
+
this.ppState.totalSessions = importSessions.length;
|
|
2157
|
+
const existingTaskCount = (this.store as any).db.prepare("SELECT COUNT(*) as c FROM tasks WHERE session_key IN (" + importSessions.map(() => "?").join(",") + ")").get(...importSessions)?.c ?? 0;
|
|
2158
|
+
const existingSkillCount = this.store.countSkills("active");
|
|
2091
2159
|
send("info", {
|
|
2092
2160
|
totalSessions: importSessions.length,
|
|
2093
2161
|
alreadyProcessed: skippedCount,
|
|
2094
2162
|
pending: pendingItems.length,
|
|
2095
2163
|
agents: Array.from(agentGroups.keys()),
|
|
2096
2164
|
concurrency,
|
|
2165
|
+
existingTasks: existingTaskCount,
|
|
2166
|
+
existingSkills: existingSkillCount,
|
|
2097
2167
|
});
|
|
2098
2168
|
send("progress", { processed: 0, total: pendingItems.length });
|
|
2099
2169
|
|