@openviking/openclaw-plugin 2026.9.10 → 2026.9.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/auto-recall.ts CHANGED
@@ -331,6 +331,7 @@ export async function buildAutoRecallContext(params: {
331
331
  rawUserTextPreview?: string;
332
332
  queryTruncated?: boolean;
333
333
  resourceTypes?: RecallResourceType[];
334
+ dedupTurns?: number;
334
335
  }): Promise<{ block?: string; memoryCount: number; estimatedTokens: number }> {
335
336
  const { cfg, client, agentId, actorPeerId, queryText, logger, verbose } = params;
336
337
  const queryConfig = params.queryConfig;
@@ -373,7 +374,7 @@ export async function buildAutoRecallContext(params: {
373
374
  queryExpansion: "auto",
374
375
  maxTokens,
375
376
  detail: recallPreferAbstract ? "abstract" : undefined,
376
- dedupTurns: params.ovSessionId ? AUTO_RECALL_DEDUP_TURNS : undefined,
377
+ dedupTurns: params.dedupTurns ?? (params.ovSessionId ? AUTO_RECALL_DEDUP_TURNS : undefined),
377
378
  peerScope: "actor",
378
379
  actorPeerId,
379
380
  requestTimeoutMs: cfg.autoRecallTimeoutMs,
package/client.ts CHANGED
@@ -80,6 +80,7 @@ export type CommitSessionResult = {
80
80
  task_id?: string;
81
81
  archive_uri?: string;
82
82
  archived?: boolean;
83
+ reset_context?: boolean;
83
84
  /** Present when wait=true and extraction completed. Keyed by category. */
84
85
  memories_extracted?: Record<string, number>;
85
86
  error?: string;
@@ -508,6 +509,7 @@ export class OpenVikingClient {
508
509
  };
509
510
  const body = {
510
511
  ...buildContextSearchBody(contractConfig, { sessionId: options.sessionId }),
512
+ ...(options.dedupTurns === 0 ? { dedup_turns: 0 } : {}),
511
513
  query,
512
514
  ...(options.contextType !== undefined ? { context_type: options.contextType } : {}),
513
515
  ...(options.detail !== undefined ? { detail: options.detail } : {}),
@@ -844,6 +846,8 @@ export class OpenVikingClient {
844
846
  * preserves the pre-v2 "archive everything" behavior.
845
847
  */
846
848
  keepRecentCount?: number;
849
+ /** Start empty context in the same session after archiving. */
850
+ resetContext?: boolean;
847
851
  agentId?: string;
848
852
  },
849
853
  ): Promise<CommitSessionResult> {
@@ -865,6 +869,9 @@ export class OpenVikingClient {
865
869
  if (keepRecentCount > 0) {
866
870
  body.keep_recent_count = keepRecentCount;
867
871
  }
872
+ if (options?.resetContext) {
873
+ body.reset_context = true;
874
+ }
868
875
  const result = await this.request<CommitSessionResult>(
869
876
  `/api/v1/sessions/${encodeURIComponent(sessionId)}/commit`,
870
877
  { method: "POST", body: JSON.stringify(body) },
@@ -872,6 +879,10 @@ export class OpenVikingClient {
872
879
  options?.agentId,
873
880
  );
874
881
 
882
+ if (options?.resetContext && result.reset_context !== true) {
883
+ throw new Error("OpenViking server did not confirm reset_context; upgrade the server with the plugin.");
884
+ }
885
+
875
886
  if (!options?.wait || !result.task_id) {
876
887
  return result;
877
888
  }
package/context-engine.ts CHANGED
@@ -394,6 +394,7 @@ export function createMemoryOpenVikingContextEngine(params: {
394
394
  sessionId: assembleParams.sessionId,
395
395
  sessionKey: resolveSessionKey(assembleParams),
396
396
  messages: assembleParams.messages,
397
+ prompt: assembleParams.prompt,
397
398
  tokenBudget,
398
399
  runtimeContext: assembleParams.runtimeContext,
399
400
  isMainAssemble,
@@ -244,7 +244,7 @@ export async function buildAutoRecallContext(params) {
244
244
  queryExpansion: "auto",
245
245
  maxTokens,
246
246
  detail: recallPreferAbstract ? "abstract" : undefined,
247
- dedupTurns: params.ovSessionId ? AUTO_RECALL_DEDUP_TURNS : undefined,
247
+ dedupTurns: params.dedupTurns ?? (params.ovSessionId ? AUTO_RECALL_DEDUP_TURNS : undefined),
248
248
  peerScope: "actor",
249
249
  actorPeerId,
250
250
  requestTimeoutMs: cfg.autoRecallTimeoutMs,
package/dist/client.js CHANGED
@@ -205,6 +205,7 @@ export class OpenVikingClient {
205
205
  };
206
206
  const body = {
207
207
  ...buildContextSearchBody(contractConfig, { sessionId: options.sessionId }),
208
+ ...(options.dedupTurns === 0 ? { dedup_turns: 0 } : {}),
208
209
  query,
209
210
  ...(options.contextType !== undefined ? { context_type: options.contextType } : {}),
210
211
  ...(options.detail !== undefined ? { detail: options.detail } : {}),
@@ -414,7 +415,13 @@ export class OpenVikingClient {
414
415
  if (keepRecentCount > 0) {
415
416
  body.keep_recent_count = keepRecentCount;
416
417
  }
418
+ if (options?.resetContext) {
419
+ body.reset_context = true;
420
+ }
417
421
  const result = await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/commit`, { method: "POST", body: JSON.stringify(body) }, undefined, options?.agentId);
422
+ if (options?.resetContext && result.reset_context !== true) {
423
+ throw new Error("OpenViking server did not confirm reset_context; upgrade the server with the plugin.");
424
+ }
418
425
  if (!options?.wait || !result.task_id) {
419
426
  return result;
420
427
  }
@@ -214,6 +214,7 @@ export function createMemoryOpenVikingContextEngine(params) {
214
214
  sessionId: assembleParams.sessionId,
215
215
  sessionKey: resolveSessionKey(assembleParams),
216
216
  messages: assembleParams.messages,
217
+ prompt: assembleParams.prompt,
217
218
  tokenBudget,
218
219
  runtimeContext: assembleParams.runtimeContext,
219
220
  isMainAssemble,
@@ -157,6 +157,7 @@ export async function commitOpenVikingSession({ sessionId, sessionKey, getClient
157
157
  const commitResult = await client.commitSession(ovId, {
158
158
  wait: true,
159
159
  keepRecentCount: 0,
160
+ resetContext: true,
160
161
  });
161
162
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
162
163
  if (commitResult.status === "failed") {
@@ -195,7 +196,73 @@ function isSessionNotFoundError(err) {
195
196
  const errorMessage = String(err);
196
197
  return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
197
198
  }
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, }) {
199
+ async function recallForAssemble(params, recallQuery) {
200
+ const { sessionId, sessionKey, cfg, getClient, resolveAgentId, queryConfigStore, logger, traceRecorder } = params;
201
+ const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
202
+ const sender = extractRuntimeSenderId(params.runtimeContext);
203
+ const client = await getClient();
204
+ const routingRef = sessionId ?? sessionKey ?? ovSessionId;
205
+ const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
206
+ const actorPeerId = resolveOpenVikingActorPeerId({
207
+ peerRole: cfg.peer_role ?? "none",
208
+ senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
209
+ assistantPeerId: agentId,
210
+ });
211
+ const queryConfig = await queryConfigStore?.getEffective({
212
+ agentId,
213
+ sessionId,
214
+ sessionKey,
215
+ ovSessionId,
216
+ });
217
+ return buildAutoRecallContext({
218
+ cfg,
219
+ queryConfig,
220
+ client,
221
+ agentId,
222
+ actorPeerId,
223
+ queryText: recallQuery.query,
224
+ logger,
225
+ verbose: (message) => logger.info(message),
226
+ traceRecorder: traceRecorder,
227
+ sessionId,
228
+ sessionKey,
229
+ ovSessionId,
230
+ queryTruncated: recallQuery.truncated,
231
+ rawUserTextPreview: recallQuery.query,
232
+ // System additions are transient; the next turn must be able to recall the same URI.
233
+ dedupTurns: params.isMainAssemble ? 0 : undefined,
234
+ });
235
+ }
236
+ export async function assembleOpenVikingSession(params) {
237
+ const assembled = await assembleSessionContext(params);
238
+ // Current OpenClaw supplies the pending turn separately from history. Keep
239
+ // recalled context out of persisted messages and let the host own that turn.
240
+ if (!params.isMainAssemble || !params.cfg.autoRecall || !params.prompt ||
241
+ params.isBypassedSession(params)) {
242
+ return assembled;
243
+ }
244
+ const query = prepareRecallQuery(params.prompt);
245
+ if (query.query.length < 5)
246
+ return assembled;
247
+ try {
248
+ const recall = await recallForAssemble(params, query);
249
+ if (!recall.block)
250
+ return assembled;
251
+ const systemPromptAddition = [assembled.systemPromptAddition, recall.block].filter(Boolean).join("\n\n");
252
+ const estimatedTokens = assembled.estimatedTokens
253
+ + estimateTextTokens(systemPromptAddition)
254
+ - estimateTextTokens(assembled.systemPromptAddition ?? "");
255
+ if (estimatedTokens > params.tokenBudget)
256
+ return assembled;
257
+ return { ...assembled, systemPromptAddition, estimatedTokens };
258
+ }
259
+ catch (err) {
260
+ params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
261
+ return assembled;
262
+ }
263
+ }
264
+ async function assembleSessionContext(params) {
265
+ const { sessionId, sessionKey, messages, tokenBudget, runtimeContext, isMainAssemble, cfg, getClient, logger, resolveAgentId, rememberSessionAgentId, isBypassedSession, diag, roughEstimate, messageDigest, extractAgentMessageText, hasAutoRecallBlock, prependRecallToLatestUserMessage, } = params;
199
266
  const latestMessage = messages.at(-1);
200
267
  const isTransformContextAssemble = !isMainAssemble;
201
268
  // Tool-loop calls do no recall work on non-user tails; avoid scanning the full history.
@@ -239,36 +306,7 @@ export async function assembleOpenVikingSession({ sessionId, sessionKey, message
239
306
  `chars=${recallQuery.originalChars}->${recallQuery.finalChars})`);
240
307
  }
241
308
  try {
242
- const client = await getClient();
243
- const routingRef = sessionId ?? sessionKey ?? ovSessionId;
244
- const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
245
- const actorPeerId = resolveOpenVikingActorPeerId({
246
- peerRole: cfg.peer_role ?? "none",
247
- senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
248
- assistantPeerId: agentId,
249
- });
250
- const queryConfig = await queryConfigStore?.getEffective({
251
- agentId,
252
- sessionId,
253
- sessionKey,
254
- ovSessionId,
255
- });
256
- const recall = await buildAutoRecallContext({
257
- cfg,
258
- queryConfig,
259
- client,
260
- agentId,
261
- actorPeerId,
262
- queryText: recallQuery.query,
263
- logger,
264
- verbose: (message) => logger.info(message),
265
- traceRecorder: traceRecorder,
266
- sessionId,
267
- sessionKey,
268
- ovSessionId,
269
- queryTruncated: recallQuery.truncated,
270
- rawUserTextPreview: recallQuery.query,
271
- });
309
+ const recall = await recallForAssemble(params, recallQuery);
272
310
  if (!recall.block) {
273
311
  return assemblePassthrough({
274
312
  diag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/openclaw-plugin",
3
- "version": "2026.9.10",
3
+ "version": "2026.9.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",
@@ -72,6 +72,7 @@ export type AssembleOpenVikingSessionParams = {
72
72
  sessionId: string;
73
73
  sessionKey?: string;
74
74
  messages: AgentMessage[];
75
+ prompt?: string;
75
76
  tokenBudget: number;
76
77
  runtimeContext?: Record<string, unknown>;
77
78
  isMainAssemble: boolean;
@@ -369,6 +370,7 @@ export async function commitOpenVikingSession({
369
370
  const commitResult = await client.commitSession(ovId, {
370
371
  wait: true,
371
372
  keepRecentCount: 0,
373
+ resetContext: true,
372
374
  });
373
375
  const memCount = totalExtractedMemories(commitResult.memories_extracted);
374
376
  if (commitResult.status === "failed") {
@@ -423,28 +425,98 @@ function isSessionNotFoundError(err: unknown): boolean {
423
425
  return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
424
426
  }
425
427
 
426
- export async function assembleOpenVikingSession({
427
- sessionId,
428
- sessionKey,
429
- messages,
430
- tokenBudget,
431
- runtimeContext,
432
- isMainAssemble,
433
- cfg,
434
- getClient,
435
- logger,
436
- resolveAgentId,
437
- rememberSessionAgentId,
438
- isBypassedSession,
439
- queryConfigStore,
440
- traceRecorder,
441
- diag,
442
- roughEstimate,
443
- messageDigest,
444
- extractAgentMessageText,
445
- hasAutoRecallBlock,
446
- prependRecallToLatestUserMessage,
447
- }: AssembleOpenVikingSessionParams): Promise<AssembleOpenVikingSessionResult> {
428
+ async function recallForAssemble(
429
+ params: AssembleOpenVikingSessionParams,
430
+ recallQuery: ReturnType<typeof prepareRecallQuery>,
431
+ ) {
432
+ const { sessionId, sessionKey, cfg, getClient, resolveAgentId, queryConfigStore, logger, traceRecorder } = params;
433
+ const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
434
+ const sender = extractRuntimeSenderId(params.runtimeContext);
435
+ const client = await getClient();
436
+ const routingRef = sessionId ?? sessionKey ?? ovSessionId;
437
+ const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
438
+ const actorPeerId = resolveOpenVikingActorPeerId({
439
+ peerRole: cfg.peer_role ?? "none",
440
+ senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
441
+ assistantPeerId: agentId,
442
+ });
443
+ const queryConfig = await queryConfigStore?.getEffective({
444
+ agentId,
445
+ sessionId,
446
+ sessionKey,
447
+ ovSessionId,
448
+ });
449
+ return buildAutoRecallContext({
450
+ cfg,
451
+ queryConfig,
452
+ client,
453
+ agentId,
454
+ actorPeerId,
455
+ queryText: recallQuery.query,
456
+ logger,
457
+ verbose: (message) => logger.info(message),
458
+ traceRecorder: traceRecorder as never,
459
+ sessionId,
460
+ sessionKey,
461
+ ovSessionId,
462
+ queryTruncated: recallQuery.truncated,
463
+ rawUserTextPreview: recallQuery.query,
464
+ // System additions are transient; the next turn must be able to recall the same URI.
465
+ dedupTurns: params.isMainAssemble ? 0 : undefined,
466
+ });
467
+ }
468
+
469
+ export async function assembleOpenVikingSession(
470
+ params: AssembleOpenVikingSessionParams,
471
+ ): Promise<AssembleOpenVikingSessionResult> {
472
+ const assembled = await assembleSessionContext(params);
473
+ // Current OpenClaw supplies the pending turn separately from history. Keep
474
+ // recalled context out of persisted messages and let the host own that turn.
475
+ if (
476
+ !params.isMainAssemble || !params.cfg.autoRecall || !params.prompt ||
477
+ params.isBypassedSession(params)
478
+ ) {
479
+ return assembled;
480
+ }
481
+ const query = prepareRecallQuery(params.prompt);
482
+ if (query.query.length < 5) return assembled;
483
+
484
+ try {
485
+ const recall = await recallForAssemble(params, query);
486
+ if (!recall.block) return assembled;
487
+ const systemPromptAddition = [assembled.systemPromptAddition, recall.block].filter(Boolean).join("\n\n");
488
+ const estimatedTokens = assembled.estimatedTokens
489
+ + estimateTextTokens(systemPromptAddition)
490
+ - estimateTextTokens(assembled.systemPromptAddition ?? "");
491
+ if (estimatedTokens > params.tokenBudget) return assembled;
492
+ return { ...assembled, systemPromptAddition, estimatedTokens };
493
+ } catch (err) {
494
+ params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
495
+ return assembled;
496
+ }
497
+ }
498
+
499
+ async function assembleSessionContext(params: AssembleOpenVikingSessionParams): Promise<AssembleOpenVikingSessionResult> {
500
+ const {
501
+ sessionId,
502
+ sessionKey,
503
+ messages,
504
+ tokenBudget,
505
+ runtimeContext,
506
+ isMainAssemble,
507
+ cfg,
508
+ getClient,
509
+ logger,
510
+ resolveAgentId,
511
+ rememberSessionAgentId,
512
+ isBypassedSession,
513
+ diag,
514
+ roughEstimate,
515
+ messageDigest,
516
+ extractAgentMessageText,
517
+ hasAutoRecallBlock,
518
+ prependRecallToLatestUserMessage,
519
+ } = params;
448
520
  const latestMessage = messages.at(-1);
449
521
  const isTransformContextAssemble = !isMainAssemble;
450
522
 
@@ -497,36 +569,7 @@ export async function assembleOpenVikingSession({
497
569
  }
498
570
 
499
571
  try {
500
- const client = await getClient();
501
- const routingRef = sessionId ?? sessionKey ?? ovSessionId;
502
- const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
503
- const actorPeerId = resolveOpenVikingActorPeerId({
504
- peerRole: cfg.peer_role ?? "none",
505
- senderPeerId: sanitizeOpenVikingPeerId(sender.senderId),
506
- assistantPeerId: agentId,
507
- });
508
- const queryConfig = await queryConfigStore?.getEffective({
509
- agentId,
510
- sessionId,
511
- sessionKey,
512
- ovSessionId,
513
- });
514
- const recall = await buildAutoRecallContext({
515
- cfg,
516
- queryConfig,
517
- client,
518
- agentId,
519
- actorPeerId,
520
- queryText: recallQuery.query,
521
- logger,
522
- verbose: (message) => logger.info(message),
523
- traceRecorder: traceRecorder as never,
524
- sessionId,
525
- sessionKey,
526
- ovSessionId,
527
- queryTruncated: recallQuery.truncated,
528
- rawUserTextPreview: recallQuery.query,
529
- });
572
+ const recall = await recallForAssemble(params, recallQuery);
530
573
 
531
574
  if (!recall.block) {
532
575
  return assemblePassthrough({