@memtensor/memos-local-openclaw-plugin 1.0.6-beta.1 → 1.0.6-beta.11
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/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +10 -4
- package/dist/client/connector.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +108 -54
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +4 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +20 -4
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +0 -3
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +9 -8
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +35 -43
- package/dist/recall/engine.js.map +1 -1
- package/dist/storage/sqlite.d.ts +13 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +43 -1
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +2 -7
- package/dist/update-check.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +115 -27
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +1 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +191 -96
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +208 -253
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/native-binding.cjs +32 -0
- package/scripts/postinstall.cjs +13 -16
- package/src/client/connector.ts +10 -4
- package/src/hub/server.ts +95 -51
- package/src/ingest/providers/index.ts +26 -18
- package/src/ingest/providers/openai.ts +5 -4
- package/src/recall/engine.ts +34 -41
- package/src/storage/sqlite.ts +43 -1
- package/src/update-check.ts +2 -7
- package/src/viewer/html.ts +115 -27
- package/src/viewer/server.ts +187 -64
package/dist/viewer/server.js
CHANGED
|
@@ -108,26 +108,6 @@ function applyMigrationItemToState(state, d) {
|
|
|
108
108
|
state.lastItem = d;
|
|
109
109
|
state.success = computeMigrationSuccess(state);
|
|
110
110
|
}
|
|
111
|
-
/** Epoch ms for Chunk; OpenClaw SQLite may store Unix seconds or ms. */
|
|
112
|
-
function normalizeTimestamp(value) {
|
|
113
|
-
if (value == null)
|
|
114
|
-
return Date.now();
|
|
115
|
-
if (typeof value === "string") {
|
|
116
|
-
const parsed = Date.parse(value.trim());
|
|
117
|
-
if (Number.isFinite(parsed))
|
|
118
|
-
return parsed;
|
|
119
|
-
const n = Number(value);
|
|
120
|
-
if (Number.isFinite(n))
|
|
121
|
-
return normalizeTimestamp(n);
|
|
122
|
-
return Date.now();
|
|
123
|
-
}
|
|
124
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
125
|
-
if (value > 0 && value < 5_000_000_000)
|
|
126
|
-
return Math.round(value * 1000);
|
|
127
|
-
return Math.round(value);
|
|
128
|
-
}
|
|
129
|
-
return Date.now();
|
|
130
|
-
}
|
|
131
111
|
class ViewerServer {
|
|
132
112
|
server = null;
|
|
133
113
|
store;
|
|
@@ -2090,18 +2070,24 @@ class ViewerServer {
|
|
|
2090
2070
|
handleRetryJoin(req, res) {
|
|
2091
2071
|
this.readBody(req, async (_body) => {
|
|
2092
2072
|
if (!this.ctx)
|
|
2093
|
-
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2073
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable", errorCode: "sharing_unavailable" });
|
|
2094
2074
|
const sharing = this.ctx.config.sharing;
|
|
2095
2075
|
if (!sharing?.enabled || sharing.role !== "client") {
|
|
2096
|
-
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
|
|
2076
|
+
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode", errorCode: "not_in_client_mode" });
|
|
2097
2077
|
}
|
|
2098
2078
|
const hubAddress = sharing.client?.hubAddress ?? "";
|
|
2099
2079
|
const teamToken = sharing.client?.teamToken ?? "";
|
|
2100
2080
|
if (!hubAddress || !teamToken) {
|
|
2101
|
-
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
|
|
2081
|
+
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token", errorCode: "missing_config" });
|
|
2082
|
+
}
|
|
2083
|
+
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
2084
|
+
try {
|
|
2085
|
+
await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
2086
|
+
}
|
|
2087
|
+
catch {
|
|
2088
|
+
return this.jsonResponse(res, { ok: false, error: "hub_unreachable", errorCode: "hub_unreachable" });
|
|
2102
2089
|
}
|
|
2103
2090
|
try {
|
|
2104
|
-
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
2105
2091
|
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
2106
2092
|
const nickname = sharing.client?.nickname;
|
|
2107
2093
|
const username = nickname || os.userInfo().username || "user";
|
|
@@ -2130,10 +2116,20 @@ class ViewerServer {
|
|
|
2130
2116
|
lastKnownStatus: result.status || "",
|
|
2131
2117
|
hubInstanceId,
|
|
2132
2118
|
});
|
|
2119
|
+
if (result.status === "blocked") {
|
|
2120
|
+
return this.jsonResponse(res, { ok: false, error: "blocked", errorCode: "blocked" });
|
|
2121
|
+
}
|
|
2133
2122
|
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
2134
2123
|
}
|
|
2135
2124
|
catch (err) {
|
|
2136
|
-
|
|
2125
|
+
const errStr = String(err);
|
|
2126
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
2127
|
+
return this.jsonResponse(res, { ok: false, error: "username_taken", errorCode: "username_taken" });
|
|
2128
|
+
}
|
|
2129
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
2130
|
+
return this.jsonResponse(res, { ok: false, error: "invalid_team_token", errorCode: "invalid_team_token" });
|
|
2131
|
+
}
|
|
2132
|
+
this.jsonResponse(res, { ok: false, error: errStr, errorCode: "unknown" });
|
|
2137
2133
|
}
|
|
2138
2134
|
});
|
|
2139
2135
|
}
|
|
@@ -3118,24 +3114,24 @@ class ViewerServer {
|
|
|
3118
3114
|
const nowClient = Boolean(finalSharing?.enabled) && finalSharing?.role === "client";
|
|
3119
3115
|
const previouslyClient = oldSharingEnabled && oldSharingRole === "client";
|
|
3120
3116
|
let joinStatus;
|
|
3117
|
+
let joinError;
|
|
3121
3118
|
if (nowClient && !previouslyClient) {
|
|
3122
3119
|
try {
|
|
3123
3120
|
joinStatus = await this.autoJoinOnSave(finalSharing);
|
|
3124
3121
|
}
|
|
3125
3122
|
catch (e) {
|
|
3126
|
-
|
|
3123
|
+
const msg = String(e instanceof Error ? e.message : e);
|
|
3124
|
+
this.log.warn(`Auto-join on save failed: ${msg}`);
|
|
3125
|
+
if (msg === "hub_unreachable" || msg === "username_taken" || msg === "invalid_team_token") {
|
|
3126
|
+
joinError = msg;
|
|
3127
|
+
}
|
|
3127
3128
|
}
|
|
3128
3129
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
}
|
|
3135
|
-
catch (sig) {
|
|
3136
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
3137
|
-
}
|
|
3138
|
-
}, 500);
|
|
3130
|
+
if (joinError) {
|
|
3131
|
+
this.jsonResponse(res, { ok: true, joinError, restart: false });
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
this.jsonResponseAndRestart(res, { ok: true, joinStatus, restart: true }, "config-save");
|
|
3139
3135
|
}
|
|
3140
3136
|
catch (e) {
|
|
3141
3137
|
this.log.warn(`handleSaveConfig error: ${e}`);
|
|
@@ -3151,16 +3147,35 @@ class ViewerServer {
|
|
|
3151
3147
|
if (!hubAddress || !teamToken)
|
|
3152
3148
|
return undefined;
|
|
3153
3149
|
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
3150
|
+
try {
|
|
3151
|
+
await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
3152
|
+
}
|
|
3153
|
+
catch {
|
|
3154
|
+
throw new Error("hub_unreachable");
|
|
3155
|
+
}
|
|
3154
3156
|
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
3155
3157
|
const nickname = String(clientCfg?.nickname || "");
|
|
3156
3158
|
const username = nickname || os.userInfo().username || "user";
|
|
3157
3159
|
const hostname = os.hostname() || "unknown";
|
|
3158
3160
|
const persisted = this.store.getClientHubConnection();
|
|
3159
3161
|
const existingIdentityKey = persisted?.identityKey || "";
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3162
|
+
let result;
|
|
3163
|
+
try {
|
|
3164
|
+
result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
|
|
3165
|
+
method: "POST",
|
|
3166
|
+
body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
catch (err) {
|
|
3170
|
+
const errStr = String(err);
|
|
3171
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
3172
|
+
throw new Error("username_taken");
|
|
3173
|
+
}
|
|
3174
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
3175
|
+
throw new Error("invalid_team_token");
|
|
3176
|
+
}
|
|
3177
|
+
throw err;
|
|
3178
|
+
}
|
|
3164
3179
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
3165
3180
|
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
3166
3181
|
try {
|
|
@@ -3206,16 +3221,7 @@ class ViewerServer {
|
|
|
3206
3221
|
this.log.info("handleLeaveTeam: config updated, sharing disabled");
|
|
3207
3222
|
}
|
|
3208
3223
|
}
|
|
3209
|
-
this.
|
|
3210
|
-
setTimeout(() => {
|
|
3211
|
-
this.log.info("handleLeaveTeam: triggering gateway restart via SIGUSR1...");
|
|
3212
|
-
try {
|
|
3213
|
-
process.kill(process.pid, "SIGUSR1");
|
|
3214
|
-
}
|
|
3215
|
-
catch (sig) {
|
|
3216
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
3217
|
-
}
|
|
3218
|
-
}, 500);
|
|
3224
|
+
this.jsonResponseAndRestart(res, { ok: true, restart: true }, "handleLeaveTeam");
|
|
3219
3225
|
}
|
|
3220
3226
|
catch (e) {
|
|
3221
3227
|
this.log.warn(`handleLeaveTeam error: ${e}`);
|
|
@@ -3389,17 +3395,45 @@ class ViewerServer {
|
|
|
3389
3395
|
}
|
|
3390
3396
|
}
|
|
3391
3397
|
catch { }
|
|
3392
|
-
const
|
|
3398
|
+
const baseUrl = hubUrl.replace(/\/+$/, "");
|
|
3399
|
+
const infoUrl = baseUrl + "/api/v1/hub/info";
|
|
3393
3400
|
const ctrl = new AbortController();
|
|
3394
3401
|
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
3395
3402
|
try {
|
|
3396
|
-
const r = await fetch(
|
|
3403
|
+
const r = await fetch(infoUrl, { signal: ctrl.signal });
|
|
3397
3404
|
clearTimeout(timeout);
|
|
3398
3405
|
if (!r.ok) {
|
|
3399
3406
|
this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
|
|
3400
3407
|
return;
|
|
3401
3408
|
}
|
|
3402
3409
|
const info = await r.json();
|
|
3410
|
+
const { teamToken, nickname } = JSON.parse(body);
|
|
3411
|
+
if (teamToken) {
|
|
3412
|
+
const username = (typeof nickname === "string" && nickname.trim()) || node_os_1.default.userInfo().username || "user";
|
|
3413
|
+
const persisted = this.store.getClientHubConnection();
|
|
3414
|
+
const identityKey = persisted?.identityKey || "";
|
|
3415
|
+
try {
|
|
3416
|
+
const joinR = await fetch(baseUrl + "/api/v1/hub/join", {
|
|
3417
|
+
method: "POST",
|
|
3418
|
+
headers: { "content-type": "application/json" },
|
|
3419
|
+
body: JSON.stringify({ teamToken, username, identityKey, deviceName: node_os_1.default.hostname(), dryRun: true }),
|
|
3420
|
+
});
|
|
3421
|
+
const joinData = await joinR.json();
|
|
3422
|
+
if (!joinR.ok && joinData.error === "username_taken") {
|
|
3423
|
+
this.jsonResponse(res, { ok: false, error: "username_taken", teamName: info.teamName || "" });
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
if (!joinR.ok && joinData.error === "invalid_team_token") {
|
|
3427
|
+
this.jsonResponse(res, { ok: false, error: "invalid_team_token", teamName: info.teamName || "" });
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
if (joinR.ok && joinData.status === "blocked") {
|
|
3431
|
+
this.jsonResponse(res, { ok: false, error: "blocked", teamName: info.teamName || "" });
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
catch { /* join check is best-effort; connection itself is OK */ }
|
|
3436
|
+
}
|
|
3403
3437
|
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
3404
3438
|
}
|
|
3405
3439
|
catch (e) {
|
|
@@ -3486,26 +3520,35 @@ class ViewerServer {
|
|
|
3486
3520
|
return null;
|
|
3487
3521
|
}
|
|
3488
3522
|
async handleUpdateCheck(res) {
|
|
3523
|
+
const sendNoStore = (data, statusCode = 200) => {
|
|
3524
|
+
res.writeHead(statusCode, {
|
|
3525
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
3526
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
3527
|
+
"Pragma": "no-cache",
|
|
3528
|
+
"Expires": "0",
|
|
3529
|
+
});
|
|
3530
|
+
res.end(JSON.stringify(data));
|
|
3531
|
+
};
|
|
3489
3532
|
try {
|
|
3490
3533
|
const pkgPath = this.findPluginPackageJson();
|
|
3491
3534
|
if (!pkgPath) {
|
|
3492
|
-
|
|
3535
|
+
sendNoStore({ updateAvailable: false, error: "package.json not found" });
|
|
3493
3536
|
return;
|
|
3494
3537
|
}
|
|
3495
3538
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf-8"));
|
|
3496
3539
|
const current = pkg.version;
|
|
3497
3540
|
const name = pkg.name;
|
|
3498
3541
|
if (!current || !name) {
|
|
3499
|
-
|
|
3542
|
+
sendNoStore({ updateAvailable: false, current });
|
|
3500
3543
|
return;
|
|
3501
3544
|
}
|
|
3502
3545
|
const { computeUpdateCheck } = await Promise.resolve().then(() => __importStar(require("../update-check")));
|
|
3503
3546
|
const result = await computeUpdateCheck(name, current, fetch, 6_000);
|
|
3504
3547
|
if (!result) {
|
|
3505
|
-
|
|
3548
|
+
sendNoStore({ updateAvailable: false, current, packageName: name });
|
|
3506
3549
|
return;
|
|
3507
3550
|
}
|
|
3508
|
-
|
|
3551
|
+
sendNoStore({
|
|
3509
3552
|
updateAvailable: result.updateAvailable,
|
|
3510
3553
|
current: result.current,
|
|
3511
3554
|
latest: result.latest,
|
|
@@ -3517,7 +3560,7 @@ class ViewerServer {
|
|
|
3517
3560
|
}
|
|
3518
3561
|
catch (e) {
|
|
3519
3562
|
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
3520
|
-
|
|
3563
|
+
sendNoStore({ updateAvailable: false, error: String(e) });
|
|
3521
3564
|
}
|
|
3522
3565
|
}
|
|
3523
3566
|
handleUpdateInstall(req, res) {
|
|
@@ -3525,13 +3568,14 @@ class ViewerServer {
|
|
|
3525
3568
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
3526
3569
|
req.on("end", () => {
|
|
3527
3570
|
try {
|
|
3528
|
-
const { packageSpec: rawSpec } = JSON.parse(body);
|
|
3571
|
+
const { packageSpec: rawSpec, targetVersion: rawTargetVersion } = JSON.parse(body);
|
|
3529
3572
|
if (!rawSpec || typeof rawSpec !== "string") {
|
|
3530
3573
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3531
3574
|
res.end(JSON.stringify({ ok: false, error: "Missing packageSpec" }));
|
|
3532
3575
|
return;
|
|
3533
3576
|
}
|
|
3534
3577
|
const packageSpec = rawSpec.trim().replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/i, "");
|
|
3578
|
+
const targetVersion = typeof rawTargetVersion === "string" ? rawTargetVersion.trim() : "";
|
|
3535
3579
|
const allowed = /^@[\w-]+\/[\w.-]+(@[\w.-]+)?$/;
|
|
3536
3580
|
this.log.info(`update-install: received packageSpec="${packageSpec}" (len=${packageSpec.length})`);
|
|
3537
3581
|
if (!allowed.test(packageSpec)) {
|
|
@@ -3552,18 +3596,50 @@ class ViewerServer {
|
|
|
3552
3596
|
const shortName = pluginName?.replace(/^@[\w-]+\//, "") ?? "memos-local-openclaw-plugin";
|
|
3553
3597
|
const extDir = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "extensions", shortName);
|
|
3554
3598
|
const tmpDir = node_path_1.default.join(node_os_1.default.tmpdir(), `openclaw-update-${Date.now()}`);
|
|
3599
|
+
const backupDir = node_path_1.default.join(node_path_1.default.dirname(extDir), `${shortName}.backup-${Date.now()}`);
|
|
3600
|
+
let backupReady = false;
|
|
3601
|
+
const cleanupTmpDir = () => {
|
|
3602
|
+
try {
|
|
3603
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3604
|
+
}
|
|
3605
|
+
catch { }
|
|
3606
|
+
};
|
|
3607
|
+
const rollbackInstall = () => {
|
|
3608
|
+
try {
|
|
3609
|
+
node_fs_1.default.rmSync(extDir, { recursive: true, force: true });
|
|
3610
|
+
}
|
|
3611
|
+
catch { }
|
|
3612
|
+
if (!backupReady)
|
|
3613
|
+
return;
|
|
3614
|
+
try {
|
|
3615
|
+
node_fs_1.default.renameSync(backupDir, extDir);
|
|
3616
|
+
backupReady = false;
|
|
3617
|
+
this.log.info(`update-install: restored previous version from ${backupDir}`);
|
|
3618
|
+
}
|
|
3619
|
+
catch (restoreErr) {
|
|
3620
|
+
this.log.warn(`update-install: failed to restore previous version: ${restoreErr?.message ?? restoreErr}`);
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
const discardBackup = () => {
|
|
3624
|
+
if (!backupReady)
|
|
3625
|
+
return;
|
|
3626
|
+
try {
|
|
3627
|
+
node_fs_1.default.rmSync(backupDir, { recursive: true, force: true });
|
|
3628
|
+
backupReady = false;
|
|
3629
|
+
}
|
|
3630
|
+
catch (cleanupErr) {
|
|
3631
|
+
this.log.warn(`update-install: failed to remove backup dir ${backupDir}: ${cleanupErr?.message ?? cleanupErr}`);
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3555
3634
|
// Download via npm pack, extract, and replace extension dir.
|
|
3556
3635
|
// Does NOT touch openclaw.json → no config watcher SIGUSR1.
|
|
3557
3636
|
this.log.info(`update-install: downloading ${packageSpec} via npm pack...`);
|
|
3558
3637
|
node_fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
|
3559
|
-
(0, node_child_process_1.exec)(`npm pack ${packageSpec} --pack-destination ${tmpDir}`, { timeout: 60_000 }, (packErr, packOut) => {
|
|
3638
|
+
(0, node_child_process_1.exec)(`npm pack ${packageSpec} --pack-destination ${tmpDir} --prefer-online`, { timeout: 60_000 }, (packErr, packOut) => {
|
|
3560
3639
|
if (packErr) {
|
|
3561
3640
|
this.log.warn(`update-install: npm pack failed: ${packErr.message}`);
|
|
3562
3641
|
this.jsonResponse(res, { ok: false, error: `Download failed: ${packErr.message}` });
|
|
3563
|
-
|
|
3564
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3565
|
-
}
|
|
3566
|
-
catch { }
|
|
3642
|
+
cleanupTmpDir();
|
|
3567
3643
|
return;
|
|
3568
3644
|
}
|
|
3569
3645
|
const tgzFile = packOut.trim().split("\n").pop();
|
|
@@ -3575,40 +3651,45 @@ class ViewerServer {
|
|
|
3575
3651
|
if (tarErr) {
|
|
3576
3652
|
this.log.warn(`update-install: tar extract failed: ${tarErr.message}`);
|
|
3577
3653
|
this.jsonResponse(res, { ok: false, error: `Extract failed: ${tarErr.message}` });
|
|
3578
|
-
|
|
3579
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3580
|
-
}
|
|
3581
|
-
catch { }
|
|
3654
|
+
cleanupTmpDir();
|
|
3582
3655
|
return;
|
|
3583
3656
|
}
|
|
3584
3657
|
// npm pack extracts to a "package" subdirectory
|
|
3585
3658
|
const srcDir = node_path_1.default.join(extractDir, "package");
|
|
3586
3659
|
if (!node_fs_1.default.existsSync(srcDir)) {
|
|
3587
3660
|
this.jsonResponse(res, { ok: false, error: "Extracted package has no 'package' dir" });
|
|
3588
|
-
|
|
3589
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3590
|
-
}
|
|
3591
|
-
catch { }
|
|
3661
|
+
cleanupTmpDir();
|
|
3592
3662
|
return;
|
|
3593
3663
|
}
|
|
3594
3664
|
// Replace extension directory
|
|
3595
3665
|
this.log.info(`update-install: replacing ${extDir}...`);
|
|
3596
3666
|
try {
|
|
3597
|
-
node_fs_1.default.
|
|
3667
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(extDir), { recursive: true });
|
|
3668
|
+
try {
|
|
3669
|
+
node_fs_1.default.rmSync(backupDir, { recursive: true, force: true });
|
|
3670
|
+
}
|
|
3671
|
+
catch { }
|
|
3672
|
+
if (node_fs_1.default.existsSync(extDir)) {
|
|
3673
|
+
node_fs_1.default.renameSync(extDir, backupDir);
|
|
3674
|
+
backupReady = true;
|
|
3675
|
+
}
|
|
3676
|
+
node_fs_1.default.renameSync(srcDir, extDir);
|
|
3677
|
+
}
|
|
3678
|
+
catch (replaceErr) {
|
|
3679
|
+
this.log.warn(`update-install: replace failed: ${replaceErr?.message ?? replaceErr}`);
|
|
3680
|
+
cleanupTmpDir();
|
|
3681
|
+
rollbackInstall();
|
|
3682
|
+
this.jsonResponse(res, { ok: false, error: `Replace failed: ${replaceErr?.message ?? replaceErr}` });
|
|
3683
|
+
return;
|
|
3598
3684
|
}
|
|
3599
|
-
catch { }
|
|
3600
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(extDir), { recursive: true });
|
|
3601
|
-
node_fs_1.default.renameSync(srcDir, extDir);
|
|
3602
3685
|
// Install dependencies
|
|
3603
3686
|
this.log.info(`update-install: installing dependencies...`);
|
|
3604
3687
|
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3605
3688
|
(0, node_child_process_1.execFile)(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
|
|
3606
3689
|
if (npmErr) {
|
|
3607
|
-
try {
|
|
3608
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3609
|
-
}
|
|
3610
|
-
catch { }
|
|
3611
3690
|
this.log.warn(`update-install: npm install failed: ${npmErr.message}`);
|
|
3691
|
+
cleanupTmpDir();
|
|
3692
|
+
rollbackInstall();
|
|
3612
3693
|
this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
|
|
3613
3694
|
return;
|
|
3614
3695
|
}
|
|
@@ -3621,34 +3702,34 @@ class ViewerServer {
|
|
|
3621
3702
|
}
|
|
3622
3703
|
this.log.info(`update-install: running postinstall...`);
|
|
3623
3704
|
(0, node_child_process_1.execFile)(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
3624
|
-
|
|
3625
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3626
|
-
}
|
|
3627
|
-
catch { }
|
|
3705
|
+
cleanupTmpDir();
|
|
3628
3706
|
if (postErr) {
|
|
3629
3707
|
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
3630
3708
|
const postStderrStr = String(postStderr || "").trim();
|
|
3631
3709
|
if (postStderrStr)
|
|
3632
3710
|
this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
|
|
3711
|
+
rollbackInstall();
|
|
3712
|
+
this.jsonResponse(res, { ok: false, error: `Postinstall failed: ${postStderrStr || postErr.message}` });
|
|
3713
|
+
return;
|
|
3633
3714
|
}
|
|
3634
|
-
// Read new version
|
|
3635
3715
|
let newVersion = "unknown";
|
|
3636
3716
|
try {
|
|
3637
3717
|
const newPkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(extDir, "package.json"), "utf-8"));
|
|
3638
3718
|
newVersion = newPkg.version ?? newVersion;
|
|
3639
3719
|
}
|
|
3640
3720
|
catch { }
|
|
3721
|
+
if (targetVersion && newVersion !== targetVersion) {
|
|
3722
|
+
this.log.warn(`update-install: version mismatch! expected=${targetVersion}, got=${newVersion} — rolling back`);
|
|
3723
|
+
rollbackInstall();
|
|
3724
|
+
this.jsonResponse(res, {
|
|
3725
|
+
ok: false,
|
|
3726
|
+
error: `Version mismatch: expected ${targetVersion} but downloaded ${newVersion}. npm cache may be stale — please try again.`,
|
|
3727
|
+
});
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
discardBackup();
|
|
3641
3731
|
this.log.info(`update-install: success! Updated to ${newVersion}`);
|
|
3642
|
-
this.
|
|
3643
|
-
setTimeout(() => {
|
|
3644
|
-
this.log.info(`update-install: triggering gateway restart via SIGUSR1...`);
|
|
3645
|
-
try {
|
|
3646
|
-
process.kill(process.pid, "SIGUSR1");
|
|
3647
|
-
}
|
|
3648
|
-
catch (sig) {
|
|
3649
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
3650
|
-
}
|
|
3651
|
-
}, 500);
|
|
3732
|
+
this.jsonResponseAndRestart(res, { ok: true, version: newVersion }, "update-install");
|
|
3652
3733
|
});
|
|
3653
3734
|
});
|
|
3654
3735
|
});
|
|
@@ -4223,8 +4304,8 @@ class ViewerServer {
|
|
|
4223
4304
|
mergeCount: 0,
|
|
4224
4305
|
lastHitAt: null,
|
|
4225
4306
|
mergeHistory: "[]",
|
|
4226
|
-
createdAt:
|
|
4227
|
-
updatedAt:
|
|
4307
|
+
createdAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
4308
|
+
updatedAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
4228
4309
|
};
|
|
4229
4310
|
this.store.insertChunk(chunk);
|
|
4230
4311
|
if (embedding && dedupStatus === "active") {
|
|
@@ -4781,6 +4862,20 @@ class ViewerServer {
|
|
|
4781
4862
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
4782
4863
|
req.on("end", () => cb(body));
|
|
4783
4864
|
}
|
|
4865
|
+
jsonResponseAndRestart(res, data, source, delayMs = 1500, statusCode = 200) {
|
|
4866
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
4867
|
+
res.end(JSON.stringify(data), () => {
|
|
4868
|
+
setTimeout(() => {
|
|
4869
|
+
this.log.info(`${source}: triggering gateway restart via SIGUSR1...`);
|
|
4870
|
+
try {
|
|
4871
|
+
process.kill(process.pid, "SIGUSR1");
|
|
4872
|
+
}
|
|
4873
|
+
catch (sig) {
|
|
4874
|
+
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
4875
|
+
}
|
|
4876
|
+
}, delayMs);
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4784
4879
|
jsonResponse(res, data, statusCode = 200) {
|
|
4785
4880
|
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
4786
4881
|
res.end(JSON.stringify(data));
|