@openviking/openclaw-plugin 2026.6.18 → 2026.7.15

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 (61) hide show
  1. package/INSTALL-AGENT.md +2 -2
  2. package/INSTALL-ZH.md +2 -2
  3. package/INSTALL.md +2 -2
  4. package/README.md +1 -1
  5. package/auto-recall.ts +6 -6
  6. package/client.ts +14 -6
  7. package/commands/setup.ts +40 -10
  8. package/config.ts +44 -8
  9. package/context-engine.ts +2 -1
  10. package/dist/auto-recall.js +5 -6
  11. package/dist/client.js +11 -4
  12. package/dist/commands/setup.js +26 -10
  13. package/dist/config.js +31 -6
  14. package/dist/context-engine.js +2 -1
  15. package/dist/index.js +4 -4
  16. package/dist/plugin/openviking-archive-tools.js +1 -2
  17. package/dist/plugin/openviking-client-runtime.js +7 -1
  18. package/dist/plugin/openviking-command-definitions.js +3 -3
  19. package/dist/plugin/openviking-import-tools.js +2 -2
  20. package/dist/plugin/openviking-memory-recall-tools.js +4 -4
  21. package/dist/plugin/openviking-memory-tools.js +12 -7
  22. package/dist/plugin/openviking-query-runtime.js +1 -1
  23. package/dist/plugin/openviking-query-tools.js +4 -4
  24. package/dist/plugin/openviking-recall-trace-runtime.js +3 -3
  25. package/dist/plugin/openviking-session-routing-runtime.js +9 -2
  26. package/dist/plugin/openviking-tool-result-tools.js +3 -3
  27. package/dist/query-config.js +7 -1
  28. package/dist/request-headers.js +19 -0
  29. package/dist/routing/identity-routing.js +27 -0
  30. package/dist/routing/memory-uri.js +1 -0
  31. package/dist/services/context-lifecycle-service.js +56 -23
  32. package/dist/services/context-message-adapter.js +2 -9
  33. package/dist/text-utils.js +1 -6
  34. package/index.ts +3 -3
  35. package/install-manifest.json +2 -1
  36. package/openclaw.plugin.json +20 -4
  37. package/package.json +6 -6
  38. package/plugin/command-registration.ts +2 -0
  39. package/plugin/openviking-archive-tools.ts +1 -4
  40. package/plugin/openviking-client-runtime.ts +8 -1
  41. package/plugin/openviking-command-definitions.ts +4 -3
  42. package/plugin/openviking-import-tools.ts +4 -2
  43. package/plugin/openviking-memory-recall-tools.ts +6 -4
  44. package/plugin/openviking-memory-tools.ts +19 -11
  45. package/plugin/openviking-query-runtime.ts +1 -1
  46. package/plugin/openviking-query-tools.ts +6 -4
  47. package/plugin/openviking-recall-trace-runtime.ts +4 -3
  48. package/plugin/openviking-recall-trace-tools.ts +2 -0
  49. package/plugin/openviking-session-routing-runtime.ts +15 -1
  50. package/plugin/openviking-tool-result-tools.ts +0 -6
  51. package/query-config.ts +7 -1
  52. package/request-headers.ts +29 -0
  53. package/routing/identity-routing.ts +41 -0
  54. package/routing/memory-uri.ts +1 -0
  55. package/services/context-lifecycle-service.ts +69 -25
  56. package/services/context-message-adapter.ts +2 -9
  57. package/services/setup/probe-service.ts +35 -6
  58. package/services/setup/setup-flow.ts +61 -7
  59. package/skills/install-openviking-memory/SKILL.md +29 -15
  60. package/skills/openviking-context-database/SKILL.md +3 -3
  61. package/text-utils.ts +1 -6
@@ -4,6 +4,7 @@ import * as path from "node:path";
4
4
  import * as readline from "node:readline";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { getEnv } from "../runtime-utils.js";
7
+ import { cleanOpenVikingRequestHeaders, resolveOpenVikingRequestHeaders, } from "../request-headers.js";
7
8
  const HOME = os.homedir();
8
9
  const OPENCLAW_DIR = getEnv("OPENCLAW_STATE_DIR") || path.join(HOME, ".openclaw");
9
10
  const DEFAULT_REMOTE_URL = "http://127.0.0.1:1933";
@@ -55,6 +56,7 @@ const PLUGIN_VERSION = readPluginVersion();
55
56
  const { min: COMPATIBLE_SERVER_MIN, max: COMPATIBLE_SERVER_MAX } = readCompatRangeFromManifest();
56
57
  const CONFIG_KEYS_TO_PRESERVE = [
57
58
  "targetUri",
59
+ "headers",
58
60
  "timeoutMs",
59
61
  "autoCapture",
60
62
  "captureMode",
@@ -68,7 +70,7 @@ const CONFIG_KEYS_TO_PRESERVE = [
68
70
  "recallMaxContentChars",
69
71
  "recallPreferAbstract",
70
72
  "recallTokenBudget",
71
- "commitTokenThreshold",
73
+ "commitTokenThresholdRatio",
72
74
  "commitKeepRecentCount",
73
75
  "bypassSessionPatterns",
74
76
  "emitStandardDiagnostics",
@@ -84,6 +86,10 @@ function maskKey(key) {
84
86
  return "****";
85
87
  return `${key.slice(0, 4)}...${key.slice(-4)}`;
86
88
  }
89
+ function nonEmptyOpenVikingRequestHeaders(value) {
90
+ const headers = cleanOpenVikingRequestHeaders(value);
91
+ return Object.keys(headers).length > 0 ? headers : undefined;
92
+ }
87
93
  function isValidPeerPrefixInput(value) {
88
94
  const trimmed = value.trim();
89
95
  return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
@@ -234,14 +240,17 @@ function formatCompatRange() {
234
240
  return `<= ${COMPATIBLE_SERVER_MAX}`;
235
241
  return "any";
236
242
  }
237
- async function probeApiKeyType(baseUrl, apiKey) {
243
+ async function probeApiKeyType(baseUrl, apiKey, configuredHeaders) {
238
244
  if (!apiKey)
239
245
  return { keyType: "no_key", needsAccountId: false, needsUserId: false, detail: "No API key configured" };
240
246
  const controller = new AbortController();
241
247
  const timeoutId = setTimeout(() => controller.abort(), 10_000);
242
248
  const sessionsUrl = `${baseUrl.replace(/\/+$/, "")}/api/v1/sessions?limit=1`;
243
249
  try {
244
- const headers = { "X-API-Key": apiKey };
250
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
251
+ const headers = {};
252
+ headers["X-API-Key"] = apiKey;
253
+ Object.assign(headers, configuredRequestHeaders);
245
254
  const response = await fetch(sessionsUrl, {
246
255
  headers,
247
256
  signal: controller.signal,
@@ -278,6 +287,7 @@ async function probeApiKeyType(baseUrl, apiKey) {
278
287
  "X-API-Key": apiKey,
279
288
  "X-OpenViking-Account": "__probe__",
280
289
  "X-OpenViking-User": "__probe__",
290
+ ...configuredRequestHeaders,
281
291
  };
282
292
  const probe2 = await fetch(sessionsUrl, {
283
293
  headers: probeHeaders,
@@ -307,13 +317,16 @@ async function probeApiKeyType(baseUrl, apiKey) {
307
317
  clearTimeout(timeoutId);
308
318
  }
309
319
  }
310
- async function checkServiceHealth(baseUrl, apiKey) {
320
+ async function checkServiceHealth(baseUrl, apiKey, configuredHeaders) {
311
321
  const controller = new AbortController();
312
322
  const timeoutId = setTimeout(() => controller.abort(), 10_000);
313
323
  try {
324
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
314
325
  const headers = {};
315
- if (apiKey)
326
+ if (apiKey) {
316
327
  headers["X-API-Key"] = apiKey;
328
+ }
329
+ Object.assign(headers, configuredRequestHeaders);
317
330
  const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/health`, {
318
331
  headers,
319
332
  signal: controller.signal,
@@ -602,8 +615,9 @@ function printCompatibilityWarning(zh, health) {
602
615
  async function runRemoteCheck(zh, existing) {
603
616
  const baseUrl = String(existing.baseUrl ?? DEFAULT_REMOTE_URL);
604
617
  const apiKey = existing.apiKey ? String(existing.apiKey) : undefined;
618
+ const headers = nonEmptyOpenVikingRequestHeaders(existing.headers);
605
619
  console.log(tr(zh, `Testing connectivity to ${baseUrl}...`, `正在测试连接 ${baseUrl}...`));
606
- const health = await checkServiceHealth(baseUrl, apiKey);
620
+ const health = await checkServiceHealth(baseUrl, apiKey, headers);
607
621
  if (health.ok) {
608
622
  const ver = health.version ? ` (version: ${health.version})` : "";
609
623
  console.log(` ✓ ${tr(zh, `Connected successfully${ver}`, `连接成功${ver}`)}`);
@@ -771,8 +785,9 @@ async function getStatus(configPath) {
771
785
  }
772
786
  const baseUrl = String(existing.baseUrl ?? DEFAULT_REMOTE_URL);
773
787
  const apiKey = existing.apiKey ? String(existing.apiKey) : undefined;
774
- const health = await checkServiceHealth(baseUrl, apiKey);
775
- const keyProbe = health.ok ? await probeApiKeyType(baseUrl, apiKey) : undefined;
788
+ const headers = nonEmptyOpenVikingRequestHeaders(existing.headers);
789
+ const health = await checkServiceHealth(baseUrl, apiKey, headers);
790
+ const keyProbe = health.ok ? await probeApiKeyType(baseUrl, apiKey, headers) : undefined;
776
791
  const peerPrefix = resolveExistingPeerPrefix(existing);
777
792
  return {
778
793
  configured: true,
@@ -858,13 +873,14 @@ async function setupRemote(zh, configPath, existing, q) {
858
873
  const defaultApiKey = existing?.apiKey ? String(existing.apiKey) : "";
859
874
  const defaultPeerRole = resolveExistingPeerRole(existing);
860
875
  const defaultPeerPrefix = resolveExistingPeerPrefix(existing);
876
+ const headers = nonEmptyOpenVikingRequestHeaders(existing?.headers);
861
877
  const baseUrl = await q(tr(zh, "OpenViking server URL", "OpenViking 服务器地址"), defaultUrl);
862
878
  const apiKey = await q(tr(zh, "API Key (optional)", "API Key(可选)"), defaultApiKey);
863
879
  let accountId = existing?.accountId ? String(existing.accountId) : "";
864
880
  let userId = existing?.userId ? String(existing.userId) : "";
865
881
  if (apiKey) {
866
882
  console.log(tr(zh, " Detecting API key type...", " 正在检测 API Key 类型..."));
867
- const probe = await probeApiKeyType(baseUrl, apiKey);
883
+ const probe = await probeApiKeyType(baseUrl, apiKey, headers);
868
884
  if (probe.keyType === "root_key") {
869
885
  console.log(tr(zh, " ⚠ Root API key detected. accountId and userId are required.", " ⚠ 检测到 Root API Key,需要提供 accountId 和 userId。"));
870
886
  accountId = await q(tr(zh, "Account ID (required for root key)", "Account ID(root key 必填)"), accountId);
@@ -880,7 +896,7 @@ async function setupRemote(zh, configPath, existing, q) {
880
896
  : "";
881
897
  console.log("");
882
898
  console.log(tr(zh, `Testing connectivity to ${baseUrl}...`, `正在测试连接 ${baseUrl}...`));
883
- const health = await checkServiceHealth(baseUrl, apiKey || undefined);
899
+ const health = await checkServiceHealth(baseUrl, apiKey || undefined, headers);
884
900
  if (health.ok) {
885
901
  const ver = health.version ? ` (version: ${health.version})` : "";
886
902
  console.log(` ✓ ${tr(zh, `Connected successfully${ver}`, `连接成功${ver}`)}`);
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@ const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
11
11
  const DEFAULT_RECALL_MAX_CONTENT_CHARS = 5000;
12
12
  const DEFAULT_RECALL_PREFER_ABSTRACT = false;
13
13
  const DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000;
14
- const DEFAULT_COMMIT_TOKEN_THRESHOLD = 20000;
14
+ const DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO = 0.5;
15
15
  const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
16
16
  const DEFAULT_BYPASS_SESSION_PATTERNS = [];
17
17
  const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
@@ -168,6 +168,23 @@ function toRecord(value) {
168
168
  ? value
169
169
  : {};
170
170
  }
171
+ function toStringRecord(value, label) {
172
+ if (value === undefined || value === null) {
173
+ return {};
174
+ }
175
+ if (typeof value !== "object" || Array.isArray(value)) {
176
+ throw new Error(`${label} must be an object`);
177
+ }
178
+ const record = value;
179
+ const out = {};
180
+ for (const [key, val] of Object.entries(record)) {
181
+ if (typeof val !== "string") {
182
+ throw new Error(`${label}.${key} must be a string`);
183
+ }
184
+ out[key] = val;
185
+ }
186
+ return out;
187
+ }
171
188
  function expandToolSelectors(value, fallback, label) {
172
189
  const entries = toStringArray(value, fallback);
173
190
  const seen = new Set();
@@ -250,6 +267,7 @@ export const memoryOpenVikingConfigSchema = {
250
267
  "peer_prefix",
251
268
  "serverAuthMode",
252
269
  "apiKey",
270
+ "headers",
253
271
  "accountId",
254
272
  "userId",
255
273
  "targetUri",
@@ -267,6 +285,7 @@ export const memoryOpenVikingConfigSchema = {
267
285
  "recallPreferAbstract",
268
286
  "recallTokenBudget",
269
287
  "commitTokenThreshold",
288
+ "commitTokenThresholdRatio",
270
289
  "commitKeepRecentCount",
271
290
  "bypassSessionPatterns",
272
291
  "ingestReplyAssist",
@@ -324,6 +343,7 @@ export const memoryOpenVikingConfigSchema = {
324
343
  peer_role: peerRole,
325
344
  peer_prefix: peerPrefix,
326
345
  apiKey: rawApiKey ? resolveEnvVars(rawApiKey) : "",
346
+ headers: toStringRecord(cfg.headers, "openviking config headers"),
327
347
  accountId,
328
348
  userId,
329
349
  targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
@@ -342,7 +362,7 @@ export const memoryOpenVikingConfigSchema = {
342
362
  : DEFAULT_RECALL_PREFER_ABSTRACT,
343
363
  recallMaxInjectedChars,
344
364
  recallTokenBudget: recallMaxInjectedChars,
345
- commitTokenThreshold: Math.max(0, Math.min(100_000, Math.floor(toNumber(cfg.commitTokenThreshold, DEFAULT_COMMIT_TOKEN_THRESHOLD)))),
365
+ commitTokenThresholdRatio: Math.max(0, Math.min(1, toNumber(cfg.commitTokenThresholdRatio, DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO))),
346
366
  commitKeepRecentCount: Math.max(0, Math.min(1_000, Math.floor(toNumber(cfg.commitKeepRecentCount, DEFAULT_COMMIT_KEEP_RECENT_COUNT)))),
347
367
  bypassSessionPatterns: toStringArray(cfg.bypassSessionPatterns, toStringArray(cfg.ingestReplyAssistIgnoreSessionPatterns, DEFAULT_BYPASS_SESSION_PATTERNS)),
348
368
  emitStandardDiagnostics: typeof cfg.emitStandardDiagnostics === "boolean"
@@ -405,6 +425,11 @@ export const memoryOpenVikingConfigSchema = {
405
425
  placeholder: "${OPENVIKING_API_KEY}",
406
426
  help: "Optional API key for OpenViking server",
407
427
  },
428
+ headers: {
429
+ label: "Headers",
430
+ advanced: true,
431
+ help: "Optional HTTP headers merged into every OpenViking request.",
432
+ },
408
433
  accountId: {
409
434
  label: "Account ID",
410
435
  placeholder: "(derived from API key)",
@@ -503,11 +528,11 @@ export const memoryOpenVikingConfigSchema = {
503
528
  help: "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
504
529
  advanced: true,
505
530
  },
506
- commitTokenThreshold: {
507
- label: "Commit Token Threshold",
508
- placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD),
531
+ commitTokenThresholdRatio: {
532
+ label: "Commit Token Threshold Ratio",
533
+ placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO),
509
534
  advanced: true,
510
- help: "Minimum estimated pending tokens before auto-commit triggers. Set to 0 to commit every turn.",
535
+ help: "Auto-commit triggers once estimated pending tokens reach this fraction (0-1) of the model context window (e.g. 0.5 = 50%). Set to 0 to commit every turn.",
511
536
  },
512
537
  commitKeepRecentCount: {
513
538
  label: "Commit Keep Recent Count",
@@ -135,7 +135,6 @@ export function createMemoryOpenVikingContextEngine(params) {
135
135
  getClient,
136
136
  logger,
137
137
  rememberSessionAgentId,
138
- resolveAgentId,
139
138
  isBypassedSession,
140
139
  });
141
140
  }
@@ -204,12 +203,14 @@ export function createMemoryOpenVikingContextEngine(params) {
204
203
  });
205
204
  },
206
205
  async afterTurn(afterTurnParams) {
206
+ const tokenBudget = validTokenBudget(afterTurnParams.tokenBudget) ?? 128_000;
207
207
  await afterTurnOpenVikingSession({
208
208
  sessionId: afterTurnParams.sessionId,
209
209
  sessionKey: resolveSessionKey(afterTurnParams),
210
210
  messages: afterTurnParams.messages,
211
211
  prePromptMessageCount: afterTurnParams.prePromptMessageCount,
212
212
  isHeartbeat: afterTurnParams.isHeartbeat,
213
+ tokenBudget,
213
214
  runtimeContext: afterTurnParams.runtimeContext,
214
215
  cfg,
215
216
  getClient,
package/dist/index.js CHANGED
@@ -25,8 +25,8 @@ import { registerOpenVikingRecallTraceTools } from "./plugin/openviking-recall-t
25
25
  import { createOpenVikingRuntimeState } from "./plugin/openviking-runtime-state.js";
26
26
  import { createOpenVikingSessionRoutingRuntime } from "./plugin/openviking-session-routing-runtime.js";
27
27
  import { registerOpenVikingToolResultTools } from "./plugin/openviking-tool-result-tools.js";
28
- import { boundTraceQuery, createMemoryStoreTempSessionId, createTraceId, extractToolSenderId, inferRecallResourceType, makeBypassedToolResult, previewText, } from "./plugin/openviking-runtime-utils.js";
29
- import { formatMessageFaithful, toRoleId } from "./services/context-message-adapter.js";
28
+ import { boundTraceQuery, createMemoryStoreTempSessionId, createTraceId, inferRecallResourceType, makeBypassedToolResult, previewText, } from "./plugin/openviking-runtime-utils.js";
29
+ import { formatMessageFaithful } from "./services/context-message-adapter.js";
30
30
  import { clampScore, postProcessMemories, pickMemoriesForInjection, } from "./memory-ranking.js";
31
31
  import { createMemoryOpenVikingContextEngine, } from "./context-engine.js";
32
32
  import { openClawSessionRefToOvStorageId, openClawSessionToOvStorageId, } from "./routing/identity-routing.js";
@@ -80,6 +80,7 @@ const contextEnginePlugin = {
80
80
  logger: api.logger,
81
81
  });
82
82
  const { rememberSessionAgentId, resolveAgentId, resolvePluginSessionRouting, toQueryConfigContext, } = createOpenVikingSessionRoutingRuntime({
83
+ peerRole: cfg.peer_role,
83
84
  peerPrefix: cfg.peer_prefix,
84
85
  logFindRequests: cfg.logFindRequests,
85
86
  logger: api.logger,
@@ -182,8 +183,7 @@ const contextEnginePlugin = {
182
183
  getClient,
183
184
  normalizeSessionId: openClawSessionRefToOvStorageId,
184
185
  createTempSessionId: createMemoryStoreTempSessionId,
185
- extractSenderId: extractToolSenderId,
186
- toRoleId,
186
+ peerRole: cfg.peer_role,
187
187
  resolvePluginSessionRouting,
188
188
  isBypassedSession,
189
189
  makeBypassedToolResult,
@@ -55,7 +55,6 @@ export function registerOpenVikingArchiveTools(deps) {
55
55
  const result = await client.grepSessionArchives(ovSessionId, escapedQuery, {
56
56
  archiveId,
57
57
  caseInsensitive: true,
58
- agentId,
59
58
  });
60
59
  const traceResults = (result.matches ?? []).slice(0, deps.traceRecallMaxResultsPerSearch).map((match) => ({
61
60
  uri: match.uri,
@@ -175,7 +174,7 @@ export function registerOpenVikingArchiveTools(deps) {
175
174
  }
176
175
  try {
177
176
  const client = await deps.getClient();
178
- const detail = await client.getSessionArchive(session.ovSessionId, archiveId, session.agentId);
177
+ const detail = await client.getSessionArchive(session.ovSessionId, archiveId);
179
178
  const header = [
180
179
  `## ${detail.archive_id}`,
181
180
  detail.abstract ? `**Summary**: ${detail.abstract}` : "",
@@ -1,4 +1,5 @@
1
1
  import { OpenVikingClient } from "../client.js";
2
+ import { resolveOpenVikingRequestHeaders } from "../request-headers.js";
2
3
  export function createOpenVikingClientRuntime(options) {
3
4
  const { cfg, logger } = options;
4
5
  if (cfg.logFindRequests) {
@@ -19,7 +20,12 @@ export function createOpenVikingClientRuntime(options) {
19
20
  logger.info(msg);
20
21
  }
21
22
  : undefined;
22
- const clientPromise = Promise.resolve(new OpenVikingClient(cfg.baseUrl, cfg.apiKey, cfg.peer_prefix, cfg.timeoutMs, cfg.accountId, cfg.userId, routingDebugLog, { transport: options.transport }));
23
+ const clientPromise = Promise.resolve(new OpenVikingClient(cfg.baseUrl, cfg.apiKey, cfg.peer_prefix, cfg.timeoutMs, cfg.accountId, cfg.userId, routingDebugLog, {
24
+ transport: options.transport,
25
+ headers: resolveOpenVikingRequestHeaders({
26
+ headers: cfg.headers,
27
+ }),
28
+ }));
23
29
  const getClient = () => clientPromise;
24
30
  return {
25
31
  getClient,
@@ -124,7 +124,7 @@ export function createOpenVikingCommandDefinitions(deps) {
124
124
  }
125
125
  const session = deps.resolvePluginSessionRouting(ctx);
126
126
  const input = deps.parseAddResourceCommandArgs(ctx.args ?? "");
127
- return toCommandResult(await deps.addResourceOpenViking(input, session.agentId));
127
+ return toCommandResult(await deps.addResourceOpenViking(input, session.actorPeerId));
128
128
  }
129
129
  catch (err) {
130
130
  return { text: `OpenViking add resource failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -142,7 +142,7 @@ export function createOpenVikingCommandDefinitions(deps) {
142
142
  }
143
143
  const session = deps.resolvePluginSessionRouting(ctx);
144
144
  const input = deps.parseAddSkillCommandArgs(ctx.args ?? "");
145
- return toCommandResult(await deps.addSkillOpenViking(input, session.agentId));
145
+ return toCommandResult(await deps.addSkillOpenViking(input, session.actorPeerId));
146
146
  }
147
147
  catch (err) {
148
148
  return { text: `OpenViking add skill failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -160,7 +160,7 @@ export function createOpenVikingCommandDefinitions(deps) {
160
160
  }
161
161
  const session = deps.resolvePluginSessionRouting(ctx);
162
162
  const input = deps.parseOVSearchCommandArgs(ctx.args ?? "");
163
- return toCommandResult(await deps.searchOpenViking(input, session.agentId, session));
163
+ return toCommandResult(await deps.searchOpenViking(input, session.actorPeerId, session));
164
164
  }
165
165
  catch (err) {
166
166
  return { text: `OpenViking search failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -40,7 +40,7 @@ export function registerOpenVikingImportTools(deps) {
40
40
  instruction: typeof params.instruction === "string" ? params.instruction : undefined,
41
41
  wait: typeof params.wait === "boolean" ? params.wait : undefined,
42
42
  timeout: typeof params.timeout === "number" ? params.timeout : undefined,
43
- }, session.agentId);
43
+ }, session.actorPeerId);
44
44
  return {
45
45
  content: [{ type: "text", text: formatResourceImportText(result) }],
46
46
  details: {
@@ -73,7 +73,7 @@ export function registerOpenVikingImportTools(deps) {
73
73
  data: params.data,
74
74
  wait: typeof params.wait === "boolean" ? params.wait : undefined,
75
75
  timeout: typeof params.timeout === "number" ? params.timeout : undefined,
76
- }, session.agentId);
76
+ }, session.actorPeerId);
77
77
  return {
78
78
  content: [{ type: "text", text: formatSkillImportText(result) }],
79
79
  details: {
@@ -47,7 +47,7 @@ export function registerOpenVikingMemoryRecallTools(deps) {
47
47
  const requestLimit = queryConfig.candidateLimit;
48
48
  const recallClient = await deps.getClient();
49
49
  if (deps.cfg.logFindRequests) {
50
- deps.logger.info?.(`openviking: memory_recall X-OpenViking-Actor-Peer="${session.agentId}" ` +
50
+ deps.logger.info?.(`openviking: memory_recall X-OpenViking-Actor-Peer="${session.actorPeerId ?? "none"}" ` +
51
51
  `(plugin defaultAgentId="${recallClient.getDefaultAgentId()}" is unused when session context is present)`);
52
52
  }
53
53
  let result;
@@ -57,7 +57,7 @@ export function registerOpenVikingMemoryRecallTools(deps) {
57
57
  targetUri,
58
58
  limit: requestLimit,
59
59
  scoreThreshold: 0,
60
- actorPeerId: session.agentId,
60
+ actorPeerId: session.actorPeerId,
61
61
  });
62
62
  const traceResults = [
63
63
  ...(result.memories ?? []).map((item) => toTraceResult(item, "memory", deps)),
@@ -84,7 +84,7 @@ export function registerOpenVikingMemoryRecallTools(deps) {
84
84
  limit: requestLimit,
85
85
  scoreThreshold: 0,
86
86
  contextType: search.contextType,
87
- actorPeerId: session.agentId,
87
+ actorPeerId: session.actorPeerId,
88
88
  })));
89
89
  const allMemories = [];
90
90
  for (let index = 0; index < settled.length; index += 1) {
@@ -188,7 +188,7 @@ export function registerOpenVikingMemoryRecallTools(deps) {
188
188
  details: { count: 0, total: result.total ?? 0, scoreThreshold },
189
189
  };
190
190
  }
191
- const { lines: memoryLines } = await deps.buildMemoryLinesWithBudget(memories, (uri) => recallClient.read(uri, session.agentId), {
191
+ const { lines: memoryLines } = await deps.buildMemoryLinesWithBudget(memories, (uri) => recallClient.read(uri, session.actorPeerId), {
192
192
  recallPreferAbstract: false,
193
193
  recallMaxInjectedChars: queryConfig.maxInjectedChars,
194
194
  });
@@ -1,5 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { clampScore, postProcessMemories } from "../memory-ranking.js";
3
+ import { resolveOpenVikingMessagePeerId, } from "../routing/identity-routing.js";
3
4
  import { isMemoryUri } from "../routing/memory-uri.js";
4
5
  function totalCommitMemories(r) {
5
6
  const m = r.memories_extracted;
@@ -11,7 +12,7 @@ export function registerOpenVikingMemoryTools(deps) {
11
12
  deps.registerTool((ctx) => ({
12
13
  name: "memory_store",
13
14
  label: "Memory Store (OpenViking)",
14
- description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction.",
15
+ description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction. Use when the user explicitly asks to remember, save, or store an important long-term fact, preference, project, or decision; automatic capture is threshold/commit dependent.",
15
16
  parameters: Type.Object({
16
17
  text: Type.String({ description: "Information to store as memory source text" }),
17
18
  role: Type.Optional(Type.String({ description: "Session role, default user" })),
@@ -41,11 +42,15 @@ export function registerOpenVikingMemoryTools(deps) {
41
42
  sessionId = deps.createTempSessionId();
42
43
  usedTempSession = true;
43
44
  }
44
- const roleId = role === "user" ? deps.toRoleId(deps.extractSenderId(ctx)) : undefined;
45
- await client.addSessionMessage(sessionId, role, [{ type: "text", text }], session.agentId, undefined, roleId);
45
+ const peerId = resolveOpenVikingMessagePeerId({
46
+ peerRole: deps.peerRole,
47
+ role,
48
+ personPeerId: session.actorPeerId,
49
+ assistantPeerId: session.actorPeerId,
50
+ });
51
+ await client.addSessionMessage(sessionId, role, [{ type: "text", text }], undefined, undefined, peerId);
46
52
  const commitResult = await client.commitSession(sessionId, {
47
53
  wait: true,
48
- agentId: session.agentId,
49
54
  keepRecentCount: 0,
50
55
  });
51
56
  const memoriesCount = totalCommitMemories(commitResult);
@@ -130,7 +135,7 @@ export function registerOpenVikingMemoryTools(deps) {
130
135
  details: { action: "rejected", uri },
131
136
  };
132
137
  }
133
- await client.deleteUri(uri, session.agentId);
138
+ await client.deleteUri(uri, session.actorPeerId);
134
139
  return {
135
140
  content: [{ type: "text", text: `Forgotten: ${uri}` }],
136
141
  details: { action: "deleted", uri },
@@ -157,7 +162,7 @@ export function registerOpenVikingMemoryTools(deps) {
157
162
  targetUri,
158
163
  limit: requestLimit,
159
164
  scoreThreshold: 0,
160
- }, session.agentId);
165
+ }, session.actorPeerId);
161
166
  const candidates = postProcessMemories(result.memories ?? [], {
162
167
  limit: requestLimit,
163
168
  scoreThreshold,
@@ -176,7 +181,7 @@ export function registerOpenVikingMemoryTools(deps) {
176
181
  }
177
182
  const top = candidates[0];
178
183
  if (candidates.length === 1 && clampScore(top.score) >= 0.85) {
179
- await client.deleteUri(top.uri, session.agentId);
184
+ await client.deleteUri(top.uri, session.actorPeerId);
180
185
  return {
181
186
  content: [{ type: "text", text: `Forgotten: ${top.uri}` }],
182
187
  details: { action: "deleted", uri: top.uri, score: top.score ?? 0 },
@@ -337,7 +337,7 @@ export function createOpenVikingQueryRuntime(deps) {
337
337
  sessionId: traceCtx?.sessionId,
338
338
  sessionKey: traceCtx?.sessionKey,
339
339
  ovSessionId: traceCtx?.ovSessionId,
340
- agentId,
340
+ agentId: traceCtx?.agentId,
341
341
  source: "ov_search",
342
342
  operationType: "semantic_find",
343
343
  resourceTypes: [...new Set(searches.map((search) => search.resourceType).filter((resourceType) => resourceType !== "archive"))],
@@ -21,7 +21,7 @@ export function registerOpenVikingQueryTools(deps) {
21
21
  query: String(params.query ?? ""),
22
22
  uri: typeof params.uri === "string" ? params.uri : undefined,
23
23
  limit: typeof params.limit === "number" ? params.limit : undefined,
24
- }, session.agentId, session);
24
+ }, session.actorPeerId, session);
25
25
  },
26
26
  }), { name: "ov_search" });
27
27
  deps.registerTool((ctx) => ({
@@ -41,7 +41,7 @@ export function registerOpenVikingQueryTools(deps) {
41
41
  const session = deps.resolvePluginSessionRouting(ctx);
42
42
  return deps.readOpenVikingContent({
43
43
  uri: String(params.uri ?? ""),
44
- }, session.agentId);
44
+ }, session.actorPeerId);
45
45
  },
46
46
  }), { name: "ov_read" });
47
47
  deps.registerTool((ctx) => ({
@@ -62,7 +62,7 @@ export function registerOpenVikingQueryTools(deps) {
62
62
  const uris = Array.isArray(params.uris)
63
63
  ? params.uris.map((uri) => String(uri))
64
64
  : [];
65
- return deps.multiReadOpenVikingContent({ uris }, session.agentId);
65
+ return deps.multiReadOpenVikingContent({ uris }, session.actorPeerId);
66
66
  },
67
67
  }), { name: "ov_multi_read" });
68
68
  deps.registerTool((ctx) => ({
@@ -85,7 +85,7 @@ export function registerOpenVikingQueryTools(deps) {
85
85
  recursive: typeof params.recursive === "boolean" ? params.recursive : undefined,
86
86
  simple: typeof params.simple === "boolean" ? params.simple : undefined,
87
87
  limit: typeof params.limit === "number" ? params.limit : undefined,
88
- }, session.agentId);
88
+ }, session.actorPeerId);
89
89
  },
90
90
  }), { name: "ov_list" });
91
91
  }
@@ -157,11 +157,11 @@ export function createOpenVikingRecallTraceRuntime(deps) {
157
157
  for (const fallbackQuery of fallbackIdentities) {
158
158
  const fallback = await deps.traceRecorder.queryWithFallback(fallbackQuery);
159
159
  if (fallback.entries.length > 0) {
160
- return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.agentId);
160
+ return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.actorPeerId);
161
161
  }
162
162
  }
163
163
  }
164
- return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.agentId);
164
+ return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.actorPeerId);
165
165
  };
166
166
  const handleUriDetail = async (request) => {
167
167
  const query = toQueryObject(request);
@@ -206,7 +206,7 @@ export function createOpenVikingRecallTraceRuntime(deps) {
206
206
  }
207
207
  try {
208
208
  const client = await deps.getClient();
209
- const content = await client.read(uri, toString(query.agentId) ?? session.agentId);
209
+ const content = await client.read(uri, session.actorPeerId);
210
210
  const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
211
211
  const chars = Array.from(text);
212
212
  const slice = chars.slice(offset, offset + contentLimit).join("");
@@ -1,5 +1,6 @@
1
- import { createSessionAgentResolver, openClawSessionToOvStorageId, } from "../routing/identity-routing.js";
1
+ import { createSessionAgentResolver, openClawSessionToOvStorageId, resolveOpenVikingActorPeerId, sanitizeOpenVikingPeerId, } from "../routing/identity-routing.js";
2
2
  export function createOpenVikingSessionRoutingRuntime(options) {
3
+ const peerRole = options.peerRole ?? "assistant";
3
4
  const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
4
5
  const rememberSessionAgentId = (ctx) => {
5
6
  sessionAgentResolver.remember(ctx);
@@ -39,11 +40,17 @@ export function createOpenVikingSessionRoutingRuntime(options) {
39
40
  ovSessionId: ovSessionId || undefined,
40
41
  };
41
42
  rememberSessionAgentId(session);
43
+ const agentId = resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId);
42
44
  return {
43
45
  sessionId: session.sessionId,
44
46
  sessionKey: session.sessionKey,
45
47
  ovSessionId: session.ovSessionId,
46
- agentId: resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId),
48
+ agentId,
49
+ actorPeerId: resolveOpenVikingActorPeerId({
50
+ peerRole,
51
+ personPeerId: sanitizeOpenVikingPeerId(ctx?.requesterSenderId ?? ctx?.senderId),
52
+ assistantPeerId: agentId,
53
+ }),
47
54
  };
48
55
  };
49
56
  const toQueryConfigContext = (session) => ({
@@ -83,7 +83,7 @@ export function registerOpenVikingToolResultTools({ registerTool, getClient, res
83
83
  }
84
84
  try {
85
85
  const client = await getClient();
86
- const result = await client.readToolResult(session.ovSessionId, parsed.toolResultId, { offset, limit, includeMetadata: true }, session.agentId);
86
+ const result = await client.readToolResult(session.ovSessionId, parsed.toolResultId, { offset, limit, includeMetadata: true });
87
87
  const returnedChars = result.content.length;
88
88
  const nextOffset = result.offset + returnedChars;
89
89
  const text = result.content || "(empty tool result chunk)";
@@ -167,7 +167,7 @@ export function registerOpenVikingToolResultTools({ registerTool, getClient, res
167
167
  const contextChars = Math.max(0, getOptionalInteger(params.context_chars ?? params.contextChars, 300));
168
168
  try {
169
169
  const client = await getClient();
170
- const result = await client.searchToolResult(session.ovSessionId, parsed.toolResultId, query, { limit, contextChars }, session.agentId);
170
+ const result = await client.searchToolResult(session.ovSessionId, parsed.toolResultId, query, { limit, contextChars });
171
171
  const matches = result.matches ?? [];
172
172
  const text = matches.length
173
173
  ? [
@@ -227,7 +227,7 @@ export function registerOpenVikingToolResultTools({ registerTool, getClient, res
227
227
  const limit = getPositiveInteger(params.limit, 50);
228
228
  try {
229
229
  const client = await getClient();
230
- const result = await client.listToolResults(session.ovSessionId, { toolName, limit }, session.agentId);
230
+ const result = await client.listToolResults(session.ovSessionId, { toolName, limit });
231
231
  const items = result.tool_results ?? [];
232
232
  const text = items.length
233
233
  ? [
@@ -171,7 +171,13 @@ export class RuntimeQueryConfigStore {
171
171
  const { params, warnings } = normalizeRuntimeQueryParams(patch);
172
172
  const key = this.keyForScope(scope, ctx);
173
173
  const bucket = scope === "claw" ? this.data.claws : this.data.sessions;
174
- bucket[key] = { params, updatedAt: Date.now(), updatedBy: "command", peerId: ctx.peerId };
174
+ const previous = bucket[key];
175
+ bucket[key] = {
176
+ params: { ...(previous?.params ?? {}), ...params },
177
+ updatedAt: Date.now(),
178
+ updatedBy: "command",
179
+ peerId: ctx.peerId,
180
+ };
175
181
  this.data.updatedAt = Date.now();
176
182
  await this.persist();
177
183
  return { warnings };
@@ -0,0 +1,19 @@
1
+ export function resolveOpenVikingRequestHeaders(options = {}) {
2
+ return cleanOpenVikingRequestHeaders(options.headers);
3
+ }
4
+ export function cleanOpenVikingRequestHeaders(headers) {
5
+ if (headers === undefined || headers === null) {
6
+ return {};
7
+ }
8
+ if (typeof headers !== "object" || Array.isArray(headers)) {
9
+ throw new Error("openviking request headers must be an object");
10
+ }
11
+ const out = {};
12
+ for (const [key, value] of Object.entries(headers)) {
13
+ if (typeof value !== "string") {
14
+ throw new Error(`openviking request header ${key} must be a string`);
15
+ }
16
+ out[key] = value;
17
+ }
18
+ return out;
19
+ }