@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
@@ -3,6 +3,33 @@ const DEFAULT_OPENCLAW_AGENT_ID = "main";
3
3
  /** OpenClaw session UUID (path-safe on Windows). */
4
4
  const OPENVIKING_OV_SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
5
  const WINDOWS_BAD_SESSION_SEGMENT = /[:<>"\\/|?\u0000-\u001f]/;
6
+ export function sanitizeOpenVikingPeerId(raw) {
7
+ const normalized = raw
8
+ ?.trim()
9
+ .replace(/[^a-zA-Z0-9_-]+/g, "_")
10
+ .replace(/^_+|_+$/g, "")
11
+ .replace(/_+/g, "_");
12
+ return normalized || undefined;
13
+ }
14
+ export function resolveOpenVikingMessagePeerId(params) {
15
+ if (params.peerRole === "person" && params.role === "user") {
16
+ return params.personPeerId;
17
+ }
18
+ if (params.peerRole === "assistant" && params.role === "assistant") {
19
+ return params.assistantPeerId;
20
+ }
21
+ return undefined;
22
+ }
23
+ export function resolveOpenVikingActorPeerId(params) {
24
+ const actorPeerId = resolveOpenVikingMessagePeerId({
25
+ ...params,
26
+ role: params.peerRole === "person" ? "user" : "assistant",
27
+ });
28
+ if (params.peerRole === "person" && !actorPeerId) {
29
+ throw new Error("openviking: peer_role=person requires a sender identity");
30
+ }
31
+ return actorPeerId;
32
+ }
6
33
  /**
7
34
  * Map OpenClaw session identity to an OpenViking session_id that is safe as a single
8
35
  * AGFS path segment on Windows (no `:` etc.). Prefer UUID sessionId when present;
@@ -1,5 +1,6 @@
1
1
  const MEMORY_URI_PATTERNS = [
2
2
  /^viking:\/\/user\/(?:[^/]+(?:\/agent\/[^/]+)?\/)?memories(?:\/|$)/,
3
+ /^viking:\/\/user\/[^/]+\/peers\/[^/]+\/memories(?:\/|$)/,
3
4
  /^viking:\/\/agent\/(?:[^/]+(?:\/user\/[^/]+)?\/)?memories(?:\/|$)/,
4
5
  ];
5
6
  export function isMemoryUri(uri) {
@@ -2,7 +2,7 @@
2
2
  import { DEFAULT_PHASE2_POLL_TIMEOUT_MS } from "../client.js";
3
3
  import { buildAutoRecallContext, prepareRecallQuery } from "../auto-recall.js";
4
4
  import { toJsonLog } from "../memory-ranking.js";
5
- import { openClawSessionToOvStorageId } from "../routing/identity-routing.js";
5
+ import { openClawSessionToOvStorageId, resolveOpenVikingActorPeerId, resolveOpenVikingMessagePeerId, sanitizeOpenVikingPeerId, } from "../routing/identity-routing.js";
6
6
  import { extractNewTurnMessages } from "../text-utils.js";
7
7
  import { estimateAgentMessageTokens, estimateTextTokens } from "../token-estimator.js";
8
8
  import { convertToAgentMessages, mergeConsecutiveAssistants, sanitizeAgentMessagesForProvider, toRoleId, } from "./context-message-adapter.js";
@@ -26,12 +26,12 @@ function sleep(ms) {
26
26
  * After wait=false commit, Phase2 runs on the server. Poll task until completed/failed/timeout
27
27
  * so logs show memories_extracted (otherwise it looks like "nothing was saved").
28
28
  */
29
- async function pollPhase2ExtractionOutcome(client, taskId, agentId, logger, sessionLabel) {
29
+ async function pollPhase2ExtractionOutcome(client, taskId, logger, sessionLabel) {
30
30
  const deadline = Date.now() + PHASE2_POLL_MAX_MS;
31
31
  try {
32
32
  while (Date.now() < deadline) {
33
33
  await sleep(PHASE2_POLL_INTERVAL_MS);
34
- const task = await client.getTask(taskId, agentId).catch((e) => {
34
+ const task = await client.getTask(taskId).catch((e) => {
35
35
  logger.warn?.(`openviking: phase2 getTask failed task_id=${taskId}: ${String(e)}`);
36
36
  return null;
37
37
  });
@@ -141,7 +141,7 @@ function buildAssembledContext(overview, preAbstracts, ovMessages, tokenBudget,
141
141
  const sanitized = sanitizeAgentMessagesForProvider(assembled);
142
142
  return { sanitized, archive, session, budgets, instruction };
143
143
  }
144
- export async function commitOpenVikingSession({ sessionId, sessionKey, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, }) {
144
+ export async function commitOpenVikingSession({ sessionId, sessionKey, getClient, logger, rememberSessionAgentId, isBypassedSession, }) {
145
145
  const ovId = openClawSessionToOvStorageId(sessionId, sessionKey);
146
146
  if (isBypassedSession({ sessionId, sessionKey })) {
147
147
  logger.warn?.(`openviking: commit skipped because session is bypassed (sessionId=${sessionId}, sessionKey=${sessionKey ?? "none"})`);
@@ -154,10 +154,8 @@ export async function commitOpenVikingSession({ sessionId, sessionKey, getClient
154
154
  sessionKey,
155
155
  ovSessionId: ovId,
156
156
  });
157
- const agentId = resolveAgentId(sessionId, sessionKey, ovId);
158
157
  const commitResult = await client.commitSession(ovId, {
159
158
  wait: true,
160
- agentId,
161
159
  keepRecentCount: 0,
162
160
  });
163
161
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -191,6 +189,10 @@ function assemblePassthrough(params) {
191
189
  });
192
190
  return { messages: liveMessages, estimatedTokens: originalTokens };
193
191
  }
192
+ function isSessionNotFoundError(err) {
193
+ const errorMessage = String(err);
194
+ return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
195
+ }
194
196
  export async function assembleOpenVikingSession({ sessionId, sessionKey, messages, tokenBudget, runtimeContext, isMainAssemble, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, queryConfigStore, traceRecorder, diag, roughEstimate, messageDigest, extractAgentMessageText, hasAutoRecallBlock, prependRecallToLatestUserMessage, }) {
195
197
  const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
196
198
  const sender = extractRuntimeSenderId(runtimeContext);
@@ -244,6 +246,11 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
244
246
  const client = await getClient();
245
247
  const routingRef = sessionId ?? sessionKey ?? ovSessionId;
246
248
  const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
249
+ const actorPeerId = resolveOpenVikingActorPeerId({
250
+ peerRole: cfg.peer_role ?? "assistant",
251
+ personPeerId: sanitizeOpenVikingPeerId(sender.senderId),
252
+ assistantPeerId: agentId,
253
+ });
247
254
  const queryConfig = await queryConfigStore?.getEffective({
248
255
  agentId,
249
256
  sessionId,
@@ -255,6 +262,7 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
255
262
  queryConfig,
256
263
  client,
257
264
  agentId,
265
+ actorPeerId,
258
266
  queryText: recallQuery.query,
259
267
  logger,
260
268
  verbose: (message) => logger.info(message),
@@ -303,9 +311,7 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
303
311
  }
304
312
  try {
305
313
  const client = await getClient();
306
- const routingRef = sessionId ?? sessionKey ?? ovSessionId;
307
- const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
308
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
314
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
309
315
  const preAbstracts = ctx?.pre_archive_abstracts ?? [];
310
316
  const hasArchives = !!ctx?.latest_archive_overview || preAbstracts.length > 0;
311
317
  const activeCount = ctx?.messages?.length ?? 0;
@@ -368,6 +374,25 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
368
374
  };
369
375
  }
370
376
  catch (err) {
377
+ if (isSessionNotFoundError(err)) {
378
+ const errorMessage = String(err);
379
+ logger.info(`openviking: assemble skipped because OV session does not exist ` +
380
+ `(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`);
381
+ return assemblePassthrough({
382
+ diag,
383
+ ovSessionId,
384
+ reason: "session_not_found",
385
+ liveMessages: messages,
386
+ originalTokens,
387
+ extra: {
388
+ error: errorMessage,
389
+ tokenBudget,
390
+ agentId: resolveAgentId(ovSessionId),
391
+ senderIdFound: sender.found,
392
+ senderId: sender.senderId ?? null,
393
+ },
394
+ });
395
+ }
371
396
  logger.warn?.(`openviking: assemble failed for session=${ovSessionId}, ` +
372
397
  `tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)}: ${String(err)}`);
373
398
  diag("assemble_error", ovSessionId, {
@@ -421,7 +446,7 @@ function extractRuntimeAgentId(runtimeContext) {
421
446
  return typeof agentId === "string" && agentId.trim() ? agentId.trim() : undefined;
422
447
  }
423
448
  function isToolOnlyMessage(msg) {
424
- return msg.role === "user" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
449
+ return msg.role === "assistant" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
425
450
  }
426
451
  function coalesceConsecutiveToolMessages(messages) {
427
452
  const result = [];
@@ -435,7 +460,7 @@ function coalesceConsecutiveToolMessages(messages) {
435
460
  for (const msg of messages) {
436
461
  if (isToolOnlyMessage(msg)) {
437
462
  if (!pendingTools) {
438
- pendingTools = { role: "user", parts: [] };
463
+ pendingTools = { role: "assistant", parts: [] };
439
464
  }
440
465
  pendingTools.parts.push(...msg.parts);
441
466
  continue;
@@ -480,7 +505,7 @@ function messageDigest(messages, maxCharsPerMsg = 2000) {
480
505
  };
481
506
  });
482
507
  }
483
- export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messages: rawMessages, prePromptMessageCount, isHeartbeat, runtimeContext, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, diag, }) {
508
+ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messages: rawMessages, prePromptMessageCount, isHeartbeat, tokenBudget, runtimeContext, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, diag, }) {
484
509
  if (!cfg.autoCapture) {
485
510
  return;
486
511
  }
@@ -573,16 +598,24 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
573
598
  };
574
599
  });
575
600
  if (ovParts.length > 0) {
576
- await client.addSessionMessage(ovSessionId, msg.role, ovParts, agentId, createdAt, msg.role === "user" ? senderRoleId : undefined);
601
+ await client.addSessionMessage(ovSessionId, msg.role, ovParts, undefined, createdAt, resolveOpenVikingMessagePeerId({
602
+ peerRole: cfg.peer_role ?? "assistant",
603
+ role: msg.role,
604
+ personPeerId: senderRoleId,
605
+ assistantPeerId: agentId,
606
+ }));
577
607
  }
578
608
  }
579
- const session = await client.getSession(ovSessionId, agentId);
609
+ const session = await client.getSession(ovSessionId);
580
610
  const pendingTokens = session.pending_tokens ?? 0;
581
- if (pendingTokens < cfg.commitTokenThreshold) {
611
+ const commitTokenThreshold = Math.floor(tokenBudget * cfg.commitTokenThresholdRatio);
612
+ if (pendingTokens < commitTokenThreshold) {
582
613
  diag("afterTurn_skip", ovSessionId, {
583
614
  reason: "below_threshold",
584
615
  pendingTokens,
585
- commitTokenThreshold: cfg.commitTokenThreshold,
616
+ commitTokenThreshold,
617
+ commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
618
+ tokenBudget,
586
619
  senderIdFound: sender.found,
587
620
  senderId: sender.senderId ?? null,
588
621
  });
@@ -590,7 +623,6 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
590
623
  }
591
624
  const commitResult = await client.commitSession(ovSessionId, {
592
625
  wait: false,
593
- agentId,
594
626
  keepRecentCount: cfg.commitKeepRecentCount,
595
627
  });
596
628
  logger.info(`openviking: committed session=${ovSessionId}, ` +
@@ -598,7 +630,9 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
598
630
  `task_id=${commitResult.task_id ?? "none"}, trace_id=${commitResult.trace_id ?? "none"}`);
599
631
  diag("afterTurn_commit", ovSessionId, {
600
632
  pendingTokens,
601
- commitTokenThreshold: cfg.commitTokenThreshold,
633
+ commitTokenThreshold,
634
+ commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
635
+ tokenBudget,
602
636
  status: commitResult.status,
603
637
  archived: commitResult.archived ?? false,
604
638
  taskId: commitResult.task_id ?? null,
@@ -609,7 +643,7 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
609
643
  if (commitResult.task_id && cfg.logFindRequests) {
610
644
  logger.info(`openviking: Phase2 memory extraction runs asynchronously on the server (task_id=${commitResult.task_id}). ` +
611
645
  "memories_extracted appears only after that task completes — not in this immediate response.");
612
- void pollPhase2ExtractionOutcome(client, commitResult.task_id, agentId, logger, ovSessionId);
646
+ void pollPhase2ExtractionOutcome(client, commitResult.task_id, logger, ovSessionId);
613
647
  }
614
648
  }
615
649
  catch (err) {
@@ -669,7 +703,7 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
669
703
  let preCommitEstimatedTokens;
670
704
  if (typeof tokensBeforeOriginal !== "number") {
671
705
  try {
672
- const preCtx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
706
+ const preCtx = await client.getSessionContext(ovSessionId, tokenBudget);
673
707
  if (typeof preCtx.estimatedTokens === "number" && Number.isFinite(preCtx.estimatedTokens)) {
674
708
  preCommitEstimatedTokens = preCtx.estimatedTokens;
675
709
  }
@@ -684,7 +718,6 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
684
718
  logger.info(`openviking: compact committing session=${ovSessionId} (wait=true, tokenBudget=${tokenBudget})`);
685
719
  const commitResult = await client.commitSession(ovSessionId, {
686
720
  wait: true,
687
- agentId,
688
721
  keepRecentCount: 0,
689
722
  });
690
723
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
@@ -746,7 +779,7 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
746
779
  let tokensAfter;
747
780
  let contextFetchError;
748
781
  try {
749
- const ctx = await client.getSessionContext(ovSessionId, tokenBudget, agentId);
782
+ const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
750
783
  logger.info(`openviking: compact getSessionContext raw result for ${ovSessionId}: ` +
751
784
  JSON.stringify(ctx, null, 2));
752
785
  if (typeof ctx.latest_archive_overview === "string") {
@@ -823,7 +856,7 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
823
856
  }
824
857
  catch (err) {
825
858
  const errorMessage = String(err);
826
- if (errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found")) {
859
+ if (isSessionNotFoundError(err)) {
827
860
  logger.info(`openviking: compact skipped because OV session does not exist ` +
828
861
  `(session=${ovSessionId}, agentId=${agentId})`);
829
862
  diag("compact_result", ovSessionId, {
@@ -1,14 +1,7 @@
1
+ import { sanitizeOpenVikingPeerId } from "../routing/identity-routing.js";
1
2
  import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js";
2
3
  export function toRoleId(senderId) {
3
- if (!senderId) {
4
- return undefined;
5
- }
6
- const normalized = senderId
7
- .trim()
8
- .replace(/[^a-zA-Z0-9_-]+/g, "_")
9
- .replace(/^_+|_+$/g, "")
10
- .replace(/_+/g, "_");
11
- return normalized || undefined;
4
+ return sanitizeOpenVikingPeerId(senderId);
12
5
  }
13
6
  /**
14
7
  * Convert an OpenViking stored message (parts-based format) into one or more
@@ -36,13 +36,8 @@ function looksLikeMetadataJsonBlock(content) {
36
36
  }
37
37
  return matchedKeys.size >= 3;
38
38
  }
39
- const HEARTBEAT_RE = /\bHEARTBEAT(?:\.md|_OK)\b/;
40
39
  const TOOL_PLACEHOLDER_RE = /^\s*\[tool(?::\s*|Use:\s*)[^\]]+\]\s*$/i;
41
40
  export function sanitizeUserTextForCapture(text) {
42
- // 过滤 HEARTBEAT 健康检查消息
43
- if (HEARTBEAT_RE.test(text)) {
44
- return "";
45
- }
46
41
  // Drop legacy synthetic tool placeholders before they reach memory extraction.
47
42
  if (TOOL_PLACEHOLDER_RE.test(text)) {
48
43
  return "";
@@ -344,7 +339,7 @@ export function extractNewTurnMessages(messages, startIndex) {
344
339
  : undefined);
345
340
  if (output) {
346
341
  result.push({
347
- role: "user",
342
+ role: "assistant",
348
343
  parts: [{
349
344
  type: "tool",
350
345
  toolCallId: toolCallId || undefined,
package/index.ts CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  import type { CommandDefinition } from "./plugin/command-registration.js";
42
42
 
43
43
  import type { HttpTransport } from "./adapters/http-transport.js";
44
- import { formatMessageFaithful, toRoleId } from "./services/context-message-adapter.js";
44
+ import { formatMessageFaithful } from "./services/context-message-adapter.js";
45
45
  import {
46
46
  clampScore,
47
47
  postProcessMemories,
@@ -212,6 +212,7 @@ const contextEnginePlugin = {
212
212
  resolvePluginSessionRouting,
213
213
  toQueryConfigContext,
214
214
  } = createOpenVikingSessionRoutingRuntime({
215
+ peerRole: cfg.peer_role,
215
216
  peerPrefix: cfg.peer_prefix,
216
217
  logFindRequests: cfg.logFindRequests,
217
218
  logger: api.logger,
@@ -329,8 +330,7 @@ const contextEnginePlugin = {
329
330
  getClient,
330
331
  normalizeSessionId: openClawSessionRefToOvStorageId,
331
332
  createTempSessionId: createMemoryStoreTempSessionId,
332
- extractSenderId: extractToolSenderId,
333
- toRoleId,
333
+ peerRole: cfg.peer_role,
334
334
  resolvePluginSessionRouting,
335
335
  isBypassedSession,
336
336
  makeBypassedToolResult,
@@ -20,6 +20,7 @@
20
20
  "tool-call-id.ts",
21
21
  "session-transcript-repair.ts",
22
22
  "runtime-utils.ts",
23
+ "request-headers.ts",
23
24
  "recall-trace.ts",
24
25
  "query-config.ts",
25
26
  "adapters/",
@@ -52,7 +53,7 @@
52
53
  "pruneAfterBuild": true
53
54
  },
54
55
  "compatibility": {
55
- "minOpenclawVersion": "2026.4.8",
56
+ "minOpenclawVersion": "2026.5.27",
56
57
  "recommendedOpenclawVersion": "2026.6.6",
57
58
  "minOpenvikingVersion": "0.4.1",
58
59
  "recommendedOpenvikingVersion": "0.4.1"
@@ -98,6 +98,11 @@
98
98
  "placeholder": "${OPENVIKING_API_KEY}",
99
99
  "help": "Optional API key for OpenViking server"
100
100
  },
101
+ "headers": {
102
+ "label": "Headers",
103
+ "advanced": true,
104
+ "help": "Optional HTTP headers merged into every OpenViking request."
105
+ },
101
106
  "accountId": {
102
107
  "label": "Account ID",
103
108
  "placeholder": "(derived from API key)",
@@ -196,11 +201,11 @@
196
201
  "help": "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
197
202
  "advanced": true
198
203
  },
199
- "commitTokenThreshold": {
200
- "label": "Commit Token Threshold",
201
- "placeholder": "20000",
204
+ "commitTokenThresholdRatio": {
205
+ "label": "Commit Token Threshold Ratio",
206
+ "placeholder": "0.5",
202
207
  "advanced": true,
203
- "help": "Minimum estimated pending tokens before auto-commit triggers. Set to 0 to commit every turn."
208
+ "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."
204
209
  },
205
210
  "commitKeepRecentCount": {
206
211
  "label": "Commit Keep Recent Count",
@@ -287,6 +292,12 @@
287
292
  "apiKey": {
288
293
  "type": "string"
289
294
  },
295
+ "headers": {
296
+ "type": "object",
297
+ "additionalProperties": {
298
+ "type": "string"
299
+ }
300
+ },
290
301
  "accountId": {
291
302
  "type": "string"
292
303
  },
@@ -358,6 +369,11 @@
358
369
  "commitTokenThreshold": {
359
370
  "type": "number"
360
371
  },
372
+ "commitTokenThresholdRatio": {
373
+ "type": "number",
374
+ "minimum": 0,
375
+ "maximum": 1
376
+ },
361
377
  "commitKeepRecentCount": {
362
378
  "type": "number",
363
379
  "minimum": 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/openclaw-plugin",
3
- "version": "2026.6.18",
3
+ "version": "2026.7.15",
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",
@@ -47,7 +47,7 @@
47
47
  "test:watch": "vitest"
48
48
  },
49
49
  "engines": {
50
- "openclaw": ">=2026.4.8"
50
+ "openclaw": ">=2026.5.27"
51
51
  },
52
52
  "dependencies": {
53
53
  "@sinclair/typebox": "0.34.48",
@@ -69,12 +69,12 @@
69
69
  ],
70
70
  "setupEntry": "./dist/commands/setup.js",
71
71
  "compat": {
72
- "pluginApi": ">=2026.4.8",
73
- "minGatewayVersion": "2026.4.8"
72
+ "pluginApi": ">=2026.5.27",
73
+ "minGatewayVersion": "2026.5.27"
74
74
  },
75
75
  "build": {
76
- "openclawVersion": "2026.4.8",
77
- "pluginSdkVersion": "2026.4.8"
76
+ "openclawVersion": "2026.5.27",
77
+ "pluginSdkVersion": "2026.5.27"
78
78
  }
79
79
  },
80
80
  "devDependencies": {
@@ -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 = [
@@ -1,5 +1,6 @@
1
1
  import { OpenVikingClient } from "../client.js";
2
2
  import type { HttpTransport } from "../adapters/http-transport.js";
3
+ import { resolveOpenVikingRequestHeaders } from "../request-headers.js";
3
4
 
4
5
  type Logger = {
5
6
  info: (message: string) => void;
@@ -13,6 +14,7 @@ type ClientRuntimeConfig = {
13
14
  timeoutMs: number;
14
15
  accountId?: string;
15
16
  userId?: string;
17
+ headers?: Record<string, string>;
16
18
  logFindRequests: boolean;
17
19
  };
18
20
 
@@ -61,7 +63,12 @@ export function createOpenVikingClientRuntime(options: {
61
63
  cfg.accountId,
62
64
  cfg.userId,
63
65
  routingDebugLog,
64
- { transport: options.transport },
66
+ {
67
+ transport: options.transport,
68
+ headers: resolveOpenVikingRequestHeaders({
69
+ headers: cfg.headers,
70
+ }),
71
+ },
65
72
  ),
66
73
  );
67
74
 
@@ -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
  }
@@ -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,