@memtensor/memos-local-openclaw-plugin 1.0.2-beta.5 → 1.0.2-beta.7
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/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +4 -3
- package/dist/embedding/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 +79 -39
- 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 +79 -39
- 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 +77 -39
- 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 +107 -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 +80 -39
- 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 -97
- 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 +3 -3
- 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 +110 -0
- package/dist/update-check.js.map +1 -0
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +487 -189
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +240 -78
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +205 -197
- package/openclaw.plugin.json +3 -0
- package/package.json +8 -3
- package/scripts/postinstall.cjs +69 -2
- package/skill/memos-memory-guide/SKILL.md +73 -36
- package/src/capture/index.ts +52 -8
- package/src/embedding/index.ts +4 -2
- package/src/ingest/chunker.ts +22 -30
- package/src/ingest/providers/anthropic.ts +89 -41
- package/src/ingest/providers/bedrock.ts +90 -41
- package/src/ingest/providers/gemini.ts +89 -41
- package/src/ingest/providers/index.ts +118 -35
- package/src/ingest/providers/openai.ts +90 -41
- 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 -96
- 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 +9 -10
- package/src/update-check.ts +95 -0
- package/src/viewer/html.ts +487 -189
- package/src/viewer/server.ts +187 -66
package/src/viewer/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import crypto from "node:crypto";
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
4
|
+
import { execSync, exec } from "node:child_process";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import readline from "node:readline";
|
|
@@ -75,8 +76,8 @@ export class ViewerServer {
|
|
|
75
76
|
|
|
76
77
|
private ppRunning = false;
|
|
77
78
|
private ppAbort = false;
|
|
78
|
-
private ppState: { running: boolean; done: boolean; stopped: boolean; processed: number; total: number; tasksCreated: number; skillsCreated: number; errors: number } =
|
|
79
|
-
{ running: false, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0 };
|
|
79
|
+
private ppState: { running: boolean; done: boolean; stopped: boolean; processed: number; total: number; tasksCreated: number; skillsCreated: number; errors: number; skippedSessions: number; totalSessions: number } =
|
|
80
|
+
{ running: false, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0, skippedSessions: 0, totalSessions: 0 };
|
|
80
81
|
private ppSSEClients: http.ServerResponse[] = [];
|
|
81
82
|
|
|
82
83
|
constructor(opts: ViewerServerOptions) {
|
|
@@ -235,7 +236,6 @@ export class ViewerServer {
|
|
|
235
236
|
else if (p.startsWith("/api/skill/") && req.method === "DELETE") this.handleSkillDelete(res, p);
|
|
236
237
|
else if (p.startsWith("/api/skill/") && req.method === "PUT") this.handleSkillUpdate(req, res, p);
|
|
237
238
|
else if (p.startsWith("/api/skill/") && req.method === "GET") this.serveSkillDetail(res, p);
|
|
238
|
-
else if (p === "/api/memory" && req.method === "POST") this.handleCreate(req, res);
|
|
239
239
|
else if (p.startsWith("/api/memory/") && req.method === "GET") this.serveMemoryDetail(res, p);
|
|
240
240
|
else if (p.startsWith("/api/memory/") && req.method === "PUT") this.handleUpdate(req, res, p);
|
|
241
241
|
else if (p.startsWith("/api/memory/") && req.method === "DELETE") this.handleDelete(res, p);
|
|
@@ -249,6 +249,7 @@ export class ViewerServer {
|
|
|
249
249
|
else if (p === "/api/model-health" && req.method === "GET") this.serveModelHealth(res);
|
|
250
250
|
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
251
251
|
else if (p === "/api/update-check" && req.method === "GET") this.handleUpdateCheck(res);
|
|
252
|
+
else if (p === "/api/update-install" && req.method === "POST") this.handleUpdateInstall(req, res);
|
|
252
253
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
253
254
|
else if (p === "/api/cleanup-polluted" && req.method === "POST") this.handleCleanupPolluted(res);
|
|
254
255
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
@@ -382,7 +383,6 @@ export class ViewerServer {
|
|
|
382
383
|
const offset = (page - 1) * limit;
|
|
383
384
|
const session = url.searchParams.get("session") ?? undefined;
|
|
384
385
|
const role = url.searchParams.get("role") ?? undefined;
|
|
385
|
-
const kind = url.searchParams.get("kind") ?? undefined;
|
|
386
386
|
const dateFrom = url.searchParams.get("dateFrom") ?? undefined;
|
|
387
387
|
const dateTo = url.searchParams.get("dateTo") ?? undefined;
|
|
388
388
|
const owner = url.searchParams.get("owner") ?? undefined;
|
|
@@ -393,7 +393,6 @@ export class ViewerServer {
|
|
|
393
393
|
const params: any[] = [];
|
|
394
394
|
if (session) { conditions.push("session_key = ?"); params.push(session); }
|
|
395
395
|
if (role) { conditions.push("role = ?"); params.push(role); }
|
|
396
|
-
if (kind) { conditions.push("kind = ?"); params.push(kind); }
|
|
397
396
|
if (owner) { conditions.push("owner = ?"); params.push(owner); }
|
|
398
397
|
if (dateFrom) { conditions.push("created_at >= ?"); params.push(new Date(dateFrom).getTime()); }
|
|
399
398
|
if (dateTo) { conditions.push("created_at <= ?"); params.push(new Date(dateTo).getTime()); }
|
|
@@ -401,9 +400,14 @@ export class ViewerServer {
|
|
|
401
400
|
const where = conditions.length > 0 ? " WHERE " + conditions.join(" AND ") : "";
|
|
402
401
|
const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks" + where).get(...params) as any;
|
|
403
402
|
const rawMemories = db.prepare("SELECT * FROM chunks" + where + ` ORDER BY created_at ${sortBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
403
|
+
const findMergeSources = db.prepare("SELECT id, summary, role FROM chunks WHERE dedup_target = ? AND (dedup_status = 'merged' OR dedup_status = 'duplicate')");
|
|
404
404
|
const memories = rawMemories.map((m: any) => {
|
|
405
405
|
if (m.role === "user" && m.content) {
|
|
406
|
-
|
|
406
|
+
m = { ...m, content: stripInboundMetadata(m.content) };
|
|
407
|
+
}
|
|
408
|
+
if (m.merge_count > 0) {
|
|
409
|
+
const sources = findMergeSources.all(m.id) as Array<{ id: string; summary: string; role: string }>;
|
|
410
|
+
m.merge_sources = sources;
|
|
407
411
|
}
|
|
408
412
|
return m;
|
|
409
413
|
});
|
|
@@ -441,7 +445,7 @@ export class ViewerServer {
|
|
|
441
445
|
id: t.id,
|
|
442
446
|
sessionKey: t.sessionKey,
|
|
443
447
|
title: t.title,
|
|
444
|
-
summary: t.summary
|
|
448
|
+
summary: t.summary ?? "",
|
|
445
449
|
status: t.status,
|
|
446
450
|
startedAt: t.startedAt,
|
|
447
451
|
endedAt: t.endedAt,
|
|
@@ -464,8 +468,7 @@ export class ViewerServer {
|
|
|
464
468
|
|
|
465
469
|
const chunks = this.store.getChunksByTask(taskId);
|
|
466
470
|
const chunkItems = chunks.map((c) => {
|
|
467
|
-
|
|
468
|
-
if (text.length > 500) text = text.slice(0, 497) + "...";
|
|
471
|
+
const text = c.role === "user" ? stripInboundMetadata(c.content) : c.content;
|
|
469
472
|
return { id: c.id, role: c.role, content: text, summary: c.summary, createdAt: c.createdAt };
|
|
470
473
|
});
|
|
471
474
|
|
|
@@ -502,7 +505,7 @@ export class ViewerServer {
|
|
|
502
505
|
const emptyStats = {
|
|
503
506
|
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
504
507
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
505
|
-
|
|
508
|
+
dedupBreakdown: {},
|
|
506
509
|
timeRange: { earliest: null, latest: null },
|
|
507
510
|
sessions: [],
|
|
508
511
|
};
|
|
@@ -516,7 +519,6 @@ export class ViewerServer {
|
|
|
516
519
|
const db = (this.store as any).db;
|
|
517
520
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
|
|
518
521
|
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
|
|
519
|
-
const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
|
|
520
522
|
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
523
|
const MIN_VALID_TS = 1704067200000; // 2024-01-01
|
|
522
524
|
if (timeRange.earliest != null && timeRange.earliest < MIN_VALID_TS) {
|
|
@@ -528,7 +530,6 @@ export class ViewerServer {
|
|
|
528
530
|
}
|
|
529
531
|
let embCount = 0;
|
|
530
532
|
try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
|
|
531
|
-
const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
|
|
532
533
|
const sessionList = db.prepare(
|
|
533
534
|
"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",
|
|
534
535
|
).all() as any[];
|
|
@@ -552,8 +553,6 @@ export class ViewerServer {
|
|
|
552
553
|
totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
|
|
553
554
|
totalSkills: skillCount,
|
|
554
555
|
embeddingProvider: this.embedder.provider,
|
|
555
|
-
roleBreakdown: Object.fromEntries(roles.map((r: any) => [r.role, r.count])),
|
|
556
|
-
kindBreakdown: Object.fromEntries(kinds.map((k: any) => [k.kind, k.count])),
|
|
557
556
|
dedupBreakdown,
|
|
558
557
|
timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
|
|
559
558
|
sessions: sessionList,
|
|
@@ -570,7 +569,6 @@ export class ViewerServer {
|
|
|
570
569
|
if (!q.trim()) { this.jsonResponse(res, { results: [], query: q }); return; }
|
|
571
570
|
|
|
572
571
|
const role = url.searchParams.get("role") ?? undefined;
|
|
573
|
-
const kind = url.searchParams.get("kind") ?? undefined;
|
|
574
572
|
const session = url.searchParams.get("session") ?? undefined;
|
|
575
573
|
const owner = url.searchParams.get("owner") ?? undefined;
|
|
576
574
|
const dateFrom = url.searchParams.get("dateFrom") ?? undefined;
|
|
@@ -578,7 +576,6 @@ export class ViewerServer {
|
|
|
578
576
|
|
|
579
577
|
const passesFilter = (r: any): boolean => {
|
|
580
578
|
if (role && r.role !== role) return false;
|
|
581
|
-
if (kind && r.kind !== kind) return false;
|
|
582
579
|
if (session && r.session_key !== session) return false;
|
|
583
580
|
if (owner && r.owner !== owner) return false;
|
|
584
581
|
if (dateFrom && r.created_at < new Date(dateFrom).getTime()) return false;
|
|
@@ -920,35 +917,6 @@ export class ViewerServer {
|
|
|
920
917
|
|
|
921
918
|
// ─── CRUD ───
|
|
922
919
|
|
|
923
|
-
private handleCreate(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
924
|
-
this.readBody(req, (body) => {
|
|
925
|
-
try {
|
|
926
|
-
const data = JSON.parse(body);
|
|
927
|
-
if (!data.content || typeof data.content !== "string" || !data.content.trim()) {
|
|
928
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
929
|
-
res.end(JSON.stringify({ error: "content is required and must be a non-empty string" }));
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
const { v4: uuidv4 } = require("uuid");
|
|
933
|
-
const id = uuidv4();
|
|
934
|
-
const now = Date.now();
|
|
935
|
-
this.store.insertChunk({
|
|
936
|
-
id, sessionKey: data.session_key || "manual", turnId: `manual-${now}`, seq: 0,
|
|
937
|
-
role: data.role || "user", content: data.content, kind: data.kind || "paragraph",
|
|
938
|
-
summary: data.summary || data.content.slice(0, 100),
|
|
939
|
-
taskId: null, skillId: null, owner: data.owner || "agent:main",
|
|
940
|
-
dedupStatus: "active", dedupTarget: null, dedupReason: null,
|
|
941
|
-
mergeCount: 0, lastHitAt: null, mergeHistory: "[]",
|
|
942
|
-
createdAt: now, updatedAt: now, embedding: null,
|
|
943
|
-
});
|
|
944
|
-
this.jsonResponse(res, { ok: true, id, message: "Memory created" });
|
|
945
|
-
} catch (err) {
|
|
946
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
947
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
920
|
private serveMemoryDetail(res: http.ServerResponse, urlPath: string): void {
|
|
953
921
|
const chunkId = urlPath.replace("/api/memory/", "");
|
|
954
922
|
const chunk = this.store.getChunk(chunkId);
|
|
@@ -973,7 +941,7 @@ export class ViewerServer {
|
|
|
973
941
|
res.end(JSON.stringify({ error: "content must be a non-empty string" }));
|
|
974
942
|
return;
|
|
975
943
|
}
|
|
976
|
-
const ok = this.store.updateChunk(chunkId, { summary: data.summary, content: data.content, role: data.role,
|
|
944
|
+
const ok = this.store.updateChunk(chunkId, { summary: data.summary, content: data.content, role: data.role, owner: data.owner });
|
|
977
945
|
if (ok) this.jsonResponse(res, { ok: true, message: "Memory updated" });
|
|
978
946
|
else { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); }
|
|
979
947
|
} catch (err) {
|
|
@@ -1184,20 +1152,20 @@ export class ViewerServer {
|
|
|
1184
1152
|
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1185
1153
|
return;
|
|
1186
1154
|
}
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1155
|
+
const { computeUpdateCheck } = await import("../update-check");
|
|
1156
|
+
const result = await computeUpdateCheck(name, current, fetch, 6_000);
|
|
1157
|
+
if (!result) {
|
|
1158
|
+
this.jsonResponse(res, { updateAvailable: false, current, packageName: name });
|
|
1192
1159
|
return;
|
|
1193
1160
|
}
|
|
1194
|
-
const data = await npmResp.json() as { version?: string };
|
|
1195
|
-
const latest = data.version ?? current;
|
|
1196
1161
|
this.jsonResponse(res, {
|
|
1197
|
-
updateAvailable:
|
|
1198
|
-
current,
|
|
1199
|
-
latest,
|
|
1200
|
-
packageName:
|
|
1162
|
+
updateAvailable: result.updateAvailable,
|
|
1163
|
+
current: result.current,
|
|
1164
|
+
latest: result.latest,
|
|
1165
|
+
packageName: result.packageName,
|
|
1166
|
+
channel: result.channel,
|
|
1167
|
+
installCommand: result.installCommand,
|
|
1168
|
+
stableChannel: result.stableChannel,
|
|
1201
1169
|
});
|
|
1202
1170
|
} catch (e) {
|
|
1203
1171
|
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
@@ -1205,6 +1173,132 @@ export class ViewerServer {
|
|
|
1205
1173
|
}
|
|
1206
1174
|
}
|
|
1207
1175
|
|
|
1176
|
+
private handleUpdateInstall(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
1177
|
+
let body = "";
|
|
1178
|
+
req.on("data", (chunk: Buffer) => { body += chunk.toString(); });
|
|
1179
|
+
req.on("end", () => {
|
|
1180
|
+
try {
|
|
1181
|
+
const { packageSpec: rawSpec } = JSON.parse(body);
|
|
1182
|
+
if (!rawSpec || typeof rawSpec !== "string") {
|
|
1183
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1184
|
+
res.end(JSON.stringify({ ok: false, error: "Missing packageSpec" }));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
const packageSpec = rawSpec.trim().replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/i, "");
|
|
1188
|
+
const allowed = /^@[\w-]+\/[\w.-]+(@[\w.-]+)?$/;
|
|
1189
|
+
this.log.info(`update-install: received packageSpec="${packageSpec}" (len=${packageSpec.length})`);
|
|
1190
|
+
if (!allowed.test(packageSpec)) {
|
|
1191
|
+
this.log.warn(`update-install: rejected packageSpec="${packageSpec}" — does not match ${allowed}`);
|
|
1192
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1193
|
+
res.end(JSON.stringify({ ok: false, error: `Invalid package spec: "${packageSpec}"` }));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const pkgPath = this.findPluginPackageJson();
|
|
1198
|
+
const pluginName = pkgPath
|
|
1199
|
+
? (() => { try { return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name; } catch { return null; } })()
|
|
1200
|
+
: null;
|
|
1201
|
+
const shortName = pluginName?.replace(/^@[\w-]+\//, "") ?? "memos-local-openclaw-plugin";
|
|
1202
|
+
const extDir = path.join(os.homedir(), ".openclaw", "extensions", shortName);
|
|
1203
|
+
const tmpDir = path.join(os.tmpdir(), `openclaw-update-${Date.now()}`);
|
|
1204
|
+
|
|
1205
|
+
// Download via npm pack, extract, and replace extension dir.
|
|
1206
|
+
// Does NOT touch openclaw.json → no config watcher SIGUSR1.
|
|
1207
|
+
this.log.info(`update-install: downloading ${packageSpec} via npm pack...`);
|
|
1208
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
1209
|
+
exec(`npm pack ${packageSpec} --pack-destination ${tmpDir}`, { timeout: 60_000 }, (packErr, packOut) => {
|
|
1210
|
+
if (packErr) {
|
|
1211
|
+
this.log.warn(`update-install: npm pack failed: ${packErr.message}`);
|
|
1212
|
+
this.jsonResponse(res, { ok: false, error: `Download failed: ${packErr.message}` });
|
|
1213
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const tgzFile = packOut.trim().split("\n").pop()!;
|
|
1217
|
+
const tgzPath = path.join(tmpDir, tgzFile);
|
|
1218
|
+
this.log.info(`update-install: downloaded ${tgzFile}, extracting...`);
|
|
1219
|
+
|
|
1220
|
+
const extractDir = path.join(tmpDir, "extract");
|
|
1221
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
1222
|
+
exec(`tar -xzf ${tgzPath} -C ${extractDir}`, { timeout: 30_000 }, (tarErr) => {
|
|
1223
|
+
if (tarErr) {
|
|
1224
|
+
this.log.warn(`update-install: tar extract failed: ${tarErr.message}`);
|
|
1225
|
+
this.jsonResponse(res, { ok: false, error: `Extract failed: ${tarErr.message}` });
|
|
1226
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// npm pack extracts to a "package" subdirectory
|
|
1231
|
+
const srcDir = path.join(extractDir, "package");
|
|
1232
|
+
if (!fs.existsSync(srcDir)) {
|
|
1233
|
+
this.jsonResponse(res, { ok: false, error: "Extracted package has no 'package' dir" });
|
|
1234
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// Replace extension directory
|
|
1239
|
+
this.log.info(`update-install: replacing ${extDir}...`);
|
|
1240
|
+
try { fs.rmSync(extDir, { recursive: true, force: true }); } catch {}
|
|
1241
|
+
fs.mkdirSync(path.dirname(extDir), { recursive: true });
|
|
1242
|
+
fs.renameSync(srcDir, extDir);
|
|
1243
|
+
|
|
1244
|
+
// Install dependencies
|
|
1245
|
+
this.log.info(`update-install: installing dependencies...`);
|
|
1246
|
+
exec(`cd ${extDir} && npm install --omit=dev --ignore-scripts`, { timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
|
|
1247
|
+
if (npmErr) {
|
|
1248
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1249
|
+
this.log.warn(`update-install: npm install failed: ${npmErr.message}`);
|
|
1250
|
+
this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Rebuild native modules (do not swallow errors)
|
|
1255
|
+
exec(`cd ${extDir} && npm rebuild better-sqlite3`, { timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
|
|
1256
|
+
if (rebuildErr) {
|
|
1257
|
+
this.log.warn(`update-install: better-sqlite3 rebuild failed: ${rebuildErr.message}`);
|
|
1258
|
+
const stderr = String(rebuildStderr || "").trim();
|
|
1259
|
+
if (stderr) this.log.warn(`update-install: rebuild stderr: ${stderr.slice(0, 500)}`);
|
|
1260
|
+
// Continue so postinstall.cjs can run (it will try rebuild again and show user guidance)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Run postinstall.cjs: legacy cleanup, skill install, version marker, and optional sqlite re-check
|
|
1264
|
+
this.log.info(`update-install: running postinstall...`);
|
|
1265
|
+
exec(`cd ${extDir} && node scripts/postinstall.cjs`, { timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
1266
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1267
|
+
|
|
1268
|
+
if (postErr) {
|
|
1269
|
+
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
1270
|
+
const postStderrStr = String(postStderr || "").trim();
|
|
1271
|
+
if (postStderrStr) this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
|
|
1272
|
+
// Still report success; plugin is updated, user can run postinstall manually if needed
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// Read new version
|
|
1276
|
+
let newVersion = "unknown";
|
|
1277
|
+
try {
|
|
1278
|
+
const newPkg = JSON.parse(fs.readFileSync(path.join(extDir, "package.json"), "utf-8"));
|
|
1279
|
+
newVersion = newPkg.version ?? newVersion;
|
|
1280
|
+
} catch {}
|
|
1281
|
+
|
|
1282
|
+
this.log.info(`update-install: success! Updated to ${newVersion}`);
|
|
1283
|
+
this.jsonResponse(res, { ok: true, version: newVersion });
|
|
1284
|
+
|
|
1285
|
+
// Trigger Gateway restart after response is sent
|
|
1286
|
+
setTimeout(() => {
|
|
1287
|
+
this.log.info(`update-install: triggering gateway restart...`);
|
|
1288
|
+
process.kill(process.pid, "SIGUSR1");
|
|
1289
|
+
}, 500);
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
} catch (e) {
|
|
1296
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1297
|
+
res.end(JSON.stringify({ ok: false, error: String(e) }));
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1208
1302
|
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<number | undefined> {
|
|
1209
1303
|
if (provider === "local") {
|
|
1210
1304
|
return 384;
|
|
@@ -1438,10 +1532,18 @@ export class ViewerServer {
|
|
|
1438
1532
|
}
|
|
1439
1533
|
|
|
1440
1534
|
let importedSessions: string[] = [];
|
|
1535
|
+
let importedChunkCount = 0;
|
|
1441
1536
|
try {
|
|
1442
1537
|
if (this.store) {
|
|
1443
1538
|
importedSessions = this.store.getDistinctSessionKeys()
|
|
1444
1539
|
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
1540
|
+
if (importedSessions.length > 0) {
|
|
1541
|
+
const placeholders = importedSessions.map(() => "?").join(",");
|
|
1542
|
+
const row = (this.store as any).db.prepare(
|
|
1543
|
+
`SELECT COUNT(*) as cnt FROM chunks WHERE session_key IN (${placeholders})`
|
|
1544
|
+
).get(...importedSessions) as { cnt: number };
|
|
1545
|
+
importedChunkCount = row?.cnt ?? 0;
|
|
1546
|
+
}
|
|
1445
1547
|
}
|
|
1446
1548
|
} catch (storeErr) {
|
|
1447
1549
|
this.log.warn(`migrate/scan: store query failed: ${storeErr}`);
|
|
@@ -1456,6 +1558,7 @@ export class ViewerServer {
|
|
|
1456
1558
|
hasSummarizer,
|
|
1457
1559
|
hasImportedData: importedSessions.length > 0,
|
|
1458
1560
|
importedSessionCount: importedSessions.length,
|
|
1561
|
+
importedChunkCount,
|
|
1459
1562
|
});
|
|
1460
1563
|
} catch (e) {
|
|
1461
1564
|
this.log.warn(`migrate/scan error: ${e}`);
|
|
@@ -1587,11 +1690,14 @@ export class ViewerServer {
|
|
|
1587
1690
|
} else {
|
|
1588
1691
|
this.broadcastSSE("done", { ok: true });
|
|
1589
1692
|
}
|
|
1590
|
-
for (const c of this.migrationSSEClients) {
|
|
1591
|
-
try { c.end(); } catch { /* ignore */ }
|
|
1592
|
-
}
|
|
1593
|
-
this.migrationSSEClients = [];
|
|
1594
1693
|
this.migrationAbort = false;
|
|
1694
|
+
const clientsToClose = [...this.migrationSSEClients];
|
|
1695
|
+
this.migrationSSEClients = [];
|
|
1696
|
+
setTimeout(() => {
|
|
1697
|
+
for (const c of clientsToClose) {
|
|
1698
|
+
try { c.end(); } catch { /* ignore */ }
|
|
1699
|
+
}
|
|
1700
|
+
}, 500);
|
|
1595
1701
|
});
|
|
1596
1702
|
});
|
|
1597
1703
|
}
|
|
@@ -2022,7 +2128,7 @@ export class ViewerServer {
|
|
|
2022
2128
|
res.on("close", () => { this.ppSSEClients = this.ppSSEClients.filter(c => c !== res); });
|
|
2023
2129
|
|
|
2024
2130
|
this.ppAbort = false;
|
|
2025
|
-
this.ppState = { running: true, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0 };
|
|
2131
|
+
this.ppState = { running: true, done: false, stopped: false, processed: 0, total: 0, tasksCreated: 0, skillsCreated: 0, errors: 0, skippedSessions: 0, totalSessions: 0 };
|
|
2026
2132
|
|
|
2027
2133
|
const send = (event: string, data: unknown) => {
|
|
2028
2134
|
this.broadcastPPSSE(event, data);
|
|
@@ -2039,9 +2145,12 @@ export class ViewerServer {
|
|
|
2039
2145
|
} else {
|
|
2040
2146
|
this.broadcastPPSSE("done", { ...this.ppState });
|
|
2041
2147
|
}
|
|
2042
|
-
for (const c of this.ppSSEClients) { try { c.end(); } catch { /* */ } }
|
|
2043
|
-
this.ppSSEClients = [];
|
|
2044
2148
|
this.ppAbort = false;
|
|
2149
|
+
const ppClientsToClose = [...this.ppSSEClients];
|
|
2150
|
+
this.ppSSEClients = [];
|
|
2151
|
+
setTimeout(() => {
|
|
2152
|
+
for (const c of ppClientsToClose) { try { c.end(); } catch { /* */ } }
|
|
2153
|
+
}, 500);
|
|
2045
2154
|
});
|
|
2046
2155
|
});
|
|
2047
2156
|
}
|
|
@@ -2073,7 +2182,13 @@ export class ViewerServer {
|
|
|
2073
2182
|
}
|
|
2074
2183
|
|
|
2075
2184
|
private handlePostprocessStatus(res: http.ServerResponse): void {
|
|
2076
|
-
|
|
2185
|
+
let existingTasks = 0;
|
|
2186
|
+
let existingSkills = 0;
|
|
2187
|
+
try {
|
|
2188
|
+
existingTasks = (this.store as any).db.prepare("SELECT COUNT(*) as c FROM tasks").get()?.c ?? 0;
|
|
2189
|
+
existingSkills = this.store.countSkills("active");
|
|
2190
|
+
} catch { /* */ }
|
|
2191
|
+
this.jsonResponse(res, { ...this.ppState, existingTasks, existingSkills });
|
|
2077
2192
|
}
|
|
2078
2193
|
|
|
2079
2194
|
private broadcastPPSSE(event: string, data: unknown): void {
|
|
@@ -2123,12 +2238,18 @@ export class ViewerServer {
|
|
|
2123
2238
|
}
|
|
2124
2239
|
|
|
2125
2240
|
this.ppState.total = pendingItems.length;
|
|
2241
|
+
this.ppState.skippedSessions = skippedCount;
|
|
2242
|
+
this.ppState.totalSessions = importSessions.length;
|
|
2243
|
+
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;
|
|
2244
|
+
const existingSkillCount = this.store.countSkills("active");
|
|
2126
2245
|
send("info", {
|
|
2127
2246
|
totalSessions: importSessions.length,
|
|
2128
2247
|
alreadyProcessed: skippedCount,
|
|
2129
2248
|
pending: pendingItems.length,
|
|
2130
2249
|
agents: Array.from(agentGroups.keys()),
|
|
2131
2250
|
concurrency,
|
|
2251
|
+
existingTasks: existingTaskCount,
|
|
2252
|
+
existingSkills: existingSkillCount,
|
|
2132
2253
|
});
|
|
2133
2254
|
send("progress", { processed: 0, total: pendingItems.length });
|
|
2134
2255
|
|