memory-braid 0.3.3 → 0.3.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/README.md CHANGED
@@ -20,7 +20,7 @@ On the target machine:
20
20
  1. Install from npm:
21
21
 
22
22
  ```bash
23
- openclaw plugins install memory-braid@0.3.3
23
+ openclaw plugins install memory-braid@0.3.5
24
24
  ```
25
25
 
26
26
  2. Rebuild native dependencies inside the installed extension:
@@ -71,6 +71,7 @@ openclaw gateway restart
71
71
  If you install from npm and see native module errors like:
72
72
 
73
73
  - `Could not locate the bindings file` (sqlite3)
74
+ - `.../node_modules/jiti/.../node_sqlite3.node` in the stack/error text
74
75
  - `Cannot find module ... sharp-*.node`
75
76
 
76
77
  run:
@@ -81,6 +82,14 @@ npm rebuild sqlite3 sharp
81
82
  openclaw gateway restart
82
83
  ```
83
84
 
85
+ Note:
86
+ - The `jiti/.../node_sqlite3.node` error is still a sqlite native artifact/runtime loading issue.
87
+ - `memory-braid` now preloads sqlite via native `require` to avoid that path, but you still need `npm rebuild sqlite3 sharp` after `--ignore-scripts` installs.
88
+ - When this happens, startup logs now include `memory_braid.mem0.error` with:
89
+ - `sqliteBindingsError: true`
90
+ - `fixCommand` (copy/paste command for that machine)
91
+ - `pluginDir` (resolved extension directory when available)
92
+
84
93
  ## Quick start: hybrid capture + multilingual NER
85
94
 
86
95
  Add this under `plugins.entries["memory-braid"].config` in your OpenClaw config:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-braid",
3
- "version": "0.3.3",
3
+ "version": "0.3.6",
4
4
  "description": "OpenClaw memory plugin that augments local memory with Mem0, bootstrap import, reconcile, and capture.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
+ import { createRequire } from "node:module";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { normalizeForHash } from "./chunking.js";
@@ -46,6 +47,13 @@ type OssClientLike = {
46
47
 
47
48
  type OssMemoryCtor = new (config?: Record<string, unknown>) => OssClientLike;
48
49
 
50
+ type LoadedOssModule = {
51
+ moduleValue: unknown;
52
+ loader: "require" | "import";
53
+ mem0Path?: string;
54
+ sqlite3Path?: string;
55
+ };
56
+
49
57
  function extractCloudText(memory: CloudRecord): string {
50
58
  const byData = memory.data?.memory;
51
59
  if (typeof byData === "string" && byData.trim()) {
@@ -111,20 +119,66 @@ function asOssMemoryCtor(value: unknown): OssMemoryCtor | undefined {
111
119
  return value as OssMemoryCtor;
112
120
  }
113
121
 
114
- export function resolveOssMemoryCtor(moduleValue: unknown): OssMemoryCtor | undefined {
115
- if (!moduleValue) {
122
+ function isObjectLike(value: unknown): value is Record<string, unknown> {
123
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
124
+ }
125
+
126
+ function resolveCtorFromCandidate(candidate: unknown, depth = 0): OssMemoryCtor | undefined {
127
+ if (depth > 6 || !candidate) {
116
128
  return undefined;
117
129
  }
118
130
 
119
- const mod = asRecord(moduleValue);
120
- const defaultMod = asRecord(mod.default);
131
+ const direct = asOssMemoryCtor(candidate);
132
+ if (direct) {
133
+ return direct;
134
+ }
135
+
136
+ if (!isObjectLike(candidate)) {
137
+ return undefined;
138
+ }
121
139
 
140
+ const record = candidate as Record<string, unknown>;
122
141
  return (
123
- asOssMemoryCtor(mod.Memory) ??
124
- asOssMemoryCtor(mod.MemoryClient) ??
125
- asOssMemoryCtor(defaultMod.Memory) ??
126
- asOssMemoryCtor(defaultMod.MemoryClient) ??
127
- asOssMemoryCtor(mod.default)
142
+ resolveCtorFromCandidate(record.Memory, depth + 1) ??
143
+ resolveCtorFromCandidate(record.MemoryClient, depth + 1) ??
144
+ resolveCtorFromCandidate(record.default, depth + 1)
145
+ );
146
+ }
147
+
148
+ function asErrorMessage(error: unknown): string {
149
+ if (error instanceof Error) {
150
+ return error.message;
151
+ }
152
+ return String(error);
153
+ }
154
+
155
+ function isSqliteBindingsError(error: unknown): boolean {
156
+ const message = asErrorMessage(error);
157
+ return /Could not locate the bindings file/i.test(message) || /node_sqlite3\.node/i.test(message);
158
+ }
159
+
160
+ function createLocalRequire(): NodeJS.Require {
161
+ return createRequire(import.meta.url);
162
+ }
163
+
164
+ function tryResolve(requireFn: NodeJS.Require, id: string): string | undefined {
165
+ try {
166
+ return requireFn.resolve(id);
167
+ } catch {
168
+ return undefined;
169
+ }
170
+ }
171
+
172
+ function tryRequire(requireFn: NodeJS.Require, id: string): unknown {
173
+ return requireFn(id);
174
+ }
175
+
176
+ export function resolveOssMemoryCtor(moduleValue: unknown): OssMemoryCtor | undefined {
177
+ return (
178
+ resolveCtorFromCandidate(moduleValue) ??
179
+ resolveCtorFromCandidate(asRecord(moduleValue).Memory) ??
180
+ resolveCtorFromCandidate(asRecord(moduleValue).MemoryClient) ??
181
+ resolveCtorFromCandidate(asRecord(moduleValue).default)
128
182
  );
129
183
  }
130
184
 
@@ -282,11 +336,13 @@ export class Mem0Adapter {
282
336
  private ossClient: OssClientLike | null = null;
283
337
  private readonly cfg: MemoryBraidConfig;
284
338
  private readonly log: MemoryBraidLogger;
339
+ private readonly pluginDir?: string;
285
340
  private stateDir?: string;
286
341
 
287
342
  constructor(cfg: MemoryBraidConfig, log: MemoryBraidLogger, options?: Mem0AdapterOptions) {
288
343
  this.cfg = cfg;
289
344
  this.log = log;
345
+ this.pluginDir = this.resolvePluginDir();
290
346
  this.stateDir = options?.stateDir;
291
347
  }
292
348
 
@@ -344,7 +400,7 @@ export class Mem0Adapter {
344
400
  }
345
401
 
346
402
  try {
347
- const mod = await import("mem0ai/oss");
403
+ const { moduleValue: mod, loader, mem0Path, sqlite3Path } = await this.loadOssModule();
348
404
  const Memory = resolveOssMemoryCtor(mod);
349
405
  if (!Memory) {
350
406
  const exportKeys = Object.keys(asRecord(mod));
@@ -366,20 +422,104 @@ export class Mem0Adapter {
366
422
  this.log.debug("memory_braid.mem0.response", {
367
423
  action: "init",
368
424
  mode: "oss",
425
+ loader,
426
+ mem0Path,
427
+ sqlite3Path,
369
428
  hasCustomConfig,
370
429
  sqliteDbPaths: collectSqliteDbPaths(configToUse),
371
430
  }, true);
372
431
  return this.ossClient;
373
432
  } catch (err) {
433
+ const sqliteBindingsError = isSqliteBindingsError(err);
374
434
  this.log.error("memory_braid.mem0.error", {
375
435
  reason: "init_failed",
376
436
  mode: "oss",
437
+ sqliteBindingsError,
438
+ ...(sqliteBindingsError ? this.nativeRebuildHint() : {}),
377
439
  error: err instanceof Error ? err.message : String(err),
378
440
  });
379
441
  return null;
380
442
  }
381
443
  }
382
444
 
445
+ private async loadOssModule(): Promise<LoadedOssModule> {
446
+ const requireFromHere = createLocalRequire();
447
+ const sqlite3Path = tryResolve(requireFromHere, "sqlite3");
448
+ if (sqlite3Path) {
449
+ try {
450
+ tryRequire(requireFromHere, sqlite3Path);
451
+ } catch (error) {
452
+ this.log.warn("memory_braid.mem0.error", {
453
+ reason: "sqlite3_preload_failed",
454
+ mode: "oss",
455
+ ...this.nativeRebuildHint(),
456
+ sqlite3Path,
457
+ error: asErrorMessage(error),
458
+ });
459
+ }
460
+ }
461
+
462
+ const mem0Path = tryResolve(requireFromHere, "mem0ai/oss");
463
+ if (mem0Path) {
464
+ try {
465
+ const required = tryRequire(requireFromHere, mem0Path);
466
+ return {
467
+ moduleValue: required,
468
+ loader: "require",
469
+ mem0Path,
470
+ sqlite3Path,
471
+ };
472
+ } catch (error) {
473
+ const sqliteBindingsError = isSqliteBindingsError(error);
474
+ this.log.warn("memory_braid.mem0.error", {
475
+ reason: "oss_require_failed",
476
+ mode: "oss",
477
+ mem0Path,
478
+ sqlite3Path,
479
+ sqliteBindingsError,
480
+ ...(sqliteBindingsError ? this.nativeRebuildHint() : {}),
481
+ error: asErrorMessage(error),
482
+ });
483
+ }
484
+ }
485
+
486
+ const imported = await import("mem0ai/oss");
487
+ return {
488
+ moduleValue: imported,
489
+ loader: "import",
490
+ mem0Path,
491
+ sqlite3Path,
492
+ };
493
+ }
494
+
495
+ private resolvePluginDir(): string | undefined {
496
+ const requireFromHere = createLocalRequire();
497
+ const packageJsonPath = tryResolve(requireFromHere, "../package.json");
498
+ if (!packageJsonPath) {
499
+ return undefined;
500
+ }
501
+ return path.dirname(packageJsonPath);
502
+ }
503
+
504
+ private nativeRebuildHint(): {
505
+ pluginDir?: string;
506
+ fixCommand: string;
507
+ why: string;
508
+ } {
509
+ if (this.pluginDir) {
510
+ return {
511
+ pluginDir: this.pluginDir,
512
+ fixCommand: `cd "${this.pluginDir}" && npm rebuild sqlite3 sharp && openclaw gateway restart`,
513
+ why: "OpenClaw plugin installs use --ignore-scripts, so sqlite3/sharp native artifacts may be missing after install/update.",
514
+ };
515
+ }
516
+
517
+ return {
518
+ fixCommand: "cd ~/.openclaw/extensions/memory-braid && npm rebuild sqlite3 sharp && openclaw gateway restart",
519
+ why: "OpenClaw plugin installs use --ignore-scripts, so sqlite3/sharp native artifacts may be missing after install/update.",
520
+ };
521
+ }
522
+
383
523
  async ensureClient(): Promise<{ mode: "cloud" | "oss"; client: CloudClientLike | OssClientLike } | null> {
384
524
  if (this.cfg.mem0.mode === "oss") {
385
525
  const client = await this.ensureOssClient();