@openviking/openclaw-plugin 2026.9.4 → 2026.9.8-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.
@@ -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 ?? "assistant",
247
- personPeerId: sanitizeOpenVikingPeerId(sender.senderId),
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 ?? "assistant",
598
+ peerRole: cfg.peer_role ?? "none",
599
599
  role: msg.role,
600
- personPeerId: senderRoleId,
600
+ senderPeerId: senderRoleId,
601
601
  assistantPeerId: agentId,
602
602
  }));
603
603
  }
@@ -671,7 +671,7 @@ function compactFailureResult(reason, tokensBefore, details) {
671
671
  },
672
672
  };
673
673
  }
674
- export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, diag, }) {
674
+ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, runtimeCompact, diag, }) {
675
675
  const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
676
676
  diag("compact_entry", ovSessionId, {
677
677
  tokenBudget,
@@ -682,6 +682,18 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
682
682
  customInstructions.trim().length > 0,
683
683
  });
684
684
  if (isBypassedSession({ sessionId, sessionKey })) {
685
+ // Bypassed sessions store nothing in OV, but the host still needs a real
686
+ // compaction (preflight hard-fails on an unknown skip reason), so hand the
687
+ // request to OpenClaw's native compactor.
688
+ const delegated = await runtimeCompact?.();
689
+ if (delegated) {
690
+ diag("compact_result", ovSessionId, {
691
+ ok: delegated.ok,
692
+ compacted: delegated.compacted,
693
+ reason: delegated.reason ?? "session_bypassed_runtime_compaction",
694
+ });
695
+ return delegated;
696
+ }
685
697
  diag("compact_result", ovSessionId, {
686
698
  ok: true,
687
699
  compacted: false,
@@ -0,0 +1,112 @@
1
+ import { resolveOpenVikingRequestHeaders, } from "../../request-headers.js";
2
+ export const defaultSetupNetworkTransport = (url, init) => fetch(url, init);
3
+ export function createSetupNetworkProbes({ pluginVersion, compatRange, checkVersionCompatibility, transport = defaultSetupNetworkTransport, timeoutMs = 10_000, }) {
4
+ return {
5
+ async probeApiKeyType(baseUrl, apiKey, configuredHeaders) {
6
+ if (!apiKey) {
7
+ return { keyType: "no_key", needsAccountId: false, needsUserId: false, detail: "No API key configured" };
8
+ }
9
+ const controller = new AbortController();
10
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
11
+ const sessionsUrl = `${baseUrl.replace(/\/+$/, "")}/api/v1/sessions?limit=1`;
12
+ try {
13
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
14
+ const headers = {};
15
+ headers["X-API-Key"] = apiKey;
16
+ Object.assign(headers, configuredRequestHeaders);
17
+ const response = await transport(sessionsUrl, {
18
+ headers,
19
+ signal: controller.signal,
20
+ });
21
+ if (response.ok) {
22
+ return { keyType: "user_key", needsAccountId: false, needsUserId: false, detail: "API key has full user context" };
23
+ }
24
+ if ([400, 401, 403, 422].includes(response.status)) {
25
+ let body = "";
26
+ try {
27
+ body = await response.text();
28
+ }
29
+ catch { /* ignore parse errors */ }
30
+ const lower = body.toLowerCase();
31
+ const needsAccount = /x-openviking-account|account[_ ]?id|account context|tenant/.test(lower);
32
+ const needsUser = /x-openviking-user|user[_ ]?id|user context|user key/.test(lower);
33
+ if (needsAccount || needsUser) {
34
+ return {
35
+ keyType: "root_key",
36
+ needsAccountId: needsAccount,
37
+ needsUserId: needsUser,
38
+ detail: body.slice(0, 200),
39
+ };
40
+ }
41
+ try {
42
+ const probeHeaders = {
43
+ "X-API-Key": apiKey,
44
+ "X-OpenViking-Account": "__probe__",
45
+ "X-OpenViking-User": "__probe__",
46
+ ...configuredRequestHeaders,
47
+ };
48
+ const probe2 = await transport(sessionsUrl, {
49
+ headers: probeHeaders,
50
+ signal: controller.signal,
51
+ });
52
+ if (probe2.status !== response.status) {
53
+ return {
54
+ keyType: "root_key",
55
+ needsAccountId: true,
56
+ needsUserId: true,
57
+ detail: body.slice(0, 200) || `HTTP ${response.status} -> ${probe2.status} after adding tenant headers`,
58
+ };
59
+ }
60
+ }
61
+ catch { /* ignore probe errors, fall through to unknown */ }
62
+ if (response.status === 401 || response.status === 403) {
63
+ return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status} - authentication failed, verify your API key` };
64
+ }
65
+ return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status}${body ? ` - ${body.slice(0, 160)}` : ""}` };
66
+ }
67
+ return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: `HTTP ${response.status}` };
68
+ }
69
+ catch (err) {
70
+ return { keyType: "unknown", needsAccountId: false, needsUserId: false, detail: String(err instanceof Error ? err.message : err) };
71
+ }
72
+ finally {
73
+ clearTimeout(timeoutId);
74
+ }
75
+ },
76
+ async checkServiceHealth(baseUrl, apiKey, configuredHeaders) {
77
+ const controller = new AbortController();
78
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
79
+ try {
80
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
81
+ const headers = {};
82
+ if (apiKey) {
83
+ headers["X-API-Key"] = apiKey;
84
+ }
85
+ Object.assign(headers, configuredRequestHeaders);
86
+ const response = await transport(`${baseUrl.replace(/\/+$/, "")}/health`, {
87
+ headers,
88
+ signal: controller.signal,
89
+ });
90
+ if (response.ok) {
91
+ try {
92
+ const data = await response.json();
93
+ const result = (data.result ?? data);
94
+ const version = String(result.version ?? data.version ?? "");
95
+ const compatibility = checkVersionCompatibility(version);
96
+ return { ok: true, version, error: "", compatibility, pluginVersion, compatRange };
97
+ }
98
+ catch {
99
+ return { ok: true, version: "", error: "", compatibility: "unknown", pluginVersion, compatRange };
100
+ }
101
+ }
102
+ return { ok: false, version: "", error: `HTTP ${response.status}`, compatibility: "unknown", pluginVersion, compatRange };
103
+ }
104
+ catch (err) {
105
+ return { ok: false, version: "", error: String(err instanceof Error ? err.message : err), compatibility: "unknown", pluginVersion, compatRange };
106
+ }
107
+ finally {
108
+ clearTimeout(timeoutId);
109
+ }
110
+ },
111
+ };
112
+ }
@@ -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": "Peer Role",
84
- "placeholder": "assistant",
85
- "help": "Controls which messages include peer_id: none, assistant, or person. New installs default to assistant."
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": "Optional prefix for assistant peer_id values derived from OpenClaw runtime agent IDs."
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",
@@ -195,7 +195,7 @@
195
195
  "bypassSessionPatterns": {
196
196
  "label": "Bypass Session Patterns",
197
197
  "placeholder": "agent:*:cron:**",
198
- "help": "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
198
+ "help": "Completely bypass OpenViking for matching session keys (no capture, recall, or commit; compaction falls back to OpenClaw's native compactor). Use * within one segment and ** across segments.",
199
199
  "advanced": true
200
200
  },
201
201
  "commitTokenThresholdRatio": {
@@ -279,10 +279,11 @@
279
279
  "enum": [
280
280
  "none",
281
281
  "assistant",
282
+ "sender",
282
283
  "person"
283
284
  ],
284
- "default": "assistant",
285
- "description": "Controls which session messages include peer_id."
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.4",
3
+ "version": "2026.9.8-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",
@@ -77,11 +77,5 @@
77
77
  "openclawVersion": "2026.5.27",
78
78
  "pluginSdkVersion": "2026.5.27"
79
79
  }
80
- },
81
- "devDependencies": {
82
- "@types/node": "^25.3.5",
83
- "@volcengine/tos-sdk": "^2.9.1",
84
- "typescript": "^5.8.0",
85
- "vitest": "^4.1.0"
86
80
  }
87
81
  }
@@ -0,0 +1,41 @@
1
+ // Bridge to OpenClaw's built-in compactor for sessions that bypass OpenViking.
2
+ // The host exports `delegateCompactionToRuntime` from `openclaw/plugin-sdk/core`
3
+ // (present since the earliest supported host); it is resolved lazily because the
4
+ // plugin builds without the host SDK installed.
5
+
6
+ export type RuntimeCompactionResult = {
7
+ ok: boolean;
8
+ compacted: boolean;
9
+ reason?: string;
10
+ result?: {
11
+ summary?: string;
12
+ firstKeptEntryId?: string;
13
+ tokensBefore: number;
14
+ tokensAfter?: number;
15
+ details?: unknown;
16
+ };
17
+ };
18
+
19
+ export type RuntimeCompactionDelegate = (
20
+ params: Record<string, unknown>,
21
+ ) => Promise<RuntimeCompactionResult>;
22
+
23
+ const PLUGIN_SDK_CORE_SPECIFIER = "openclaw/plugin-sdk/core";
24
+
25
+ let cached: Promise<RuntimeCompactionDelegate | undefined> | undefined;
26
+
27
+ export function loadRuntimeCompactionDelegate(): Promise<RuntimeCompactionDelegate | undefined> {
28
+ cached ??= (async () => {
29
+ try {
30
+ const mod = (await import(PLUGIN_SDK_CORE_SPECIFIER)) as {
31
+ delegateCompactionToRuntime?: unknown;
32
+ };
33
+ return typeof mod?.delegateCompactionToRuntime === "function"
34
+ ? (mod.delegateCompactionToRuntime as RuntimeCompactionDelegate)
35
+ : undefined;
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ })();
40
+ return cached;
41
+ }
@@ -9,7 +9,7 @@ type Logger = {
9
9
  type ClientRuntimeConfig = {
10
10
  baseUrl: string;
11
11
  apiKey: string;
12
- peer_role: "none" | "assistant" | "person";
12
+ peer_role: "none" | "assistant" | "sender";
13
13
  peer_prefix: string;
14
14
  timeoutMs: number;
15
15
  accountId?: string;
@@ -114,7 +114,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
114
114
  const peerId = resolveOpenVikingMessagePeerId({
115
115
  peerRole: deps.peerRole,
116
116
  role,
117
- personPeerId: session.actorPeerId,
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 ?? "assistant";
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
- personPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
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" | "person";
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
- personPeerId?: string;
32
+ senderPeerId?: string;
33
33
  assistantPeerId?: string;
34
34
  }): string | undefined {
35
- if (params.peerRole === "person" && params.role === "user") {
36
- return params.personPeerId;
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
- personPeerId?: string;
46
+ senderPeerId?: string;
47
47
  assistantPeerId?: string;
48
48
  }): string | undefined {
49
49
  const actorPeerId = resolveOpenVikingMessagePeerId({
50
50
  ...params,
51
- role: params.peerRole === "person" ? "user" : "assistant",
51
+ role: params.peerRole === "sender" ? "user" : "assistant",
52
52
  });
53
- if (params.peerRole === "person" && !actorPeerId) {
54
- throw new Error("openviking: peer_role=person requires a sender identity");
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
  }
@@ -117,6 +117,8 @@ export type CompactOpenVikingSessionParams = {
117
117
  logger: ContextEngineLifecycleLogger;
118
118
  resolveAgentId: (sessionId: string, sessionKey?: string, ovSessionId?: string) => string;
119
119
  isBypassedSession: (params: { sessionId?: string; sessionKey?: string }) => boolean;
120
+ /** Host-native compaction for bypassed sessions; resolves undefined when unavailable. */
121
+ runtimeCompact?: () => Promise<CompactOpenVikingSessionResult | undefined>;
120
122
  diag: (stage: string, sessionId: string, data: Record<string, unknown>) => void;
121
123
  };
122
124
 
@@ -497,8 +499,8 @@ export async function assembleOpenVikingSession({
497
499
  const routingRef = sessionId ?? sessionKey ?? ovSessionId;
498
500
  const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
499
501
  const actorPeerId = resolveOpenVikingActorPeerId({
500
- peerRole: cfg.peer_role ?? "assistant",
501
- personPeerId: sanitizeOpenVikingPeerId(sender.senderId),
502
+ peerRole: cfg.peer_role ?? "none",
503
+ senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
502
504
  assistantPeerId: agentId,
503
505
  });
504
506
  const queryConfig = await queryConfigStore?.getEffective({
@@ -910,9 +912,9 @@ export async function afterTurnOpenVikingSession({
910
912
  undefined,
911
913
  createdAt,
912
914
  resolveOpenVikingMessagePeerId({
913
- peerRole: cfg.peer_role ?? "assistant",
915
+ peerRole: cfg.peer_role ?? "none",
914
916
  role: msg.role,
915
- personPeerId: senderRoleId,
917
+ senderPeerId: senderRoleId,
916
918
  assistantPeerId: agentId,
917
919
  }),
918
920
  );
@@ -1014,6 +1016,7 @@ export async function compactOpenVikingSession({
1014
1016
  logger,
1015
1017
  resolveAgentId,
1016
1018
  isBypassedSession,
1019
+ runtimeCompact,
1017
1020
  diag,
1018
1021
  }: CompactOpenVikingSessionParams): Promise<CompactOpenVikingSessionResult> {
1019
1022
  const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
@@ -1027,6 +1030,18 @@ export async function compactOpenVikingSession({
1027
1030
  });
1028
1031
 
1029
1032
  if (isBypassedSession({ sessionId, sessionKey })) {
1033
+ // Bypassed sessions store nothing in OV, but the host still needs a real
1034
+ // compaction (preflight hard-fails on an unknown skip reason), so hand the
1035
+ // request to OpenClaw's native compactor.
1036
+ const delegated = await runtimeCompact?.();
1037
+ if (delegated) {
1038
+ diag("compact_result", ovSessionId, {
1039
+ ok: delegated.ok,
1040
+ compacted: delegated.compacted,
1041
+ reason: delegated.reason ?? "session_bypassed_runtime_compaction",
1042
+ });
1043
+ return delegated;
1044
+ }
1030
1045
  diag("compact_result", ovSessionId, {
1031
1046
  ok: true,
1032
1047
  compacted: false,
@@ -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?: "none" | "assistant" | "person";
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?: "none" | "assistant" | "person";
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?: "none" | "assistant" | "person";
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?: "none" | "assistant" | "person";
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 "none" | "assistant" | "person" } : {}),
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 "none" | "assistant" | "person" } : {}),
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,
@@ -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. **Agent 标识前缀**(可选) —— 用于区分多个 agent 的记忆命名空间,留空就用默认
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. **peer prefix** (optional) — used to namespace memories across agents. Leave blank for default.
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. `PEER_PREFIX` (OPTIONAL)
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
- ### 4d. (Conditional) Multi-Tenant Root-Key Fields
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-prefix PEER_PREFIX` only if the user gave one.
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-prefix` only if the user gave one. Use **`--peer-prefix`** for assistant peer prefixes; the old ID-style setup flag is no longer supported.
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` | `assistant` | Peer identity mode: `none`, `assistant`, or `person`. Session messages use body `peer_id`; data-plane recall/search uses `X-OpenViking-Actor-Peer`. |
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` | `assistant` | Peer identity mode: `none`, `assistant`, or `person`. Session messages use body `peer_id`; data-plane recall/search uses `X-OpenViking-Actor-Peer`. |
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