@openviking/openclaw-plugin 2026.10.7 → 2026.10.8-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/client.ts
CHANGED
|
@@ -554,11 +554,11 @@ export class OpenVikingClient {
|
|
|
554
554
|
};
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
-
async read(uri: string, actorPeerId?: string): Promise<string> {
|
|
557
|
+
async read(uri: string, actorPeerId?: string, requestTimeoutMs?: number): Promise<string> {
|
|
558
558
|
return this.request<string>(
|
|
559
559
|
`/api/v1/content/read?uri=${encodeURIComponent(uri)}`,
|
|
560
560
|
{},
|
|
561
|
-
|
|
561
|
+
requestTimeoutMs,
|
|
562
562
|
actorPeerId,
|
|
563
563
|
);
|
|
564
564
|
}
|
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(/\/+$/, "");
|
|
@@ -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,
|
|
@@ -422,7 +459,7 @@ async function assembleSessionContext(params) {
|
|
|
422
459
|
catch (err) {
|
|
423
460
|
if (isSessionNotFoundError(err)) {
|
|
424
461
|
const errorMessage = String(err);
|
|
425
|
-
logger.info(`openviking:
|
|
462
|
+
logger.info(`openviking: session history not assembled because OV session does not exist ` +
|
|
426
463
|
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`);
|
|
427
464
|
return assemblePassthrough({
|
|
428
465
|
diag,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.8-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",
|
|
@@ -428,14 +428,13 @@ function isSessionNotFoundError(err: unknown): boolean {
|
|
|
428
428
|
return errorMessage.includes("[NOT_FOUND]") && errorMessage.includes("Session not found");
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
) {
|
|
435
|
-
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;
|
|
436
436
|
const ovSessionId = openClawSessionToOvStorageId(sessionId, sessionKey);
|
|
437
437
|
const sender = extractRuntimeSenderId(params.runtimeContext);
|
|
438
|
-
const client = await getClient();
|
|
439
438
|
const routingRef = sessionId ?? sessionKey ?? ovSessionId;
|
|
440
439
|
const agentId = resolveAgentId(routingRef, sessionKey, ovSessionId);
|
|
441
440
|
const actorPeerId = resolveOpenVikingActorPeerId({
|
|
@@ -444,6 +443,44 @@ async function recallForAssemble(
|
|
|
444
443
|
assistantPeerId: agentId,
|
|
445
444
|
warn: (message) => logger.warn?.(message),
|
|
446
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;
|
|
447
484
|
const queryConfig = await queryConfigStore?.getEffective({
|
|
448
485
|
agentId,
|
|
449
486
|
sessionId,
|
|
@@ -476,24 +513,36 @@ export async function assembleOpenVikingSession(
|
|
|
476
513
|
const assembled = await assembleSessionContext(params);
|
|
477
514
|
// Current OpenClaw supplies the pending turn separately from history. Keep
|
|
478
515
|
// recalled context out of persisted messages and let the host own that turn.
|
|
479
|
-
if (
|
|
480
|
-
!params.isMainAssemble || !params.cfg.autoRecall || !params.prompt ||
|
|
481
|
-
params.isBypassedSession(params)
|
|
482
|
-
) {
|
|
516
|
+
if (!params.isMainAssemble || !params.cfg.autoRecall || params.isBypassedSession(params)) {
|
|
483
517
|
return assembled;
|
|
484
518
|
}
|
|
485
|
-
const query = prepareRecallQuery(params.prompt);
|
|
486
|
-
if (query.query.length < 5) return assembled;
|
|
519
|
+
const query = prepareRecallQuery(params.prompt ?? "");
|
|
487
520
|
|
|
488
521
|
try {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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;
|
|
497
546
|
} catch (err) {
|
|
498
547
|
params.logger.warn?.(`openviking: auto-recall failed: ${String(err)}`);
|
|
499
548
|
return assembled;
|
|
@@ -573,7 +622,7 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
573
622
|
}
|
|
574
623
|
|
|
575
624
|
try {
|
|
576
|
-
const recall = await recallForAssemble(params, recallQuery);
|
|
625
|
+
const recall = await recallForAssemble(params, recallQuery, await getClient(), resolveAssembleRouting(params));
|
|
577
626
|
|
|
578
627
|
if (!recall.block) {
|
|
579
628
|
return assemblePassthrough({
|
|
@@ -707,7 +756,7 @@ async function assembleSessionContext(params: AssembleOpenVikingSessionParams):
|
|
|
707
756
|
if (isSessionNotFoundError(err)) {
|
|
708
757
|
const errorMessage = String(err);
|
|
709
758
|
logger.info(
|
|
710
|
-
`openviking:
|
|
759
|
+
`openviking: session history not assembled because OV session does not exist ` +
|
|
711
760
|
`(session=${ovSessionId}, tokenBudget=${tokenBudget}, agentId=${resolveAgentId(ovSessionId)})`,
|
|
712
761
|
);
|
|
713
762
|
return assemblePassthrough({
|