@openviking/openclaw-plugin 2026.10.6 → 2026.10.7-1
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/README.md +3 -1
- package/client.ts +7 -2
- package/commands/setup.ts +1 -0
- package/config.ts +6 -0
- package/context-engine.ts +3 -2
- package/dist/client.js +5 -2
- package/dist/commands/setup.js +1 -0
- package/dist/config.js +5 -0
- package/dist/context-engine.js +2 -1
- package/dist/plugin/openviking-memory-tools.js +24 -6
- package/dist/services/context-lifecycle-service.js +81 -21
- package/install-manifest.json +1 -1
- package/openclaw.plugin.json +6 -0
- package/package.json +1 -1
- package/plugin/openviking-memory-tools.ts +26 -7
- package/services/context-lifecycle-service.ts +105 -22
- package/skills/ov-experience-memory/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# OpenViking for OpenClaw
|
|
2
2
|
|
|
3
|
+
> **Working memory is now opt-in.** Update installed plugins separately from the OV server, then restart the host. Existing explicit settings still take precedence. See the [default-off upgrade guide](../../docs/en/guides/working-memory-default-off.md) for native history, re-enabling WM, and old-conversation handoffs.
|
|
4
|
+
|
|
3
5
|
Use [OpenViking](https://github.com/volcengine/OpenViking) as OpenClaw's long-term context engine: automatic recall, session archive, memory extraction, semantic search, and RAG over a remote OpenViking server.
|
|
4
6
|
|
|
5
7
|
## Quick Start
|
|
@@ -243,7 +245,7 @@ Use it as a complement to auto-capture, not a replacement:
|
|
|
243
245
|
|
|
244
246
|
- auto-capture still preserves ordinary conversation flow and batches extraction for cost and latency
|
|
245
247
|
- `memory_store` is for explicit durable-memory intent such as "remember my main project is X" or "save this preference"
|
|
246
|
-
- if `memory_store` commits but extracts 0 memories,
|
|
248
|
+
- if `memory_store` commits but extracts 0 memories, it returns `action: "failed"` with `error: "no_memories_extracted"` and the agent tells the user no new memory was created; possible causes are that the content is already stored, extraction is disabled, or the extraction/model configuration needs checking
|
|
247
249
|
|
|
248
250
|
### What `compact()` does
|
|
249
251
|
|
package/client.ts
CHANGED
|
@@ -75,6 +75,7 @@ export type OpenVikingClientOptions = {
|
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
export type CommitSessionResult = {
|
|
78
|
+
effective_enable_working_memory?: boolean;
|
|
78
79
|
session_id: string;
|
|
79
80
|
/** "accepted" (async), "skipped" (no archive), "completed", "failed", or "timeout" (wait mode). */
|
|
80
81
|
status: string;
|
|
@@ -553,11 +554,11 @@ export class OpenVikingClient {
|
|
|
553
554
|
};
|
|
554
555
|
}
|
|
555
556
|
|
|
556
|
-
async read(uri: string, actorPeerId?: string): Promise<string> {
|
|
557
|
+
async read(uri: string, actorPeerId?: string, requestTimeoutMs?: number): Promise<string> {
|
|
557
558
|
return this.request<string>(
|
|
558
559
|
`/api/v1/content/read?uri=${encodeURIComponent(uri)}`,
|
|
559
560
|
{},
|
|
560
|
-
|
|
561
|
+
requestTimeoutMs,
|
|
561
562
|
actorPeerId,
|
|
562
563
|
);
|
|
563
564
|
}
|
|
@@ -841,6 +842,7 @@ export class OpenVikingClient {
|
|
|
841
842
|
sessionId: string,
|
|
842
843
|
options?: {
|
|
843
844
|
wait?: boolean;
|
|
845
|
+
enableWorkingMemory?: boolean;
|
|
844
846
|
timeoutMs?: number;
|
|
845
847
|
/**
|
|
846
848
|
* WM v2: number of most-recent messages to keep live after commit.
|
|
@@ -871,6 +873,9 @@ export class OpenVikingClient {
|
|
|
871
873
|
options?.agentId,
|
|
872
874
|
);
|
|
873
875
|
const body: Record<string, unknown> = {};
|
|
876
|
+
if (options?.enableWorkingMemory !== undefined) {
|
|
877
|
+
body.enable_working_memory = options.enableWorkingMemory;
|
|
878
|
+
}
|
|
874
879
|
if (options?.retentionMode === "turn_budget") {
|
|
875
880
|
body.retention_mode = "turn_budget";
|
|
876
881
|
} else if (keepRecentCount > 0) {
|
package/commands/setup.ts
CHANGED
|
@@ -66,6 +66,7 @@ function readCompatRangeFromManifest(): { min: string; max: string } {
|
|
|
66
66
|
const PLUGIN_VERSION = readPluginVersion();
|
|
67
67
|
const { min: COMPATIBLE_SERVER_MIN, max: COMPATIBLE_SERVER_MAX } = readCompatRangeFromManifest();
|
|
68
68
|
const CONFIG_KEYS_TO_PRESERVE = [
|
|
69
|
+
"contextManagementMode",
|
|
69
70
|
"targetUri",
|
|
70
71
|
"headers",
|
|
71
72
|
"timeoutMs",
|
package/config.ts
CHANGED
|
@@ -42,6 +42,7 @@ export type MemoryOpenVikingConfig = {
|
|
|
42
42
|
userId?: string;
|
|
43
43
|
targetUri?: string;
|
|
44
44
|
timeoutMs?: number;
|
|
45
|
+
contextManagementMode?: "native" | "openviking";
|
|
45
46
|
autoCapture?: boolean;
|
|
46
47
|
captureMode?: "semantic" | "keyword";
|
|
47
48
|
captureMaxLength?: number;
|
|
@@ -514,6 +515,9 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
514
515
|
value = {};
|
|
515
516
|
}
|
|
516
517
|
const cfg = value as Record<string, unknown>;
|
|
518
|
+
if (cfg.contextManagementMode !== undefined && !["native", "openviking"].includes(String(cfg.contextManagementMode))) {
|
|
519
|
+
throw new Error("contextManagementMode must be native or openviking");
|
|
520
|
+
}
|
|
517
521
|
assertAllowedKeys(
|
|
518
522
|
cfg,
|
|
519
523
|
[
|
|
@@ -528,6 +532,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
528
532
|
"userId",
|
|
529
533
|
"targetUri",
|
|
530
534
|
"timeoutMs",
|
|
535
|
+
"contextManagementMode",
|
|
531
536
|
"autoCapture",
|
|
532
537
|
"captureMode",
|
|
533
538
|
"captureMaxLength",
|
|
@@ -655,6 +660,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
655
660
|
userId,
|
|
656
661
|
targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
|
|
657
662
|
timeoutMs: Math.max(1000, Math.floor(toNumber(cfg.timeoutMs, DEFAULT_TIMEOUT_MS))),
|
|
663
|
+
contextManagementMode: cfg.contextManagementMode === "openviking" ? "openviking" : "native",
|
|
658
664
|
autoCapture: cfg.autoCapture !== false,
|
|
659
665
|
captureMode: captureMode ?? DEFAULT_CAPTURE_MODE,
|
|
660
666
|
captureMaxLength: Math.max(
|
package/context-engine.ts
CHANGED
|
@@ -25,7 +25,7 @@ type ContextEngineInfo = {
|
|
|
25
25
|
id: string;
|
|
26
26
|
name: string;
|
|
27
27
|
version?: string;
|
|
28
|
-
ownsCompaction:
|
|
28
|
+
ownsCompaction: boolean;
|
|
29
29
|
/** OpenClaw >=2026.8.1: without both declarations (and commitTurn) the engine is degraded to "legacy" every turn. */
|
|
30
30
|
transcriptSemantics: {
|
|
31
31
|
currentTurnFence: "before-current-turn-entry-v1";
|
|
@@ -365,7 +365,7 @@ export function createMemoryOpenVikingContextEngine(params: {
|
|
|
365
365
|
id,
|
|
366
366
|
name,
|
|
367
367
|
version,
|
|
368
|
-
ownsCompaction:
|
|
368
|
+
ownsCompaction: cfg.contextManagementMode === "openviking",
|
|
369
369
|
transcriptSemantics: {
|
|
370
370
|
currentTurnFence: "before-current-turn-entry-v1",
|
|
371
371
|
turnAdvancementIdempotency: "atomic-idempotent-v1",
|
|
@@ -490,6 +490,7 @@ export function createMemoryOpenVikingContextEngine(params: {
|
|
|
490
490
|
async compact(compactParams): Promise<CompactResult> {
|
|
491
491
|
const tokenBudget = validTokenBudget(compactParams.tokenBudget) ?? 128_000;
|
|
492
492
|
return compactOpenVikingSession({
|
|
493
|
+
contextManagementMode: cfg.contextManagementMode,
|
|
493
494
|
sessionId: compactParams.sessionId,
|
|
494
495
|
sessionKey: resolveSessionKey(compactParams),
|
|
495
496
|
tokenBudget,
|
package/dist/client.js
CHANGED
|
@@ -244,8 +244,8 @@ export class OpenVikingClient {
|
|
|
244
244
|
: result.entries,
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
-
async read(uri, actorPeerId) {
|
|
248
|
-
return this.request(`/api/v1/content/read?uri=${encodeURIComponent(uri)}`, {},
|
|
247
|
+
async read(uri, actorPeerId, requestTimeoutMs) {
|
|
248
|
+
return this.request(`/api/v1/content/read?uri=${encodeURIComponent(uri)}`, {}, requestTimeoutMs, actorPeerId);
|
|
249
249
|
}
|
|
250
250
|
async list(uri, options) {
|
|
251
251
|
const normalizedUri = uri.trim().replace(/\/+$/, "");
|
|
@@ -414,6 +414,9 @@ export class OpenVikingClient {
|
|
|
414
414
|
retentionMode: options?.retentionMode,
|
|
415
415
|
}, options?.agentId);
|
|
416
416
|
const body = {};
|
|
417
|
+
if (options?.enableWorkingMemory !== undefined) {
|
|
418
|
+
body.enable_working_memory = options.enableWorkingMemory;
|
|
419
|
+
}
|
|
417
420
|
if (options?.retentionMode === "turn_budget") {
|
|
418
421
|
body.retention_mode = "turn_budget";
|
|
419
422
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -56,6 +56,7 @@ function readCompatRangeFromManifest() {
|
|
|
56
56
|
const PLUGIN_VERSION = readPluginVersion();
|
|
57
57
|
const { min: COMPATIBLE_SERVER_MIN, max: COMPATIBLE_SERVER_MAX } = readCompatRangeFromManifest();
|
|
58
58
|
const CONFIG_KEYS_TO_PRESERVE = [
|
|
59
|
+
"contextManagementMode",
|
|
59
60
|
"targetUri",
|
|
60
61
|
"headers",
|
|
61
62
|
"timeoutMs",
|
package/dist/config.js
CHANGED
|
@@ -328,6 +328,9 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
328
328
|
value = {};
|
|
329
329
|
}
|
|
330
330
|
const cfg = value;
|
|
331
|
+
if (cfg.contextManagementMode !== undefined && !["native", "openviking"].includes(String(cfg.contextManagementMode))) {
|
|
332
|
+
throw new Error("contextManagementMode must be native or openviking");
|
|
333
|
+
}
|
|
331
334
|
assertAllowedKeys(cfg, [
|
|
332
335
|
"mode",
|
|
333
336
|
"baseUrl",
|
|
@@ -340,6 +343,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
340
343
|
"userId",
|
|
341
344
|
"targetUri",
|
|
342
345
|
"timeoutMs",
|
|
346
|
+
"contextManagementMode",
|
|
343
347
|
"autoCapture",
|
|
344
348
|
"captureMode",
|
|
345
349
|
"captureMaxLength",
|
|
@@ -436,6 +440,7 @@ export const memoryOpenVikingConfigSchema = {
|
|
|
436
440
|
userId,
|
|
437
441
|
targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
|
|
438
442
|
timeoutMs: Math.max(1000, Math.floor(toNumber(cfg.timeoutMs, DEFAULT_TIMEOUT_MS))),
|
|
443
|
+
contextManagementMode: cfg.contextManagementMode === "openviking" ? "openviking" : "native",
|
|
439
444
|
autoCapture: cfg.autoCapture !== false,
|
|
440
445
|
captureMode: captureMode ?? DEFAULT_CAPTURE_MODE,
|
|
441
446
|
captureMaxLength: Math.max(200, Math.min(200_000, Math.floor(toNumber(cfg.captureMaxLength, DEFAULT_CAPTURE_MAX_LENGTH)))),
|
package/dist/context-engine.js
CHANGED
|
@@ -191,7 +191,7 @@ export function createMemoryOpenVikingContextEngine(params) {
|
|
|
191
191
|
id,
|
|
192
192
|
name,
|
|
193
193
|
version,
|
|
194
|
-
ownsCompaction:
|
|
194
|
+
ownsCompaction: cfg.contextManagementMode === "openviking",
|
|
195
195
|
transcriptSemantics: {
|
|
196
196
|
currentTurnFence: "before-current-turn-entry-v1",
|
|
197
197
|
turnAdvancementIdempotency: "atomic-idempotent-v1",
|
|
@@ -308,6 +308,7 @@ export function createMemoryOpenVikingContextEngine(params) {
|
|
|
308
308
|
async compact(compactParams) {
|
|
309
309
|
const tokenBudget = validTokenBudget(compactParams.tokenBudget) ?? 128_000;
|
|
310
310
|
return compactOpenVikingSession({
|
|
311
|
+
contextManagementMode: cfg.contextManagementMode,
|
|
311
312
|
sessionId: compactParams.sessionId,
|
|
312
313
|
sessionKey: resolveSessionKey(compactParams),
|
|
313
314
|
tokenBudget,
|
|
@@ -95,13 +95,31 @@ export function registerOpenVikingMemoryTools(deps) {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
if (memoriesCount === 0) {
|
|
98
|
-
deps.logger.warn(`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId})
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
deps.logger.warn(`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId}), ` +
|
|
99
|
+
`trace_id=${commitResult.trace_id ?? "none"}. ` +
|
|
100
|
+
"No memory was created or updated. Check memory.extraction_enabled, VLM configuration/API keys, or whether the content is already stored.");
|
|
101
|
+
return {
|
|
102
|
+
content: [{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Memory extraction completed for session ${sessionId}, but created or updated no memory. ` +
|
|
105
|
+
"The content may already be stored, memory extraction may be disabled, or the extractor found nothing new to store. " +
|
|
106
|
+
"Tell the user no new memory was created." +
|
|
107
|
+
(commitResult.trace_id ? ` (trace_id=${commitResult.trace_id})` : ""),
|
|
108
|
+
}],
|
|
109
|
+
details: {
|
|
110
|
+
action: "failed",
|
|
111
|
+
sessionId,
|
|
112
|
+
status: commitResult.status,
|
|
113
|
+
error: "no_memories_extracted",
|
|
114
|
+
memoriesCount,
|
|
115
|
+
archived: commitResult.archived ?? false,
|
|
116
|
+
traceId: commitResult.trace_id,
|
|
117
|
+
usedTempSession,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
104
120
|
}
|
|
121
|
+
deps.logger.info?.(`openviking: memory_store committed, memories=${memoriesCount}, ` +
|
|
122
|
+
`trace_id=${commitResult.trace_id ?? "none"}`);
|
|
105
123
|
return {
|
|
106
124
|
content: [
|
|
107
125
|
{
|
|
@@ -196,11 +196,12 @@ function isSessionNotFoundError(err) {
|
|
|
196
196
|
const errorMessage = String(err);
|
|
197
197
|
return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const PROFILE_MAX_CHARS = 8000;
|
|
200
|
+
const PROFILE_READ_TIMEOUT_MS = 3000;
|
|
201
|
+
function resolveAssembleRouting(params) {
|
|
202
|
+
const { sessionId, sessionKey, cfg, resolveAgentId, logger } = params;
|
|
201
203
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
202
204
|
const sender = extractRuntimeSenderId(params.runtimeContext);
|
|
203
|
-
const client = await getClient();
|
|
204
205
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
205
206
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
206
207
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
@@ -209,6 +210,34 @@ async function recallForAssemble(params, recallQuery) {
|
|
|
209
210
|
assistantPeerId: agentId,
|
|
210
211
|
warn: (message) => logger.warn?.(message),
|
|
211
212
|
});
|
|
213
|
+
return { ovSessionId, agentId, actorPeerId };
|
|
214
|
+
}
|
|
215
|
+
// Server-side context assembly leaves profile.md out on the assumption that the
|
|
216
|
+
// client injects it, so read it here: the user's profile and, with peer scope, the actor's.
|
|
217
|
+
async function readProfileBlock(client, actorPeerId, timeoutMs, logger) {
|
|
218
|
+
const uris = ["viking://~/memories/profile.md"];
|
|
219
|
+
if (actorPeerId)
|
|
220
|
+
uris.push(`viking://~/peers/${actorPeerId}/memories/profile.md`);
|
|
221
|
+
const reads = await Promise.allSettled(uris.map((uri) => client.read(uri, actorPeerId, timeoutMs)));
|
|
222
|
+
return reads
|
|
223
|
+
.map((read, i) => {
|
|
224
|
+
if (read.status === "rejected" && !String(read.reason).includes("[NOT_FOUND]")) {
|
|
225
|
+
logger.warn?.(`openviking: profile read failed (uri=${uris[i]}): ${String(read.reason)}`);
|
|
226
|
+
}
|
|
227
|
+
const text = read.status === "fulfilled" && typeof read.value === "string" ? read.value.trim() : "";
|
|
228
|
+
if (!text)
|
|
229
|
+
return "";
|
|
230
|
+
const capped = text.length > PROFILE_MAX_CHARS
|
|
231
|
+
? `${text.slice(0, PROFILE_MAX_CHARS).trimEnd()}\n... [profile truncated]`
|
|
232
|
+
: text;
|
|
233
|
+
return `<user-profile uri="${uris[i]}">\n${capped}\n</user-profile>`;
|
|
234
|
+
})
|
|
235
|
+
.filter(Boolean)
|
|
236
|
+
.join("\n\n");
|
|
237
|
+
}
|
|
238
|
+
async function recallForAssemble(params, recallQuery, client, routing) {
|
|
239
|
+
const { sessionId, sessionKey, cfg, queryConfigStore, logger, traceRecorder } = params;
|
|
240
|
+
const { ovSessionId, agentId, actorPeerId } = routing;
|
|
212
241
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
213
242
|
agentId,
|
|
214
243
|
sessionId,
|
|
@@ -238,24 +267,32 @@ export async function assembleOpenVikingSession(params) {
|
|
|
238
267
|
const assembled = await assembleSessionContext(params);
|
|
239
268
|
// Current OpenClaw supplies the pending turn separately from history. Keep
|
|
240
269
|
// recalled context out of persisted messages and let the host own that turn.
|
|
241
|
-
if (!params.isMainAssemble || !params.cfg.autoRecall ||
|
|
242
|
-
params.isBypassedSession(params)) {
|
|
270
|
+
if (!params.isMainAssemble || !params.cfg.autoRecall || params.isBypassedSession(params)) {
|
|
243
271
|
return assembled;
|
|
244
272
|
}
|
|
245
|
-
const query = prepareRecallQuery(params.prompt);
|
|
246
|
-
if (query.query.length < 5)
|
|
247
|
-
return assembled;
|
|
273
|
+
const query = prepareRecallQuery(params.prompt ?? "");
|
|
248
274
|
try {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
275
|
+
const routing = resolveAssembleRouting(params);
|
|
276
|
+
const client = await params.getClient();
|
|
277
|
+
const [profileBlock, recallBlock] = await Promise.all([
|
|
278
|
+
readProfileBlock(client, routing.actorPeerId, Math.min(params.cfg.autoRecallTimeoutMs ?? PROFILE_READ_TIMEOUT_MS, PROFILE_READ_TIMEOUT_MS), params.logger),
|
|
279
|
+
query.query.length < 5 ? "" : recallForAssemble(params, query, client, routing)
|
|
280
|
+
.then((recall) => recall.block)
|
|
281
|
+
.catch((err) => {
|
|
282
|
+
params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
|
|
283
|
+
return "";
|
|
284
|
+
}),
|
|
285
|
+
]);
|
|
286
|
+
const baseTokens = estimateTextTokens(assembled.systemPromptAddition ?? "");
|
|
287
|
+
for (const blocks of [[profileBlock, recallBlock], [profileBlock], [recallBlock]]) {
|
|
288
|
+
if (!blocks.some(Boolean))
|
|
289
|
+
continue;
|
|
290
|
+
const systemPromptAddition = [assembled.systemPromptAddition, ...blocks].filter(Boolean).join("\n\n");
|
|
291
|
+
const estimatedTokens = assembled.estimatedTokens + estimateTextTokens(systemPromptAddition) - baseTokens;
|
|
292
|
+
if (estimatedTokens <= params.tokenBudget)
|
|
293
|
+
return { ...assembled, systemPromptAddition, estimatedTokens };
|
|
294
|
+
}
|
|
295
|
+
return assembled;
|
|
259
296
|
}
|
|
260
297
|
catch (err) {
|
|
261
298
|
params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
|
|
@@ -307,7 +344,7 @@ async function assembleSessionContext(params) {
|
|
|
307
344
|
`chars=${recallQuery.originalChars}->${recallQuery.finalChars})`);
|
|
308
345
|
}
|
|
309
346
|
try {
|
|
310
|
-
const recall = await recallForAssemble(params, recallQuery);
|
|
347
|
+
const recall = await recallForAssemble(params, recallQuery, await getClient(), resolveAssembleRouting(params));
|
|
311
348
|
if (!recall.block) {
|
|
312
349
|
return assemblePassthrough({
|
|
313
350
|
diag,
|
|
@@ -344,12 +381,23 @@ async function assembleSessionContext(params) {
|
|
|
344
381
|
});
|
|
345
382
|
}
|
|
346
383
|
}
|
|
384
|
+
if (cfg.contextManagementMode !== "openviking") {
|
|
385
|
+
return assemblePassthrough({ diag, ovSessionId, reason: "native_context", liveMessages: messages, originalTokens });
|
|
386
|
+
}
|
|
347
387
|
try {
|
|
348
388
|
const client = await getClient();
|
|
349
389
|
const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
|
|
350
390
|
const preAbstracts = ctx?.pre_archive_abstracts ?? [];
|
|
351
391
|
const hasArchives = !!ctx?.latest_archive_overview || preAbstracts.length > 0;
|
|
352
392
|
const activeCount = ctx?.messages?.length ?? 0;
|
|
393
|
+
// A completed WM-off archive can leave a non-empty active tail. That tail
|
|
394
|
+
// alone cannot replace the host's earlier history in explicit OV mode.
|
|
395
|
+
if (!hasArchives && (ctx?.stats?.totalArchives ?? 0) > 0) {
|
|
396
|
+
logger.warn?.("openviking: archived context has no summary; keeping native history");
|
|
397
|
+
return assemblePassthrough({
|
|
398
|
+
diag, ovSessionId, reason: "archive_summary_missing", liveMessages: messages, originalTokens,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
353
401
|
if (!ctx || (!hasArchives && activeCount === 0)) {
|
|
354
402
|
return assemblePassthrough({
|
|
355
403
|
diag,
|
|
@@ -411,7 +459,7 @@ async function assembleSessionContext(params) {
|
|
|
411
459
|
catch (err) {
|
|
412
460
|
if (isSessionNotFoundError(err)) {
|
|
413
461
|
const errorMessage = String(err);
|
|
414
|
-
logger.info(`openviking:
|
|
462
|
+
logger.info(`openviking: session history not assembled because OV session does not exist ` +
|
|
415
463
|
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`);
|
|
416
464
|
return assemblePassthrough({
|
|
417
465
|
diag,
|
|
@@ -658,6 +706,7 @@ export async function afterTurnOpenVikingSession({ throwOnError = false, session
|
|
|
658
706
|
}
|
|
659
707
|
const commitResult = await client.commitSession(ovSessionId, {
|
|
660
708
|
wait: false,
|
|
709
|
+
...(cfg.contextManagementMode === "openviking" ? { enableWorkingMemory: true } : {}),
|
|
661
710
|
...(cfg.commitRetentionMode === "turn_budget"
|
|
662
711
|
? { retentionMode: "turn_budget" }
|
|
663
712
|
: { keepRecentCount: cfg.commitKeepRecentCount }),
|
|
@@ -666,6 +715,7 @@ export async function afterTurnOpenVikingSession({ throwOnError = false, session
|
|
|
666
715
|
`status=${commitResult.status}, archived=${commitResult.archived ?? false}, ` +
|
|
667
716
|
`task_id=${commitResult.task_id ?? "none"}, trace_id=${commitResult.trace_id ?? "none"}`);
|
|
668
717
|
diag("afterTurn_commit", ovSessionId, {
|
|
718
|
+
effectiveEnableWorkingMemory: commitResult.effective_enable_working_memory,
|
|
669
719
|
pendingTokens,
|
|
670
720
|
commitTokenThreshold,
|
|
671
721
|
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
@@ -715,7 +765,7 @@ function compactFailureResult(reason, tokensBefore, details) {
|
|
|
715
765
|
},
|
|
716
766
|
};
|
|
717
767
|
}
|
|
718
|
-
export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, runtimeCompact, diag, }) {
|
|
768
|
+
export async function compactOpenVikingSession({ contextManagementMode = "native", sessionId, sessionKey, tokenBudget, currentTokenCount, force, compactionTarget, customInstructions, getClient, logger, resolveAgentId, isBypassedSession, runtimeCompact, diag, }) {
|
|
719
769
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
720
770
|
diag("compact_entry", ovSessionId, {
|
|
721
771
|
tokenBudget,
|
|
@@ -749,6 +799,9 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
749
799
|
reason: "session_bypassed",
|
|
750
800
|
};
|
|
751
801
|
}
|
|
802
|
+
if (contextManagementMode === "native") {
|
|
803
|
+
return await runtimeCompact?.() ?? compactFailureResult("native_compaction_unavailable", validTokenCount(currentTokenCount) ?? -1, { message: "Upgrade OpenClaw to a version with runtime compaction delegation." });
|
|
804
|
+
}
|
|
752
805
|
const client = await getClient();
|
|
753
806
|
const agentId = resolveAgentId(sessionId, sessionKey, ovSessionId);
|
|
754
807
|
const tokensBeforeOriginal = validTokenCount(currentTokenCount);
|
|
@@ -771,6 +824,7 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
771
824
|
const commitResult = await client.commitSession(ovSessionId, {
|
|
772
825
|
wait: true,
|
|
773
826
|
keepRecentCount: 0,
|
|
827
|
+
enableWorkingMemory: true,
|
|
774
828
|
});
|
|
775
829
|
const memCount = totalExtractedMemories(commitResult.memories_extracted);
|
|
776
830
|
if (commitResult.status === "failed") {
|
|
@@ -828,6 +882,9 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
828
882
|
},
|
|
829
883
|
};
|
|
830
884
|
}
|
|
885
|
+
if (commitResult.effective_enable_working_memory !== true) {
|
|
886
|
+
return await runtimeCompact?.() ?? compactFailureResult("working_memory_disabled", tokensBefore, { commit: commitResult });
|
|
887
|
+
}
|
|
831
888
|
let summary = "";
|
|
832
889
|
const firstKeptEntryId = commitResult.archive_uri?.split("/").pop() ?? "";
|
|
833
890
|
let tokensAfter;
|
|
@@ -875,6 +932,9 @@ export async function compactOpenVikingSession({ sessionId, sessionKey, tokenBud
|
|
|
875
932
|
logger.info(`openviking: compact tokens session=${ovSessionId}, ` +
|
|
876
933
|
`tokensBefore=${tokensBefore}, tokensAfter=${tokensAfter ?? "unknown"}, ` +
|
|
877
934
|
`latestArchiveId=${firstKeptEntryId || "none"}`);
|
|
935
|
+
if (!summary) {
|
|
936
|
+
return await runtimeCompact?.() ?? compactFailureResult("archive_summary_missing", tokensBefore, { commit: commitResult });
|
|
937
|
+
}
|
|
878
938
|
diag("compact_result", ovSessionId, {
|
|
879
939
|
ok: true,
|
|
880
940
|
compacted: true,
|
package/install-manifest.json
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -353,6 +353,12 @@
|
|
|
353
353
|
"timeoutMs": {
|
|
354
354
|
"type": "number"
|
|
355
355
|
},
|
|
356
|
+
"contextManagementMode": {
|
|
357
|
+
"type": "string",
|
|
358
|
+
"enum": ["native", "openviking"],
|
|
359
|
+
"default": "native",
|
|
360
|
+
"description": "Use host history and compaction; openviking explicitly enables Working Memory context management."
|
|
361
|
+
},
|
|
356
362
|
"autoCapture": {
|
|
357
363
|
"type": "boolean"
|
|
358
364
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.7-1",
|
|
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",
|
|
@@ -176,15 +176,34 @@ export function registerOpenVikingMemoryTools(deps: OpenVikingMemoryToolsDeps):
|
|
|
176
176
|
}
|
|
177
177
|
if (memoriesCount === 0) {
|
|
178
178
|
deps.logger.warn(
|
|
179
|
-
`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId})
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
} else {
|
|
183
|
-
deps.logger.info?.(
|
|
184
|
-
`openviking: memory_store committed, memories=${memoriesCount}, ` +
|
|
185
|
-
`trace_id=${commitResult.trace_id ?? "none"}`,
|
|
179
|
+
`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId}), ` +
|
|
180
|
+
`trace_id=${commitResult.trace_id ?? "none"}. ` +
|
|
181
|
+
"No memory was created or updated. Check memory.extraction_enabled, VLM configuration/API keys, or whether the content is already stored.",
|
|
186
182
|
);
|
|
183
|
+
return {
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Memory extraction completed for session ${sessionId}, but created or updated no memory. ` +
|
|
187
|
+
"The content may already be stored, memory extraction may be disabled, or the extractor found nothing new to store. " +
|
|
188
|
+
"Tell the user no new memory was created." +
|
|
189
|
+
(commitResult.trace_id ? ` (trace_id=${commitResult.trace_id})` : ""),
|
|
190
|
+
}],
|
|
191
|
+
details: {
|
|
192
|
+
action: "failed",
|
|
193
|
+
sessionId,
|
|
194
|
+
status: commitResult.status,
|
|
195
|
+
error: "no_memories_extracted",
|
|
196
|
+
memoriesCount,
|
|
197
|
+
archived: commitResult.archived ?? false,
|
|
198
|
+
traceId: commitResult.trace_id,
|
|
199
|
+
usedTempSession,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
187
202
|
}
|
|
203
|
+
deps.logger.info?.(
|
|
204
|
+
`openviking: memory_store committed, memories=${memoriesCount}, ` +
|
|
205
|
+
`trace_id=${commitResult.trace_id ?? "none"}`,
|
|
206
|
+
);
|
|
188
207
|
return {
|
|
189
208
|
content: [
|
|
190
209
|
{
|
|
@@ -107,6 +107,7 @@ export type AssembleOpenVikingSessionParams = {
|
|
|
107
107
|
type CompactClient = Pick<OpenVikingClient, "commitSession" | "getSessionContext">;
|
|
108
108
|
|
|
109
109
|
export type CompactOpenVikingSessionParams = {
|
|
110
|
+
contextManagementMode?: "native" | "openviking";
|
|
110
111
|
sessionId: string;
|
|
111
112
|
sessionKey?: string;
|
|
112
113
|
tokenBudget: number;
|
|
@@ -138,6 +139,7 @@ export type AfterTurnOpenVikingSessionParams = {
|
|
|
138
139
|
runtimeContext?: Record<string, unknown>;
|
|
139
140
|
cfg: {
|
|
140
141
|
autoCapture: boolean;
|
|
142
|
+
contextManagementMode?: "native" | "openviking";
|
|
141
143
|
commitTokenThresholdRatio: number;
|
|
142
144
|
commitKeepRecentCount: number;
|
|
143
145
|
commitRetentionMode?: "message_count" | "turn_budget";
|
|
@@ -426,14 +428,13 @@ function isSessionNotFoundError(err: unknown): boolean {
|
|
|
426
428
|
return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
|
|
427
429
|
}
|
|
428
430
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
) {
|
|
433
|
-
const { sessionId, sessionKey, cfg,
|
|
431
|
+
const PROFILE_MAX_CHARS = 8000;
|
|
432
|
+
const PROFILE_READ_TIMEOUT_MS = 3000;
|
|
433
|
+
|
|
434
|
+
function resolveAssembleRouting(params: AssembleOpenVikingSessionParams) {
|
|
435
|
+
const { sessionId, sessionKey, cfg, resolveAgentId, logger } = params;
|
|
434
436
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
435
437
|
const sender = extractRuntimeSenderId(params.runtimeContext);
|
|
436
|
-
const client = await getClient();
|
|
437
438
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
438
439
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
439
440
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
@@ -442,6 +443,44 @@ async function recallForAssemble(
|
|
|
442
443
|
assistantPeerId: agentId,
|
|
443
444
|
warn: (message) => logger.warn?.(message),
|
|
444
445
|
});
|
|
446
|
+
return { ovSessionId, agentId, actorPeerId };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Server-side context assembly leaves profile.md out on the assumption that the
|
|
450
|
+
// client injects it, so read it here: the user's profile and, with peer scope, the actor's.
|
|
451
|
+
async function readProfileBlock(
|
|
452
|
+
client: OpenVikingClient,
|
|
453
|
+
actorPeerId: string | undefined,
|
|
454
|
+
timeoutMs: number,
|
|
455
|
+
logger: ContextEngineLifecycleLogger,
|
|
456
|
+
): Promise<string> {
|
|
457
|
+
const uris = ["viking://~/memories/profile.md"];
|
|
458
|
+
if (actorPeerId) uris.push(`viking://~/peers/${actorPeerId}/memories/profile.md`);
|
|
459
|
+
const reads = await Promise.allSettled(uris.map((uri) => client.read(uri, actorPeerId, timeoutMs)));
|
|
460
|
+
return reads
|
|
461
|
+
.map((read, i) => {
|
|
462
|
+
if (read.status === "rejected" && !String(read.reason).includes("[NOT_FOUND]")) {
|
|
463
|
+
logger.warn?.(`openviking: profile read failed (uri=${uris[i]}): ${String(read.reason)}`);
|
|
464
|
+
}
|
|
465
|
+
const text = read.status === "fulfilled" && typeof read.value === "string" ? read.value.trim() : "";
|
|
466
|
+
if (!text) return "";
|
|
467
|
+
const capped = text.length > PROFILE_MAX_CHARS
|
|
468
|
+
? `${text.slice(0, PROFILE_MAX_CHARS).trimEnd()}\n... [profile truncated]`
|
|
469
|
+
: text;
|
|
470
|
+
return `<user-profile uri="${uris[i]}">\n${capped}\n</user-profile>`;
|
|
471
|
+
})
|
|
472
|
+
.filter(Boolean)
|
|
473
|
+
.join("\n\n");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function recallForAssemble(
|
|
477
|
+
params: AssembleOpenVikingSessionParams,
|
|
478
|
+
recallQuery: ReturnType<typeof prepareRecallQuery>,
|
|
479
|
+
client: OpenVikingClient,
|
|
480
|
+
routing: ReturnType<typeof resolveAssembleRouting>,
|
|
481
|
+
) {
|
|
482
|
+
const { sessionId, sessionKey, cfg, queryConfigStore, logger, traceRecorder } = params;
|
|
483
|
+
const { ovSessionId, agentId, actorPeerId } = routing;
|
|
445
484
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
446
485
|
agentId,
|
|
447
486
|
sessionId,
|
|
@@ -474,24 +513,36 @@ export async function assembleOpenVikingSession(
|
|
|
474
513
|
const assembled = await assembleSessionContext(params);
|
|
475
514
|
// Current OpenClaw supplies the pending turn separately from history. Keep
|
|
476
515
|
// recalled context out of persisted messages and let the host own that turn.
|
|
477
|
-
if (
|
|
478
|
-
!params.isMainAssemble || !params.cfg.autoRecall || !params.prompt ||
|
|
479
|
-
params.isBypassedSession(params)
|
|
480
|
-
) {
|
|
516
|
+
if (!params.isMainAssemble || !params.cfg.autoRecall || params.isBypassedSession(params)) {
|
|
481
517
|
return assembled;
|
|
482
518
|
}
|
|
483
|
-
const query = prepareRecallQuery(params.prompt);
|
|
484
|
-
if (query.query.length < 5) return assembled;
|
|
519
|
+
const query = prepareRecallQuery(params.prompt ?? "");
|
|
485
520
|
|
|
486
521
|
try {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
522
|
+
const routing = resolveAssembleRouting(params);
|
|
523
|
+
const client = await params.getClient();
|
|
524
|
+
const [profileBlock, recallBlock] = await Promise.all([
|
|
525
|
+
readProfileBlock(
|
|
526
|
+
client,
|
|
527
|
+
routing.actorPeerId,
|
|
528
|
+
Math.min(params.cfg.autoRecallTimeoutMs ?? PROFILE_READ_TIMEOUT_MS, PROFILE_READ_TIMEOUT_MS),
|
|
529
|
+
params.logger,
|
|
530
|
+
),
|
|
531
|
+
query.query.length < 5 ? "" : recallForAssemble(params, query, client, routing)
|
|
532
|
+
.then((recall) => recall.block)
|
|
533
|
+
.catch((err) => {
|
|
534
|
+
params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
|
|
535
|
+
return "";
|
|
536
|
+
}),
|
|
537
|
+
]);
|
|
538
|
+
const baseTokens = estimateTextTokens(assembled.systemPromptAddition ?? "");
|
|
539
|
+
for (const blocks of [[profileBlock, recallBlock], [profileBlock], [recallBlock]]) {
|
|
540
|
+
if (!blocks.some(Boolean)) continue;
|
|
541
|
+
const systemPromptAddition = [assembled.systemPromptAddition, ...blocks].filter(Boolean).join("\n\n");
|
|
542
|
+
const estimatedTokens = assembled.estimatedTokens + estimateTextTokens(systemPromptAddition) - baseTokens;
|
|
543
|
+
if (estimatedTokens <= params.tokenBudget) return { ...assembled, systemPromptAddition, estimatedTokens };
|
|
544
|
+
}
|
|
545
|
+
return assembled;
|
|
495
546
|
} catch (err) {
|
|
496
547
|
params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
|
|
497
548
|
return assembled;
|
|
@@ -571,7 +622,7 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
571
622
|
}
|
|
572
623
|
|
|
573
624
|
try {
|
|
574
|
-
const recall = await recallForAssemble(params, recallQuery);
|
|
625
|
+
const recall = await recallForAssemble(params, recallQuery, await getClient(), resolveAssembleRouting(params));
|
|
575
626
|
|
|
576
627
|
if (!recall.block) {
|
|
577
628
|
return assemblePassthrough({
|
|
@@ -610,6 +661,10 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
610
661
|
}
|
|
611
662
|
}
|
|
612
663
|
|
|
664
|
+
if (cfg.contextManagementMode !== "openviking") {
|
|
665
|
+
return assemblePassthrough({ diag, ovSessionId, reason: "native_context", liveMessages: messages, originalTokens });
|
|
666
|
+
}
|
|
667
|
+
|
|
613
668
|
try {
|
|
614
669
|
const client = await getClient();
|
|
615
670
|
const ctx = await client.getSessionContext(ovSessionId, tokenBudget);
|
|
@@ -618,6 +673,15 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
618
673
|
const hasArchives = !!ctx?.latest_archive_overview || preAbstracts.length > 0;
|
|
619
674
|
const activeCount = ctx?.messages?.length ?? 0;
|
|
620
675
|
|
|
676
|
+
// A completed WM-off archive can leave a non-empty active tail. That tail
|
|
677
|
+
// alone cannot replace the host's earlier history in explicit OV mode.
|
|
678
|
+
if (!hasArchives && (ctx?.stats?.totalArchives ?? 0) > 0) {
|
|
679
|
+
logger.warn?.("openviking: archived context has no summary; keeping native history");
|
|
680
|
+
return assemblePassthrough({
|
|
681
|
+
diag, ovSessionId, reason: "archive_summary_missing", liveMessages: messages, originalTokens,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
621
685
|
if (!ctx || (!hasArchives && activeCount === 0)) {
|
|
622
686
|
return assemblePassthrough({
|
|
623
687
|
diag,
|
|
@@ -692,7 +756,7 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
692
756
|
if (isSessionNotFoundError(err)) {
|
|
693
757
|
const errorMessage = String(err);
|
|
694
758
|
logger.info(
|
|
695
|
-
`openviking:
|
|
759
|
+
`openviking: session history not assembled because OV session does not exist ` +
|
|
696
760
|
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`,
|
|
697
761
|
);
|
|
698
762
|
return assemblePassthrough({
|
|
@@ -989,6 +1053,7 @@ export async function afterTurnOpenVikingSession({
|
|
|
989
1053
|
|
|
990
1054
|
const commitResult = await client.commitSession(ovSessionId, {
|
|
991
1055
|
wait: false,
|
|
1056
|
+
...(cfg.contextManagementMode === "openviking" ? { enableWorkingMemory: true } : {}),
|
|
992
1057
|
...(cfg.commitRetentionMode === "turn_budget"
|
|
993
1058
|
? { retentionMode: "turn_budget" as const }
|
|
994
1059
|
: { keepRecentCount: cfg.commitKeepRecentCount }),
|
|
@@ -1000,6 +1065,7 @@ export async function afterTurnOpenVikingSession({
|
|
|
1000
1065
|
);
|
|
1001
1066
|
|
|
1002
1067
|
diag("afterTurn_commit", ovSessionId, {
|
|
1068
|
+
effectiveEnableWorkingMemory: commitResult.effective_enable_working_memory,
|
|
1003
1069
|
pendingTokens,
|
|
1004
1070
|
commitTokenThreshold,
|
|
1005
1071
|
commitTokenThresholdRatio: cfg.commitTokenThresholdRatio,
|
|
@@ -1058,6 +1124,7 @@ function compactFailureResult(
|
|
|
1058
1124
|
}
|
|
1059
1125
|
|
|
1060
1126
|
export async function compactOpenVikingSession({
|
|
1127
|
+
contextManagementMode = "native",
|
|
1061
1128
|
sessionId,
|
|
1062
1129
|
sessionKey,
|
|
1063
1130
|
tokenBudget,
|
|
@@ -1107,6 +1174,13 @@ export async function compactOpenVikingSession({
|
|
|
1107
1174
|
};
|
|
1108
1175
|
}
|
|
1109
1176
|
|
|
1177
|
+
if (contextManagementMode === "native") {
|
|
1178
|
+
return await runtimeCompact?.() ?? compactFailureResult(
|
|
1179
|
+
"native_compaction_unavailable", validTokenCount(currentTokenCount) ?? -1,
|
|
1180
|
+
{ message: "Upgrade OpenClaw to a version with runtime compaction delegation." },
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1110
1184
|
const client = await getClient();
|
|
1111
1185
|
const agentId = resolveAgentId(sessionId, sessionKey, ovSessionId);
|
|
1112
1186
|
const tokensBeforeOriginal = validTokenCount(currentTokenCount);
|
|
@@ -1134,6 +1208,7 @@ export async function compactOpenVikingSession({
|
|
|
1134
1208
|
const commitResult = await client.commitSession(ovSessionId, {
|
|
1135
1209
|
wait: true,
|
|
1136
1210
|
keepRecentCount: 0,
|
|
1211
|
+
enableWorkingMemory: true,
|
|
1137
1212
|
});
|
|
1138
1213
|
const memCount = totalExtractedMemories(commitResult.memories_extracted);
|
|
1139
1214
|
|
|
@@ -1204,6 +1279,10 @@ export async function compactOpenVikingSession({
|
|
|
1204
1279
|
};
|
|
1205
1280
|
}
|
|
1206
1281
|
|
|
1282
|
+
if (commitResult.effective_enable_working_memory !== true) {
|
|
1283
|
+
return await runtimeCompact?.() ?? compactFailureResult("working_memory_disabled", tokensBefore, { commit: commitResult });
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1207
1286
|
let summary = "";
|
|
1208
1287
|
const firstKeptEntryId = commitResult.archive_uri?.split("/").pop() ?? "";
|
|
1209
1288
|
let tokensAfter: number | undefined;
|
|
@@ -1264,6 +1343,10 @@ export async function compactOpenVikingSession({
|
|
|
1264
1343
|
`latestArchiveId=${firstKeptEntryId || "none"}`,
|
|
1265
1344
|
);
|
|
1266
1345
|
|
|
1346
|
+
if (!summary) {
|
|
1347
|
+
return await runtimeCompact?.() ?? compactFailureResult("archive_summary_missing", tokensBefore, { commit: commitResult });
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1267
1350
|
diag("compact_result", ovSessionId, {
|
|
1268
1351
|
ok: true,
|
|
1269
1352
|
compacted: true,
|
|
@@ -30,7 +30,7 @@ Choose the registered names that match the current runtime:
|
|
|
30
30
|
| Runtime | Search | Read |
|
|
31
31
|
| --- | --- | --- |
|
|
32
32
|
| OpenViking MCP, Codex, Claude Code | `find` or `search` | `read` |
|
|
33
|
-
| OpenCode | `openviking_find` or `openviking_search` | `openviking_read` |
|
|
33
|
+
| OpenCode, pi | `openviking_find` or `openviking_search` | `openviking_read` |
|
|
34
34
|
| OpenClaw | `ov_search` | `ov_read` or `ov_multi_read` |
|
|
35
35
|
|
|
36
36
|
The host may display MCP names with a namespace such as
|