@openviking/openclaw-plugin 2026.5.8 → 2026.5.21
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 +10 -10
- package/INSTALL-ZH.md +8 -8
- package/INSTALL.md +8 -8
- package/README.md +60 -37
- 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 +389 -69
- 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 -1597
- package/install-manifest.json +47 -47
- package/memory-ranking.ts +267 -267
- package/openclaw.plugin.json +335 -331
- package/package.json +2 -1
- 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/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
|
|
@@ -121,15 +150,6 @@ function getNumberFlag(flags, name) {
|
|
|
121
150
|
function getBoolFlag(flags, name) {
|
|
122
151
|
return flags.get(name) === true;
|
|
123
152
|
}
|
|
124
|
-
function parseImportKind(value) {
|
|
125
|
-
if (!value) {
|
|
126
|
-
return "resource";
|
|
127
|
-
}
|
|
128
|
-
if (value === "resource" || value === "skill") {
|
|
129
|
-
return value;
|
|
130
|
-
}
|
|
131
|
-
throw new Error("--kind must be resource or skill");
|
|
132
|
-
}
|
|
133
153
|
function extractToolSenderId(ctx) {
|
|
134
154
|
if (!ctx || typeof ctx !== "object") {
|
|
135
155
|
return undefined;
|
|
@@ -149,23 +169,18 @@ function extractToolSenderId(ctx) {
|
|
|
149
169
|
}
|
|
150
170
|
return undefined;
|
|
151
171
|
}
|
|
152
|
-
export function
|
|
172
|
+
export function parseAddResourceCommandArgs(args) {
|
|
153
173
|
const parsed = parseFlagArgs(args);
|
|
154
|
-
const kind = parseImportKind(getStringFlag(parsed.flags, "kind"));
|
|
155
174
|
const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim();
|
|
156
175
|
if (!source) {
|
|
157
|
-
throw new Error("Usage: /
|
|
176
|
+
throw new Error("Usage: /add-resource <source> [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait] [--timeout SEC]");
|
|
158
177
|
}
|
|
159
178
|
const to = getStringFlag(parsed.flags, "to");
|
|
160
179
|
const parent = getStringFlag(parsed.flags, "parent");
|
|
161
180
|
if (to && parent) {
|
|
162
181
|
throw new Error("Cannot specify both --to and --parent.");
|
|
163
182
|
}
|
|
164
|
-
if (kind === "skill" && (to || parent || parsed.flags.has("reason") || parsed.flags.has("instruction"))) {
|
|
165
|
-
throw new Error("--to, --parent, --reason, and --instruction are resource-only options.");
|
|
166
|
-
}
|
|
167
183
|
return {
|
|
168
|
-
kind,
|
|
169
184
|
source,
|
|
170
185
|
to,
|
|
171
186
|
parent,
|
|
@@ -175,6 +190,21 @@ export function parseOvImportCommandArgs(args) {
|
|
|
175
190
|
timeout: getNumberFlag(parsed.flags, "timeout"),
|
|
176
191
|
};
|
|
177
192
|
}
|
|
193
|
+
export function parseAddSkillCommandArgs(args) {
|
|
194
|
+
const parsed = parseFlagArgs(args);
|
|
195
|
+
const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim();
|
|
196
|
+
if (!source) {
|
|
197
|
+
throw new Error("Usage: /add-skill <source> [--wait] [--timeout SEC]");
|
|
198
|
+
}
|
|
199
|
+
if (parsed.flags.has("to") || parsed.flags.has("parent") || parsed.flags.has("reason") || parsed.flags.has("instruction")) {
|
|
200
|
+
throw new Error("--to, --parent, --reason, and --instruction are resource-only options.");
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
source,
|
|
204
|
+
wait: getBoolFlag(parsed.flags, "wait"),
|
|
205
|
+
timeout: getNumberFlag(parsed.flags, "timeout"),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
178
208
|
export function parseMemorySearchCommandArgs(args) {
|
|
179
209
|
const parsed = parseFlagArgs(args);
|
|
180
210
|
// `/memory-search` only accepts a single query string, so positional segments are
|
|
@@ -417,32 +447,21 @@ const contextEnginePlugin = {
|
|
|
417
447
|
},
|
|
418
448
|
};
|
|
419
449
|
};
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
return importResource({
|
|
437
|
-
pathOrUrl: input.source ?? "",
|
|
438
|
-
to: input.to,
|
|
439
|
-
parent: input.parent,
|
|
440
|
-
reason: input.reason,
|
|
441
|
-
instruction: input.instruction,
|
|
442
|
-
wait: input.wait,
|
|
443
|
-
timeout: input.timeout,
|
|
444
|
-
}, agentId);
|
|
445
|
-
};
|
|
450
|
+
const addResourceOpenViking = (input, agentId) => importResource({
|
|
451
|
+
pathOrUrl: input.source ?? "",
|
|
452
|
+
to: input.to,
|
|
453
|
+
parent: input.parent,
|
|
454
|
+
reason: input.reason,
|
|
455
|
+
instruction: input.instruction,
|
|
456
|
+
wait: input.wait,
|
|
457
|
+
timeout: input.timeout,
|
|
458
|
+
}, agentId);
|
|
459
|
+
const addSkillOpenViking = (input, agentId) => importSkill({
|
|
460
|
+
path: input.source,
|
|
461
|
+
data: input.data,
|
|
462
|
+
wait: input.wait,
|
|
463
|
+
timeout: input.timeout,
|
|
464
|
+
}, agentId);
|
|
446
465
|
const mergeFindResults = (results) => {
|
|
447
466
|
const deduplicate = (items) => {
|
|
448
467
|
const seen = new Map();
|
|
@@ -566,30 +585,26 @@ const contextEnginePlugin = {
|
|
|
566
585
|
};
|
|
567
586
|
};
|
|
568
587
|
api.registerTool((ctx) => ({
|
|
569
|
-
name: "
|
|
570
|
-
label: "
|
|
571
|
-
description: "
|
|
572
|
-
"
|
|
588
|
+
name: "add_resource",
|
|
589
|
+
label: "Add Resource (OpenViking)",
|
|
590
|
+
description: "Use only when the user explicitly asks to import, add, upload, save, or index a document, directory, URL, Git repository, or OpenClaw media attachment into OpenViking resources. " +
|
|
591
|
+
"For a '[media attached: /path ...]' document, set source to that exact local media path. Do not invent OpenViking upload REST endpoints.",
|
|
573
592
|
parameters: Type.Object({
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
reason: Type.Optional(Type.String({ description: "Resource only: reason or note for adding this resource" })),
|
|
580
|
-
instruction: Type.Optional(Type.String({ description: "Resource only: processing instruction for semantic extraction" })),
|
|
593
|
+
source: Type.String({ description: "Local path, OpenClaw media attachment path, directory path, public URL, or Git URL" }),
|
|
594
|
+
to: Type.Optional(Type.String({ description: "Exact target URI, e.g. viking://resources/project-docs" })),
|
|
595
|
+
parent: Type.Optional(Type.String({ description: "Parent URI under viking://resources" })),
|
|
596
|
+
reason: Type.Optional(Type.String({ description: "Reason or note for adding this resource" })),
|
|
597
|
+
instruction: Type.Optional(Type.String({ description: "Processing instruction for semantic extraction" })),
|
|
581
598
|
wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
|
|
582
599
|
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
|
|
583
600
|
}),
|
|
584
601
|
async execute(_toolCallId, params) {
|
|
585
602
|
if (isBypassedSession(ctx)) {
|
|
586
|
-
return makeBypassedToolResult("
|
|
603
|
+
return makeBypassedToolResult("add_resource");
|
|
587
604
|
}
|
|
588
605
|
const session = resolvePluginSessionRouting(ctx);
|
|
589
|
-
return
|
|
590
|
-
kind: params.kind === "skill" ? "skill" : "resource",
|
|
606
|
+
return addResourceOpenViking({
|
|
591
607
|
source: typeof params.source === "string" ? params.source : undefined,
|
|
592
|
-
data: params.data,
|
|
593
608
|
to: typeof params.to === "string" ? params.to : undefined,
|
|
594
609
|
parent: typeof params.parent === "string" ? params.parent : undefined,
|
|
595
610
|
reason: typeof params.reason === "string" ? params.reason : undefined,
|
|
@@ -598,7 +613,31 @@ const contextEnginePlugin = {
|
|
|
598
613
|
timeout: typeof params.timeout === "number" ? params.timeout : undefined,
|
|
599
614
|
}, session.agentId);
|
|
600
615
|
},
|
|
601
|
-
}), { name: "
|
|
616
|
+
}), { name: "add_resource" });
|
|
617
|
+
api.registerTool((ctx) => ({
|
|
618
|
+
name: "add_skill",
|
|
619
|
+
label: "Add Skill (OpenViking)",
|
|
620
|
+
description: "Use only when the user explicitly asks to import, add, install, or register a skill into OpenViking. " +
|
|
621
|
+
"Set source to a local SKILL.md file or skill directory, or data to raw SKILL.md content or an MCP tool dict.",
|
|
622
|
+
parameters: Type.Object({
|
|
623
|
+
source: Type.Optional(Type.String({ description: "Local SKILL.md path or skill directory path" })),
|
|
624
|
+
data: Type.Optional(Type.Any({ description: "Raw SKILL.md content or MCP tool dict" })),
|
|
625
|
+
wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
|
|
626
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
|
|
627
|
+
}),
|
|
628
|
+
async execute(_toolCallId, params) {
|
|
629
|
+
if (isBypassedSession(ctx)) {
|
|
630
|
+
return makeBypassedToolResult("add_skill");
|
|
631
|
+
}
|
|
632
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
633
|
+
return addSkillOpenViking({
|
|
634
|
+
source: typeof params.source === "string" ? params.source : undefined,
|
|
635
|
+
data: params.data,
|
|
636
|
+
wait: typeof params.wait === "boolean" ? params.wait : undefined,
|
|
637
|
+
timeout: typeof params.timeout === "number" ? params.timeout : undefined,
|
|
638
|
+
}, session.agentId);
|
|
639
|
+
},
|
|
640
|
+
}), { name: "add_skill" });
|
|
602
641
|
api.registerTool((ctx) => ({
|
|
603
642
|
name: "memory_search",
|
|
604
643
|
label: "Memory Search (OpenViking)",
|
|
@@ -621,22 +660,42 @@ const contextEnginePlugin = {
|
|
|
621
660
|
},
|
|
622
661
|
}), { name: "memory_search" });
|
|
623
662
|
api.registerCommand?.({
|
|
624
|
-
name: "
|
|
625
|
-
description: "
|
|
663
|
+
name: "add-resource",
|
|
664
|
+
description: "Add a resource into OpenViking.",
|
|
626
665
|
acceptsArgs: true,
|
|
627
666
|
handler: async (ctx) => {
|
|
628
667
|
try {
|
|
629
668
|
if (isBypassedSession(ctx)) {
|
|
630
|
-
const bypassed = makeBypassedToolResult("
|
|
669
|
+
const bypassed = makeBypassedToolResult("add_resource");
|
|
631
670
|
return { text: bypassed.content[0].text, details: bypassed.details };
|
|
632
671
|
}
|
|
633
672
|
const session = resolvePluginSessionRouting(ctx);
|
|
634
|
-
const input =
|
|
635
|
-
const result = await
|
|
673
|
+
const input = parseAddResourceCommandArgs(ctx.args ?? "");
|
|
674
|
+
const result = await addResourceOpenViking(input, session.agentId);
|
|
636
675
|
return { text: result.content[0].text, details: result.details };
|
|
637
676
|
}
|
|
638
677
|
catch (err) {
|
|
639
|
-
return { text: `OpenViking
|
|
678
|
+
return { text: `OpenViking add resource failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
api.registerCommand?.({
|
|
683
|
+
name: "add-skill",
|
|
684
|
+
description: "Add a skill into OpenViking.",
|
|
685
|
+
acceptsArgs: true,
|
|
686
|
+
handler: async (ctx) => {
|
|
687
|
+
try {
|
|
688
|
+
if (isBypassedSession(ctx)) {
|
|
689
|
+
const bypassed = makeBypassedToolResult("add_skill");
|
|
690
|
+
return { text: bypassed.content[0].text, details: bypassed.details };
|
|
691
|
+
}
|
|
692
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
693
|
+
const input = parseAddSkillCommandArgs(ctx.args ?? "");
|
|
694
|
+
const result = await addSkillOpenViking(input, session.agentId);
|
|
695
|
+
return { text: result.content[0].text, details: result.details };
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
return { text: `OpenViking add skill failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
640
699
|
}
|
|
641
700
|
},
|
|
642
701
|
});
|
|
@@ -734,29 +793,54 @@ const contextEnginePlugin = {
|
|
|
734
793
|
total: leafOnly.length,
|
|
735
794
|
};
|
|
736
795
|
}
|
|
737
|
-
const
|
|
738
|
-
|
|
796
|
+
const leafOnly = (result.memories ?? []).filter((m) => !m.level || m.level === 2);
|
|
797
|
+
const processed = postProcessMemories(leafOnly, {
|
|
798
|
+
limit: requestLimit,
|
|
739
799
|
scoreThreshold,
|
|
740
800
|
});
|
|
801
|
+
const memories = pickMemoriesForInjection(processed, limit, query);
|
|
741
802
|
if (memories.length === 0) {
|
|
742
803
|
return {
|
|
743
804
|
content: [{ type: "text", text: "No relevant OpenViking memories found." }],
|
|
744
805
|
details: { count: 0, total: result.total ?? 0, scoreThreshold },
|
|
745
806
|
};
|
|
746
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
|
+
}
|
|
747
830
|
return {
|
|
748
831
|
content: [
|
|
749
832
|
{
|
|
750
833
|
type: "text",
|
|
751
|
-
text: `Found ${
|
|
834
|
+
text: `Found ${memoryLines.length} memories:\n\n${memoryLines.join("\n")}`,
|
|
752
835
|
},
|
|
753
836
|
],
|
|
754
837
|
details: {
|
|
755
|
-
count:
|
|
838
|
+
count: memoryLines.length,
|
|
756
839
|
memories,
|
|
757
840
|
total: result.total ?? memories.length,
|
|
758
841
|
scoreThreshold,
|
|
759
842
|
requestLimit,
|
|
843
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
760
844
|
},
|
|
761
845
|
};
|
|
762
846
|
},
|
|
@@ -1103,6 +1187,242 @@ const contextEnginePlugin = {
|
|
|
1103
1187
|
}
|
|
1104
1188
|
},
|
|
1105
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" });
|
|
1106
1426
|
let contextEngineRef = null;
|
|
1107
1427
|
const sessionAgentResolver = createSessionAgentResolver(cfg.agent_prefix);
|
|
1108
1428
|
const rememberSessionAgentId = (ctx) => {
|