@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.
- package/README.md +46 -0
- package/docs/README.md +14 -0
- package/docs/architecture-decisions/README.md +6 -0
- package/docs/architecture-decisions/adr-001-onnx-over-ollama.md +21 -0
- package/docs/architecture-decisions/adr-002-libravdb-over-lancedb.md +19 -0
- package/docs/architecture-decisions/adr-003-convex-gating-over-threshold.md +27 -0
- package/docs/architecture-decisions/adr-004-sidecar-over-native-ts.md +21 -0
- package/docs/architecture.md +188 -0
- package/docs/contributing.md +76 -0
- package/docs/dependencies.md +38 -0
- package/docs/embedding-profiles.md +42 -0
- package/docs/gating.md +329 -0
- package/docs/implementation.md +381 -0
- package/docs/installation.md +272 -0
- package/docs/mathematics.md +695 -0
- package/docs/models.md +63 -0
- package/docs/problem.md +64 -0
- package/docs/security.md +86 -0
- package/openclaw.plugin.json +84 -0
- package/package.json +41 -0
- package/scripts/build-sidecar.sh +30 -0
- package/scripts/postinstall.js +169 -0
- package/scripts/setup.sh +20 -0
- package/scripts/setup.ts +505 -0
- package/scripts/sidecar-release.d.ts +4 -0
- package/scripts/sidecar-release.js +17 -0
- package/sidecar/cmd/inspect_onnx/main.go +105 -0
- package/sidecar/compact/gate.go +273 -0
- package/sidecar/compact/gate_test.go +85 -0
- package/sidecar/compact/summarize.go +345 -0
- package/sidecar/compact/summarize_test.go +319 -0
- package/sidecar/compact/tokens.go +11 -0
- package/sidecar/config/config.go +119 -0
- package/sidecar/config/config_test.go +75 -0
- package/sidecar/embed/engine.go +696 -0
- package/sidecar/embed/engine_test.go +349 -0
- package/sidecar/embed/matryoshka.go +93 -0
- package/sidecar/embed/matryoshka_test.go +150 -0
- package/sidecar/embed/onnx_local.go +319 -0
- package/sidecar/embed/onnx_local_test.go +159 -0
- package/sidecar/embed/profile_contract_test.go +71 -0
- package/sidecar/embed/profile_eval_test.go +923 -0
- package/sidecar/embed/profiles.go +39 -0
- package/sidecar/go.mod +21 -0
- package/sidecar/go.sum +30 -0
- package/sidecar/health/check.go +33 -0
- package/sidecar/health/check_test.go +55 -0
- package/sidecar/main.go +151 -0
- package/sidecar/model/encoder.go +222 -0
- package/sidecar/model/registry.go +262 -0
- package/sidecar/model/registry_test.go +102 -0
- package/sidecar/model/seq2seq.go +133 -0
- package/sidecar/server/rpc.go +343 -0
- package/sidecar/server/rpc_test.go +350 -0
- package/sidecar/server/transport.go +160 -0
- package/sidecar/store/libravdb.go +676 -0
- package/sidecar/store/libravdb_test.go +472 -0
- package/sidecar/summarize/engine.go +360 -0
- package/sidecar/summarize/engine_test.go +148 -0
- package/sidecar/summarize/onnx_local.go +494 -0
- package/sidecar/summarize/onnx_local_test.go +48 -0
- package/sidecar/summarize/profiles.go +52 -0
- package/sidecar/summarize/tokenizer.go +13 -0
- package/sidecar/summarize/tokenizer_hf.go +76 -0
- package/sidecar/summarize/util.go +13 -0
- package/src/cli.ts +205 -0
- package/src/context-engine.ts +195 -0
- package/src/index.ts +27 -0
- package/src/memory-provider.ts +24 -0
- package/src/openclaw-plugin-sdk.d.ts +53 -0
- package/src/plugin-runtime.ts +67 -0
- package/src/recall-cache.ts +34 -0
- package/src/recall-utils.ts +22 -0
- package/src/rpc.ts +84 -0
- package/src/scoring.ts +58 -0
- package/src/sidecar.ts +506 -0
- package/src/tokens.ts +36 -0
- package/src/types.ts +146 -0
- package/tsconfig.json +20 -0
- 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.”
|
package/docs/problem.md
ADDED
|
@@ -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.
|
package/docs/security.md
ADDED
|
@@ -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
|
+
}
|
package/scripts/setup.sh
ADDED
|
@@ -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
|