@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.
- 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 +6 -6
- package/client.ts +14 -6
- package/commands/setup.ts +40 -10
- package/config.ts +44 -8
- package/context-engine.ts +2 -1
- package/dist/auto-recall.js +5 -6
- 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 -1
- package/dist/index.js +4 -4
- package/dist/plugin/openviking-archive-tools.js +1 -2
- package/dist/plugin/openviking-client-runtime.js +7 -1
- package/dist/plugin/openviking-command-definitions.js +3 -3
- package/dist/plugin/openviking-import-tools.js +2 -2
- package/dist/plugin/openviking-memory-recall-tools.js +4 -4
- package/dist/plugin/openviking-memory-tools.js +12 -7
- package/dist/plugin/openviking-query-runtime.js +1 -1
- package/dist/plugin/openviking-query-tools.js +4 -4
- package/dist/plugin/openviking-recall-trace-runtime.js +3 -3
- package/dist/plugin/openviking-session-routing-runtime.js +9 -2
- package/dist/plugin/openviking-tool-result-tools.js +3 -3
- package/dist/query-config.js +7 -1
- package/dist/request-headers.js +19 -0
- package/dist/routing/identity-routing.js +27 -0
- package/dist/routing/memory-uri.js +1 -0
- package/dist/services/context-lifecycle-service.js +56 -23
- package/dist/services/context-message-adapter.js +2 -9
- package/dist/text-utils.js +1 -6
- package/index.ts +3 -3
- package/install-manifest.json +2 -1
- package/openclaw.plugin.json +20 -4
- package/package.json +6 -6
- package/plugin/command-registration.ts +2 -0
- package/plugin/openviking-archive-tools.ts +1 -4
- package/plugin/openviking-client-runtime.ts +8 -1
- package/plugin/openviking-command-definitions.ts +4 -3
- package/plugin/openviking-import-tools.ts +4 -2
- package/plugin/openviking-memory-recall-tools.ts +6 -4
- package/plugin/openviking-memory-tools.ts +19 -11
- package/plugin/openviking-query-runtime.ts +1 -1
- package/plugin/openviking-query-tools.ts +6 -4
- package/plugin/openviking-recall-trace-runtime.ts +4 -3
- package/plugin/openviking-recall-trace-tools.ts +2 -0
- package/plugin/openviking-session-routing-runtime.ts +15 -1
- package/plugin/openviking-tool-result-tools.ts +0 -6
- package/query-config.ts +7 -1
- package/request-headers.ts +29 -0
- package/routing/identity-routing.ts +41 -0
- package/routing/memory-uri.ts +1 -0
- package/services/context-lifecycle-service.ts +69 -25
- package/services/context-message-adapter.ts +2 -9
- 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
|
|
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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,
|
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;
|
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
-
})(),
|
|
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);
|