@openviking/openclaw-plugin 2026.6.2 → 2026.6.17-dev.2

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.
Files changed (108) hide show
  1. package/INSTALL-AGENT.md +4 -4
  2. package/INSTALL-ZH.md +68 -8
  3. package/INSTALL.md +12 -8
  4. package/README.md +45 -35
  5. package/adapters/http-transport.ts +3 -0
  6. package/adapters/resource-packager.ts +141 -0
  7. package/auto-recall.ts +258 -27
  8. package/client.ts +201 -177
  9. package/commands/setup.ts +200 -33
  10. package/config/feature-gates.json +10 -0
  11. package/config.ts +428 -85
  12. package/context-engine.ts +71 -1402
  13. package/dist/adapters/http-transport.js +1 -0
  14. package/dist/adapters/resource-packager.js +114 -0
  15. package/dist/auto-recall.js +201 -25
  16. package/dist/client.js +125 -161
  17. package/dist/commands/setup.js +170 -32
  18. package/dist/config.js +285 -64
  19. package/dist/context-engine.js +62 -1173
  20. package/dist/index.js +209 -1489
  21. package/dist/memory-ranking.js +35 -12
  22. package/dist/plugin/command-registration.js +5 -0
  23. package/dist/plugin/openviking-archive-tools.js +210 -0
  24. package/dist/plugin/openviking-bypass-runtime.js +8 -0
  25. package/dist/plugin/openviking-client-runtime.js +28 -0
  26. package/dist/plugin/openviking-command-args.js +146 -0
  27. package/dist/plugin/openviking-command-definitions.js +206 -0
  28. package/dist/plugin/openviking-context-engine-ref.js +11 -0
  29. package/dist/plugin/openviking-context-engine-registration.js +23 -0
  30. package/dist/plugin/openviking-feature-gates.js +250 -0
  31. package/dist/plugin/openviking-import-runtime.js +50 -0
  32. package/dist/plugin/openviking-import-tools.js +86 -0
  33. package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
  34. package/dist/plugin/openviking-memory-recall-tools.js +233 -0
  35. package/dist/plugin/openviking-memory-tools.js +199 -0
  36. package/dist/plugin/openviking-query-config-command.js +203 -0
  37. package/dist/plugin/openviking-query-runtime.js +367 -0
  38. package/dist/plugin/openviking-query-tools.js +91 -0
  39. package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
  40. package/dist/plugin/openviking-recall-trace-tools.js +38 -0
  41. package/dist/plugin/openviking-runtime-state.js +26 -0
  42. package/dist/plugin/openviking-runtime-utils.js +62 -0
  43. package/dist/plugin/openviking-services.js +20 -0
  44. package/dist/plugin/openviking-session-routing-runtime.js +61 -0
  45. package/dist/plugin/openviking-tool-result-tools.js +268 -0
  46. package/dist/plugin/recall-trace-routes.js +77 -0
  47. package/dist/plugin/tool-registration.js +23 -0
  48. package/dist/process-manager.js +1 -1
  49. package/dist/query-config.js +336 -0
  50. package/dist/recall-trace.js +309 -0
  51. package/dist/registries/recall-resource-types.js +58 -0
  52. package/dist/routing/identity-routing.js +152 -0
  53. package/dist/routing/memory-uri.js +7 -0
  54. package/dist/services/context-lifecycle-service.js +847 -0
  55. package/dist/services/context-message-adapter.js +340 -0
  56. package/dist/session-transcript-repair.js +0 -1
  57. package/dist/text-utils.js +14 -31
  58. package/index.ts +271 -1873
  59. package/install-manifest.json +10 -1
  60. package/memory-ranking.ts +46 -10
  61. package/openclaw.plugin.json +198 -36
  62. package/package.json +14 -4
  63. package/plugin/command-registration.ts +34 -0
  64. package/plugin/openviking-archive-tools.ts +301 -0
  65. package/plugin/openviking-bypass-runtime.ts +26 -0
  66. package/plugin/openviking-client-runtime.ts +74 -0
  67. package/plugin/openviking-command-args.ts +187 -0
  68. package/plugin/openviking-command-definitions.ts +294 -0
  69. package/plugin/openviking-context-engine-ref.ts +16 -0
  70. package/plugin/openviking-context-engine-registration.ts +97 -0
  71. package/plugin/openviking-feature-gates.ts +335 -0
  72. package/plugin/openviking-import-runtime.ts +105 -0
  73. package/plugin/openviking-import-tools.ts +133 -0
  74. package/plugin/openviking-lifecycle-hooks.ts +65 -0
  75. package/plugin/openviking-memory-recall-tools.ts +368 -0
  76. package/plugin/openviking-memory-tools.ts +299 -0
  77. package/plugin/openviking-query-config-command.ts +230 -0
  78. package/plugin/openviking-query-formatters.ts +108 -0
  79. package/plugin/openviking-query-runtime.ts +476 -0
  80. package/plugin/openviking-query-tools.ts +161 -0
  81. package/plugin/openviking-recall-trace-runtime.ts +482 -0
  82. package/plugin/openviking-recall-trace-tools.ts +99 -0
  83. package/plugin/openviking-runtime-state.ts +52 -0
  84. package/plugin/openviking-runtime-utils.ts +67 -0
  85. package/plugin/openviking-services.ts +49 -0
  86. package/plugin/openviking-session-routing-runtime.ts +105 -0
  87. package/plugin/openviking-tool-result-tools.ts +397 -0
  88. package/plugin/recall-trace-routes.ts +133 -0
  89. package/plugin/tool-registration.ts +51 -0
  90. package/process-manager.ts +1 -1
  91. package/query-config.ts +400 -0
  92. package/recall-trace.ts +442 -0
  93. package/registries/openviking-tools.ts +84 -0
  94. package/registries/recall-resource-types.ts +74 -0
  95. package/routing/identity-routing.ts +210 -0
  96. package/routing/memory-uri.ts +8 -0
  97. package/services/context-lifecycle-service.ts +1209 -0
  98. package/services/context-message-adapter.ts +375 -0
  99. package/services/setup/config-writer.ts +125 -0
  100. package/services/setup/exit-utils.ts +5 -0
  101. package/services/setup/package-metadata.ts +56 -0
  102. package/services/setup/probe-service.ts +143 -0
  103. package/services/setup/setup-flow.ts +306 -0
  104. package/services/setup/version-compatibility.ts +41 -0
  105. package/session-transcript-repair.ts +0 -2
  106. package/skills/install-openviking-memory/SKILL.md +123 -50
  107. package/skills/openviking-context-database/SKILL.md +286 -0
  108. package/text-utils.ts +15 -34
@@ -0,0 +1,367 @@
1
+ function mergeFindResults(results) {
2
+ const deduplicate = (items) => {
3
+ const seen = new Map();
4
+ for (const item of items) {
5
+ if (!seen.has(item.uri)) {
6
+ seen.set(item.uri, item);
7
+ }
8
+ }
9
+ return Array.from(seen.values());
10
+ };
11
+ const memories = deduplicate(results.flatMap((result) => result.memories ?? []));
12
+ const resources = deduplicate(results.flatMap((result) => result.resources ?? []));
13
+ const skills = deduplicate(results.flatMap((result) => result.skills ?? []));
14
+ return {
15
+ memories,
16
+ resources,
17
+ skills,
18
+ total: memories.length + resources.length + skills.length,
19
+ };
20
+ }
21
+ function formatOVSearchRows(result) {
22
+ const truncateSummary = (value, maxChars = 220) => {
23
+ const collapsed = value.replace(/\s+/g, " ").trim();
24
+ if (collapsed.length <= maxChars) {
25
+ return collapsed;
26
+ }
27
+ return `${collapsed.slice(0, maxChars - 3)}...`;
28
+ };
29
+ const items = [
30
+ ...(result.memories ?? []).map((item) => ({ contextType: "memory", item })),
31
+ ...(result.resources ?? []).map((item) => ({ contextType: "resource", item })),
32
+ ...(result.skills ?? []).map((item) => ({ contextType: "skill", item })),
33
+ ];
34
+ if (items.length === 0) {
35
+ return [];
36
+ }
37
+ const numberHeader = "no";
38
+ const numberWidth = Math.max(numberHeader.length, String(items.length).length);
39
+ const typeWidth = Math.max("type".length, ...items.map(({ contextType }) => contextType.length));
40
+ const uriWidth = Math.max("uri".length, ...items.map(({ item }) => item.uri.length));
41
+ const levelWidth = Math.max("level".length, ...items.map(({ item }) => String(item.level ?? "").length));
42
+ const scoreWidth = Math.max("score".length, ...items.map(({ item }) => (typeof item.score === "number" ? item.score.toFixed(2).length : 0)));
43
+ return [
44
+ `${numberHeader.padEnd(numberWidth)} ${"type".padEnd(typeWidth)} ${"uri".padEnd(uriWidth)} ${"level".padEnd(levelWidth)} ${"score".padEnd(scoreWidth)} abstract`,
45
+ ...items.map(({ contextType, item }, index) => {
46
+ const score = typeof item.score === "number" ? item.score.toFixed(2) : "";
47
+ const summary = truncateSummary(item.abstract || item.overview || "(no summary)");
48
+ return `${String(index + 1).padEnd(numberWidth)} ${contextType.padEnd(typeWidth)} ${item.uri.padEnd(uriWidth)} ${String(item.level ?? "").padEnd(levelWidth)} ${score.padEnd(scoreWidth)} ${summary}`;
49
+ }),
50
+ ];
51
+ }
52
+ function formatOVSearchText(query, uri, result) {
53
+ if ((result.total ?? 0) <= 0) {
54
+ const scope = uri ? ` under ${uri}` : "";
55
+ return `No OpenViking resource or skill results found for "${query}"${scope}.`;
56
+ }
57
+ const scope = uri ? ` under ${uri}` : "";
58
+ const lines = [
59
+ `Found ${result.total ?? 0} OpenViking results for "${query}"${scope}`,
60
+ "Tip: search results are ranked snippets. Use ov_read on exact hit URIs before answering precise questions. Use ov_list on a hit's parent URI to inspect sibling chunks or overview files before answering procedural or multi-step questions.",
61
+ "",
62
+ ...formatOVSearchRows(result),
63
+ "",
64
+ "Note: result URIs are OpenViking virtual URIs, not local file paths. Use the ov_read tool with the exact viking:// URI to read full content; do not use filesystem read tools for these URIs.",
65
+ ].filter((line, index, all) => line || (all[index - 1] && all[index + 1]));
66
+ return lines.join("\n");
67
+ }
68
+ function validateOpenVikingUri(toolName, uri) {
69
+ if (!uri) {
70
+ throw new Error("uri is required");
71
+ }
72
+ if (!uri.startsWith("viking://")) {
73
+ throw new Error(`${toolName} only accepts OpenViking viking:// URIs, not local file paths or openviking:// display aliases`);
74
+ }
75
+ if (uri.endsWith("...") || uri.includes("…")) {
76
+ throw new Error(`${toolName} received a truncated display URI. Use the exact full viking:// URI from ov_search details/results; do not shorten it with ... or ….`);
77
+ }
78
+ }
79
+ function formatOVListEntry(entry) {
80
+ if (typeof entry === "string") {
81
+ return entry;
82
+ }
83
+ if (!entry || typeof entry !== "object") {
84
+ return String(entry);
85
+ }
86
+ const item = entry;
87
+ const uri = typeof item.uri === "string" ? item.uri : "";
88
+ const name = typeof item.name === "string" ? item.name : "";
89
+ const isDir = item.isDir === true || item.type === "directory";
90
+ const marker = isDir ? "[dir]" : "[file]";
91
+ const summary = typeof item.abstract === "string" && item.abstract.trim()
92
+ ? item.abstract.trim().replace(/\s+/g, " ")
93
+ : typeof item.overview === "string" && item.overview.trim()
94
+ ? item.overview.trim().replace(/\s+/g, " ")
95
+ : "";
96
+ const label = uri || name || JSON.stringify(item);
97
+ return summary ? `${marker} ${label} - ${summary}` : `${marker} ${label}`;
98
+ }
99
+ function formatOVListText(uri, entries) {
100
+ if (entries.length === 0) {
101
+ return `No OpenViking entries found under ${uri}.`;
102
+ }
103
+ return [
104
+ `Listed ${entries.length} OpenViking entr${entries.length === 1 ? "y" : "ies"} under ${uri}`,
105
+ "",
106
+ ...entries.map((entry) => formatOVListEntry(entry)),
107
+ ].join("\n");
108
+ }
109
+ function formatOVReadText(uri, content) {
110
+ const body = content || "(empty OpenViking content)";
111
+ return [`--- START OF ${uri} ---`, body, `--- END OF ${uri} ---`].join("\n");
112
+ }
113
+ function formatOVMultiReadText(results) {
114
+ return [
115
+ `Multi-read results for ${results.length} OpenViking resource${results.length === 1 ? "" : "s"}:`,
116
+ "",
117
+ ...results.flatMap((result) => [
118
+ `--- START OF ${result.uri} ---`,
119
+ result.success ? (result.content || "(empty OpenViking content)") : `ERROR: ${result.content}`,
120
+ `--- END OF ${result.uri} ---`,
121
+ "",
122
+ ]),
123
+ ].join("\n").trimEnd();
124
+ }
125
+ export function createOpenVikingQueryRuntime(deps) {
126
+ const toTraceResult = (item, resultType) => ({
127
+ uri: item.uri,
128
+ resourceType: deps.inferRecallResourceType(item.uri),
129
+ category: item.category,
130
+ score: item.score,
131
+ level: item.level,
132
+ abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
133
+ resultType,
134
+ });
135
+ const readOpenVikingContent = async (input, agentId) => {
136
+ const uri = input.uri.trim();
137
+ validateOpenVikingUri("ov_read", uri);
138
+ const client = await deps.getClient();
139
+ const content = await client.read(uri, agentId);
140
+ const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
141
+ return {
142
+ content: [{ type: "text", text: formatOVReadText(uri, text) }],
143
+ details: {
144
+ action: "read",
145
+ uri,
146
+ chars: text.length,
147
+ },
148
+ };
149
+ };
150
+ const multiReadOpenVikingContent = async (input, agentId) => {
151
+ const uris = input.uris
152
+ .map((uri) => (typeof uri === "string" ? uri.trim() : ""))
153
+ .filter((uri) => uri.length > 0);
154
+ if (uris.length === 0) {
155
+ throw new Error("uris is required");
156
+ }
157
+ for (const uri of uris) {
158
+ validateOpenVikingUri("ov_multi_read", uri);
159
+ }
160
+ const client = await deps.getClient();
161
+ const results = await Promise.all(uris.map(async (uri) => {
162
+ try {
163
+ const content = await client.read(uri, agentId);
164
+ const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
165
+ return {
166
+ uri,
167
+ content: text,
168
+ success: true,
169
+ chars: text.length,
170
+ };
171
+ }
172
+ catch (err) {
173
+ return {
174
+ uri,
175
+ content: err instanceof Error ? err.message : String(err),
176
+ success: false,
177
+ chars: 0,
178
+ };
179
+ }
180
+ }));
181
+ return {
182
+ content: [{ type: "text", text: formatOVMultiReadText(results) }],
183
+ details: {
184
+ action: "multi_read",
185
+ count: results.length,
186
+ success_count: results.filter((result) => result.success).length,
187
+ results,
188
+ },
189
+ };
190
+ };
191
+ const listOpenVikingDirectory = async (input, agentId) => {
192
+ const uri = input.uri.trim();
193
+ validateOpenVikingUri("ov_list", uri);
194
+ const limit = Math.max(1, Math.floor(input.limit ?? 100));
195
+ const client = await deps.getClient();
196
+ const entries = await client.list(uri, {
197
+ recursive: input.recursive ?? false,
198
+ simple: input.simple ?? false,
199
+ nodeLimit: limit,
200
+ }, agentId);
201
+ return {
202
+ content: [{ type: "text", text: formatOVListText(uri, entries) }],
203
+ details: {
204
+ action: "listed",
205
+ uri,
206
+ recursive: input.recursive ?? false,
207
+ simple: input.simple ?? false,
208
+ count: entries.length,
209
+ entries,
210
+ },
211
+ };
212
+ };
213
+ const searchOpenViking = async (input, agentId, traceCtx) => {
214
+ const query = input.query.trim();
215
+ if (!query) {
216
+ throw new Error("query is required");
217
+ }
218
+ const queryConfig = traceCtx
219
+ ? await deps.queryConfigStore.getEffective(deps.toQueryConfigContext(traceCtx), {
220
+ ovSearchLimit: typeof input.limit === "number" ? input.limit : undefined,
221
+ targetUri: input.uri,
222
+ })
223
+ : undefined;
224
+ const limit = Math.max(1, Math.floor(input.limit ?? queryConfig?.ovSearchLimit ?? 10));
225
+ const searchUri = input.uri ?? queryConfig?.targetUri;
226
+ const client = await deps.getClient();
227
+ let result;
228
+ const searches = [];
229
+ if (searchUri) {
230
+ const started = Date.now();
231
+ result = await client.find(query, { targetUri: searchUri, limit }, agentId);
232
+ const items = [
233
+ ...(result.memories ?? []).map((item) => toTraceResult(item, "memory")),
234
+ ...(result.resources ?? []).map((item) => toTraceResult(item, "resource")),
235
+ ...(result.skills ?? []).map((item) => toTraceResult(item, "skill")),
236
+ ].slice(0, deps.cfg.traceRecallMaxResultsPerSearch);
237
+ searches.push({
238
+ resourceType: deps.inferRecallResourceType(searchUri) ?? "resource",
239
+ targetUriInput: searchUri,
240
+ targetUriResolved: searchUri,
241
+ limit,
242
+ durationMs: Date.now() - started,
243
+ total: result.total ?? items.length,
244
+ results: items,
245
+ });
246
+ }
247
+ else {
248
+ const runSearch = async (targetUri) => {
249
+ const started = Date.now();
250
+ try {
251
+ const found = await client.find(query, { targetUri, limit }, agentId);
252
+ const items = [
253
+ ...(found.memories ?? []).map((item) => toTraceResult(item, "memory")),
254
+ ...(found.resources ?? []).map((item) => toTraceResult(item, "resource")),
255
+ ...(found.skills ?? []).map((item) => toTraceResult(item, "skill")),
256
+ ].slice(0, deps.cfg.traceRecallMaxResultsPerSearch);
257
+ searches.push({
258
+ resourceType: deps.inferRecallResourceType(targetUri) ?? "resource",
259
+ targetUriResolved: targetUri,
260
+ limit,
261
+ durationMs: Date.now() - started,
262
+ total: found.total ?? items.length,
263
+ results: items,
264
+ });
265
+ return found;
266
+ }
267
+ catch (err) {
268
+ searches.push({
269
+ resourceType: deps.inferRecallResourceType(targetUri) ?? "resource",
270
+ targetUriResolved: targetUri,
271
+ limit,
272
+ durationMs: Date.now() - started,
273
+ total: 0,
274
+ results: [],
275
+ error: err instanceof Error ? err.message : String(err),
276
+ });
277
+ throw err;
278
+ }
279
+ };
280
+ const [resourcesSettled, skillsSettled] = await Promise.allSettled([
281
+ runSearch("viking://resources"),
282
+ runSearch("viking://user/skills"),
283
+ ]);
284
+ const successful = [];
285
+ if (resourcesSettled.status === "fulfilled") {
286
+ successful.push(resourcesSettled.value);
287
+ }
288
+ if (skillsSettled.status === "fulfilled") {
289
+ successful.push(skillsSettled.value);
290
+ }
291
+ if (successful.length === 0) {
292
+ const firstError = resourcesSettled.status === "rejected"
293
+ ? resourcesSettled.reason
294
+ : skillsSettled.status === "rejected"
295
+ ? skillsSettled.reason
296
+ : "Both searches failed";
297
+ throw firstError instanceof Error ? firstError : new Error(String(firstError));
298
+ }
299
+ if (resourcesSettled.status === "rejected") {
300
+ deps.logger.warn?.(`openviking: resource search failed: ${String(resourcesSettled.reason)}`);
301
+ }
302
+ if (skillsSettled.status === "rejected") {
303
+ deps.logger.warn?.(`openviking: skill search failed: ${String(skillsSettled.reason)}`);
304
+ }
305
+ result = mergeFindResults(successful);
306
+ }
307
+ const selected = [
308
+ ...(result.memories ?? []).map((item) => ({
309
+ uri: item.uri,
310
+ resourceType: deps.inferRecallResourceType(item.uri),
311
+ category: item.category,
312
+ score: item.score,
313
+ abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
314
+ displayed: true,
315
+ })),
316
+ ...(result.resources ?? []).map((item) => ({
317
+ uri: item.uri,
318
+ resourceType: deps.inferRecallResourceType(item.uri),
319
+ category: item.category,
320
+ score: item.score,
321
+ abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
322
+ displayed: true,
323
+ })),
324
+ ...(result.skills ?? []).map((item) => ({
325
+ uri: item.uri,
326
+ resourceType: deps.inferRecallResourceType(item.uri),
327
+ category: item.category,
328
+ score: item.score,
329
+ abstractPreview: deps.previewText(item.abstract || item.overview, deps.cfg.traceRecallPreviewChars),
330
+ displayed: true,
331
+ })),
332
+ ];
333
+ await deps.traceRecorder?.recordAndFlush?.({
334
+ schemaVersion: "1.0",
335
+ traceId: deps.createTraceId("ov_search"),
336
+ ts: Date.now(),
337
+ sessionId: traceCtx?.sessionId,
338
+ sessionKey: traceCtx?.sessionKey,
339
+ ovSessionId: traceCtx?.ovSessionId,
340
+ agentId,
341
+ source: "ov_search",
342
+ operationType: "semantic_find",
343
+ resourceTypes: [...new Set(searches.map((search) => search.resourceType).filter((resourceType) => resourceType !== "archive"))],
344
+ trigger: deps.boundTraceQuery(query, deps.cfg.traceRecallQueryMaxChars),
345
+ searches,
346
+ selected,
347
+ stats: {
348
+ candidateCount: searches.reduce((sum, search) => sum + search.results.length, 0),
349
+ selectedCount: selected.length,
350
+ injectedCount: 0,
351
+ },
352
+ });
353
+ return {
354
+ content: [{ type: "text", text: formatOVSearchText(query, searchUri, result) }],
355
+ details: {
356
+ action: "searched",
357
+ query,
358
+ uri: searchUri,
359
+ memories: result.memories ?? [],
360
+ resources: result.resources ?? [],
361
+ skills: result.skills ?? [],
362
+ total: result.total ?? 0,
363
+ },
364
+ };
365
+ };
366
+ return { readOpenVikingContent, multiReadOpenVikingContent, listOpenVikingDirectory, searchOpenViking };
367
+ }
@@ -0,0 +1,91 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ export function registerOpenVikingQueryTools(deps) {
3
+ deps.registerTool((ctx) => ({
4
+ name: "ov_search",
5
+ label: "Search (OpenViking)",
6
+ description: "Search OpenViking resources and skills. Use after importing, or when the user asks to search OpenViking resources or skills. " +
7
+ "Search only returns ranked snippets; call ov_read on exact hit URIs before answering precise questions. " +
8
+ "When a result is part of a split document or a multi-step procedure, call ov_list on the parent URI to inspect sibling chunks and overview files before answering. " +
9
+ "Returned viking:// URIs are OpenViking virtual URIs, not local file paths.",
10
+ parameters: Type.Object({
11
+ query: Type.String({ description: "Search query" }),
12
+ uri: Type.Optional(Type.String({ description: "Optional search URI. Defaults to resources plus agent skills." })),
13
+ limit: Type.Optional(Type.Number({ description: "Max results per search scope. Default: 10" })),
14
+ }),
15
+ async execute(_toolCallId, params) {
16
+ if (deps.isBypassedSession(ctx)) {
17
+ return deps.makeBypassedToolResult("ov_search");
18
+ }
19
+ const session = deps.resolvePluginSessionRouting(ctx);
20
+ return deps.searchOpenViking({
21
+ query: String(params.query ?? ""),
22
+ uri: typeof params.uri === "string" ? params.uri : undefined,
23
+ limit: typeof params.limit === "number" ? params.limit : undefined,
24
+ }, session.agentId, session);
25
+ },
26
+ }), { name: "ov_search" });
27
+ deps.registerTool((ctx) => ({
28
+ name: "ov_read",
29
+ label: "Read (OpenViking)",
30
+ description: "Read the full original content of one exact OpenViking viking:// URI returned by ov_search, ov_list, or recall traces. " +
31
+ "Use after ov_search before answering precise documentation, codebase, configuration, or procedural questions. " +
32
+ "OpenViking URIs are virtual context-database identifiers, not local file paths; do not use filesystem read tools for them. " +
33
+ "Never pass shortened or display-truncated URIs ending with ... or containing …. Use the exact full URI.",
34
+ parameters: Type.Object({
35
+ uri: Type.String({ description: "Exact viking:// URI returned by ov_search; pass the full URI, e.g. viking://resources/project-docs/api.md#chunk-3; do not use shortened display text with ... or …." }),
36
+ }),
37
+ async execute(_toolCallId, params) {
38
+ if (deps.isBypassedSession(ctx)) {
39
+ return deps.makeBypassedToolResult("ov_read");
40
+ }
41
+ const session = deps.resolvePluginSessionRouting(ctx);
42
+ return deps.readOpenVikingContent({
43
+ uri: String(params.uri ?? ""),
44
+ }, session.agentId);
45
+ },
46
+ }), { name: "ov_read" });
47
+ deps.registerTool((ctx) => ({
48
+ name: "ov_multi_read",
49
+ label: "Multi Read (OpenViking)",
50
+ description: "Read the full original content of multiple exact OpenViking URIs concurrently. " +
51
+ "Use after ov_search and ov_list to read an overview plus sibling chunks for split documents or multi-step procedures.",
52
+ parameters: Type.Object({
53
+ uris: Type.Array(Type.String({ description: "Exact OpenViking viking:// URI to read" }), {
54
+ description: "Exact OpenViking viking:// URIs to read",
55
+ }),
56
+ }),
57
+ async execute(_toolCallId, params) {
58
+ if (deps.isBypassedSession(ctx)) {
59
+ return deps.makeBypassedToolResult("ov_multi_read");
60
+ }
61
+ const session = deps.resolvePluginSessionRouting(ctx);
62
+ const uris = Array.isArray(params.uris)
63
+ ? params.uris.map((uri) => String(uri))
64
+ : [];
65
+ return deps.multiReadOpenVikingContent({ uris }, session.agentId);
66
+ },
67
+ }), { name: "ov_multi_read" });
68
+ deps.registerTool((ctx) => ({
69
+ name: "ov_list",
70
+ label: "List (OpenViking)",
71
+ description: "List files and directories under an OpenViking URI. Use after ov_search to inspect a hit's parent directory, sibling chunks, or .overview.md files when search only returns ranked snippets.",
72
+ parameters: Type.Object({
73
+ uri: Type.String({ description: "OpenViking directory URI to list, e.g. viking://resources/project/docs" }),
74
+ recursive: Type.Optional(Type.Boolean({ description: "List nested entries recursively. Default: false" })),
75
+ simple: Type.Optional(Type.Boolean({ description: "Return only URI entries from OpenViking. Default: false" })),
76
+ limit: Type.Optional(Type.Number({ description: "Maximum entries to list. Default: 100" })),
77
+ }),
78
+ async execute(_toolCallId, params) {
79
+ if (deps.isBypassedSession(ctx)) {
80
+ return deps.makeBypassedToolResult("ov_list");
81
+ }
82
+ const session = deps.resolvePluginSessionRouting(ctx);
83
+ return deps.listOpenVikingDirectory({
84
+ uri: String(params.uri ?? ""),
85
+ recursive: typeof params.recursive === "boolean" ? params.recursive : undefined,
86
+ simple: typeof params.simple === "boolean" ? params.simple : undefined,
87
+ limit: typeof params.limit === "number" ? params.limit : undefined,
88
+ }, session.agentId);
89
+ },
90
+ }), { name: "ov_list" });
91
+ }