@openviking/openclaw-plugin 2026.6.18 → 2026.7.11
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.
- package/INSTALL-AGENT.md +2 -2
- package/INSTALL-ZH.md +2 -2
- package/INSTALL.md +2 -2
- package/README.md +1 -1
- package/auto-recall.ts +1 -2
- package/client.ts +14 -6
- package/commands/setup.ts +40 -10
- package/config.ts +44 -8
- package/context-engine.ts +2 -0
- package/dist/auto-recall.js +1 -2
- package/dist/client.js +11 -4
- package/dist/commands/setup.js +26 -10
- package/dist/config.js +31 -6
- package/dist/context-engine.js +2 -0
- package/dist/plugin/openviking-client-runtime.js +7 -1
- package/dist/plugin/openviking-memory-tools.js +1 -1
- package/dist/query-config.js +7 -1
- package/dist/request-headers.js +19 -0
- package/dist/services/context-lifecycle-service.js +35 -7
- package/dist/text-utils.js +1 -6
- package/install-manifest.json +2 -1
- package/openclaw.plugin.json +20 -4
- package/package.json +6 -6
- package/plugin/openviking-client-runtime.ts +8 -1
- package/plugin/openviking-memory-tools.ts +1 -1
- package/query-config.ts +7 -1
- package/request-headers.ts +29 -0
- package/services/context-lifecycle-service.ts +42 -7
- package/services/setup/probe-service.ts +35 -6
- package/services/setup/setup-flow.ts +61 -7
- package/skills/install-openviking-memory/SKILL.md +29 -15
- package/skills/openviking-context-database/SKILL.md +3 -3
- 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.
|
|
156
|
+
- OpenClaw >= 2026.5.27
|
|
157
157
|
|
|
158
158
|
Version boundaries:
|
|
159
159
|
|
|
160
|
-
- `2026.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
|
@@ -512,7 +511,7 @@ export async function buildAutoRecallContext(params: {
|
|
|
512
511
|
await recordTrace(memories.slice(0, memoryLines.length), memoryLines.length, estimatedTokens);
|
|
513
512
|
return { block, memoryCount: memoryLines.length, estimatedTokens };
|
|
514
513
|
})(),
|
|
515
|
-
|
|
514
|
+
cfg.autoRecallTimeoutMs,
|
|
516
515
|
"openviking: auto-recall search timeout",
|
|
517
516
|
);
|
|
518
517
|
}
|
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
|
-
|
|
762
|
+
peerId?: string,
|
|
755
763
|
): Promise<void> {
|
|
756
764
|
const body: {
|
|
757
765
|
role: string;
|
|
758
|
-
|
|
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 (
|
|
766
|
-
body.
|
|
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
|
-
|
|
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
|
-
"
|
|
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(
|
|
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
|
|
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(
|
|
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)
|
|
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
|
|
959
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
544
|
+
commitTokenThresholdRatio: Math.max(
|
|
514
545
|
0,
|
|
515
|
-
Math.min(
|
|
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
|
-
|
|
751
|
-
label: "Commit Token Threshold",
|
|
752
|
-
placeholder: String(
|
|
786
|
+
commitTokenThresholdRatio: {
|
|
787
|
+
label: "Commit Token Threshold Ratio",
|
|
788
|
+
placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO),
|
|
753
789
|
advanced: true,
|
|
754
|
-
help: "
|
|
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
|
@@ -370,12 +370,14 @@ export function createMemoryOpenVikingContextEngine(params: {
|
|
|
370
370
|
},
|
|
371
371
|
|
|
372
372
|
async afterTurn(afterTurnParams): Promise<void> {
|
|
373
|
+
const tokenBudget = validTokenBudget(afterTurnParams.tokenBudget) ?? 128_000;
|
|
373
374
|
await afterTurnOpenVikingSession({
|
|
374
375
|
sessionId: afterTurnParams.sessionId,
|
|
375
376
|
sessionKey: resolveSessionKey(afterTurnParams),
|
|
376
377
|
messages: afterTurnParams.messages,
|
|
377
378
|
prePromptMessageCount: afterTurnParams.prePromptMessageCount,
|
|
378
379
|
isHeartbeat: afterTurnParams.isHeartbeat,
|
|
380
|
+
tokenBudget,
|
|
379
381
|
runtimeContext: afterTurnParams.runtimeContext,
|
|
380
382
|
cfg,
|
|
381
383
|
getClient,
|
package/dist/auto-recall.js
CHANGED
|
@@ -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;
|
|
@@ -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
|
-
})(),
|
|
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,
|
|
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 (
|
|
396
|
-
body.
|
|
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
|
-
|
|
409
|
+
peer_id: peerId ?? null,
|
|
403
410
|
partCount: parts.length,
|
|
404
411
|
created_at: createdAt ?? null,
|
|
405
412
|
}, actorPeerId);
|
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
-
"
|
|
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
|
|
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
|
|
775
|
-
const
|
|
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}`)}`);
|