@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
package/INSTALL-AGENT.md CHANGED
@@ -153,11 +153,11 @@ openclaw --version
153
153
  Requirements:
154
154
 
155
155
  - Node.js >= 22
156
- - OpenClaw >= 2026.4.8
156
+ - OpenClaw >= 2026.5.27
157
157
 
158
158
  Version boundaries:
159
159
 
160
- - `2026.4.8` is the minimum supported OpenClaw version for the current `@openviking/openclaw-plugin` plugin.
160
+ - `2026.5.27` is the minimum supported OpenClaw version for the current `@openviking/openclaw-plugin` plugin.
161
161
  - `2026.5.3` starts requiring compiled JavaScript runtime output during package install when a plugin package declares TypeScript entries.
162
162
  - `2026.5.4` and later no longer fall back to `.ts` source for installed/global plugin runtime loading when the compiled JavaScript output is missing; the plugin may be skipped.
163
163
  - Published ClawHub packages are built before release and include `dist/*.js`, so normal users do not need to build locally.
package/INSTALL-ZH.md CHANGED
@@ -27,13 +27,13 @@ openclaw plugins install clawhub:@openviking/openclaw-plugin
27
27
  | 组件 | 要求 |
28
28
  | --- | --- |
29
29
  | Node.js | >= 22 |
30
- | OpenClaw | >= 2026.4.8 |
30
+ | OpenClaw | >= 2026.5.27 |
31
31
 
32
32
  插件以远程模式连接到已有的 OpenViking 服务。它不会帮你启动 OpenViking server。需要先启动 OpenViking,并保持服务运行,再把插件的 `baseUrl` 指向这个 HTTP 服务。默认本地地址是 `http://127.0.0.1:1933`。
33
33
 
34
34
  OpenClaw 插件包版本边界:
35
35
 
36
- - `2026.4.8` 是当前插件支持的最低 OpenClaw 版本。
36
+ - `2026.5.27` 是当前插件支持的最低 OpenClaw 版本。这个版本下限包含 2026 年 7 月 2 日 OpenClaw 安全公告批次的修复,包括 GHSA-8wg3-5mcm-fjq8 和 GHSA-83w9-h5wv-j9xm。
37
37
  - `2026.5.3` 开始,OpenClaw 在安装包时会校验 TypeScript 插件入口是否有编译后的 JavaScript 产物。
38
38
  - `2026.5.4` 及之后,已安装/全局插件如果缺少编译后的 JavaScript,运行时不再回退加载 `.ts` 源码,插件可能被跳过。
39
39
  - 推荐的 `openclaw plugins install clawhub:@openviking/openclaw-plugin` 会安装已经发布并包含 `dist/*.js` 的插件包,普通用户不需要本地编译。
package/INSTALL.md CHANGED
@@ -27,13 +27,13 @@ openclaw plugins install clawhub:@openviking/openclaw-plugin
27
27
  | Component | Required |
28
28
  | --- | --- |
29
29
  | Node.js | >= 22 |
30
- | OpenClaw | >= 2026.4.8 |
30
+ | OpenClaw | >= 2026.5.27 |
31
31
 
32
32
  The plugin connects to an existing OpenViking server. It does not start the OpenViking server for you. Start OpenViking first, keep it running, then point the plugin `baseUrl` at that HTTP service. The default local URL is `http://127.0.0.1:1933`.
33
33
 
34
34
  OpenClaw plugin package boundaries:
35
35
 
36
- - `2026.4.8` is the minimum supported OpenClaw version for the current plugin.
36
+ - `2026.5.27` is the minimum supported OpenClaw version for the current plugin. This floor includes the July 2, 2026 OpenClaw advisory batch fixes, including GHSA-8wg3-5mcm-fjq8 and GHSA-83w9-h5wv-j9xm.
37
37
  - `2026.5.3` starts validating package installs so TypeScript plugin entries need compiled JavaScript output.
38
38
  - `2026.5.4` and later stop falling back to `.ts` source for installed/global plugin runtime loading when compiled JavaScript is missing.
39
39
  - The recommended `openclaw plugins install clawhub:@openviking/openclaw-plugin` path installs a published package that already includes `dist/*.js`.
package/README.md CHANGED
@@ -204,7 +204,7 @@ That means OpenClaw sees "compressed history summary + archive index + active me
204
204
  - it strips injected `<openviking-context>` blocks, historical `<relevant-memories>` blocks, and metadata noise before capture
205
205
  - it appends the sanitized turn text into the OpenViking session
206
206
 
207
- After that, the plugin checks `pending_tokens`. Once the session crosses `commitTokenThreshold`, it triggers `commit(wait=false)`:
207
+ After that, the plugin checks `pending_tokens`. Once it reaches `commitTokenThresholdRatio` of the model context window (`tokenBudget`), it triggers `commit(wait=false)`:
208
208
 
209
209
  - archive generation and Phase 2 memory extraction continue asynchronously on the server
210
210
  - the current turn is not blocked waiting for extraction
package/auto-recall.ts CHANGED
@@ -19,7 +19,6 @@ import {
19
19
  import { sanitizeUserTextForCapture } from "./text-utils.js";
20
20
  import { estimateTextTokens } from "./token-estimator.js";
21
21
 
22
- const AUTO_RECALL_TIMEOUT_MS = 5_000;
23
22
  const RECALL_QUERY_MAX_CHARS = 4_000;
24
23
  export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
25
24
 
@@ -314,6 +313,7 @@ export async function buildAutoRecallContext(params: {
314
313
  queryConfig?: EffectiveQueryConfig;
315
314
  client: OpenVikingClient;
316
315
  agentId: string;
316
+ actorPeerId?: string;
317
317
  queryText: string;
318
318
  logger: Logger;
319
319
  verbose?: (message: string) => void;
@@ -325,14 +325,14 @@ export async function buildAutoRecallContext(params: {
325
325
  queryTruncated?: boolean;
326
326
  resourceTypes?: RecallResourceType[];
327
327
  }): Promise<{ block?: string; memoryCount: number; estimatedTokens: number }> {
328
- const { cfg, client, agentId, queryText, logger, verbose } = params;
328
+ const { cfg, client, agentId, actorPeerId, queryText, logger, verbose } = params;
329
329
  const queryConfig = params.queryConfig;
330
330
 
331
331
  if (!cfg.autoRecall || queryText.length < 5) {
332
332
  return { memoryCount: 0, estimatedTokens: 0 };
333
333
  }
334
334
 
335
- const precheck = await quickRecallPrecheck(client, agentId);
335
+ const precheck = await quickRecallPrecheck(client, actorPeerId);
336
336
  if (!precheck.ok) {
337
337
  verbose?.(`openviking: skipping auto-recall because precheck failed (${precheck.reason})`);
338
338
  return { memoryCount: 0, estimatedTokens: 0 };
@@ -370,7 +370,7 @@ export async function buildAutoRecallContext(params: {
370
370
  limit: candidateLimit,
371
371
  scoreThreshold: 0,
372
372
  contextType: search.contextType,
373
- actorPeerId: agentId,
373
+ actorPeerId,
374
374
  });
375
375
  return {
376
376
  resourceType: search.resourceType,
@@ -485,7 +485,7 @@ export async function buildAutoRecallContext(params: {
485
485
 
486
486
  const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(
487
487
  memories,
488
- (uri) => client.read(uri, agentId),
488
+ (uri) => client.read(uri, actorPeerId),
489
489
  {
490
490
  recallPreferAbstract,
491
491
  recallMaxInjectedChars: maxInjectedChars,
@@ -512,7 +512,7 @@ export async function buildAutoRecallContext(params: {
512
512
  await recordTrace(memories.slice(0, memoryLines.length), memoryLines.length, estimatedTokens);
513
513
  return { block, memoryCount: memoryLines.length, estimatedTokens };
514
514
  })(),
515
- AUTO_RECALL_TIMEOUT_MS,
515
+ cfg.autoRecallTimeoutMs,
516
516
  "openviking: auto-recall search timeout",
517
517
  );
518
518
  }
package/client.ts CHANGED
@@ -41,6 +41,7 @@ function userSessionUri(sessionId: string): string {
41
41
 
42
42
  export type OpenVikingClientOptions = {
43
43
  transport?: HttpTransport;
44
+ headers?: Record<string, string>;
44
45
  resourcePackager?: ResourcePackager;
45
46
  now?: () => number;
46
47
  sleep?: (ms: number) => Promise<void>;
@@ -48,7 +49,7 @@ export type OpenVikingClientOptions = {
48
49
 
49
50
  export type CommitSessionResult = {
50
51
  session_id: string;
51
- /** "accepted" (async), "completed", "failed", or "timeout" (wait mode). */
52
+ /** "accepted" (async), "skipped" (no archive), "completed", "failed", or "timeout" (wait mode). */
52
53
  status: string;
53
54
  task_id?: string;
54
55
  archive_uri?: string;
@@ -254,6 +255,7 @@ async function cleanupUploadTempPath(path?: string): Promise<void> {
254
255
 
255
256
  export class OpenVikingClient {
256
257
  private readonly transport: HttpTransport;
258
+ private readonly configuredHeaders: Record<string, string>;
257
259
  private readonly now: () => number;
258
260
  private readonly sleep: (ms: number) => Promise<void>;
259
261
  private readonly resourcePackager: ResourcePackager;
@@ -277,6 +279,7 @@ export class OpenVikingClient {
277
279
  ? optionsOrLegacyUserScope
278
280
  : (legacyOptions ?? {});
279
281
  this.transport = options.transport ?? defaultHttpTransport;
282
+ this.configuredHeaders = options.headers ?? {};
280
283
  this.now = options.now ?? Date.now;
281
284
  this.sleep = options.sleep ?? sleep;
282
285
  this.resourcePackager = options.resourcePackager ?? defaultResourcePackager;
@@ -357,6 +360,11 @@ export class OpenVikingClient {
357
360
  if (actorPeerHeader) {
358
361
  headers.set("X-OpenViking-Actor-Peer", actorPeerHeader);
359
362
  }
363
+ for (const [key, value] of Object.entries(this.configuredHeaders)) {
364
+ if (key && typeof value === "string" && value.trim()) {
365
+ headers.set(key, value);
366
+ }
367
+ }
360
368
  if (init.body && !(init.body instanceof FormData) && !headers.has("Content-Type")) {
361
369
  headers.set("Content-Type", "application/json");
362
370
  }
@@ -751,19 +759,19 @@ export class OpenVikingClient {
751
759
  }>,
752
760
  actorPeerId?: string,
753
761
  createdAt?: string,
754
- roleId?: string,
762
+ peerId?: string,
755
763
  ): Promise<void> {
756
764
  const body: {
757
765
  role: string;
758
- role_id?: string;
766
+ peer_id?: string;
759
767
  parts: typeof parts;
760
768
  created_at?: string;
761
769
  } = { role, parts };
762
770
  if (createdAt) {
763
771
  body.created_at = createdAt;
764
772
  }
765
- if (roleId) {
766
- body.role_id = roleId;
773
+ if (peerId) {
774
+ body.peer_id = peerId;
767
775
  }
768
776
  await this.emitRoutingDebug(
769
777
  "session message POST (with parts)",
@@ -771,7 +779,7 @@ export class OpenVikingClient {
771
779
  path: `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`,
772
780
  sessionId,
773
781
  role,
774
- role_id: roleId ?? null,
782
+ peer_id: peerId ?? null,
775
783
  partCount: parts.length,
776
784
  created_at: createdAt ?? null,
777
785
  },
package/commands/setup.ts CHANGED
@@ -4,6 +4,11 @@ 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 {
8
+ cleanOpenVikingRequestHeaders,
9
+ resolveOpenVikingRequestHeaders,
10
+ type OpenVikingRequestHeaders,
11
+ } from "../request-headers.js";
7
12
 
8
13
  const HOME = os.homedir();
9
14
  const OPENCLAW_DIR = getEnv("OPENCLAW_STATE_DIR") || path.join(HOME, ".openclaw");
@@ -58,6 +63,7 @@ const PLUGIN_VERSION = readPluginVersion();
58
63
  const { min: COMPATIBLE_SERVER_MIN, max: COMPATIBLE_SERVER_MAX } = readCompatRangeFromManifest();
59
64
  const CONFIG_KEYS_TO_PRESERVE = [
60
65
  "targetUri",
66
+ "headers",
61
67
  "timeoutMs",
62
68
  "autoCapture",
63
69
  "captureMode",
@@ -71,7 +77,7 @@ const CONFIG_KEYS_TO_PRESERVE = [
71
77
  "recallMaxContentChars",
72
78
  "recallPreferAbstract",
73
79
  "recallTokenBudget",
74
- "commitTokenThreshold",
80
+ "commitTokenThresholdRatio",
75
81
  "commitKeepRecentCount",
76
82
  "bypassSessionPatterns",
77
83
  "emitStandardDiagnostics",
@@ -107,6 +113,11 @@ function maskKey(key: string): string {
107
113
  return `${key.slice(0, 4)}...${key.slice(-4)}`;
108
114
  }
109
115
 
116
+ function nonEmptyOpenVikingRequestHeaders(value: unknown): OpenVikingRequestHeaders | undefined {
117
+ const headers = cleanOpenVikingRequestHeaders(value);
118
+ return Object.keys(headers).length > 0 ? headers : undefined;
119
+ }
120
+
110
121
  function isValidPeerPrefixInput(value: string): boolean {
111
122
  const trimmed = value.trim();
112
123
  return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
@@ -301,14 +312,21 @@ type ApiKeyProbeResult = {
301
312
  detail: string;
302
313
  };
303
314
 
304
- async function probeApiKeyType(baseUrl: string, apiKey?: string): Promise<ApiKeyProbeResult> {
315
+ async function probeApiKeyType(
316
+ baseUrl: string,
317
+ apiKey?: string,
318
+ configuredHeaders?: OpenVikingRequestHeaders,
319
+ ): Promise<ApiKeyProbeResult> {
305
320
  if (!apiKey) return { keyType: "no_key", needsAccountId: false, needsUserId: false, detail: "No API key configured" };
306
321
 
307
322
  const controller = new AbortController();
308
323
  const timeoutId = setTimeout(() => controller.abort(), 10_000);
309
324
  const sessionsUrl = `${baseUrl.replace(/\/+$/, "")}/api/v1/sessions?limit=1`;
310
325
  try {
311
- const headers: Record<string, string> = { "X-API-Key": apiKey };
326
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
327
+ const headers: Record<string, string> = {};
328
+ headers["X-API-Key"] = apiKey;
329
+ Object.assign(headers, configuredRequestHeaders);
312
330
  const response = await fetch(sessionsUrl, {
313
331
  headers,
314
332
  signal: controller.signal,
@@ -347,6 +365,7 @@ async function probeApiKeyType(baseUrl: string, apiKey?: string): Promise<ApiKey
347
365
  "X-API-Key": apiKey,
348
366
  "X-OpenViking-Account": "__probe__",
349
367
  "X-OpenViking-User": "__probe__",
368
+ ...configuredRequestHeaders,
350
369
  };
351
370
  const probe2 = await fetch(sessionsUrl, {
352
371
  headers: probeHeaders,
@@ -376,12 +395,20 @@ async function probeApiKeyType(baseUrl: string, apiKey?: string): Promise<ApiKey
376
395
  }
377
396
  }
378
397
 
379
- async function checkServiceHealth(baseUrl: string, apiKey?: string): Promise<HealthResult> {
398
+ async function checkServiceHealth(
399
+ baseUrl: string,
400
+ apiKey?: string,
401
+ configuredHeaders?: OpenVikingRequestHeaders,
402
+ ): Promise<HealthResult> {
380
403
  const controller = new AbortController();
381
404
  const timeoutId = setTimeout(() => controller.abort(), 10_000);
382
405
  try {
406
+ const configuredRequestHeaders = resolveOpenVikingRequestHeaders({ headers: configuredHeaders });
383
407
  const headers: Record<string, string> = {};
384
- if (apiKey) headers["X-API-Key"] = apiKey;
408
+ if (apiKey) {
409
+ headers["X-API-Key"] = apiKey;
410
+ }
411
+ Object.assign(headers, configuredRequestHeaders);
385
412
  const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/health`, {
386
413
  headers,
387
414
  signal: controller.signal,
@@ -772,8 +799,9 @@ async function runRemoteCheck(
772
799
  ): Promise<void> {
773
800
  const baseUrl = String(existing.baseUrl ?? DEFAULT_REMOTE_URL);
774
801
  const apiKey = existing.apiKey ? String(existing.apiKey) : undefined;
802
+ const headers = nonEmptyOpenVikingRequestHeaders(existing.headers);
775
803
  console.log(tr(zh, `Testing connectivity to ${baseUrl}...`, `正在测试连接 ${baseUrl}...`));
776
- const health = await checkServiceHealth(baseUrl, apiKey);
804
+ const health = await checkServiceHealth(baseUrl, apiKey, headers);
777
805
  if (health.ok) {
778
806
  const ver = health.version ? ` (version: ${health.version})` : "";
779
807
  console.log(` ✓ ${tr(zh, `Connected successfully${ver}`, `连接成功${ver}`)}`);
@@ -955,8 +983,9 @@ async function getStatus(configPath: string): Promise<StatusResult> {
955
983
 
956
984
  const baseUrl = String(existing.baseUrl ?? DEFAULT_REMOTE_URL);
957
985
  const apiKey = existing.apiKey ? String(existing.apiKey) : undefined;
958
- const health = await checkServiceHealth(baseUrl, apiKey);
959
- const keyProbe = health.ok ? await probeApiKeyType(baseUrl, apiKey) : undefined;
986
+ const headers = nonEmptyOpenVikingRequestHeaders(existing.headers);
987
+ const health = await checkServiceHealth(baseUrl, apiKey, headers);
988
+ const keyProbe = health.ok ? await probeApiKeyType(baseUrl, apiKey, headers) : undefined;
960
989
 
961
990
  const peerPrefix = resolveExistingPeerPrefix(existing);
962
991
  return {
@@ -1068,6 +1097,7 @@ async function setupRemote(
1068
1097
  const defaultApiKey = existing?.apiKey ? String(existing.apiKey) : "";
1069
1098
  const defaultPeerRole = resolveExistingPeerRole(existing);
1070
1099
  const defaultPeerPrefix = resolveExistingPeerPrefix(existing);
1100
+ const headers = nonEmptyOpenVikingRequestHeaders(existing?.headers);
1071
1101
 
1072
1102
  const baseUrl = await q(tr(zh, "OpenViking server URL", "OpenViking 服务器地址"), defaultUrl);
1073
1103
  const apiKey = await q(tr(zh, "API Key (optional)", "API Key(可选)"), defaultApiKey);
@@ -1077,7 +1107,7 @@ async function setupRemote(
1077
1107
 
1078
1108
  if (apiKey) {
1079
1109
  console.log(tr(zh, " Detecting API key type...", " 正在检测 API Key 类型..."));
1080
- const probe = await probeApiKeyType(baseUrl, apiKey);
1110
+ const probe = await probeApiKeyType(baseUrl, apiKey, headers);
1081
1111
  if (probe.keyType === "root_key") {
1082
1112
  console.log(tr(zh,
1083
1113
  " ⚠ Root API key detected. accountId and userId are required.",
@@ -1098,7 +1128,7 @@ async function setupRemote(
1098
1128
  console.log("");
1099
1129
 
1100
1130
  console.log(tr(zh, `Testing connectivity to ${baseUrl}...`, `正在测试连接 ${baseUrl}...`));
1101
- const health = await checkServiceHealth(baseUrl, apiKey || undefined);
1131
+ const health = await checkServiceHealth(baseUrl, apiKey || undefined, headers);
1102
1132
  if (health.ok) {
1103
1133
  const ver = health.version ? ` (version: ${health.version})` : "";
1104
1134
  console.log(` ✓ ${tr(zh, `Connected successfully${ver}`, `连接成功${ver}`)}`);
package/config.ts CHANGED
@@ -8,6 +8,8 @@ export type MemoryOpenVikingConfig = {
8
8
  peer_role?: "none" | "assistant" | "person";
9
9
  peer_prefix?: string;
10
10
  apiKey?: string;
11
+ /** Optional HTTP headers merged into every OpenViking request. */
12
+ headers?: Record<string, string>;
11
13
  /** Advanced option. Only needed when explicitly sending tenant identity headers. With a user key the server derives identity from the key. */
12
14
  accountId?: string;
13
15
  /** Advanced option. Only needed when explicitly sending tenant identity headers. */
@@ -31,7 +33,15 @@ export type MemoryOpenVikingConfig = {
31
33
  recallPreferAbstract?: boolean;
32
34
  /** @deprecated Use recallMaxInjectedChars. */
33
35
  recallTokenBudget?: number;
34
- commitTokenThreshold?: number;
36
+ /**
37
+ * Auto-commit threshold expressed as a fraction (0-1) of the model context
38
+ * window (tokenBudget). afterTurn triggers an async commit once estimated
39
+ * pending tokens reach `commitTokenThresholdRatio * tokenBudget`
40
+ * (e.g. 0.5 = 50% of the window). Replaces the former absolute
41
+ * `commitTokenThreshold` (still accepted but ignored for backward
42
+ * compatibility). Set to 0 to commit every turn.
43
+ */
44
+ commitTokenThresholdRatio?: number;
35
45
  /**
36
46
  * WM v2: number of most-recent messages to keep live after an afterTurn
37
47
  * commit so the next turn still has immediate context. Forwarded to the
@@ -109,7 +119,7 @@ const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
109
119
  const DEFAULT_RECALL_MAX_CONTENT_CHARS = 5000;
110
120
  const DEFAULT_RECALL_PREFER_ABSTRACT = false;
111
121
  const DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000;
112
- const DEFAULT_COMMIT_TOKEN_THRESHOLD = 20000;
122
+ const DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO = 0.5;
113
123
  const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
114
124
  const DEFAULT_BYPASS_SESSION_PATTERNS: string[] = [];
115
125
  const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
@@ -280,6 +290,24 @@ function toRecord(value: unknown): Record<string, unknown> {
280
290
  : {};
281
291
  }
282
292
 
293
+ function toStringRecord(value: unknown, label: string): Record<string, string> {
294
+ if (value === undefined || value === null) {
295
+ return {};
296
+ }
297
+ if (typeof value !== "object" || Array.isArray(value)) {
298
+ throw new Error(`${label} must be an object`);
299
+ }
300
+ const record = value as Record<string, unknown>;
301
+ const out: Record<string, string> = {};
302
+ for (const [key, val] of Object.entries(record)) {
303
+ if (typeof val !== "string") {
304
+ throw new Error(`${label}.${key} must be a string`);
305
+ }
306
+ out[key] = val;
307
+ }
308
+ return out;
309
+ }
310
+
283
311
  function expandToolSelectors(value: unknown, fallback: string[], label: string): OpenVikingToolName[] {
284
312
  const entries = toStringArray(value, fallback);
285
313
  const seen = new Set<OpenVikingToolName>();
@@ -377,6 +405,7 @@ export const memoryOpenVikingConfigSchema = {
377
405
  "peer_prefix",
378
406
  "serverAuthMode",
379
407
  "apiKey",
408
+ "headers",
380
409
  "accountId",
381
410
  "userId",
382
411
  "targetUri",
@@ -394,6 +423,7 @@ export const memoryOpenVikingConfigSchema = {
394
423
  "recallPreferAbstract",
395
424
  "recallTokenBudget",
396
425
  "commitTokenThreshold",
426
+ "commitTokenThresholdRatio",
397
427
  "commitKeepRecentCount",
398
428
  "bypassSessionPatterns",
399
429
  "ingestReplyAssist",
@@ -479,6 +509,7 @@ export const memoryOpenVikingConfigSchema = {
479
509
  peer_role: peerRole,
480
510
  peer_prefix: peerPrefix,
481
511
  apiKey: rawApiKey ? resolveEnvVars(rawApiKey) : "",
512
+ headers: toStringRecord(cfg.headers, "openviking config headers"),
482
513
  accountId,
483
514
  userId,
484
515
  targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
@@ -510,9 +541,9 @@ export const memoryOpenVikingConfigSchema = {
510
541
  : DEFAULT_RECALL_PREFER_ABSTRACT,
511
542
  recallMaxInjectedChars,
512
543
  recallTokenBudget: recallMaxInjectedChars,
513
- commitTokenThreshold: Math.max(
544
+ commitTokenThresholdRatio: Math.max(
514
545
  0,
515
- Math.min(100_000, Math.floor(toNumber(cfg.commitTokenThreshold, DEFAULT_COMMIT_TOKEN_THRESHOLD))),
546
+ Math.min(1, toNumber(cfg.commitTokenThresholdRatio, DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO)),
516
547
  ),
517
548
  commitKeepRecentCount: Math.max(
518
549
  0,
@@ -649,6 +680,11 @@ export const memoryOpenVikingConfigSchema = {
649
680
  placeholder: "${OPENVIKING_API_KEY}",
650
681
  help: "Optional API key for OpenViking server",
651
682
  },
683
+ headers: {
684
+ label: "Headers",
685
+ advanced: true,
686
+ help: "Optional HTTP headers merged into every OpenViking request.",
687
+ },
652
688
  accountId: {
653
689
  label: "Account ID",
654
690
  placeholder: "(derived from API key)",
@@ -747,11 +783,11 @@ export const memoryOpenVikingConfigSchema = {
747
783
  help: "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
748
784
  advanced: true,
749
785
  },
750
- commitTokenThreshold: {
751
- label: "Commit Token Threshold",
752
- placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD),
786
+ commitTokenThresholdRatio: {
787
+ label: "Commit Token Threshold Ratio",
788
+ placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO),
753
789
  advanced: true,
754
- help: "Minimum estimated pending tokens before auto-commit triggers. Set to 0 to commit every turn.",
790
+ 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.",
755
791
  },
756
792
  commitKeepRecentCount: {
757
793
  label: "Commit Keep Recent Count",
package/context-engine.ts CHANGED
@@ -283,7 +283,6 @@ export function createMemoryOpenVikingContextEngine(params: {
283
283
  getClient,
284
284
  logger,
285
285
  rememberSessionAgentId,
286
- resolveAgentId,
287
286
  isBypassedSession,
288
287
  });
289
288
  }
@@ -370,12 +369,14 @@ export function createMemoryOpenVikingContextEngine(params: {
370
369
  },
371
370
 
372
371
  async afterTurn(afterTurnParams): Promise<void> {
372
+ const tokenBudget = validTokenBudget(afterTurnParams.tokenBudget) ?? 128_000;
373
373
  await afterTurnOpenVikingSession({
374
374
  sessionId: afterTurnParams.sessionId,
375
375
  sessionKey: resolveSessionKey(afterTurnParams),
376
376
  messages: afterTurnParams.messages,
377
377
  prePromptMessageCount: afterTurnParams.prePromptMessageCount,
378
378
  isHeartbeat: afterTurnParams.isHeartbeat,
379
+ tokenBudget,
379
380
  runtimeContext: afterTurnParams.runtimeContext,
380
381
  cfg,
381
382
  getClient,
@@ -3,7 +3,6 @@ import { quickRecallPrecheck, withTimeout } from "./process-manager.js";
3
3
  import { resolveRecallSearchPlan, } from "./registries/recall-resource-types.js";
4
4
  import { sanitizeUserTextForCapture } from "./text-utils.js";
5
5
  import { estimateTextTokens } from "./token-estimator.js";
6
- const AUTO_RECALL_TIMEOUT_MS = 5_000;
7
6
  const RECALL_QUERY_MAX_CHARS = 4_000;
8
7
  export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
9
8
  const WRITE_OR_EFFECT_RE = /\b(write|edit|modify|delete|remove|migrate|deploy|release|publish|configure|patch)\b|写|改|修改|删除|迁移|部署|发布|配置|打补丁/i;
@@ -201,12 +200,12 @@ export function shouldRecallAgentExperience(input) {
201
200
  return { recall: false, score, reason: score < 0 ? "non_execution" : "below_threshold" };
202
201
  }
203
202
  export async function buildAutoRecallContext(params) {
204
- const { cfg, client, agentId, queryText, logger, verbose } = params;
203
+ const { cfg, client, agentId, actorPeerId, queryText, logger, verbose } = params;
205
204
  const queryConfig = params.queryConfig;
206
205
  if (!cfg.autoRecall || queryText.length < 5) {
207
206
  return { memoryCount: 0, estimatedTokens: 0 };
208
207
  }
209
- const precheck = await quickRecallPrecheck(client, agentId);
208
+ const precheck = await quickRecallPrecheck(client, actorPeerId);
210
209
  if (!precheck.ok) {
211
210
  verbose?.(`openviking: skipping auto-recall because precheck failed (${precheck.reason})`);
212
211
  return { memoryCount: 0, estimatedTokens: 0 };
@@ -237,7 +236,7 @@ export async function buildAutoRecallContext(params) {
237
236
  limit: candidateLimit,
238
237
  scoreThreshold: 0,
239
238
  contextType: search.contextType,
240
- actorPeerId: agentId,
239
+ actorPeerId,
241
240
  });
242
241
  return {
243
242
  resourceType: search.resourceType,
@@ -344,7 +343,7 @@ export async function buildAutoRecallContext(params) {
344
343
  await recordTrace([], 0, 0);
345
344
  return { memoryCount: 0, estimatedTokens: 0 };
346
345
  }
347
- const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(memories, (uri) => client.read(uri, agentId), {
346
+ const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(memories, (uri) => client.read(uri, actorPeerId), {
348
347
  recallPreferAbstract,
349
348
  recallMaxInjectedChars: maxInjectedChars,
350
349
  includeUri: true,
@@ -359,5 +358,5 @@ export async function buildAutoRecallContext(params) {
359
358
  verbose?.(`openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`);
360
359
  await recordTrace(memories.slice(0, memoryLines.length), memoryLines.length, estimatedTokens);
361
360
  return { block, memoryCount: memoryLines.length, estimatedTokens };
362
- })(), AUTO_RECALL_TIMEOUT_MS, "openviking: auto-recall search timeout");
361
+ })(), cfg.autoRecallTimeoutMs, "openviking: auto-recall search timeout");
363
362
  }
package/dist/client.js CHANGED
@@ -51,6 +51,7 @@ export class OpenVikingClient {
51
51
  userId;
52
52
  routingDebugLog;
53
53
  transport;
54
+ configuredHeaders;
54
55
  now;
55
56
  sleep;
56
57
  resourcePackager;
@@ -70,6 +71,7 @@ export class OpenVikingClient {
70
71
  ? optionsOrLegacyUserScope
71
72
  : (legacyOptions ?? {});
72
73
  this.transport = options.transport ?? defaultHttpTransport;
74
+ this.configuredHeaders = options.headers ?? {};
73
75
  this.now = options.now ?? Date.now;
74
76
  this.sleep = options.sleep ?? sleep;
75
77
  this.resourcePackager = options.resourcePackager ?? defaultResourcePackager;
@@ -131,6 +133,11 @@ export class OpenVikingClient {
131
133
  if (actorPeerHeader) {
132
134
  headers.set("X-OpenViking-Actor-Peer", actorPeerHeader);
133
135
  }
136
+ for (const [key, value] of Object.entries(this.configuredHeaders)) {
137
+ if (key && typeof value === "string" && value.trim()) {
138
+ headers.set(key, value);
139
+ }
140
+ }
134
141
  if (init.body && !(init.body instanceof FormData) && !headers.has("Content-Type")) {
135
142
  headers.set("Content-Type", "application/json");
136
143
  }
@@ -387,19 +394,19 @@ export class OpenVikingClient {
387
394
  await this.resourcePackager.cleanup(packagedSource);
388
395
  }
389
396
  }
390
- async addSessionMessage(sessionId, role, parts, actorPeerId, createdAt, roleId) {
397
+ async addSessionMessage(sessionId, role, parts, actorPeerId, createdAt, peerId) {
391
398
  const body = { role, parts };
392
399
  if (createdAt) {
393
400
  body.created_at = createdAt;
394
401
  }
395
- if (roleId) {
396
- body.role_id = roleId;
402
+ if (peerId) {
403
+ body.peer_id = peerId;
397
404
  }
398
405
  await this.emitRoutingDebug("session message POST (with parts)", {
399
406
  path: `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`,
400
407
  sessionId,
401
408
  role,
402
- role_id: roleId ?? null,
409
+ peer_id: peerId ?? null,
403
410
  partCount: parts.length,
404
411
  created_at: createdAt ?? null,
405
412
  }, actorPeerId);