@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
package/dist/index.js CHANGED
@@ -1,327 +1,38 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import { memoryOpenVikingConfigSchema } from "./config.js";
3
2
  import { registerSetupCli } from "./commands/setup.js";
4
- import { OpenVikingClient, isMemoryUri } from "./client.js";
5
- import { formatMessageFaithful, toRoleId } from "./context-engine.js";
6
- import { compileSessionPatterns, shouldBypassSession, } from "./text-utils.js";
3
+ import { createOpenVikingBypassRuntime } from "./plugin/openviking-bypass-runtime.js";
4
+ import { createOpenVikingClientRuntime } from "./plugin/openviking-client-runtime.js";
5
+ import { createOpenVikingCommandDefinitions } from "./plugin/openviking-command-definitions.js";
6
+ import { parseAddResourceCommandArgs, parseAddSkillCommandArgs, parseOVSearchCommandArgs, } from "./plugin/openviking-command-args.js";
7
+ import { createOpenVikingContextEngineRef } from "./plugin/openviking-context-engine-ref.js";
8
+ import { registerOpenVikingContextEngine } from "./plugin/openviking-context-engine-registration.js";
9
+ import { registerOpenVikingFeatureGatesMethod } from "./plugin/openviking-feature-gates.js";
10
+ import { createOpenVikingQueryConfigCommandHandler } from "./plugin/openviking-query-config-command.js";
11
+ import { createOpenVikingQueryRuntime } from "./plugin/openviking-query-runtime.js";
12
+ import { createOpenVikingRecallTraceRuntime } from "./plugin/openviking-recall-trace-runtime.js";
13
+ import { createOpenVikingToolRegistrationRuntime } from "./plugin/tool-registration.js";
14
+ import { registerOpenVikingCommands } from "./plugin/command-registration.js";
15
+ import { registerRecallTraceRoutes as registerRecallTraceRouteAdapters } from "./plugin/recall-trace-routes.js";
16
+ import { createOpenVikingService } from "./plugin/openviking-services.js";
17
+ import { registerOpenVikingArchiveTools } from "./plugin/openviking-archive-tools.js";
18
+ import { createOpenVikingImportRuntime } from "./plugin/openviking-import-runtime.js";
19
+ import { registerOpenVikingImportTools } from "./plugin/openviking-import-tools.js";
20
+ import { registerOpenVikingLifecycleHooks } from "./plugin/openviking-lifecycle-hooks.js";
21
+ import { registerOpenVikingMemoryRecallTools } from "./plugin/openviking-memory-recall-tools.js";
22
+ import { registerOpenVikingMemoryTools } from "./plugin/openviking-memory-tools.js";
23
+ import { registerOpenVikingQueryTools } from "./plugin/openviking-query-tools.js";
24
+ import { registerOpenVikingRecallTraceTools } from "./plugin/openviking-recall-trace-tools.js";
25
+ import { createOpenVikingRuntimeState } from "./plugin/openviking-runtime-state.js";
26
+ import { createOpenVikingSessionRoutingRuntime } from "./plugin/openviking-session-routing-runtime.js";
27
+ import { registerOpenVikingToolResultTools } from "./plugin/openviking-tool-result-tools.js";
28
+ import { boundTraceQuery, createMemoryStoreTempSessionId, createTraceId, extractToolSenderId, inferRecallResourceType, makeBypassedToolResult, previewText, } from "./plugin/openviking-runtime-utils.js";
29
+ import { formatMessageFaithful, toRoleId } from "./services/context-message-adapter.js";
7
30
  import { clampScore, postProcessMemories, pickMemoriesForInjection, } from "./memory-ranking.js";
8
- import { createMemoryOpenVikingContextEngine, openClawSessionToOvStorageId, openClawSessionRefToOvStorageId, } from "./context-engine.js";
9
- import { buildMemoryLines, buildMemoryLinesWithBudget, estimateTokenCount, prepareRecallQuery, } from "./auto-recall.js";
10
- export { buildMemoryLines, buildMemoryLinesWithBudget, estimateTokenCount, prepareRecallQuery, };
11
- export { estimateAgentMessageTokens, estimateAgentMessagesTokens, estimateSerializedTokens, estimateTextTokens, } from "./token-estimator.js";
12
- function parseToolResultRef(value) {
13
- const raw = typeof value === "string" ? value.trim() : "";
14
- if (!raw) {
15
- return null;
16
- }
17
- const match = raw.match(/^viking:\/\/session\/([^/]+)\/tool-results\/([^/?#]+)(?:[?#].*)?$/);
18
- if (!match) {
19
- return null;
20
- }
21
- const sessionId = decodeURIComponent(match[1]);
22
- const toolResultId = decodeURIComponent(match[2]);
23
- if (!sessionId || !toolResultId) {
24
- return null;
25
- }
26
- return {
27
- sessionId,
28
- toolResultId,
29
- ref: `viking://session/${encodeURIComponent(sessionId)}/tool-results/${encodeURIComponent(toolResultId)}`,
30
- };
31
- }
32
- function getOptionalInteger(value, fallback) {
33
- if (typeof value !== "number" || !Number.isFinite(value)) {
34
- return fallback;
35
- }
36
- return Math.floor(value);
37
- }
38
- function getPositiveInteger(value, fallback) {
39
- return Math.max(1, getOptionalInteger(value, fallback));
40
- }
41
- const DEFAULT_OPENCLAW_AGENT_ID = "main";
42
- /**
43
- * OpenViking `UserIdentifier` allows only [a-zA-Z0-9_-] for agent_id
44
- * (see openviking_cli/session/user_id.py). OpenClaw ids may contain ":"
45
- * (e.g. session keys); never send raw colons in X-OpenViking-Agent.
46
- */
47
- export function sanitizeOpenVikingAgentIdHeader(raw) {
48
- const trimmed = raw.trim();
49
- if (!trimmed) {
50
- return "default";
51
- }
52
- const normalized = trimmed
53
- .replace(/[^a-zA-Z0-9_-]/g, "_")
54
- .replace(/_+/g, "_")
55
- .replace(/^_|_$/g, "");
56
- return normalized.length > 0 ? normalized : "ov_agent";
57
- }
58
- export function tokenizeCommandArgs(args) {
59
- const tokens = [];
60
- let current = "";
61
- let quote = null;
62
- let escaping = false;
63
- for (let i = 0; i < args.length; i += 1) {
64
- const ch = args[i];
65
- const next = args[i + 1];
66
- if (escaping) {
67
- current += ch;
68
- escaping = false;
69
- continue;
70
- }
71
- if (ch === "\\") {
72
- const shouldEscape = quote === '"'
73
- ? next === '"' || next === "\\"
74
- : !quote && Boolean(next && (/\s/.test(next) || next === '"' || next === "'"));
75
- if (shouldEscape) {
76
- escaping = true;
77
- continue;
78
- }
79
- current += ch;
80
- continue;
81
- }
82
- if ((ch === '"' || ch === "'") && (!quote || quote === ch)) {
83
- quote = quote ? null : ch;
84
- continue;
85
- }
86
- if (!quote && /\s/.test(ch)) {
87
- if (current) {
88
- tokens.push(current);
89
- current = "";
90
- }
91
- continue;
92
- }
93
- current += ch;
94
- }
95
- if (escaping) {
96
- current += "\\";
97
- }
98
- if (quote) {
99
- throw new Error("Unterminated quoted argument");
100
- }
101
- if (current) {
102
- tokens.push(current);
103
- }
104
- return tokens;
105
- }
106
- function parseFlagArgs(args) {
107
- const tokens = tokenizeCommandArgs(args);
108
- const positionals = [];
109
- const flags = new Map();
110
- for (let i = 0; i < tokens.length; i += 1) {
111
- const token = tokens[i];
112
- if (!token.startsWith("--")) {
113
- positionals.push(token);
114
- continue;
115
- }
116
- const raw = token.slice(2);
117
- if (!raw) {
118
- continue;
119
- }
120
- const eqIndex = raw.indexOf("=");
121
- if (eqIndex >= 0) {
122
- flags.set(raw.slice(0, eqIndex), raw.slice(eqIndex + 1));
123
- continue;
124
- }
125
- const next = tokens[i + 1];
126
- if (next && !next.startsWith("--")) {
127
- flags.set(raw, next);
128
- i += 1;
129
- }
130
- else {
131
- flags.set(raw, true);
132
- }
133
- }
134
- return { positionals, flags };
135
- }
136
- function getStringFlag(flags, name) {
137
- const value = flags.get(name);
138
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
139
- }
140
- function getNumberFlag(flags, name) {
141
- const raw = getStringFlag(flags, name);
142
- if (!raw) {
143
- return undefined;
144
- }
145
- const value = Number(raw);
146
- if (!Number.isFinite(value)) {
147
- throw new Error(`--${name} must be a number`);
148
- }
149
- return value;
150
- }
151
- function getBoolFlag(flags, name) {
152
- return flags.get(name) === true;
153
- }
154
- function extractToolSenderId(ctx) {
155
- if (!ctx || typeof ctx !== "object") {
156
- return undefined;
157
- }
158
- const toolCtx = ctx;
159
- if (typeof toolCtx.requesterSenderId === "string") {
160
- const trimmed = toolCtx.requesterSenderId.trim();
161
- if (trimmed) {
162
- return trimmed;
163
- }
164
- }
165
- if (typeof toolCtx.senderId === "string") {
166
- const trimmed = toolCtx.senderId.trim();
167
- if (trimmed) {
168
- return trimmed;
169
- }
170
- }
171
- return undefined;
172
- }
173
- export function parseAddResourceCommandArgs(args) {
174
- const parsed = parseFlagArgs(args);
175
- const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim();
176
- if (!source) {
177
- throw new Error("Usage: /add-resource <source> [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait] [--timeout SEC]");
178
- }
179
- const to = getStringFlag(parsed.flags, "to");
180
- const parent = getStringFlag(parsed.flags, "parent");
181
- if (to && parent) {
182
- throw new Error("Cannot specify both --to and --parent.");
183
- }
184
- return {
185
- source,
186
- to,
187
- parent,
188
- reason: getStringFlag(parsed.flags, "reason"),
189
- instruction: getStringFlag(parsed.flags, "instruction"),
190
- wait: getBoolFlag(parsed.flags, "wait"),
191
- timeout: getNumberFlag(parsed.flags, "timeout"),
192
- };
193
- }
194
- export function parseAddSkillCommandArgs(args) {
195
- const parsed = parseFlagArgs(args);
196
- const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim();
197
- if (!source) {
198
- throw new Error("Usage: /add-skill <source> [--wait] [--timeout SEC]");
199
- }
200
- if (parsed.flags.has("to") || parsed.flags.has("parent") || parsed.flags.has("reason") || parsed.flags.has("instruction")) {
201
- throw new Error("--to, --parent, --reason, and --instruction are resource-only options.");
202
- }
203
- return {
204
- source,
205
- wait: getBoolFlag(parsed.flags, "wait"),
206
- timeout: getNumberFlag(parsed.flags, "timeout"),
207
- };
208
- }
209
- export function parseOVSearchCommandArgs(args) {
210
- const parsed = parseFlagArgs(args);
211
- // `/ov-search` only accepts a single query string, so positional segments are
212
- // always re-joined to preserve unquoted multi-word searches.
213
- const query = parsed.positionals.join(" ").trim();
214
- if (!query) {
215
- throw new Error('Usage: /ov-search "<query>" [--uri URI] [--limit N]');
216
- }
217
- return {
218
- query,
219
- uri: getStringFlag(parsed.flags, "uri"),
220
- limit: getNumberFlag(parsed.flags, "limit"),
221
- };
222
- }
223
- function extractAgentIdFromSessionKey(sessionKey) {
224
- const raw = typeof sessionKey === "string" ? sessionKey.trim() : "";
225
- if (!raw) {
226
- return undefined;
227
- }
228
- const match = raw.match(/^agent:([^:]+):/);
229
- const agentId = match?.[1]?.trim();
230
- return agentId || undefined;
231
- }
232
- function collectSessionAgentAliases(sessionId, sessionKey, ovSessionId) {
233
- const aliases = new Set();
234
- const sid = typeof sessionId === "string" ? sessionId.trim() : "";
235
- const sk = typeof sessionKey === "string" ? sessionKey.trim() : "";
236
- const ovSid = typeof ovSessionId === "string" ? ovSessionId.trim() : "";
237
- if (sid) {
238
- aliases.add(sid);
239
- }
240
- if (sk) {
241
- aliases.add(sk);
242
- }
243
- if (ovSid) {
244
- aliases.add(ovSid);
245
- }
246
- if (!ovSid && (sid || sk)) {
247
- try {
248
- aliases.add(openClawSessionToOvStorageId(sid || undefined, sk || undefined));
249
- }
250
- catch {
251
- /* need a resolvable OpenClaw session identity */
252
- }
253
- }
254
- return [...aliases];
255
- }
256
- export function createSessionAgentResolver(configAgentId) {
257
- const configAgentPrefix = configAgentId.trim() === "default" ? "" : configAgentId.trim();
258
- const sessionAgentIds = new Map();
259
- const remember = (ctx) => {
260
- const sessionScopedAgentId = extractAgentIdFromSessionKey(ctx.sessionKey) ||
261
- extractAgentIdFromSessionKey(ctx.sessionId);
262
- const rawAgentId = (typeof ctx.agentId === "string" ? ctx.agentId.trim() : "") ||
263
- sessionScopedAgentId ||
264
- "";
265
- if (!rawAgentId) {
266
- return;
267
- }
268
- const prefix = configAgentPrefix;
269
- const resolvedBeforeSanitize = prefix ? `${prefix}_${rawAgentId}` : rawAgentId;
270
- const resolved = sanitizeOpenVikingAgentIdHeader(resolvedBeforeSanitize);
271
- for (const alias of collectSessionAgentAliases(ctx.sessionId, ctx.sessionKey, ctx.ovSessionId)) {
272
- sessionAgentIds.set(alias, resolved);
273
- }
274
- };
275
- const resolve = (sessionId, sessionKey, ovSessionId) => {
276
- const aliases = collectSessionAgentAliases(sessionId, sessionKey, ovSessionId);
277
- const mappedAlias = aliases.find((alias) => sessionAgentIds.has(alias));
278
- const mappedResolvedAgentId = mappedAlias ? sessionAgentIds.get(mappedAlias) : undefined;
279
- const sessionScopedAgentId = extractAgentIdFromSessionKey(sessionKey) ||
280
- extractAgentIdFromSessionKey(sessionId);
281
- let resolvedBeforeSanitize;
282
- let resolved;
283
- let branch;
284
- const prefix = configAgentPrefix;
285
- if (mappedResolvedAgentId) {
286
- resolvedBeforeSanitize = mappedResolvedAgentId;
287
- resolved = mappedResolvedAgentId;
288
- branch = "session_resolved";
289
- }
290
- else if (sessionScopedAgentId) {
291
- resolvedBeforeSanitize = prefix ? `${prefix}_${sessionScopedAgentId}` : sessionScopedAgentId;
292
- resolved = sanitizeOpenVikingAgentIdHeader(resolvedBeforeSanitize);
293
- branch = "session_resolved";
294
- }
295
- else if (!prefix) {
296
- resolvedBeforeSanitize = DEFAULT_OPENCLAW_AGENT_ID;
297
- resolved = DEFAULT_OPENCLAW_AGENT_ID;
298
- branch = "default_no_session";
299
- }
300
- else {
301
- resolvedBeforeSanitize = `${prefix}_${DEFAULT_OPENCLAW_AGENT_ID}`;
302
- resolved = sanitizeOpenVikingAgentIdHeader(resolvedBeforeSanitize);
303
- branch = "config_only_fallback";
304
- }
305
- return {
306
- resolved,
307
- resolvedBeforeSanitize,
308
- branch,
309
- mappedResolvedAgentId: mappedResolvedAgentId ?? null,
310
- aliases,
311
- fromExplicitBinding: !!(mappedResolvedAgentId || sessionScopedAgentId),
312
- };
313
- };
314
- return {
315
- remember,
316
- resolve,
317
- };
318
- }
319
- function totalCommitMemories(r) {
320
- const m = r.memories_extracted;
321
- if (!m || typeof m !== "object")
322
- return 0;
323
- return Object.values(m).reduce((sum, n) => sum + (n ?? 0), 0);
324
- }
31
+ import { createMemoryOpenVikingContextEngine, } from "./context-engine.js";
32
+ import { openClawSessionRefToOvStorageId, openClawSessionToOvStorageId, } from "./routing/identity-routing.js";
33
+ import { buildMemoryLinesWithBudget, } from "./auto-recall.js";
34
+ import { normalizeRecallResourceTypes as normalizeResourceTypes, resolveRecallSearchPlan, } from "./registries/recall-resource-types.js";
35
+ import { normalizeRuntimeQueryParams, } from "./query-config.js";
325
36
  const contextEnginePlugin = {
326
37
  id: "openviking",
327
38
  name: "Context Engine (OpenViking)",
@@ -329,6 +40,7 @@ const contextEnginePlugin = {
329
40
  kind: "context-engine",
330
41
  configSchema: memoryOpenVikingConfigSchema,
331
42
  register(api) {
43
+ registerOpenVikingFeatureGatesMethod(api);
332
44
  const rawCfg = api.pluginConfig && typeof api.pluginConfig === "object" && !Array.isArray(api.pluginConfig)
333
45
  ? api.pluginConfig
334
46
  : {};
@@ -351,1183 +63,191 @@ const contextEnginePlugin = {
351
63
  registerSetupCli(api);
352
64
  return;
353
65
  }
354
- const bypassSessionPatterns = compileSessionPatterns(cfg.bypassSessionPatterns);
355
- const rawAgentId = rawCfg.agent_prefix;
356
- if (cfg.logFindRequests) {
357
- api.logger.info("openviking: routing debug logging enabled (config logFindRequests, or env OPENVIKING_LOG_ROUTING=1 / OPENVIKING_DEBUG=1)");
358
- }
359
- const verboseRoutingInfo = (message) => {
360
- if (cfg.logFindRequests) {
361
- api.logger.info(message);
362
- }
363
- };
364
- verboseRoutingInfo(`openviking: loaded plugin config agent_prefix="${cfg.agent_prefix}" ` +
365
- `(raw plugins.entries.openviking.config.agent_prefix=${JSON.stringify(rawAgentId ?? "(missing)")}; ` +
366
- `${cfg.agent_prefix
367
- ? 'non-empty → X-OpenViking-Agent is <agent_prefix>_<ctx.agentId> when hooks expose session agent, or <agent_prefix>_main when ctx.agentId is unknown'
368
- : 'empty → X-OpenViking-Agent follows OpenClaw ctx.agentId per session, or "main" when ctx.agentId is unknown'})`);
369
- verboseRoutingInfo(`openviking: auth/namespace config ` +
370
- JSON.stringify({
371
- isolateUserScopeByAgent: cfg.isolateUserScopeByAgent,
372
- isolateAgentScopeByUser: cfg.isolateAgentScopeByUser,
373
- deprecatedAgentScopeMode: cfg.agentScopeMode,
374
- }));
375
- const routingDebugLog = cfg.logFindRequests
376
- ? (msg) => {
377
- api.logger.info(msg);
378
- }
379
- : undefined;
380
- const tenantAccount = cfg.accountId;
381
- const tenantUser = cfg.userId;
382
- const clientPromise = Promise.resolve(new OpenVikingClient(cfg.baseUrl, cfg.apiKey, cfg.agent_prefix, cfg.timeoutMs, tenantAccount, tenantUser, routingDebugLog, cfg.isolateUserScopeByAgent, cfg.isolateAgentScopeByUser));
383
- const getClient = () => clientPromise;
384
- const isBypassedSession = (ctx) => shouldBypassSession(ctx ?? {}, bypassSessionPatterns);
385
- const makeBypassedToolResult = (toolName) => ({
386
- content: [
387
- {
388
- type: "text",
389
- text: `OpenViking is bypassed for this session by bypassSessionPatterns; ${toolName} was skipped.`,
390
- },
391
- ],
392
- details: {
393
- action: "bypassed",
394
- reason: "session_bypassed",
395
- toolName,
396
- },
66
+ const { isBypassedSession } = createOpenVikingBypassRuntime({ cfg });
67
+ const { getClient, verboseRoutingInfo } = createOpenVikingClientRuntime({
68
+ cfg,
69
+ rawPeerPrefix: rawCfg.peer_prefix,
70
+ logger: api.logger,
71
+ transport: api.openVikingTransport,
397
72
  });
398
- const resolvePluginSessionRouting = (ctx) => {
399
- const sessionId = typeof ctx?.sessionId === "string" ? ctx.sessionId.trim() : "";
400
- const sessionKey = typeof ctx?.sessionKey === "string" ? ctx.sessionKey.trim() : "";
401
- let ovSessionId = typeof ctx?.ovSessionId === "string" ? ctx.ovSessionId.trim() : "";
402
- if (!ovSessionId && (sessionId || sessionKey)) {
403
- ovSessionId = openClawSessionToOvStorageId(sessionId || undefined, sessionKey || undefined);
404
- }
405
- const session = {
406
- agentId: ctx?.agentId,
407
- sessionId: sessionId || undefined,
408
- sessionKey: sessionKey || undefined,
409
- ovSessionId: ovSessionId || undefined,
410
- };
411
- rememberSessionAgentId(session);
412
- return {
413
- sessionId: session.sessionId,
414
- sessionKey: session.sessionKey,
415
- ovSessionId: session.ovSessionId,
416
- agentId: resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId),
417
- };
418
- };
419
- const formatResourceImportText = (result) => {
420
- const root = result.root_uri ? ` ${result.root_uri}` : "";
421
- const warnings = result.warnings?.length ? ` Warnings: ${result.warnings.join("; ")}` : "";
422
- return `Imported OpenViking resource.${root}${warnings}`.trim();
423
- };
424
- const formatSkillImportText = (result) => {
425
- const uri = result.uri ? ` ${result.uri}` : "";
426
- const name = result.name ? ` (${result.name})` : "";
427
- return `Imported OpenViking skill${name}.${uri}`.trim();
428
- };
429
- const importResource = async (input, agentId) => {
430
- const client = await getClient();
431
- const result = await client.addResource(input, agentId);
432
- return {
433
- content: [{ type: "text", text: formatResourceImportText(result) }],
434
- details: {
435
- action: "resource_imported",
436
- ...result,
437
- },
438
- };
439
- };
440
- const importSkill = async (input, agentId) => {
441
- const client = await getClient();
442
- const result = await client.addSkill(input, agentId);
443
- return {
444
- content: [{ type: "text", text: formatSkillImportText(result) }],
445
- details: {
446
- action: "skill_imported",
447
- ...result,
448
- },
449
- };
450
- };
451
- const addResourceOpenViking = (input, agentId) => importResource({
452
- pathOrUrl: input.source ?? "",
453
- to: input.to,
454
- parent: input.parent,
455
- reason: input.reason,
456
- instruction: input.instruction,
457
- wait: input.wait,
458
- timeout: input.timeout,
459
- }, agentId);
460
- const addSkillOpenViking = (input, agentId) => importSkill({
461
- path: input.source,
462
- data: input.data,
463
- wait: input.wait,
464
- timeout: input.timeout,
465
- }, agentId);
466
- const mergeFindResults = (results) => {
467
- const deduplicate = (items) => {
468
- const seen = new Map();
469
- for (const item of items) {
470
- if (!seen.has(item.uri)) {
471
- seen.set(item.uri, item);
472
- }
473
- }
474
- return Array.from(seen.values());
475
- };
476
- const memories = deduplicate(results.flatMap((result) => result.memories ?? []));
477
- const resources = deduplicate(results.flatMap((result) => result.resources ?? []));
478
- const skills = deduplicate(results.flatMap((result) => result.skills ?? []));
479
- return {
480
- memories,
481
- resources,
482
- skills,
483
- total: memories.length + resources.length + skills.length,
484
- };
485
- };
486
- const formatOVSearchRows = (result) => {
487
- const truncateSummary = (value, maxChars = 220) => {
488
- const collapsed = value.replace(/\s+/g, " ").trim();
489
- if (collapsed.length <= maxChars) {
490
- return collapsed;
491
- }
492
- return `${collapsed.slice(0, maxChars - 3)}...`;
493
- };
494
- const truncateUri = (value, maxChars = 84) => {
495
- if (value.length <= maxChars) {
496
- return value;
497
- }
498
- return `${value.slice(0, maxChars - 3)}...`;
499
- };
500
- const items = [
501
- ...(result.memories ?? []).map((item) => ({ contextType: "memory", item })),
502
- ...(result.resources ?? []).map((item) => ({ contextType: "resource", item })),
503
- ...(result.skills ?? []).map((item) => ({ contextType: "skill", item })),
504
- ];
505
- if (items.length === 0) {
506
- return [];
507
- }
508
- const numberHeader = "no";
509
- const numberWidth = Math.max(numberHeader.length, String(items.length).length);
510
- const typeWidth = Math.max("type".length, ...items.map(({ contextType }) => contextType.length));
511
- const uriWidth = Math.max("uri".length, ...items.map(({ item }) => truncateUri(item.uri).length));
512
- const levelWidth = Math.max("level".length, ...items.map(({ item }) => String(item.level ?? "").length));
513
- const scoreWidth = Math.max("score".length, ...items.map(({ item }) => (typeof item.score === "number" ? item.score.toFixed(2).length : 0)));
514
- return [
515
- `${numberHeader.padEnd(numberWidth)} ${"type".padEnd(typeWidth)} ${"uri".padEnd(uriWidth)} ${"level".padEnd(levelWidth)} ${"score".padEnd(scoreWidth)} abstract`,
516
- ...items.map(({ contextType, item }, index) => {
517
- const score = typeof item.score === "number" ? item.score.toFixed(2) : "";
518
- const summary = truncateSummary(item.abstract || item.overview || "(no summary)");
519
- return `${String(index + 1).padEnd(numberWidth)} ${contextType.padEnd(typeWidth)} ${truncateUri(item.uri).padEnd(uriWidth)} ${String(item.level ?? "").padEnd(levelWidth)} ${score.padEnd(scoreWidth)} ${summary}`;
520
- }),
521
- ];
522
- };
523
- const formatOVSearchText = (query, uri, result) => {
524
- if ((result.total ?? 0) <= 0) {
525
- const scope = uri ? ` under ${uri}` : "";
526
- return `No OpenViking resource or skill results found for "${query}"${scope}.`;
527
- }
528
- const scope = uri ? ` under ${uri}` : "";
529
- const lines = [
530
- `Found ${result.total ?? 0} OpenViking results for "${query}"${scope}`,
531
- "",
532
- ...formatOVSearchRows(result),
533
- ].filter((line, index, all) => line || (all[index - 1] && all[index + 1]));
534
- return lines.join("\n");
535
- };
536
- const searchOpenViking = async (input, agentId) => {
537
- const query = input.query.trim();
538
- if (!query) {
539
- throw new Error("query is required");
540
- }
541
- const limit = Math.max(1, Math.floor(input.limit ?? 10));
542
- const client = await getClient();
543
- let result;
544
- if (input.uri) {
545
- result = await client.find(query, { targetUri: input.uri, limit }, agentId);
546
- }
547
- else {
548
- const [resourcesSettled, skillsSettled] = await Promise.allSettled([
549
- client.find(query, { targetUri: "viking://resources", limit }, agentId),
550
- client.find(query, { targetUri: "viking://agent/skills", limit }, agentId),
551
- ]);
552
- const successful = [];
553
- if (resourcesSettled.status === "fulfilled") {
554
- successful.push(resourcesSettled.value);
555
- }
556
- if (skillsSettled.status === "fulfilled") {
557
- successful.push(skillsSettled.value);
558
- }
559
- if (successful.length === 0) {
560
- const firstError = resourcesSettled.status === "rejected"
561
- ? resourcesSettled.reason
562
- : skillsSettled.status === "rejected"
563
- ? skillsSettled.reason
564
- : "Both searches failed";
565
- throw firstError instanceof Error ? firstError : new Error(String(firstError));
566
- }
567
- if (resourcesSettled.status === "rejected") {
568
- api.logger.warn?.(`openviking: resource search failed: ${String(resourcesSettled.reason)}`);
569
- }
570
- if (skillsSettled.status === "rejected") {
571
- api.logger.warn?.(`openviking: skill search failed: ${String(skillsSettled.reason)}`);
572
- }
573
- result = mergeFindResults(successful);
574
- }
575
- return {
576
- content: [{ type: "text", text: formatOVSearchText(query, input.uri, result) }],
577
- details: {
578
- action: "searched",
579
- query,
580
- uri: input.uri,
581
- memories: result.memories ?? [],
582
- resources: result.resources ?? [],
583
- skills: result.skills ?? [],
584
- total: result.total ?? 0,
585
- },
586
- };
587
- };
588
- api.registerTool((ctx) => ({
589
- name: "add_resource",
590
- label: "Add Resource (OpenViking)",
591
- 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. " +
592
- "For a '[media attached: /path ...]' document, set source to that exact local media path. Do not invent OpenViking upload REST endpoints.",
593
- parameters: Type.Object({
594
- source: Type.String({ description: "Local path, OpenClaw media attachment path, directory path, public URL, or Git URL" }),
595
- to: Type.Optional(Type.String({ description: "Exact target URI, e.g. viking://resources/project-docs" })),
596
- parent: Type.Optional(Type.String({ description: "Parent URI under viking://resources" })),
597
- reason: Type.Optional(Type.String({ description: "Reason or note for adding this resource" })),
598
- instruction: Type.Optional(Type.String({ description: "Processing instruction for semantic extraction" })),
599
- wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
600
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
601
- }),
602
- async execute(_toolCallId, params) {
603
- if (isBypassedSession(ctx)) {
604
- return makeBypassedToolResult("add_resource");
605
- }
606
- const session = resolvePluginSessionRouting(ctx);
607
- return addResourceOpenViking({
608
- source: typeof params.source === "string" ? params.source : undefined,
609
- to: typeof params.to === "string" ? params.to : undefined,
610
- parent: typeof params.parent === "string" ? params.parent : undefined,
611
- reason: typeof params.reason === "string" ? params.reason : undefined,
612
- instruction: typeof params.instruction === "string" ? params.instruction : undefined,
613
- wait: typeof params.wait === "boolean" ? params.wait : undefined,
614
- timeout: typeof params.timeout === "number" ? params.timeout : undefined,
615
- }, session.agentId);
616
- },
617
- }), { name: "add_resource" });
618
- api.registerTool((ctx) => ({
619
- name: "add_skill",
620
- label: "Add Skill (OpenViking)",
621
- description: "Use only when the user explicitly asks to import, add, install, or register a skill into OpenViking. " +
622
- "Set source to a local SKILL.md file or skill directory, or data to raw SKILL.md content or an MCP tool dict.",
623
- parameters: Type.Object({
624
- source: Type.Optional(Type.String({ description: "Local SKILL.md path or skill directory path" })),
625
- data: Type.Optional(Type.Any({ description: "Raw SKILL.md content or MCP tool dict" })),
626
- wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })),
627
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })),
628
- }),
629
- async execute(_toolCallId, params) {
630
- if (isBypassedSession(ctx)) {
631
- return makeBypassedToolResult("add_skill");
632
- }
633
- const session = resolvePluginSessionRouting(ctx);
634
- return addSkillOpenViking({
635
- source: typeof params.source === "string" ? params.source : undefined,
636
- data: params.data,
637
- wait: typeof params.wait === "boolean" ? params.wait : undefined,
638
- timeout: typeof params.timeout === "number" ? params.timeout : undefined,
639
- }, session.agentId);
640
- },
641
- }), { name: "add_skill" });
642
- api.registerTool((ctx) => ({
643
- name: "ov_search",
644
- label: "Search (OpenViking)",
645
- description: "Search OpenViking resources and skills. Use after importing, or when the user asks to search OpenViking resources or skills.",
646
- parameters: Type.Object({
647
- query: Type.String({ description: "Search query" }),
648
- uri: Type.Optional(Type.String({ description: "Optional search URI. Defaults to resources plus agent skills." })),
649
- limit: Type.Optional(Type.Number({ description: "Max results per search scope. Default: 10" })),
650
- }),
651
- async execute(_toolCallId, params) {
652
- if (isBypassedSession(ctx)) {
653
- return makeBypassedToolResult("ov_search");
654
- }
655
- const session = resolvePluginSessionRouting(ctx);
656
- return searchOpenViking({
657
- query: String(params.query ?? ""),
658
- uri: typeof params.uri === "string" ? params.uri : undefined,
659
- limit: typeof params.limit === "number" ? params.limit : undefined,
660
- }, session.agentId);
661
- },
662
- }), { name: "ov_search" });
663
- api.registerCommand?.({
664
- name: "add-resource",
665
- description: "Add a resource into OpenViking.",
666
- acceptsArgs: true,
667
- handler: async (ctx) => {
668
- try {
669
- if (isBypassedSession(ctx)) {
670
- const bypassed = makeBypassedToolResult("add_resource");
671
- return { text: bypassed.content[0].text, details: bypassed.details };
672
- }
673
- const session = resolvePluginSessionRouting(ctx);
674
- const input = parseAddResourceCommandArgs(ctx.args ?? "");
675
- const result = await addResourceOpenViking(input, session.agentId);
676
- return { text: result.content[0].text, details: result.details };
677
- }
678
- catch (err) {
679
- return { text: `OpenViking add resource failed: ${err instanceof Error ? err.message : String(err)}` };
680
- }
681
- },
73
+ const { registerOpenVikingTool } = createOpenVikingToolRegistrationRuntime({
74
+ api: api,
75
+ cfg,
76
+ logger: api.logger,
682
77
  });
683
- api.registerCommand?.({
684
- name: "add-skill",
685
- description: "Add a skill into OpenViking.",
686
- acceptsArgs: true,
687
- handler: async (ctx) => {
688
- try {
689
- if (isBypassedSession(ctx)) {
690
- const bypassed = makeBypassedToolResult("add_skill");
691
- return { text: bypassed.content[0].text, details: bypassed.details };
692
- }
693
- const session = resolvePluginSessionRouting(ctx);
694
- const input = parseAddSkillCommandArgs(ctx.args ?? "");
695
- const result = await addSkillOpenViking(input, session.agentId);
696
- return { text: result.content[0].text, details: result.details };
697
- }
698
- catch (err) {
699
- return { text: `OpenViking add skill failed: ${err instanceof Error ? err.message : String(err)}` };
700
- }
701
- },
78
+ const { queryConfigStore, traceRecorder } = createOpenVikingRuntimeState({
79
+ cfg,
80
+ logger: api.logger,
702
81
  });
703
- api.registerCommand?.({
704
- name: "ov-search",
705
- description: "Search OpenViking resources and skills.",
706
- acceptsArgs: true,
707
- handler: async (ctx) => {
708
- try {
709
- if (isBypassedSession(ctx)) {
710
- const bypassed = makeBypassedToolResult("ov_search");
711
- return { text: bypassed.content[0].text, details: bypassed.details };
712
- }
713
- const session = resolvePluginSessionRouting(ctx);
714
- const input = parseOVSearchCommandArgs(ctx.args ?? "");
715
- const result = await searchOpenViking(input, session.agentId);
716
- return { text: result.content[0].text, details: result.details };
717
- }
718
- catch (err) {
719
- return { text: `OpenViking search failed: ${err instanceof Error ? err.message : String(err)}` };
720
- }
721
- },
82
+ const { rememberSessionAgentId, resolveAgentId, resolvePluginSessionRouting, toQueryConfigContext, } = createOpenVikingSessionRoutingRuntime({
83
+ peerPrefix: cfg.peer_prefix,
84
+ logFindRequests: cfg.logFindRequests,
85
+ logger: api.logger,
722
86
  });
723
- api.registerTool((ctx) => ({
724
- name: "memory_recall",
725
- label: "Memory Recall (OpenViking)",
726
- description: "Search long-term memories from OpenViking. Use when you need past user preferences, facts, or decisions.",
727
- parameters: Type.Object({
728
- query: Type.String({ description: "Search query" }),
729
- limit: Type.Optional(Type.Number({ description: "Max results (default: plugin config)" })),
730
- scoreThreshold: Type.Optional(Type.Number({ description: "Minimum score (0-1, default: plugin config)" })),
731
- targetUri: Type.Optional(Type.String({ description: "Search scope URI (default: plugin config)" })),
732
- }),
733
- async execute(_toolCallId, params) {
734
- if (isBypassedSession(ctx)) {
735
- return makeBypassedToolResult("memory_recall");
736
- }
737
- const session = resolvePluginSessionRouting(ctx);
738
- const { query } = params;
739
- const limit = typeof params.limit === "number"
740
- ? Math.max(1, Math.floor(params.limit))
741
- : cfg.recallLimit;
742
- const scoreThreshold = typeof params.scoreThreshold === "number"
743
- ? Math.max(0, Math.min(1, params.scoreThreshold))
744
- : cfg.recallScoreThreshold;
745
- const targetUri = typeof params.targetUri === "string"
746
- ? params.targetUri
747
- : undefined;
748
- const requestLimit = Math.max(limit * 4, 20);
749
- const recallClient = await getClient();
750
- if (cfg.logFindRequests) {
751
- api.logger.info(`openviking: memory_recall X-OpenViking-Agent="${session.agentId}" ` +
752
- `(plugin defaultAgentId="${recallClient.getDefaultAgentId()}" is unused when session context is present)`);
753
- }
754
- let result;
755
- if (targetUri) {
756
- // 如果指定了目标 URI,只检索该位置
757
- result = await recallClient.find(query, {
758
- targetUri,
759
- limit: requestLimit,
760
- scoreThreshold: 0,
761
- }, session.agentId);
762
- }
763
- else {
764
- const searchPromises = [
765
- recallClient.find(query, {
766
- targetUri: "viking://user/memories",
767
- limit: requestLimit,
768
- scoreThreshold: 0,
769
- }, session.agentId),
770
- recallClient.find(query, {
771
- targetUri: "viking://agent/memories",
772
- limit: requestLimit,
773
- scoreThreshold: 0,
774
- }, session.agentId),
775
- ];
776
- if (cfg.recallResources) {
777
- searchPromises.push(recallClient.find(query, {
778
- targetUri: "viking://resources",
779
- limit: requestLimit,
780
- scoreThreshold: 0,
781
- }, session.agentId));
782
- }
783
- const settled = await Promise.allSettled(searchPromises);
784
- const allMemories = [];
785
- for (const s of settled) {
786
- if (s.status === "fulfilled") {
787
- allMemories.push(...(s.value.memories ?? []), ...(s.value.resources ?? []));
788
- }
789
- }
790
- const uniqueMemories = allMemories.filter((memory, index, self) => index === self.findIndex((m) => m.uri === memory.uri));
791
- const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
792
- result = {
793
- memories: leafOnly,
794
- total: leafOnly.length,
795
- };
796
- }
797
- const leafOnly = (result.memories ?? []).filter((m) => !m.level || m.level === 2);
798
- const processed = postProcessMemories(leafOnly, {
799
- limit: requestLimit,
800
- scoreThreshold,
801
- });
802
- const memories = pickMemoriesForInjection(processed, limit, query);
803
- if (memories.length === 0) {
804
- return {
805
- content: [{ type: "text", text: "No relevant OpenViking memories found." }],
806
- details: { count: 0, total: result.total ?? 0, scoreThreshold },
807
- };
808
- }
809
- const { lines: memoryLines } = await buildMemoryLinesWithBudget(memories, (uri) => recallClient.read(uri, session.agentId), {
810
- recallPreferAbstract: false,
811
- recallMaxInjectedChars: cfg.recallMaxInjectedChars,
812
- });
813
- if (memoryLines.length === 0) {
814
- return {
815
- content: [
816
- {
817
- type: "text",
818
- text: `No complete OpenViking memories fit recallMaxInjectedChars=${cfg.recallMaxInjectedChars}.`,
819
- },
820
- ],
821
- details: {
822
- count: 0,
823
- memories,
824
- total: result.total ?? memories.length,
825
- scoreThreshold,
826
- requestLimit,
827
- recallMaxInjectedChars: cfg.recallMaxInjectedChars,
828
- },
829
- };
830
- }
831
- return {
832
- content: [
833
- {
834
- type: "text",
835
- text: `Found ${memoryLines.length} memories:\n\n${memoryLines.join("\n")}`,
836
- },
837
- ],
838
- details: {
839
- count: memoryLines.length,
840
- memories,
841
- total: result.total ?? memories.length,
842
- scoreThreshold,
843
- requestLimit,
844
- recallMaxInjectedChars: cfg.recallMaxInjectedChars,
845
- },
846
- };
847
- },
848
- }), { name: "memory_recall" });
849
- api.registerTool((ctx) => ({
850
- name: "memory_store",
851
- label: "Memory Store (OpenViking)",
852
- description: "Store text in OpenViking memory pipeline by writing to a session and running memory extraction. Use when the user explicitly asks to remember, save, or store an important long-term fact, preference, project, or decision; automatic capture is threshold/commit dependent.",
853
- parameters: Type.Object({
854
- text: Type.String({ description: "Information to store as memory source text" }),
855
- role: Type.Optional(Type.String({ description: "Session role, default user" })),
856
- sessionId: Type.Optional(Type.String({ description: "Existing OpenViking session ID" })),
857
- }),
858
- async execute(_toolCallId, params) {
859
- if (isBypassedSession(ctx)) {
860
- return makeBypassedToolResult("memory_store");
861
- }
862
- const session = resolvePluginSessionRouting(ctx);
863
- const { text } = params;
864
- const role = typeof params.role === "string"
865
- ? params.role
866
- : "user";
867
- const explicitSessionId = typeof params.sessionId === "string" &&
868
- params.sessionId.trim()
869
- ? openClawSessionRefToOvStorageId(params.sessionId)
870
- : undefined;
871
- if (cfg.logFindRequests) {
872
- api.logger.info?.(`openviking: memory_store invoked (textLength=${text?.length ?? 0}, sessionId=${explicitSessionId ?? "auto"})`);
873
- }
874
- let sessionId = explicitSessionId;
875
- let usedTempSession = false;
876
- try {
877
- const c = await getClient();
878
- if (!sessionId) {
879
- sessionId = `memory-store-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
880
- usedTempSession = true;
881
- }
882
- const roleId = role === "user" ? toRoleId(extractToolSenderId(ctx)) : undefined;
883
- await c.addSessionMessage(sessionId, role, [{ type: "text", text }], session.agentId, undefined, roleId);
884
- const commitResult = await c.commitSession(sessionId, {
885
- wait: true,
886
- agentId: session.agentId,
887
- keepRecentCount: 0,
888
- });
889
- const memoriesCount = totalCommitMemories(commitResult);
890
- if (commitResult.status === "failed") {
891
- api.logger.warn(`openviking: memory_store commit failed (sessionId=${sessionId}): ${commitResult.error ?? "unknown"}`);
892
- return {
893
- content: [{ type: "text", text: `Memory extraction failed for session ${sessionId}: ${commitResult.error ?? "unknown"}` }],
894
- details: {
895
- action: "failed",
896
- sessionId,
897
- status: "failed",
898
- error: commitResult.error,
899
- usedTempSession,
900
- },
901
- };
902
- }
903
- if (commitResult.status === "timeout") {
904
- api.logger.warn(`openviking: memory_store commit timed out (sessionId=${sessionId}), task_id=${commitResult.task_id ?? "none"}. Memories may still be extracting in background.`);
905
- return {
906
- content: [{ type: "text", text: `Memory extraction timed out for session ${sessionId}. It may still complete in the background (task_id=${commitResult.task_id ?? "none"}).` }],
907
- details: {
908
- action: "timeout",
909
- sessionId,
910
- status: "timeout",
911
- taskId: commitResult.task_id,
912
- usedTempSession,
913
- },
914
- };
915
- }
916
- if (memoriesCount === 0) {
917
- api.logger.warn(`openviking: memory_store committed but 0 memories extracted (sessionId=${sessionId}). ` +
918
- "No OpenViking-managed long-term memory was created. Check memory.extraction_enabled, VLM configuration/API keys, or whether the text was judged not durable.");
919
- return {
920
- content: [
921
- {
922
- type: "text",
923
- text: `Memory extraction completed for session ${sessionId}, but produced 0 memories. ` +
924
- "No OpenViking-managed long-term memory was stored. Check memory.extraction_enabled, VLM configuration/API keys, or whether the text is durable enough to remember.",
925
- },
926
- ],
927
- details: {
928
- action: "failed",
929
- sessionId,
930
- status: commitResult.status,
931
- error: "no_memories_extracted",
932
- memoriesCount,
933
- archived: commitResult.archived ?? false,
934
- usedTempSession,
935
- },
936
- };
937
- }
938
- else {
939
- api.logger.info?.(`openviking: memory_store committed, memories=${memoriesCount}`);
940
- }
941
- return {
942
- content: [
943
- {
944
- type: "text",
945
- text: `Stored in OpenViking session ${sessionId} and committed ${memoriesCount} memories.`,
946
- },
947
- ],
948
- details: {
949
- action: "stored",
950
- sessionId,
951
- memoriesCount,
952
- status: commitResult.status,
953
- archived: commitResult.archived ?? false,
954
- usedTempSession,
955
- },
956
- };
957
- }
958
- catch (err) {
959
- api.logger.warn(`openviking: memory_store failed: ${String(err)}`);
960
- throw err;
961
- }
962
- },
963
- }), { name: "memory_store" });
964
- api.registerTool((ctx) => ({
965
- name: "memory_forget",
966
- label: "Memory Forget (OpenViking)",
967
- description: "Forget memory by URI, or search then delete when a strong single match is found.",
968
- parameters: Type.Object({
969
- uri: Type.Optional(Type.String({ description: "Exact memory URI to delete" })),
970
- query: Type.Optional(Type.String({ description: "Search query to find memory URI" })),
971
- targetUri: Type.Optional(Type.String({ description: "Search scope URI (default: plugin config)" })),
972
- limit: Type.Optional(Type.Number({ description: "Search limit (default: 5)" })),
973
- scoreThreshold: Type.Optional(Type.Number({ description: "Minimum score (0-1, default: plugin config)" })),
974
- }),
975
- async execute(_toolCallId, params) {
976
- if (isBypassedSession(ctx)) {
977
- return makeBypassedToolResult("memory_forget");
978
- }
979
- const session = resolvePluginSessionRouting(ctx);
980
- const client = await getClient();
981
- const uri = params.uri;
982
- if (uri) {
983
- if (!isMemoryUri(uri)) {
984
- return {
985
- content: [{ type: "text", text: `Refusing to delete non-memory URI: ${uri}` }],
986
- details: { action: "rejected", uri },
987
- };
988
- }
989
- await client.deleteUri(uri, session.agentId);
990
- return {
991
- content: [{ type: "text", text: `Forgotten: ${uri}` }],
992
- details: { action: "deleted", uri },
993
- };
994
- }
995
- const query = params.query;
996
- if (!query) {
997
- return {
998
- content: [{ type: "text", text: "Provide uri or query." }],
999
- details: { error: "missing_param" },
1000
- };
1001
- }
1002
- const limit = typeof params.limit === "number"
1003
- ? Math.max(1, Math.floor(params.limit))
1004
- : 5;
1005
- const scoreThreshold = typeof params.scoreThreshold === "number"
1006
- ? Math.max(0, Math.min(1, params.scoreThreshold))
1007
- : cfg.recallScoreThreshold;
1008
- const targetUri = typeof params.targetUri === "string"
1009
- ? params.targetUri
1010
- : cfg.targetUri;
1011
- const requestLimit = Math.max(limit * 4, 20);
1012
- const result = await client.find(query, {
1013
- targetUri,
1014
- limit: requestLimit,
1015
- scoreThreshold: 0,
1016
- }, session.agentId);
1017
- const candidates = postProcessMemories(result.memories ?? [], {
1018
- limit: requestLimit,
1019
- scoreThreshold,
1020
- leafOnly: true,
1021
- }).filter((item) => isMemoryUri(item.uri));
1022
- if (candidates.length === 0) {
1023
- return {
1024
- content: [
1025
- {
1026
- type: "text",
1027
- text: "No matching leaf memory candidates found. Try a more specific query.",
1028
- },
1029
- ],
1030
- details: { action: "none", scoreThreshold },
1031
- };
1032
- }
1033
- const top = candidates[0];
1034
- if (candidates.length === 1 && clampScore(top.score) >= 0.85) {
1035
- await client.deleteUri(top.uri, session.agentId);
1036
- return {
1037
- content: [{ type: "text", text: `Forgotten: ${top.uri}` }],
1038
- details: { action: "deleted", uri: top.uri, score: top.score ?? 0 },
1039
- };
1040
- }
1041
- const list = candidates
1042
- .map((item) => `- ${item.uri} (${(clampScore(item.score) * 100).toFixed(0)}%)`)
1043
- .join("\n");
1044
- return {
1045
- content: [
1046
- {
1047
- type: "text",
1048
- text: `Found ${candidates.length} candidates. Specify uri:\n${list}`,
1049
- },
1050
- ],
1051
- details: { action: "candidates", candidates, scoreThreshold, requestLimit },
1052
- };
1053
- },
1054
- }), { name: "memory_forget" });
1055
- api.registerTool((ctx) => ({
1056
- name: "ov_archive_search",
1057
- label: "Archive Search (OpenViking)",
1058
- description: "Keyword-grep across all archived original conversation messages of the current session. " +
1059
- "Use this whenever the [Session History Summary] does not contain the specific detail " +
1060
- "the user is asking about. Extract 2-3 concrete entity words from the question " +
1061
- "(names, places, objects, dates) and search each separately. " +
1062
- "Only conclude information is unavailable after trying at least 2 different keyword variations.",
1063
- parameters: Type.Object({
1064
- query: Type.String({
1065
- description: "A single keyword or short phrase to grep. Use concrete nouns, names, dates, " +
1066
- "or distinctive phrases. Case-insensitive. Prefer entity words over full sentences.",
1067
- }),
1068
- archiveId: Type.Optional(Type.String({
1069
- description: 'Optional: limit search to one archive (e.g. "archive_005")',
1070
- })),
1071
- }),
1072
- async execute(_toolCallId, params) {
1073
- if (isBypassedSession(ctx)) {
1074
- return makeBypassedToolResult("ov_archive_search");
1075
- }
1076
- rememberSessionAgentId(ctx);
1077
- const sessionId = ctx.sessionId ?? "";
1078
- const sessionKey = ctx.sessionKey ?? "";
1079
- if (!sessionId && !sessionKey) {
1080
- return {
1081
- content: [{ type: "text", text: "Error: no active session." }],
1082
- details: { error: "no_session" },
1083
- };
1084
- }
1085
- const ovSessionId = openClawSessionToOvStorageId(ctx.sessionId, ctx.sessionKey);
1086
- const query = String(params.query ?? "").trim();
1087
- const archiveId = params.archiveId;
1088
- if (!query) {
1089
- return {
1090
- content: [{ type: "text", text: "Error: query is required." }],
1091
- details: { error: "missing_param", param: "query" },
1092
- };
1093
- }
1094
- const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1095
- api.logger.info?.(`openviking: ov_archive_search query="${query}" escaped="${escapedQuery}" archive=${archiveId ?? "all"} session=${ovSessionId}`);
1096
- try {
1097
- const client = await getClient();
1098
- const agentId = resolveAgentId(ctx.sessionId, ctx.sessionKey);
1099
- const result = await client.grepSessionArchives(ovSessionId, escapedQuery, {
1100
- archiveId,
1101
- caseInsensitive: true,
1102
- agentId,
1103
- });
1104
- if (!result.matches || result.matches.length === 0) {
1105
- return {
1106
- content: [{
1107
- type: "text",
1108
- text: `No matches found for "${query}". Try a different keyword — ` +
1109
- "the original conversation may use different wording than the question. " +
1110
- "Try synonyms, related terms, or shorter fragments.",
1111
- }],
1112
- details: { query, matchCount: 0 },
1113
- };
1114
- }
1115
- const MAX_MATCHES = 12;
1116
- const MAX_LINE_LEN = 1500;
1117
- const shown = result.matches.slice(0, MAX_MATCHES);
1118
- const blocks = shown.map((m, i) => {
1119
- const archiveTag = m.uri.match(/archive_\d+/)?.[0] ?? "unknown";
1120
- const truncated = m.content.length > MAX_LINE_LEN
1121
- ? m.content.slice(0, MAX_LINE_LEN) + "…(truncated)"
1122
- : m.content;
1123
- return `## Match ${i + 1}: ${archiveTag} (line ${m.line})\n${truncated}`;
1124
- });
1125
- const header = `Found ${result.matches.length} match(es) for "${query}"` +
1126
- (result.matches.length > MAX_MATCHES ? ` (showing first ${MAX_MATCHES})` : "") + ":";
1127
- return {
1128
- content: [{ type: "text", text: header + "\n\n" + blocks.join("\n\n") }],
1129
- details: { query, matchCount: result.matches.length },
1130
- };
1131
- }
1132
- catch (err) {
1133
- const msg = err instanceof Error ? err.message : String(err);
1134
- api.logger.error?.(`openviking: ov_archive_search error: ${msg}`);
1135
- return {
1136
- content: [{ type: "text", text: `Archive search failed: ${msg}` }],
1137
- details: { error: msg },
1138
- };
1139
- }
1140
- },
1141
- }), { name: "ov_archive_search" });
1142
- api.registerTool((ctx) => ({
1143
- name: "ov_archive_expand",
1144
- label: "Archive Expand (OpenViking)",
1145
- description: "Retrieve original messages from a compressed session archive. " +
1146
- "Use when a session summary lacks specific details " +
1147
- "such as exact commands, file paths, code snippets, or config values. " +
1148
- "Check [Archive Index] to find the right archive ID.",
1149
- parameters: Type.Object({
1150
- archiveId: Type.String({
1151
- description: 'Archive ID from [Archive Index] (e.g. "archive_002")',
1152
- }),
1153
- }),
1154
- async execute(_toolCallId, params) {
1155
- if (isBypassedSession(ctx)) {
1156
- return makeBypassedToolResult("ov_archive_expand");
1157
- }
1158
- const session = resolvePluginSessionRouting(ctx);
1159
- const archiveId = String(params.archiveId ?? "").trim();
1160
- const sessionId = session.sessionId ?? "";
1161
- api.logger.info?.(`openviking: ov_archive_expand invoked (archiveId=${archiveId || "(empty)"}, sessionId=${sessionId || "(empty)"})`);
1162
- if (!archiveId) {
1163
- api.logger.warn?.(`openviking: ov_archive_expand missing archiveId`);
1164
- return {
1165
- content: [{ type: "text", text: "Error: archiveId is required." }],
1166
- details: { error: "missing_param", param: "archiveId" },
1167
- };
1168
- }
1169
- if (!session.ovSessionId) {
1170
- return {
1171
- content: [{ type: "text", text: "Error: no active session." }],
1172
- details: { error: "no_session" },
1173
- };
1174
- }
1175
- try {
1176
- const client = await getClient();
1177
- const detail = await client.getSessionArchive(session.ovSessionId, archiveId, session.agentId);
1178
- const header = [
1179
- `## ${detail.archive_id}`,
1180
- detail.abstract ? `**Summary**: ${detail.abstract}` : "",
1181
- `**Messages**: ${detail.messages.length}`,
1182
- "",
1183
- ].filter(Boolean).join("\n");
1184
- const body = detail.messages
1185
- .map((m) => formatMessageFaithful(m))
1186
- .join("\n\n");
1187
- api.logger.info?.(`openviking: ov_archive_expand expanded ${detail.archive_id}, messages=${detail.messages.length}, chars=${body.length}, sessionId=${sessionId}`);
1188
- return {
1189
- content: [{ type: "text", text: `${header}\n${body}` }],
1190
- details: {
1191
- action: "expanded",
1192
- archiveId: detail.archive_id,
1193
- messageCount: detail.messages.length,
1194
- sessionId,
1195
- ovSessionId: session.ovSessionId,
1196
- },
1197
- };
1198
- }
1199
- catch (err) {
1200
- const msg = err instanceof Error ? err.message : String(err);
1201
- api.logger.warn?.(`openviking: ov_archive_expand failed (archiveId=${archiveId}, sessionId=${sessionId}): ${msg}`);
1202
- return {
1203
- content: [{ type: "text", text: `Failed to expand ${archiveId}: ${msg}` }],
1204
- details: { error: msg, archiveId, sessionId, ovSessionId: session.ovSessionId },
1205
- };
1206
- }
1207
- },
1208
- }), { name: "ov_archive_expand" });
1209
- api.registerTool((ctx) => ({
1210
- name: "openviking_tool_result_read",
1211
- label: "Tool Result Read (OpenViking)",
1212
- description: "Restore the full original content of a tool result that was externalized by OpenViking. " +
1213
- "Use when a previous tool result was externalized and only a preview is visible — " +
1214
- "the preview contains a [tool-result-ref] or viking://session/.../tool-results/... URI. " +
1215
- "\"Read\" tool returns the same truncated preview; this tool returns the complete content. " +
1216
- "To read all content: pass offset=0 and a limit large enough to cover the whole result " +
1217
- "(e.g. limit=100000). Use offset/limit for paging only when you need a specific section.",
1218
- parameters: Type.Object({
1219
- tool_output_ref: Type.String({
1220
- description: "Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
1221
- }),
1222
- offset: Type.Optional(Type.Number({ description: "Unicode character offset. Default: 0" })),
1223
- limit: Type.Optional(Type.Number({ description: "Maximum Unicode characters to read. Default: 20000" })),
1224
- }),
1225
- async execute(_toolCallId, params) {
1226
- if (isBypassedSession(ctx)) {
1227
- return makeBypassedToolResult("openviking_tool_result_read");
1228
- }
1229
- const session = resolvePluginSessionRouting(ctx);
1230
- if (!session.ovSessionId) {
1231
- return {
1232
- content: [{ type: "text", text: "Error: no active session." }],
1233
- details: { error: "no_session" },
1234
- };
1235
- }
1236
- const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
1237
- if (!parsed) {
1238
- return {
1239
- content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
1240
- details: { error: "invalid_tool_output_ref" },
1241
- };
1242
- }
1243
- if (parsed.sessionId !== session.ovSessionId) {
1244
- return {
1245
- content: [{ type: "text", text: "Error: refusing to read a tool result from another session." }],
1246
- details: {
1247
- error: "session_mismatch",
1248
- requestedSessionId: parsed.sessionId,
1249
- currentSessionId: session.ovSessionId,
1250
- },
1251
- };
1252
- }
1253
- const offset = Math.max(0, getOptionalInteger(params.offset, 0));
1254
- const limit = getOptionalInteger(params.limit, 20_000);
1255
- if (limit < -1) {
1256
- return {
1257
- content: [{ type: "text", text: "Error: limit must be -1 or greater than or equal to 0." }],
1258
- details: { error: "invalid_limit", limit },
1259
- };
1260
- }
1261
- try {
1262
- const client = await getClient();
1263
- const result = await client.readToolResult(session.ovSessionId, parsed.toolResultId, { offset, limit, includeMetadata: true }, session.agentId);
1264
- const returnedChars = result.content.length;
1265
- const nextOffset = result.offset + returnedChars;
1266
- const text = result.content || "(empty tool result chunk)";
1267
- return {
1268
- content: [{ type: "text", text }],
1269
- details: {
1270
- action: "read",
1271
- tool_output_ref: parsed.ref,
1272
- tool_result_id: result.tool_result_id,
1273
- offset: result.offset,
1274
- limit: result.limit,
1275
- returned_chars: returnedChars,
1276
- total_chars: result.total_chars,
1277
- has_more: result.has_more,
1278
- next_offset: result.has_more ? nextOffset : null,
1279
- metadata: result.metadata ?? null,
1280
- },
1281
- };
1282
- }
1283
- catch (err) {
1284
- const msg = err instanceof Error ? err.message : String(err);
1285
- api.logger.warn?.(`openviking: openviking_tool_result_read failed: ${msg}`);
1286
- return {
1287
- content: [{ type: "text", text: `Failed to read tool result: ${msg}` }],
1288
- details: { error: msg, tool_output_ref: parsed.ref },
1289
- };
1290
- }
1291
- },
1292
- }), { name: "openviking_tool_result_read" });
1293
- api.registerTool((ctx) => ({
1294
- name: "openviking_tool_result_search",
1295
- label: "Tool Result Search (OpenViking)",
1296
- description: "Search inside an externalized tool result for a keyword. " +
1297
- "Use when you need to find specific content in a large externalized result, " +
1298
- "before reading it with openviking_tool_result_read. " +
1299
- "Returns matching snippets with their character offsets.",
1300
- parameters: Type.Object({
1301
- tool_output_ref: Type.String({
1302
- description: "Exact OV URI from the preview, e.g. viking://session/<session_id>/tool-results/<tool_result_id>",
1303
- }),
1304
- query: Type.String({ description: "Keyword or exact text to search for" }),
1305
- limit: Type.Optional(Type.Number({ description: "Maximum matches. Default: 20" })),
1306
- context_chars: Type.Optional(Type.Number({ description: "Characters around each match. Default: 300" })),
1307
- }),
1308
- async execute(_toolCallId, params) {
1309
- if (isBypassedSession(ctx)) {
1310
- return makeBypassedToolResult("openviking_tool_result_search");
1311
- }
1312
- const session = resolvePluginSessionRouting(ctx);
1313
- if (!session.ovSessionId) {
1314
- return {
1315
- content: [{ type: "text", text: "Error: no active session." }],
1316
- details: { error: "no_session" },
1317
- };
1318
- }
1319
- const parsed = parseToolResultRef(params.tool_output_ref ?? params.ref ?? params.uri);
1320
- if (!parsed) {
1321
- return {
1322
- content: [{ type: "text", text: "Error: tool_output_ref must be a viking://session/.../tool-results/... URI." }],
1323
- details: { error: "invalid_tool_output_ref" },
1324
- };
1325
- }
1326
- if (parsed.sessionId !== session.ovSessionId) {
1327
- return {
1328
- content: [{ type: "text", text: "Error: refusing to search a tool result from another session." }],
1329
- details: {
1330
- error: "session_mismatch",
1331
- requestedSessionId: parsed.sessionId,
1332
- currentSessionId: session.ovSessionId,
1333
- },
1334
- };
1335
- }
1336
- const query = String(params.query ?? "").trim();
1337
- if (!query) {
1338
- return {
1339
- content: [{ type: "text", text: "Error: query is required." }],
1340
- details: { error: "missing_param", param: "query" },
1341
- };
1342
- }
1343
- const limit = getPositiveInteger(params.limit, 20);
1344
- const contextChars = Math.max(0, getOptionalInteger(params.context_chars ?? params.contextChars, 300));
1345
- try {
1346
- const client = await getClient();
1347
- const result = await client.searchToolResult(session.ovSessionId, parsed.toolResultId, query, { limit, contextChars }, session.agentId);
1348
- const matches = result.matches ?? [];
1349
- const text = matches.length
1350
- ? [
1351
- `Found ${matches.length} match(es) for "${query}" in ${parsed.ref}:`,
1352
- "",
1353
- ...matches.map((match, index) => `## Match ${index + 1} (offset ${match.offset})\n${match.snippet}`),
1354
- ].join("\n")
1355
- : `No matches found for "${query}" in ${parsed.ref}.`;
1356
- return {
1357
- content: [{ type: "text", text }],
1358
- details: {
1359
- action: "searched",
1360
- tool_output_ref: parsed.ref,
1361
- tool_result_id: result.tool_result_id,
1362
- query,
1363
- match_count: matches.length,
1364
- matches,
1365
- },
1366
- };
1367
- }
1368
- catch (err) {
1369
- const msg = err instanceof Error ? err.message : String(err);
1370
- api.logger.warn?.(`openviking: openviking_tool_result_search failed: ${msg}`);
1371
- return {
1372
- content: [{ type: "text", text: `Failed to search tool result: ${msg}` }],
1373
- details: { error: msg, tool_output_ref: parsed.ref, query },
1374
- };
1375
- }
1376
- },
1377
- }), { name: "openviking_tool_result_search" });
1378
- api.registerTool((ctx) => ({
1379
- name: "openviking_tool_result_list",
1380
- label: "Tool Result List (OpenViking)",
1381
- description: "List externalized tool results for the current session. " +
1382
- "Use to discover available refs before calling openviking_tool_result_read. " +
1383
- "Optionally filter by tool_name to narrow down results.",
1384
- parameters: Type.Object({
1385
- tool_name: Type.Optional(Type.String({ description: "Optional exact tool name filter" })),
1386
- limit: Type.Optional(Type.Number({ description: "Maximum results. Default: 50" })),
1387
- }),
1388
- async execute(_toolCallId, params) {
1389
- if (isBypassedSession(ctx)) {
1390
- return makeBypassedToolResult("openviking_tool_result_list");
1391
- }
1392
- const session = resolvePluginSessionRouting(ctx);
1393
- if (!session.ovSessionId) {
1394
- return {
1395
- content: [{ type: "text", text: "Error: no active session." }],
1396
- details: { error: "no_session" },
1397
- };
1398
- }
1399
- const toolName = typeof params.tool_name === "string" && params.tool_name.trim()
1400
- ? params.tool_name.trim()
1401
- : typeof params.toolName === "string" && params.toolName.trim()
1402
- ? params.toolName.trim()
1403
- : undefined;
1404
- const limit = getPositiveInteger(params.limit, 50);
1405
- try {
1406
- const client = await getClient();
1407
- const result = await client.listToolResults(session.ovSessionId, { toolName, limit }, session.agentId);
1408
- const items = result.tool_results ?? [];
1409
- const text = items.length
1410
- ? [
1411
- `Found ${items.length} externalized tool result(s) in current session:`,
1412
- "",
1413
- ...items.map((item, index) => {
1414
- const ref = typeof item.storage_uri === "string" ? item.storage_uri : "(missing ref)";
1415
- const name = typeof item.tool_name === "string" ? item.tool_name : "tool";
1416
- const chars = typeof item.original_chars === "number" ? item.original_chars : "unknown";
1417
- const created = typeof item.created_at === "string" ? ` created_at=${item.created_at}` : "";
1418
- return `${index + 1}. ${name} original_chars=${chars}${created}\nref: ${ref}`;
1419
- }),
1420
- ].join("\n")
1421
- : toolName
1422
- ? `No externalized tool results found for tool "${toolName}" in current session.`
1423
- : "No externalized tool results found in current session.";
1424
- return {
1425
- content: [{ type: "text", text }],
1426
- details: {
1427
- action: "listed",
1428
- session_id: session.ovSessionId,
1429
- tool_name: toolName ?? null,
1430
- count: items.length,
1431
- tool_results: items,
1432
- },
1433
- };
1434
- }
1435
- catch (err) {
1436
- const msg = err instanceof Error ? err.message : String(err);
1437
- api.logger.warn?.(`openviking: openviking_tool_result_list failed: ${msg}`);
1438
- return {
1439
- content: [{ type: "text", text: `Failed to list tool results: ${msg}` }],
1440
- details: { error: msg, session_id: session.ovSessionId, tool_name: toolName ?? null },
1441
- };
1442
- }
1443
- },
1444
- }), { name: "openviking_tool_result_list" });
1445
- let contextEngineRef = null;
1446
- const sessionAgentResolver = createSessionAgentResolver(cfg.agent_prefix);
1447
- const rememberSessionAgentId = (ctx) => {
1448
- sessionAgentResolver.remember(ctx);
1449
- };
1450
- const resolveAgentId = (sessionId, sessionKey, ovSessionId) => {
1451
- const sid = typeof sessionId === "string" ? sessionId.trim() : "";
1452
- const sk = typeof sessionKey === "string" ? sessionKey.trim() : "";
1453
- const ovSid = typeof ovSessionId === "string" ? ovSessionId.trim() : "";
1454
- const result = sessionAgentResolver.resolve(sid, sk, ovSid);
1455
- if (cfg.logFindRequests) {
1456
- api.logger.info(`openviking: resolveAgentId ${JSON.stringify({
1457
- sessionId: sid || "(empty)",
1458
- sessionKey: sk || "(empty)",
1459
- ovSessionId: ovSid || "(empty)",
1460
- parsedConfigAgentPrefix: cfg.agent_prefix,
1461
- mappedResolvedAgentId: result.mappedResolvedAgentId,
1462
- resolvedBeforeSanitize: result.resolvedBeforeSanitize,
1463
- resolved: result.resolved,
1464
- branch: result.branch,
1465
- aliases: result.aliases,
1466
- fromExplicitBinding: result.fromExplicitBinding,
1467
- })}`);
1468
- }
1469
- return result.resolved;
1470
- };
1471
- api.on("session_start", async (_event, ctx) => {
1472
- rememberSessionAgentId(ctx ?? {});
87
+ const recallTraceRuntime = createOpenVikingRecallTraceRuntime({
88
+ getClient,
89
+ resolvePluginSessionRouting,
90
+ traceRecorder,
91
+ registerRecallTraceRoutes: registerRecallTraceRouteAdapters,
92
+ normalizeResourceTypes,
93
+ clampScore,
94
+ previewText,
95
+ cfg,
1473
96
  });
1474
- api.on("session_end", async (_event, ctx) => {
1475
- rememberSessionAgentId(ctx ?? {});
97
+ const { queryRecallTraces, formatRecallTraceText, registerRecallTraceRoutes } = recallTraceRuntime;
98
+ const handleQueryConfigCommand = createOpenVikingQueryConfigCommandHandler({
99
+ resolvePluginSessionRouting,
100
+ toQueryConfigContext,
101
+ queryConfigStore,
102
+ normalizeRuntimeQueryParams,
1476
103
  });
1477
- api.on("before_reset", async (_event, ctx) => {
1478
- if (isBypassedSession(ctx)) {
1479
- verboseRoutingInfo(`openviking: bypassing before_reset due to session pattern match (sessionKey=${ctx?.sessionKey ?? "none"}, sessionId=${ctx?.sessionId ?? "none"})`);
1480
- return;
1481
- }
1482
- const sessionId = ctx?.sessionId;
1483
- if (sessionId && contextEngineRef) {
1484
- try {
1485
- const ok = await contextEngineRef.commitOVSession({
1486
- sessionId,
1487
- sessionKey: ctx?.sessionKey,
1488
- });
1489
- if (ok) {
1490
- api.logger.info(`openviking: committed OV session on reset for session=${sessionId}`);
1491
- }
1492
- }
1493
- catch (err) {
1494
- api.logger.warn(`openviking: failed to commit OV session on reset: ${String(err)}`);
1495
- }
1496
- }
104
+ const { addResourceOpenViking, addSkillOpenViking } = createOpenVikingImportRuntime({
105
+ getClient,
1497
106
  });
1498
- api.on("after_compaction", async (_event, _ctx) => {
1499
- // Reserved hook registration for future post-compaction memory integration.
107
+ const queryRuntime = createOpenVikingQueryRuntime({
108
+ getClient,
109
+ queryConfigStore,
110
+ toQueryConfigContext,
111
+ traceRecorder,
112
+ inferRecallResourceType,
113
+ createTraceId,
114
+ boundTraceQuery,
115
+ previewText,
116
+ logger: api.logger,
117
+ cfg,
1500
118
  });
1501
- if (typeof api.registerContextEngine === "function") {
1502
- api.registerContextEngine(contextEnginePlugin.id, () => {
1503
- contextEngineRef = createMemoryOpenVikingContextEngine({
1504
- id: contextEnginePlugin.id,
1505
- name: contextEnginePlugin.name,
1506
- version: "0.1.0",
1507
- cfg,
1508
- logger: api.logger,
1509
- getClient,
1510
- resolveAgentId,
1511
- rememberSessionAgentId,
1512
- });
1513
- return contextEngineRef;
1514
- });
1515
- api.logger.info("openviking: registered context-engine (assemble=archive+active+auto-recall, afterTurn=auto-capture, session→OV id=uuid-or-sha256 + diag/Phase2 options)");
1516
- }
1517
- else {
1518
- api.logger.warn("openviking: registerContextEngine is unavailable; context-engine behavior will not run");
1519
- }
1520
- registerSetupCli(api);
1521
- api.registerService({
1522
- id: "openviking",
1523
- start: async () => {
1524
- await (await getClient()).healthCheck().catch(() => { });
1525
- api.logger.info(`openviking: initialized (url: ${cfg.baseUrl}, targetUri: ${cfg.targetUri}, search: hybrid endpoint)`);
1526
- },
1527
- stop: () => {
1528
- api.logger.info("openviking: stopped");
1529
- },
119
+ const { searchOpenViking, readOpenVikingContent, multiReadOpenVikingContent, listOpenVikingDirectory, } = queryRuntime;
120
+ registerOpenVikingImportTools({
121
+ registerTool: registerOpenVikingTool,
122
+ getClient,
123
+ resolvePluginSessionRouting,
124
+ isBypassedSession,
125
+ makeBypassedToolResult,
126
+ enableAddResourceTool: cfg.enableAddResourceTool,
127
+ });
128
+ registerOpenVikingQueryTools({
129
+ registerTool: registerOpenVikingTool,
130
+ searchOpenViking,
131
+ readOpenVikingContent,
132
+ multiReadOpenVikingContent,
133
+ listOpenVikingDirectory,
134
+ resolvePluginSessionRouting,
135
+ isBypassedSession,
136
+ makeBypassedToolResult,
1530
137
  });
138
+ registerOpenVikingMemoryRecallTools({
139
+ registerTool: registerOpenVikingTool,
140
+ getClient,
141
+ queryConfigStore,
142
+ toQueryConfigContext,
143
+ resolvePluginSessionRouting,
144
+ isBypassedSession,
145
+ makeBypassedToolResult,
146
+ resolveRecallSearchPlan,
147
+ postProcessMemories,
148
+ pickMemoriesForInjection,
149
+ buildMemoryLinesWithBudget,
150
+ inferRecallResourceType,
151
+ createTraceId,
152
+ boundTraceQuery,
153
+ previewText,
154
+ traceRecorder,
155
+ cfg,
156
+ logger: api.logger,
157
+ });
158
+ registerOpenVikingRecallTraceTools({
159
+ registerTool: registerOpenVikingTool,
160
+ queryRecallTraces,
161
+ formatRecallTraceText,
162
+ resolvePluginSessionRouting,
163
+ isBypassedSession,
164
+ makeBypassedToolResult,
165
+ });
166
+ registerOpenVikingCommands(api, createOpenVikingCommandDefinitions({
167
+ resolvePluginSessionRouting,
168
+ isBypassedSession,
169
+ makeBypassedToolResult,
170
+ parseAddResourceCommandArgs,
171
+ parseAddSkillCommandArgs,
172
+ parseOVSearchCommandArgs,
173
+ addResourceOpenViking,
174
+ addSkillOpenViking,
175
+ searchOpenViking,
176
+ handleQueryConfigCommand,
177
+ queryRecallTraces,
178
+ formatRecallTraceText,
179
+ }));
180
+ registerOpenVikingMemoryTools({
181
+ registerTool: registerOpenVikingTool,
182
+ getClient,
183
+ normalizeSessionId: openClawSessionRefToOvStorageId,
184
+ createTempSessionId: createMemoryStoreTempSessionId,
185
+ extractSenderId: extractToolSenderId,
186
+ toRoleId,
187
+ resolvePluginSessionRouting,
188
+ isBypassedSession,
189
+ makeBypassedToolResult,
190
+ defaultTargetUri: cfg.targetUri,
191
+ defaultRecallScoreThreshold: cfg.recallScoreThreshold,
192
+ logFindRequests: cfg.logFindRequests,
193
+ logger: api.logger,
194
+ });
195
+ registerOpenVikingArchiveTools({
196
+ registerTool: registerOpenVikingTool,
197
+ getClient,
198
+ rememberSessionAgentId,
199
+ toOvSessionId: openClawSessionToOvStorageId,
200
+ resolveAgentId,
201
+ resolvePluginSessionRouting,
202
+ isBypassedSession,
203
+ makeBypassedToolResult,
204
+ formatMessage: formatMessageFaithful,
205
+ traceRecorder,
206
+ traceRecallMaxResultsPerSearch: cfg.traceRecallMaxResultsPerSearch,
207
+ traceRecallPreviewChars: cfg.traceRecallPreviewChars,
208
+ createTraceId,
209
+ logger: api.logger,
210
+ });
211
+ registerOpenVikingToolResultTools({
212
+ registerTool: registerOpenVikingTool,
213
+ getClient,
214
+ resolvePluginSessionRouting,
215
+ isBypassedSession,
216
+ makeBypassedToolResult,
217
+ logger: api.logger,
218
+ });
219
+ const { getContextEngine, setContextEngineRef } = createOpenVikingContextEngineRef();
220
+ registerOpenVikingLifecycleHooks({
221
+ api,
222
+ rememberSessionAgentId,
223
+ isBypassedSession,
224
+ verboseRoutingInfo,
225
+ getContextEngine,
226
+ logger: api.logger,
227
+ });
228
+ registerOpenVikingContextEngine({
229
+ api,
230
+ plugin: contextEnginePlugin,
231
+ version: "0.1.0",
232
+ cfg,
233
+ logger: api.logger,
234
+ getClient,
235
+ resolveAgentId,
236
+ rememberSessionAgentId,
237
+ queryConfigStore,
238
+ traceRecorder,
239
+ createContextEngine: createMemoryOpenVikingContextEngine,
240
+ setContextEngineRef,
241
+ });
242
+ registerSetupCli(api);
243
+ const recallTraceHttpRoutesRegistered = registerRecallTraceRoutes(api);
244
+ api.registerService(createOpenVikingService({
245
+ cfg,
246
+ getClient,
247
+ logger: api.logger,
248
+ recallTraceHttpRoutesRegistered,
249
+ registerRecallTraceRoutes,
250
+ }));
1531
251
  },
1532
252
  };
1533
253
  export default contextEnginePlugin;