@memtensor/memos-local-openclaw-plugin 1.0.6-beta.3 → 1.0.6-beta.5

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.
@@ -3,7 +3,7 @@
3
3
  "name": "MemOS Local Memory",
4
4
  "description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency), task summarization, skill evolution, and team sharing (Hub-Client). Provides memory_search, memory_get, task_summary, skill_search, task_share, network_skill_pull, network_team_info, memory_viewer for layered retrieval and team collaboration.",
5
5
  "kind": "memory",
6
- "version": "0.1.12",
6
+ "version": "1.0.6-beta.5",
7
7
  "skills": [
8
8
  "skill/memos-memory-guide"
9
9
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memtensor/memos-local-openclaw-plugin",
3
- "version": "1.0.6-beta.3",
3
+ "version": "1.0.6-beta.5",
4
4
  "description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -11,6 +11,7 @@
11
11
  "dist",
12
12
  "skill",
13
13
  "prebuilds",
14
+ "scripts/native-binding.cjs",
14
15
  "scripts/postinstall.cjs",
15
16
  "openclaw.plugin.json",
16
17
  "telemetry.credentials.json",
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ function errorMessage(error) {
4
+ if (error && typeof error.message === "string") return error.message;
5
+ return String(error || "Unknown native binding error");
6
+ }
7
+
8
+ function defaultLoadBinding(bindingPath) {
9
+ process.dlopen({ exports: {} }, bindingPath);
10
+ }
11
+
12
+ function validateNativeBinding(bindingPath, loadBinding = defaultLoadBinding) {
13
+ if (!bindingPath) {
14
+ return { ok: false, reason: "missing", message: "Native binding path not found" };
15
+ }
16
+
17
+ try {
18
+ loadBinding(bindingPath);
19
+ return { ok: true, reason: "ok", message: "" };
20
+ } catch (error) {
21
+ const message = errorMessage(error);
22
+ if (/NODE_MODULE_VERSION/.test(message)) {
23
+ return { ok: false, reason: "node-module-version", message };
24
+ }
25
+ return { ok: false, reason: "load-error", message };
26
+ }
27
+ }
28
+
29
+ module.exports = {
30
+ defaultLoadBinding,
31
+ validateNativeBinding,
32
+ };
@@ -4,6 +4,7 @@
4
4
  const { spawnSync } = require("child_process");
5
5
  const path = require("path");
6
6
  const fs = require("fs");
7
+ const { validateNativeBinding } = require("./native-binding.cjs");
7
8
 
8
9
  const RESET = "\x1b[0m";
9
10
  const GREEN = "\x1b[32m";
@@ -377,35 +378,31 @@ function findSqliteBinding() {
377
378
 
378
379
  function sqliteBindingsExist() {
379
380
  const found = findSqliteBinding();
380
- if (found) {
381
- log(`Native binding found: ${DIM}${found}${RESET}`);
382
- return true;
381
+ if (!found) return false;
382
+ log(`Native binding found: ${DIM}${found}${RESET}`);
383
+ const status = validateNativeBinding(found);
384
+ if (status.ok) return true;
385
+ if (status.reason === "node-module-version") {
386
+ warn("Native binding exists but was compiled for a different Node.js version.");
387
+ } else {
388
+ warn("Native binding exists but failed to load.");
383
389
  }
390
+ warn(`${DIM}${status.message}${RESET}`);
384
391
  return false;
385
392
  }
386
393
 
387
394
  if (sqliteBindingsExist()) {
388
395
  ok("better-sqlite3 is ready.");
389
396
  } else {
390
- warn("better-sqlite3 native bindings not found in plugin dir.");
397
+ warn("better-sqlite3 native bindings are missing or not loadable.");
391
398
  log(`Searched in: ${DIM}${sqliteModulePath}/build/${RESET}`);
392
399
  log("Running: npm rebuild better-sqlite3 (may take 30-60s)...");
393
- }
394
-
395
- const startMs = Date.now();
396
-
397
- const result = spawnSync(npmCmd, ["rebuild", "better-sqlite3"], {
398
- cwd: pluginDir,
399
- stdio: "pipe",
400
- shell: false,
401
- timeout: 180_000,
402
- });
403
400
 
404
401
  const startMs = Date.now();
405
- const result = spawnSync("npm", ["rebuild", "better-sqlite3"], {
402
+ const result = spawnSync(npmCmd, ["rebuild", "better-sqlite3"], {
406
403
  cwd: pluginDir,
407
404
  stdio: "pipe",
408
- shell: true,
405
+ shell: false,
409
406
  timeout: 180_000,
410
407
  });
411
408
  const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
@@ -3356,6 +3356,32 @@ export class ViewerServer {
3356
3356
  const shortName = pluginName?.replace(/^@[\w-]+\//, "") ?? "memos-local-openclaw-plugin";
3357
3357
  const extDir = path.join(os.homedir(), ".openclaw", "extensions", shortName);
3358
3358
  const tmpDir = path.join(os.tmpdir(), `openclaw-update-${Date.now()}`);
3359
+ const backupDir = path.join(path.dirname(extDir), `${shortName}.backup-${Date.now()}`);
3360
+ let backupReady = false;
3361
+
3362
+ const cleanupTmpDir = () => {
3363
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3364
+ };
3365
+ const rollbackInstall = () => {
3366
+ try { fs.rmSync(extDir, { recursive: true, force: true }); } catch {}
3367
+ if (!backupReady) return;
3368
+ try {
3369
+ fs.renameSync(backupDir, extDir);
3370
+ backupReady = false;
3371
+ this.log.info(`update-install: restored previous version from ${backupDir}`);
3372
+ } catch (restoreErr: any) {
3373
+ this.log.warn(`update-install: failed to restore previous version: ${restoreErr?.message ?? restoreErr}`);
3374
+ }
3375
+ };
3376
+ const discardBackup = () => {
3377
+ if (!backupReady) return;
3378
+ try {
3379
+ fs.rmSync(backupDir, { recursive: true, force: true });
3380
+ backupReady = false;
3381
+ } catch (cleanupErr: any) {
3382
+ this.log.warn(`update-install: failed to remove backup dir ${backupDir}: ${cleanupErr?.message ?? cleanupErr}`);
3383
+ }
3384
+ };
3359
3385
 
3360
3386
  // Download via npm pack, extract, and replace extension dir.
3361
3387
  // Does NOT touch openclaw.json → no config watcher SIGUSR1.
@@ -3365,7 +3391,7 @@ export class ViewerServer {
3365
3391
  if (packErr) {
3366
3392
  this.log.warn(`update-install: npm pack failed: ${packErr.message}`);
3367
3393
  this.jsonResponse(res, { ok: false, error: `Download failed: ${packErr.message}` });
3368
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3394
+ cleanupTmpDir();
3369
3395
  return;
3370
3396
  }
3371
3397
  const tgzFile = packOut.trim().split("\n").pop()!;
@@ -3378,7 +3404,7 @@ export class ViewerServer {
3378
3404
  if (tarErr) {
3379
3405
  this.log.warn(`update-install: tar extract failed: ${tarErr.message}`);
3380
3406
  this.jsonResponse(res, { ok: false, error: `Extract failed: ${tarErr.message}` });
3381
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3407
+ cleanupTmpDir();
3382
3408
  return;
3383
3409
  }
3384
3410
 
@@ -3386,23 +3412,36 @@ export class ViewerServer {
3386
3412
  const srcDir = path.join(extractDir, "package");
3387
3413
  if (!fs.existsSync(srcDir)) {
3388
3414
  this.jsonResponse(res, { ok: false, error: "Extracted package has no 'package' dir" });
3389
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3415
+ cleanupTmpDir();
3390
3416
  return;
3391
3417
  }
3392
3418
 
3393
3419
  // Replace extension directory
3394
3420
  this.log.info(`update-install: replacing ${extDir}...`);
3395
- try { fs.rmSync(extDir, { recursive: true, force: true }); } catch {}
3396
- fs.mkdirSync(path.dirname(extDir), { recursive: true });
3397
- fs.renameSync(srcDir, extDir);
3421
+ try {
3422
+ fs.mkdirSync(path.dirname(extDir), { recursive: true });
3423
+ try { fs.rmSync(backupDir, { recursive: true, force: true }); } catch {}
3424
+ if (fs.existsSync(extDir)) {
3425
+ fs.renameSync(extDir, backupDir);
3426
+ backupReady = true;
3427
+ }
3428
+ fs.renameSync(srcDir, extDir);
3429
+ } catch (replaceErr: any) {
3430
+ this.log.warn(`update-install: replace failed: ${replaceErr?.message ?? replaceErr}`);
3431
+ cleanupTmpDir();
3432
+ rollbackInstall();
3433
+ this.jsonResponse(res, { ok: false, error: `Replace failed: ${replaceErr?.message ?? replaceErr}` });
3434
+ return;
3435
+ }
3398
3436
 
3399
3437
  // Install dependencies
3400
3438
  this.log.info(`update-install: installing dependencies...`);
3401
3439
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
3402
3440
  execFile(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
3403
3441
  if (npmErr) {
3404
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3405
3442
  this.log.warn(`update-install: npm install failed: ${npmErr.message}`);
3443
+ cleanupTmpDir();
3444
+ rollbackInstall();
3406
3445
  this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
3407
3446
  return;
3408
3447
  }
@@ -3416,12 +3455,15 @@ export class ViewerServer {
3416
3455
 
3417
3456
  this.log.info(`update-install: running postinstall...`);
3418
3457
  execFile(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
3419
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
3458
+ cleanupTmpDir();
3420
3459
 
3421
3460
  if (postErr) {
3422
3461
  this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
3423
3462
  const postStderrStr = String(postStderr || "").trim();
3424
3463
  if (postStderrStr) this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
3464
+ rollbackInstall();
3465
+ this.jsonResponse(res, { ok: false, error: `Postinstall failed: ${postStderrStr || postErr.message}` });
3466
+ return;
3425
3467
  }
3426
3468
 
3427
3469
  // Read new version
@@ -3431,6 +3473,7 @@ export class ViewerServer {
3431
3473
  newVersion = newPkg.version ?? newVersion;
3432
3474
  } catch {}
3433
3475
 
3476
+ discardBackup();
3434
3477
  this.log.info(`update-install: success! Updated to ${newVersion}`);
3435
3478
  this.jsonResponse(res, { ok: true, version: newVersion });
3436
3479