@openviking/openclaw-plugin 2026.7.11 → 2026.8.6

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.
Files changed (48) hide show
  1. package/INSTALL-ZH.md +39 -0
  2. package/INSTALL.md +39 -0
  3. package/README.md +5 -0
  4. package/auto-recall.ts +5 -4
  5. package/config.ts +145 -6
  6. package/context-engine.ts +0 -1
  7. package/dist/auto-recall.js +4 -4
  8. package/dist/config.js +101 -4
  9. package/dist/context-engine.js +0 -1
  10. package/dist/index.js +13 -4
  11. package/dist/plugin/openviking-archive-tools.js +1 -2
  12. package/dist/plugin/openviking-command-definitions.js +3 -3
  13. package/dist/plugin/openviking-experience-tools.js +105 -0
  14. package/dist/plugin/openviking-import-tools.js +2 -2
  15. package/dist/plugin/openviking-memory-recall-tools.js +4 -4
  16. package/dist/plugin/openviking-memory-tools.js +11 -6
  17. package/dist/plugin/openviking-query-runtime.js +1 -1
  18. package/dist/plugin/openviking-query-tools.js +4 -4
  19. package/dist/plugin/openviking-recall-trace-runtime.js +3 -3
  20. package/dist/plugin/openviking-session-routing-runtime.js +9 -2
  21. package/dist/plugin/openviking-tool-result-tools.js +3 -3
  22. package/dist/routing/identity-routing.js +27 -0
  23. package/dist/routing/memory-uri.js +1 -0
  24. package/dist/services/context-lifecycle-service.js +21 -16
  25. package/dist/services/context-message-adapter.js +2 -9
  26. package/index.ts +13 -3
  27. package/install-manifest.json +1 -0
  28. package/openclaw.plugin.json +46 -3
  29. package/package.json +1 -1
  30. package/plugin/command-registration.ts +2 -0
  31. package/plugin/openviking-archive-tools.ts +1 -4
  32. package/plugin/openviking-command-definitions.ts +4 -3
  33. package/plugin/openviking-experience-tools.ts +160 -0
  34. package/plugin/openviking-import-tools.ts +4 -2
  35. package/plugin/openviking-memory-recall-tools.ts +6 -4
  36. package/plugin/openviking-memory-tools.ts +18 -10
  37. package/plugin/openviking-query-runtime.ts +1 -1
  38. package/plugin/openviking-query-tools.ts +6 -4
  39. package/plugin/openviking-recall-trace-runtime.ts +4 -3
  40. package/plugin/openviking-recall-trace-tools.ts +2 -0
  41. package/plugin/openviking-session-routing-runtime.ts +15 -1
  42. package/plugin/openviking-tool-result-tools.ts +0 -6
  43. package/registries/openviking-tools.ts +6 -0
  44. package/routing/identity-routing.ts +41 -0
  45. package/routing/memory-uri.ts +1 -0
  46. package/services/context-lifecycle-service.ts +27 -18
  47. package/services/context-message-adapter.ts +2 -9
  48. package/skills/ov-experience-memory/SKILL.md +156 -0
@@ -3,6 +3,7 @@
3
3
  "name": "OpenViking",
4
4
  "kind": "context-engine",
5
5
  "description": "OpenClaw context-engine plugin for memory management — powered by OpenViking",
6
+ "icon": "https://raw.githubusercontent.com/volcengine/OpenViking/main/docs/images/ov-logo-icon.png",
6
7
  "activation": {
7
8
  "onStartup": true,
8
9
  "onCapabilities": [
@@ -12,11 +13,14 @@
12
13
  },
13
14
  "contracts": {
14
15
  "tools": [
16
+ "add_resource",
15
17
  "add_skill",
16
18
  "ov_search",
17
19
  "ov_read",
18
20
  "ov_multi_read",
19
21
  "ov_list",
22
+ "search_experience",
23
+ "read_experience",
20
24
  "memory_recall",
21
25
  "ov_recall_trace",
22
26
  "memory_store",
@@ -96,7 +100,7 @@
96
100
  "label": "OpenViking API Key",
97
101
  "sensitive": true,
98
102
  "placeholder": "${OPENVIKING_API_KEY}",
99
- "help": "Optional API key for OpenViking server"
103
+ "help": "Optional API key for OpenViking server. Accepts a plain string, ${ENV_VAR} interpolation, or a SecretRef object ({source: env/file/exec, id}). Prefer a SecretRef so the key is never stored as plaintext in openclaw.json."
100
104
  },
101
105
  "headers": {
102
106
  "label": "Headers",
@@ -250,7 +254,7 @@
250
254
  "enabledTools": {
251
255
  "label": "Enabled Tools",
252
256
  "placeholder": "default",
253
- "help": "Agent-visible tool allowlist. Accepts tool names or groups: default, all, memory, resource_query, import, recall_trace, archive, tool_result. add_resource also requires enableAddResourceTool=true.",
257
+ "help": "Agent-visible tool allowlist. Accepts tool names or groups: default, all, memory, resource_query, experience, import, recall_trace, archive, tool_result. add_resource also requires enableAddResourceTool=true.",
254
258
  "advanced": true
255
259
  },
256
260
  "disabledTools": {
@@ -290,7 +294,46 @@
290
294
  "description": "Deprecated and ignored. Tenant identity headers are controlled by explicit accountId/userId."
291
295
  },
292
296
  "apiKey": {
293
- "type": "string"
297
+ "oneOf": [
298
+ {
299
+ "type": "string",
300
+ "description": "Plain API key or ${ENV_VAR} interpolation. For secret-manager-managed keys, prefer the SecretRef object shape below."
301
+ },
302
+ {
303
+ "type": "object",
304
+ "title": "SecretRef (env)",
305
+ "description": "Read the API key from an environment variable.",
306
+ "additionalProperties": false,
307
+ "required": ["source", "id"],
308
+ "properties": {
309
+ "source": { "type": "string", "const": "env", "description": "Look up the key in process.env[id] at plugin load time. Equivalent to ${ENV} interpolation but declarative and visible in the UI." },
310
+ "id": { "type": "string", "description": "Name of the environment variable. Example: OPENVIKING_API_KEY." }
311
+ }
312
+ },
313
+ {
314
+ "type": "object",
315
+ "title": "SecretRef (file)",
316
+ "description": "Read the API key from a file on disk, e.g. a Kubernetes secretKeyRef mount volume or a 0600-permission managed file. ~ is expanded and trailing whitespace is trimmed.",
317
+ "additionalProperties": false,
318
+ "required": ["source", "id"],
319
+ "properties": {
320
+ "source": { "type": "string", "const": "file" },
321
+ "id": { "type": "string", "description": "Absolute file path or ~/relative path. Example: /etc/secrets/openviking.key." }
322
+ }
323
+ },
324
+ {
325
+ "type": "object",
326
+ "title": "SecretRef (exec)",
327
+ "description": "Read the API key by shelling out to a secret-manager CLI. Matches the shape used by OpenClaw's core provider configs so 1Password, HashiCorp Vault, gopass, etc. can manage the OpenViking key alongside LLM/TTS/MCP credentials.",
328
+ "additionalProperties": false,
329
+ "required": ["source", "provider", "id"],
330
+ "properties": {
331
+ "source": { "type": "string", "const": "exec" },
332
+ "provider": { "type": "string", "description": "CLI binary name on PATH. Examples: op, vault, gopass." },
333
+ "id": { "type": "string", "description": "Argument passed to the provider CLI to select the specific secret, e.g. 'vault kv get -field=key secret/openviking' in plain-arg form; this implementation passes id as a single arg." }
334
+ }
335
+ }
336
+ ]
294
337
  },
295
338
  "headers": {
296
339
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/openclaw-plugin",
3
- "version": "2026.7.11",
3
+ "version": "2026.8.6",
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",
@@ -5,6 +5,8 @@ export type PluginCommandContext = {
5
5
  sessionId?: string;
6
6
  agentId?: string;
7
7
  ovSessionId?: string;
8
+ senderId?: string;
9
+ requesterSenderId?: string;
8
10
  };
9
11
 
10
12
  export type CommandResult = {
@@ -26,7 +26,7 @@ export type OpenVikingArchiveClient = {
26
26
  grepSessionArchives: (
27
27
  sessionId: string,
28
28
  pattern: string,
29
- options: { archiveId?: string; caseInsensitive: boolean; agentId?: string },
29
+ options: { archiveId?: string; caseInsensitive: boolean },
30
30
  ) => Promise<{
31
31
  count?: number;
32
32
  matches?: OpenVikingArchiveMatch[];
@@ -34,7 +34,6 @@ export type OpenVikingArchiveClient = {
34
34
  getSessionArchive: (
35
35
  sessionId: string,
36
36
  archiveId: string,
37
- agentId?: string,
38
37
  ) => Promise<{
39
38
  archive_id: string;
40
39
  abstract?: string;
@@ -128,7 +127,6 @@ export function registerOpenVikingArchiveTools(deps: OpenVikingArchiveToolsDeps)
128
127
  const result = await client.grepSessionArchives(ovSessionId, escapedQuery, {
129
128
  archiveId,
130
129
  caseInsensitive: true,
131
- agentId,
132
130
  });
133
131
  const traceResults: RecallTraceResult[] = (result.matches ?? []).slice(0, deps.traceRecallMaxResultsPerSearch).map((match) => ({
134
132
  uri: match.uri,
@@ -263,7 +261,6 @@ export function registerOpenVikingArchiveTools(deps: OpenVikingArchiveToolsDeps)
263
261
  const detail = await client.getSessionArchive(
264
262
  session.ovSessionId,
265
263
  archiveId,
266
- session.agentId,
267
264
  );
268
265
 
269
266
  const header = [
@@ -6,6 +6,7 @@ export type OpenVikingCommandSession = {
6
6
  sessionKey?: string;
7
7
  ovSessionId?: string;
8
8
  agentId: string;
9
+ actorPeerId?: string;
9
10
  };
10
11
 
11
12
  export type OpenVikingCommandToolResult = {
@@ -217,7 +218,7 @@ export function createOpenVikingCommandDefinitions(
217
218
  }
218
219
  const session = deps.resolvePluginSessionRouting(ctx);
219
220
  const input = deps.parseAddResourceCommandArgs(ctx.args ?? "");
220
- return toCommandResult(await deps.addResourceOpenViking(input, session.agentId));
221
+ return toCommandResult(await deps.addResourceOpenViking(input, session.actorPeerId));
221
222
  } catch (err) {
222
223
  return { text: `OpenViking add resource failed: ${err instanceof Error ? err.message : String(err)}` };
223
224
  }
@@ -234,7 +235,7 @@ export function createOpenVikingCommandDefinitions(
234
235
  }
235
236
  const session = deps.resolvePluginSessionRouting(ctx);
236
237
  const input = deps.parseAddSkillCommandArgs(ctx.args ?? "");
237
- return toCommandResult(await deps.addSkillOpenViking(input, session.agentId));
238
+ return toCommandResult(await deps.addSkillOpenViking(input, session.actorPeerId));
238
239
  } catch (err) {
239
240
  return { text: `OpenViking add skill failed: ${err instanceof Error ? err.message : String(err)}` };
240
241
  }
@@ -251,7 +252,7 @@ export function createOpenVikingCommandDefinitions(
251
252
  }
252
253
  const session = deps.resolvePluginSessionRouting(ctx);
253
254
  const input = deps.parseOVSearchCommandArgs(ctx.args ?? "");
254
- return toCommandResult(await deps.searchOpenViking(input, session.agentId, session));
255
+ return toCommandResult(await deps.searchOpenViking(input, session.actorPeerId, session));
255
256
  } catch (err) {
256
257
  return { text: `OpenViking search failed: ${err instanceof Error ? err.message : String(err)}` };
257
258
  }
@@ -0,0 +1,160 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ import type { FindResult } from "../client.js";
4
+
5
+ const EXPERIENCE_TARGET_URI = "viking://user/memories/experiences/";
6
+ const DEFAULT_LIMIT = 5;
7
+ const MAX_LIMIT = 20;
8
+ const EXPERIENCE_SIDECAR_FILENAMES = new Set([".abstract.md", ".overview.md", ".relations.json"]);
9
+
10
+ export type OpenVikingExperienceToolContext = {
11
+ sessionKey?: string;
12
+ sessionId?: string;
13
+ agentId?: string;
14
+ senderId?: string;
15
+ requesterSenderId?: string;
16
+ };
17
+
18
+ export type OpenVikingExperienceSession = {
19
+ sessionId?: string;
20
+ sessionKey?: string;
21
+ ovSessionId?: string;
22
+ agentId: string;
23
+ actorPeerId?: string;
24
+ };
25
+
26
+ type OpenVikingExperienceClient = {
27
+ find: (
28
+ query: string,
29
+ options: { targetUri: string; limit: number; scoreThreshold?: number },
30
+ actorPeerId?: string,
31
+ ) => Promise<FindResult>;
32
+ read: (uri: string, actorPeerId?: string) => Promise<unknown>;
33
+ };
34
+
35
+ export type OpenVikingExperienceToolsDeps = {
36
+ registerTool: (toolOrFactory: unknown, opts: { name: string }) => void;
37
+ getClient: () => Promise<OpenVikingExperienceClient>;
38
+ resolvePluginSessionRouting: (ctx?: OpenVikingExperienceToolContext) => OpenVikingExperienceSession;
39
+ isBypassedSession: (ctx?: OpenVikingExperienceToolContext) => boolean;
40
+ makeBypassedToolResult: (toolName: string) => unknown;
41
+ userId?: string;
42
+ };
43
+
44
+ function clampLimit(value: unknown): number {
45
+ const parsed = Number.isFinite(Number(value)) ? Math.trunc(Number(value)) : DEFAULT_LIMIT;
46
+ return Math.max(1, Math.min(MAX_LIMIT, parsed));
47
+ }
48
+
49
+ function experienceRelativePath(uri: string): string {
50
+ return uri.match(/^viking:\/\/user\/[^/]+\/memories\/experiences\/(.+)$/)?.[1] ?? "";
51
+ }
52
+
53
+ export function isCanonicalExperienceUri(uri: unknown, expectedUserId?: string): uri is string {
54
+ const value = typeof uri === "string" ? uri.trim() : "";
55
+ if (!value || value.includes("?") || value.includes("#")) {
56
+ return false;
57
+ }
58
+ const owner = value.match(/^viking:\/\/user\/([^/]+)\//)?.[1] ?? "";
59
+ if (expectedUserId?.trim() && owner !== expectedUserId.trim()) {
60
+ return false;
61
+ }
62
+ const relative = experienceRelativePath(value);
63
+ const segments = relative.split("/");
64
+ const basename = segments.at(-1) ?? "";
65
+ return Boolean(
66
+ relative
67
+ && segments.every((segment) => segment && segment !== "." && segment !== "..")
68
+ && !EXPERIENCE_SIDECAR_FILENAMES.has(basename)
69
+ );
70
+ }
71
+
72
+ function titleFromUri(uri: string): string {
73
+ const basename = uri.split("/").at(-1) ?? uri;
74
+ const title = basename.replace(/\.md$/i, "");
75
+ try {
76
+ return decodeURIComponent(title);
77
+ } catch {
78
+ return title;
79
+ }
80
+ }
81
+
82
+ function jsonToolResult(payload: Record<string, unknown>): {
83
+ content: Array<{ type: "text"; text: string }>;
84
+ } {
85
+ return {
86
+ content: [{ type: "text", text: JSON.stringify(payload) }],
87
+ };
88
+ }
89
+
90
+ export function registerOpenVikingExperienceTools(deps: OpenVikingExperienceToolsDeps): void {
91
+ deps.registerTool(
92
+ (ctx: OpenVikingExperienceToolContext) => ({
93
+ name: "search_experience",
94
+ label: "Search Experience (OpenViking)",
95
+ description:
96
+ "Search reusable task-execution experiences for the current OpenViking user. " +
97
+ "Use before executing operational or multi-step tasks, then call read_experience for selected results.",
98
+ parameters: Type.Object({
99
+ query: Type.String({ description: "Current task, situation, or execution problem to search for" }),
100
+ limit: Type.Optional(Type.Integer({
101
+ description: `Maximum results to return. Default: ${DEFAULT_LIMIT}; maximum: ${MAX_LIMIT}`,
102
+ minimum: 1,
103
+ maximum: MAX_LIMIT,
104
+ })),
105
+ }, { additionalProperties: false }),
106
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
107
+ if (deps.isBypassedSession(ctx)) {
108
+ return deps.makeBypassedToolResult("search_experience");
109
+ }
110
+ const query = typeof params.query === "string" ? params.query.trim() : "";
111
+ if (!query) {
112
+ throw new Error("query is required");
113
+ }
114
+ const session = deps.resolvePluginSessionRouting(ctx);
115
+ const client = await deps.getClient();
116
+ const result = await client.find(query, {
117
+ targetUri: EXPERIENCE_TARGET_URI,
118
+ limit: clampLimit(params.limit),
119
+ }, session.actorPeerId);
120
+ const results = (result.memories ?? [])
121
+ .filter((item) => isCanonicalExperienceUri(item.uri, deps.userId))
122
+ .map((item) => ({
123
+ uri: item.uri,
124
+ title: titleFromUri(item.uri),
125
+ score: typeof item.score === "number" ? item.score : 0,
126
+ snippet: item.abstract || item.overview || "",
127
+ }));
128
+ return jsonToolResult({ results });
129
+ },
130
+ }),
131
+ { name: "search_experience" },
132
+ );
133
+
134
+ deps.registerTool(
135
+ (ctx: OpenVikingExperienceToolContext) => ({
136
+ name: "read_experience",
137
+ label: "Read Experience (OpenViking)",
138
+ description:
139
+ "Read the complete Markdown body of one Experience returned by search_experience and use it as execution guidance.",
140
+ parameters: Type.Object({
141
+ uri: Type.String({ description: "Exact canonical Experience URI returned by search_experience" }),
142
+ }, { additionalProperties: false }),
143
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
144
+ if (deps.isBypassedSession(ctx)) {
145
+ return deps.makeBypassedToolResult("read_experience");
146
+ }
147
+ const uri = typeof params.uri === "string" ? params.uri.trim() : "";
148
+ if (!isCanonicalExperienceUri(uri, deps.userId)) {
149
+ throw new Error("uri must be a canonical OpenViking Experience URI returned by search_experience");
150
+ }
151
+ const session = deps.resolvePluginSessionRouting(ctx);
152
+ const client = await deps.getClient();
153
+ const content = await client.read(uri, session.actorPeerId);
154
+ const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
155
+ return jsonToolResult({ uri, content: text });
156
+ },
157
+ }),
158
+ { name: "read_experience" },
159
+ );
160
+ }
@@ -12,6 +12,7 @@ export type OpenVikingImportToolContext = {
12
12
  sessionId?: string;
13
13
  agentId?: string;
14
14
  senderId?: string;
15
+ requesterSenderId?: string;
15
16
  };
16
17
 
17
18
  export type OpenVikingImportSession = {
@@ -19,6 +20,7 @@ export type OpenVikingImportSession = {
19
20
  sessionKey?: string;
20
21
  ovSessionId?: string;
21
22
  agentId: string;
23
+ actorPeerId?: string;
22
24
  };
23
25
 
24
26
  export type OpenVikingImportClient = {
@@ -80,7 +82,7 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps):
80
82
  instruction: typeof params.instruction === "string" ? params.instruction : undefined,
81
83
  wait: typeof params.wait === "boolean" ? params.wait : undefined,
82
84
  timeout: typeof params.timeout === "number" ? params.timeout : undefined,
83
- }, session.agentId);
85
+ }, session.actorPeerId);
84
86
  return {
85
87
  content: [{ type: "text" as const, text: formatResourceImportText(result) }],
86
88
  details: {
@@ -118,7 +120,7 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps):
118
120
  data: params.data,
119
121
  wait: typeof params.wait === "boolean" ? params.wait : undefined,
120
122
  timeout: typeof params.timeout === "number" ? params.timeout : undefined,
121
- }, session.agentId);
123
+ }, session.actorPeerId);
122
124
  return {
123
125
  content: [{ type: "text" as const, text: formatSkillImportText(result) }],
124
126
  details: {
@@ -15,6 +15,7 @@ export type OpenVikingMemoryRecallToolContext = {
15
15
  sessionId?: string;
16
16
  agentId?: string;
17
17
  senderId?: string;
18
+ requesterSenderId?: string;
18
19
  };
19
20
 
20
21
  export type OpenVikingMemoryRecallSession = {
@@ -22,6 +23,7 @@ export type OpenVikingMemoryRecallSession = {
22
23
  sessionKey?: string;
23
24
  ovSessionId?: string;
24
25
  agentId: string;
26
+ actorPeerId?: string;
25
27
  };
26
28
 
27
29
  export type OpenVikingMemoryRecallClient = {
@@ -163,7 +165,7 @@ export function registerOpenVikingMemoryRecallTools(
163
165
  const recallClient = await deps.getClient();
164
166
  if (deps.cfg.logFindRequests) {
165
167
  deps.logger.info?.(
166
- `openviking: memory_recall X-OpenViking-Actor-Peer="${session.agentId}" ` +
168
+ `openviking: memory_recall X-OpenViking-Actor-Peer="${session.actorPeerId ?? "none"}" ` +
167
169
  `(plugin defaultAgentId="${recallClient.getDefaultAgentId()}" is unused when session context is present)`,
168
170
  );
169
171
  }
@@ -177,7 +179,7 @@ export function registerOpenVikingMemoryRecallTools(
177
179
  targetUri,
178
180
  limit: requestLimit,
179
181
  scoreThreshold: 0,
180
- actorPeerId: session.agentId,
182
+ actorPeerId: session.actorPeerId,
181
183
  },
182
184
  );
183
185
  const traceResults = [
@@ -207,7 +209,7 @@ export function registerOpenVikingMemoryRecallTools(
207
209
  limit: requestLimit,
208
210
  scoreThreshold: 0,
209
211
  contextType: search.contextType,
210
- actorPeerId: session.agentId,
212
+ actorPeerId: session.actorPeerId,
211
213
  },
212
214
  ),
213
215
  ));
@@ -319,7 +321,7 @@ export function registerOpenVikingMemoryRecallTools(
319
321
  }
320
322
  const { lines: memoryLines } = await deps.buildMemoryLinesWithBudget(
321
323
  memories,
322
- (uri) => recallClient.read(uri, session.agentId),
324
+ (uri) => recallClient.read(uri, session.actorPeerId),
323
325
  {
324
326
  recallPreferAbstract: false,
325
327
  recallMaxInjectedChars: queryConfig.maxInjectedChars,
@@ -2,6 +2,10 @@ import { Type } from "@sinclair/typebox";
2
2
 
3
3
  import type { CommitSessionResult, FindResultItem } from "../client.js";
4
4
  import { clampScore, postProcessMemories } from "../memory-ranking.js";
5
+ import {
6
+ resolveOpenVikingMessagePeerId,
7
+ type OpenVikingPeerRole,
8
+ } from "../routing/identity-routing.js";
5
9
  import { isMemoryUri } from "../routing/memory-uri.js";
6
10
 
7
11
  export type OpenVikingMemoryToolContext = {
@@ -17,6 +21,7 @@ export type OpenVikingMemorySession = {
17
21
  sessionKey?: string;
18
22
  ovSessionId?: string;
19
23
  agentId: string;
24
+ actorPeerId?: string;
20
25
  };
21
26
 
22
27
  export type OpenVikingMemoryClient = {
@@ -30,7 +35,7 @@ export type OpenVikingMemoryClient = {
30
35
  ) => Promise<void>;
31
36
  commitSession: (
32
37
  sessionId: string,
33
- options: { wait: true; agentId: string; keepRecentCount: number },
38
+ options: { wait: true; agentId?: string; keepRecentCount: number },
34
39
  ) => Promise<CommitSessionResult>;
35
40
  deleteUri: (uri: string, agentId?: string) => Promise<void>;
36
41
  find: (
@@ -45,8 +50,7 @@ export type OpenVikingMemoryToolsDeps = {
45
50
  getClient: () => Promise<OpenVikingMemoryClient>;
46
51
  normalizeSessionId: (sessionId: string) => string;
47
52
  createTempSessionId: () => string;
48
- extractSenderId: (ctx?: OpenVikingMemoryToolContext) => string | undefined;
49
- toRoleId: (senderId?: string) => string | undefined;
53
+ peerRole: OpenVikingPeerRole;
50
54
  resolvePluginSessionRouting: (ctx?: OpenVikingMemoryToolContext) => OpenVikingMemorySession;
51
55
  isBypassedSession: (ctx?: OpenVikingMemoryToolContext) => boolean;
52
56
  makeBypassedToolResult: (toolName: string) => unknown;
@@ -107,18 +111,22 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
107
111
  sessionId = deps.createTempSessionId();
108
112
  usedTempSession = true;
109
113
  }
110
- const roleId = role === "user" ? deps.toRoleId(deps.extractSenderId(ctx)) : undefined;
114
+ const peerId = resolveOpenVikingMessagePeerId({
115
+ peerRole: deps.peerRole,
116
+ role,
117
+ personPeerId: session.actorPeerId,
118
+ assistantPeerId: session.actorPeerId,
119
+ });
111
120
  await client.addSessionMessage(
112
121
  sessionId,
113
122
  role,
114
123
  [{ type: "text", text }],
115
- session.agentId,
116
124
  undefined,
117
- roleId,
125
+ undefined,
126
+ peerId,
118
127
  );
119
128
  const commitResult = await client.commitSession(sessionId, {
120
129
  wait: true,
121
- agentId: session.agentId,
122
130
  keepRecentCount: 0,
123
131
  });
124
132
  const memoriesCount = totalCommitMemories(commitResult);
@@ -216,7 +224,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
216
224
  details: { action: "rejected", uri },
217
225
  };
218
226
  }
219
- await client.deleteUri(uri, session.agentId);
227
+ await client.deleteUri(uri, session.actorPeerId);
220
228
  return {
221
229
  content: [{ type: "text", text: `Forgotten: ${uri}` }],
222
230
  details: { action: "deleted", uri },
@@ -252,7 +260,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
252
260
  limit: requestLimit,
253
261
  scoreThreshold: 0,
254
262
  },
255
- session.agentId,
263
+ session.actorPeerId,
256
264
  );
257
265
  const candidates = postProcessMemories(result.memories ?? [], {
258
266
  limit: requestLimit,
@@ -272,7 +280,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
272
280
  }
273
281
  const top = candidates[0];
274
282
  if (candidates.length === 1 && clampScore(top.score) >= 0.85) {
275
- await client.deleteUri(top.uri, session.agentId);
283
+ await client.deleteUri(top.uri, session.actorPeerId);
276
284
  return {
277
285
  content: [{ type: "text", text: `Forgotten: ${top.uri}` }],
278
286
  details: { action: "deleted", uri: top.uri, score: top.score ?? 0 },
@@ -445,7 +445,7 @@ export function createOpenVikingQueryRuntime<TQueryConfigContext>(deps: OpenViki
445
445
  sessionId: traceCtx?.sessionId,
446
446
  sessionKey: traceCtx?.sessionKey,
447
447
  ovSessionId: traceCtx?.ovSessionId,
448
- agentId,
448
+ agentId: traceCtx?.agentId,
449
449
  source: "ov_search",
450
450
  operationType: "semantic_find",
451
451
  resourceTypes: [...new Set(searches.map((search) => search.resourceType).filter((resourceType): resourceType is RecallResourceType => resourceType !== "archive"))],
@@ -5,6 +5,7 @@ export type OpenVikingQueryToolContext = {
5
5
  sessionId?: string;
6
6
  agentId?: string;
7
7
  senderId?: string;
8
+ requesterSenderId?: string;
8
9
  };
9
10
 
10
11
  export type OpenVikingQuerySession = {
@@ -12,6 +13,7 @@ export type OpenVikingQuerySession = {
12
13
  sessionKey?: string;
13
14
  ovSessionId?: string;
14
15
  agentId: string;
16
+ actorPeerId?: string;
15
17
  };
16
18
 
17
19
  export type OVSearchInput = {
@@ -74,7 +76,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
74
76
  query: String((params as { query?: unknown }).query ?? ""),
75
77
  uri: typeof params.uri === "string" ? params.uri : undefined,
76
78
  limit: typeof params.limit === "number" ? params.limit : undefined,
77
- }, session.agentId, session);
79
+ }, session.actorPeerId, session);
78
80
  },
79
81
  }),
80
82
  { name: "ov_search" },
@@ -99,7 +101,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
99
101
  const session = deps.resolvePluginSessionRouting(ctx);
100
102
  return deps.readOpenVikingContent({
101
103
  uri: String((params as { uri?: unknown }).uri ?? ""),
102
- }, session.agentId);
104
+ }, session.actorPeerId);
103
105
  },
104
106
  }),
105
107
  { name: "ov_read" },
@@ -125,7 +127,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
125
127
  const uris = Array.isArray((params as { uris?: unknown }).uris)
126
128
  ? (params as { uris: unknown[] }).uris.map((uri) => String(uri))
127
129
  : [];
128
- return deps.multiReadOpenVikingContent({ uris }, session.agentId);
130
+ return deps.multiReadOpenVikingContent({ uris }, session.actorPeerId);
129
131
  },
130
132
  }),
131
133
  { name: "ov_multi_read" },
@@ -153,7 +155,7 @@ export function registerOpenVikingQueryTools(deps: OpenVikingQueryToolsDeps): vo
153
155
  recursive: typeof params.recursive === "boolean" ? params.recursive : undefined,
154
156
  simple: typeof params.simple === "boolean" ? params.simple : undefined,
155
157
  limit: typeof params.limit === "number" ? params.limit : undefined,
156
- }, session.agentId);
158
+ }, session.actorPeerId);
157
159
  },
158
160
  }),
159
161
  { name: "ov_list" },
@@ -26,6 +26,7 @@ export type OpenVikingRecallTraceSession = {
26
26
  sessionKey?: string;
27
27
  ovSessionId?: string;
28
28
  agentId: string;
29
+ actorPeerId?: string;
29
30
  };
30
31
 
31
32
  export type OpenVikingRecallTraceQueryResult = {
@@ -248,11 +249,11 @@ export function createOpenVikingRecallTraceRuntime(deps: OpenVikingRecallTraceRu
248
249
  for (const fallbackQuery of fallbackIdentities) {
249
250
  const fallback = await deps.traceRecorder.queryWithFallback(fallbackQuery);
250
251
  if (fallback.entries.length > 0) {
251
- return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.agentId);
252
+ return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.actorPeerId);
252
253
  }
253
254
  }
254
255
  }
255
- return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.agentId);
256
+ return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.actorPeerId);
256
257
  };
257
258
 
258
259
  const handleUriDetail = async (request?: RecallTraceRouteRequest) => {
@@ -298,7 +299,7 @@ export function createOpenVikingRecallTraceRuntime(deps: OpenVikingRecallTraceRu
298
299
  }
299
300
  try {
300
301
  const client = await deps.getClient();
301
- const content = await client.read(uri, toString(query.agentId) ?? session.agentId);
302
+ const content = await client.read(uri, session.actorPeerId);
302
303
  const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
303
304
  const chars = Array.from(text);
304
305
  const slice = chars.slice(offset, offset + contentLimit).join("");
@@ -11,6 +11,7 @@ export type OpenVikingRecallTraceToolContext = {
11
11
  sessionId?: string;
12
12
  agentId?: string;
13
13
  senderId?: string;
14
+ requesterSenderId?: string;
14
15
  };
15
16
 
16
17
  export type OpenVikingRecallTraceSession = {
@@ -18,6 +19,7 @@ export type OpenVikingRecallTraceSession = {
18
19
  sessionKey?: string;
19
20
  ovSessionId?: string;
20
21
  agentId: string;
22
+ actorPeerId?: string;
21
23
  };
22
24
 
23
25
  export type RecallTraceToolInput = {
@@ -2,6 +2,9 @@ import type { QueryConfigContext } from "../query-config.js";
2
2
  import {
3
3
  createSessionAgentResolver,
4
4
  openClawSessionToOvStorageId,
5
+ resolveOpenVikingActorPeerId,
6
+ sanitizeOpenVikingPeerId,
7
+ type OpenVikingPeerRole,
5
8
  } from "../routing/identity-routing.js";
6
9
 
7
10
  type Logger = {
@@ -10,6 +13,8 @@ type Logger = {
10
13
 
11
14
  export type SessionAgentLookup = {
12
15
  agentId?: string;
16
+ senderId?: string;
17
+ requesterSenderId?: string;
13
18
  sessionId?: string;
14
19
  sessionKey?: string;
15
20
  ovSessionId?: string;
@@ -20,13 +25,16 @@ export type PluginSessionRouting = {
20
25
  sessionKey?: string;
21
26
  ovSessionId?: string;
22
27
  agentId: string;
28
+ actorPeerId?: string;
23
29
  };
24
30
 
25
31
  export function createOpenVikingSessionRoutingRuntime(options: {
32
+ peerRole?: OpenVikingPeerRole;
26
33
  peerPrefix: string;
27
34
  logFindRequests: boolean;
28
35
  logger: Logger;
29
36
  }) {
37
+ const peerRole = options.peerRole ?? "assistant";
30
38
  const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
31
39
 
32
40
  const rememberSessionAgentId = (ctx: SessionAgentLookup) => {
@@ -81,11 +89,17 @@ export function createOpenVikingSessionRoutingRuntime(options: {
81
89
  };
82
90
  rememberSessionAgentId(session);
83
91
 
92
+ const agentId = resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId);
84
93
  return {
85
94
  sessionId: session.sessionId,
86
95
  sessionKey: session.sessionKey,
87
96
  ovSessionId: session.ovSessionId,
88
- agentId: resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId),
97
+ agentId,
98
+ actorPeerId: resolveOpenVikingActorPeerId({
99
+ peerRole,
100
+ personPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
101
+ assistantPeerId: agentId,
102
+ }),
89
103
  };
90
104
  };
91
105