@openviking/openclaw-plugin 2026.5.10-dev.3 → 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 +13 -13
- package/INSTALL-ZH.md +11 -11
- package/INSTALL.md +11 -11
- package/README.md +53 -32
- package/client.ts +111 -0
- package/context-engine.ts +52 -15
- package/dist/client.js +30 -0
- package/dist/context-engine.js +40 -8
- package/dist/index.js +295 -5
- 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 +351 -5
- package/openclaw.plugin.json +4 -1
- package/package.json +3 -2
- package/process-manager.ts +0 -124
- package/runtime-utils.ts +0 -3
- package/skills/install-openviking-memory/SKILL.md +9 -9
- package/text-utils.ts +35 -52
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
|
|
@@ -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) => {
|
package/dist/process-manager.js
CHANGED
|
@@ -1,66 +1,3 @@
|
|
|
1
|
-
import { Socket } from "node:net";
|
|
2
|
-
export function waitForHealth(baseUrl, timeoutMs, intervalMs) {
|
|
3
|
-
const deadline = Date.now() + timeoutMs;
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
const tick = () => {
|
|
6
|
-
if (Date.now() > deadline) {
|
|
7
|
-
reject(new Error(`OpenViking health check timeout at ${baseUrl}`));
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
fetch(`${baseUrl}/health`)
|
|
11
|
-
.then((r) => r.json())
|
|
12
|
-
.then((body) => {
|
|
13
|
-
if (body?.status === "ok") {
|
|
14
|
-
resolve();
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
setTimeout(tick, intervalMs);
|
|
18
|
-
})
|
|
19
|
-
.catch(() => setTimeout(tick, intervalMs));
|
|
20
|
-
};
|
|
21
|
-
tick();
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
export function waitForHealthOrExit(baseUrl, timeoutMs, intervalMs, child) {
|
|
25
|
-
const exited = child.killed || child.exitCode !== null || child.signalCode !== null;
|
|
26
|
-
if (exited) {
|
|
27
|
-
return Promise.reject(new Error(`OpenViking subprocess exited before health check ` +
|
|
28
|
-
`(code=${child.exitCode}, signal=${child.signalCode})`));
|
|
29
|
-
}
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
let settled = false;
|
|
32
|
-
const cleanup = () => {
|
|
33
|
-
child.off?.("error", onError);
|
|
34
|
-
child.off?.("exit", onExit);
|
|
35
|
-
};
|
|
36
|
-
const finishResolve = () => {
|
|
37
|
-
if (settled) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
settled = true;
|
|
41
|
-
cleanup();
|
|
42
|
-
resolve();
|
|
43
|
-
};
|
|
44
|
-
const finishReject = (err) => {
|
|
45
|
-
if (settled) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
settled = true;
|
|
49
|
-
cleanup();
|
|
50
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
51
|
-
};
|
|
52
|
-
const onError = (err) => {
|
|
53
|
-
finishReject(err);
|
|
54
|
-
};
|
|
55
|
-
const onExit = (code, signal) => {
|
|
56
|
-
finishReject(new Error(`OpenViking subprocess exited before health check ` +
|
|
57
|
-
`(code=${code}, signal=${signal})`));
|
|
58
|
-
};
|
|
59
|
-
child.once("error", onError);
|
|
60
|
-
child.once("exit", onExit);
|
|
61
|
-
waitForHealth(baseUrl, timeoutMs, intervalMs).then(finishResolve, finishReject);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
1
|
export function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
65
2
|
return new Promise((resolve, reject) => {
|
|
66
3
|
const timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
|
@@ -73,30 +10,6 @@ export function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
73
10
|
});
|
|
74
11
|
});
|
|
75
12
|
}
|
|
76
|
-
export function quickTcpProbe(host, port, timeoutMs) {
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
const socket = new Socket();
|
|
79
|
-
let done = false;
|
|
80
|
-
const finish = (ok) => {
|
|
81
|
-
if (done) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
done = true;
|
|
85
|
-
socket.destroy();
|
|
86
|
-
resolve(ok);
|
|
87
|
-
};
|
|
88
|
-
socket.setTimeout(timeoutMs);
|
|
89
|
-
socket.once("connect", () => finish(true));
|
|
90
|
-
socket.once("timeout", () => finish(false));
|
|
91
|
-
socket.once("error", () => finish(false));
|
|
92
|
-
try {
|
|
93
|
-
socket.connect(port, host);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
finish(false);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
13
|
export async function quickHealthCheck(baseUrl, timeoutMs) {
|
|
101
14
|
const controller = new AbortController();
|
|
102
15
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|