@xdarkicex/openclaw-memory-libravdb 1.3.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.
Files changed (80) hide show
  1. package/README.md +46 -0
  2. package/docs/README.md +14 -0
  3. package/docs/architecture-decisions/README.md +6 -0
  4. package/docs/architecture-decisions/adr-001-onnx-over-ollama.md +21 -0
  5. package/docs/architecture-decisions/adr-002-libravdb-over-lancedb.md +19 -0
  6. package/docs/architecture-decisions/adr-003-convex-gating-over-threshold.md +27 -0
  7. package/docs/architecture-decisions/adr-004-sidecar-over-native-ts.md +21 -0
  8. package/docs/architecture.md +188 -0
  9. package/docs/contributing.md +76 -0
  10. package/docs/dependencies.md +38 -0
  11. package/docs/embedding-profiles.md +42 -0
  12. package/docs/gating.md +329 -0
  13. package/docs/implementation.md +381 -0
  14. package/docs/installation.md +272 -0
  15. package/docs/mathematics.md +695 -0
  16. package/docs/models.md +63 -0
  17. package/docs/problem.md +64 -0
  18. package/docs/security.md +86 -0
  19. package/openclaw.plugin.json +84 -0
  20. package/package.json +41 -0
  21. package/scripts/build-sidecar.sh +30 -0
  22. package/scripts/postinstall.js +169 -0
  23. package/scripts/setup.sh +20 -0
  24. package/scripts/setup.ts +505 -0
  25. package/scripts/sidecar-release.d.ts +4 -0
  26. package/scripts/sidecar-release.js +17 -0
  27. package/sidecar/cmd/inspect_onnx/main.go +105 -0
  28. package/sidecar/compact/gate.go +273 -0
  29. package/sidecar/compact/gate_test.go +85 -0
  30. package/sidecar/compact/summarize.go +345 -0
  31. package/sidecar/compact/summarize_test.go +319 -0
  32. package/sidecar/compact/tokens.go +11 -0
  33. package/sidecar/config/config.go +119 -0
  34. package/sidecar/config/config_test.go +75 -0
  35. package/sidecar/embed/engine.go +696 -0
  36. package/sidecar/embed/engine_test.go +349 -0
  37. package/sidecar/embed/matryoshka.go +93 -0
  38. package/sidecar/embed/matryoshka_test.go +150 -0
  39. package/sidecar/embed/onnx_local.go +319 -0
  40. package/sidecar/embed/onnx_local_test.go +159 -0
  41. package/sidecar/embed/profile_contract_test.go +71 -0
  42. package/sidecar/embed/profile_eval_test.go +923 -0
  43. package/sidecar/embed/profiles.go +39 -0
  44. package/sidecar/go.mod +21 -0
  45. package/sidecar/go.sum +30 -0
  46. package/sidecar/health/check.go +33 -0
  47. package/sidecar/health/check_test.go +55 -0
  48. package/sidecar/main.go +151 -0
  49. package/sidecar/model/encoder.go +222 -0
  50. package/sidecar/model/registry.go +262 -0
  51. package/sidecar/model/registry_test.go +102 -0
  52. package/sidecar/model/seq2seq.go +133 -0
  53. package/sidecar/server/rpc.go +343 -0
  54. package/sidecar/server/rpc_test.go +350 -0
  55. package/sidecar/server/transport.go +160 -0
  56. package/sidecar/store/libravdb.go +676 -0
  57. package/sidecar/store/libravdb_test.go +472 -0
  58. package/sidecar/summarize/engine.go +360 -0
  59. package/sidecar/summarize/engine_test.go +148 -0
  60. package/sidecar/summarize/onnx_local.go +494 -0
  61. package/sidecar/summarize/onnx_local_test.go +48 -0
  62. package/sidecar/summarize/profiles.go +52 -0
  63. package/sidecar/summarize/tokenizer.go +13 -0
  64. package/sidecar/summarize/tokenizer_hf.go +76 -0
  65. package/sidecar/summarize/util.go +13 -0
  66. package/src/cli.ts +205 -0
  67. package/src/context-engine.ts +195 -0
  68. package/src/index.ts +27 -0
  69. package/src/memory-provider.ts +24 -0
  70. package/src/openclaw-plugin-sdk.d.ts +53 -0
  71. package/src/plugin-runtime.ts +67 -0
  72. package/src/recall-cache.ts +34 -0
  73. package/src/recall-utils.ts +22 -0
  74. package/src/rpc.ts +84 -0
  75. package/src/scoring.ts +58 -0
  76. package/src/sidecar.ts +506 -0
  77. package/src/tokens.ts +36 -0
  78. package/src/types.ts +146 -0
  79. package/tsconfig.json +20 -0
  80. package/tsconfig.tests.json +12 -0
package/docs/models.md ADDED
@@ -0,0 +1,63 @@
1
+ # Model Strategy
2
+
3
+ ## Why ONNX Over Ollama
4
+
5
+ The plugin uses ONNX-first local inference for embedding and optional abstractive summarization.
6
+
7
+ ### Latency
8
+
9
+ `assemble` is on the critical path before every response build. An embedding request that crosses process and HTTP boundaries adds avoidable tail latency. Local ONNX inference inside the sidecar keeps the retrieval path in the low-millisecond range on the target hardware profile.
10
+ `assemble` is on the critical path before every response build. An embedding
11
+ request that crosses process and HTTP boundaries adds avoidable tail latency.
12
+ Local ONNX inference inside the sidecar keeps the retrieval path local and
13
+ predictable. On the current Apple M2 development machine, the repository's own
14
+ benchmark harness measures roughly `16-23 ms/op` for MiniLM query embeddings and
15
+ about `44 ms/op` for Nomic in the steady-state Go benchmark path.
16
+
17
+ ### Offline Operation
18
+
19
+ The plugin is designed to be local-first. Requiring a running Ollama server would break that guarantee. ONNX assets can be provisioned once and reused without network or daemon availability.
20
+
21
+ ### Determinism
22
+
23
+ ONNX inference is deterministic given fixed weights and input. Deterministic embeddings give stable similarity ordering and reproducible retrieval behavior.
24
+
25
+ ### Binary Size Trade-Off
26
+
27
+ Local models increase the artifact footprint. That is an explicit trade-off accepted by the architecture because predictable latency and offline operation are more important for this plugin than minimal package size.
28
+
29
+ ## Why `nomic-embed-text-v1.5`
30
+
31
+ This is the default embedding profile because it earned the role on two axes:
32
+
33
+ - long-context document support
34
+ - Matryoshka structure for tiered retrieval
35
+
36
+ The model’s Matryoshka training is what makes the `64d -> 256d -> 768d` cascade principled rather than arbitrary truncation.
37
+
38
+ ## Why `all-minilm-l6-v2` Still Exists
39
+
40
+ MiniLM remains the lightweight fallback profile. It is useful when:
41
+
42
+ - the full Nomic profile is unavailable
43
+ - a smaller bundled footprint matters more than long-context or Matryoshka behavior
44
+
45
+ It is no longer the quality-first default.
46
+
47
+ ## Why T5-small for Summarization
48
+
49
+ The abstractive summarization path is optional and must remain CPU-feasible on local machines. T5-small fits that constraint better than larger generative models:
50
+
51
+ - small enough to run locally
52
+ - expressive enough for session-cluster summarization
53
+ - does not require a remote server
54
+
55
+ The plugin still degrades gracefully to extractive compaction when the T5 assets are not provisioned.
56
+
57
+ ## Model Roles in the System
58
+
59
+ - Nomic embedder: quality-first retrieval path, Matryoshka tiers
60
+ - MiniLM: fallback embedder
61
+ - T5-small: optional higher-quality compaction summarizer
62
+
63
+ The model strategy is therefore not “use ONNX everywhere because ONNX is fashionable.” It is “use ONNX where local deterministic inference is part of the product contract.”
@@ -0,0 +1,64 @@
1
+ # The Problem This Plugin Solves
2
+
3
+ This plugin exists because the stock OpenClaw memory path is optimized for lightweight persistence, not for a full context lifecycle with scope separation, compaction, and bounded retrieval.
4
+
5
+ ## What the Stock Plugin Is Good At
6
+
7
+ The default `memory-lancedb` style plugin is still a good fit when you want:
8
+
9
+ - a simple top-k semantic lookup over durable notes
10
+ - a low-complexity setup
11
+ - a heuristic capture model where the agent decides what to save
12
+ - no additional sidecar process
13
+
14
+ For short sessions and light persistent memory, that is often the right answer.
15
+
16
+ ## Where the Stock Model Breaks Down
17
+
18
+ The workload this plugin targets is different:
19
+
20
+ - long technical sessions
21
+ - repeated workflow state that must not be lost
22
+ - user/session/global scopes that behave differently
23
+ - compaction instead of unbounded append-only growth
24
+ - retrieval that must fit within a prompt token budget
25
+
26
+ The failure modes are structural, not cosmetic.
27
+
28
+ ### Context collapse in long sessions
29
+
30
+ A single-table top-k memory system has no first-class notion of ephemeral session state versus durable user memory. A long active session accumulates turns that are highly relevant right now but should not pollute long-term recall forever.
31
+
32
+ ### No scope separation
33
+
34
+ This plugin uses distinct namespaces for:
35
+
36
+ - `session:<sessionId>`
37
+ - `turns:<userId>`
38
+ - `user:<userId>`
39
+ - `global`
40
+
41
+ Those scopes are not interchangeable. Session state needs different retention, different recency behavior, and different retrieval weighting than durable user memory.
42
+
43
+ ### No token budget management
44
+
45
+ Retrieval is only half the problem. The selected memories must still fit inside a bounded prompt budget. This plugin treats ranking and packing as one pipeline, not as disconnected concerns.
46
+
47
+ ### No automatic compaction
48
+
49
+ Raw turns are not the right long-term storage format. The plugin clusters session turns, summarizes them, removes source turns after confirmed summary insertion, and lets summary confidence affect later retrieval. Without compaction, memory becomes an append-only dump that eventually competes with itself.
50
+
51
+ ## Why This Is a Lifecycle Replacement
52
+
53
+ The solution is not a different LanceDB parameter or a larger `topK`.
54
+
55
+ The plugin replaces the whole context lifecycle:
56
+
57
+ 1. ingestion
58
+ 2. gating and promotion
59
+ 3. retrieval and ranking
60
+ 4. token-budget fitting
61
+ 5. compaction and summarization
62
+ 6. quality feedback into future retrieval
63
+
64
+ That is why the plugin is a slot-level memory replacement instead of a helper library layered beside the stock plugin.
@@ -0,0 +1,86 @@
1
+ # Security Model
2
+
3
+ This plugin treats recalled memory as untrusted historical context. That is a structural design rule, not a prompt-style suggestion.
4
+
5
+ ## Ingestion Security Layers
6
+
7
+ The practical pipeline is:
8
+
9
+ 1. collection scoping
10
+ 2. metadata tagging
11
+ 3. bounded retrieval
12
+ 4. untrusted-memory framing
13
+ 5. host fallback when memory is unavailable
14
+
15
+ The system is designed so a failure in one layer does not automatically collapse the others.
16
+
17
+ ## Supply Chain and Installer Trust Boundary
18
+
19
+ This repository uses both `postinstall` and `openclaw.setup`. That is a real
20
+ security-sensitive surface in the OpenClaw ecosystem and should be evaluated
21
+ explicitly rather than hand-waved away.
22
+
23
+ Current implementation facts:
24
+
25
+ - [`scripts/postinstall.js`](../scripts/postinstall.js) installs the sidecar
26
+ binary using a prebuilt-first strategy with a local Go fallback
27
+ - [`scripts/setup.ts`](../scripts/setup.ts) provisions model/runtime assets and
28
+ verifies them before they are accepted
29
+ - required downloaded assets are SHA-256 checked before use
30
+ - an asset that exists but fails verification is deleted and re-downloaded
31
+
32
+ The current installer fetches from these classes of sources only:
33
+
34
+ - GitHub release assets for prebuilt sidecar binaries
35
+ - ONNX Runtime release assets
36
+ - model artifacts explicitly referenced in `setup.ts`
37
+
38
+ After installation, the plugin is local-first:
39
+
40
+ - no required network calls are made for embedding
41
+ - no required network calls are made for extractive compaction
42
+ - the only optional runtime network path is an explicitly configured external
43
+ summarizer endpoint, such as an Ollama server
44
+
45
+ That trust boundary matters because it is exactly the area security-conscious
46
+ users will inspect first.
47
+
48
+ ## Untrusted-Memory Framing
49
+
50
+ Retrieved memory is injected with framing that explicitly tells the downstream model to treat it as untrusted historical context only.
51
+
52
+ This matters because memory is persistent user-controlled content. Without structural framing, a stored memory can become an unintended prompt injection surface.
53
+
54
+ The framing is implemented in the host-side memory header builder and is applied consistently at assembly time.
55
+
56
+ ## Collection Isolation
57
+
58
+ The plugin structurally separates:
59
+
60
+ - session memory
61
+ - raw turn history
62
+ - durable user memory
63
+ - global memory
64
+
65
+ Cross-user leakage is prevented by collection naming and lookup boundaries. The gate, compaction, and retrieval code all operate on explicit scope-qualified collection names rather than a shared unscoped table.
66
+
67
+ ## What the Plugin Cannot Protect Against
68
+
69
+ The plugin does not claim to protect against:
70
+
71
+ - a compromised host process
72
+ - a compromised local machine
73
+ - a downstream model that ignores the untrusted-memory framing instruction
74
+ - intentionally malicious content stored by an already-authorized local actor
75
+
76
+ It reduces risk; it does not create a trusted execution environment.
77
+
78
+ ## Deletion and Data Protection
79
+
80
+ The sidecar exposes deletion and flush primitives. That matters operationally for:
81
+
82
+ - user-requested memory removal
83
+ - namespace cleanup
84
+ - compaction source-turn deletion
85
+
86
+ The GDPR-relevant boundary is simple: local stored memory can be deleted by namespace. The plugin does not by itself guarantee remote erasure from any external system because the architecture is intentionally local-first.
@@ -0,0 +1,84 @@
1
+ {
2
+ "id": "libravdb-memory",
3
+ "name": "LibraVDB Memory",
4
+ "description": "Persistent vector memory with three-tier hybrid scoring",
5
+ "version": "1.3.3",
6
+ "kind": "memory",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "dbPath": { "type": "string" },
12
+ "sidecarPath": { "type": "string" },
13
+ "embeddingRuntimePath": { "type": "string" },
14
+ "embeddingBackend": { "type": "string", "enum": ["bundled", "onnx-local", "custom-local"] },
15
+ "embeddingProfile": {
16
+ "type": "string",
17
+ "default": "nomic-embed-text-v1.5"
18
+ },
19
+ "fallbackProfile": {
20
+ "type": "string",
21
+ "default": "all-minilm-l6-v2"
22
+ },
23
+ "embeddingModelPath": { "type": "string" },
24
+ "embeddingTokenizerPath": { "type": "string" },
25
+ "embeddingDimensions": { "type": "number" },
26
+ "embeddingNormalize": { "type": "boolean" },
27
+ "summarizerBackend": { "type": "string", "enum": ["bundled", "onnx-local", "ollama-local", "custom-local"] },
28
+ "summarizerProfile": { "type": "string" },
29
+ "summarizerRuntimePath": { "type": "string" },
30
+ "summarizerModelPath": { "type": "string" },
31
+ "summarizerTokenizerPath": { "type": "string" },
32
+ "summarizerModel": { "type": "string" },
33
+ "summarizerEndpoint": { "type": "string" },
34
+ "sessionTTL": { "type": "number" },
35
+ "topK": { "type": "number" },
36
+ "alpha": { "type": "number" },
37
+ "beta": { "type": "number" },
38
+ "gamma": { "type": "number" },
39
+ "ingestionGateThreshold": {
40
+ "type": "number",
41
+ "default": 0.35
42
+ },
43
+ "gatingWeights": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "default": {
47
+ "w1c": 0.35, "w2c": 0.40, "w3c": 0.25,
48
+ "w1t": 0.40, "w2t": 0.35, "w3t": 0.25
49
+ },
50
+ "properties": {
51
+ "w1c": { "type": "number", "default": 0.35 },
52
+ "w2c": { "type": "number", "default": 0.40 },
53
+ "w3c": { "type": "number", "default": 0.25 },
54
+ "w1t": { "type": "number", "default": 0.40 },
55
+ "w2t": { "type": "number", "default": 0.35 },
56
+ "w3t": { "type": "number", "default": 0.25 }
57
+ }
58
+ },
59
+ "gatingTechNorm": {
60
+ "type": "number",
61
+ "default": 1.5
62
+ },
63
+ "gatingCentroidK": {
64
+ "type": "number",
65
+ "default": 10
66
+ },
67
+ "compactionQualityWeight": {
68
+ "type": "number",
69
+ "default": 0.5,
70
+ "description": "Controls how much summary confidence affects retrieval score. 0 ignores summary quality and 1 fully suppresses zero-confidence summaries."
71
+ },
72
+ "recencyLambdaSession": { "type": "number" },
73
+ "recencyLambdaUser": { "type": "number" },
74
+ "recencyLambdaGlobal": { "type": "number" },
75
+ "tokenBudgetFraction": { "type": "number" },
76
+ "compactThreshold": { "type": "number" },
77
+ "ollamaUrl": { "type": "string" },
78
+ "compactModel": { "type": "string" },
79
+ "rpcTimeoutMs": { "type": "number", "default": 30000 },
80
+ "maxRetries": { "type": "number" },
81
+ "logLevel": { "type": "string" }
82
+ }
83
+ }
84
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@xdarkicex/openclaw-memory-libravdb",
3
+ "version": "1.3.5",
4
+ "type": "module",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "files": [
9
+ "README.md",
10
+ "openclaw.plugin.json",
11
+ "package.json",
12
+ "src/",
13
+ "scripts/",
14
+ "sidecar/",
15
+ "docs/",
16
+ "tsconfig.json",
17
+ "tsconfig.tests.json"
18
+ ],
19
+ "engines": {
20
+ "node": ">=22"
21
+ },
22
+ "openclaw": {
23
+ "extensions": [
24
+ "./src/index.ts"
25
+ ],
26
+ "setup": "./scripts/setup.ts"
27
+ },
28
+ "scripts": {
29
+ "check": "./.ts-toolchain/node_modules/.bin/tsc --noEmit && pnpm run test:ts",
30
+ "test:ts": "./.ts-toolchain/node_modules/.bin/tsc -p tsconfig.tests.json && node --test .ts-build/test/unit/*.test.js",
31
+ "test:integration": "./.ts-toolchain/node_modules/.bin/tsc -p tsconfig.tests.json && node --test .ts-build/test/integration/checklist-validation.test.js .ts-build/test/integration/host-flow.test.js .ts-build/test/integration/sidecar-lifecycle.test.js",
32
+ "postinstall": "node scripts/postinstall.js",
33
+ "build:sidecar": "bash scripts/build-sidecar.sh"
34
+ },
35
+ "dependencies": {
36
+ "openclaw": "*"
37
+ },
38
+ "peerDependencies": {
39
+ "openclaw": ">=2026.3.22"
40
+ }
41
+ }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ OUT_DIR="$ROOT_DIR/.sidecar-bin"
6
+ BIN_NAME="libravdb-sidecar"
7
+ MODELS_DIR="$ROOT_DIR/.models"
8
+ OUT_MODELS_DIR="$OUT_DIR/models"
9
+ OUT_RUNTIME_DIR="$OUT_DIR/onnxruntime"
10
+
11
+ if [[ "${OS:-}" == "Windows_NT" ]]; then
12
+ BIN_NAME="libravdb-sidecar.exe"
13
+ fi
14
+
15
+ mkdir -p "$OUT_DIR"
16
+ cd "$ROOT_DIR/sidecar"
17
+ GOCACHE="${GOCACHE:-/tmp/openclaw-memory-libravdb-gocache}" go build -o "$OUT_DIR/$BIN_NAME" .
18
+ rm -rf "$OUT_MODELS_DIR" "$OUT_RUNTIME_DIR"
19
+ mkdir -p "$OUT_MODELS_DIR"
20
+ if [[ -d "$MODELS_DIR/all-minilm-l6-v2" ]]; then
21
+ cp -R "$MODELS_DIR/all-minilm-l6-v2" "$OUT_MODELS_DIR/all-minilm-l6-v2"
22
+ fi
23
+ if [[ -d "$MODELS_DIR/nomic-embed-text-v1.5" ]]; then
24
+ cp -R "$MODELS_DIR/nomic-embed-text-v1.5" "$OUT_MODELS_DIR/nomic-embed-text-v1.5"
25
+ fi
26
+ if [[ -d "$MODELS_DIR/onnxruntime" ]]; then
27
+ mkdir -p "$OUT_RUNTIME_DIR"
28
+ cp -R "$MODELS_DIR/onnxruntime/." "$OUT_RUNTIME_DIR/"
29
+ fi
30
+ echo "built sidecar: $OUT_DIR/$BIN_NAME"
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chmodSync, cpSync, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { createHash } from "node:crypto";
7
+ import { spawnSync } from "node:child_process";
8
+ import { Readable } from "node:stream";
9
+ import { pipeline } from "node:stream/promises";
10
+ import { fileURLToPath } from "node:url";
11
+ import { buildSidecarReleaseAssetURL, detectSidecarReleaseTarget } from "./sidecar-release.js";
12
+
13
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
+ const sidecarDir = path.join(root, "sidecar");
15
+ const outDir = path.join(root, ".sidecar-bin");
16
+ const binary = process.platform === "win32" ? "libravdb-sidecar.exe" : "libravdb-sidecar";
17
+ const modelsDir = path.join(root, ".models");
18
+ const outModelsDir = path.join(outDir, "models");
19
+ const outRuntimeDir = path.join(outDir, "onnxruntime");
20
+ const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
21
+
22
+ mkdirSync(outDir, { recursive: true });
23
+
24
+ const installed = await installSidecar(pkg.version);
25
+ if (!installed) {
26
+ console.error("[openclaw-memory-libravdb] FATAL: sidecar binary could not be installed.");
27
+ console.error(" 1. Check your internet connection (prebuilt download failed)");
28
+ console.error(" 2. Or install Go >= 1.22 and retry: https://go.dev/dl/");
29
+ process.exit(1);
30
+ }
31
+
32
+ rmSync(outModelsDir, { recursive: true, force: true });
33
+ rmSync(outRuntimeDir, { recursive: true, force: true });
34
+ mkdirSync(outModelsDir, { recursive: true });
35
+
36
+ const bundledMiniLM = path.join(modelsDir, "all-minilm-l6-v2");
37
+ if (existsSync(bundledMiniLM)) {
38
+ cpSync(bundledMiniLM, path.join(outModelsDir, "all-minilm-l6-v2"), { recursive: true });
39
+ }
40
+
41
+ const bundledNomic = path.join(modelsDir, "nomic-embed-text-v1.5");
42
+ if (existsSync(bundledNomic)) {
43
+ cpSync(bundledNomic, path.join(outModelsDir, "nomic-embed-text-v1.5"), { recursive: true });
44
+ }
45
+
46
+ const runtimeBundle = path.join(modelsDir, "onnxruntime");
47
+ if (existsSync(runtimeBundle)) {
48
+ cpSync(runtimeBundle, outRuntimeDir, { recursive: true });
49
+ }
50
+
51
+ async function installSidecar(version) {
52
+ const target = detectSidecarReleaseTarget();
53
+ if (target) {
54
+ const assetUrl = buildSidecarReleaseAssetURL(version, target);
55
+ const checksumUrl = `${assetUrl}.sha256`;
56
+ const downloaded = await tryDownloadPrebuilt(assetUrl, checksumUrl, path.join(outDir, binary));
57
+ if (downloaded) {
58
+ console.log(`[openclaw-memory-libravdb] Sidecar installed (prebuilt ${target})`);
59
+ return true;
60
+ }
61
+ console.warn("[openclaw-memory-libravdb] Prebuilt binary unavailable or failed verification; attempting local go build.");
62
+ } else {
63
+ console.warn(`[openclaw-memory-libravdb] No prebuilt target for ${process.platform}-${process.arch}; attempting local go build.`);
64
+ }
65
+
66
+ return tryGoBuild(path.join(outDir, binary));
67
+ }
68
+
69
+ async function tryDownloadPrebuilt(assetUrl, checksumUrl, dest) {
70
+ try {
71
+ const checksum = await fetchChecksum(checksumUrl);
72
+ if (!checksum) {
73
+ return false;
74
+ }
75
+ await downloadToFile(assetUrl, dest);
76
+ const actual = sha256File(dest);
77
+ if (actual !== checksum) {
78
+ rmSync(dest, { force: true });
79
+ console.warn(`[openclaw-memory-libravdb] Prebuilt sidecar checksum mismatch for ${assetUrl}.`);
80
+ return false;
81
+ }
82
+ if (process.platform !== "win32") {
83
+ chmodSync(dest, 0o755);
84
+ }
85
+ return true;
86
+ } catch (error) {
87
+ rmSync(dest, { force: true });
88
+ console.warn(`[openclaw-memory-libravdb] Prebuilt sidecar download failed: ${formatError(error)}`);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ function tryGoBuild(dest) {
94
+ const goCheck = spawnSync("go", ["version"], {
95
+ stdio: "pipe",
96
+ env: process.env,
97
+ });
98
+
99
+ if (goCheck.error && goCheck.error.code === "ENOENT") {
100
+ console.error("FATAL: Go toolchain not found on PATH. The LibraVDB sidecar cannot be built locally.");
101
+ return false;
102
+ }
103
+
104
+ if (goCheck.status !== 0) {
105
+ console.error("FATAL: Go toolchain check failed. The LibraVDB sidecar cannot be built locally.");
106
+ if (goCheck.stderr?.length) {
107
+ process.stderr.write(goCheck.stderr);
108
+ }
109
+ return false;
110
+ }
111
+
112
+ const result = spawnSync("go", ["build", "-o", dest, "."], {
113
+ cwd: sidecarDir,
114
+ stdio: "inherit",
115
+ env: {
116
+ ...process.env,
117
+ GOCACHE: process.env.GOCACHE ?? path.join(os.tmpdir(), "openclaw-memory-libravdb-gocache"),
118
+ },
119
+ });
120
+
121
+ if (result.error && result.error.code === "ENOENT") {
122
+ console.error("FATAL: Go toolchain disappeared while building the LibraVDB sidecar.");
123
+ return false;
124
+ }
125
+
126
+ if (result.status !== 0) {
127
+ console.error("FATAL: go build failed. The LibraVDB sidecar will not start.");
128
+ return false;
129
+ }
130
+
131
+ console.log("[openclaw-memory-libravdb] Sidecar installed (local build)");
132
+ return true;
133
+ }
134
+
135
+ async function fetchChecksum(url) {
136
+ try {
137
+ const response = await fetch(url);
138
+ if (!response.ok) {
139
+ return null;
140
+ }
141
+ const text = (await response.text()).trim();
142
+ const match = text.match(/[a-f0-9]{64}/i);
143
+ return match ? match[0].toLowerCase() : null;
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
149
+ async function downloadToFile(url, dest) {
150
+ const response = await fetch(url);
151
+ if (!response.ok || !response.body) {
152
+ throw new Error(`download failed: ${url} (${response.status})`);
153
+ }
154
+ const tempPath = `${dest}.tmp`;
155
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(tempPath));
156
+ rmSync(dest, { force: true });
157
+ renameSync(tempPath, dest);
158
+ }
159
+
160
+ function sha256File(filePath) {
161
+ return createHash("sha256").update(readFileSync(filePath)).digest("hex");
162
+ }
163
+
164
+ function formatError(error) {
165
+ if (error instanceof Error) {
166
+ return error.message;
167
+ }
168
+ return String(error);
169
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ MODELS_DIR="$ROOT_DIR/.models"
6
+ ORT_ARCHIVE="$MODELS_DIR/onnxruntime/onnxruntime-osx-arm64-1.23.0.tgz"
7
+ ORT_LIB="$MODELS_DIR/onnxruntime/onnxruntime-osx-arm64-1.23.0/lib/libonnxruntime.dylib"
8
+
9
+ if [[ ! -f "$ORT_ARCHIVE" ]]; then
10
+ echo "ONNX Runtime archive not found: $ORT_ARCHIVE" >&2
11
+ exit 1
12
+ fi
13
+
14
+ if [[ ! -f "$ORT_LIB" ]]; then
15
+ echo "Unpacking ONNX Runtime..."
16
+ tar -xzf "$ORT_ARCHIVE" -C "$MODELS_DIR/onnxruntime/"
17
+ echo "Done: $ORT_LIB"
18
+ else
19
+ echo "ONNX Runtime already unpacked."
20
+ fi