@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/dist/config.js
CHANGED
|
@@ -11,7 +11,7 @@ const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
|
|
|
11
11
|
const DEFAULT_RECALL_MAX_CONTENT_CHARS = 5000;
|
|
12
12
|
const DEFAULT_RECALL_PREFER_ABSTRACT = false;
|
|
13
13
|
const DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000;
|
|
14
|
-
const
|
|
14
|
+
const DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO = 0.5;
|
|
15
15
|
const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
|
|
16
16
|
const DEFAULT_BYPASS_SESSION_PATTERNS = [];
|
|
17
17
|
const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
|
|
@@ -168,6 +168,23 @@ function toRecord(value) {
|
|
|
168
168
|
? value
|
|
169
169
|
: {};
|
|
170
170
|
}
|
|
171
|
+
function toStringRecord(value, label) {
|
|
172
|
+
if (value === undefined || value === null) {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
176
|
+
throw new Error(`${label} must be an object`);
|
|
177
|
+
}
|
|
178
|
+
const record = value;
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const [key, val] of Object.entries(record)) {
|
|
181
|
+
if (typeof val !== "string") {
|
|
182
|
+
throw new Error(`${label}.${key} must be a string`);
|
|
183
|
+
}
|
|
184
|
+
out[key] = val;
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
171
188
|
function expandToolSelectors(value, fallback, label) {
|
|
172
189
|
const entries = toStringArray(value, fallback);
|
|
173
190
|
const seen = new Set();
|
|
@@ -250,6 +267,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
250
267
|
"peer_prefix",
|
|
251
268
|
"serverAuthMode",
|
|
252
269
|
"apiKey",
|
|
270
|
+
"headers",
|
|
253
271
|
"accountId",
|
|
254
272
|
"userId",
|
|
255
273
|
"targetUri",
|
|
@@ -267,6 +285,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
267
285
|
"recallPreferAbstract",
|
|
268
286
|
"recallTokenBudget",
|
|
269
287
|
"commitTokenThreshold",
|
|
288
|
+
"commitTokenThresholdRatio",
|
|
270
289
|
"commitKeepRecentCount",
|
|
271
290
|
"bypassSessionPatterns",
|
|
272
291
|
"ingestReplyAssist",
|
|
@@ -324,6 +343,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
324
343
|
peer_role: peerRole,
|
|
325
344
|
peer_prefix: peerPrefix,
|
|
326
345
|
apiKey: rawApiKey ? resolveEnvVars(rawApiKey) : "",
|
|
346
|
+
headers: toStringRecord(cfg.headers, "openviking config headers"),
|
|
327
347
|
accountId,
|
|
328
348
|
userId,
|
|
329
349
|
targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
|
|
@@ -342,7 +362,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
342
362
|
: DEFAULT_RECALL_PREFER_ABSTRACT,
|
|
343
363
|
recallMaxInjectedChars,
|
|
344
364
|
recallTokenBudget: recallMaxInjectedChars,
|
|
345
|
-
|
|
365
|
+
commitTokenThresholdRatio: Math.max(0, Math.min(1, toNumber(cfg.commitTokenThresholdRatio, DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO))),
|
|
346
366
|
commitKeepRecentCount: Math.max(0, Math.min(1_000, Math.floor(toNumber(cfg.commitKeepRecentCount, DEFAULT_COMMIT_KEEP_RECENT_COUNT)))),
|
|
347
367
|
bypassSessionPatterns: toStringArray(cfg.bypassSessionPatterns, toStringArray(cfg.ingestReplyAssistIgnoreSessionPatterns, DEFAULT_BYPASS_SESSION_PATTERNS)),
|
|
348
368
|
emitStandardDiagnostics: typeof cfg.emitStandardDiagnostics === "boolean"
|
|
@@ -405,6 +425,11 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
405
425
|
placeholder: "${OPENVIKING_API_KEY}",
|
|
406
426
|
help: "Optional API key for OpenViking server",
|
|
407
427
|
},
|
|
428
|
+
headers: {
|
|
429
|
+
label: "Headers",
|
|
430
|
+
advanced: true,
|
|
431
|
+
help: "Optional HTTP headers merged into every OpenViking request.",
|
|
432
|
+
},
|
|
408
433
|
accountId: {
|
|
409
434
|
label: "Account ID",
|
|
410
435
|
placeholder: "(derived from API key)",
|
|
@@ -503,11 +528,11 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
503
528
|
help: "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
|
|
504
529
|
advanced: true,
|
|
505
530
|
},
|
|
506
|
-
|
|
507
|
-
label: "Commit Token Threshold",
|
|
508
|
-
placeholder: String(
|
|
531
|
+
commitTokenThresholdRatio: {
|
|
532
|
+
label: "Commit Token Threshold Ratio",
|
|
533
|
+
placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD_RATIO),
|
|
509
534
|
advanced: true,
|
|
510
|
-
help: "
|
|
535
|
+
help: "Auto-commit triggers once estimated pending tokens reach this fraction (0-1) of the model context window (e.g. 0.5 = 50%). Set to 0 to commit every turn.",
|
|
511
536
|
},
|
|
512
537
|
commitKeepRecentCount: {
|
|
513
538
|
label: "Commit Keep Recent Count",
|
package/dist/context-engine.js
CHANGED
|
@@ -204,12 +204,14 @@ export function createMemoryOpenVikingContextEngine(params) {
|
|
|
204
204
|
});
|
|
205
205
|
},
|
|
206
206
|
async afterTurn(afterTurnParams) {
|
|
207
|
+
const tokenBudget = validTokenBudget(afterTurnParams.tokenBudget) ?? 128_000;
|
|
207
208
|
await afterTurnOpenVikingSession({
|
|
208
209
|
sessionId: afterTurnParams.sessionId,
|
|
209
210
|
sessionKey: resolveSessionKey(afterTurnParams),
|
|
210
211
|
messages: afterTurnParams.messages,
|
|
211
212
|
prePromptMessageCount: afterTurnParams.prePromptMessageCount,
|
|
212
213
|
isHeartbeat: afterTurnParams.isHeartbeat,
|
|
214
|
+
tokenBudget,
|
|
213
215
|
runtimeContext: afterTurnParams.runtimeContext,
|
|
214
216
|
cfg,
|
|
215
217
|
getClient,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OpenVikingClient } from "../client.js";
|
|
2
|
+
import { resolveOpenVikingRequestHeaders } from "../request-headers.js";
|
|
2
3
|
export function createOpenVikingClientRuntime(options) {
|
|
3
4
|
const { cfg, logger } = options;
|
|
4
5
|
if (cfg.logFindRequests) {
|
|
@@ -19,7 +20,12 @@ export function createOpenVikingClientRuntime(options) {
|
|
|
19
20
|
logger.info(msg);
|
|
20
21
|
}
|
|
21
22
|
: undefined;
|
|
22
|
-
const clientPromise = Promise.resolve(new OpenVikingClient(cfg.baseUrl, cfg.apiKey, cfg.peer_prefix, cfg.timeoutMs, cfg.accountId, cfg.userId, routingDebugLog, {
|
|
23
|
+
const clientPromise = Promise.resolve(new OpenVikingClient(cfg.baseUrl, cfg.apiKey, cfg.peer_prefix, cfg.timeoutMs, cfg.accountId, cfg.userId, routingDebugLog, {
|
|
24
|
+
transport: options.transport,
|
|
25
|
+
headers: resolveOpenVikingRequestHeaders({
|
|
26
|
+
headers: cfg.headers,
|
|
27
|
+
}),
|
|
28
|
+
}));
|
|
23
29
|
const getClient = () => clientPromise;
|
|
24
30
|
return {
|
|
25
31
|
getClient,
|
|
@@ -11,7 +11,7 @@ export function registerOpenVikingMemoryTools(deps) {
|
|
|
11
11
|
deps.registerTool((ctx) => ({
|
|
12
12
|
name: "memory_store",
|
|
13
13
|
label: "Memory Store (OpenViking)",
|
|
14
|
-
description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction.",
|
|
14
|
+
description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction. Use when the user explicitly asks to remember, save, or store an important long-term fact, preference, project, or decision; automatic capture is threshold/commit dependent.",
|
|
15
15
|
parameters: Type.Object({
|
|
16
16
|
text: Type.String({ description: "Information to store as memory source text" }),
|
|
17
17
|
role: Type.Optional(Type.String({ description: "Session role, default user" })),
|
package/dist/query-config.js
CHANGED
|
@@ -171,7 +171,13 @@ export class RuntimeQueryConfigStore {
|
|
|
171
171
|
const { params, warnings } = normalizeRuntimeQueryParams(patch);
|
|
172
172
|
const key = this.keyForScope(scope, ctx);
|
|
173
173
|
const bucket = scope === "claw" ? this.data.claws : this.data.sessions;
|
|
174
|
-
bucket[key]
|
|
174
|
+
const previous = bucket[key];
|
|
175
|
+
bucket[key] = {
|
|
176
|
+
params: { ...(previous?.params ?? {}), ...params },
|
|
177
|
+
updatedAt: Date.now(),
|
|
178
|
+
updatedBy: "command",
|
|
179
|
+
peerId: ctx.peerId,
|
|
180
|
+
};
|
|
175
181
|
this.data.updatedAt = Date.now();
|
|
176
182
|
await this.persist();
|
|
177
183
|
return { warnings };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function resolveOpenVikingRequestHeaders(options = {}) {
|
|
2
|
+
return cleanOpenVikingRequestHeaders(options.headers);
|
|
3
|
+
}
|
|
4
|
+
export function cleanOpenVikingRequestHeaders(headers) {
|
|
5
|
+
if (headers === undefined || headers === null) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
if (typeof headers !== "object" || Array.isArray(headers)) {
|
|
9
|
+
throw new Error("openviking request headers must be an object");
|
|
10
|
+
}
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
13
|
+
if (typeof value !== "string") {
|
|
14
|
+
throw new Error(`openviking request header ${key} must be a string`);
|
|
15
|
+
}
|
|
16
|
+
out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
@@ -191,6 +191,10 @@ function assemblePassthrough(params) {
|
|
|
191
191
|
});
|
|
192
192
|
return { messages: liveMessages, estimatedTokens: originalTokens };
|
|
193
193
|
}
|
|
194
|
+
function isSessionNotFoundError(err) {
|
|
195
|
+
const errorMessage = String(err);
|
|
196
|
+
return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
|
|
197
|
+
}
|
|
194
198
|
export async function assembleOpenVikingSession({ sessionId, sessionKey, messages, tokenBudget, runtimeContext, isMainAssemble, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, queryConfigStore, traceRecorder, diag, roughEstimate, messageDigest, extractAgentMessageText, hasAutoRecallBlock, prependRecallToLatestUserMessage, }) {
|
|
195
199
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
196
200
|
const sender = extractRuntimeSenderId(runtimeContext);
|
|
@@ -368,6 +372,25 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
|
|
|
368
372
|
};
|
|
369
373
|
}
|
|
370
374
|
catch (err) {
|
|
375
|
+
if (isSessionNotFoundError(err)) {
|
|
376
|
+
const errorMessage = String(err);
|
|
377
|
+
logger.info(`openviking: assemble skipped because OV session does not exist ` +
|
|
378
|
+
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`);
|
|
379
|
+
return assemblePassthrough({
|
|
380
|
+
diag,
|
|
381
|
+
ovSessionId,
|
|
382
|
+
reason: "session_not_found",
|
|
383
|
+
liveMessages: messages,
|
|
384
|
+
originalTokens,
|
|
385
|
+
extra: {
|
|
386
|
+
error: errorMessage,
|
|
387
|
+
tokenBudget,
|
|
388
|
+
agentId: resolveAgentId(ovSessionId),
|
|
389
|
+
senderIdFound: sender.found,
|
|
390
|
+
senderId: sender.senderId ?? null,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
}
|
|
371
394
|
logger.warn?.(`openviking: assemble failed for session=${ovSessionId}, ` +
|
|
372
395
|
`tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)}: ${String(err)}`);
|
|
373
396
|
diag("assemble_error", ovSessionId, {
|
|
@@ -421,7 +444,7 @@ function extractRuntimeAgentId(runtimeContext) {
|
|
|
421
444
|
return typeof agentId === "string" && agentId.trim() ? agentId.trim() : undefined;
|
|
422
445
|
}
|
|
423
446
|
function isToolOnlyMessage(msg) {
|
|
424
|
-
return msg.role === "
|
|
447
|
+
return msg.role === "assistant" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
|
|
425
448
|
}
|
|
426
449
|
function coalesceConsecutiveToolMessages(messages) {
|
|
427
450
|
const result = [];
|
|
@@ -435,7 +458,7 @@ function coalesceConsecutiveToolMessages(messages) {
|
|
|
435
458
|
for (const msg of messages) {
|
|
436
459
|
if (isToolOnlyMessage(msg)) {
|
|
437
460
|
if (!pendingTools) {
|
|
438
|
-
pendingTools = { role: "
|
|
461
|
+
pendingTools = { role: "assistant", parts: [] };
|
|
439
462
|
}
|
|
440
463
|
pendingTools.parts.push(...msg.parts);
|
|
441
464
|
continue;
|
|
@@ -480,7 +503,7 @@ function messageDigest(messages, maxCharsPerMsg = 2000) {
|
|
|
480
503
|
};
|
|
481
504
|
});
|
|
482
505
|
}
|
|
483
|
-
export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messages: rawMessages, prePromptMessageCount, isHeartbeat, runtimeContext, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, diag, }) {
|
|
506
|
+
export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messages: rawMessages, prePromptMessageCount, isHeartbeat, tokenBudget, runtimeContext, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, diag, }) {
|
|
484
507
|
if (!cfg.autoCapture) {
|
|
485
508
|
return;
|
|
486
509
|
}
|
|
@@ -578,11 +601,14 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
|
|
|
578
601
|
}
|
|
579
602
|
const session = await client.getSession(ovSessionId, agentId);
|
|
580
603
|
const pendingTokens = session.pending_tokens ?? 0;
|
|
581
|
-
|
|
604
|
+
const commitTokenThreshold = Math.floor(tokenBudget * cfg.commitTokenThresholdRatio);
|
|
605
|
+
if (pendingTokens < commitTokenThreshold) {
|
|
582
606
|
diag("afterTurn_skip", ovSessionId, {
|
|
583
607
|
reason: "below_threshold",
|
|
584
608
|
pendingTokens,
|
|
585
|
-
commitTokenThreshold
|
|
609
|
+
commitTokenThreshold,
|
|
610
|
+
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
611
|
+
tokenBudget,
|
|
586
612
|
senderIdFound: sender.found,
|
|
587
613
|
senderId: sender.senderId ?? null,
|
|
588
614
|
});
|
|
@@ -598,7 +624,9 @@ export async function afterTurnOpenVikingSession({ sessionId, sessionKey, messag
|
|
|
598
624
|
`task_id=${commitResult.task_id ?? "none"}, trace_id=${commitResult.trace_id ?? "none"}`);
|
|
599
625
|
diag("afterTurn_commit", ovSessionId, {
|
|
600
626
|
pendingTokens,
|
|
601
|
-
commitTokenThreshold
|
|
627
|
+
commitTokenThreshold,
|
|
628
|
+
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
629
|
+
tokenBudget,
|
|
602
630
|
status: commitResult.status,
|
|
603
631
|
archived: commitResult.archived ?? false,
|
|
604
632
|
taskId: commitResult.task_id ?? null,
|
|
@@ -823,7 +851,7 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
823
851
|
}
|
|
824
852
|
catch (err) {
|
|
825
853
|
const errorMessage = String(err);
|
|
826
|
-
if (
|
|
854
|
+
if (isSessionNotFoundError(err)) {
|
|
827
855
|
logger.info(`openviking: compact skipped because OV session does not exist ` +
|
|
828
856
|
`(session=${ovSessionId}, agentId=${agentId})`);
|
|
829
857
|
diag("compact_result", ovSessionId, {
|
package/dist/text-utils.js
CHANGED
|
@@ -36,13 +36,8 @@ function looksLikeMetadataJsonBlock(content) {
|
|
|
36
36
|
}
|
|
37
37
|
return matchedKeys.size >= 3;
|
|
38
38
|
}
|
|
39
|
-
const HEARTBEAT_RE = /\bHEARTBEAT(?:\.md|_OK)\b/;
|
|
40
39
|
const TOOL_PLACEHOLDER_RE = /^\s*\[tool(?::\s*|Use:\s*)[^\]]+\]\s*$/i;
|
|
41
40
|
export function sanitizeUserTextForCapture(text) {
|
|
42
|
-
// 过滤 HEARTBEAT 健康检查消息
|
|
43
|
-
if (HEARTBEAT_RE.test(text)) {
|
|
44
|
-
return "";
|
|
45
|
-
}
|
|
46
41
|
// Drop legacy synthetic tool placeholders before they reach memory extraction.
|
|
47
42
|
if (TOOL_PLACEHOLDER_RE.test(text)) {
|
|
48
43
|
return "";
|
|
@@ -344,7 +339,7 @@ export function extractNewTurnMessages(messages, startIndex) {
|
|
|
344
339
|
: undefined);
|
|
345
340
|
if (output) {
|
|
346
341
|
result.push({
|
|
347
|
-
role: "
|
|
342
|
+
role: "assistant",
|
|
348
343
|
parts: [{
|
|
349
344
|
type: "tool",
|
|
350
345
|
toolCallId: toolCallId || undefined,
|
package/install-manifest.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"tool-call-id.ts",
|
|
21
21
|
"session-transcript-repair.ts",
|
|
22
22
|
"runtime-utils.ts",
|
|
23
|
+
"request-headers.ts",
|
|
23
24
|
"recall-trace.ts",
|
|
24
25
|
"query-config.ts",
|
|
25
26
|
"adapters/",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"pruneAfterBuild": true
|
|
53
54
|
},
|
|
54
55
|
"compatibility": {
|
|
55
|
-
"minOpenclawVersion": "2026.
|
|
56
|
+
"minOpenclawVersion": "2026.5.27",
|
|
56
57
|
"recommendedOpenclawVersion": "2026.6.6",
|
|
57
58
|
"minOpenvikingVersion": "0.4.1",
|
|
58
59
|
"recommendedOpenvikingVersion": "0.4.1"
|
package/openclaw.plugin.json
CHANGED
|
@@ -98,6 +98,11 @@
|
|
|
98
98
|
"placeholder": "${OPENVIKING_API_KEY}",
|
|
99
99
|
"help": "Optional API key for OpenViking server"
|
|
100
100
|
},
|
|
101
|
+
"headers": {
|
|
102
|
+
"label": "Headers",
|
|
103
|
+
"advanced": true,
|
|
104
|
+
"help": "Optional HTTP headers merged into every OpenViking request."
|
|
105
|
+
},
|
|
101
106
|
"accountId": {
|
|
102
107
|
"label": "Account ID",
|
|
103
108
|
"placeholder": "(derived from API key)",
|
|
@@ -196,11 +201,11 @@
|
|
|
196
201
|
"help": "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
|
|
197
202
|
"advanced": true
|
|
198
203
|
},
|
|
199
|
-
"
|
|
200
|
-
"label": "Commit Token Threshold",
|
|
201
|
-
"placeholder": "
|
|
204
|
+
"commitTokenThresholdRatio": {
|
|
205
|
+
"label": "Commit Token Threshold Ratio",
|
|
206
|
+
"placeholder": "0.5",
|
|
202
207
|
"advanced": true,
|
|
203
|
-
"help": "
|
|
208
|
+
"help": "Auto-commit triggers once estimated pending tokens reach this fraction (0-1) of the model context window (e.g. 0.5 = 50%). Set to 0 to commit every turn."
|
|
204
209
|
},
|
|
205
210
|
"commitKeepRecentCount": {
|
|
206
211
|
"label": "Commit Keep Recent Count",
|
|
@@ -287,6 +292,12 @@
|
|
|
287
292
|
"apiKey": {
|
|
288
293
|
"type": "string"
|
|
289
294
|
},
|
|
295
|
+
"headers": {
|
|
296
|
+
"type": "object",
|
|
297
|
+
"additionalProperties": {
|
|
298
|
+
"type": "string"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
290
301
|
"accountId": {
|
|
291
302
|
"type": "string"
|
|
292
303
|
},
|
|
@@ -358,6 +369,11 @@
|
|
|
358
369
|
"commitTokenThreshold": {
|
|
359
370
|
"type": "number"
|
|
360
371
|
},
|
|
372
|
+
"commitTokenThresholdRatio": {
|
|
373
|
+
"type": "number",
|
|
374
|
+
"minimum": 0,
|
|
375
|
+
"maximum": 1
|
|
376
|
+
},
|
|
361
377
|
"commitKeepRecentCount": {
|
|
362
378
|
"type": "number",
|
|
363
379
|
"minimum": 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.7.11",
|
|
4
4
|
"displayName": "OpenViking",
|
|
5
5
|
"description": "OpenClaw context-engine plugin — OpenViking AI memory, long-term memory, knowledge base, semantic search, RAG, context engine. 长期记忆 · 知识库 · 语义检索 · 上下文引擎",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"test:watch": "vitest"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
|
-
"openclaw": ">=2026.
|
|
50
|
+
"openclaw": ">=2026.5.27"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@sinclair/typebox": "0.34.48",
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
],
|
|
70
70
|
"setupEntry": "./dist/commands/setup.js",
|
|
71
71
|
"compat": {
|
|
72
|
-
"pluginApi": ">=2026.
|
|
73
|
-
"minGatewayVersion": "2026.
|
|
72
|
+
"pluginApi": ">=2026.5.27",
|
|
73
|
+
"minGatewayVersion": "2026.5.27"
|
|
74
74
|
},
|
|
75
75
|
"build": {
|
|
76
|
-
"openclawVersion": "2026.
|
|
77
|
-
"pluginSdkVersion": "2026.
|
|
76
|
+
"openclawVersion": "2026.5.27",
|
|
77
|
+
"pluginSdkVersion": "2026.5.27"
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OpenVikingClient } from "../client.js";
|
|
2
2
|
import type { HttpTransport } from "../adapters/http-transport.js";
|
|
3
|
+
import { resolveOpenVikingRequestHeaders } from "../request-headers.js";
|
|
3
4
|
|
|
4
5
|
type Logger = {
|
|
5
6
|
info: (message: string) => void;
|
|
@@ -13,6 +14,7 @@ type ClientRuntimeConfig = {
|
|
|
13
14
|
timeoutMs: number;
|
|
14
15
|
accountId?: string;
|
|
15
16
|
userId?: string;
|
|
17
|
+
headers?: Record<string, string>;
|
|
16
18
|
logFindRequests: boolean;
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -61,7 +63,12 @@ export function createOpenVikingClientRuntime(options: {
|
|
|
61
63
|
cfg.accountId,
|
|
62
64
|
cfg.userId,
|
|
63
65
|
routingDebugLog,
|
|
64
|
-
{
|
|
66
|
+
{
|
|
67
|
+
transport: options.transport,
|
|
68
|
+
headers: resolveOpenVikingRequestHeaders({
|
|
69
|
+
headers: cfg.headers,
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
65
72
|
),
|
|
66
73
|
);
|
|
67
74
|
|
|
@@ -71,7 +71,7 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
|
|
|
71
71
|
name: "memory_store",
|
|
72
72
|
label: "Memory Store (OpenViking)",
|
|
73
73
|
description:
|
|
74
|
-
"Store text in OpenViking memory pipeline by writing to a session and running memory extraction.",
|
|
74
|
+
"Store text in OpenViking memory pipeline by writing to a session and running memory extraction. Use when the user explicitly asks to remember, save, or store an important long-term fact, preference, project, or decision; automatic capture is threshold/commit dependent.",
|
|
75
75
|
parameters: Type.Object({
|
|
76
76
|
text: Type.String({ description: "Information to store as memory source text" }),
|
|
77
77
|
role: Type.Optional(Type.String({ description: "Session role, default user" })),
|
package/query-config.ts
CHANGED
|
@@ -231,7 +231,13 @@ export class RuntimeQueryConfigStore {
|
|
|
231
231
|
const { params, warnings } = normalizeRuntimeQueryParams(patch);
|
|
232
232
|
const key = this.keyForScope(scope, ctx);
|
|
233
233
|
const bucket = scope === "claw" ? this.data.claws : this.data.sessions;
|
|
234
|
-
bucket[key]
|
|
234
|
+
const previous = bucket[key];
|
|
235
|
+
bucket[key] = {
|
|
236
|
+
params: { ...(previous?.params ?? {}), ...params },
|
|
237
|
+
updatedAt: Date.now(),
|
|
238
|
+
updatedBy: "command",
|
|
239
|
+
peerId: ctx.peerId,
|
|
240
|
+
};
|
|
235
241
|
this.data.updatedAt = Date.now();
|
|
236
242
|
await this.persist();
|
|
237
243
|
return { warnings };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type OpenVikingRequestHeaders = Record<string, string>;
|
|
2
|
+
|
|
3
|
+
export type ResolveOpenVikingRequestHeadersOptions = {
|
|
4
|
+
headers?: unknown;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function resolveOpenVikingRequestHeaders(
|
|
8
|
+
options: ResolveOpenVikingRequestHeadersOptions = {},
|
|
9
|
+
): OpenVikingRequestHeaders {
|
|
10
|
+
return cleanOpenVikingRequestHeaders(options.headers);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function cleanOpenVikingRequestHeaders(headers: unknown): OpenVikingRequestHeaders {
|
|
14
|
+
if (headers === undefined || headers === null) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
if (typeof headers !== "object" || Array.isArray(headers)) {
|
|
18
|
+
throw new Error("openviking request headers must be an object");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const out: OpenVikingRequestHeaders = {};
|
|
22
|
+
for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
|
|
23
|
+
if (typeof value !== "string") {
|
|
24
|
+
throw new Error(`openviking request header ${key} must be a string`);
|
|
25
|
+
}
|
|
26
|
+
out[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
@@ -123,10 +123,12 @@ export type AfterTurnOpenVikingSessionParams = {
|
|
|
123
123
|
messages?: AgentMessage[];
|
|
124
124
|
prePromptMessageCount?: number;
|
|
125
125
|
isHeartbeat?: boolean;
|
|
126
|
+
/** Model context window in tokens; the auto-commit threshold is a fraction of this. */
|
|
127
|
+
tokenBudget: number;
|
|
126
128
|
runtimeContext?: Record<string, unknown>;
|
|
127
129
|
cfg: {
|
|
128
130
|
autoCapture: boolean;
|
|
129
|
-
|
|
131
|
+
commitTokenThresholdRatio: number;
|
|
130
132
|
commitKeepRecentCount: number;
|
|
131
133
|
logFindRequests: boolean;
|
|
132
134
|
};
|
|
@@ -404,6 +406,11 @@ function assemblePassthrough(
|
|
|
404
406
|
return { messages: liveMessages, estimatedTokens: originalTokens };
|
|
405
407
|
}
|
|
406
408
|
|
|
409
|
+
function isSessionNotFoundError(err: unknown): boolean {
|
|
410
|
+
const errorMessage = String(err);
|
|
411
|
+
return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
|
|
412
|
+
}
|
|
413
|
+
|
|
407
414
|
export async function assembleOpenVikingSession({
|
|
408
415
|
sessionId,
|
|
409
416
|
sessionKey,
|
|
@@ -625,6 +632,27 @@ export async function assembleOpenVikingSession({
|
|
|
625
632
|
...(instruction.text ? { systemPromptAddition: instruction.text } : {}),
|
|
626
633
|
};
|
|
627
634
|
} catch (err) {
|
|
635
|
+
if (isSessionNotFoundError(err)) {
|
|
636
|
+
const errorMessage = String(err);
|
|
637
|
+
logger.info(
|
|
638
|
+
`openviking: assemble skipped because OV session does not exist ` +
|
|
639
|
+
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`,
|
|
640
|
+
);
|
|
641
|
+
return assemblePassthrough({
|
|
642
|
+
diag,
|
|
643
|
+
ovSessionId,
|
|
644
|
+
reason: "session_not_found",
|
|
645
|
+
liveMessages: messages,
|
|
646
|
+
originalTokens,
|
|
647
|
+
extra: {
|
|
648
|
+
error: errorMessage,
|
|
649
|
+
tokenBudget,
|
|
650
|
+
agentId: resolveAgentId(ovSessionId),
|
|
651
|
+
senderIdFound: sender.found,
|
|
652
|
+
senderId: sender.senderId ?? null,
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
}
|
|
628
656
|
logger.warn?.(
|
|
629
657
|
`openviking: assemble failed for session=${ovSessionId}, ` +
|
|
630
658
|
`tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)}: ${String(err)}`,
|
|
@@ -688,7 +716,7 @@ function extractRuntimeAgentId(runtimeContext: Record<string, unknown> | undefin
|
|
|
688
716
|
}
|
|
689
717
|
|
|
690
718
|
function isToolOnlyMessage(msg: ExtractedTurnMessage): boolean {
|
|
691
|
-
return msg.role === "
|
|
719
|
+
return msg.role === "assistant" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
|
|
692
720
|
}
|
|
693
721
|
|
|
694
722
|
function coalesceConsecutiveToolMessages(messages: ExtractedTurnMessage[]): ExtractedTurnMessage[] {
|
|
@@ -705,7 +733,7 @@ function coalesceConsecutiveToolMessages(messages: ExtractedTurnMessage[]): Extr
|
|
|
705
733
|
for (const msg of messages) {
|
|
706
734
|
if (isToolOnlyMessage(msg)) {
|
|
707
735
|
if (!pendingTools) {
|
|
708
|
-
pendingTools = { role: "
|
|
736
|
+
pendingTools = { role: "assistant", parts: [] };
|
|
709
737
|
}
|
|
710
738
|
pendingTools.parts.push(...msg.parts);
|
|
711
739
|
continue;
|
|
@@ -753,6 +781,7 @@ export async function afterTurnOpenVikingSession({
|
|
|
753
781
|
messages: rawMessages,
|
|
754
782
|
prePromptMessageCount,
|
|
755
783
|
isHeartbeat,
|
|
784
|
+
tokenBudget,
|
|
756
785
|
runtimeContext,
|
|
757
786
|
cfg,
|
|
758
787
|
getClient,
|
|
@@ -880,11 +909,15 @@ export async function afterTurnOpenVikingSession({
|
|
|
880
909
|
const session = await client.getSession(ovSessionId, agentId);
|
|
881
910
|
const pendingTokens = session.pending_tokens ?? 0;
|
|
882
911
|
|
|
883
|
-
|
|
912
|
+
const commitTokenThreshold = Math.floor(tokenBudget * cfg.commitTokenThresholdRatio);
|
|
913
|
+
|
|
914
|
+
if (pendingTokens < commitTokenThreshold) {
|
|
884
915
|
diag("afterTurn_skip", ovSessionId, {
|
|
885
916
|
reason: "below_threshold",
|
|
886
917
|
pendingTokens,
|
|
887
|
-
commitTokenThreshold
|
|
918
|
+
commitTokenThreshold,
|
|
919
|
+
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
920
|
+
tokenBudget,
|
|
888
921
|
senderIdFound: sender.found,
|
|
889
922
|
senderId: sender.senderId ?? null,
|
|
890
923
|
});
|
|
@@ -904,7 +937,9 @@ export async function afterTurnOpenVikingSession({
|
|
|
904
937
|
|
|
905
938
|
diag("afterTurn_commit", ovSessionId, {
|
|
906
939
|
pendingTokens,
|
|
907
|
-
commitTokenThreshold
|
|
940
|
+
commitTokenThreshold,
|
|
941
|
+
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
942
|
+
tokenBudget,
|
|
908
943
|
status: commitResult.status,
|
|
909
944
|
archived: commitResult.archived ?? false,
|
|
910
945
|
taskId: commitResult.task_id ?? null,
|
|
@@ -1183,7 +1218,7 @@ export async function compactOpenVikingSession({
|
|
|
1183
1218
|
};
|
|
1184
1219
|
} catch (err) {
|
|
1185
1220
|
const errorMessage = String(err);
|
|
1186
|
-
if (
|
|
1221
|
+
if (isSessionNotFoundError(err)) {
|
|
1187
1222
|
logger.info(
|
|
1188
1223
|
`openviking: compact skipped because OV session does not exist ` +
|
|
1189
1224
|
`(session=${ovSessionId}, agentId=${agentId})`,
|