@openviking/openclaw-plugin 2026.9.1 → 2026.9.4-1
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/INSTALL-AGENT.md +25 -4
- package/INSTALL-ZH.md +22 -4
- package/INSTALL.md +22 -4
- package/README.md +19 -15
- package/auto-recall.ts +126 -135
- package/client.ts +95 -0
- package/commands/setup.ts +22 -8
- package/config.ts +31 -16
- package/context-engine.ts +2 -2
- package/dist/auto-recall.js +113 -114
- package/dist/client.js +54 -0
- package/dist/commands/setup.js +15 -6
- package/dist/config.js +18 -11
- package/dist/plugin/openviking-memory-tools.js +1 -1
- package/dist/plugin/openviking-session-routing-runtime.js +2 -2
- package/dist/routing/identity-routing.js +5 -5
- package/dist/services/context-lifecycle-service.js +4 -4
- package/dist/shared/recall-compress-core.mjs +188 -0
- package/dist/shared/recall-core.mjs +645 -0
- package/install-manifest.json +1 -0
- package/openclaw.plugin.json +9 -8
- package/package.json +2 -1
- package/plugin/openviking-client-runtime.ts +1 -1
- package/plugin/openviking-memory-tools.ts +1 -1
- package/plugin/openviking-session-routing-runtime.ts +2 -2
- package/routing/identity-routing.ts +8 -8
- package/services/context-lifecycle-service.ts +4 -4
- package/services/setup/setup-flow.ts +8 -6
- package/shared/recall-compress-core.mjs +195 -0
- package/shared/recall-core.d.mts +39 -0
- package/shared/recall-core.mjs +678 -0
- package/skills/install-openviking-memory/SKILL.md +33 -10
- package/skills/openviking-context-database/SKILL.md +2 -2
package/INSTALL-AGENT.md
CHANGED
|
@@ -42,6 +42,7 @@ Before setup, determine:
|
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| OpenViking base URL | Yes | Ask user or read `OPENVIKING_BASE_URL` |
|
|
44
44
|
| API key | Usually | Ask user or read `OPENVIKING_API_KEY` |
|
|
45
|
+
| Memory scope | Optional; default `none` | Ask whether memory is shared, separated by assistant, or separated by sender. Use `sender`; accept legacy `person` as an alias. |
|
|
45
46
|
| Account ID | Only for root API keys | Ask user if setup reports root-key tenant context is needed |
|
|
46
47
|
| User ID | Only for root API keys | Ask user if setup reports root-key tenant context is needed |
|
|
47
48
|
| Slot replacement approval | Only if another context engine owns the slot | Ask user before using `--force-slot` |
|
|
@@ -73,6 +74,23 @@ Run setup with `--json` whenever possible:
|
|
|
73
74
|
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --json
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
Choose memory scope before building that command:
|
|
78
|
+
|
|
79
|
+
| Value | Concrete layout | Use when |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `none` (default; omit the flag) | Shared `viking://user/<user_id>/memories/...`; no concrete peer memory subtree | General case |
|
|
82
|
+
| `assistant` | Assistant-attributed peer memory under `.../peers/<assistant_id>/memories/...` | A human is the OpenViking user and uses multiple OpenClaw assistants |
|
|
83
|
+
| `sender` | Sender-attributed peer memory under `.../peers/<sender_id>/memories/...` | An agent is the OpenViking user and receives messages from multiple humans |
|
|
84
|
+
|
|
85
|
+
`person` is a legacy alias for `sender`. Accept it from existing config or user input, but write `sender` in new setup commands. OpenViking initializes the managed `peers/` container for every user; `none` only means that no concrete `peers/<peer_id>/memories` subtree is used. Actor-peer recall includes shared user memory plus the current peer memory, and changing the scope does not move existing memories.
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role assistant --json
|
|
91
|
+
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role sender --json
|
|
92
|
+
```
|
|
93
|
+
|
|
76
94
|
Branch on the result:
|
|
77
95
|
|
|
78
96
|
| JSON result | Meaning | Agent action |
|
|
@@ -96,10 +114,10 @@ openclaw openviking setup \
|
|
|
96
114
|
--json
|
|
97
115
|
```
|
|
98
116
|
|
|
99
|
-
Custom
|
|
117
|
+
Custom assistant routing prefix (optional; only with `peer_role=assistant` and when the user explicitly requests a prefix):
|
|
100
118
|
|
|
101
119
|
```bash
|
|
102
|
-
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-prefix <PREFIX> --json
|
|
120
|
+
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role assistant --peer-prefix <PREFIX> --json
|
|
103
121
|
```
|
|
104
122
|
|
|
105
123
|
Slot replacement retry, only after user approval:
|
|
@@ -139,6 +157,7 @@ Also inspect:
|
|
|
139
157
|
| `health.version` | Records server version |
|
|
140
158
|
| `health.compatibility` | Determines whether to warn |
|
|
141
159
|
| `config.hasApiKey` | Confirms whether an API key was saved |
|
|
160
|
+
| `config.peer_role` | Confirms `none`, `assistant`, or canonical `sender` scope |
|
|
142
161
|
| `config.peer_prefix` | Confirms configured peer prefix when present |
|
|
143
162
|
|
|
144
163
|
## Environment Detection
|
|
@@ -267,7 +286,8 @@ Core fields:
|
|
|
267
286
|
| `mode` | Legacy compatibility field. Expected value: `remote`. |
|
|
268
287
|
| `baseUrl` | OpenViking HTTP endpoint |
|
|
269
288
|
| `apiKey` | OpenViking API key |
|
|
270
|
-
| `
|
|
289
|
+
| `peer_role` | `none` (shared user memory), `assistant` (`peers/<assistant_id>`), or `sender` (`peers/<sender_id>`). Legacy `person` is accepted as `sender`. |
|
|
290
|
+
| `peer_prefix` | Optional only with `peer_role=assistant`; prefix for OpenClaw agent IDs. Interactive setup accepts only letters, digits, `_`, and `-`. If unset, the plugin follows session agent IDs. |
|
|
271
291
|
| `accountId` | Required for root API keys |
|
|
272
292
|
| `userId` | Required for root API keys |
|
|
273
293
|
|
|
@@ -329,7 +349,8 @@ Existing config fields are preserved during migration. The new plugin reads old
|
|
|
329
349
|
|
|
330
350
|
- `baseUrl`
|
|
331
351
|
- `apiKey`
|
|
332
|
-
- `
|
|
352
|
+
- `peer_role`: optional; `none`, `assistant`, or `sender`; legacy `person` is accepted as `sender`
|
|
353
|
+
- `peer_prefix`: optional only with `peer_role=assistant`; interactive setup accepts only letters, digits, `_`, and `-`
|
|
333
354
|
|
|
334
355
|
## Backup Path: ov-install
|
|
335
356
|
|
package/INSTALL-ZH.md
CHANGED
|
@@ -197,12 +197,30 @@ openclaw openviking setup \
|
|
|
197
197
|
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --force-slot --json
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
根据 OpenViking user 代表谁来选择 `--peer-role`:
|
|
201
|
+
|
|
202
|
+
| 值 | 存储示例 | 适用场景 |
|
|
203
|
+
| --- | --- | --- |
|
|
204
|
+
| `none`(默认) | `viking://user/alice/memories/...` | 该 OpenViking 用户下的所有对话共享 user-level 记忆,不使用具体 peer 的记忆子树。 |
|
|
205
|
+
| `assistant` | `viking://user/alice/peers/main/memories/...` | OpenViking user 代表人,并希望把 assistant 归因的 peer 记忆按不同 OpenClaw 助手分开。 |
|
|
206
|
+
| `sender` | `viking://user/support-agent/peers/customer-42/memories/...` | OpenViking user 代表 agent,并希望把 sender 归因的 peer 记忆按不同发送者分开。 |
|
|
207
|
+
|
|
208
|
+
`person` 仍作为 `sender` 的旧配置别名被兼容;新配置请使用 `sender`。
|
|
209
|
+
|
|
210
|
+
例如,让每个助手使用独立的 peer 记忆,并可选给 assistant id 加前缀:
|
|
201
211
|
|
|
202
212
|
```bash
|
|
203
213
|
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role assistant --peer-prefix <PREFIX> --json
|
|
204
214
|
```
|
|
205
215
|
|
|
216
|
+
如果 OpenViking user 是 agent,按给它发消息的 sender 分开 peer 记忆:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role sender --json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
OpenViking 会为每个用户初始化受管的 `peers/` 容器。`none` 表示插件不创建、也不路由到具体的 `peers/<peer_id>/memories` 子树。Actor-peer 召回同时包含用户共享记忆和当前 peer 记忆;切换配置不会搬迁已有记忆。
|
|
223
|
+
|
|
206
224
|
#### 无法执行 CLI 时直接配置文件
|
|
207
225
|
|
|
208
226
|
如果容器内无法执行 `openclaw` CLI,可以把以下字段合并到 OpenClaw 实际读取的配置文件。设置了 `OPENCLAW_CONFIG_PATH` 时使用该路径;否则通常是 `$OPENCLAW_STATE_DIR/openclaw.json`(默认 `~/.openclaw/openclaw.json`)。
|
|
@@ -300,7 +318,7 @@ plugins.entries.openviking.config
|
|
|
300
318
|
| `mode` | `remote` | 兼容旧配置的字段。当前只支持 remote。 |
|
|
301
319
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking HTTP 地址 |
|
|
302
320
|
| `apiKey` | 空 | OpenViking API key |
|
|
303
|
-
| `peer_role` | `
|
|
321
|
+
| `peer_role` | `none` | 记忆归属:`none`(共享 `viking://user/<user_id>/memories`)、`assistant`(`.../peers/<assistant_id>/memories`)或 `sender`(`.../peers/<sender_id>/memories`)。旧值 `person` 作为 `sender` 的别名兼容。Session message 使用 body `peer_id`;数据面 recall/search 使用 `X-OpenViking-Actor-Peer`。 |
|
|
304
322
|
| `peer_prefix` | 空 | `peer_role=assistant` 时 assistant `peer_id` / actor peer 值的可选前缀。 |
|
|
305
323
|
| `accountId` | 空 | 使用 root API key 时需要 |
|
|
306
324
|
| `userId` | 空 | 使用 root API key 时需要 |
|
|
@@ -325,7 +343,7 @@ openclaw config get plugins.entries.openviking.config
|
|
|
325
343
|
| --- | --- | --- |
|
|
326
344
|
| `baseUrl` | `http://127.0.0.1:1933` | 远端 OpenViking 服务地址 |
|
|
327
345
|
| `apiKey` | 空 | 远端 OpenViking API Key;服务端未开启认证时可不填 |
|
|
328
|
-
| `peer_role` | `
|
|
346
|
+
| `peer_role` | `none` | 记忆归属:`none`、`assistant` 或 `sender`;旧值 `person` 作为 `sender` 的别名兼容。Session message 使用 body `peer_id`,数据面 recall/search 使用 `X-OpenViking-Actor-Peer` |
|
|
329
347
|
| `peer_prefix` | 空 | `peer_role=assistant` 时 assistant `peer_id` / actor peer 值的可选前缀 |
|
|
330
348
|
|
|
331
349
|
常见设置:
|
|
@@ -387,7 +405,7 @@ ov-install
|
|
|
387
405
|
| `--current-version` | 查看 helper 记录的当前版本 |
|
|
388
406
|
| `--base-url URL` | OpenViking 服务器地址(启用非交互模式) |
|
|
389
407
|
| `--api-key KEY` | OpenViking API key |
|
|
390
|
-
| `--peer-role ROLE` |
|
|
408
|
+
| `--peer-role ROLE` | 记忆归属:`none`、`assistant` 或 `sender`;旧值 `person` 作为 `sender` 的别名兼容 |
|
|
391
409
|
| `--peer-prefix PREFIX` | assistant `peer_id` / actor peer 值的前缀 |
|
|
392
410
|
| `--update` | 更新 helper 管理的安装 |
|
|
393
411
|
|
package/INSTALL.md
CHANGED
|
@@ -141,12 +141,30 @@ If another context engine already owns the slot, setup will not replace it by de
|
|
|
141
141
|
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --force-slot --json
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
Choose `--peer-role` from what the OpenViking user represents:
|
|
145
|
+
|
|
146
|
+
| Value | Storage example | Use when |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `none` (default) | `viking://user/alice/memories/...` | All conversations for this OpenViking user share user-level memory. No peer-specific memory subtree is used. |
|
|
149
|
+
| `assistant` | `viking://user/alice/peers/main/memories/...` | The OpenViking user is a human and assistant-attributed peer memory should be separated by OpenClaw assistant. |
|
|
150
|
+
| `sender` | `viking://user/support-agent/peers/customer-42/memories/...` | The OpenViking user is an agent and sender-attributed peer memory should be separated by human sender. |
|
|
151
|
+
|
|
152
|
+
`person` is still accepted as a legacy alias for `sender`. New configuration should use `sender`.
|
|
153
|
+
|
|
154
|
+
For example, give each assistant its own peer memory and optionally namespace the assistant id:
|
|
145
155
|
|
|
146
156
|
```bash
|
|
147
157
|
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role assistant --peer-prefix <PREFIX> --json
|
|
148
158
|
```
|
|
149
159
|
|
|
160
|
+
Or scope peer memory by the sender talking to an agent:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --peer-role sender --json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
OpenViking initializes the managed `peers/` container for every user. `none` means the plugin does not create or route into a concrete `peers/<peer_id>/memories` subtree. Actor-peer recall includes shared user memory plus the current peer memory, and changing the setting does not move existing memories.
|
|
167
|
+
|
|
150
168
|
#### Configure The File Directly When The CLI Is Unavailable
|
|
151
169
|
|
|
152
170
|
If the `openclaw` CLI cannot run inside the container, merge the following fields into the config file that OpenClaw actually reads. Use `OPENCLAW_CONFIG_PATH` when it is set; otherwise the file is usually `$OPENCLAW_STATE_DIR/openclaw.json` (default: `~/.openclaw/openclaw.json`).
|
|
@@ -244,7 +262,7 @@ Core fields:
|
|
|
244
262
|
| `mode` | `remote` | Legacy compatibility field. Only remote mode is supported. |
|
|
245
263
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking HTTP endpoint |
|
|
246
264
|
| `apiKey` | empty | OpenViking API key |
|
|
247
|
-
| `peer_role` | `
|
|
265
|
+
| `peer_role` | `none` | Memory scope: `none` (shared `viking://user/<user_id>/memories`), `assistant` (`.../peers/<assistant_id>/memories`), or `sender` (`.../peers/<sender_id>/memories`). Legacy `person` is accepted as `sender`. Session messages use body `peer_id`; data-plane recall/search uses `X-OpenViking-Actor-Peer`. |
|
|
248
266
|
| `peer_prefix` | empty | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant`. |
|
|
249
267
|
| `accountId` | empty | Required when using a root API key |
|
|
250
268
|
| `userId` | empty | Required when using a root API key |
|
|
@@ -311,7 +329,7 @@ Useful backup/source flags:
|
|
|
311
329
|
| `--current-version` | Print the version tracked by the helper |
|
|
312
330
|
| `--base-url URL` | OpenViking server URL (enables non-interactive mode) |
|
|
313
331
|
| `--api-key KEY` | OpenViking API key |
|
|
314
|
-
| `--peer-role ROLE` |
|
|
332
|
+
| `--peer-role ROLE` | Memory scope: `none`, `assistant`, or `sender`; legacy `person` is accepted as `sender` |
|
|
315
333
|
| `--peer-prefix PREFIX` | Prefix for assistant `peer_id` / actor peer values |
|
|
316
334
|
| `--update` | Update an existing helper-managed install |
|
|
317
335
|
|
|
@@ -356,7 +374,7 @@ The plugin connects to an existing remote OpenViking server.
|
|
|
356
374
|
| --- | --- | --- |
|
|
357
375
|
| `baseUrl` | `http://127.0.0.1:1933` | Remote OpenViking HTTP endpoint |
|
|
358
376
|
| `apiKey` | empty | Optional OpenViking API key |
|
|
359
|
-
| `peer_role` | `
|
|
377
|
+
| `peer_role` | `none` | Memory scope: `none`, `assistant`, or `sender`; legacy `person` is accepted as `sender`. Session messages use body `peer_id`, while data-plane recall/search uses `X-OpenViking-Actor-Peer` |
|
|
360
378
|
| `peer_prefix` | empty | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant` |
|
|
361
379
|
|
|
362
380
|
Common settings:
|
package/README.md
CHANGED
|
@@ -124,15 +124,26 @@ The main rules are:
|
|
|
124
124
|
- reuse `sessionId` directly when it is already a UUID
|
|
125
125
|
- prefer `sessionKey` when deriving a stable `ovSessionId`
|
|
126
126
|
- normalize unsafe path characters, or fall back to a stable SHA-256 when needed
|
|
127
|
-
- `peer_role=
|
|
128
|
-
- `peer_role=
|
|
129
|
-
- `peer_role=
|
|
130
|
-
-
|
|
127
|
+
- `peer_role=none` is the default: messages have no peer attribution and memory stays in the shared user scope, for example `viking://user/alice/memories/...`; no peer-specific memory subtree is used
|
|
128
|
+
- `peer_role=assistant` writes assistant messages with `peer_id=<sessionAgent>` and uses peer-scoped memory such as `viking://user/alice/peers/main/memories/...`; if `peer_prefix` is set, the peer id becomes `<peer_prefix>_<sessionAgent>`
|
|
129
|
+
- `peer_role=sender` writes user messages with the OpenClaw sender identity as `peer_id` and uses peer-scoped memory such as `viking://user/support-agent/peers/customer-42/memories/...`; assistant messages do not get `peer_id`
|
|
130
|
+
- `person` remains accepted as a legacy config alias for `sender`, but new configuration and documentation use `sender`
|
|
131
|
+
- data-plane recall/search/read/import/delete sends the same resolved peer identity as `X-OpenViking-Actor-Peer` when `peer_role` is `assistant` or `sender`
|
|
131
132
|
- when OpenClaw does not provide a session agent, use its default agent `main` for local session and assistant peer metadata
|
|
132
133
|
- only add `X-OpenViking-Account` / `X-OpenViking-User` when `accountId` / `userId` are explicitly configured
|
|
133
134
|
|
|
134
135
|
This matters because OpenViking tenant identity is account/user-scoped, while OpenClaw agent identity is runtime metadata.
|
|
135
136
|
|
|
137
|
+
Choose the scope from what `viking://user/<user_id>` represents:
|
|
138
|
+
|
|
139
|
+
| Model | Example | Result |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| General/shared (`none`) | `user_id=alice` uses any OpenClaw assistant | Shared user memory under `viking://user/alice/memories/...` |
|
|
142
|
+
| Human is the OpenViking user (`assistant`) | Alice uses OpenClaw assistants `main` and `research` | Assistant-scoped memories are separated under `.../peers/main/memories/...` and `.../peers/research/memories/...` |
|
|
143
|
+
| Agent is the OpenViking user (`sender`) | `user_id=support-agent` receives messages from `customer-42` and `customer-99` | Sender-scoped memories are separated under `.../peers/customer-42/memories/...` and `.../peers/customer-99/memories/...` |
|
|
144
|
+
|
|
145
|
+
OpenViking creates the managed `peers/` container as part of the user namespace. `none` means that the plugin does not create or route into a specific `peers/<peer_id>/memories` subtree. With `assistant` or `sender`, actor-peer recall includes the shared user memory plus the current peer's memory; changing the setting does not move existing memories.
|
|
146
|
+
|
|
136
147
|
The recommended remote-mode configuration only needs:
|
|
137
148
|
|
|
138
149
|
- `baseUrl`
|
|
@@ -143,7 +154,7 @@ The recommended remote-mode configuration only needs:
|
|
|
143
154
|
In this setup:
|
|
144
155
|
|
|
145
156
|
- `apiKey` should usually be a user key
|
|
146
|
-
- new installs default to `peer_role=
|
|
157
|
+
- new installs default to `peer_role=none`
|
|
147
158
|
- `accountId` / `userId` are advanced options only when the deployment needs explicit identity headers, such as root-key or trusted-server flows
|
|
148
159
|
|
|
149
160
|
### User namespace
|
|
@@ -164,16 +175,9 @@ During recall, the plugin:
|
|
|
164
175
|
1. Extracts query text from the latest user message.
|
|
165
176
|
2. Resolves the agent routing for the current `sessionId/sessionKey`.
|
|
166
177
|
3. Runs a quick availability precheck so model requests do not stall when OpenViking is unavailable.
|
|
167
|
-
4.
|
|
168
|
-
5.
|
|
169
|
-
6. Prepends the
|
|
170
|
-
|
|
171
|
-
The reranking logic is not pure vector-score sorting. The current implementation also considers:
|
|
172
|
-
|
|
173
|
-
- whether a result is a leaf memory with `level == 2`
|
|
174
|
-
- whether it looks like a preference memory
|
|
175
|
-
- whether it looks like an event memory
|
|
176
|
-
- lexical overlap with the current query
|
|
178
|
+
4. Sends one session-aware context search for the configured `recallTargetTypes` (`user,agent` by default; optionally `resource`; use `ov_archive_search` and `ov_archive_expand` to inspect raw session history).
|
|
179
|
+
5. Lets OpenViking expand the query from session history, filter and rank candidates, apply cross-turn deduplication, select detail tiers, and assemble the result under the injection budget.
|
|
180
|
+
6. Prepends the server-rendered context inside `<relevant-memories>` to the current user message; it does not append a standalone synthetic user message.
|
|
177
181
|
|
|
178
182
|
## Session Lifecycle
|
|
179
183
|
|
package/auto-recall.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FindResultItem, OpenVikingClient, SearchContextEntry } from "./client.js";
|
|
2
2
|
import type { MemoryOpenVikingConfig } from "./config.js";
|
|
3
3
|
import type { EffectiveQueryConfig } from "./query-config.js";
|
|
4
|
-
import {
|
|
5
|
-
pickMemoriesForInjection,
|
|
6
|
-
postProcessMemories,
|
|
7
|
-
summarizeInjectionMemories,
|
|
8
|
-
toJsonLog,
|
|
9
|
-
} from "./memory-ranking.js";
|
|
4
|
+
import { toJsonLog } from "./memory-ranking.js";
|
|
10
5
|
import { quickRecallPrecheck, withTimeout } from "./process-manager.js";
|
|
11
6
|
import {
|
|
12
7
|
resolveRecallSearchPlan,
|
|
@@ -20,6 +15,8 @@ import { sanitizeUserTextForCapture } from "./text-utils.js";
|
|
|
20
15
|
import { estimateTextTokens } from "./token-estimator.js";
|
|
21
16
|
|
|
22
17
|
const RECALL_QUERY_MAX_CHARS = 4_000;
|
|
18
|
+
const AUTO_RECALL_DEDUP_TURNS = 5;
|
|
19
|
+
const LEGACY_CHARS_PER_TOKEN = 4;
|
|
23
20
|
export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
|
|
24
21
|
|
|
25
22
|
type Logger = {
|
|
@@ -231,16 +228,26 @@ function preview(value: string | undefined, maxChars: number): string | undefine
|
|
|
231
228
|
return trimmed.length > maxChars ? trimmed.slice(0, maxChars) : trimmed;
|
|
232
229
|
}
|
|
233
230
|
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
function traceResourceType(entry: SearchContextEntry): RecallResourceType {
|
|
232
|
+
if (entry.category === "resources" || entry.uri.startsWith("viking://resources")) {
|
|
233
|
+
return "resource";
|
|
234
|
+
}
|
|
235
|
+
if (entry.origin === "actor_peer") {
|
|
236
|
+
return "agent";
|
|
237
|
+
}
|
|
238
|
+
return "user";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toTraceResult(entry: SearchContextEntry): RecallTraceResult {
|
|
242
|
+
const resourceType = traceResourceType(entry);
|
|
243
|
+
return {
|
|
244
|
+
uri: entry.uri,
|
|
237
245
|
resourceType,
|
|
238
|
-
category:
|
|
239
|
-
score:
|
|
240
|
-
|
|
241
|
-
abstractPreview: preview(item.abstract ?? item.overview, 240),
|
|
246
|
+
category: entry.category,
|
|
247
|
+
score: entry.score,
|
|
248
|
+
abstractPreview: preview(entry.text, 240),
|
|
242
249
|
resultType: resourceType === "resource" ? "resource" : "memory",
|
|
243
|
-
}
|
|
250
|
+
};
|
|
244
251
|
}
|
|
245
252
|
|
|
246
253
|
function boundTraceQuery(query: string, maxChars: number): { query: string; queryTruncated?: boolean } {
|
|
@@ -340,7 +347,6 @@ export async function buildAutoRecallContext(params: {
|
|
|
340
347
|
|
|
341
348
|
return withTimeout(
|
|
342
349
|
(async () => {
|
|
343
|
-
const candidateLimit = queryConfig?.candidateLimit ?? Math.max(cfg.recallLimit * 4, 20);
|
|
344
350
|
const scoreThreshold = queryConfig?.scoreThreshold ?? cfg.recallScoreThreshold;
|
|
345
351
|
const recallLimit = queryConfig?.recallLimit ?? cfg.recallLimit;
|
|
346
352
|
const maxInjectedChars = queryConfig?.maxInjectedChars ?? cfg.recallMaxInjectedChars;
|
|
@@ -349,98 +355,88 @@ export async function buildAutoRecallContext(params: {
|
|
|
349
355
|
ovSessionId: params.ovSessionId,
|
|
350
356
|
agentId,
|
|
351
357
|
});
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
contextType: search.contextType,
|
|
358
|
+
const contextTypes = [...new Set(searchPlan.searches.map((search) => search.contextType))];
|
|
359
|
+
const maxTokens = Math.min(
|
|
360
|
+
32_000,
|
|
361
|
+
Math.max(64, Math.round(maxInjectedChars / LEGACY_CHARS_PER_TOKEN)),
|
|
362
|
+
);
|
|
363
|
+
const startedAt = Date.now();
|
|
364
|
+
let contextResult: Awaited<ReturnType<OpenVikingClient["searchContext"]>> | undefined;
|
|
365
|
+
let requestError: unknown;
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
contextResult = await client.searchContext(queryText, {
|
|
369
|
+
sessionId: params.ovSessionId,
|
|
370
|
+
limit: recallLimit,
|
|
371
|
+
scoreThreshold,
|
|
372
|
+
contextType: contextTypes.length === 1 ? contextTypes[0] : contextTypes,
|
|
373
|
+
queryExpansion: "auto",
|
|
374
|
+
maxTokens,
|
|
375
|
+
detail: recallPreferAbstract ? "abstract" : undefined,
|
|
376
|
+
dedupTurns: params.ovSessionId ? AUTO_RECALL_DEDUP_TURNS : undefined,
|
|
377
|
+
peerScope: "actor",
|
|
373
378
|
actorPeerId,
|
|
379
|
+
requestTimeoutMs: cfg.autoRecallTimeoutMs,
|
|
374
380
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
targetUri: search.targetUri,
|
|
378
|
-
result,
|
|
379
|
-
durationMs: Date.now() - start,
|
|
380
|
-
};
|
|
381
|
-
});
|
|
382
|
-
const autoRecallSettled = await Promise.allSettled(autoRecallPromises);
|
|
383
|
-
|
|
384
|
-
const allMemories: FindResultItem[] = [];
|
|
385
|
-
for (const s of autoRecallSettled) {
|
|
386
|
-
if (s.status === "fulfilled") {
|
|
387
|
-
const result = s.value.result ?? {};
|
|
388
|
-
const memories = result.memories ?? [];
|
|
389
|
-
const resources = result.resources ?? [];
|
|
390
|
-
allMemories.push(...memories, ...resources);
|
|
391
|
-
traceSearches.push({
|
|
392
|
-
resourceType: s.value.resourceType,
|
|
393
|
-
targetUriInput: s.value.targetUri,
|
|
394
|
-
limit: candidateLimit,
|
|
395
|
-
scoreThreshold: 0,
|
|
396
|
-
durationMs: s.value.durationMs,
|
|
397
|
-
total: result.total ?? memories.length + resources.length + (result.skills?.length ?? 0),
|
|
398
|
-
results: [
|
|
399
|
-
...toTraceResults(memories, s.value.resourceType),
|
|
400
|
-
...toTraceResults(resources, "resource"),
|
|
401
|
-
...(result.skills ?? []).map((item): RecallTraceResult => ({
|
|
402
|
-
uri: item.uri,
|
|
403
|
-
resourceType: s.value.resourceType,
|
|
404
|
-
category: item.category,
|
|
405
|
-
score: item.score,
|
|
406
|
-
level: item.level,
|
|
407
|
-
abstractPreview: preview(item.abstract ?? item.overview, 240),
|
|
408
|
-
resultType: "skill",
|
|
409
|
-
})),
|
|
410
|
-
].slice(0, cfg.traceRecallMaxResultsPerSearch),
|
|
411
|
-
});
|
|
412
|
-
} else {
|
|
413
|
-
logger.warn?.(`openviking: auto-recall search failed: ${String(s.reason)}`);
|
|
414
|
-
const failedIndex = traceSearches.length;
|
|
415
|
-
const search = searchPlan.searches[failedIndex - searchPlan.skipped.length];
|
|
416
|
-
traceSearches.push({
|
|
417
|
-
resourceType: search?.resourceType ?? "user",
|
|
418
|
-
targetUriInput: search?.targetUri,
|
|
419
|
-
limit: candidateLimit,
|
|
420
|
-
scoreThreshold: 0,
|
|
421
|
-
durationMs: 0,
|
|
422
|
-
total: 0,
|
|
423
|
-
results: [],
|
|
424
|
-
error: String(s.reason),
|
|
425
|
-
});
|
|
426
|
-
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
requestError = error;
|
|
427
383
|
}
|
|
428
384
|
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
385
|
+
const durationMs = Date.now() - startedAt;
|
|
386
|
+
const entries = (contextResult?.entries ?? []).filter((entry) => Boolean(entry.uri));
|
|
387
|
+
const rawRetrievalErrors = contextResult?.stats?.retrieval_errors;
|
|
388
|
+
const retrievalErrors = Array.isArray(rawRetrievalErrors)
|
|
389
|
+
? rawRetrievalErrors.map((error) => String(error))
|
|
390
|
+
: [];
|
|
391
|
+
const searchError = requestError
|
|
392
|
+
? String(requestError)
|
|
393
|
+
: retrievalErrors.length > 0
|
|
394
|
+
? retrievalErrors.join("; ")
|
|
395
|
+
: undefined;
|
|
396
|
+
|
|
397
|
+
if (searchError) {
|
|
398
|
+
logger.warn?.(`openviking: auto-recall context search failed: ${searchError}`);
|
|
399
|
+
}
|
|
442
400
|
|
|
443
|
-
const
|
|
401
|
+
const traceSearches: RecallTraceEntry["searches"] = [
|
|
402
|
+
...searchPlan.skipped.map((skipped) => ({
|
|
403
|
+
resourceType: skipped.resourceType,
|
|
404
|
+
limit: recallLimit,
|
|
405
|
+
scoreThreshold,
|
|
406
|
+
durationMs: 0,
|
|
407
|
+
total: 0,
|
|
408
|
+
results: [],
|
|
409
|
+
error: skipped.reason,
|
|
410
|
+
})),
|
|
411
|
+
...searchPlan.searches.map((search) => {
|
|
412
|
+
const matchingEntries = entries.filter((entry) =>
|
|
413
|
+
search.contextType === "resource"
|
|
414
|
+
? traceResourceType(entry) === "resource"
|
|
415
|
+
: traceResourceType(entry) !== "resource"
|
|
416
|
+
);
|
|
417
|
+
return {
|
|
418
|
+
resourceType: search.resourceType,
|
|
419
|
+
contextType: search.contextType,
|
|
420
|
+
limit: recallLimit,
|
|
421
|
+
scoreThreshold,
|
|
422
|
+
durationMs,
|
|
423
|
+
total: matchingEntries.length,
|
|
424
|
+
results: matchingEntries
|
|
425
|
+
.map(toTraceResult)
|
|
426
|
+
.slice(0, cfg.traceRecallMaxResultsPerSearch),
|
|
427
|
+
error: searchError,
|
|
428
|
+
};
|
|
429
|
+
}),
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
const candidateCount = typeof contextResult?.stats?.candidates === "number"
|
|
433
|
+
? contextResult.stats.candidates
|
|
434
|
+
: entries.length;
|
|
435
|
+
const recordTrace = async (
|
|
436
|
+
injectedEntries: SearchContextEntry[],
|
|
437
|
+
injectedCount: number,
|
|
438
|
+
estimatedTokens?: number,
|
|
439
|
+
) => {
|
|
444
440
|
const entry: RecallTraceEntry = {
|
|
445
441
|
schemaVersion: "1.0",
|
|
446
442
|
traceId: newTraceId(),
|
|
@@ -459,17 +455,17 @@ export async function buildAutoRecallContext(params: {
|
|
|
459
455
|
queryTruncated: params.queryTruncated || queryText.length > cfg.traceRecallQueryMaxChars,
|
|
460
456
|
},
|
|
461
457
|
searches: traceSearches,
|
|
462
|
-
selected:
|
|
463
|
-
uri:
|
|
464
|
-
resourceType:
|
|
465
|
-
category:
|
|
466
|
-
score:
|
|
467
|
-
abstractPreview: preview(
|
|
458
|
+
selected: injectedEntries.map((contextEntry) => ({
|
|
459
|
+
uri: contextEntry.uri,
|
|
460
|
+
resourceType: traceResourceType(contextEntry),
|
|
461
|
+
category: contextEntry.category,
|
|
462
|
+
score: contextEntry.score,
|
|
463
|
+
abstractPreview: preview(contextEntry.text, cfg.traceRecallPreviewChars),
|
|
468
464
|
injected: true,
|
|
469
465
|
})),
|
|
470
466
|
stats: {
|
|
471
|
-
candidateCount
|
|
472
|
-
selectedCount:
|
|
467
|
+
candidateCount,
|
|
468
|
+
selectedCount: injectedEntries.length,
|
|
473
469
|
injectedCount,
|
|
474
470
|
estimatedTokens,
|
|
475
471
|
},
|
|
@@ -478,39 +474,34 @@ export async function buildAutoRecallContext(params: {
|
|
|
478
474
|
params.traceRecorder?.record(entry);
|
|
479
475
|
};
|
|
480
476
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return { memoryCount: 0, estimatedTokens: 0 };
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(
|
|
487
|
-
memories,
|
|
488
|
-
(uri) => client.read(uri, actorPeerId),
|
|
489
|
-
{
|
|
490
|
-
recallPreferAbstract,
|
|
491
|
-
recallMaxInjectedChars: maxInjectedChars,
|
|
492
|
-
includeUri: true,
|
|
493
|
-
},
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
if (memoryLines.length === 0) {
|
|
497
|
-
verbose?.(
|
|
498
|
-
`openviking: skipping auto-recall injection; no complete memories fit maxInjectedChars=${maxInjectedChars}`,
|
|
499
|
-
);
|
|
477
|
+
const rendered = contextResult?.rendered?.trim() ?? "";
|
|
478
|
+
if (requestError || entries.length === 0 || !rendered) {
|
|
500
479
|
await recordTrace([], 0, 0);
|
|
501
480
|
return { memoryCount: 0, estimatedTokens: 0 };
|
|
502
481
|
}
|
|
503
482
|
|
|
504
|
-
const
|
|
483
|
+
const usedTokens = contextResult?.stats?.used_tokens;
|
|
484
|
+
const estimatedTokens = typeof usedTokens === "number" && Number.isFinite(usedTokens)
|
|
485
|
+
? Math.max(0, Math.ceil(usedTokens))
|
|
486
|
+
: estimateTokenCount(rendered);
|
|
487
|
+
const block = buildRecallContextBlock([rendered]);
|
|
505
488
|
verbose?.(
|
|
506
|
-
`openviking: injecting ${
|
|
489
|
+
`openviking: injecting ${entries.length} memories (${block.length} chars, ~${estimatedTokens} tokens, serverMaxTokens=${maxTokens})`,
|
|
507
490
|
);
|
|
508
491
|
verbose?.(
|
|
509
|
-
`openviking: inject-detail ${toJsonLog({
|
|
492
|
+
`openviking: inject-detail ${toJsonLog({
|
|
493
|
+
count: entries.length,
|
|
494
|
+
entries: entries.map((contextEntry) => ({
|
|
495
|
+
uri: contextEntry.uri,
|
|
496
|
+
category: contextEntry.category,
|
|
497
|
+
score: contextEntry.score,
|
|
498
|
+
detail: contextEntry.detail,
|
|
499
|
+
})),
|
|
500
|
+
})}`,
|
|
510
501
|
);
|
|
511
502
|
|
|
512
|
-
await recordTrace(
|
|
513
|
-
return { block, memoryCount:
|
|
503
|
+
await recordTrace(entries, entries.length, estimatedTokens);
|
|
504
|
+
return { block, memoryCount: entries.length, estimatedTokens };
|
|
514
505
|
})(),
|
|
515
506
|
cfg.autoRecallTimeoutMs,
|
|
516
507
|
"openviking: auto-recall search timeout",
|