@openviking/openclaw-plugin 2026.9.2 → 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 +16 -5
- package/commands/setup.ts +22 -8
- package/config.ts +23 -10
- package/context-engine.ts +2 -2
- package/dist/commands/setup.js +15 -6
- package/dist/config.js +12 -7
- 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-core.mjs +60 -0
- package/openclaw.plugin.json +7 -6
- package/package.json +1 -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-core.mjs +65 -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
|
package/commands/setup.ts
CHANGED
|
@@ -84,8 +84,8 @@ const CONFIG_KEYS_TO_PRESERVE = [
|
|
|
84
84
|
"logFindRequests",
|
|
85
85
|
] as const;
|
|
86
86
|
|
|
87
|
-
type PeerRole = "none" | "assistant" | "
|
|
88
|
-
const DEFAULT_SETUP_PEER_ROLE: PeerRole = "
|
|
87
|
+
type PeerRole = "none" | "assistant" | "sender";
|
|
88
|
+
const DEFAULT_SETUP_PEER_ROLE: PeerRole = "none";
|
|
89
89
|
type RecallTargetType = "resource" | "user" | "agent";
|
|
90
90
|
const ALLOWED_RECALL_TARGET_TYPES = ["resource", "user", "agent"] as const;
|
|
91
91
|
|
|
@@ -126,7 +126,8 @@ function isValidPeerPrefixInput(value: string): boolean {
|
|
|
126
126
|
function normalizePeerRole(value: unknown): PeerRole | undefined {
|
|
127
127
|
if (typeof value !== "string") return undefined;
|
|
128
128
|
const role = value.trim().toLowerCase();
|
|
129
|
-
if (role === "
|
|
129
|
+
if (role === "person") return "sender";
|
|
130
|
+
if (role === "none" || role === "assistant" || role === "sender") return role;
|
|
130
131
|
return undefined;
|
|
131
132
|
}
|
|
132
133
|
|
|
@@ -147,7 +148,7 @@ function resolveSetupPeerRole(value: unknown): PeerRole {
|
|
|
147
148
|
if (value === undefined) return DEFAULT_SETUP_PEER_ROLE;
|
|
148
149
|
const role = normalizePeerRole(value);
|
|
149
150
|
if (role) return role;
|
|
150
|
-
throw new Error('peer_role must be "none", "assistant", or "person"');
|
|
151
|
+
throw new Error('peer_role must be "none", "assistant", or "sender" (legacy alias: "person")');
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
function normalizeSetupRecallTargetTypes(value: unknown): RecallTargetType[] | undefined {
|
|
@@ -206,9 +207,20 @@ async function askPeerRole(
|
|
|
206
207
|
q: (prompt: string, def?: string) => Promise<string>,
|
|
207
208
|
defaultValue: PeerRole,
|
|
208
209
|
): Promise<PeerRole> {
|
|
210
|
+
console.log(
|
|
211
|
+
` ${tr(
|
|
212
|
+
zh,
|
|
213
|
+
"Memory scope — none (default): viking://user/<user_id>/memories, shared across all conversations; " +
|
|
214
|
+
"assistant: viking://user/<user_id>/peers/<assistant_id>/memories; " +
|
|
215
|
+
"sender: viking://user/<user_id>/peers/<sender_id>/memories (legacy alias: person).",
|
|
216
|
+
"记忆归属 —— none(默认):viking://user/<user_id>/memories,所有对话共享;" +
|
|
217
|
+
"assistant:viking://user/<user_id>/peers/<assistant_id>/memories;" +
|
|
218
|
+
"sender:viking://user/<user_id>/peers/<sender_id>/memories(兼容旧值 person)。",
|
|
219
|
+
)}`,
|
|
220
|
+
);
|
|
209
221
|
while (true) {
|
|
210
222
|
const value = await q(
|
|
211
|
-
tr(zh, "
|
|
223
|
+
tr(zh, "Memory scope (none/assistant/sender)", "记忆归属(none/assistant/sender)"),
|
|
212
224
|
defaultValue,
|
|
213
225
|
);
|
|
214
226
|
const role = normalizePeerRole(value);
|
|
@@ -216,8 +228,8 @@ async function askPeerRole(
|
|
|
216
228
|
console.log(
|
|
217
229
|
` ✗ ${tr(
|
|
218
230
|
zh,
|
|
219
|
-
'
|
|
220
|
-
'
|
|
231
|
+
'Memory scope must be "none", "assistant", or "sender" (legacy "person" is also accepted).',
|
|
232
|
+
'记忆归属必须是 "none"、"assistant" 或 "sender"(也兼容旧值 "person")。',
|
|
221
233
|
)}`,
|
|
222
234
|
);
|
|
223
235
|
}
|
|
@@ -605,7 +617,7 @@ export function registerSetupCli(api: any): void {
|
|
|
605
617
|
.option("--zh", "Chinese prompts")
|
|
606
618
|
.option("--base-url <url>", "OpenViking server URL (enables non-interactive mode)")
|
|
607
619
|
.option("--api-key <key>", "API key for authentication")
|
|
608
|
-
.option("--peer-role <role>", "
|
|
620
|
+
.option("--peer-role <role>", "Memory scope: none (shared), assistant (per assistant), or sender (per sender); person is a legacy alias")
|
|
609
621
|
.option("--peer-prefix <prefix>", "Prefix for assistant peer_id values")
|
|
610
622
|
.option("--account-id <id>", "Account ID (required for root API keys)")
|
|
611
623
|
.option("--user-id <id>", "User ID (required for root API keys)")
|
|
@@ -1182,6 +1194,8 @@ async function setupRemote(
|
|
|
1182
1194
|
export const __test__ = {
|
|
1183
1195
|
isLegacyLocalMode,
|
|
1184
1196
|
isValidPeerPrefixInput,
|
|
1197
|
+
normalizePeerRole,
|
|
1198
|
+
resolveSetupPeerRole,
|
|
1185
1199
|
normalizeSetupRecallTargetTypes,
|
|
1186
1200
|
activateContextEngineSlot,
|
|
1187
1201
|
isContextEngineSlotActive,
|
package/config.ts
CHANGED
|
@@ -30,7 +30,8 @@ export type OpenVikingSecretRef =
|
|
|
30
30
|
export type MemoryOpenVikingConfig = {
|
|
31
31
|
mode?: "remote";
|
|
32
32
|
baseUrl?: string;
|
|
33
|
-
|
|
33
|
+
/** `person` is a legacy alias for `sender`. */
|
|
34
|
+
peer_role?: "none" | "assistant" | "sender" | "person";
|
|
34
35
|
peer_prefix?: string;
|
|
35
36
|
apiKey?: string | OpenVikingSecretRef;
|
|
36
37
|
/** Optional HTTP headers merged into every OpenViking request. */
|
|
@@ -127,10 +128,12 @@ export type MemoryOpenVikingConfig = {
|
|
|
127
128
|
|
|
128
129
|
/** Runtime config after memoryOpenVikingConfigSchema.parse() has applied defaults. */
|
|
129
130
|
export type ParsedMemoryOpenVikingConfig = Required<
|
|
130
|
-
Omit<MemoryOpenVikingConfig, "agentExperience" | "recallTargetTypes" | "apiKey">
|
|
131
|
+
Omit<MemoryOpenVikingConfig, "agentExperience" | "recallTargetTypes" | "apiKey" | "peer_role">
|
|
131
132
|
> & {
|
|
132
133
|
/** parse() resolves SecretRef values, so the runtime shape is always a plain string. */
|
|
133
134
|
apiKey: string;
|
|
135
|
+
/** Runtime uses the canonical name; legacy `person` input normalizes to `sender`. */
|
|
136
|
+
peer_role: "none" | "assistant" | "sender";
|
|
134
137
|
agentExperience: Required<NonNullable<MemoryOpenVikingConfig["agentExperience"]>>;
|
|
135
138
|
recallTargetTypes: Array<"resource" | "user" | "agent">;
|
|
136
139
|
};
|
|
@@ -152,7 +155,7 @@ const DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO = 0.5;
|
|
|
152
155
|
const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
|
|
153
156
|
const DEFAULT_BYPASS_SESSION_PATTERNS: string[] = [];
|
|
154
157
|
const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
|
|
155
|
-
const DEFAULT_PEER_ROLE = "
|
|
158
|
+
const DEFAULT_PEER_ROLE = "none" as const;
|
|
156
159
|
const DEFAULT_PEER_PREFIX = "";
|
|
157
160
|
const DEFAULT_TRACE_RECALL_DIR = "~/.openclaw/openviking/recall-traces";
|
|
158
161
|
const DEFAULT_TRACE_RECALL_RETENTION_DAYS = 14;
|
|
@@ -292,16 +295,23 @@ function resolveSecret(
|
|
|
292
295
|
}
|
|
293
296
|
}
|
|
294
297
|
|
|
295
|
-
function resolvePeerRole(configured: unknown) {
|
|
298
|
+
function resolvePeerRole(configured: unknown): "none" | "assistant" | "sender" {
|
|
296
299
|
if (typeof configured === "string") {
|
|
297
300
|
const role = configured.trim().toLowerCase();
|
|
298
|
-
if (role === "none" || role === "assistant" || role === "
|
|
301
|
+
if (role === "none" || role === "assistant" || role === "sender") {
|
|
299
302
|
return role;
|
|
300
303
|
}
|
|
301
|
-
|
|
304
|
+
if (role === "person") {
|
|
305
|
+
return "sender";
|
|
306
|
+
}
|
|
307
|
+
throw new Error(
|
|
308
|
+
`openviking peer_role must be "none", "assistant", or "sender" (legacy alias: "person")`,
|
|
309
|
+
);
|
|
302
310
|
}
|
|
303
311
|
if (configured !== undefined) {
|
|
304
|
-
throw new Error(
|
|
312
|
+
throw new Error(
|
|
313
|
+
`openviking peer_role must be "none", "assistant", or "sender" (legacy alias: "person")`,
|
|
314
|
+
);
|
|
305
315
|
}
|
|
306
316
|
return DEFAULT_PEER_ROLE;
|
|
307
317
|
}
|
|
@@ -776,14 +786,17 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
776
786
|
help: "HTTP URL when mode is remote (or use ${OPENVIKING_BASE_URL})",
|
|
777
787
|
},
|
|
778
788
|
peer_role: {
|
|
779
|
-
label: "
|
|
789
|
+
label: "Memory Scope (peer_role)",
|
|
780
790
|
placeholder: DEFAULT_PEER_ROLE,
|
|
781
|
-
help:
|
|
791
|
+
help:
|
|
792
|
+
'Where peer-scoped memories are stored. "none" (default): viking://user/<user_id>/memories. ' +
|
|
793
|
+
'"assistant": viking://user/<user_id>/peers/<assistant_id>/memories. ' +
|
|
794
|
+
'"sender": viking://user/<user_id>/peers/<sender_id>/memories. Legacy "person" is accepted as "sender".',
|
|
782
795
|
},
|
|
783
796
|
peer_prefix: {
|
|
784
797
|
label: "Peer Prefix",
|
|
785
798
|
placeholder: "optional-prefix",
|
|
786
|
-
help: "
|
|
799
|
+
help: 'Only used when Memory Scope is "assistant". Prefix added to the peer id derived from the OpenClaw agent id.',
|
|
787
800
|
},
|
|
788
801
|
apiKey: {
|
|
789
802
|
label: "OpenViking API Key",
|
package/context-engine.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenVikingClient } from "./client.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ParsedMemoryOpenVikingConfig } from "./config.js";
|
|
3
3
|
import type { RuntimeQueryConfigStore } from "./query-config.js";
|
|
4
4
|
import {
|
|
5
5
|
AUTO_RECALL_SOURCE_MARKER,
|
|
@@ -248,7 +248,7 @@ export function createMemoryOpenVikingContextEngine(params: {
|
|
|
248
248
|
id: string;
|
|
249
249
|
name: string;
|
|
250
250
|
version?: string;
|
|
251
|
-
cfg:
|
|
251
|
+
cfg: ParsedMemoryOpenVikingConfig;
|
|
252
252
|
logger: Logger;
|
|
253
253
|
getClient: () => Promise<OpenVikingClient>;
|
|
254
254
|
/** Extra args help match hook-populated routing when OpenClaw provides sessionKey / OV session id. */
|
package/dist/commands/setup.js
CHANGED
|
@@ -76,7 +76,7 @@ const CONFIG_KEYS_TO_PRESERVE = [
|
|
|
76
76
|
"emitStandardDiagnostics",
|
|
77
77
|
"logFindRequests",
|
|
78
78
|
];
|
|
79
|
-
const DEFAULT_SETUP_PEER_ROLE = "
|
|
79
|
+
const DEFAULT_SETUP_PEER_ROLE = "none";
|
|
80
80
|
const ALLOWED_RECALL_TARGET_TYPES = ["resource", "user", "agent"];
|
|
81
81
|
function tr(langZh, en, zh) {
|
|
82
82
|
return langZh ? zh : en;
|
|
@@ -98,7 +98,9 @@ function normalizePeerRole(value) {
|
|
|
98
98
|
if (typeof value !== "string")
|
|
99
99
|
return undefined;
|
|
100
100
|
const role = value.trim().toLowerCase();
|
|
101
|
-
if (role === "
|
|
101
|
+
if (role === "person")
|
|
102
|
+
return "sender";
|
|
103
|
+
if (role === "none" || role === "assistant" || role === "sender")
|
|
102
104
|
return role;
|
|
103
105
|
return undefined;
|
|
104
106
|
}
|
|
@@ -121,7 +123,7 @@ function resolveSetupPeerRole(value) {
|
|
|
121
123
|
const role = normalizePeerRole(value);
|
|
122
124
|
if (role)
|
|
123
125
|
return role;
|
|
124
|
-
throw new Error('peer_role must be "none", "assistant", or "person"');
|
|
126
|
+
throw new Error('peer_role must be "none", "assistant", or "sender" (legacy alias: "person")');
|
|
125
127
|
}
|
|
126
128
|
function normalizeSetupRecallTargetTypes(value) {
|
|
127
129
|
if (value === undefined || value === null || value === "") {
|
|
@@ -172,12 +174,17 @@ function preserveCurrentConfig(existing) {
|
|
|
172
174
|
return config;
|
|
173
175
|
}
|
|
174
176
|
async function askPeerRole(zh, q, defaultValue) {
|
|
177
|
+
console.log(` ${tr(zh, "Memory scope — none (default): viking://user/<user_id>/memories, shared across all conversations; " +
|
|
178
|
+
"assistant: viking://user/<user_id>/peers/<assistant_id>/memories; " +
|
|
179
|
+
"sender: viking://user/<user_id>/peers/<sender_id>/memories (legacy alias: person).", "记忆归属 —— none(默认):viking://user/<user_id>/memories,所有对话共享;" +
|
|
180
|
+
"assistant:viking://user/<user_id>/peers/<assistant_id>/memories;" +
|
|
181
|
+
"sender:viking://user/<user_id>/peers/<sender_id>/memories(兼容旧值 person)。")}`);
|
|
175
182
|
while (true) {
|
|
176
|
-
const value = await q(tr(zh, "
|
|
183
|
+
const value = await q(tr(zh, "Memory scope (none/assistant/sender)", "记忆归属(none/assistant/sender)"), defaultValue);
|
|
177
184
|
const role = normalizePeerRole(value);
|
|
178
185
|
if (role)
|
|
179
186
|
return role;
|
|
180
|
-
console.log(` ✗ ${tr(zh, '
|
|
187
|
+
console.log(` ✗ ${tr(zh, 'Memory scope must be "none", "assistant", or "sender" (legacy "person" is also accepted).', '记忆归属必须是 "none"、"assistant" 或 "sender"(也兼容旧值 "person")。')}`);
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
async function askPeerPrefix(zh, q, defaultValue) {
|
|
@@ -473,7 +480,7 @@ export function registerSetupCli(api) {
|
|
|
473
480
|
.option("--zh", "Chinese prompts")
|
|
474
481
|
.option("--base-url <url>", "OpenViking server URL (enables non-interactive mode)")
|
|
475
482
|
.option("--api-key <key>", "API key for authentication")
|
|
476
|
-
.option("--peer-role <role>", "
|
|
483
|
+
.option("--peer-role <role>", "Memory scope: none (shared), assistant (per assistant), or sender (per sender); person is a legacy alias")
|
|
477
484
|
.option("--peer-prefix <prefix>", "Prefix for assistant peer_id values")
|
|
478
485
|
.option("--account-id <id>", "Account ID (required for root API keys)")
|
|
479
486
|
.option("--user-id <id>", "User ID (required for root API keys)")
|
|
@@ -952,6 +959,8 @@ async function setupRemote(zh, configPath, existing, q) {
|
|
|
952
959
|
export const __test__ = {
|
|
953
960
|
isLegacyLocalMode,
|
|
954
961
|
isValidPeerPrefixInput,
|
|
962
|
+
normalizePeerRole,
|
|
963
|
+
resolveSetupPeerRole,
|
|
955
964
|
normalizeSetupRecallTargetTypes,
|
|
956
965
|
activateContextEngineSlot,
|
|
957
966
|
isContextEngineSlotActive,
|
package/dist/config.js
CHANGED
|
@@ -18,7 +18,7 @@ const DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO = 0.5;
|
|
|
18
18
|
const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
|
|
19
19
|
const DEFAULT_BYPASS_SESSION_PATTERNS = [];
|
|
20
20
|
const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
|
|
21
|
-
const DEFAULT_PEER_ROLE = "
|
|
21
|
+
const DEFAULT_PEER_ROLE = "none";
|
|
22
22
|
const DEFAULT_PEER_PREFIX = "";
|
|
23
23
|
const DEFAULT_TRACE_RECALL_DIR = "~/.openclaw/openviking/recall-traces";
|
|
24
24
|
const DEFAULT_TRACE_RECALL_RETENTION_DAYS = 14;
|
|
@@ -144,13 +144,16 @@ function resolveSecret(value, label) {
|
|
|
144
144
|
function resolvePeerRole(configured) {
|
|
145
145
|
if (typeof configured === "string") {
|
|
146
146
|
const role = configured.trim().toLowerCase();
|
|
147
|
-
if (role === "none" || role === "assistant" || role === "
|
|
147
|
+
if (role === "none" || role === "assistant" || role === "sender") {
|
|
148
148
|
return role;
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
if (role === "person") {
|
|
151
|
+
return "sender";
|
|
152
|
+
}
|
|
153
|
+
throw new Error(`openviking peer_role must be "none", "assistant", or "sender" (legacy alias: "person")`);
|
|
151
154
|
}
|
|
152
155
|
if (configured !== undefined) {
|
|
153
|
-
throw new Error(`openviking peer_role must be "none", "assistant", or "person"`);
|
|
156
|
+
throw new Error(`openviking peer_role must be "none", "assistant", or "sender" (legacy alias: "person")`);
|
|
154
157
|
}
|
|
155
158
|
return DEFAULT_PEER_ROLE;
|
|
156
159
|
}
|
|
@@ -481,14 +484,16 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
481
484
|
help: "HTTP URL when mode is remote (or use ${OPENVIKING_BASE_URL})",
|
|
482
485
|
},
|
|
483
486
|
peer_role: {
|
|
484
|
-
label: "
|
|
487
|
+
label: "Memory Scope (peer_role)",
|
|
485
488
|
placeholder: DEFAULT_PEER_ROLE,
|
|
486
|
-
help: '
|
|
489
|
+
help: 'Where peer-scoped memories are stored. "none" (default): viking://user/<user_id>/memories. ' +
|
|
490
|
+
'"assistant": viking://user/<user_id>/peers/<assistant_id>/memories. ' +
|
|
491
|
+
'"sender": viking://user/<user_id>/peers/<sender_id>/memories. Legacy "person" is accepted as "sender".',
|
|
487
492
|
},
|
|
488
493
|
peer_prefix: {
|
|
489
494
|
label: "Peer Prefix",
|
|
490
495
|
placeholder: "optional-prefix",
|
|
491
|
-
help: "
|
|
496
|
+
help: 'Only used when Memory Scope is "assistant". Prefix added to the peer id derived from the OpenClaw agent id.',
|
|
492
497
|
},
|
|
493
498
|
apiKey: {
|
|
494
499
|
label: "OpenViking API Key",
|
|
@@ -45,7 +45,7 @@ export function registerOpenVikingMemoryTools(deps) {
|
|
|
45
45
|
const peerId = resolveOpenVikingMessagePeerId({
|
|
46
46
|
peerRole: deps.peerRole,
|
|
47
47
|
role,
|
|
48
|
-
|
|
48
|
+
senderPeerId: session.actorPeerId,
|
|
49
49
|
assistantPeerId: session.actorPeerId,
|
|
50
50
|
});
|
|
51
51
|
await client.addSessionMessage(sessionId, role, [{ type: "text", text }], undefined, undefined, peerId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSessionAgentResolver, openClawSessionToOvStorageId, resolveOpenVikingActorPeerId, sanitizeOpenVikingPeerId, } from "../routing/identity-routing.js";
|
|
2
2
|
export function createOpenVikingSessionRoutingRuntime(options) {
|
|
3
|
-
const peerRole = options.peerRole ?? "
|
|
3
|
+
const peerRole = options.peerRole ?? "none";
|
|
4
4
|
const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
|
|
5
5
|
const rememberSessionAgentId = (ctx) => {
|
|
6
6
|
sessionAgentResolver.remember(ctx);
|
|
@@ -48,7 +48,7 @@ export function createOpenVikingSessionRoutingRuntime(options) {
|
|
|
48
48
|
agentId,
|
|
49
49
|
actorPeerId: resolveOpenVikingActorPeerId({
|
|
50
50
|
peerRole,
|
|
51
|
-
|
|
51
|
+
senderPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
|
|
52
52
|
assistantPeerId: agentId,
|
|
53
53
|
}),
|
|
54
54
|
};
|
|
@@ -12,8 +12,8 @@ export function sanitizeOpenVikingPeerId(raw) {
|
|
|
12
12
|
return normalized || undefined;
|
|
13
13
|
}
|
|
14
14
|
export function resolveOpenVikingMessagePeerId(params) {
|
|
15
|
-
if (params.peerRole === "
|
|
16
|
-
return params.
|
|
15
|
+
if (params.peerRole === "sender" && params.role === "user") {
|
|
16
|
+
return params.senderPeerId;
|
|
17
17
|
}
|
|
18
18
|
if (params.peerRole === "assistant" && params.role === "assistant") {
|
|
19
19
|
return params.assistantPeerId;
|
|
@@ -23,10 +23,10 @@ export function resolveOpenVikingMessagePeerId(params) {
|
|
|
23
23
|
export function resolveOpenVikingActorPeerId(params) {
|
|
24
24
|
const actorPeerId = resolveOpenVikingMessagePeerId({
|
|
25
25
|
...params,
|
|
26
|
-
role: params.peerRole === "
|
|
26
|
+
role: params.peerRole === "sender" ? "user" : "assistant",
|
|
27
27
|
});
|
|
28
|
-
if (params.peerRole === "
|
|
29
|
-
throw new Error("openviking: peer_role=
|
|
28
|
+
if (params.peerRole === "sender" && !actorPeerId) {
|
|
29
|
+
throw new Error("openviking: peer_role=sender requires a sender identity");
|
|
30
30
|
}
|
|
31
31
|
return actorPeerId;
|
|
32
32
|
}
|
|
@@ -243,8 +243,8 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
|
|
|
243
243
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
244
244
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
245
245
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
246
|
-
peerRole: cfg.peer_role ?? "
|
|
247
|
-
|
|
246
|
+
peerRole: cfg.peer_role ?? "none",
|
|
247
|
+
senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
|
|
248
248
|
assistantPeerId: agentId,
|
|
249
249
|
});
|
|
250
250
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
@@ -595,9 +595,9 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
|
|
|
595
595
|
});
|
|
596
596
|
if (ovParts.length > 0) {
|
|
597
597
|
await client.addSessionMessage(ovSessionId, msg.role, ovParts, undefined, createdAt, resolveOpenVikingMessagePeerId({
|
|
598
|
-
peerRole: cfg.peer_role ?? "
|
|
598
|
+
peerRole: cfg.peer_role ?? "none",
|
|
599
599
|
role: msg.role,
|
|
600
|
-
|
|
600
|
+
senderPeerId: senderRoleId,
|
|
601
601
|
assistantPeerId: agentId,
|
|
602
602
|
}));
|
|
603
603
|
}
|
|
@@ -397,6 +397,29 @@ export async function isContextFaceLegacy(path = stateFile("context-face.json"),
|
|
|
397
397
|
export async function markContextFaceLegacy(path = stateFile("context-face.json"), now = Date.now()) {
|
|
398
398
|
await writeJsonFile(path, { legacyUntil: now + LEGACY_CACHE_TTL_MS });
|
|
399
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* A `peer_scope` the server rejects is remembered the same way, but unlike the
|
|
402
|
+
* context face this one is not a silent capability probe: dropping the field
|
|
403
|
+
* widens recall from the caller's own peer to the whole user root, so doctor
|
|
404
|
+
* reads this file back and warns.
|
|
405
|
+
*/
|
|
406
|
+
export function peerScopeMemoPath() {
|
|
407
|
+
return stateFile("peer-scope.json");
|
|
408
|
+
}
|
|
409
|
+
export async function readPeerScopeDowngrade(path = peerScopeMemoPath(), now = Date.now()) {
|
|
410
|
+
const cached = await readJsonFile(path);
|
|
411
|
+
if (!cached?.legacyUntil || Number(cached.legacyUntil) <= now)
|
|
412
|
+
return null;
|
|
413
|
+
return cached;
|
|
414
|
+
}
|
|
415
|
+
export async function markPeerScopeDowngrade(scope, status, path = peerScopeMemoPath(), now = Date.now()) {
|
|
416
|
+
await writeJsonFile(path, {
|
|
417
|
+
legacyUntil: now + LEGACY_CACHE_TTL_MS,
|
|
418
|
+
scope: String(scope || ""),
|
|
419
|
+
status: Number(status) || 0,
|
|
420
|
+
at: now,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
400
423
|
function looksLikeUnknownField(res) {
|
|
401
424
|
const text = JSON.stringify(res?.error ?? res?.result ?? res?.detail ?? "").toLowerCase();
|
|
402
425
|
return text.includes("extra") || text.includes("mode") || text.includes("unexpected");
|
|
@@ -532,7 +555,13 @@ async function recallViaEndpoint(fetchJSON, cfg, query, actorPeerId = "", log =
|
|
|
532
555
|
export async function postRecall(fetchJSON, body, opts = {}) {
|
|
533
556
|
const actorPeerId = opts.actorPeerId || "";
|
|
534
557
|
const log = opts.log || (() => { });
|
|
558
|
+
const memoPath = opts.peerScopeMemoPath;
|
|
535
559
|
const request = { ...body };
|
|
560
|
+
// A remembered downgrade is still a downgrade: recall runs wider than the
|
|
561
|
+
// caller asked for, so the memo doubles as the doctor's evidence.
|
|
562
|
+
if (request.peer_scope && await readPeerScopeDowngrade(memoPath)) {
|
|
563
|
+
delete request.peer_scope;
|
|
564
|
+
}
|
|
536
565
|
const res = await fetchJSON("/api/v1/search/recall", {
|
|
537
566
|
method: "POST",
|
|
538
567
|
body: JSON.stringify(request),
|
|
@@ -540,15 +569,46 @@ export async function postRecall(fetchJSON, body, opts = {}) {
|
|
|
540
569
|
if (!request.peer_scope || (res.status !== 400 && res.status !== 422)) {
|
|
541
570
|
return res;
|
|
542
571
|
}
|
|
572
|
+
// Only an unknown-field rejection means "this server predates peer_scope".
|
|
573
|
+
// Retrying every other 400/422 without it silently widens the search from
|
|
574
|
+
// the caller's own peer to the whole user root.
|
|
575
|
+
if (!looksLikeUnknownField(res)) {
|
|
576
|
+
log("recall_peer_scope_error", { status: res.status || 0 });
|
|
577
|
+
return res;
|
|
578
|
+
}
|
|
543
579
|
const downgraded = { ...request };
|
|
544
580
|
delete downgraded.peer_scope;
|
|
581
|
+
await markPeerScopeDowngrade(String(request.peer_scope), res.status || 0, memoPath);
|
|
545
582
|
log("recall_peer_scope_downgrade", { status: res.status || 0 });
|
|
546
583
|
return fetchJSON("/api/v1/search/recall", {
|
|
547
584
|
method: "POST",
|
|
548
585
|
body: JSON.stringify(downgraded),
|
|
549
586
|
}, { actorPeerId });
|
|
550
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* Recall, plus whatever is still filed under the peer this workspace used
|
|
590
|
+
* before the identity rule changed.
|
|
591
|
+
*
|
|
592
|
+
* Under the default `peer_scope: "all"` this costs nothing: the server's own
|
|
593
|
+
* sweep of `{user_root}/peers` already reaches the old peer's memories. Under
|
|
594
|
+
* `"actor"` that sweep is off by definition, so the old peer is asked for
|
|
595
|
+
* separately — as itself, which is both cheaper and wider than a bare
|
|
596
|
+
* cross-peer read (that would need the user id, and reaches memories only).
|
|
597
|
+
*/
|
|
551
598
|
export async function buildRecallBlock(fetchJSON, cfg, query, options = {}) {
|
|
599
|
+
const primary = await recallForPeer(fetchJSON, cfg, query, options);
|
|
600
|
+
const legacyPeerId = String(options.legacyPeerId || "").trim();
|
|
601
|
+
const actorPeerId = options.actorPeerId ?? cfg.peerId ?? "";
|
|
602
|
+
if (cfg.recallPeerScope !== "actor" || !legacyPeerId || legacyPeerId === actorPeerId)
|
|
603
|
+
return primary;
|
|
604
|
+
const log = options.log || (() => { });
|
|
605
|
+
const legacy = await recallForPeer(fetchJSON, cfg, query, { ...options, actorPeerId: legacyPeerId });
|
|
606
|
+
if (!legacy)
|
|
607
|
+
return primary;
|
|
608
|
+
log("recall_legacy_peer_hit", { legacyPeerId });
|
|
609
|
+
return primary ? `${primary}\n${legacy}` : legacy;
|
|
610
|
+
}
|
|
611
|
+
async function recallForPeer(fetchJSON, cfg, query, options = {}) {
|
|
552
612
|
const actorPeerId = options.actorPeerId ?? cfg.peerId ?? "";
|
|
553
613
|
const log = options.log || (() => { });
|
|
554
614
|
const trimmed = String(query || "").trim();
|
package/openclaw.plugin.json
CHANGED
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"help": "HTTP URL when mode is remote (or ${OPENVIKING_BASE_URL})"
|
|
81
81
|
},
|
|
82
82
|
"peer_role": {
|
|
83
|
-
"label": "
|
|
84
|
-
"placeholder": "
|
|
85
|
-
"help": "
|
|
83
|
+
"label": "Memory Scope (peer_role)",
|
|
84
|
+
"placeholder": "none",
|
|
85
|
+
"help": "Where peer-scoped memories are stored. none (default): viking://user/<user_id>/memories. assistant: viking://user/<user_id>/peers/<assistant_id>/memories. sender: viking://user/<user_id>/peers/<sender_id>/memories. Legacy person is accepted as sender."
|
|
86
86
|
},
|
|
87
87
|
"peer_prefix": {
|
|
88
88
|
"label": "Peer Prefix",
|
|
89
89
|
"placeholder": "optional-prefix",
|
|
90
|
-
"help": "
|
|
90
|
+
"help": "Only used when Memory Scope is assistant. Prefix added to the peer id derived from the OpenClaw agent id."
|
|
91
91
|
},
|
|
92
92
|
"apiKey": {
|
|
93
93
|
"label": "OpenViking API Key",
|
|
@@ -279,10 +279,11 @@
|
|
|
279
279
|
"enum": [
|
|
280
280
|
"none",
|
|
281
281
|
"assistant",
|
|
282
|
+
"sender",
|
|
282
283
|
"person"
|
|
283
284
|
],
|
|
284
|
-
"default": "
|
|
285
|
-
"description": "
|
|
285
|
+
"default": "none",
|
|
286
|
+
"description": "Memory scope. none: shared user memory. assistant: peer memory keyed by OpenClaw assistant id. sender: peer memory keyed by sender id. person is a legacy alias for sender. Also controls which session messages include peer_id."
|
|
286
287
|
},
|
|
287
288
|
"peer_prefix": {
|
|
288
289
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.4-1",
|
|
4
4
|
"displayName": "OpenViking",
|
|
5
5
|
"description": "OpenClaw context-engine plugin — OpenViking AI memory, long-term memory, knowledge base, semantic search, RAG, context engine. 长期记忆 · 知识库 · 语义检索 · 上下文引擎",
|
|
6
6
|
"type": "module",
|
|
@@ -114,7 +114,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
|
|
|
114
114
|
const peerId = resolveOpenVikingMessagePeerId({
|
|
115
115
|
peerRole: deps.peerRole,
|
|
116
116
|
role,
|
|
117
|
-
|
|
117
|
+
senderPeerId: session.actorPeerId,
|
|
118
118
|
assistantPeerId: session.actorPeerId,
|
|
119
119
|
});
|
|
120
120
|
await client.addSessionMessage(
|
|
@@ -34,7 +34,7 @@ export function createOpenVikingSessionRoutingRuntime(options: {
|
|
|
34
34
|
logFindRequests: boolean;
|
|
35
35
|
logger: Logger;
|
|
36
36
|
}) {
|
|
37
|
-
const peerRole = options.peerRole ?? "
|
|
37
|
+
const peerRole = options.peerRole ?? "none";
|
|
38
38
|
const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
|
|
39
39
|
|
|
40
40
|
const rememberSessionAgentId = (ctx: SessionAgentLookup) => {
|
|
@@ -97,7 +97,7 @@ export function createOpenVikingSessionRoutingRuntime(options: {
|
|
|
97
97
|
agentId,
|
|
98
98
|
actorPeerId: resolveOpenVikingActorPeerId({
|
|
99
99
|
peerRole,
|
|
100
|
-
|
|
100
|
+
senderPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
|
|
101
101
|
assistantPeerId: agentId,
|
|
102
102
|
}),
|
|
103
103
|
};
|
|
@@ -15,7 +15,7 @@ export type SessionAgentLookup = {
|
|
|
15
15
|
ovSessionId?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type OpenVikingPeerRole = "none" | "assistant" | "
|
|
18
|
+
export type OpenVikingPeerRole = "none" | "assistant" | "sender";
|
|
19
19
|
|
|
20
20
|
export function sanitizeOpenVikingPeerId(raw?: string): string | undefined {
|
|
21
21
|
const normalized = raw
|
|
@@ -29,11 +29,11 @@ export function sanitizeOpenVikingPeerId(raw?: string): string | undefined {
|
|
|
29
29
|
export function resolveOpenVikingMessagePeerId(params: {
|
|
30
30
|
peerRole: OpenVikingPeerRole;
|
|
31
31
|
role?: string;
|
|
32
|
-
|
|
32
|
+
senderPeerId?: string;
|
|
33
33
|
assistantPeerId?: string;
|
|
34
34
|
}): string | undefined {
|
|
35
|
-
if (params.peerRole === "
|
|
36
|
-
return params.
|
|
35
|
+
if (params.peerRole === "sender" && params.role === "user") {
|
|
36
|
+
return params.senderPeerId;
|
|
37
37
|
}
|
|
38
38
|
if (params.peerRole === "assistant" && params.role === "assistant") {
|
|
39
39
|
return params.assistantPeerId;
|
|
@@ -43,15 +43,15 @@ export function resolveOpenVikingMessagePeerId(params: {
|
|
|
43
43
|
|
|
44
44
|
export function resolveOpenVikingActorPeerId(params: {
|
|
45
45
|
peerRole: OpenVikingPeerRole;
|
|
46
|
-
|
|
46
|
+
senderPeerId?: string;
|
|
47
47
|
assistantPeerId?: string;
|
|
48
48
|
}): string | undefined {
|
|
49
49
|
const actorPeerId = resolveOpenVikingMessagePeerId({
|
|
50
50
|
...params,
|
|
51
|
-
role: params.peerRole === "
|
|
51
|
+
role: params.peerRole === "sender" ? "user" : "assistant",
|
|
52
52
|
});
|
|
53
|
-
if (params.peerRole === "
|
|
54
|
-
throw new Error("openviking: peer_role=
|
|
53
|
+
if (params.peerRole === "sender" && !actorPeerId) {
|
|
54
|
+
throw new Error("openviking: peer_role=sender requires a sender identity");
|
|
55
55
|
}
|
|
56
56
|
return actorPeerId;
|
|
57
57
|
}
|
|
@@ -497,8 +497,8 @@ export async function assembleOpenVikingSession({
|
|
|
497
497
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
498
498
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
499
499
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
500
|
-
peerRole: cfg.peer_role ?? "
|
|
501
|
-
|
|
500
|
+
peerRole: cfg.peer_role ?? "none",
|
|
501
|
+
senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
|
|
502
502
|
assistantPeerId: agentId,
|
|
503
503
|
});
|
|
504
504
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
@@ -910,9 +910,9 @@ export async function afterTurnOpenVikingSession({
|
|
|
910
910
|
undefined,
|
|
911
911
|
createdAt,
|
|
912
912
|
resolveOpenVikingMessagePeerId({
|
|
913
|
-
peerRole: cfg.peer_role ?? "
|
|
913
|
+
peerRole: cfg.peer_role ?? "none",
|
|
914
914
|
role: msg.role,
|
|
915
|
-
|
|
915
|
+
senderPeerId: senderRoleId,
|
|
916
916
|
assistantPeerId: agentId,
|
|
917
917
|
}),
|
|
918
918
|
);
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
|
|
14
14
|
export type { SetupIO, SlotActivationResult };
|
|
15
15
|
|
|
16
|
+
type SetupPeerRole = "none" | "assistant" | "sender" | "person";
|
|
17
|
+
|
|
16
18
|
export type VersionCompatibility = "compatible" | "server_too_old" | "server_too_new" | "unknown";
|
|
17
19
|
|
|
18
20
|
export type HealthResult = {
|
|
@@ -38,7 +40,7 @@ export type SetupResult = {
|
|
|
38
40
|
mode: string;
|
|
39
41
|
baseUrl: string;
|
|
40
42
|
apiKey?: string;
|
|
41
|
-
peer_role?:
|
|
43
|
+
peer_role?: SetupPeerRole;
|
|
42
44
|
peer_prefix?: string;
|
|
43
45
|
accountId?: string;
|
|
44
46
|
userId?: string;
|
|
@@ -57,7 +59,7 @@ export type StatusResult = {
|
|
|
57
59
|
mode: string;
|
|
58
60
|
baseUrl: string;
|
|
59
61
|
hasApiKey: boolean;
|
|
60
|
-
peer_role?:
|
|
62
|
+
peer_role?: SetupPeerRole;
|
|
61
63
|
peer_prefix?: string;
|
|
62
64
|
hasAccountId: boolean;
|
|
63
65
|
hasUserId: boolean;
|
|
@@ -71,7 +73,7 @@ export type SetupParams = {
|
|
|
71
73
|
baseUrl: string;
|
|
72
74
|
apiKey?: string;
|
|
73
75
|
headers?: OpenVikingRequestHeaders;
|
|
74
|
-
peerRole?:
|
|
76
|
+
peerRole?: SetupPeerRole;
|
|
75
77
|
peerPrefix?: string;
|
|
76
78
|
accountId?: string;
|
|
77
79
|
userId?: string;
|
|
@@ -85,7 +87,7 @@ export type InteractiveRemoteConfigParams = {
|
|
|
85
87
|
baseUrl: string;
|
|
86
88
|
apiKey?: string;
|
|
87
89
|
headers?: OpenVikingRequestHeaders;
|
|
88
|
-
peerRole?:
|
|
90
|
+
peerRole?: SetupPeerRole;
|
|
89
91
|
peerPrefix?: string;
|
|
90
92
|
accountId?: string;
|
|
91
93
|
userId?: string;
|
|
@@ -315,7 +317,7 @@ export function createOpenVikingSetupService({
|
|
|
315
317
|
baseUrl,
|
|
316
318
|
...(apiKey ? { apiKey: maskKey(apiKey) } : {}),
|
|
317
319
|
...(headers ? { headers } : {}),
|
|
318
|
-
...(existing.peer_role ? { peer_role: String(existing.peer_role) as
|
|
320
|
+
...(existing.peer_role ? { peer_role: String(existing.peer_role) as SetupPeerRole } : {}),
|
|
319
321
|
...(existing.peer_prefix ? { peer_prefix: String(existing.peer_prefix) } : {}),
|
|
320
322
|
...(existing.accountId ? { accountId: String(existing.accountId) } : {}),
|
|
321
323
|
...(existing.userId ? { userId: String(existing.userId) } : {}),
|
|
@@ -346,7 +348,7 @@ export function createOpenVikingSetupService({
|
|
|
346
348
|
mode: String(existing.mode ?? "remote"),
|
|
347
349
|
baseUrl,
|
|
348
350
|
hasApiKey: !!existing.apiKey,
|
|
349
|
-
...(existing.peer_role ? { peer_role: String(existing.peer_role) as
|
|
351
|
+
...(existing.peer_role ? { peer_role: String(existing.peer_role) as SetupPeerRole } : {}),
|
|
350
352
|
...(existing.peer_prefix ? { peer_prefix: String(existing.peer_prefix) } : {}),
|
|
351
353
|
hasAccountId: !!existing.accountId,
|
|
352
354
|
hasUserId: !!existing.userId,
|
package/shared/recall-core.mjs
CHANGED
|
@@ -417,6 +417,31 @@ export async function markContextFaceLegacy(path = stateFile("context-face.json"
|
|
|
417
417
|
await writeJsonFile(path, { legacyUntil: now + LEGACY_CACHE_TTL_MS });
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
/**
|
|
421
|
+
* A `peer_scope` the server rejects is remembered the same way, but unlike the
|
|
422
|
+
* context face this one is not a silent capability probe: dropping the field
|
|
423
|
+
* widens recall from the caller's own peer to the whole user root, so doctor
|
|
424
|
+
* reads this file back and warns.
|
|
425
|
+
*/
|
|
426
|
+
export function peerScopeMemoPath() {
|
|
427
|
+
return stateFile("peer-scope.json");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export async function readPeerScopeDowngrade(path = peerScopeMemoPath(), now = Date.now()) {
|
|
431
|
+
const cached = await readJsonFile(path);
|
|
432
|
+
if (!cached?.legacyUntil || Number(cached.legacyUntil) <= now) return null;
|
|
433
|
+
return cached;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export async function markPeerScopeDowngrade(scope, status, path = peerScopeMemoPath(), now = Date.now()) {
|
|
437
|
+
await writeJsonFile(path, {
|
|
438
|
+
legacyUntil: now + LEGACY_CACHE_TTL_MS,
|
|
439
|
+
scope: String(scope || ""),
|
|
440
|
+
status: Number(status) || 0,
|
|
441
|
+
at: now,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
420
445
|
function looksLikeUnknownField(res) {
|
|
421
446
|
const text = JSON.stringify(res?.error ?? res?.result ?? res?.detail ?? "").toLowerCase();
|
|
422
447
|
return text.includes("extra") || text.includes("mode") || text.includes("unexpected");
|
|
@@ -558,7 +583,15 @@ async function recallViaEndpoint(fetchJSON, cfg, query, actorPeerId = "", log =
|
|
|
558
583
|
export async function postRecall(fetchJSON, body, opts = {}) {
|
|
559
584
|
const actorPeerId = opts.actorPeerId || "";
|
|
560
585
|
const log = opts.log || (() => {});
|
|
586
|
+
const memoPath = opts.peerScopeMemoPath;
|
|
561
587
|
const request = { ...body };
|
|
588
|
+
|
|
589
|
+
// A remembered downgrade is still a downgrade: recall runs wider than the
|
|
590
|
+
// caller asked for, so the memo doubles as the doctor's evidence.
|
|
591
|
+
if (request.peer_scope && await readPeerScopeDowngrade(memoPath)) {
|
|
592
|
+
delete request.peer_scope;
|
|
593
|
+
}
|
|
594
|
+
|
|
562
595
|
const res = await fetchJSON("/api/v1/search/recall", {
|
|
563
596
|
method: "POST",
|
|
564
597
|
body: JSON.stringify(request),
|
|
@@ -566,9 +599,17 @@ export async function postRecall(fetchJSON, body, opts = {}) {
|
|
|
566
599
|
if (!request.peer_scope || (res.status !== 400 && res.status !== 422)) {
|
|
567
600
|
return res;
|
|
568
601
|
}
|
|
602
|
+
// Only an unknown-field rejection means "this server predates peer_scope".
|
|
603
|
+
// Retrying every other 400/422 without it silently widens the search from
|
|
604
|
+
// the caller's own peer to the whole user root.
|
|
605
|
+
if (!looksLikeUnknownField(res)) {
|
|
606
|
+
log("recall_peer_scope_error", { status: res.status || 0 });
|
|
607
|
+
return res;
|
|
608
|
+
}
|
|
569
609
|
|
|
570
610
|
const downgraded = { ...request };
|
|
571
611
|
delete downgraded.peer_scope;
|
|
612
|
+
await markPeerScopeDowngrade(String(request.peer_scope), res.status || 0, memoPath);
|
|
572
613
|
log("recall_peer_scope_downgrade", { status: res.status || 0 });
|
|
573
614
|
return fetchJSON("/api/v1/search/recall", {
|
|
574
615
|
method: "POST",
|
|
@@ -576,7 +617,31 @@ export async function postRecall(fetchJSON, body, opts = {}) {
|
|
|
576
617
|
}, { actorPeerId });
|
|
577
618
|
}
|
|
578
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Recall, plus whatever is still filed under the peer this workspace used
|
|
622
|
+
* before the identity rule changed.
|
|
623
|
+
*
|
|
624
|
+
* Under the default `peer_scope: "all"` this costs nothing: the server's own
|
|
625
|
+
* sweep of `{user_root}/peers` already reaches the old peer's memories. Under
|
|
626
|
+
* `"actor"` that sweep is off by definition, so the old peer is asked for
|
|
627
|
+
* separately — as itself, which is both cheaper and wider than a bare
|
|
628
|
+
* cross-peer read (that would need the user id, and reaches memories only).
|
|
629
|
+
*/
|
|
579
630
|
export async function buildRecallBlock(fetchJSON, cfg, query, options = {}) {
|
|
631
|
+
const primary = await recallForPeer(fetchJSON, cfg, query, options);
|
|
632
|
+
|
|
633
|
+
const legacyPeerId = String(options.legacyPeerId || "").trim();
|
|
634
|
+
const actorPeerId = options.actorPeerId ?? cfg.peerId ?? "";
|
|
635
|
+
if (cfg.recallPeerScope !== "actor" || !legacyPeerId || legacyPeerId === actorPeerId) return primary;
|
|
636
|
+
|
|
637
|
+
const log = options.log || (() => {});
|
|
638
|
+
const legacy = await recallForPeer(fetchJSON, cfg, query, { ...options, actorPeerId: legacyPeerId });
|
|
639
|
+
if (!legacy) return primary;
|
|
640
|
+
log("recall_legacy_peer_hit", { legacyPeerId });
|
|
641
|
+
return primary ? `${primary}\n${legacy}` : legacy;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function recallForPeer(fetchJSON, cfg, query, options = {}) {
|
|
580
645
|
const actorPeerId = options.actorPeerId ?? cfg.peerId ?? "";
|
|
581
646
|
const log = options.log || (() => {});
|
|
582
647
|
const trimmed = String(query || "").trim();
|
|
@@ -106,7 +106,7 @@ Send this message:
|
|
|
106
106
|
> 我需要 3 条信息,不知道的可以问你的管理员:
|
|
107
107
|
> 1. **OpenViking 服务地址** —— 例如 `https://ov.example.com` 或 `http://192.168.1.100:1933`,本机服务可以直接说"本机"
|
|
108
108
|
> 2. **API Key** —— 用来鉴权;服务没开认证可以说"没有"
|
|
109
|
-
> 3.
|
|
109
|
+
> 3. **记忆归属**(可选) —— 所有对话共用 user 记忆(默认),还是在 `peers/<assistant_id>` 或 `peers/<sender_id>` 下分开存放
|
|
110
110
|
>
|
|
111
111
|
> 先告诉我服务地址吧?
|
|
112
112
|
|
|
@@ -115,7 +115,7 @@ Send this message:
|
|
|
115
115
|
> I need 3 things (ask your admin if unsure):
|
|
116
116
|
> 1. **OpenViking server URL** — e.g. `https://ov.example.com` or `http://192.168.1.100:1933`. For a local server, just say "local".
|
|
117
117
|
> 2. **API Key** — for auth. Say "none" if the server has no auth.
|
|
118
|
-
> 3. **
|
|
118
|
+
> 3. **Memory scope** (optional) — shared user memory (default), or peer memory under `peers/<assistant_id>` / `peers/<sender_id>`.
|
|
119
119
|
>
|
|
120
120
|
> What's the server URL?
|
|
121
121
|
|
|
@@ -141,15 +141,36 @@ Collect 3 values through natural conversation. Be flexible: if the user gives se
|
|
|
141
141
|
- "no" / "none" / "没有" / "空" / empty → `API_KEY=""` (you will skip the flag later).
|
|
142
142
|
- Otherwise store as-is.
|
|
143
143
|
|
|
144
|
-
### 4c. `
|
|
144
|
+
### 4c. `PEER_ROLE` (OPTIONAL)
|
|
145
|
+
|
|
146
|
+
> (CN) 记忆要存在哪一层?默认共享在 `viking://user/<user_id>/memories`(`none`)。也可以按 OpenClaw 助手存到 `.../peers/<assistant_id>/memories`(`assistant`),或按发送者存到 `.../peers/<sender_id>/memories`(`sender`)。
|
|
147
|
+
> (EN) Where should memory live? The default is shared `viking://user/<user_id>/memories` (`none`). You can instead use `.../peers/<assistant_id>/memories` (`assistant`) or `.../peers/<sender_id>/memories` (`sender`).
|
|
148
|
+
|
|
149
|
+
Default to `none`. **Never silently pick another value** — the three options mean different storage layouts and are awkward to change later:
|
|
150
|
+
|
|
151
|
+
| Value | Concrete layout | Choose it when |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `none` (default) | Shared memory under `viking://user/<user_id>/memories/...`; no concrete `peers/<peer_id>/memories` subtree is used. | General case. All conversations for this OpenViking user share user-level memory. |
|
|
154
|
+
| `assistant` | Assistant-attributed peer memory under `viking://user/<user_id>/peers/<assistant_id>/memories/...`, keyed by the resolved OpenClaw agent id (fallback `main`; optional prefix supported). | **Human as OpenViking user**: one human uses multiple assistants and wants their peer memories separated. Example: Alice's `main` and `research` assistants use `.../peers/main/...` and `.../peers/research/...`. |
|
|
155
|
+
| `sender` | Sender-attributed peer memory under `viking://user/<user_id>/peers/<sender_id>/memories/...`, keyed by the sender identity supplied by OpenClaw. | **Agent as OpenViking user**: one agent talks to multiple humans and wants their peer memories separated. Example: `support-agent` stores `customer-42` and `customer-99` under different peer subtrees. |
|
|
156
|
+
|
|
157
|
+
`person` is a legacy alias for `sender`. Accept it from an existing config or explicit user input, but normalize/write new setup values as `sender`.
|
|
158
|
+
|
|
159
|
+
OpenViking initializes the managed `peers/` container for every user. Do not tell the user that `none` removes that container: it only avoids a concrete peer memory subtree. With `assistant` or `sender`, actor-peer recall includes shared user memory plus the current peer memory; it hides other peer subtrees. Changing this setting does not move existing memories.
|
|
160
|
+
|
|
161
|
+
- Empty / "default" / "默认" / "共用" → leave unset (plugin defaults to `none`).
|
|
162
|
+
- Otherwise pass `--peer-role none|assistant|sender`. Treat explicit legacy `person` as `sender`. Any other value → ask again.
|
|
163
|
+
|
|
164
|
+
### 4d. `PEER_PREFIX` (OPTIONAL, only when `PEER_ROLE=assistant`)
|
|
145
165
|
|
|
146
166
|
> (CN) 想给这个 agent 一个记忆前缀吗?留空就用默认。只能用字母、数字、`_`、`-`。
|
|
147
167
|
> (EN) Want to set an peer prefix? Leave blank for the default. Letters, digits, `_`, `-` only.
|
|
148
168
|
|
|
169
|
+
- Only meaningful when `PEER_ROLE=assistant`; skip the question otherwise.
|
|
149
170
|
- Empty / "default" / "默认" → leave unset (plugin defaults to `""`).
|
|
150
171
|
- Otherwise validate against `/^[A-Za-z0-9_-]+$/`. If invalid, ask again.
|
|
151
172
|
|
|
152
|
-
###
|
|
173
|
+
### 4e. (Conditional) Multi-Tenant Root-Key Fields
|
|
153
174
|
|
|
154
175
|
Only ask for these if STEP 7 detects a root key (`Root API key detected. Missing: --account-id, --user-id`). Don't ask up front.
|
|
155
176
|
|
|
@@ -259,14 +280,15 @@ Tell the user:
|
|
|
259
280
|
Run the installer with `npx` (no global install needed):
|
|
260
281
|
|
|
261
282
|
```bash
|
|
262
|
-
npx -y openclaw-openviking-setup-helper@latest --base-url BASE_URL [--api-key API_KEY] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID]
|
|
283
|
+
npx -y openclaw-openviking-setup-helper@latest --base-url BASE_URL [--api-key API_KEY] [--peer-role PEER_ROLE] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID]
|
|
263
284
|
```
|
|
264
285
|
|
|
265
286
|
Build the flag list according to what the user gave you:
|
|
266
287
|
|
|
267
288
|
- Always pass `--base-url BASE_URL`.
|
|
268
289
|
- Pass `--api-key API_KEY` only if `API_KEY` is non-empty.
|
|
269
|
-
- Pass `--peer-
|
|
290
|
+
- Pass `--peer-role PEER_ROLE` only if the user chose `assistant` or `sender`; omit it for the `none` default. Normalize legacy `person` to `sender` first.
|
|
291
|
+
- Pass `--peer-prefix PEER_PREFIX` only if the user gave one and `PEER_ROLE=assistant`.
|
|
270
292
|
- `--account-id` / `--user-id` only if the root-key path requires them.
|
|
271
293
|
|
|
272
294
|
`ov-install` will, in one shot:
|
|
@@ -287,14 +309,15 @@ If `ov-install` exits non-zero, capture the last 30 lines of its output, show th
|
|
|
287
309
|
Run the setup wizard non-interactively. Build flags from collected values:
|
|
288
310
|
|
|
289
311
|
```bash
|
|
290
|
-
openclaw openviking setup --base-url BASE_URL --json [--api-key API_KEY] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID] [--allow-offline] [--force-slot]
|
|
312
|
+
openclaw openviking setup --base-url BASE_URL --json [--api-key API_KEY] [--peer-role PEER_ROLE] [--peer-prefix PEER_PREFIX] [--account-id ACCOUNT_ID] [--user-id USER_ID] [--allow-offline] [--force-slot]
|
|
291
313
|
```
|
|
292
314
|
|
|
293
315
|
Rules:
|
|
294
316
|
|
|
295
317
|
- `--base-url BASE_URL` is **required** under `--json`. Without it, the wizard prints `--json requires --base-url for non-interactive mode`.
|
|
296
318
|
- `--api-key` only if `API_KEY` is non-empty.
|
|
297
|
-
- `--peer-
|
|
319
|
+
- `--peer-role` only if the user chose `assistant` or `sender`; omit it for the `none` default. Normalize legacy `person` to `sender` first.
|
|
320
|
+
- `--peer-prefix` only if the user gave one and `--peer-role assistant` is in play. Use **`--peer-prefix`** for assistant peer prefixes; the old ID-style setup flag is no longer supported.
|
|
298
321
|
- `--account-id` / `--user-id` only after STEP 7 root-key detection (see below).
|
|
299
322
|
- `--allow-offline` only if the user explicitly approved it in STEP 5.
|
|
300
323
|
- `--force-slot` **never** in the first attempt. Add only after the user confirms (see slot_blocked handling below).
|
|
@@ -583,8 +606,8 @@ These are the keys under `plugins.entries.openviking.config` in `openclaw.json`.
|
|
|
583
606
|
| `mode` | `"remote"` (forced by plugin) | Always remote in this skill. Don't set manually. |
|
|
584
607
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking server URL. |
|
|
585
608
|
| `apiKey` | — | API key. Optional if server has no auth. |
|
|
586
|
-
| `peer_role` | `
|
|
587
|
-
| `peer_prefix` | `""` | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant`. Letters / digits / `_` / `-`. |
|
|
609
|
+
| `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`. |
|
|
610
|
+
| `peer_prefix` | `""` | Optional prefix for assistant `peer_id` / actor peer values. Only used when `peer_role=assistant`. Letters / digits / `_` / `-`. |
|
|
588
611
|
| `accountId` | — | Required when `apiKey` is a root key. |
|
|
589
612
|
| `userId` | — | Required when `apiKey` is a root key. |
|
|
590
613
|
| `targetUri` | `viking://~/memories` | Default search scope URI. |
|
|
@@ -64,7 +64,7 @@ Core config lives under `plugins.entries.openviking.config`:
|
|
|
64
64
|
|---|---:|---|
|
|
65
65
|
| `baseUrl` | `http://127.0.0.1:1933` | OpenViking HTTP endpoint. Can also come from `OPENVIKING_BASE_URL` / `OPENVIKING_URL`. |
|
|
66
66
|
| `apiKey` | empty | Optional API key. Can also come from `OPENVIKING_API_KEY`. |
|
|
67
|
-
| `peer_role` | `
|
|
67
|
+
| `peer_role` | `none` | Memory scope: `none` (shared `viking://user/<user_id>/memories`), `assistant` (assistant-attributed memory under `.../peers/<assistant_id>/memories`), or `sender` (sender-attributed memory under `.../peers/<sender_id>/memories`). Legacy `person` is accepted as `sender`. |
|
|
68
68
|
| `peer_prefix` | empty | Optional prefix for assistant `peer_id` / actor peer values when `peer_role=assistant`. |
|
|
69
69
|
| `accountId` / `userId` | empty | Advanced tenant identity headers for root-key or trusted deployments. |
|
|
70
70
|
| `targetUri` | `viking://~/memories` | Default search scope for legacy targeted memory search. |
|
|
@@ -95,7 +95,7 @@ openclaw openviking setup --base-url <OPENVIKING_URL> --api-key <API_KEY> --json
|
|
|
95
95
|
Useful variants:
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
openclaw openviking setup --base-url <URL> --api-key <KEY> --peer-prefix openclaw-prod --json
|
|
98
|
+
openclaw openviking setup --base-url <URL> --api-key <KEY> --peer-role assistant --peer-prefix openclaw-prod --json
|
|
99
99
|
openclaw openviking setup --base-url <URL> --api-key <ROOT_KEY> --account-id <ACCOUNT_ID> --user-id <USER_ID> --json
|
|
100
100
|
openclaw openviking setup --base-url <URL> --api-key <KEY> --recall-target-types resource --json
|
|
101
101
|
openclaw openviking setup --base-url <URL> --api-key <KEY> --allow-offline --json
|