@openviking/openclaw-plugin 2026.5.10-dev.3 → 2026.5.26
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 +355 -355
- package/INSTALL-ZH.md +345 -345
- package/INSTALL.md +362 -362
- package/README.md +338 -317
- package/auto-recall.ts +257 -257
- package/client.ts +939 -828
- package/commands/setup.ts +1002 -1002
- package/config.ts +476 -476
- package/context-engine.ts +1737 -1700
- package/dist/client.js +30 -0
- package/dist/context-engine.js +40 -8
- package/dist/index.js +313 -23
- package/dist/process-manager.js +0 -87
- package/dist/runtime-utils.js +0 -3
- package/dist/text-utils.js +31 -51
- package/images/openclaw-plugin-engine-overview.png +0 -0
- package/images/openclaw-plugin-recall-flow.png +0 -0
- package/images/openclaw-plugin-runtime-routing.png +0 -0
- package/images/openclaw-plugin-session-lifecycle.png +0 -0
- package/index.ts +1985 -1639
- package/install-manifest.json +47 -47
- package/memory-ranking.ts +267 -267
- package/openclaw.plugin.json +335 -332
- package/package.json +76 -75
- package/process-manager.ts +45 -169
- package/runtime-utils.ts +5 -8
- package/session-transcript-repair.ts +530 -530
- package/skills/install-openviking-memory/SKILL.md +665 -665
- package/text-utils.ts +507 -524
- package/tool-call-id.ts +331 -331
package/dist/client.js
CHANGED
|
@@ -245,6 +245,36 @@ export class OpenVikingClient {
|
|
|
245
245
|
async read(uri, agentId) {
|
|
246
246
|
return this.request(`/api/v1/content/read?uri=${encodeURIComponent(uri)}`, {}, agentId);
|
|
247
247
|
}
|
|
248
|
+
async readToolResult(sessionId, toolResultId, options, agentId) {
|
|
249
|
+
const params = new URLSearchParams();
|
|
250
|
+
if (options?.offset !== undefined)
|
|
251
|
+
params.set("offset", String(options.offset));
|
|
252
|
+
if (options?.limit !== undefined)
|
|
253
|
+
params.set("limit", String(options.limit));
|
|
254
|
+
if (options?.includeMetadata !== undefined) {
|
|
255
|
+
params.set("include_metadata", String(options.includeMetadata));
|
|
256
|
+
}
|
|
257
|
+
const query = params.toString();
|
|
258
|
+
return this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}${query ? `?${query}` : ""}`, {}, agentId);
|
|
259
|
+
}
|
|
260
|
+
async searchToolResult(sessionId, toolResultId, queryText, options, agentId) {
|
|
261
|
+
const params = new URLSearchParams({ q: queryText });
|
|
262
|
+
if (options?.limit !== undefined)
|
|
263
|
+
params.set("limit", String(options.limit));
|
|
264
|
+
if (options?.contextChars !== undefined) {
|
|
265
|
+
params.set("context_chars", String(options.contextChars));
|
|
266
|
+
}
|
|
267
|
+
return this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}/search?${params.toString()}`, {}, agentId);
|
|
268
|
+
}
|
|
269
|
+
async listToolResults(sessionId, options, agentId) {
|
|
270
|
+
const params = new URLSearchParams();
|
|
271
|
+
if (options?.toolName)
|
|
272
|
+
params.set("tool_name", options.toolName);
|
|
273
|
+
if (options?.limit !== undefined)
|
|
274
|
+
params.set("limit", String(options.limit));
|
|
275
|
+
const query = params.toString();
|
|
276
|
+
return this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/tool-results${query ? `?${query}` : ""}`, {}, agentId);
|
|
277
|
+
}
|
|
248
278
|
async uploadTempFile(filePath, agentId) {
|
|
249
279
|
const fileBytes = await readFile(filePath);
|
|
250
280
|
const form = new FormData();
|
package/dist/context-engine.js
CHANGED
|
@@ -29,9 +29,6 @@ function allocateContextBudget(totalBudget, instructionTokens = 0) {
|
|
|
29
29
|
const sessionContext = Math.max(usableBudget - archiveMemory, 0);
|
|
30
30
|
return { archiveMemory, sessionContext, reserved };
|
|
31
31
|
}
|
|
32
|
-
function estimateTokens(messages) {
|
|
33
|
-
return Math.max(1, messages.length * 80);
|
|
34
|
-
}
|
|
35
32
|
function roughEstimate(messages) {
|
|
36
33
|
return Math.ceil(JSON.stringify(messages).length / 4);
|
|
37
34
|
}
|
|
@@ -265,6 +262,10 @@ export function convertToAgentMessages(msg) {
|
|
|
265
262
|
const toolName = typeof p.tool_name === "string" ? p.tool_name : undefined;
|
|
266
263
|
const status = typeof p.tool_status === "string" ? p.tool_status : "unknown";
|
|
267
264
|
const output = typeof p.tool_output === "string" ? p.tool_output : "";
|
|
265
|
+
const ref = typeof p.tool_output_ref === "string" ? p.tool_output_ref : "";
|
|
266
|
+
const originalChars = typeof p.tool_output_original_chars === "number"
|
|
267
|
+
? p.tool_output_original_chars
|
|
268
|
+
: undefined;
|
|
268
269
|
if (toolId) {
|
|
269
270
|
// Structured path: emit canonical toolCall + toolResult pair (works for any role)
|
|
270
271
|
toolCallBlocks.push({
|
|
@@ -273,9 +274,15 @@ export function convertToAgentMessages(msg) {
|
|
|
273
274
|
name: toolName ?? "unknown",
|
|
274
275
|
arguments: p.tool_input ?? {},
|
|
275
276
|
});
|
|
276
|
-
|
|
277
|
+
let resultText = (status === "completed" || status === "error")
|
|
277
278
|
? (output || "(no output)")
|
|
278
279
|
: "(interrupted — tool did not complete)";
|
|
280
|
+
if (ref && !resultText.includes(ref)) {
|
|
281
|
+
const suffix = originalChars !== undefined
|
|
282
|
+
? `\n[tool-result-ref] ${ref} original_chars=${originalChars}`
|
|
283
|
+
: `\n[tool-result-ref] ${ref}`;
|
|
284
|
+
resultText += suffix;
|
|
285
|
+
}
|
|
279
286
|
const resultPayload = {
|
|
280
287
|
role: "toolResult",
|
|
281
288
|
toolCallId: toolId,
|
|
@@ -330,6 +337,32 @@ export function convertToAgentMessages(msg) {
|
|
|
330
337
|
}
|
|
331
338
|
return result;
|
|
332
339
|
}
|
|
340
|
+
function isToolOnlyMessage(msg) {
|
|
341
|
+
return msg.role === "user" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "tool");
|
|
342
|
+
}
|
|
343
|
+
function coalesceConsecutiveToolMessages(messages) {
|
|
344
|
+
const result = [];
|
|
345
|
+
let pendingTools;
|
|
346
|
+
const flush = () => {
|
|
347
|
+
if (pendingTools) {
|
|
348
|
+
result.push(pendingTools);
|
|
349
|
+
pendingTools = undefined;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
for (const msg of messages) {
|
|
353
|
+
if (isToolOnlyMessage(msg)) {
|
|
354
|
+
if (!pendingTools) {
|
|
355
|
+
pendingTools = { role: "user", parts: [] };
|
|
356
|
+
}
|
|
357
|
+
pendingTools.parts.push(...msg.parts);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
flush();
|
|
361
|
+
result.push(msg);
|
|
362
|
+
}
|
|
363
|
+
flush();
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
333
366
|
function normalizeAssistantContent(messages) {
|
|
334
367
|
for (let i = 0; i < messages.length; i++) {
|
|
335
368
|
const msg = messages[i];
|
|
@@ -965,7 +998,8 @@ export function createMemoryOpenVikingContextEngine(params) {
|
|
|
965
998
|
afterTurnParams.prePromptMessageCount >= 0
|
|
966
999
|
? afterTurnParams.prePromptMessageCount
|
|
967
1000
|
: 0;
|
|
968
|
-
const { messages:
|
|
1001
|
+
const { messages: extractedMessagesRaw, newCount } = extractNewTurnMessages(messages, start);
|
|
1002
|
+
const extractedMessages = coalesceConsecutiveToolMessages(extractedMessagesRaw);
|
|
969
1003
|
if (extractedMessages.length === 0) {
|
|
970
1004
|
diag("afterTurn_skip", OVSessionId, {
|
|
971
1005
|
reason: "no_new_turn_messages",
|
|
@@ -1055,9 +1089,7 @@ export function createMemoryOpenVikingContextEngine(params) {
|
|
|
1055
1089
|
if (commitResult.task_id && cfg.logFindRequests) {
|
|
1056
1090
|
logger.info(`openviking: Phase2 memory extraction runs asynchronously on the server (task_id=${commitResult.task_id}). ` +
|
|
1057
1091
|
"memories_extracted appears only after that task completes — not in this immediate response.");
|
|
1058
|
-
|
|
1059
|
-
void pollPhase2ExtractionOutcome(getClient, commitResult.task_id, agentId, logger, OVSessionId);
|
|
1060
|
-
}
|
|
1092
|
+
void pollPhase2ExtractionOutcome(getClient, commitResult.task_id, agentId, logger, OVSessionId);
|
|
1061
1093
|
}
|
|
1062
1094
|
}
|
|
1063
1095
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,39 @@ import { registerSetupCli } from "./commands/setup.js";
|
|
|
4
4
|
import { OpenVikingClient, isMemoryUri } from "./client.js";
|
|
5
5
|
import { formatMessageFaithful, toRoleId } from "./context-engine.js";
|
|
6
6
|
import { compileSessionPatterns, shouldBypassSession, } from "./text-utils.js";
|
|
7
|
-
import { clampScore, postProcessMemories,
|
|
7
|
+
import { clampScore, postProcessMemories, pickMemoriesForInjection, } from "./memory-ranking.js";
|
|
8
8
|
import { createMemoryOpenVikingContextEngine, openClawSessionToOvStorageId, openClawSessionRefToOvStorageId, } from "./context-engine.js";
|
|
9
9
|
import { buildMemoryLines, buildMemoryLinesWithBudget, estimateTokenCount, prepareRecallQuery, } from "./auto-recall.js";
|
|
10
10
|
export { buildMemoryLines, buildMemoryLinesWithBudget, estimateTokenCount, prepareRecallQuery, };
|
|
11
|
+
function parseToolResultRef(value) {
|
|
12
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
13
|
+
if (!raw) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const match = raw.match(/^viking:\/\/session\/([^/]+)\/tool-results\/([^/?#]+)(?:[?#].*)?$/);
|
|
17
|
+
if (!match) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const sessionId = decodeURIComponent(match[1]);
|
|
21
|
+
const toolResultId = decodeURIComponent(match[2]);
|
|
22
|
+
if (!sessionId || !toolResultId) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
sessionId,
|
|
27
|
+
toolResultId,
|
|
28
|
+
ref: `viking://session/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function getOptionalInteger(value, fallback) {
|
|
32
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
return Math.floor(value);
|
|
36
|
+
}
|
|
37
|
+
function getPositiveInteger(value, fallback) {
|
|
38
|
+
return Math.max(1, getOptionalInteger(value, fallback));
|
|
39
|
+
}
|
|
11
40
|
const DEFAULT_OPENCLAW_AGENT_ID = "main";
|
|
12
41
|
/**
|
|
13
42
|
* OpenViking `UserIdentifier` allows only [a-zA-Z0-9_-] for agent_id
|
|
@@ -176,13 +205,13 @@ export function parseAddSkillCommandArgs(args) {
|
|
|
176
205
|
timeout: getNumberFlag(parsed.flags, "timeout"),
|
|
177
206
|
};
|
|
178
207
|
}
|
|
179
|
-
export function
|
|
208
|
+
export function parseOVSearchCommandArgs(args) {
|
|
180
209
|
const parsed = parseFlagArgs(args);
|
|
181
|
-
// `/
|
|
210
|
+
// `/ov-search` only accepts a single query string, so positional segments are
|
|
182
211
|
// always re-joined to preserve unquoted multi-word searches.
|
|
183
212
|
const query = parsed.positionals.join(" ").trim();
|
|
184
213
|
if (!query) {
|
|
185
|
-
throw new Error('Usage: /
|
|
214
|
+
throw new Error('Usage: /ov-search "<query>" [--uri URI] [--limit N]');
|
|
186
215
|
}
|
|
187
216
|
return {
|
|
188
217
|
query,
|
|
@@ -453,7 +482,7 @@ const contextEnginePlugin = {
|
|
|
453
482
|
total: memories.length + resources.length + skills.length,
|
|
454
483
|
};
|
|
455
484
|
};
|
|
456
|
-
const
|
|
485
|
+
const formatOVSearchRows = (result) => {
|
|
457
486
|
const truncateSummary = (value, maxChars = 220) => {
|
|
458
487
|
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
459
488
|
if (collapsed.length <= maxChars) {
|
|
@@ -490,7 +519,7 @@ const contextEnginePlugin = {
|
|
|
490
519
|
}),
|
|
491
520
|
];
|
|
492
521
|
};
|
|
493
|
-
const
|
|
522
|
+
const formatOVSearchText = (query, uri, result) => {
|
|
494
523
|
if ((result.total ?? 0) <= 0) {
|
|
495
524
|
const scope = uri ? ` under ${uri}` : "";
|
|
496
525
|
return `No OpenViking resource or skill results found for "${query}"${scope}.`;
|
|
@@ -499,11 +528,11 @@ const contextEnginePlugin = {
|
|
|
499
528
|
const lines = [
|
|
500
529
|
`Found ${result.total ?? 0} OpenViking results for "${query}"${scope}`,
|
|
501
530
|
"",
|
|
502
|
-
...
|
|
531
|
+
...formatOVSearchRows(result),
|
|
503
532
|
].filter((line, index, all) => line || (all[index - 1] && all[index + 1]));
|
|
504
533
|
return lines.join("\n");
|
|
505
534
|
};
|
|
506
|
-
const
|
|
535
|
+
const searchOpenViking = async (input, agentId) => {
|
|
507
536
|
const query = input.query.trim();
|
|
508
537
|
if (!query) {
|
|
509
538
|
throw new Error("query is required");
|
|
@@ -543,7 +572,7 @@ const contextEnginePlugin = {
|
|
|
543
572
|
result = mergeFindResults(successful);
|
|
544
573
|
}
|
|
545
574
|
return {
|
|
546
|
-
content: [{ type: "text", text:
|
|
575
|
+
content: [{ type: "text", text: formatOVSearchText(query, input.uri, result) }],
|
|
547
576
|
details: {
|
|
548
577
|
action: "searched",
|
|
549
578
|
query,
|
|
@@ -610,8 +639,8 @@ const contextEnginePlugin = {
|
|
|
610
639
|
},
|
|
611
640
|
}), { name: "add_skill" });
|
|
612
641
|
api.registerTool((ctx) => ({
|
|
613
|
-
name: "
|
|
614
|
-
label: "
|
|
642
|
+
name: "ov_search",
|
|
643
|
+
label: "Search (OpenViking)",
|
|
615
644
|
description: "Search OpenViking resources and skills. Use after importing, or when the user asks to search OpenViking resources or skills.",
|
|
616
645
|
parameters: Type.Object({
|
|
617
646
|
query: Type.String({ description: "Search query" }),
|
|
@@ -620,16 +649,16 @@ const contextEnginePlugin = {
|
|
|
620
649
|
}),
|
|
621
650
|
async execute(_toolCallId, params) {
|
|
622
651
|
if (isBypassedSession(ctx)) {
|
|
623
|
-
return makeBypassedToolResult("
|
|
652
|
+
return makeBypassedToolResult("ov_search");
|
|
624
653
|
}
|
|
625
654
|
const session = resolvePluginSessionRouting(ctx);
|
|
626
|
-
return
|
|
655
|
+
return searchOpenViking({
|
|
627
656
|
query: String(params.query ?? ""),
|
|
628
657
|
uri: typeof params.uri === "string" ? params.uri : undefined,
|
|
629
658
|
limit: typeof params.limit === "number" ? params.limit : undefined,
|
|
630
659
|
}, session.agentId);
|
|
631
660
|
},
|
|
632
|
-
}), { name: "
|
|
661
|
+
}), { name: "ov_search" });
|
|
633
662
|
api.registerCommand?.({
|
|
634
663
|
name: "add-resource",
|
|
635
664
|
description: "Add a resource into OpenViking.",
|
|
@@ -671,22 +700,22 @@ const contextEnginePlugin = {
|
|
|
671
700
|
},
|
|
672
701
|
});
|
|
673
702
|
api.registerCommand?.({
|
|
674
|
-
name: "
|
|
703
|
+
name: "ov-search",
|
|
675
704
|
description: "Search OpenViking resources and skills.",
|
|
676
705
|
acceptsArgs: true,
|
|
677
706
|
handler: async (ctx) => {
|
|
678
707
|
try {
|
|
679
708
|
if (isBypassedSession(ctx)) {
|
|
680
|
-
const bypassed = makeBypassedToolResult("
|
|
709
|
+
const bypassed = makeBypassedToolResult("ov_search");
|
|
681
710
|
return { text: bypassed.content[0].text, details: bypassed.details };
|
|
682
711
|
}
|
|
683
712
|
const session = resolvePluginSessionRouting(ctx);
|
|
684
|
-
const input =
|
|
685
|
-
const result = await
|
|
713
|
+
const input = parseOVSearchCommandArgs(ctx.args ?? "");
|
|
714
|
+
const result = await searchOpenViking(input, session.agentId);
|
|
686
715
|
return { text: result.content[0].text, details: result.details };
|
|
687
716
|
}
|
|
688
717
|
catch (err) {
|
|
689
|
-
return { text: `OpenViking
|
|
718
|
+
return { text: `OpenViking search failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
690
719
|
}
|
|
691
720
|
},
|
|
692
721
|
});
|
|
@@ -764,29 +793,54 @@ const contextEnginePlugin = {
|
|
|
764
793
|
total: leafOnly.length,
|
|
765
794
|
};
|
|
766
795
|
}
|
|
767
|
-
const
|
|
768
|
-
|
|
796
|
+
const leafOnly = (result.memories ?? []).filter((m) => !m.level || m.level === 2);
|
|
797
|
+
const processed = postProcessMemories(leafOnly, {
|
|
798
|
+
limit: requestLimit,
|
|
769
799
|
scoreThreshold,
|
|
770
800
|
});
|
|
801
|
+
const memories = pickMemoriesForInjection(processed, limit, query);
|
|
771
802
|
if (memories.length === 0) {
|
|
772
803
|
return {
|
|
773
804
|
content: [{ type: "text", text: "No relevant OpenViking memories found." }],
|
|
774
805
|
details: { count: 0, total: result.total ?? 0, scoreThreshold },
|
|
775
806
|
};
|
|
776
807
|
}
|
|
808
|
+
const { lines: memoryLines } = await buildMemoryLinesWithBudget(memories, (uri) => recallClient.read(uri, session.agentId), {
|
|
809
|
+
recallPreferAbstract: false,
|
|
810
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
811
|
+
});
|
|
812
|
+
if (memoryLines.length === 0) {
|
|
813
|
+
return {
|
|
814
|
+
content: [
|
|
815
|
+
{
|
|
816
|
+
type: "text",
|
|
817
|
+
text: `No complete OpenViking memories fit recallMaxInjectedChars=${cfg.recallMaxInjectedChars}.`,
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
details: {
|
|
821
|
+
count: 0,
|
|
822
|
+
memories,
|
|
823
|
+
total: result.total ?? memories.length,
|
|
824
|
+
scoreThreshold,
|
|
825
|
+
requestLimit,
|
|
826
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
827
|
+
},
|
|
828
|
+
};
|
|
829
|
+
}
|
|
777
830
|
return {
|
|
778
831
|
content: [
|
|
779
832
|
{
|
|
780
833
|
type: "text",
|
|
781
|
-
text: `Found ${
|
|
834
|
+
text: `Found ${memoryLines.length} memories:\n\n${memoryLines.join("\n")}`,
|
|
782
835
|
},
|
|
783
836
|
],
|
|
784
837
|
details: {
|
|
785
|
-
count:
|
|
838
|
+
count: memoryLines.length,
|
|
786
839
|
memories,
|
|
787
840
|
total: result.total ?? memories.length,
|
|
788
841
|
scoreThreshold,
|
|
789
842
|
requestLimit,
|
|
843
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
790
844
|
},
|
|
791
845
|
};
|
|
792
846
|
},
|
|
@@ -1133,6 +1187,242 @@ const contextEnginePlugin = {
|
|
|
1133
1187
|
}
|
|
1134
1188
|
},
|
|
1135
1189
|
}), { name: "ov_archive_expand" });
|
|
1190
|
+
api.registerTool((ctx) => ({
|
|
1191
|
+
name: "openviking_tool_result_read",
|
|
1192
|
+
label: "Tool Result Read (OpenViking)",
|
|
1193
|
+
description: "Restore the full original content of a tool result that was externalized by OpenViking. " +
|
|
1194
|
+
"Use when a previous tool result was externalized and only a preview is visible — " +
|
|
1195
|
+
"the preview contains a [tool-result-ref] or viking://session/.../tool-results/... URI. " +
|
|
1196
|
+
"\"Read\" tool returns the same truncated preview; this tool returns the complete content. " +
|
|
1197
|
+
"To read all content: pass offset=0 and a limit large enough to cover the whole result " +
|
|
1198
|
+
"(e.g. limit=100000). Use offset/limit for paging only when you need a specific section.",
|
|
1199
|
+
parameters: Type.Object({
|
|
1200
|
+
tool_output_ref: Type.String({
|
|
1201
|
+
description: "Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
|
|
1202
|
+
}),
|
|
1203
|
+
offset: Type.Optional(Type.Number({ description: "Unicode character offset. Default: 0" })),
|
|
1204
|
+
limit: Type.Optional(Type.Number({ description: "Maximum Unicode characters to read. Default: 20000" })),
|
|
1205
|
+
}),
|
|
1206
|
+
async execute(_toolCallId, params) {
|
|
1207
|
+
if (isBypassedSession(ctx)) {
|
|
1208
|
+
return makeBypassedToolResult("openviking_tool_result_read");
|
|
1209
|
+
}
|
|
1210
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1211
|
+
if (!session.ovSessionId) {
|
|
1212
|
+
return {
|
|
1213
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1214
|
+
details: { error: "no_session" },
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
|
|
1218
|
+
if (!parsed) {
|
|
1219
|
+
return {
|
|
1220
|
+
content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
|
|
1221
|
+
details: { error: "invalid_tool_output_ref" },
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
if (parsed.sessionId !== session.ovSessionId) {
|
|
1225
|
+
return {
|
|
1226
|
+
content: [{ type: "text", text: "Error: refusing to read a tool result from another session." }],
|
|
1227
|
+
details: {
|
|
1228
|
+
error: "session_mismatch",
|
|
1229
|
+
requestedSessionId: parsed.sessionId,
|
|
1230
|
+
currentSessionId: session.ovSessionId,
|
|
1231
|
+
},
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
const offset = Math.max(0, getOptionalInteger(params.offset, 0));
|
|
1235
|
+
const limit = getOptionalInteger(params.limit, 20_000);
|
|
1236
|
+
if (limit < -1) {
|
|
1237
|
+
return {
|
|
1238
|
+
content: [{ type: "text", text: "Error: limit must be -1 or greater than or equal to 0." }],
|
|
1239
|
+
details: { error: "invalid_limit", limit },
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
const client = await getClient();
|
|
1244
|
+
const result = await client.readToolResult(session.ovSessionId, parsed.toolResultId, { offset, limit, includeMetadata: true }, session.agentId);
|
|
1245
|
+
const returnedChars = result.content.length;
|
|
1246
|
+
const nextOffset = result.offset + returnedChars;
|
|
1247
|
+
const text = result.content || "(empty tool result chunk)";
|
|
1248
|
+
return {
|
|
1249
|
+
content: [{ type: "text", text }],
|
|
1250
|
+
details: {
|
|
1251
|
+
action: "read",
|
|
1252
|
+
tool_output_ref: parsed.ref,
|
|
1253
|
+
tool_result_id: result.tool_result_id,
|
|
1254
|
+
offset: result.offset,
|
|
1255
|
+
limit: result.limit,
|
|
1256
|
+
returned_chars: returnedChars,
|
|
1257
|
+
total_chars: result.total_chars,
|
|
1258
|
+
has_more: result.has_more,
|
|
1259
|
+
next_offset: result.has_more ? nextOffset : null,
|
|
1260
|
+
metadata: result.metadata ?? null,
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
catch (err) {
|
|
1265
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1266
|
+
api.logger.warn?.(`openviking: openviking_tool_result_read failed: ${msg}`);
|
|
1267
|
+
return {
|
|
1268
|
+
content: [{ type: "text", text: `Failed to read tool result: ${msg}` }],
|
|
1269
|
+
details: { error: msg, tool_output_ref: parsed.ref },
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
}), { name: "openviking_tool_result_read" });
|
|
1274
|
+
api.registerTool((ctx) => ({
|
|
1275
|
+
name: "openviking_tool_result_search",
|
|
1276
|
+
label: "Tool Result Search (OpenViking)",
|
|
1277
|
+
description: "Search inside an externalized tool result for a keyword. " +
|
|
1278
|
+
"Use when you need to find specific content in a large externalized result, " +
|
|
1279
|
+
"before reading it with openviking_tool_result_read. " +
|
|
1280
|
+
"Returns matching snippets with their character offsets.",
|
|
1281
|
+
parameters: Type.Object({
|
|
1282
|
+
tool_output_ref: Type.String({
|
|
1283
|
+
description: "Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
|
|
1284
|
+
}),
|
|
1285
|
+
query: Type.String({ description: "Keyword or exact text to search for" }),
|
|
1286
|
+
limit: Type.Optional(Type.Number({ description: "Maximum matches. Default: 20" })),
|
|
1287
|
+
context_chars: Type.Optional(Type.Number({ description: "Characters around each match. Default: 300" })),
|
|
1288
|
+
}),
|
|
1289
|
+
async execute(_toolCallId, params) {
|
|
1290
|
+
if (isBypassedSession(ctx)) {
|
|
1291
|
+
return makeBypassedToolResult("openviking_tool_result_search");
|
|
1292
|
+
}
|
|
1293
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1294
|
+
if (!session.ovSessionId) {
|
|
1295
|
+
return {
|
|
1296
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1297
|
+
details: { error: "no_session" },
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
|
|
1301
|
+
if (!parsed) {
|
|
1302
|
+
return {
|
|
1303
|
+
content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
|
|
1304
|
+
details: { error: "invalid_tool_output_ref" },
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
if (parsed.sessionId !== session.ovSessionId) {
|
|
1308
|
+
return {
|
|
1309
|
+
content: [{ type: "text", text: "Error: refusing to search a tool result from another session." }],
|
|
1310
|
+
details: {
|
|
1311
|
+
error: "session_mismatch",
|
|
1312
|
+
requestedSessionId: parsed.sessionId,
|
|
1313
|
+
currentSessionId: session.ovSessionId,
|
|
1314
|
+
},
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
const query = String(params.query ?? "").trim();
|
|
1318
|
+
if (!query) {
|
|
1319
|
+
return {
|
|
1320
|
+
content: [{ type: "text", text: "Error: query is required." }],
|
|
1321
|
+
details: { error: "missing_param", param: "query" },
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
const limit = getPositiveInteger(params.limit, 20);
|
|
1325
|
+
const contextChars = Math.max(0, getOptionalInteger(params.context_chars ?? params.contextChars, 300));
|
|
1326
|
+
try {
|
|
1327
|
+
const client = await getClient();
|
|
1328
|
+
const result = await client.searchToolResult(session.ovSessionId, parsed.toolResultId, query, { limit, contextChars }, session.agentId);
|
|
1329
|
+
const matches = result.matches ?? [];
|
|
1330
|
+
const text = matches.length
|
|
1331
|
+
? [
|
|
1332
|
+
`Found ${matches.length} match(es) for "${query}" in ${parsed.ref}:`,
|
|
1333
|
+
"",
|
|
1334
|
+
...matches.map((match, index) => `## Match ${index + 1} (offset ${match.offset})\n${match.snippet}`),
|
|
1335
|
+
].join("\n")
|
|
1336
|
+
: `No matches found for "${query}" in ${parsed.ref}.`;
|
|
1337
|
+
return {
|
|
1338
|
+
content: [{ type: "text", text }],
|
|
1339
|
+
details: {
|
|
1340
|
+
action: "searched",
|
|
1341
|
+
tool_output_ref: parsed.ref,
|
|
1342
|
+
tool_result_id: result.tool_result_id,
|
|
1343
|
+
query,
|
|
1344
|
+
match_count: matches.length,
|
|
1345
|
+
matches,
|
|
1346
|
+
},
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
catch (err) {
|
|
1350
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1351
|
+
api.logger.warn?.(`openviking: openviking_tool_result_search failed: ${msg}`);
|
|
1352
|
+
return {
|
|
1353
|
+
content: [{ type: "text", text: `Failed to search tool result: ${msg}` }],
|
|
1354
|
+
details: { error: msg, tool_output_ref: parsed.ref, query },
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
}), { name: "openviking_tool_result_search" });
|
|
1359
|
+
api.registerTool((ctx) => ({
|
|
1360
|
+
name: "openviking_tool_result_list",
|
|
1361
|
+
label: "Tool Result List (OpenViking)",
|
|
1362
|
+
description: "List externalized tool results for the current session. " +
|
|
1363
|
+
"Use to discover available refs before calling openviking_tool_result_read. " +
|
|
1364
|
+
"Optionally filter by tool_name to narrow down results.",
|
|
1365
|
+
parameters: Type.Object({
|
|
1366
|
+
tool_name: Type.Optional(Type.String({ description: "Optional exact tool name filter" })),
|
|
1367
|
+
limit: Type.Optional(Type.Number({ description: "Maximum results. Default: 50" })),
|
|
1368
|
+
}),
|
|
1369
|
+
async execute(_toolCallId, params) {
|
|
1370
|
+
if (isBypassedSession(ctx)) {
|
|
1371
|
+
return makeBypassedToolResult("openviking_tool_result_list");
|
|
1372
|
+
}
|
|
1373
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1374
|
+
if (!session.ovSessionId) {
|
|
1375
|
+
return {
|
|
1376
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1377
|
+
details: { error: "no_session" },
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
const toolName = typeof params.tool_name === "string" && params.tool_name.trim()
|
|
1381
|
+
? params.tool_name.trim()
|
|
1382
|
+
: typeof params.toolName === "string" && params.toolName.trim()
|
|
1383
|
+
? params.toolName.trim()
|
|
1384
|
+
: undefined;
|
|
1385
|
+
const limit = getPositiveInteger(params.limit, 50);
|
|
1386
|
+
try {
|
|
1387
|
+
const client = await getClient();
|
|
1388
|
+
const result = await client.listToolResults(session.ovSessionId, { toolName, limit }, session.agentId);
|
|
1389
|
+
const items = result.tool_results ?? [];
|
|
1390
|
+
const text = items.length
|
|
1391
|
+
? [
|
|
1392
|
+
`Found ${items.length} externalized tool result(s) in current session:`,
|
|
1393
|
+
"",
|
|
1394
|
+
...items.map((item, index) => {
|
|
1395
|
+
const ref = typeof item.storage_uri === "string" ? item.storage_uri : "(missing ref)";
|
|
1396
|
+
const name = typeof item.tool_name === "string" ? item.tool_name : "tool";
|
|
1397
|
+
const chars = typeof item.original_chars === "number" ? item.original_chars : "unknown";
|
|
1398
|
+
const created = typeof item.created_at === "string" ? ` created_at=${item.created_at}` : "";
|
|
1399
|
+
return `${index + 1}. ${name} original_chars=${chars}${created}\nref: ${ref}`;
|
|
1400
|
+
}),
|
|
1401
|
+
].join("\n")
|
|
1402
|
+
: toolName
|
|
1403
|
+
? `No externalized tool results found for tool "${toolName}" in current session.`
|
|
1404
|
+
: "No externalized tool results found in current session.";
|
|
1405
|
+
return {
|
|
1406
|
+
content: [{ type: "text", text }],
|
|
1407
|
+
details: {
|
|
1408
|
+
action: "listed",
|
|
1409
|
+
session_id: session.ovSessionId,
|
|
1410
|
+
tool_name: toolName ?? null,
|
|
1411
|
+
count: items.length,
|
|
1412
|
+
tool_results: items,
|
|
1413
|
+
},
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
catch (err) {
|
|
1417
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1418
|
+
api.logger.warn?.(`openviking: openviking_tool_result_list failed: ${msg}`);
|
|
1419
|
+
return {
|
|
1420
|
+
content: [{ type: "text", text: `Failed to list tool results: ${msg}` }],
|
|
1421
|
+
details: { error: msg, session_id: session.ovSessionId, tool_name: toolName ?? null },
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
}), { name: "openviking_tool_result_list" });
|
|
1136
1426
|
let contextEngineRef = null;
|
|
1137
1427
|
const sessionAgentResolver = createSessionAgentResolver(cfg.agent_prefix);
|
|
1138
1428
|
const rememberSessionAgentId = (ctx) => {
|