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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/INSTALL-AGENT.md +4 -4
  2. package/INSTALL-ZH.md +68 -8
  3. package/INSTALL.md +12 -8
  4. package/README.md +45 -35
  5. package/adapters/http-transport.ts +3 -0
  6. package/adapters/resource-packager.ts +141 -0
  7. package/auto-recall.ts +258 -27
  8. package/client.ts +201 -177
  9. package/commands/setup.ts +200 -33
  10. package/config/feature-gates.json +10 -0
  11. package/config.ts +428 -85
  12. package/context-engine.ts +71 -1402
  13. package/dist/adapters/http-transport.js +1 -0
  14. package/dist/adapters/resource-packager.js +114 -0
  15. package/dist/auto-recall.js +201 -25
  16. package/dist/client.js +125 -161
  17. package/dist/commands/setup.js +170 -32
  18. package/dist/config.js +285 -64
  19. package/dist/context-engine.js +62 -1173
  20. package/dist/index.js +209 -1489
  21. package/dist/memory-ranking.js +35 -12
  22. package/dist/plugin/command-registration.js +5 -0
  23. package/dist/plugin/openviking-archive-tools.js +210 -0
  24. package/dist/plugin/openviking-bypass-runtime.js +8 -0
  25. package/dist/plugin/openviking-client-runtime.js +28 -0
  26. package/dist/plugin/openviking-command-args.js +146 -0
  27. package/dist/plugin/openviking-command-definitions.js +206 -0
  28. package/dist/plugin/openviking-context-engine-ref.js +11 -0
  29. package/dist/plugin/openviking-context-engine-registration.js +23 -0
  30. package/dist/plugin/openviking-feature-gates.js +250 -0
  31. package/dist/plugin/openviking-import-runtime.js +50 -0
  32. package/dist/plugin/openviking-import-tools.js +86 -0
  33. package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
  34. package/dist/plugin/openviking-memory-recall-tools.js +233 -0
  35. package/dist/plugin/openviking-memory-tools.js +199 -0
  36. package/dist/plugin/openviking-query-config-command.js +203 -0
  37. package/dist/plugin/openviking-query-runtime.js +367 -0
  38. package/dist/plugin/openviking-query-tools.js +91 -0
  39. package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
  40. package/dist/plugin/openviking-recall-trace-tools.js +38 -0
  41. package/dist/plugin/openviking-runtime-state.js +26 -0
  42. package/dist/plugin/openviking-runtime-utils.js +62 -0
  43. package/dist/plugin/openviking-services.js +20 -0
  44. package/dist/plugin/openviking-session-routing-runtime.js +61 -0
  45. package/dist/plugin/openviking-tool-result-tools.js +268 -0
  46. package/dist/plugin/recall-trace-routes.js +77 -0
  47. package/dist/plugin/tool-registration.js +23 -0
  48. package/dist/process-manager.js +1 -1
  49. package/dist/query-config.js +336 -0
  50. package/dist/recall-trace.js +309 -0
  51. package/dist/registries/recall-resource-types.js +58 -0
  52. package/dist/routing/identity-routing.js +152 -0
  53. package/dist/routing/memory-uri.js +7 -0
  54. package/dist/services/context-lifecycle-service.js +847 -0
  55. package/dist/services/context-message-adapter.js +340 -0
  56. package/dist/session-transcript-repair.js +0 -1
  57. package/dist/text-utils.js +14 -31
  58. package/index.ts +271 -1873
  59. package/install-manifest.json +10 -1
  60. package/memory-ranking.ts +46 -10
  61. package/openclaw.plugin.json +198 -36
  62. package/package.json +14 -4
  63. package/plugin/command-registration.ts +34 -0
  64. package/plugin/openviking-archive-tools.ts +301 -0
  65. package/plugin/openviking-bypass-runtime.ts +26 -0
  66. package/plugin/openviking-client-runtime.ts +74 -0
  67. package/plugin/openviking-command-args.ts +187 -0
  68. package/plugin/openviking-command-definitions.ts +294 -0
  69. package/plugin/openviking-context-engine-ref.ts +16 -0
  70. package/plugin/openviking-context-engine-registration.ts +97 -0
  71. package/plugin/openviking-feature-gates.ts +335 -0
  72. package/plugin/openviking-import-runtime.ts +105 -0
  73. package/plugin/openviking-import-tools.ts +133 -0
  74. package/plugin/openviking-lifecycle-hooks.ts +65 -0
  75. package/plugin/openviking-memory-recall-tools.ts +368 -0
  76. package/plugin/openviking-memory-tools.ts +299 -0
  77. package/plugin/openviking-query-config-command.ts +230 -0
  78. package/plugin/openviking-query-formatters.ts +108 -0
  79. package/plugin/openviking-query-runtime.ts +476 -0
  80. package/plugin/openviking-query-tools.ts +161 -0
  81. package/plugin/openviking-recall-trace-runtime.ts +482 -0
  82. package/plugin/openviking-recall-trace-tools.ts +99 -0
  83. package/plugin/openviking-runtime-state.ts +52 -0
  84. package/plugin/openviking-runtime-utils.ts +67 -0
  85. package/plugin/openviking-services.ts +49 -0
  86. package/plugin/openviking-session-routing-runtime.ts +105 -0
  87. package/plugin/openviking-tool-result-tools.ts +397 -0
  88. package/plugin/recall-trace-routes.ts +133 -0
  89. package/plugin/tool-registration.ts +51 -0
  90. package/process-manager.ts +1 -1
  91. package/query-config.ts +400 -0
  92. package/recall-trace.ts +442 -0
  93. package/registries/openviking-tools.ts +84 -0
  94. package/registries/recall-resource-types.ts +74 -0
  95. package/routing/identity-routing.ts +210 -0
  96. package/routing/memory-uri.ts +8 -0
  97. package/services/context-lifecycle-service.ts +1209 -0
  98. package/services/context-message-adapter.ts +375 -0
  99. package/services/setup/config-writer.ts +125 -0
  100. package/services/setup/exit-utils.ts +5 -0
  101. package/services/setup/package-metadata.ts +56 -0
  102. package/services/setup/probe-service.ts +143 -0
  103. package/services/setup/setup-flow.ts +306 -0
  104. package/services/setup/version-compatibility.ts +41 -0
  105. package/session-transcript-repair.ts +0 -2
  106. package/skills/install-openviking-memory/SKILL.md +123 -50
  107. package/skills/openviking-context-database/SKILL.md +286 -0
  108. package/text-utils.ts +15 -34
@@ -0,0 +1,387 @@
1
+ function getOptionalInteger(value, fallback) {
2
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3
+ return fallback;
4
+ }
5
+ return Math.floor(value);
6
+ }
7
+ function getPositiveInteger(value, fallback) {
8
+ return Math.max(1, getOptionalInteger(value, fallback));
9
+ }
10
+ function toQueryObject(request) {
11
+ const query = { ...(request?.query ?? {}) };
12
+ if (request?.url) {
13
+ const parsed = new URL(request.url, "http://openclaw.local");
14
+ for (const [key, value] of parsed.searchParams.entries()) {
15
+ query[key] = value;
16
+ }
17
+ }
18
+ return { ...query, ...(request?.params ?? {}) };
19
+ }
20
+ function toBoolean(value) {
21
+ if (typeof value === "boolean")
22
+ return value;
23
+ if (typeof value !== "string")
24
+ return undefined;
25
+ return ["1", "true", "yes"].includes(value.trim().toLowerCase());
26
+ }
27
+ function toNumber(value) {
28
+ if (typeof value === "number")
29
+ return value;
30
+ if (typeof value === "string" && value.trim()) {
31
+ const parsed = Number(value);
32
+ return Number.isFinite(parsed) ? parsed : undefined;
33
+ }
34
+ return undefined;
35
+ }
36
+ function toString(value) {
37
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
38
+ }
39
+ function queryValue(query, ...keys) {
40
+ for (const key of keys) {
41
+ if (query[key] !== undefined) {
42
+ return query[key];
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ function toSessionKey(query) {
48
+ return toString(queryValue(query, "sessionKey", "sessionkey", "session_key", "session-key"));
49
+ }
50
+ function toLimitedInteger(value, fallback, min, max) {
51
+ const parsed = toNumber(value);
52
+ const raw = parsed === undefined ? fallback : Math.floor(parsed);
53
+ return Number.isFinite(raw) && raw >= min && raw <= max ? raw : undefined;
54
+ }
55
+ function inferUriType(uri) {
56
+ if (uri.startsWith("viking://resources") || uri.startsWith("viking://resource"))
57
+ return "resource";
58
+ if (uri.startsWith("viking://session/"))
59
+ return "session";
60
+ if (uri.startsWith("viking://user/skills") || uri.startsWith("viking://skills"))
61
+ return "skill";
62
+ if (uri.startsWith("viking://user/"))
63
+ return "user_memory";
64
+ if (uri.includes("/archive") || uri.includes("/history/"))
65
+ return "archive";
66
+ return "unknown";
67
+ }
68
+ function hasExplicitRecallTraceIdentityFilter(input) {
69
+ const traceId = typeof input.traceId === "string" && input.traceId.trim();
70
+ const sessionId = typeof input.sessionId === "string" && input.sessionId.trim();
71
+ const sessionKey = typeof input.sessionKey === "string" && input.sessionKey.trim();
72
+ const ovSessionId = typeof input.ovSessionId === "string" && input.ovSessionId.trim();
73
+ return !!(traceId || sessionId || sessionKey || ovSessionId);
74
+ }
75
+ function findTraceItem(entry, uri) {
76
+ const selected = entry.selected.find((item) => item.uri === uri);
77
+ const searchResult = entry.searches.flatMap((search) => search.results).find((item) => item.uri === uri);
78
+ if (!selected && !searchResult) {
79
+ return undefined;
80
+ }
81
+ return {
82
+ category: selected?.category ?? searchResult?.category,
83
+ score: selected?.score ?? searchResult?.score,
84
+ level: searchResult?.level,
85
+ abstractPreview: selected?.abstractPreview ?? searchResult?.abstractPreview,
86
+ resultType: searchResult?.resultType,
87
+ sourceTraceId: entry.traceId,
88
+ source: entry.source,
89
+ };
90
+ }
91
+ export function createOpenVikingRecallTraceRuntime(deps) {
92
+ const parseRecallTraceInput = (input, ctx) => {
93
+ const traceId = typeof input.traceId === "string" && input.traceId.trim() ? input.traceId.trim() : undefined;
94
+ const explicitSessionId = typeof input.sessionId === "string" && input.sessionId.trim() ? input.sessionId.trim() : undefined;
95
+ const explicitSessionKey = typeof input.sessionKey === "string" && input.sessionKey.trim() ? input.sessionKey.trim() : undefined;
96
+ const explicitOvSessionId = typeof input.ovSessionId === "string" && input.ovSessionId.trim() ? input.ovSessionId.trim() : undefined;
97
+ const hasExplicitIdentityFilter = !!(traceId || explicitSessionId || explicitSessionKey || explicitOvSessionId);
98
+ const defaultSessionKey = !hasExplicitIdentityFilter ? ctx.sessionKey : undefined;
99
+ const defaultSessionId = !hasExplicitIdentityFilter && !defaultSessionKey ? ctx.sessionId : undefined;
100
+ const defaultOvSessionId = !hasExplicitIdentityFilter && !defaultSessionKey && !defaultSessionId ? ctx.ovSessionId : undefined;
101
+ return {
102
+ turn: input.turn === "all" ? "all" : "latest",
103
+ traceId,
104
+ sessionId: explicitSessionId ?? defaultSessionId,
105
+ sessionKey: explicitSessionKey ?? defaultSessionKey,
106
+ ovSessionId: explicitOvSessionId ?? defaultOvSessionId,
107
+ source: typeof input.source === "string" && input.source.trim() ? input.source : undefined,
108
+ resourceTypes: input.resourceTypes ? deps.normalizeResourceTypes(input.resourceTypes) : undefined,
109
+ since: typeof input.since === "number" ? input.since : undefined,
110
+ until: typeof input.until === "number" ? input.until : undefined,
111
+ limit: getPositiveInteger(input.limit, 20),
112
+ };
113
+ };
114
+ const shouldIncludeTraceContent = (input) => typeof input?.includeContent === "boolean" ? input.includeContent : deps.cfg.traceRecallIncludeContentByDefault;
115
+ const enrichTraceEntriesWithContent = async (result, includeContent, agentId) => {
116
+ if (!includeContent || result.entries.length === 0) {
117
+ return result;
118
+ }
119
+ const client = await deps.getClient();
120
+ const warnings = [...result.warnings];
121
+ const entries = await Promise.all(result.entries.map(async (entry) => {
122
+ const selected = await Promise.all(entry.selected.map(async (item) => {
123
+ try {
124
+ const content = await client.read(item.uri, agentId);
125
+ const text = typeof content === "string" ? content : JSON.stringify(content);
126
+ return { ...item, contentPreview: deps.previewText(text, deps.cfg.recallMaxContentChars) };
127
+ }
128
+ catch (err) {
129
+ const readError = err instanceof Error ? err.message : String(err);
130
+ warnings.push(`Failed to read recall trace content ${item.uri}: ${readError}`);
131
+ return { ...item, readError };
132
+ }
133
+ }));
134
+ return { ...entry, selected };
135
+ }));
136
+ return { ...result, entries, warnings };
137
+ };
138
+ const queryTraceForRoute = async (query, session) => deps.traceRecorder
139
+ ? deps.traceRecorder.queryWithFallback(parseRecallTraceInput(query, session))
140
+ : Promise.resolve({ entries: [], lookupLayer: "memory", warnings: ["traceRecall is disabled"] });
141
+ const queryRecallTraces = async (input, session) => {
142
+ const query = parseRecallTraceInput(input, session);
143
+ const base = deps.traceRecorder
144
+ ? await deps.traceRecorder.queryWithFallback(query)
145
+ : { entries: [], lookupLayer: "memory", warnings: ["traceRecall is disabled"] };
146
+ if (deps.traceRecorder &&
147
+ base.entries.length === 0 &&
148
+ !hasExplicitRecallTraceIdentityFilter(input) &&
149
+ query.sessionKey) {
150
+ const fallbackIdentities = [];
151
+ if (session.sessionId) {
152
+ fallbackIdentities.push({ ...query, sessionKey: undefined, sessionId: session.sessionId, ovSessionId: undefined });
153
+ }
154
+ if (session.ovSessionId) {
155
+ fallbackIdentities.push({ ...query, sessionKey: undefined, sessionId: undefined, ovSessionId: session.ovSessionId });
156
+ }
157
+ for (const fallbackQuery of fallbackIdentities) {
158
+ const fallback = await deps.traceRecorder.queryWithFallback(fallbackQuery);
159
+ if (fallback.entries.length > 0) {
160
+ return enrichTraceEntriesWithContent(fallback, shouldIncludeTraceContent(input), session.agentId);
161
+ }
162
+ }
163
+ }
164
+ return enrichTraceEntriesWithContent(base, shouldIncludeTraceContent(input), session.agentId);
165
+ };
166
+ const handleUriDetail = async (request) => {
167
+ const query = toQueryObject(request);
168
+ const uri = toString(query.uri);
169
+ if (!uri || !uri.startsWith("viking://") || uri.endsWith("...") || uri.includes("…")) {
170
+ return { status: 400, body: { ok: false, uri: uri ?? "", readStatus: "not_requested", warnings: [], error: { code: "invalid_uri", message: "uri must be a complete viking:// URI" } } };
171
+ }
172
+ const offset = toLimitedInteger(query.offset, 0, 0, 1_000_000_000);
173
+ const contentLimit = toLimitedInteger(queryValue(query, "contentLimit", "content-limit", "content_limit"), 20_000, 1, 100_000);
174
+ if (offset === undefined || contentLimit === undefined) {
175
+ return { status: 400, body: { ok: false, uri, readStatus: "not_requested", warnings: [], error: { code: "invalid_param", message: "offset or contentLimit is invalid" } } };
176
+ }
177
+ const includeContent = toBoolean(queryValue(query, "includeContent", "include-content", "include_content")) ?? true;
178
+ const preferTracePreview = toBoolean(queryValue(query, "preferTracePreview", "prefer-trace-preview", "prefer_trace_preview")) ?? true;
179
+ const session = deps.resolvePluginSessionRouting(query);
180
+ const traceId = toString(queryValue(query, "traceId", "trace-id", "trace_id"));
181
+ let traceMetadata;
182
+ const warnings = [];
183
+ if (preferTracePreview && traceId && deps.traceRecorder) {
184
+ const traceResult = await queryTraceForRoute({ traceId, turn: "latest", limit: 1 }, session);
185
+ warnings.push(...traceResult.warnings);
186
+ traceMetadata = traceResult.entries[0] ? findTraceItem(traceResult.entries[0], uri) : undefined;
187
+ }
188
+ const baseBody = {
189
+ ok: true,
190
+ uri,
191
+ uriType: inferUriType(uri),
192
+ abstractPreview: traceMetadata?.abstractPreview,
193
+ metadata: {
194
+ category: traceMetadata?.category,
195
+ score: traceMetadata?.score,
196
+ level: traceMetadata?.level,
197
+ resultType: traceMetadata?.resultType,
198
+ sourceTraceId: traceMetadata?.sourceTraceId,
199
+ source: traceMetadata?.source,
200
+ },
201
+ readStatus: "not_requested",
202
+ warnings,
203
+ };
204
+ if (!includeContent) {
205
+ return { status: 200, body: baseBody };
206
+ }
207
+ try {
208
+ const client = await deps.getClient();
209
+ const content = await client.read(uri, toString(query.agentId) ?? session.agentId);
210
+ const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
211
+ const chars = Array.from(text);
212
+ const slice = chars.slice(offset, offset + contentLimit).join("");
213
+ return {
214
+ status: 200,
215
+ body: {
216
+ ...baseBody,
217
+ content: {
218
+ text: slice,
219
+ offset,
220
+ limit: contentLimit,
221
+ returnedChars: Array.from(slice).length,
222
+ totalChars: chars.length,
223
+ hasMore: offset + contentLimit < chars.length,
224
+ },
225
+ readStatus: "ok",
226
+ },
227
+ };
228
+ }
229
+ catch (err) {
230
+ return {
231
+ status: 502,
232
+ body: {
233
+ ...baseBody,
234
+ ok: false,
235
+ readStatus: "read_failed",
236
+ error: { code: "read_failed", message: err instanceof Error ? err.message : String(err) },
237
+ },
238
+ };
239
+ }
240
+ };
241
+ const handleLatestOvSearchList = async (request) => {
242
+ const query = toQueryObject(request);
243
+ const session = deps.resolvePluginSessionRouting(query);
244
+ const limit = toLimitedInteger(query.limit, 20, 1, 100);
245
+ if (limit === undefined) {
246
+ return { status: 400, body: { ok: false, items: [], totalItems: 0, warnings: [], error: { code: "invalid_param", message: "limit is invalid" } } };
247
+ }
248
+ const includeSelected = toBoolean(query.includeSelected) ?? true;
249
+ const dedupe = toBoolean(query.dedupe) ?? true;
250
+ const includeSkills = toBoolean(query.includeSkills) ?? true;
251
+ const strict = toBoolean(query.strict) ?? false;
252
+ const result = await queryTraceForRoute({
253
+ turn: "latest",
254
+ source: "ov_search",
255
+ sessionId: toString(query.sessionId),
256
+ sessionKey: toSessionKey(query),
257
+ ovSessionId: toString(query.ovSessionId),
258
+ limit: 1,
259
+ }, session);
260
+ const agentId = toString(query.agentId);
261
+ const entry = agentId ? result.entries.find((candidate) => candidate.agentId === agentId) : result.entries[0];
262
+ if (!entry) {
263
+ const body = {
264
+ ok: !strict,
265
+ lookupLayer: "none",
266
+ fallbackUsed: false,
267
+ query: { sessionId: toString(query.sessionId), sessionKey: toString(query.sessionKey), ovSessionId: toString(query.ovSessionId), agentId, limit, lookup: toString(query.lookup) ?? "auto" },
268
+ items: [],
269
+ totalItems: 0,
270
+ warnings: [...result.warnings, "no ov_search trace found for latest interaction"],
271
+ ...(strict ? { error: { code: "not_found", message: "no ov_search trace found for latest interaction" } } : {}),
272
+ };
273
+ return { status: strict ? 404 : 200, body };
274
+ }
275
+ const searchResultByUri = new Map();
276
+ for (const search of entry.searches) {
277
+ for (const item of search.results) {
278
+ searchResultByUri.set(item.uri, { ...item, targetUri: search.targetUriResolved ?? search.targetUriInput });
279
+ }
280
+ }
281
+ const items = [];
282
+ const addItem = (item, source) => {
283
+ const matched = searchResultByUri.get(item.uri);
284
+ const resultType = matched?.resultType ?? (inferUriType(item.uri) === "skill" ? "skill" : inferUriType(item.uri).includes("memory") ? "memory" : "resource");
285
+ if (!includeSkills && resultType === "skill") {
286
+ return;
287
+ }
288
+ const row = {
289
+ uri: item.uri,
290
+ abstractPreview: item.abstractPreview ?? matched?.abstractPreview,
291
+ resourceType: item.resourceType ?? matched?.resourceType,
292
+ resultType,
293
+ category: item.category ?? matched?.category,
294
+ score: item.score ?? matched?.score,
295
+ source,
296
+ targetUri: matched?.targetUri,
297
+ detailUrl: `/api/openviking/uri-detail?uri=${encodeURIComponent(item.uri)}&traceId=${encodeURIComponent(entry.traceId)}`,
298
+ };
299
+ if (dedupe) {
300
+ const existing = items.findIndex((candidate) => candidate.uri === item.uri);
301
+ if (existing >= 0) {
302
+ if (source === "selected")
303
+ items[existing] = row;
304
+ return;
305
+ }
306
+ }
307
+ items.push(row);
308
+ };
309
+ if (includeSelected) {
310
+ entry.selected.forEach((item) => addItem(item, "selected"));
311
+ }
312
+ for (const search of entry.searches) {
313
+ search.results.forEach((item) => addItem(item, "search_result"));
314
+ }
315
+ const limited = items.slice(0, limit);
316
+ return {
317
+ status: 200,
318
+ body: {
319
+ ok: true,
320
+ lookupLayer: result.lookupLayer,
321
+ fallbackUsed: result.lookupLayer === "persistent",
322
+ query: { sessionId: toString(query.sessionId), sessionKey: toString(query.sessionKey), ovSessionId: toString(query.ovSessionId), agentId, limit, lookup: toString(query.lookup) ?? "auto" },
323
+ trace: {
324
+ traceId: entry.traceId,
325
+ ts: entry.ts,
326
+ isoTime: new Date(entry.ts).toISOString(),
327
+ triggerQuery: entry.trigger.query,
328
+ totalSearches: entry.searches.length,
329
+ },
330
+ items: limited,
331
+ totalItems: limited.length,
332
+ warnings: result.warnings,
333
+ },
334
+ };
335
+ };
336
+ const handleRecallTraces = async (request) => {
337
+ const query = toQueryObject(request);
338
+ const session = deps.resolvePluginSessionRouting(query);
339
+ const result = await queryRecallTraces({
340
+ turn: query.turn === "all" ? "all" : "latest",
341
+ traceId: toString(queryValue(query, "traceId", "trace-id", "trace_id")),
342
+ sessionId: typeof query.sessionId === "string" ? query.sessionId : undefined,
343
+ sessionKey: toSessionKey(query),
344
+ ovSessionId: typeof query.ovSessionId === "string" ? query.ovSessionId : undefined,
345
+ source: typeof query.source === "string" ? query.source : undefined,
346
+ resourceTypes: typeof query.resourceTypes === "string" ? query.resourceTypes : undefined,
347
+ since: toNumber(query.since),
348
+ until: toNumber(query.until),
349
+ includeContent: toBoolean(queryValue(query, "includeContent", "include-content", "include_content")),
350
+ limit: toNumber(query.limit),
351
+ }, session);
352
+ return { status: 200, body: { ok: true, ...result } };
353
+ };
354
+ const formatRecallTraceText = (result) => {
355
+ if (result.entries.length === 0) {
356
+ return `No OpenViking recall traces found (lookupLayer=${result.lookupLayer}).`;
357
+ }
358
+ const blocks = result.entries.map((entry, index) => {
359
+ const selected = entry.selected.slice(0, 8)
360
+ .map((item) => ` - ${item.uri}${item.score !== undefined ? ` (${(deps.clampScore(item.score) * 100).toFixed(0)}%)` : ""}`)
361
+ .join("\n");
362
+ return [
363
+ `## Trace ${index + 1}: ${entry.source}`,
364
+ `traceId: ${entry.traceId}`,
365
+ `query: ${entry.trigger.query}`,
366
+ `resourceTypes: ${entry.resourceTypes.join(", ")}`,
367
+ `stats: candidates=${entry.stats.candidateCount}, selected=${entry.stats.selectedCount}, injected=${entry.stats.injectedCount}`,
368
+ selected ? `selected:\n${selected}` : "selected: (none)",
369
+ ].join("\n");
370
+ });
371
+ const warnings = result.warnings.length > 0
372
+ ? `\n\nWarnings:\n${result.warnings.map((w) => `- ${w}`).join("\n")}`
373
+ : "";
374
+ return `${blocks.join("\n\n")}${warnings}`;
375
+ };
376
+ const routeHandlers = {
377
+ handleRecallTraces,
378
+ handleUriDetail,
379
+ handleLatestOvSearchList,
380
+ };
381
+ return {
382
+ queryRecallTraces,
383
+ formatRecallTraceText,
384
+ routeHandlers,
385
+ registerRecallTraceRoutes: (ctx) => deps.registerRecallTraceRoutes(ctx, routeHandlers),
386
+ };
387
+ }
@@ -0,0 +1,38 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ export function registerOpenVikingRecallTraceTools(deps) {
3
+ deps.registerTool((ctx) => ({
4
+ name: "ov_recall_trace",
5
+ label: "Recall Trace (OpenViking)",
6
+ description: "Query OpenViking recall trace records captured by auto-recall and explicit recall/search tools.",
7
+ parameters: Type.Object({
8
+ turn: Type.Optional(Type.String({ description: "latest or all (default: latest)" })),
9
+ traceId: Type.Optional(Type.String({ description: "Exact trace id" })),
10
+ sessionId: Type.Optional(Type.String({ description: "OpenClaw session id" })),
11
+ sessionKey: Type.Optional(Type.String({ description: "OpenClaw session key" })),
12
+ ovSessionId: Type.Optional(Type.String({ description: "OpenViking session id" })),
13
+ source: Type.Optional(Type.String({ description: "auto_recall, memory_recall, ov_search, or ov_archive_search" })),
14
+ resourceTypes: Type.Optional(Type.Array(Type.String({ description: "resource, user, or agent" }))),
15
+ since: Type.Optional(Type.Number({ description: "Unix timestamp lower bound in milliseconds" })),
16
+ until: Type.Optional(Type.Number({ description: "Unix timestamp upper bound in milliseconds" })),
17
+ includeContent: Type.Optional(Type.Boolean({ description: "Read selected/displayed URI content previews on demand" })),
18
+ limit: Type.Optional(Type.Number({ description: "Maximum traces to return (default: 20)" })),
19
+ }),
20
+ async execute(_toolCallId, params) {
21
+ if (deps.isBypassedSession(ctx)) {
22
+ return deps.makeBypassedToolResult("ov_recall_trace");
23
+ }
24
+ const session = deps.resolvePluginSessionRouting(ctx);
25
+ const result = await deps.queryRecallTraces(params, session);
26
+ return {
27
+ content: [{ type: "text", text: deps.formatRecallTraceText(result) }],
28
+ details: {
29
+ action: "queried",
30
+ count: result.entries.length,
31
+ lookupLayer: result.lookupLayer,
32
+ warnings: result.warnings,
33
+ entries: result.entries,
34
+ },
35
+ };
36
+ },
37
+ }), { name: "ov_recall_trace" });
38
+ }
@@ -0,0 +1,26 @@
1
+ import { RuntimeQueryConfigStore } from "../query-config.js";
2
+ import { RecallTraceRecorder } from "../recall-trace.js";
3
+ export function createOpenVikingRuntimeState(options) {
4
+ const { cfg, logger } = options;
5
+ const queryConfigStore = new RuntimeQueryConfigStore({
6
+ staticConfig: cfg,
7
+ path: cfg.runtimeQueryConfigPath || undefined,
8
+ });
9
+ void queryConfigStore.load().catch((err) => {
10
+ logger.warn?.(`openviking: failed to load runtime query config: ${String(err)}`);
11
+ });
12
+ const traceRecorder = cfg.traceRecall
13
+ ? new RecallTraceRecorder({
14
+ memoryMaxEntries: cfg.traceRecallMaxEntries,
15
+ persist: cfg.traceRecallPersist,
16
+ traceDir: cfg.traceRecallDir,
17
+ includeRawUserPreview: cfg.traceRecallIncludeRawUserPreview,
18
+ retentionDays: cfg.traceRecallRetentionDays,
19
+ queryMaxDays: cfg.traceRecallQueryMaxDays,
20
+ })
21
+ : undefined;
22
+ return {
23
+ queryConfigStore,
24
+ traceRecorder,
25
+ };
26
+ }
@@ -0,0 +1,62 @@
1
+ export function previewText(value, maxChars) {
2
+ const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
3
+ if (!text) {
4
+ return undefined;
5
+ }
6
+ return text.length <= maxChars ? text : `${text.slice(0, Math.max(0, maxChars - 1))}…`;
7
+ }
8
+ export function inferRecallResourceType(uri) {
9
+ if (uri.startsWith("viking://resources"))
10
+ return "resource";
11
+ if (uri.startsWith("viking://user/skills") || uri.startsWith("viking://skills"))
12
+ return "agent";
13
+ if (uri.startsWith("viking://user/"))
14
+ return "user";
15
+ return undefined;
16
+ }
17
+ export function createTraceId(source) {
18
+ return `${source}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
19
+ }
20
+ export function createMemoryStoreTempSessionId() {
21
+ return `memory-store-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
22
+ }
23
+ export function boundTraceQuery(query, maxChars) {
24
+ if (query.length <= maxChars) {
25
+ return { query };
26
+ }
27
+ return { query: query.slice(0, maxChars), queryTruncated: true };
28
+ }
29
+ export function extractToolSenderId(ctx) {
30
+ if (!ctx || typeof ctx !== "object") {
31
+ return undefined;
32
+ }
33
+ const toolCtx = ctx;
34
+ if (typeof toolCtx.requesterSenderId === "string") {
35
+ const trimmed = toolCtx.requesterSenderId.trim();
36
+ if (trimmed) {
37
+ return trimmed;
38
+ }
39
+ }
40
+ if (typeof toolCtx.senderId === "string") {
41
+ const trimmed = toolCtx.senderId.trim();
42
+ if (trimmed) {
43
+ return trimmed;
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+ export function makeBypassedToolResult(toolName) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `OpenViking is bypassed for this session by bypassSessionPatterns; ${toolName} was skipped.`,
54
+ },
55
+ ],
56
+ details: {
57
+ action: "bypassed",
58
+ reason: "session_bypassed",
59
+ toolName,
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,20 @@
1
+ export function createOpenVikingService({ cfg, getClient, logger, recallTraceHttpRoutesRegistered, registerRecallTraceRoutes, }) {
2
+ return {
3
+ id: "openviking",
4
+ start: async (ctx) => {
5
+ const runtimeRouteRegistered = registerRecallTraceRoutes(ctx);
6
+ const routeRegistered = recallTraceHttpRoutesRegistered || runtimeRouteRegistered;
7
+ await (await getClient()).healthCheck().catch(() => { });
8
+ logger.info(`openviking: initialized (url: ${cfg.baseUrl}, targetUri: ${cfg.targetUri}, search: hybrid endpoint)`);
9
+ if (routeRegistered) {
10
+ logger.info("openviking: registered recall trace Gateway routes");
11
+ }
12
+ else {
13
+ logger.warn?.("openviking: recall trace Gateway route adapter unavailable; use ov_recall_trace tool or /ov-recall-trace command");
14
+ }
15
+ },
16
+ stop: () => {
17
+ logger.info("openviking: stopped");
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,61 @@
1
+ import { createSessionAgentResolver, openClawSessionToOvStorageId, } from "../routing/identity-routing.js";
2
+ export function createOpenVikingSessionRoutingRuntime(options) {
3
+ const sessionAgentResolver = createSessionAgentResolver(options.peerPrefix);
4
+ const rememberSessionAgentId = (ctx) => {
5
+ sessionAgentResolver.remember(ctx);
6
+ };
7
+ const resolveAgentId = (sessionId, sessionKey, ovSessionId) => {
8
+ const sid = typeof sessionId === "string" ? sessionId.trim() : "";
9
+ const sk = typeof sessionKey === "string" ? sessionKey.trim() : "";
10
+ const ovSid = typeof ovSessionId === "string" ? ovSessionId.trim() : "";
11
+ const result = sessionAgentResolver.resolve(sid, sk, ovSid);
12
+ if (options.logFindRequests) {
13
+ options.logger.info(`openviking: resolveAgentId ${JSON.stringify({
14
+ sessionId: sid || "(empty)",
15
+ sessionKey: sk || "(empty)",
16
+ ovSessionId: ovSid || "(empty)",
17
+ parsedConfigPeerPrefix: options.peerPrefix,
18
+ mappedResolvedAgentId: result.mappedResolvedAgentId,
19
+ resolvedBeforeSanitize: result.resolvedBeforeSanitize,
20
+ resolved: result.resolved,
21
+ branch: result.branch,
22
+ aliases: result.aliases,
23
+ fromExplicitBinding: result.fromExplicitBinding,
24
+ })}`);
25
+ }
26
+ return result.resolved;
27
+ };
28
+ const resolvePluginSessionRouting = (ctx) => {
29
+ const sessionId = typeof ctx?.sessionId === "string" ? ctx.sessionId.trim() : "";
30
+ const sessionKey = typeof ctx?.sessionKey === "string" ? ctx.sessionKey.trim() : "";
31
+ let ovSessionId = typeof ctx?.ovSessionId === "string" ? ctx.ovSessionId.trim() : "";
32
+ if (!ovSessionId && (sessionId || sessionKey)) {
33
+ ovSessionId = openClawSessionToOvStorageId(sessionId || undefined, sessionKey || undefined);
34
+ }
35
+ const session = {
36
+ agentId: ctx?.agentId,
37
+ sessionId: sessionId || undefined,
38
+ sessionKey: sessionKey || undefined,
39
+ ovSessionId: ovSessionId || undefined,
40
+ };
41
+ rememberSessionAgentId(session);
42
+ return {
43
+ sessionId: session.sessionId,
44
+ sessionKey: session.sessionKey,
45
+ ovSessionId: session.ovSessionId,
46
+ agentId: resolveAgentId(session.sessionId, session.sessionKey, session.ovSessionId),
47
+ };
48
+ };
49
+ const toQueryConfigContext = (session) => ({
50
+ peerId: session.agentId,
51
+ sessionId: session.sessionId,
52
+ sessionKey: session.sessionKey,
53
+ ovSessionId: session.ovSessionId,
54
+ });
55
+ return {
56
+ rememberSessionAgentId,
57
+ resolveAgentId,
58
+ resolvePluginSessionRouting,
59
+ toQueryConfigContext,
60
+ };
61
+ }