@xdarkicex/openclaw-memory-libravdb 1.6.23 → 1.6.27
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 +219 -38
- package/dist/context-engine.js +72 -8
- package/dist/identity.d.ts +10 -1
- package/dist/identity.js +20 -0
- package/dist/index.js +814 -136
- package/dist/libravdb-client.d.ts +3 -0
- package/dist/libravdb-client.js +12 -1
- package/dist/manifest.d.ts +46 -0
- package/dist/manifest.js +127 -0
- package/dist/markdown-ingest.js +14 -1
- package/dist/memory-runtime.js +12 -7
- package/dist/plugin-runtime.d.ts +1 -1
- package/dist/plugin-runtime.js +10 -3
- package/dist/types.d.ts +5 -0
- package/docs/README.md +4 -4
- package/docs/TLS_configuration.md +17 -17
- package/docs/architecture-decisions/adr-004-sidecar-over-native-ts.md +1 -1
- package/docs/architecture.md +8 -8
- package/docs/configuration.md +15 -15
- package/docs/contributing.md +5 -5
- package/docs/dependencies.md +1 -1
- package/docs/development.md +9 -9
- package/docs/embedding-profiles.md +13 -11
- package/docs/features.md +6 -6
- package/docs/install.md +19 -19
- package/docs/installation.md +33 -25
- package/docs/mTLS_configuration.md +2 -2
- package/docs/models.md +3 -3
- package/docs/performance-and-tuning.md +5 -5
- package/docs/problem.md +1 -1
- package/docs/security.md +4 -4
- package/docs/uninstall.md +5 -5
- package/openclaw.plugin.json +7 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
`@xdarkicex/openclaw-memory-libravdb` is a local-first OpenClaw memory plugin
|
|
15
15
|
backed by the `libravdbd` vector service. It replaces the lightweight default memory
|
|
16
16
|
path with scoped session, user, and global memory; continuity-aware prompt
|
|
17
|
-
assembly; durable recall; and
|
|
17
|
+
assembly; durable recall; and vector-service-owned compaction.
|
|
18
18
|
|
|
19
19
|
[Install](./docs/install.md) · [Full installation reference](./docs/installation.md) · [Architecture](./docs/architecture.md) · [Security](./docs/security.md) · [Performance and tuning](./docs/performance-and-tuning.md) · [Contributing](./docs/contributing.md)
|
|
20
20
|
|
|
@@ -33,7 +33,7 @@ brew install libravdbd
|
|
|
33
33
|
brew services start libravdbd
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
> **After upgrades:** Always restart the
|
|
36
|
+
> **After upgrades:** Always restart the vector service so the newly installed binary takes effect:
|
|
37
37
|
> ```bash
|
|
38
38
|
> # macOS (Homebrew)
|
|
39
39
|
> brew services restart libravdbd
|
|
@@ -69,25 +69,16 @@ systemctl --user enable --now libravdbd
|
|
|
69
69
|
openclaw plugins install @xdarkicex/openclaw-memory-libravdb
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
This automatically configures `plugins.slots.memory` and `plugins.slots.contextEngine` to point to `libravdb-memory`, and sets up the plugin entry with defaults.
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"libravdb-memory": {
|
|
83
|
-
"enabled": true,
|
|
84
|
-
"config": {
|
|
85
|
-
"sidecarPath": "auto"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
74
|
+
Then restart the gateway so the plugin loads:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# macOS/Linux
|
|
78
|
+
openclaw daemon restart
|
|
79
|
+
|
|
80
|
+
# Verify the plugin is loaded
|
|
81
|
+
openclaw plugins list | grep libravdb
|
|
91
82
|
```
|
|
92
83
|
|
|
93
84
|
Verify the service and plugin:
|
|
@@ -109,7 +100,7 @@ Runtime requirements:
|
|
|
109
100
|
|
|
110
101
|
Compatibility note:
|
|
111
102
|
|
|
112
|
-
- this plugin is currently verified against OpenClaw `2026.
|
|
103
|
+
- this plugin is currently verified against OpenClaw `2026.5.22`
|
|
113
104
|
|
|
114
105
|
Default endpoints:
|
|
115
106
|
|
|
@@ -136,22 +127,33 @@ If your service runs elsewhere, set `sidecarPath`:
|
|
|
136
127
|
|
|
137
128
|
## Highlights
|
|
138
129
|
|
|
139
|
-
- **
|
|
140
|
-
|
|
141
|
-
- **
|
|
142
|
-
|
|
143
|
-
- **
|
|
144
|
-
|
|
145
|
-
- **
|
|
146
|
-
|
|
147
|
-
- **
|
|
148
|
-
|
|
149
|
-
- **
|
|
150
|
-
|
|
151
|
-
- **
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
130
|
+
- **Unified Cognitive Scoring** - moves beyond standard semantic search by mathematically blending cosine similarity with frequency, recency, authored salience, and cognitive authority composite weights (`ω(c)`).
|
|
131
|
+
- **Topological Causal Graphs** - internally models temporal memory chains via directed acyclic graphs (`WhyIDs`), injecting causal proximity into retrieval scoring to anticipate temporally connected context.
|
|
132
|
+
- **Zero-GC Slab Allocation** - manages model tensor and inference data via a custom contiguous slab allocator (`slabby`), completely bypassing Go garbage collection pauses during high-throughput memory assembly.
|
|
133
|
+
- **Deontic & Salience Retrieval** - applies structural authority weightings and deontic logic rules to ensure critical behavioral constraints mathematically outrank conversational chatter.
|
|
134
|
+
- **Matryoshka Representation Learning** - natively supports dynamically tiered embedding dimensions (e.g., slicing 768d vectors down to 64d) for ultra-fast cascading coarse search followed by precision reranking.
|
|
135
|
+
- **Cognitive Routing Circuit Breakers** - strictly monitors remote endpoint health via stateful circuit breakers that trip and safely auto-disable complex ML routing during downstream outages, preserving foundational search uptime.
|
|
136
|
+
- **Zero-ML Local Compaction** - evaluates contextual gating thresholds to execute purely localized session summarization and compaction cycles natively within the vector service.
|
|
137
|
+
- **True multi-tenancy** - routes multiple OpenClaw agents to strictly isolated, per-agent vector databases within a single lightweight vector service process.
|
|
138
|
+
- **Zero-copy caching** - shares a memory-mapped, cross-tenant embedding cache across all active agents to keep hardware utilization incredibly low.
|
|
139
|
+
- **Three memory scopes** - keeps active session, durable user, and global memory separate.
|
|
140
|
+
- **Local-first inference** - uses local embedding paths by default, with optional remote model configurations.
|
|
141
|
+
- **Operational tooling** - provides a dedicated CLI (`libravdbd status`, `migrate`, `tenant evict`) for live observability and safe health management without interrupting active sessions.
|
|
142
|
+
- **Explicit service lifecycle** - the npm/OpenClaw package stays connect-only; `libravdbd` is installed and supervised separately over a secure gRPC transport.
|
|
143
|
+
|
|
144
|
+
## Embedding Backend Providers
|
|
145
|
+
|
|
146
|
+
The plugin supports multiple embedding backends. Set via `embeddingBackend` in plugin config:
|
|
147
|
+
|
|
148
|
+
| Backend | Description | Config required |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `gguf` (recommended) | Hardware-native acceleration via llama.cpp. Apple Silicon gets Metal, NVIDIA gets CUDA, everything else falls back to CPU. No ONNX Runtime dependency. | None — just `embeddingBackend: "gguf"` |
|
|
151
|
+
| `bundled` | ONNX build of `nomic-embed-text-v1.5`. Full-featured fallback when GGUF is unavailable. | None |
|
|
152
|
+
| `onnx-local` | Custom ONNX model from local assets. Requires `embeddingModelPath` and `embeddingRuntimePath`. | `embeddingModelPath`, `embeddingRuntimePath` |
|
|
153
|
+
| `custom-local` | Custom ONNX variant with your own assets and runtime. | `embeddingModelPath`, `embeddingRuntimePath`, `embeddingProfile` |
|
|
154
|
+
| `remote` | HTTP API embedder (e.g. OpenAI-compatible). Requires `embeddingEndpoint` and `embeddingRemoteModel`. | `embeddingEndpoint`, `embeddingRemoteModel` |
|
|
155
|
+
|
|
156
|
+
GGUF is the recommended default. It delivers `nomic-embed-text-v1.5` embeddings with hardware-native acceleration and no ONNX Runtime dependency. See [Embedding profiles](./docs/embedding-profiles.md) for full details.
|
|
155
157
|
|
|
156
158
|
## Security Defaults
|
|
157
159
|
|
|
@@ -184,21 +186,200 @@ All keys are optional. For the full reference, see [Configuration](./docs/config
|
|
|
184
186
|
| Key | Type | Default | |
|
|
185
187
|
|---|---|---|---|
|
|
186
188
|
| `sidecarPath` | string | `auto` | `"auto"` probes standard paths; set `unix:/path` or `tcp:host:port` to override |
|
|
189
|
+
| `embeddingBackend` | string | `gguf` | Embedding backend: `gguf` (recommended), `bundled`, `onnx-local`, `custom-local`, `remote` |
|
|
187
190
|
| `embeddingProfile` | string | `nomic-embed-text-v1.5` | Primary embedding model |
|
|
188
191
|
| `fallbackProfile` | string | `bge-small-en-v1.5` | Fallback profile for dimension mismatches |
|
|
189
192
|
| `embeddingRuntimePath` | string | — | Required with `embeddingBackend: "onnx-local"`; path to `libonnxruntime` visible to `libravdbd` |
|
|
190
193
|
| `embeddingModelPath` | string | — | Required with `embeddingBackend: "onnx-local"`; directory containing `embedding.json`, `model.onnx`, and `tokenizer.json` |
|
|
191
|
-
| `onnxDevice` | string | `
|
|
194
|
+
| `onnxDevice` | string | `cpu` | ONNX execution provider; `cpu` is the default; `auto` lets libravdbd auto-detect |
|
|
192
195
|
| `userId` | string | auto-derived | Stable identity for cross-session durable memory |
|
|
196
|
+
| `tenantId` | string | auto-derived | Multi-tenant identifier. Resolved as `cfg.tenantId` > `LIBRAVDB_AGENT_ID` env > `userId`. Isolates the agent to a dedicated `.libravdb` file. |
|
|
193
197
|
| `crossSessionRecall` | boolean | `true` | When `false`, only session-scoped memories are retrieved |
|
|
194
198
|
| `compactSessionTokenBudget` | number | `2000` | Auto-compaction token threshold; `0` disables |
|
|
195
199
|
|
|
200
|
+
## Multi-Tenant Support
|
|
201
|
+
|
|
202
|
+
`libravdbd` supports true multi-tenancy, allowing you to run multiple OpenClaw agents on the same machine with completely isolated vector databases. By default, the plugin connects to a single-tenant database named after your `userId`.
|
|
203
|
+
|
|
204
|
+
If you want to run multiple distinct agents (e.g., a "research-agent" and a "coding-agent"), you can assign each a unique `tenantId` in the OpenClaw configuration:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"plugins": {
|
|
209
|
+
"entries": {
|
|
210
|
+
"libravdb-memory": {
|
|
211
|
+
"enabled": true,
|
|
212
|
+
"config": {
|
|
213
|
+
"tenantId": "research-agent"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The vector service will seamlessly route the agent's requests to a dedicated, isolated vector database file. It manages all tenant instances efficiently within a single process and automatically shares a centralized, memory-mapped embedding cache to keep hardware usage incredibly low.
|
|
222
|
+
|
|
223
|
+
### Directory Structure
|
|
224
|
+
|
|
225
|
+
When running in multi-tenant mode, the vector service automatically scaffolds an isolated directory structure inside your configured `agent_db_root` (or the default profile directory). It scopes databases to the specific embedding model in use:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
~/.libravdbd/data_nomic-embed-text-v1_5/
|
|
229
|
+
├── _internal:dedupe.libravdb # Cross-session deduplication state
|
|
230
|
+
├── _internal:registry.libravdb # Tenant registry and health logs
|
|
231
|
+
└── agents/
|
|
232
|
+
├── research-agent.libravdb # Isolated database for research-agent
|
|
233
|
+
├── coding-agent.libravdb # Isolated database for coding-agent
|
|
234
|
+
└── my-default-user.libravdb # Isolated database for default user
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Multi-Tenant Operations
|
|
238
|
+
|
|
239
|
+
The vector service exposes tenant-aware operational commands:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# View global vector service health, cache stats, and all active tenant footprints
|
|
243
|
+
libravdbd status
|
|
244
|
+
|
|
245
|
+
# Evict a specific tenant from memory without shutting down the vector service
|
|
246
|
+
libravdbd tenant evict <tenantId>
|
|
247
|
+
|
|
248
|
+
# Safely migrate an old single-tenant DB to a named tenant
|
|
249
|
+
libravdbd migrate --from ~/.libravdbd/data.libravdb --tenant <tenantId>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Vector Service Configuration (YAML) & Kubernetes
|
|
253
|
+
|
|
254
|
+
`libravdbd` is heavily configurable via environment variables or a YAML configuration file. The vector service looks for `config.yaml` in this order:
|
|
255
|
+
1. `LIBRAVDB_CONFIG=/path/to/config.yaml`
|
|
256
|
+
2. `/etc/libravdbd/config.yaml`
|
|
257
|
+
3. `~/.libravdbd/config.yaml`
|
|
258
|
+
|
|
259
|
+
Example `config.yaml` for a Kubernetes StatefulSet deployment in multi-tenant mode:
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
# /etc/libravdbd/config.yaml
|
|
263
|
+
agent_db_root: "/var/lib/libravdbd/agents"
|
|
264
|
+
tenant_mode: "auto"
|
|
265
|
+
tenant_max_open: 128
|
|
266
|
+
grpc_endpoint: "tcp:0.0.0.0:9090"
|
|
267
|
+
embedding_backend: "gguf"
|
|
268
|
+
embedding_profile: "nomic-embed-text-v1.5"
|
|
269
|
+
drain_timeout: "25s" # Must be less than k8s terminationGracePeriodSeconds
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Securing gRPC with mTLS
|
|
273
|
+
|
|
274
|
+
For distributed deployments where `libravdbd` and OpenClaw run on different machines, you must secure the TCP transport using Mutual TLS (mTLS).
|
|
275
|
+
|
|
276
|
+
**1. Generate Local Certificates:**
|
|
277
|
+
```bash
|
|
278
|
+
# 1. Generate Certificate Authority (CA)
|
|
279
|
+
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes -keyout ca.key -out ca.crt -subj "/CN=LibraVDB-CA"
|
|
280
|
+
|
|
281
|
+
# 2. Generate Vector Service Server Certificate
|
|
282
|
+
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=libravdbd.local"
|
|
283
|
+
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
|
|
284
|
+
|
|
285
|
+
# 3. Generate Client Certificate (For OpenClaw plugins)
|
|
286
|
+
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=openclaw-client"
|
|
287
|
+
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**2. Configure the Vector Service:**
|
|
291
|
+
Add the generated TLS paths to your vector service's `config.yaml`:
|
|
292
|
+
```yaml
|
|
293
|
+
grpc_endpoint: "tcp:0.0.0.0:9090"
|
|
294
|
+
grpc_tls_cert: "/etc/libravdbd/certs/server.crt"
|
|
295
|
+
grpc_tls_key: "/etc/libravdbd/certs/server.key"
|
|
296
|
+
grpc_tls_ca: "/etc/libravdbd/certs/ca.crt" # Enforces mTLS client verification
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**3. Connect Your Client:**
|
|
300
|
+
Add the TLS client certificate paths to your OpenClaw plugin config in `openclaw.json`:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"plugins": {
|
|
305
|
+
"entries": {
|
|
306
|
+
"libravdb-memory": {
|
|
307
|
+
"config": {
|
|
308
|
+
"grpcEndpoint": "tcp:libravdbd.local:9090",
|
|
309
|
+
"grpcEndpointTlsMode": "tls",
|
|
310
|
+
"grpcEndpointTlsClientCert": "/etc/libravdbd/certs/client.crt",
|
|
311
|
+
"grpcEndpointTlsClientKey": "/etc/libravdbd/certs/client.key",
|
|
312
|
+
"grpcEndpointTlsCa": "/etc/libravdbd/certs/ca.crt"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The `grpcEndpointTlsCa` field is required for mTLS; the plugin will verify the server certificate against this CA. When `grpcEndpointTlsMode` is `"tls"`, plaintext and unauthenticated connections are rejected.
|
|
321
|
+
|
|
196
322
|
## Optional Features
|
|
197
323
|
|
|
198
324
|
- **Markdown ingestion** watches OpenClaw-owned markdown roots or Obsidian vaults
|
|
199
325
|
and syncs eligible notes into memory. See [Features](./docs/features.md).
|
|
200
326
|
- **Dream promotion** promotes vetted dream diary bullets into an isolated
|
|
201
327
|
`dream:{userId}` collection. See [Features](./docs/features.md).
|
|
328
|
+
|
|
329
|
+
### Dream Promotion
|
|
330
|
+
|
|
331
|
+
OpenClaw's dreaming cron writes AI-generated memory reflections to a dream diary
|
|
332
|
+
markdown file. The plugin can watch this file and automatically promote vetted
|
|
333
|
+
entries into the `dream:{userId}` durable collection managed by the vector service.
|
|
334
|
+
|
|
335
|
+
Enable by adding these config keys:
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"plugins": {
|
|
340
|
+
"entries": {
|
|
341
|
+
"libravdb-memory": {
|
|
342
|
+
"config": {
|
|
343
|
+
"dreamPromotionEnabled": true,
|
|
344
|
+
"dreamPromotionUserId": "<your-user-id>",
|
|
345
|
+
"dreamPromotionDiaryPath": "~/DREAMS.md"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
| Key | Type | Required | Description |
|
|
354
|
+
|---|---|---|---|
|
|
355
|
+
| `dreamPromotionEnabled` | boolean | yes | Enable the dream diary file watcher |
|
|
356
|
+
| `dreamPromotionUserId` | string | yes | User ID whose `dream:` collection receives promoted entries |
|
|
357
|
+
| `dreamPromotionDiaryPath` | string | yes | Path to the dream diary markdown file (supports `~`) |
|
|
358
|
+
| `dreamPromotionDebounceMs` | number | no | Debounce delay before scanning after a change (default: `150`) |
|
|
359
|
+
|
|
360
|
+
The diary file is standard markdown. Entries under a `## Deep Sleep` or
|
|
361
|
+
`## Dream Promotion` heading are parsed as bullet points with trailing metadata:
|
|
362
|
+
|
|
363
|
+
```markdown
|
|
364
|
+
## Deep Sleep
|
|
365
|
+
- A key insight about the user's workflow patterns {score=0.85, recall=4, unique=3}
|
|
366
|
+
- Another consolidated observation {score=0.72, recall=2, unique=2}
|
|
367
|
+
|
|
368
|
+
<!-- or equivalently: -->
|
|
369
|
+
|
|
370
|
+
## Dream Promotion
|
|
371
|
+
- A key insight about the user's workflow patterns {score=0.85, recall=4, unique=3}
|
|
372
|
+
- Another consolidated observation {score=0.72, recall=2, unique=2}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Entries are promoted to `dream:<userId>` and surfaced when the user asks
|
|
376
|
+
dream-related questions (e.g. "what did I dream about?").
|
|
377
|
+
|
|
378
|
+
You can also promote manually without enabling the watcher:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
openclaw memory dream-promote --user-id <userId> --dream-file ~/DREAMS.md
|
|
382
|
+
```
|
|
202
383
|
- **Embedding profiles** default to `nomic-embed-text-v1.5` with `bge-small-en-v1.5`
|
|
203
384
|
fallback. See [Embedding profiles](./docs/embedding-profiles.md).
|
|
204
385
|
|
package/dist/context-engine.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { resolveIdentity } from "./identity.js";
|
|
2
3
|
import { resolveUserCollection } from "./memory-scopes.js";
|
|
4
|
+
import { manifestStore } from "./manifest.js";
|
|
3
5
|
const APPROX_CHARS_PER_TOKEN = 4;
|
|
4
6
|
const PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW = "preassembly_may_overflow";
|
|
5
7
|
const ASSEMBLE_BUDGET_HEADROOM_TOKENS = 256;
|
|
@@ -377,7 +379,7 @@ export function normalizeKernelMessage(message) {
|
|
|
377
379
|
return {
|
|
378
380
|
role: message.role,
|
|
379
381
|
content: normalizeKernelContent(message.content),
|
|
380
|
-
|
|
382
|
+
id: message.id || randomUUID(),
|
|
381
383
|
};
|
|
382
384
|
}
|
|
383
385
|
/**
|
|
@@ -471,9 +473,12 @@ function escapeMemoryFactText(text) {
|
|
|
471
473
|
.replaceAll("\t", "	");
|
|
472
474
|
}
|
|
473
475
|
// Tool-call pattern detection for sanitization
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
476
|
+
// Matches [tool:name] followed by optional whitespace and any trailing JSON object {...}, array [...], or string "..."
|
|
477
|
+
const TOOL_CALL_BRACKET_RE = /\[tool:([^\]]+)\](?:\s*(?:\{[\s\S]*?\}|\[[\s\S]*?\]|".*?"))?/gi;
|
|
478
|
+
// Matches raw JSON tool-call objects targeting a "name\" field
|
|
479
|
+
const TOOL_CALL_JSON_RE = /\{\s*"name"\s*:\s*"([^"]+)"[\s\S]*?\}/g;
|
|
480
|
+
// Matches older annotations, aggressively consuming trailing characters on the same line
|
|
481
|
+
const TOOL_RESULT_ANNOTATION_RE = /\[tool:[^\]]+\][^\n]*/g;
|
|
477
482
|
/**
|
|
478
483
|
* Sanitizes text that may contain tool-call syntax to prevent loop-priming.
|
|
479
484
|
* Replaces executable-looking patterns with neutral summaries rather than
|
|
@@ -697,9 +702,10 @@ export function normalizeAssembleResult(result, sourceMessages) {
|
|
|
697
702
|
isRealTranscript = message.role === "user" || message.role === "assistant";
|
|
698
703
|
}
|
|
699
704
|
if (isRealTranscript) {
|
|
705
|
+
// BUG PATH A SEALED: Sanitize the content before pushing to the trajectory
|
|
700
706
|
messages.push({
|
|
701
707
|
role: message.role === "user" ? "user" : "assistant",
|
|
702
|
-
content,
|
|
708
|
+
content: sanitizeToolCallPatterns(content),
|
|
703
709
|
...(typeof message.id === "string" ? { id: message.id } : {}),
|
|
704
710
|
});
|
|
705
711
|
}
|
|
@@ -724,6 +730,20 @@ export function normalizeAssembleResult(result, sourceMessages) {
|
|
|
724
730
|
...(result.debug != null ? { debug: result.debug } : {}),
|
|
725
731
|
};
|
|
726
732
|
}
|
|
733
|
+
function extractCursorFromResult(result) {
|
|
734
|
+
if (result && typeof result === "object" && "cursor" in result) {
|
|
735
|
+
const cursor = result.cursor;
|
|
736
|
+
if (cursor && typeof cursor === "object") {
|
|
737
|
+
const c = cursor;
|
|
738
|
+
if (typeof c.lastProcessedIndex === "number" &&
|
|
739
|
+
typeof c.sessionVersion === "number" &&
|
|
740
|
+
typeof c.manifestTailHash === "string") {
|
|
741
|
+
return c;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
727
747
|
/**
|
|
728
748
|
* Builds the context engine factory with the given client getter.
|
|
729
749
|
*/
|
|
@@ -1140,12 +1160,24 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
|
|
|
1140
1160
|
userIdOverride: args.userId,
|
|
1141
1161
|
sessionKey: args.sessionKey,
|
|
1142
1162
|
});
|
|
1163
|
+
// Load manifest and normalize messages in parallel
|
|
1164
|
+
const manifest = manifestStore.load(sessionId, logger);
|
|
1143
1165
|
const afterTurnMessages = selectAfterTurnMessages(args.messages, args.prePromptMessageCount, logger);
|
|
1144
1166
|
const messages = normalizeKernelMessages(afterTurnMessages);
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1167
|
+
// Find overlap: messages already in our manifest
|
|
1168
|
+
const overlapIndex = manifestStore.findOverlapIndex(manifest, messages);
|
|
1169
|
+
const newMessages = messages.slice(overlapIndex);
|
|
1170
|
+
// Apply token budget cap only to new messages
|
|
1171
|
+
const ingestMessages = boundAfterTurnMessagesForIngest(newMessages, logger, sessionId);
|
|
1172
|
+
const startIndex = manifestStore.deriveStartingIndex(manifest, args.prePromptMessageCount);
|
|
1173
|
+
const cursor = {
|
|
1174
|
+
lastProcessedIndex: startIndex > 0 ? startIndex - 1 : 0,
|
|
1175
|
+
sessionVersion: manifest.version,
|
|
1176
|
+
manifestTailHash: manifest.tailHash,
|
|
1177
|
+
};
|
|
1147
1178
|
logger.info?.(`LibraVDB afterTurn sessionId=${sessionId} userId=${userId} ` +
|
|
1148
|
-
`messageCount=${
|
|
1179
|
+
`messageCount=${messages.length} newMessages=${newMessages.length} ` +
|
|
1180
|
+
`overlapIndex=${overlapIndex} startIndex=${startIndex} ` +
|
|
1149
1181
|
`prePromptMessageCount=${args.prePromptMessageCount ?? "unknown"} ` +
|
|
1150
1182
|
`heartbeat=${args.isHeartbeat ?? false}`);
|
|
1151
1183
|
try {
|
|
@@ -1158,8 +1190,40 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
|
|
|
1158
1190
|
sessionKey: args.sessionKey,
|
|
1159
1191
|
userId,
|
|
1160
1192
|
messages: ingestMessages,
|
|
1193
|
+
prePromptMessageCount: args.prePromptMessageCount,
|
|
1161
1194
|
isHeartbeat: args.isHeartbeat,
|
|
1195
|
+
cursor,
|
|
1162
1196
|
});
|
|
1197
|
+
// Reconcile manifest with daemon-confirmed cursor.
|
|
1198
|
+
// The daemon returns a cursor even when it ingests zero messages
|
|
1199
|
+
// (e.g. gap detected, all messages deduped). Trust its
|
|
1200
|
+
// lastProcessedIndex over our optimistic startIndex math.
|
|
1201
|
+
const daemonCursor = extractCursorFromResult(result);
|
|
1202
|
+
if (daemonCursor) {
|
|
1203
|
+
if (!daemonCursor.manifestTailHash) {
|
|
1204
|
+
// Daemon detected a gap: its DB is behind our manifest.
|
|
1205
|
+
// It did NOT ingest our messages. Reset the manifest so the
|
|
1206
|
+
// next turn does a full re-sync.
|
|
1207
|
+
logger.warn?.(`[LibraVDB] Daemon reported cursor gap for session ${sessionId}. ` +
|
|
1208
|
+
`Resetting manifest for full re-sync next turn.`);
|
|
1209
|
+
manifestStore.save(manifestStore.createEmpty(sessionId));
|
|
1210
|
+
}
|
|
1211
|
+
else if (ingestMessages.length > 0) {
|
|
1212
|
+
// Normal path: reconcile to what the daemon actually confirmed.
|
|
1213
|
+
const confirmedIndex = daemonCursor.lastProcessedIndex;
|
|
1214
|
+
const ackCount = Math.max(0, confirmedIndex - startIndex + 1);
|
|
1215
|
+
if (ackCount > 0) {
|
|
1216
|
+
const ackedMessages = ingestMessages.slice(0, ackCount);
|
|
1217
|
+
const updatedManifest = manifestStore.appendACKedMessages(manifest, ackedMessages, startIndex);
|
|
1218
|
+
manifestStore.save(updatedManifest);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
else if (ingestMessages.length > 0) {
|
|
1223
|
+
// Legacy daemon (no cursor in response): optimistic ACK.
|
|
1224
|
+
const updatedManifest = manifestStore.appendACKedMessages(manifest, ingestMessages, startIndex);
|
|
1225
|
+
manifestStore.save(updatedManifest);
|
|
1226
|
+
}
|
|
1163
1227
|
await performAfterTurnPredictiveCompaction({
|
|
1164
1228
|
sessionId,
|
|
1165
1229
|
messages,
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoggerLike } from "./types.js";
|
|
1
|
+
import type { LoggerLike, PluginConfig } from "./types.js";
|
|
2
2
|
export type IdentitySource = "config" | "file" | "auto" | "session-key" | "default";
|
|
3
3
|
export type ResolvedIdentity = {
|
|
4
4
|
userId: string;
|
|
@@ -13,3 +13,12 @@ export declare function resolveIdentity(params: {
|
|
|
13
13
|
* read-only commands (e.g. status --deep) that should not mutate disk. */
|
|
14
14
|
noAutoPersist?: boolean;
|
|
15
15
|
}): ResolvedIdentity;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a stable tenant key for multi-agent DB routing.
|
|
18
|
+
*
|
|
19
|
+
* Priority chain:
|
|
20
|
+
* 1. cfg.tenantId (explicit config, highest priority)
|
|
21
|
+
* 2. LIBRAVDB_AGENT_ID env var (container/CI override)
|
|
22
|
+
* 3. Fall back to resolved userId (existing identity system)
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveTenantKey(cfg: PluginConfig): string;
|
package/dist/identity.js
CHANGED
|
@@ -118,3 +118,23 @@ export function resolveIdentity(params) {
|
|
|
118
118
|
}
|
|
119
119
|
return { userId: autoId, source: "auto" };
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolves a stable tenant key for multi-agent DB routing.
|
|
123
|
+
*
|
|
124
|
+
* Priority chain:
|
|
125
|
+
* 1. cfg.tenantId (explicit config, highest priority)
|
|
126
|
+
* 2. LIBRAVDB_AGENT_ID env var (container/CI override)
|
|
127
|
+
* 3. Fall back to resolved userId (existing identity system)
|
|
128
|
+
*/
|
|
129
|
+
export function resolveTenantKey(cfg) {
|
|
130
|
+
const explicit = cfg.tenantId?.trim();
|
|
131
|
+
if (explicit)
|
|
132
|
+
return explicit;
|
|
133
|
+
const envId = process.env.LIBRAVDB_AGENT_ID?.trim();
|
|
134
|
+
if (envId)
|
|
135
|
+
return envId;
|
|
136
|
+
return resolveIdentity({
|
|
137
|
+
configUserId: cfg.userId,
|
|
138
|
+
identityPath: cfg.identityPath,
|
|
139
|
+
}).userId;
|
|
140
|
+
}
|