@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/text-utils.js
CHANGED
|
@@ -21,7 +21,6 @@ const NON_CONTENT_TEXT_RE = /^[\p{P}\p{S}\s]+$/u;
|
|
|
21
21
|
const SUBAGENT_CONTEXT_RE = /^\s*\[Subagent Context\]/i;
|
|
22
22
|
const MEMORY_INTENT_RE = /记住|记下|remember|save|store|偏好|preference|规则|rule|事实|fact/i;
|
|
23
23
|
const QUESTION_CUE_RE = /[??]|\b(?:what|when|where|who|why|how|which|can|could|would|did|does|is|are)\b|^(?:请问|能否|可否|怎么|如何|什么时候|谁|什么|哪|是否)/i;
|
|
24
|
-
export const CAPTURE_LIMIT = 3;
|
|
25
24
|
function resolveCaptureMinLength(text) {
|
|
26
25
|
return CJK_CHAR_REGEX.test(text) ? 4 : 10;
|
|
27
26
|
}
|
|
@@ -232,21 +231,6 @@ export function extractTextsFromUserMessages(messages) {
|
|
|
232
231
|
}
|
|
233
232
|
return texts;
|
|
234
233
|
}
|
|
235
|
-
function formatToolUseBlock(b) {
|
|
236
|
-
const name = typeof b.name === "string" ? b.name : "unknown";
|
|
237
|
-
let inputStr = "";
|
|
238
|
-
if (b.input !== undefined && b.input !== null) {
|
|
239
|
-
try {
|
|
240
|
-
inputStr = typeof b.input === "string" ? b.input : JSON.stringify(b.input);
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
inputStr = String(b.input);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return inputStr
|
|
247
|
-
? `[toolUse: ${name}]\n${inputStr}`
|
|
248
|
-
: `[toolUse: ${name}]`;
|
|
249
|
-
}
|
|
250
234
|
function formatToolResultContent(content) {
|
|
251
235
|
if (typeof content === "string")
|
|
252
236
|
return content.trim();
|
|
@@ -270,40 +254,6 @@ function formatToolResultContent(content) {
|
|
|
270
254
|
}
|
|
271
255
|
return "";
|
|
272
256
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Extract text from a single message without a `[role]:` prefix.
|
|
275
|
-
* Used by afterTurn to send messages with their actual role.
|
|
276
|
-
*/
|
|
277
|
-
export function extractSingleMessageText(msg) {
|
|
278
|
-
if (!msg || typeof msg !== "object")
|
|
279
|
-
return "";
|
|
280
|
-
const m = msg;
|
|
281
|
-
const role = m.role;
|
|
282
|
-
if (!role || role === "system")
|
|
283
|
-
return "";
|
|
284
|
-
if (role === "toolResult") {
|
|
285
|
-
const toolName = typeof m.toolName === "string" ? m.toolName : "tool";
|
|
286
|
-
const resultText = formatToolResultContent(m.content);
|
|
287
|
-
return resultText ? `[${toolName} result]: ${resultText}` : "";
|
|
288
|
-
}
|
|
289
|
-
const content = m.content;
|
|
290
|
-
if (typeof content === "string")
|
|
291
|
-
return content.trim();
|
|
292
|
-
if (Array.isArray(content)) {
|
|
293
|
-
const parts = [];
|
|
294
|
-
for (const block of content) {
|
|
295
|
-
const b = block;
|
|
296
|
-
if (b?.type === "text" && typeof b.text === "string") {
|
|
297
|
-
parts.push(b.text.trim());
|
|
298
|
-
}
|
|
299
|
-
else if (b?.type === "toolUse") {
|
|
300
|
-
parts.push(formatToolUseBlock(b));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return parts.join("\n");
|
|
304
|
-
}
|
|
305
|
-
return "";
|
|
306
|
-
}
|
|
307
257
|
/**
|
|
308
258
|
* 提取消息中的一个 part 的文本内容,并清理时间戳等噪音
|
|
309
259
|
*/
|
|
@@ -394,7 +344,6 @@ export function extractNewTurnMessages(messages, startIndex) {
|
|
|
394
344
|
continue;
|
|
395
345
|
}
|
|
396
346
|
// user/assistant -> type: "text"
|
|
397
|
-
// 统一 role 为 user
|
|
398
347
|
const content = msg.content;
|
|
399
348
|
const text = extractPartText(content);
|
|
400
349
|
if (text) {
|
|
@@ -412,6 +361,37 @@ export function extractNewTurnMessages(messages, startIndex) {
|
|
|
412
361
|
});
|
|
413
362
|
}
|
|
414
363
|
}
|
|
364
|
+
else {
|
|
365
|
+
/// 如果原始消息有 toolCall,提取所有工具名并生成占位符
|
|
366
|
+
if (role === "assistant" && Array.isArray(content)) {
|
|
367
|
+
const toolNames = [];
|
|
368
|
+
// 收集所有 toolCall 的 tool_name
|
|
369
|
+
for (const block of content) {
|
|
370
|
+
const b = block;
|
|
371
|
+
const blockType = b?.type;
|
|
372
|
+
if (blockType === "toolCall" ||
|
|
373
|
+
blockType === "toolUse" ||
|
|
374
|
+
blockType === "tool_use" ||
|
|
375
|
+
blockType === "tool_call") {
|
|
376
|
+
const name = b?.name || b?.toolName;
|
|
377
|
+
if (name && typeof name === "string" && name.trim()) {
|
|
378
|
+
toolNames.push(name.trim());
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// 只有找到 toolCall 时才添加占位符
|
|
383
|
+
if (toolNames.length > 0) {
|
|
384
|
+
const toolNamesStr = toolNames.join(", ");
|
|
385
|
+
result.push({
|
|
386
|
+
role: "assistant",
|
|
387
|
+
parts: [{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: `[tool: ${toolNamesStr}]`,
|
|
390
|
+
}],
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
415
395
|
}
|
|
416
396
|
return { messages: result, newCount: count };
|
|
417
397
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
clampScore,
|
|
24
24
|
postProcessMemories,
|
|
25
|
-
|
|
25
|
+
pickMemoriesForInjection,
|
|
26
26
|
} from "./memory-ranking.js";
|
|
27
27
|
import { withTimeout } from "./process-manager.js";
|
|
28
28
|
import {
|
|
@@ -150,6 +150,44 @@ type MemorySearchInput = {
|
|
|
150
150
|
limit?: number;
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
+
type ToolResultRef = {
|
|
154
|
+
sessionId: string;
|
|
155
|
+
toolResultId: string;
|
|
156
|
+
ref: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function parseToolResultRef(value: unknown): ToolResultRef | null {
|
|
160
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
161
|
+
if (!raw) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const match = raw.match(/^viking:\/\/session\/([^/]+)\/tool-results\/([^/?#]+)(?:[?#].*)?$/);
|
|
165
|
+
if (!match) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const sessionId = decodeURIComponent(match[1]!);
|
|
169
|
+
const toolResultId = decodeURIComponent(match[2]!);
|
|
170
|
+
if (!sessionId || !toolResultId) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
sessionId,
|
|
175
|
+
toolResultId,
|
|
176
|
+
ref: `viking://session/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getOptionalInteger(value: unknown, fallback: number): number {
|
|
181
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
182
|
+
return fallback;
|
|
183
|
+
}
|
|
184
|
+
return Math.floor(value);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getPositiveInteger(value: unknown, fallback: number): number {
|
|
188
|
+
return Math.max(1, getOptionalInteger(value, fallback));
|
|
189
|
+
}
|
|
190
|
+
|
|
153
191
|
type OpenClawPluginApi = {
|
|
154
192
|
pluginConfig?: unknown;
|
|
155
193
|
logger: PluginLogger;
|
|
@@ -1090,29 +1128,58 @@ const contextEnginePlugin = {
|
|
|
1090
1128
|
};
|
|
1091
1129
|
}
|
|
1092
1130
|
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1131
|
+
const leafOnly = (result.memories ?? []).filter((m) => !m.level || m.level === 2);
|
|
1132
|
+
const processed = postProcessMemories(leafOnly, {
|
|
1133
|
+
limit: requestLimit,
|
|
1095
1134
|
scoreThreshold,
|
|
1096
1135
|
});
|
|
1136
|
+
const memories = pickMemoriesForInjection(processed, limit, query);
|
|
1097
1137
|
if (memories.length === 0) {
|
|
1098
1138
|
return {
|
|
1099
1139
|
content: [{ type: "text", text: "No relevant OpenViking memories found." }],
|
|
1100
1140
|
details: { count: 0, total: result.total ?? 0, scoreThreshold },
|
|
1101
1141
|
};
|
|
1102
1142
|
}
|
|
1143
|
+
const { lines: memoryLines } = await buildMemoryLinesWithBudget(
|
|
1144
|
+
memories,
|
|
1145
|
+
(uri) => recallClient.read(uri, session.agentId),
|
|
1146
|
+
{
|
|
1147
|
+
recallPreferAbstract: false,
|
|
1148
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
1149
|
+
},
|
|
1150
|
+
);
|
|
1151
|
+
if (memoryLines.length === 0) {
|
|
1152
|
+
return {
|
|
1153
|
+
content: [
|
|
1154
|
+
{
|
|
1155
|
+
type: "text",
|
|
1156
|
+
text: `No complete OpenViking memories fit recallMaxInjectedChars=${cfg.recallMaxInjectedChars}.`,
|
|
1157
|
+
},
|
|
1158
|
+
],
|
|
1159
|
+
details: {
|
|
1160
|
+
count: 0,
|
|
1161
|
+
memories,
|
|
1162
|
+
total: result.total ?? memories.length,
|
|
1163
|
+
scoreThreshold,
|
|
1164
|
+
requestLimit,
|
|
1165
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
1166
|
+
},
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1103
1169
|
return {
|
|
1104
1170
|
content: [
|
|
1105
1171
|
{
|
|
1106
1172
|
type: "text",
|
|
1107
|
-
text: `Found ${
|
|
1173
|
+
text: `Found ${memoryLines.length} memories:\n\n${memoryLines.join("\n")}`,
|
|
1108
1174
|
},
|
|
1109
1175
|
],
|
|
1110
1176
|
details: {
|
|
1111
|
-
count:
|
|
1177
|
+
count: memoryLines.length,
|
|
1112
1178
|
memories,
|
|
1113
1179
|
total: result.total ?? memories.length,
|
|
1114
1180
|
scoreThreshold,
|
|
1115
1181
|
requestLimit,
|
|
1182
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
1116
1183
|
},
|
|
1117
1184
|
};
|
|
1118
1185
|
},
|
|
@@ -1531,6 +1598,285 @@ const contextEnginePlugin = {
|
|
|
1531
1598
|
},
|
|
1532
1599
|
}), { name: "ov_archive_expand" });
|
|
1533
1600
|
|
|
1601
|
+
api.registerTool(
|
|
1602
|
+
(ctx: ToolContext) => ({
|
|
1603
|
+
name: "openviking_tool_result_read",
|
|
1604
|
+
label: "Tool Result Read (OpenViking)",
|
|
1605
|
+
description:
|
|
1606
|
+
"Restore the full original content of a tool result that was externalized by OpenViking. " +
|
|
1607
|
+
"Use when a previous tool result was externalized and only a preview is visible — " +
|
|
1608
|
+
"the preview contains a [tool-result-ref] or viking://session/.../tool-results/... URI. " +
|
|
1609
|
+
"\"Read\" tool returns the same truncated preview; this tool returns the complete content. " +
|
|
1610
|
+
"To read all content: pass offset=0 and a limit large enough to cover the whole result " +
|
|
1611
|
+
"(e.g. limit=100000). Use offset/limit for paging only when you need a specific section.",
|
|
1612
|
+
parameters: Type.Object({
|
|
1613
|
+
tool_output_ref: Type.String({
|
|
1614
|
+
description:
|
|
1615
|
+
"Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
|
|
1616
|
+
}),
|
|
1617
|
+
offset: Type.Optional(Type.Number({ description: "Unicode character offset. Default: 0" })),
|
|
1618
|
+
limit: Type.Optional(Type.Number({ description: "Maximum Unicode characters to read. Default: 20000" })),
|
|
1619
|
+
}),
|
|
1620
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
1621
|
+
if (isBypassedSession(ctx)) {
|
|
1622
|
+
return makeBypassedToolResult("openviking_tool_result_read");
|
|
1623
|
+
}
|
|
1624
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1625
|
+
if (!session.ovSessionId) {
|
|
1626
|
+
return {
|
|
1627
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1628
|
+
details: { error: "no_session" },
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
|
|
1633
|
+
if (!parsed) {
|
|
1634
|
+
return {
|
|
1635
|
+
content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
|
|
1636
|
+
details: { error: "invalid_tool_output_ref" },
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
if (parsed.sessionId !== session.ovSessionId) {
|
|
1640
|
+
return {
|
|
1641
|
+
content: [{ type: "text", text: "Error: refusing to read a tool result from another session." }],
|
|
1642
|
+
details: {
|
|
1643
|
+
error: "session_mismatch",
|
|
1644
|
+
requestedSessionId: parsed.sessionId,
|
|
1645
|
+
currentSessionId: session.ovSessionId,
|
|
1646
|
+
},
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const offset = Math.max(0, getOptionalInteger(params.offset, 0));
|
|
1651
|
+
const limit = getOptionalInteger(params.limit, 20_000);
|
|
1652
|
+
if (limit < -1) {
|
|
1653
|
+
return {
|
|
1654
|
+
content: [{ type: "text", text: "Error: limit must be -1 or greater than or equal to 0." }],
|
|
1655
|
+
details: { error: "invalid_limit", limit },
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
try {
|
|
1660
|
+
const client = await getClient();
|
|
1661
|
+
const result = await client.readToolResult(
|
|
1662
|
+
session.ovSessionId,
|
|
1663
|
+
parsed.toolResultId,
|
|
1664
|
+
{ offset, limit, includeMetadata: true },
|
|
1665
|
+
session.agentId,
|
|
1666
|
+
);
|
|
1667
|
+
const returnedChars = result.content.length;
|
|
1668
|
+
const nextOffset = result.offset + returnedChars;
|
|
1669
|
+
const text = result.content || "(empty tool result chunk)";
|
|
1670
|
+
return {
|
|
1671
|
+
content: [{ type: "text", text }],
|
|
1672
|
+
details: {
|
|
1673
|
+
action: "read",
|
|
1674
|
+
tool_output_ref: parsed.ref,
|
|
1675
|
+
tool_result_id: result.tool_result_id,
|
|
1676
|
+
offset: result.offset,
|
|
1677
|
+
limit: result.limit,
|
|
1678
|
+
returned_chars: returnedChars,
|
|
1679
|
+
total_chars: result.total_chars,
|
|
1680
|
+
has_more: result.has_more,
|
|
1681
|
+
next_offset: result.has_more ? nextOffset : null,
|
|
1682
|
+
metadata: result.metadata ?? null,
|
|
1683
|
+
},
|
|
1684
|
+
};
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1687
|
+
api.logger.warn?.(`openviking: openviking_tool_result_read failed: ${msg}`);
|
|
1688
|
+
return {
|
|
1689
|
+
content: [{ type: "text", text: `Failed to read tool result: ${msg}` }],
|
|
1690
|
+
details: { error: msg, tool_output_ref: parsed.ref },
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
}),
|
|
1695
|
+
{ name: "openviking_tool_result_read" },
|
|
1696
|
+
);
|
|
1697
|
+
|
|
1698
|
+
api.registerTool(
|
|
1699
|
+
(ctx: ToolContext) => ({
|
|
1700
|
+
name: "openviking_tool_result_search",
|
|
1701
|
+
label: "Tool Result Search (OpenViking)",
|
|
1702
|
+
description:
|
|
1703
|
+
"Search inside an externalized tool result for a keyword. " +
|
|
1704
|
+
"Use when you need to find specific content in a large externalized result, " +
|
|
1705
|
+
"before reading it with openviking_tool_result_read. " +
|
|
1706
|
+
"Returns matching snippets with their character offsets.",
|
|
1707
|
+
parameters: Type.Object({
|
|
1708
|
+
tool_output_ref: Type.String({
|
|
1709
|
+
description:
|
|
1710
|
+
"Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
|
|
1711
|
+
}),
|
|
1712
|
+
query: Type.String({ description: "Keyword or exact text to search for" }),
|
|
1713
|
+
limit: Type.Optional(Type.Number({ description: "Maximum matches. Default: 20" })),
|
|
1714
|
+
context_chars: Type.Optional(Type.Number({ description: "Characters around each match. Default: 300" })),
|
|
1715
|
+
}),
|
|
1716
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
1717
|
+
if (isBypassedSession(ctx)) {
|
|
1718
|
+
return makeBypassedToolResult("openviking_tool_result_search");
|
|
1719
|
+
}
|
|
1720
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1721
|
+
if (!session.ovSessionId) {
|
|
1722
|
+
return {
|
|
1723
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1724
|
+
details: { error: "no_session" },
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
|
|
1729
|
+
if (!parsed) {
|
|
1730
|
+
return {
|
|
1731
|
+
content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
|
|
1732
|
+
details: { error: "invalid_tool_output_ref" },
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
if (parsed.sessionId !== session.ovSessionId) {
|
|
1736
|
+
return {
|
|
1737
|
+
content: [{ type: "text", text: "Error: refusing to search a tool result from another session." }],
|
|
1738
|
+
details: {
|
|
1739
|
+
error: "session_mismatch",
|
|
1740
|
+
requestedSessionId: parsed.sessionId,
|
|
1741
|
+
currentSessionId: session.ovSessionId,
|
|
1742
|
+
},
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
const query = String(params.query ?? "").trim();
|
|
1747
|
+
if (!query) {
|
|
1748
|
+
return {
|
|
1749
|
+
content: [{ type: "text", text: "Error: query is required." }],
|
|
1750
|
+
details: { error: "missing_param", param: "query" },
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
const limit = getPositiveInteger(params.limit, 20);
|
|
1754
|
+
const contextChars = Math.max(
|
|
1755
|
+
0,
|
|
1756
|
+
getOptionalInteger(params.context_chars ?? params.contextChars, 300),
|
|
1757
|
+
);
|
|
1758
|
+
|
|
1759
|
+
try {
|
|
1760
|
+
const client = await getClient();
|
|
1761
|
+
const result = await client.searchToolResult(
|
|
1762
|
+
session.ovSessionId,
|
|
1763
|
+
parsed.toolResultId,
|
|
1764
|
+
query,
|
|
1765
|
+
{ limit, contextChars },
|
|
1766
|
+
session.agentId,
|
|
1767
|
+
);
|
|
1768
|
+
const matches = result.matches ?? [];
|
|
1769
|
+
const text = matches.length
|
|
1770
|
+
? [
|
|
1771
|
+
`Found ${matches.length} match(es) for "${query}" in ${parsed.ref}:`,
|
|
1772
|
+
"",
|
|
1773
|
+
...matches.map((match, index) =>
|
|
1774
|
+
`## Match ${index + 1} (offset ${match.offset})\n${match.snippet}`,
|
|
1775
|
+
),
|
|
1776
|
+
].join("\n")
|
|
1777
|
+
: `No matches found for "${query}" in ${parsed.ref}.`;
|
|
1778
|
+
return {
|
|
1779
|
+
content: [{ type: "text", text }],
|
|
1780
|
+
details: {
|
|
1781
|
+
action: "searched",
|
|
1782
|
+
tool_output_ref: parsed.ref,
|
|
1783
|
+
tool_result_id: result.tool_result_id,
|
|
1784
|
+
query,
|
|
1785
|
+
match_count: matches.length,
|
|
1786
|
+
matches,
|
|
1787
|
+
},
|
|
1788
|
+
};
|
|
1789
|
+
} catch (err) {
|
|
1790
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1791
|
+
api.logger.warn?.(`openviking: openviking_tool_result_search failed: ${msg}`);
|
|
1792
|
+
return {
|
|
1793
|
+
content: [{ type: "text", text: `Failed to search tool result: ${msg}` }],
|
|
1794
|
+
details: { error: msg, tool_output_ref: parsed.ref, query },
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
},
|
|
1798
|
+
}),
|
|
1799
|
+
{ name: "openviking_tool_result_search" },
|
|
1800
|
+
);
|
|
1801
|
+
|
|
1802
|
+
api.registerTool(
|
|
1803
|
+
(ctx: ToolContext) => ({
|
|
1804
|
+
name: "openviking_tool_result_list",
|
|
1805
|
+
label: "Tool Result List (OpenViking)",
|
|
1806
|
+
description:
|
|
1807
|
+
"List externalized tool results for the current session. " +
|
|
1808
|
+
"Use to discover available refs before calling openviking_tool_result_read. " +
|
|
1809
|
+
"Optionally filter by tool_name to narrow down results.",
|
|
1810
|
+
parameters: Type.Object({
|
|
1811
|
+
tool_name: Type.Optional(Type.String({ description: "Optional exact tool name filter" })),
|
|
1812
|
+
limit: Type.Optional(Type.Number({ description: "Maximum results. Default: 50" })),
|
|
1813
|
+
}),
|
|
1814
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
1815
|
+
if (isBypassedSession(ctx)) {
|
|
1816
|
+
return makeBypassedToolResult("openviking_tool_result_list");
|
|
1817
|
+
}
|
|
1818
|
+
const session = resolvePluginSessionRouting(ctx);
|
|
1819
|
+
if (!session.ovSessionId) {
|
|
1820
|
+
return {
|
|
1821
|
+
content: [{ type: "text", text: "Error: no active session." }],
|
|
1822
|
+
details: { error: "no_session" },
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const toolName =
|
|
1827
|
+
typeof params.tool_name === "string" && params.tool_name.trim()
|
|
1828
|
+
? params.tool_name.trim()
|
|
1829
|
+
: typeof params.toolName === "string" && params.toolName.trim()
|
|
1830
|
+
? params.toolName.trim()
|
|
1831
|
+
: undefined;
|
|
1832
|
+
const limit = getPositiveInteger(params.limit, 50);
|
|
1833
|
+
|
|
1834
|
+
try {
|
|
1835
|
+
const client = await getClient();
|
|
1836
|
+
const result = await client.listToolResults(
|
|
1837
|
+
session.ovSessionId,
|
|
1838
|
+
{ toolName, limit },
|
|
1839
|
+
session.agentId,
|
|
1840
|
+
);
|
|
1841
|
+
const items = result.tool_results ?? [];
|
|
1842
|
+
const text = items.length
|
|
1843
|
+
? [
|
|
1844
|
+
`Found ${items.length} externalized tool result(s) in current session:`,
|
|
1845
|
+
"",
|
|
1846
|
+
...items.map((item, index) => {
|
|
1847
|
+
const ref = typeof item.storage_uri === "string" ? item.storage_uri : "(missing ref)";
|
|
1848
|
+
const name = typeof item.tool_name === "string" ? item.tool_name : "tool";
|
|
1849
|
+
const chars = typeof item.original_chars === "number" ? item.original_chars : "unknown";
|
|
1850
|
+
const created = typeof item.created_at === "string" ? ` created_at=${item.created_at}` : "";
|
|
1851
|
+
return `${index + 1}. ${name} original_chars=${chars}${created}\nref: ${ref}`;
|
|
1852
|
+
}),
|
|
1853
|
+
].join("\n")
|
|
1854
|
+
: toolName
|
|
1855
|
+
? `No externalized tool results found for tool "${toolName}" in current session.`
|
|
1856
|
+
: "No externalized tool results found in current session.";
|
|
1857
|
+
return {
|
|
1858
|
+
content: [{ type: "text", text }],
|
|
1859
|
+
details: {
|
|
1860
|
+
action: "listed",
|
|
1861
|
+
session_id: session.ovSessionId,
|
|
1862
|
+
tool_name: toolName ?? null,
|
|
1863
|
+
count: items.length,
|
|
1864
|
+
tool_results: items,
|
|
1865
|
+
},
|
|
1866
|
+
};
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1869
|
+
api.logger.warn?.(`openviking: openviking_tool_result_list failed: ${msg}`);
|
|
1870
|
+
return {
|
|
1871
|
+
content: [{ type: "text", text: `Failed to list tool results: ${msg}` }],
|
|
1872
|
+
details: { error: msg, session_id: session.ovSessionId, tool_name: toolName ?? null },
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
}),
|
|
1877
|
+
{ name: "openviking_tool_result_list" },
|
|
1878
|
+
);
|
|
1879
|
+
|
|
1534
1880
|
let contextEngineRef: ContextEngineWithCommit | null = null;
|
|
1535
1881
|
const sessionAgentResolver = createSessionAgentResolver(cfg.agent_prefix);
|
|
1536
1882
|
const rememberSessionAgentId = (ctx: SessionAgentLookup) => {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/openclaw-plugin",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.21",
|
|
4
4
|
"displayName": "OpenViking",
|
|
5
5
|
"description": "OpenClaw context-engine plugin — OpenViking AI memory, long-term memory, knowledge base, semantic search, RAG, context engine. 长期记忆 · 知识库 · 语义检索 · 上下文引擎",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"INSTALL.md",
|
|
31
31
|
"INSTALL-ZH.md",
|
|
32
32
|
"INSTALL-AGENT.md",
|
|
33
|
+
"images/",
|
|
33
34
|
"skills/"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
},
|
|
49
50
|
"repository": {
|
|
50
51
|
"type": "git",
|
|
51
|
-
"url": "
|
|
52
|
+
"url": "https://github.com/volcengine/OpenViking"
|
|
52
53
|
},
|
|
53
54
|
"homepage": "https://github.com/volcengine/OpenViking/tree/main/examples/openclaw-plugin",
|
|
54
55
|
"license": "MIT",
|