@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,442 @@
1
+ import { appendFile, mkdir, readFile, readdir, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ export type RecallResourceType = "resource" | "session" | "user" | "agent";
5
+
6
+ export type RecallTraceSource = "auto_recall" | "memory_recall" | "ov_search" | "ov_archive_search";
7
+
8
+ export type RecallTraceOperationType = "semantic_find" | "archive_grep";
9
+
10
+ export type RecallTraceResult = {
11
+ uri: string;
12
+ resourceType?: RecallResourceType | "archive";
13
+ category?: string;
14
+ score?: number;
15
+ level?: number;
16
+ abstractPreview?: string;
17
+ resultType: "memory" | "resource" | "skill" | "archive_match";
18
+ };
19
+
20
+ export type RecallTraceEntry = {
21
+ schemaVersion: "1.0";
22
+ traceId: string;
23
+ ts: number;
24
+ sessionId?: string;
25
+ sessionKey?: string;
26
+ ovSessionId?: string;
27
+ agentId?: string;
28
+ source: RecallTraceSource;
29
+ operationType: RecallTraceOperationType;
30
+ resourceTypes: RecallResourceType[];
31
+ trigger: {
32
+ rawUserTextPreview?: string;
33
+ query: string;
34
+ derivedKeywords?: string[];
35
+ queryTruncated?: boolean;
36
+ };
37
+ searches: Array<{
38
+ resourceType: RecallResourceType | "archive";
39
+ contextType?: "memory" | "resource" | "skill";
40
+ targetUriInput?: string;
41
+ targetUriResolved?: string;
42
+ limit: number;
43
+ scoreThreshold?: number;
44
+ durationMs: number;
45
+ total: number;
46
+ results: RecallTraceResult[];
47
+ archiveId?: string;
48
+ caseInsensitive?: boolean;
49
+ error?: string;
50
+ }>;
51
+ selected: Array<{
52
+ uri: string;
53
+ resourceType?: RecallResourceType | "archive";
54
+ category?: string;
55
+ score?: number;
56
+ line?: number;
57
+ abstractPreview?: string;
58
+ contentPreview?: string;
59
+ readError?: string;
60
+ injected?: boolean;
61
+ displayed?: boolean;
62
+ skippedReason?: "score_threshold" | "dedupe" | "non_leaf" | "budget" | "not_top_k" | "search_error";
63
+ }>;
64
+ stats: {
65
+ candidateCount: number;
66
+ selectedCount: number;
67
+ injectedCount: number;
68
+ estimatedTokens?: number;
69
+ };
70
+ };
71
+
72
+ export type RecallTraceQuery = {
73
+ turn?: "latest" | "all";
74
+ traceId?: string;
75
+ sessionId?: string;
76
+ sessionKey?: string;
77
+ ovSessionId?: string;
78
+ source?: RecallTraceSource;
79
+ resourceTypes?: RecallResourceType[];
80
+ since?: number;
81
+ until?: number;
82
+ limit?: number;
83
+ };
84
+
85
+ export type RecallTraceQueryResult = {
86
+ entries: RecallTraceEntry[];
87
+ lookupLayer: "memory" | "persistent";
88
+ warnings: string[];
89
+ };
90
+
91
+ export type RecallTraceFlushResult = {
92
+ warnings: string[];
93
+ };
94
+
95
+ const ALLOWED_RESOURCE_TYPES: RecallResourceType[] = ["resource", "user", "agent"];
96
+ const DEFAULT_RESOURCE_TYPES: RecallResourceType[] = ["user", "agent"];
97
+
98
+ function toResourceTypeEntries(value: unknown): string[] {
99
+ if (Array.isArray(value)) {
100
+ return value
101
+ .filter((entry): entry is string => typeof entry === "string")
102
+ .map((entry) => entry.trim())
103
+ .filter(Boolean);
104
+ }
105
+ if (typeof value === "string") {
106
+ return value
107
+ .split(/[,\n]/)
108
+ .map((entry) => entry.trim())
109
+ .filter(Boolean);
110
+ }
111
+ return [];
112
+ }
113
+
114
+ export function normalizeResourceTypes(value: unknown): RecallResourceType[] {
115
+ const entries = toResourceTypeEntries(value);
116
+ if (entries.length === 0) {
117
+ return [...DEFAULT_RESOURCE_TYPES];
118
+ }
119
+
120
+ const seen = new Set<RecallResourceType>();
121
+ const normalized: RecallResourceType[] = [];
122
+ const invalid: string[] = [];
123
+ for (const entry of entries) {
124
+ if ((ALLOWED_RESOURCE_TYPES as string[]).includes(entry)) {
125
+ const typed = entry as RecallResourceType;
126
+ if (!seen.has(typed)) {
127
+ seen.add(typed);
128
+ normalized.push(typed);
129
+ }
130
+ } else {
131
+ invalid.push(entry);
132
+ }
133
+ }
134
+
135
+ if (invalid.length > 0) {
136
+ throw new Error(`invalid resourceTypes: ${invalid.join(", ")}`);
137
+ }
138
+
139
+ return normalized.length > 0 ? normalized : [...DEFAULT_RESOURCE_TYPES];
140
+ }
141
+
142
+ export function resolveRecallSearchPlan(
143
+ resourceTypes: unknown,
144
+ _ctx: { ovSessionId?: string; agentId?: string },
145
+ ): {
146
+ resourceTypes: RecallResourceType[];
147
+ searches: Array<{ resourceType: RecallResourceType; targetUri?: string; contextType: "memory" | "resource" }>;
148
+ skipped: Array<{ resourceType: RecallResourceType; reason: "missing_session" }>;
149
+ } {
150
+ const normalized = normalizeResourceTypes(resourceTypes);
151
+ const searches: Array<{ resourceType: RecallResourceType; targetUri?: string; contextType: "memory" | "resource" }> = [];
152
+ const skipped: Array<{ resourceType: RecallResourceType; reason: "missing_session" }> = [];
153
+ let addedMemorySearch = false;
154
+
155
+ for (const resourceType of normalized) {
156
+ if (resourceType === "resource") {
157
+ searches.push({ resourceType, contextType: "resource" });
158
+ } else if ((resourceType === "user" || resourceType === "agent") && !addedMemorySearch) {
159
+ searches.push({ resourceType: "user", contextType: "memory" });
160
+ addedMemorySearch = true;
161
+ }
162
+ }
163
+
164
+ return { resourceTypes: normalized, searches, skipped };
165
+ }
166
+
167
+ export class RecallTraceMemoryStore {
168
+ private readonly maxEntries: number;
169
+ private readonly entries: RecallTraceEntry[] = [];
170
+
171
+ constructor(maxEntries: number) {
172
+ this.maxEntries = Math.max(1, Math.floor(maxEntries));
173
+ }
174
+
175
+ record(entry: RecallTraceEntry): void {
176
+ this.entries.push(entry);
177
+ while (this.entries.length > this.maxEntries) {
178
+ this.entries.shift();
179
+ }
180
+ }
181
+
182
+ query(query: RecallTraceQuery): RecallTraceQueryResult {
183
+ const limit = Math.max(1, Math.floor(query.limit ?? 20));
184
+ const turn = query.turn ?? "latest";
185
+ const resourceTypes = query.resourceTypes && query.resourceTypes.length > 0
186
+ ? new Set(query.resourceTypes)
187
+ : undefined;
188
+
189
+ const filtered = this.entries
190
+ .filter((entry) => {
191
+ if (query.traceId && entry.traceId !== query.traceId) return false;
192
+ if (query.source && entry.source !== query.source) return false;
193
+ if (query.sessionId && entry.sessionId !== query.sessionId) return false;
194
+ if (query.sessionKey && entry.sessionKey !== query.sessionKey) return false;
195
+ if (query.ovSessionId && entry.ovSessionId !== query.ovSessionId) return false;
196
+ if (typeof query.since === "number" && entry.ts < query.since) return false;
197
+ if (typeof query.until === "number" && entry.ts > query.until) return false;
198
+ if (resourceTypes && !entry.resourceTypes.some((resourceType) => resourceTypes.has(resourceType))) {
199
+ return false;
200
+ }
201
+ return true;
202
+ })
203
+ .sort((a, b) => b.ts - a.ts);
204
+
205
+ return { entries: filtered.slice(0, turn === "latest" ? 1 : limit), lookupLayer: "memory", warnings: [] };
206
+ }
207
+ }
208
+
209
+ function jsonlFileNameForTimestamp(ts: number): string {
210
+ return `${new Date(ts).toISOString().slice(0, 10)}.jsonl`;
211
+ }
212
+
213
+ function startOfUtcDay(ts: number): number {
214
+ const d = new Date(ts);
215
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
216
+ }
217
+
218
+ function timestampFromJsonlFileName(name: string): number | undefined {
219
+ const match = /^(\d{4})-(\d{2})-(\d{2})\.jsonl$/.exec(name);
220
+ if (!match) {
221
+ return undefined;
222
+ }
223
+ const ts = Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
224
+ return Number.isFinite(ts) ? ts : undefined;
225
+ }
226
+
227
+ function fileMayOverlapQueryWindow(name: string, since: number, until: number): boolean {
228
+ const dayStart = timestampFromJsonlFileName(name);
229
+ if (dayStart === undefined) {
230
+ return true;
231
+ }
232
+ const dayEnd = dayStart + 86_400_000 - 1;
233
+ return dayEnd >= since && dayStart <= until;
234
+ }
235
+
236
+ function isRecallTraceEntry(value: unknown): value is RecallTraceEntry {
237
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
238
+ return false;
239
+ }
240
+ const candidate = value as Partial<RecallTraceEntry>;
241
+ return candidate.schemaVersion === "1.0" &&
242
+ typeof candidate.traceId === "string" &&
243
+ typeof candidate.ts === "number" &&
244
+ typeof candidate.source === "string" &&
245
+ typeof candidate.operationType === "string" &&
246
+ Array.isArray(candidate.resourceTypes) &&
247
+ !!candidate.trigger &&
248
+ typeof candidate.trigger.query === "string";
249
+ }
250
+
251
+ export class RecallTraceJsonlStore {
252
+ private readonly dir: string;
253
+ private readonly includeRawUserPreview: boolean;
254
+ private readonly retentionDays: number;
255
+ private readonly queryMaxDays: number;
256
+ private readonly pending: Promise<void>[] = [];
257
+ private readonly warnings: string[] = [];
258
+
259
+ constructor(options: { dir: string; includeRawUserPreview?: boolean; retentionDays?: number; queryMaxDays?: number }) {
260
+ this.dir = options.dir;
261
+ this.includeRawUserPreview = options.includeRawUserPreview === true;
262
+ this.retentionDays = Math.max(1, Math.floor(options.retentionDays ?? 14));
263
+ this.queryMaxDays = Math.max(1, Math.floor(options.queryMaxDays ?? 14));
264
+ }
265
+
266
+ private entryForPersistence(entry: RecallTraceEntry): RecallTraceEntry {
267
+ if (this.includeRawUserPreview || entry.trigger.rawUserTextPreview === undefined) {
268
+ return entry;
269
+ }
270
+ return {
271
+ ...entry,
272
+ trigger: {
273
+ ...entry.trigger,
274
+ rawUserTextPreview: undefined,
275
+ },
276
+ };
277
+ }
278
+
279
+ append(entry: RecallTraceEntry): Promise<void> {
280
+ const write = (async () => {
281
+ await mkdir(this.dir, { recursive: true });
282
+ await this.pruneExpiredFiles(entry.ts);
283
+ await appendFile(
284
+ join(this.dir, jsonlFileNameForTimestamp(entry.ts)),
285
+ `${JSON.stringify(this.entryForPersistence(entry))}\n`,
286
+ "utf8",
287
+ );
288
+ })().catch((err: unknown) => {
289
+ this.warnings.push(`Failed to append recall trace JSONL: ${err instanceof Error ? err.message : String(err)}`);
290
+ });
291
+
292
+ this.pending.push(write);
293
+ return write;
294
+ }
295
+
296
+ private async pruneExpiredFiles(nowTs: number): Promise<void> {
297
+ const cutoff = startOfUtcDay(nowTs - this.retentionDays * 86_400_000);
298
+ let files: string[];
299
+ try {
300
+ files = await readdir(this.dir);
301
+ } catch {
302
+ return;
303
+ }
304
+ await Promise.all(files
305
+ .filter((name) => name.endsWith(".jsonl"))
306
+ .filter((name) => {
307
+ const ts = timestampFromJsonlFileName(name);
308
+ return ts !== undefined && ts < cutoff;
309
+ })
310
+ .map(async (name) => {
311
+ try {
312
+ await unlink(join(this.dir, name));
313
+ } catch (err: unknown) {
314
+ this.warnings.push(`Failed to prune recall trace file ${name}: ${err instanceof Error ? err.message : String(err)}`);
315
+ }
316
+ }));
317
+ }
318
+
319
+ async flush(): Promise<RecallTraceFlushResult> {
320
+ const pending = this.pending.splice(0);
321
+ await Promise.all(pending);
322
+ return { warnings: [...this.warnings] };
323
+ }
324
+
325
+ async query(query: RecallTraceQuery): Promise<RecallTraceQueryResult> {
326
+ await this.flush();
327
+ const warnings = [...this.warnings];
328
+ const entries: RecallTraceEntry[] = [];
329
+
330
+ let files: string[];
331
+ try {
332
+ files = await readdir(this.dir);
333
+ } catch (err: unknown) {
334
+ const code = typeof err === "object" && err !== null && "code" in err ? String((err as { code?: unknown }).code) : "";
335
+ if (code === "ENOENT") {
336
+ return { entries: [], lookupLayer: "persistent", warnings };
337
+ }
338
+ return {
339
+ entries: [],
340
+ lookupLayer: "persistent",
341
+ warnings: [...warnings, `Failed to read recall trace directory: ${err instanceof Error ? err.message : String(err)}`],
342
+ };
343
+ }
344
+
345
+ const queryStart = typeof query.since === "number"
346
+ ? query.since
347
+ : Date.now() - this.queryMaxDays * 86_400_000;
348
+ const queryEnd = typeof query.until === "number" ? query.until : Date.now();
349
+ for (const file of files
350
+ .filter((name) => name.endsWith(".jsonl"))
351
+ .filter((name) => fileMayOverlapQueryWindow(name, queryStart, queryEnd))
352
+ .sort()) {
353
+ const path = join(this.dir, file);
354
+ let content: string;
355
+ try {
356
+ content = await readFile(path, "utf8");
357
+ } catch (err: unknown) {
358
+ warnings.push(`Failed to read recall trace file ${file}: ${err instanceof Error ? err.message : String(err)}`);
359
+ continue;
360
+ }
361
+
362
+ const lines = content.split("\n");
363
+ for (let index = 0; index < lines.length; index++) {
364
+ const line = lines[index]!.trim();
365
+ if (!line) {
366
+ continue;
367
+ }
368
+ try {
369
+ const parsed: unknown = JSON.parse(line);
370
+ if (isRecallTraceEntry(parsed)) {
371
+ entries.push(parsed);
372
+ } else {
373
+ warnings.push(`Skipping corrupted recall trace line ${file}:${index + 1}`);
374
+ }
375
+ } catch {
376
+ warnings.push(`Skipping corrupted recall trace line ${file}:${index + 1}`);
377
+ }
378
+ }
379
+ }
380
+
381
+ const memory = new RecallTraceMemoryStore(Math.max(1, entries.length));
382
+ for (const entry of entries) {
383
+ memory.record(entry);
384
+ }
385
+ const filtered = memory.query(query);
386
+ return { entries: filtered.entries, lookupLayer: "persistent", warnings };
387
+ }
388
+ }
389
+
390
+ export class RecallTraceRecorder {
391
+ private readonly memory: RecallTraceMemoryStore;
392
+ private readonly persistent?: RecallTraceJsonlStore;
393
+
394
+ constructor(options: {
395
+ memoryMaxEntries: number;
396
+ persist: boolean;
397
+ traceDir: string;
398
+ includeRawUserPreview?: boolean;
399
+ retentionDays?: number;
400
+ queryMaxDays?: number;
401
+ }) {
402
+ this.memory = new RecallTraceMemoryStore(options.memoryMaxEntries);
403
+ this.persistent = options.persist ? new RecallTraceJsonlStore({
404
+ dir: options.traceDir,
405
+ includeRawUserPreview: options.includeRawUserPreview,
406
+ retentionDays: options.retentionDays,
407
+ queryMaxDays: options.queryMaxDays,
408
+ }) : undefined;
409
+ }
410
+
411
+ record(entry: RecallTraceEntry): void {
412
+ this.memory.record(entry);
413
+ void this.persistent?.append(entry);
414
+ }
415
+
416
+ async recordAndFlush(entry: RecallTraceEntry): Promise<RecallTraceFlushResult> {
417
+ this.memory.record(entry);
418
+ await this.persistent?.append(entry);
419
+ return this.flush();
420
+ }
421
+
422
+ query(query: RecallTraceQuery): RecallTraceQueryResult {
423
+ return this.memory.query(query);
424
+ }
425
+
426
+ async queryWithFallback(query: RecallTraceQuery): Promise<RecallTraceQueryResult> {
427
+ const memoryResult = this.memory.query(query);
428
+ if (memoryResult.entries.length > 0 || !this.persistent) {
429
+ return memoryResult;
430
+ }
431
+ const persistentResult = await this.persistent.query(query);
432
+ return {
433
+ entries: persistentResult.entries,
434
+ lookupLayer: "persistent",
435
+ warnings: [...memoryResult.warnings, ...persistentResult.warnings],
436
+ };
437
+ }
438
+
439
+ async flush(): Promise<RecallTraceFlushResult> {
440
+ return this.persistent ? this.persistent.flush() : { warnings: [] };
441
+ }
442
+ }
@@ -0,0 +1,84 @@
1
+ export type OpenVikingToolGroup =
2
+ | "memory"
3
+ | "resource_query"
4
+ | "import"
5
+ | "recall_trace"
6
+ | "archive"
7
+ | "tool_result";
8
+
9
+ export type OpenVikingToolName =
10
+ | "add_resource"
11
+ | "add_skill"
12
+ | "ov_search"
13
+ | "ov_read"
14
+ | "ov_multi_read"
15
+ | "ov_list"
16
+ | "memory_recall"
17
+ | "ov_recall_trace"
18
+ | "memory_store"
19
+ | "memory_forget"
20
+ | "ov_archive_search"
21
+ | "ov_archive_expand"
22
+ | "openviking_tool_result_read"
23
+ | "openviking_tool_result_search"
24
+ | "openviking_tool_result_list";
25
+
26
+ export type OpenVikingToolSpec = {
27
+ name: OpenVikingToolName;
28
+ group: OpenVikingToolGroup;
29
+ defaultEnabled: boolean;
30
+ requiresLegacyFlag?: "enableAddResourceTool";
31
+ };
32
+
33
+ export const OPENVIKING_ADD_RESOURCE_TOOL_NAME = "add_resource" as const;
34
+
35
+ export const OPENVIKING_TOOL_SPECS = [
36
+ {
37
+ name: OPENVIKING_ADD_RESOURCE_TOOL_NAME,
38
+ group: "import",
39
+ defaultEnabled: false,
40
+ requiresLegacyFlag: "enableAddResourceTool",
41
+ },
42
+ { name: "add_skill", group: "import", defaultEnabled: true },
43
+ { name: "ov_search", group: "resource_query", defaultEnabled: true },
44
+ { name: "ov_read", group: "resource_query", defaultEnabled: true },
45
+ { name: "ov_multi_read", group: "resource_query", defaultEnabled: true },
46
+ { name: "ov_list", group: "resource_query", defaultEnabled: true },
47
+ { name: "memory_recall", group: "memory", defaultEnabled: true },
48
+ { name: "ov_recall_trace", group: "recall_trace", defaultEnabled: true },
49
+ { name: "memory_store", group: "memory", defaultEnabled: true },
50
+ { name: "memory_forget", group: "memory", defaultEnabled: true },
51
+ { name: "ov_archive_search", group: "archive", defaultEnabled: true },
52
+ { name: "ov_archive_expand", group: "archive", defaultEnabled: true },
53
+ { name: "openviking_tool_result_read", group: "tool_result", defaultEnabled: true },
54
+ { name: "openviking_tool_result_search", group: "tool_result", defaultEnabled: true },
55
+ { name: "openviking_tool_result_list", group: "tool_result", defaultEnabled: true },
56
+ ] as const satisfies readonly OpenVikingToolSpec[];
57
+
58
+ export const OPENVIKING_ALL_TOOL_NAMES = OPENVIKING_TOOL_SPECS.map((spec) => spec.name) as OpenVikingToolName[];
59
+
60
+ export const OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES = OPENVIKING_TOOL_SPECS
61
+ .filter((spec) => spec.defaultEnabled)
62
+ .map((spec) => spec.name) as OpenVikingToolName[];
63
+
64
+ const OPENVIKING_TOOL_GROUP_ORDER: readonly OpenVikingToolGroup[] = [
65
+ "memory",
66
+ "resource_query",
67
+ "import",
68
+ "recall_trace",
69
+ "archive",
70
+ "tool_result",
71
+ ];
72
+
73
+ export const OPENVIKING_TOOL_GROUPS: Record<string, readonly OpenVikingToolName[]> = {
74
+ all: OPENVIKING_ALL_TOOL_NAMES,
75
+ default: OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES,
76
+ ...Object.fromEntries(
77
+ OPENVIKING_TOOL_GROUP_ORDER.map((group) => [
78
+ group,
79
+ OPENVIKING_TOOL_SPECS
80
+ .filter((spec) => spec.group === group)
81
+ .map((spec) => spec.name),
82
+ ]),
83
+ ),
84
+ };
@@ -0,0 +1,74 @@
1
+ export const ALLOWED_RECALL_RESOURCE_TYPES = ["resource", "user", "agent"] as const;
2
+ export type RecallResourceType = typeof ALLOWED_RECALL_RESOURCE_TYPES[number];
3
+ export const DEFAULT_RECALL_RESOURCE_TYPES: readonly RecallResourceType[] = ["user", "agent"];
4
+
5
+ export type RecallSearchPlan = {
6
+ resourceTypes: RecallResourceType[];
7
+ searches: Array<{ resourceType: RecallResourceType; contextType: "memory" | "resource"; targetUri?: string }>;
8
+ skipped: Array<{ resourceType: RecallResourceType; reason: "missing_session" }>;
9
+ };
10
+
11
+ function toResourceTypeEntries(value: unknown): string[] {
12
+ if (Array.isArray(value)) {
13
+ return value
14
+ .filter((entry): entry is string => typeof entry === "string")
15
+ .map((entry) => entry.trim())
16
+ .filter(Boolean);
17
+ }
18
+ if (typeof value === "string") {
19
+ return value
20
+ .split(/[,\n]/)
21
+ .map((entry) => entry.trim())
22
+ .filter(Boolean);
23
+ }
24
+ return [];
25
+ }
26
+
27
+ export function normalizeRecallResourceTypes(value: unknown): RecallResourceType[] {
28
+ const entries = toResourceTypeEntries(value);
29
+ if (entries.length === 0) {
30
+ return [...DEFAULT_RECALL_RESOURCE_TYPES];
31
+ }
32
+
33
+ const seen = new Set<RecallResourceType>();
34
+ const normalized: RecallResourceType[] = [];
35
+ const invalid: string[] = [];
36
+ for (const entry of entries) {
37
+ if ((ALLOWED_RECALL_RESOURCE_TYPES as readonly string[]).includes(entry)) {
38
+ const typed = entry as RecallResourceType;
39
+ if (!seen.has(typed)) {
40
+ seen.add(typed);
41
+ normalized.push(typed);
42
+ }
43
+ } else {
44
+ invalid.push(entry);
45
+ }
46
+ }
47
+
48
+ if (invalid.length > 0) {
49
+ throw new Error(`invalid resourceTypes: ${invalid.join(", ")}`);
50
+ }
51
+
52
+ return normalized.length > 0 ? normalized : [...DEFAULT_RECALL_RESOURCE_TYPES];
53
+ }
54
+
55
+ export function resolveRecallSearchPlan(
56
+ resourceTypes: unknown,
57
+ _ctx: { ovSessionId?: string; agentId?: string },
58
+ ): RecallSearchPlan {
59
+ const normalized = normalizeRecallResourceTypes(resourceTypes);
60
+ const searches: RecallSearchPlan["searches"] = [];
61
+ const skipped: RecallSearchPlan["skipped"] = [];
62
+ let addedMemorySearch = false;
63
+
64
+ for (const resourceType of normalized) {
65
+ if (resourceType === "resource") {
66
+ searches.push({ resourceType, contextType: "resource" });
67
+ } else if ((resourceType === "user" || resourceType === "agent") && !addedMemorySearch) {
68
+ searches.push({ resourceType: "user", contextType: "memory" });
69
+ addedMemorySearch = true;
70
+ }
71
+ }
72
+
73
+ return { resourceTypes: normalized, searches, skipped };
74
+ }