@openviking/openclaw-plugin 2026.5.26 → 2026.6.17-dev.1

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 (112) hide show
  1. package/INSTALL-AGENT.md +355 -355
  2. package/INSTALL-ZH.md +405 -345
  3. package/INSTALL.md +366 -362
  4. package/README.md +364 -338
  5. package/adapters/http-transport.ts +3 -0
  6. package/adapters/resource-packager.ts +141 -0
  7. package/auto-recall.ts +518 -257
  8. package/client.ts +963 -939
  9. package/commands/setup.ts +1169 -1002
  10. package/config/feature-gates.json +10 -0
  11. package/config.ts +819 -476
  12. package/context-engine.ts +408 -1737
  13. package/dist/adapters/http-transport.js +1 -0
  14. package/dist/adapters/resource-packager.js +114 -0
  15. package/dist/auto-recall.js +228 -32
  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 +64 -1179
  20. package/dist/index.js +209 -1470
  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 +5 -17
  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/dist/token-estimator.js +51 -0
  59. package/index.ts +408 -1985
  60. package/install-manifest.json +57 -47
  61. package/memory-ranking.ts +303 -267
  62. package/openclaw.plugin.json +497 -335
  63. package/package.json +86 -76
  64. package/plugin/command-registration.ts +34 -0
  65. package/plugin/openviking-archive-tools.ts +301 -0
  66. package/plugin/openviking-bypass-runtime.ts +26 -0
  67. package/plugin/openviking-client-runtime.ts +74 -0
  68. package/plugin/openviking-command-args.ts +187 -0
  69. package/plugin/openviking-command-definitions.ts +294 -0
  70. package/plugin/openviking-context-engine-ref.ts +16 -0
  71. package/plugin/openviking-context-engine-registration.ts +97 -0
  72. package/plugin/openviking-feature-gates.ts +335 -0
  73. package/plugin/openviking-import-runtime.ts +105 -0
  74. package/plugin/openviking-import-tools.ts +133 -0
  75. package/plugin/openviking-lifecycle-hooks.ts +65 -0
  76. package/plugin/openviking-memory-recall-tools.ts +368 -0
  77. package/plugin/openviking-memory-tools.ts +299 -0
  78. package/plugin/openviking-query-config-command.ts +230 -0
  79. package/plugin/openviking-query-formatters.ts +108 -0
  80. package/plugin/openviking-query-runtime.ts +476 -0
  81. package/plugin/openviking-query-tools.ts +161 -0
  82. package/plugin/openviking-recall-trace-runtime.ts +482 -0
  83. package/plugin/openviking-recall-trace-tools.ts +99 -0
  84. package/plugin/openviking-runtime-state.ts +52 -0
  85. package/plugin/openviking-runtime-utils.ts +67 -0
  86. package/plugin/openviking-services.ts +49 -0
  87. package/plugin/openviking-session-routing-runtime.ts +105 -0
  88. package/plugin/openviking-tool-result-tools.ts +397 -0
  89. package/plugin/recall-trace-routes.ts +133 -0
  90. package/plugin/tool-registration.ts +51 -0
  91. package/process-manager.ts +41 -45
  92. package/query-config.ts +400 -0
  93. package/recall-trace.ts +442 -0
  94. package/registries/openviking-tools.ts +84 -0
  95. package/registries/recall-resource-types.ts +74 -0
  96. package/routing/identity-routing.ts +210 -0
  97. package/routing/memory-uri.ts +8 -0
  98. package/runtime-utils.ts +5 -5
  99. package/services/context-lifecycle-service.ts +1209 -0
  100. package/services/context-message-adapter.ts +375 -0
  101. package/services/setup/config-writer.ts +125 -0
  102. package/services/setup/exit-utils.ts +5 -0
  103. package/services/setup/package-metadata.ts +56 -0
  104. package/services/setup/probe-service.ts +143 -0
  105. package/services/setup/setup-flow.ts +306 -0
  106. package/services/setup/version-compatibility.ts +41 -0
  107. package/session-transcript-repair.ts +528 -530
  108. package/skills/install-openviking-memory/SKILL.md +752 -665
  109. package/skills/openviking-context-database/SKILL.md +286 -0
  110. package/text-utils.ts +488 -507
  111. package/token-estimator.ts +65 -0
  112. package/tool-call-id.ts +331 -331
@@ -0,0 +1 @@
1
+ export const defaultHttpTransport = (url, init) => fetch(url, init);
@@ -0,0 +1,114 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { once } from "node:events";
3
+ import { createWriteStream } from "node:fs";
4
+ import { mkdtemp, readdir, readFile, rm, stat } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { basename, dirname, join, relative } from "node:path";
7
+ import { Zip, ZipDeflate } from "fflate";
8
+ const REMOTE_RESOURCE_PREFIXES = ["http://", "https://", "git@", "ssh://", "git://"];
9
+ function toBlobPart(value) {
10
+ return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
11
+ }
12
+ export function isRemoteResourceSource(source) {
13
+ return REMOTE_RESOURCE_PREFIXES.some((prefix) => source.startsWith(prefix));
14
+ }
15
+ export async function cleanupUploadTempPath(path) {
16
+ if (!path) {
17
+ return;
18
+ }
19
+ await rm(path, { force: true }).catch(() => undefined);
20
+ await rm(dirname(path), { recursive: true, force: true }).catch(() => undefined);
21
+ }
22
+ export async function zipDirectoryForUpload(dirPath) {
23
+ const rootStats = await stat(dirPath);
24
+ if (!rootStats.isDirectory()) {
25
+ throw new Error(`Not a directory: ${dirPath}`);
26
+ }
27
+ const zipDir = await mkdtemp(join(tmpdir(), "openviking-openclaw-upload-"));
28
+ const zipPath = join(zipDir, `${basename(dirPath).replace(/[^a-zA-Z0-9._-]/g, "_")}-${randomUUID()}.zip`);
29
+ const output = createWriteStream(zipPath);
30
+ const outputClosed = once(output, "close");
31
+ const outputErrored = once(output, "error").then(([err]) => Promise.reject(err));
32
+ const zip = new Zip((err, chunk, final) => {
33
+ if (err) {
34
+ output.destroy(err);
35
+ return;
36
+ }
37
+ if (chunk?.length) {
38
+ output.write(Buffer.from(chunk));
39
+ }
40
+ if (final) {
41
+ output.end();
42
+ }
43
+ });
44
+ const walk = async (currentDir) => {
45
+ const entries = await readdir(currentDir, { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ const fullPath = join(currentDir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ await walk(fullPath);
50
+ continue;
51
+ }
52
+ if (!entry.isFile()) {
53
+ continue;
54
+ }
55
+ const relPath = relative(dirPath, fullPath).split("\\").join("/");
56
+ if (!relPath || relPath.startsWith("../") || relPath.includes("/../")) {
57
+ throw new Error(`Unsafe relative path while zipping: ${relPath}`);
58
+ }
59
+ const file = new ZipDeflate(relPath);
60
+ zip.add(file);
61
+ file.push(new Uint8Array(await readFile(fullPath)), true);
62
+ }
63
+ };
64
+ try {
65
+ await walk(dirPath);
66
+ zip.end();
67
+ await Promise.race([outputClosed, outputErrored]);
68
+ }
69
+ catch (err) {
70
+ zip.terminate();
71
+ output.destroy(err);
72
+ await cleanupUploadTempPath(zipPath);
73
+ throw err;
74
+ }
75
+ return zipPath;
76
+ }
77
+ async function prepareLocalUploadSource(path, includeSourceName = false) {
78
+ const localStats = await stat(path);
79
+ if (localStats.isDirectory()) {
80
+ const uploadPath = await zipDirectoryForUpload(path);
81
+ return {
82
+ kind: "upload",
83
+ uploadPath,
84
+ cleanupPath: uploadPath,
85
+ ...(includeSourceName ? { sourceName: basename(path) } : {}),
86
+ };
87
+ }
88
+ if (!localStats.isFile()) {
89
+ throw new Error(`Path is not a file or directory: ${path}`);
90
+ }
91
+ return { kind: "upload", uploadPath: path };
92
+ }
93
+ export const defaultResourcePackager = {
94
+ async prepareResourceSource(pathOrUrl) {
95
+ if (isRemoteResourceSource(pathOrUrl)) {
96
+ return { kind: "remote", path: pathOrUrl };
97
+ }
98
+ return prepareLocalUploadSource(pathOrUrl, true);
99
+ },
100
+ async prepareLocalUploadSource(path) {
101
+ return prepareLocalUploadSource(path, false);
102
+ },
103
+ async createTempUploadBody(uploadPath) {
104
+ const fileBytes = await readFile(uploadPath);
105
+ const form = new FormData();
106
+ form.append("file", new Blob([toBlobPart(fileBytes)], { type: "application/octet-stream" }), basename(uploadPath));
107
+ return form;
108
+ },
109
+ async cleanup(source) {
110
+ if (source?.kind === "upload") {
111
+ await cleanupUploadTempPath(source.cleanupPath);
112
+ }
113
+ },
114
+ };
@@ -1,9 +1,18 @@
1
1
  import { pickMemoriesForInjection, postProcessMemories, summarizeInjectionMemories, toJsonLog, } from "./memory-ranking.js";
2
2
  import { quickRecallPrecheck, withTimeout } from "./process-manager.js";
3
+ import { resolveRecallSearchPlan, } from "./registries/recall-resource-types.js";
3
4
  import { sanitizeUserTextForCapture } from "./text-utils.js";
5
+ import { estimateTextTokens } from "./token-estimator.js";
4
6
  const AUTO_RECALL_TIMEOUT_MS = 5_000;
5
7
  const RECALL_QUERY_MAX_CHARS = 4_000;
6
8
  export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
9
+ const WRITE_OR_EFFECT_RE = /\b(write|edit|modify|delete|remove|migrate|deploy|release|publish|configure|patch)\b|写|改|修改|删除|迁移|部署|发布|配置|打补丁/i;
10
+ const EXECUTION_RE = /\b(fix|debug|test|build|run|implement|refactor|integrate|repair|troubleshoot)\b|修复|调试|测试|构建|运行|实现|重构|对接|排查/i;
11
+ const FAILURE_RE = /\b(error|exception|traceback|failed|failure|retry|exit code|test failed)\b|报错|异常|失败|重试|挂了|不通过/i;
12
+ const ENGINEERING_OBJECT_RE = /(?:^|\s)(?:[\w.-]+\/[\w./-]+|[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|md|json|ya?ml|toml|sh|sql))\b|`[^`]+`|\b(?:repo|workspace|plugin|service|component|hook|api|tool|package|module)\b|仓库|工作区|插件|服务|组件|接口|工具|模块|文件/i;
13
+ const EXPERIENCE_INTENT_RE = /经验|踩坑|最佳实践|不要再|按之前|avoid|best practice|lesson|pitfall/i;
14
+ const QUESTION_ONLY_RE = /^(?:什么是|是什么|区别|解释|讲讲|怎么看|为什么|如何理解|where is|what is|explain|difference between)\b|[??]$/i;
15
+ const CASUAL_RE = /闲聊|翻译|总结当前对话|天气|笑话|hello|hi\b|你好/i;
7
16
  export function prepareRecallQuery(rawText) {
8
17
  const sanitized = sanitizeUserTextForCapture(rawText).trim();
9
18
  const originalChars = sanitized.length;
@@ -25,11 +34,29 @@ export function prepareRecallQuery(rawText) {
25
34
  finalChars: query.length,
26
35
  };
27
36
  }
28
- /** Estimate token count using chars/4 heuristic for diagnostics. */
37
+ /** Estimate token count using the shared CJK-aware fallback for diagnostics. */
29
38
  export function estimateTokenCount(text) {
30
- if (!text)
31
- return 0;
32
- return Math.ceil(text.length / 4);
39
+ return estimateTextTokens(text);
40
+ }
41
+ function memoryCategory(item) {
42
+ return item.category?.trim() || "memory";
43
+ }
44
+ function indentContent(content) {
45
+ return content
46
+ .split("\n")
47
+ .map((line) => ` ${line}`)
48
+ .join("\n");
49
+ }
50
+ function formatMemoryLine(item, content, options) {
51
+ const category = memoryCategory(item);
52
+ if (!options.includeUri) {
53
+ return `- [${category}] ${content}`;
54
+ }
55
+ return [
56
+ `- [${category}]`,
57
+ ` <uri>${item.uri}</uri>`,
58
+ indentContent(content),
59
+ ].join("\n");
33
60
  }
34
61
  async function resolveMemoryContent(item, readFn, options) {
35
62
  let content;
@@ -57,7 +84,7 @@ export async function buildMemoryLines(memories, readFn, options) {
57
84
  const lines = [];
58
85
  for (const item of memories) {
59
86
  const content = await resolveMemoryContent(item, readFn, options);
60
- lines.push(`- [${item.category ?? "memory"}] ${content}`);
87
+ lines.push(formatMemoryLine(item, content, options));
61
88
  }
62
89
  return lines;
63
90
  }
@@ -78,7 +105,7 @@ export async function buildMemoryLinesWithBudget(memories, readFn, options) {
78
105
  break;
79
106
  }
80
107
  const content = await resolveMemoryContent(item, readFn, options);
81
- const line = `- [${item.category ?? "memory"}] ${content}`;
108
+ const line = formatMemoryLine(item, content, options);
82
109
  const separatorChars = lines.length > 0 ? 1 : 0;
83
110
  const projectedChars = totalChars + separatorChars + line.length;
84
111
  if (projectedChars > charBudget) {
@@ -100,68 +127,237 @@ export function buildRecallContextBlock(memoryLines) {
100
127
  "</relevant-memories>",
101
128
  ].join("\n");
102
129
  }
130
+ function newTraceId() {
131
+ return `recall_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
132
+ }
133
+ function preview(value, maxChars) {
134
+ const trimmed = value?.trim();
135
+ if (!trimmed)
136
+ return undefined;
137
+ return trimmed.length > maxChars ? trimmed.slice(0, maxChars) : trimmed;
138
+ }
139
+ function toTraceResults(items, resourceType) {
140
+ return items.map((item) => ({
141
+ uri: item.uri,
142
+ resourceType,
143
+ category: item.category,
144
+ score: item.score,
145
+ level: item.level,
146
+ abstractPreview: preview(item.abstract ?? item.overview, 240),
147
+ resultType: resourceType === "resource" ? "resource" : "memory",
148
+ }));
149
+ }
150
+ function boundTraceQuery(query, maxChars) {
151
+ if (query.length <= maxChars) {
152
+ return { query };
153
+ }
154
+ return { query: query.slice(0, maxChars), queryTruncated: true };
155
+ }
156
+ function runtimeFlag(runtimeContext, key) {
157
+ return runtimeContext && typeof runtimeContext === "object"
158
+ ? runtimeContext[key]
159
+ : undefined;
160
+ }
161
+ export function isCronSession(sessionKey, runtimeContext) {
162
+ return Boolean(sessionKey?.includes(":cron:") ||
163
+ runtimeFlag(runtimeContext, "isCron") === true ||
164
+ runtimeFlag(runtimeContext, "automationKind") === "cron");
165
+ }
166
+ export function shouldRecallAgentExperience(input) {
167
+ const text = sanitizeUserTextForCapture(input.latestUserText).trim();
168
+ const minQueryChars = input.minQueryChars ?? 12;
169
+ if (input.isBypassed) {
170
+ return { recall: false, score: 0, reason: "session_bypassed" };
171
+ }
172
+ if (!text || text.length < minQueryChars) {
173
+ return { recall: false, score: 0, reason: "query_too_short" };
174
+ }
175
+ if (/<openviking-context\b/i.test(input.latestUserText)) {
176
+ return { recall: false, score: 0, reason: "already_injected" };
177
+ }
178
+ const trigger = input.triggerHint ?? (isCronSession(input.sessionKey, input.runtimeContext) ? "cron_start" : "task_start");
179
+ if (trigger !== "task_start") {
180
+ return { recall: true, trigger, score: 99, reason: "forced_trigger" };
181
+ }
182
+ let score = 0;
183
+ if (WRITE_OR_EFFECT_RE.test(text))
184
+ score += 3;
185
+ if (EXECUTION_RE.test(text))
186
+ score += 2;
187
+ if (FAILURE_RE.test(text))
188
+ score += 2;
189
+ if (ENGINEERING_OBJECT_RE.test(text))
190
+ score += 2;
191
+ if (EXPERIENCE_INTENT_RE.test(text))
192
+ score += 1;
193
+ if (CASUAL_RE.test(text))
194
+ score -= 3;
195
+ if (QUESTION_ONLY_RE.test(text) && !ENGINEERING_OBJECT_RE.test(text) && !EXECUTION_RE.test(text)) {
196
+ score -= 2;
197
+ }
198
+ if (score >= 3) {
199
+ return { recall: true, trigger: "task_start", score, reason: "task_execution" };
200
+ }
201
+ return { recall: false, score, reason: score < 0 ? "non_execution" : "below_threshold" };
202
+ }
103
203
  export async function buildAutoRecallContext(params) {
104
204
  const { cfg, client, agentId, queryText, logger, verbose } = params;
205
+ const queryConfig = params.queryConfig;
105
206
  if (!cfg.autoRecall || queryText.length < 5) {
106
207
  return { memoryCount: 0, estimatedTokens: 0 };
107
208
  }
108
- const precheck = await quickRecallPrecheck(cfg.baseUrl);
209
+ const precheck = await quickRecallPrecheck(client, agentId);
109
210
  if (!precheck.ok) {
110
211
  verbose?.(`openviking: skipping auto-recall because precheck failed (${precheck.reason})`);
111
212
  return { memoryCount: 0, estimatedTokens: 0 };
112
213
  }
113
214
  return withTimeout((async () => {
114
- const candidateLimit = Math.max(cfg.recallLimit * 4, 20);
115
- const autoRecallPromises = [
116
- client.find(queryText, {
117
- targetUri: "viking://user/memories",
118
- limit: candidateLimit,
119
- scoreThreshold: 0,
120
- }, agentId),
121
- client.find(queryText, {
122
- targetUri: "viking://agent/memories",
123
- limit: candidateLimit,
124
- scoreThreshold: 0,
125
- }, agentId),
126
- ];
127
- if (cfg.recallResources) {
128
- autoRecallPromises.push(client.find(queryText, {
129
- targetUri: "viking://resources",
215
+ const candidateLimit = queryConfig?.candidateLimit ?? Math.max(cfg.recallLimit * 4, 20);
216
+ const scoreThreshold = queryConfig?.scoreThreshold ?? cfg.recallScoreThreshold;
217
+ const recallLimit = queryConfig?.recallLimit ?? cfg.recallLimit;
218
+ const maxInjectedChars = queryConfig?.maxInjectedChars ?? cfg.recallMaxInjectedChars;
219
+ const recallPreferAbstract = queryConfig?.recallPreferAbstract ?? cfg.recallPreferAbstract;
220
+ const searchPlan = resolveRecallSearchPlan(params.resourceTypes ?? queryConfig?.resourceTypes ?? cfg.recallTargetTypes, {
221
+ ovSessionId: params.ovSessionId,
222
+ agentId,
223
+ });
224
+ const traceSearches = searchPlan.skipped.map((skipped) => ({
225
+ resourceType: skipped.resourceType,
226
+ limit: candidateLimit,
227
+ scoreThreshold: 0,
228
+ durationMs: 0,
229
+ total: 0,
230
+ results: [],
231
+ error: skipped.reason,
232
+ }));
233
+ const autoRecallPromises = searchPlan.searches.map(async (search) => {
234
+ const start = Date.now();
235
+ const result = await client.find(queryText, {
236
+ targetUri: search.targetUri,
130
237
  limit: candidateLimit,
131
238
  scoreThreshold: 0,
132
- }, agentId));
133
- }
239
+ contextType: search.contextType,
240
+ actorPeerId: agentId,
241
+ });
242
+ return {
243
+ resourceType: search.resourceType,
244
+ targetUri: search.targetUri,
245
+ result,
246
+ durationMs: Date.now() - start,
247
+ };
248
+ });
134
249
  const autoRecallSettled = await Promise.allSettled(autoRecallPromises);
135
250
  const allMemories = [];
136
251
  for (const s of autoRecallSettled) {
137
252
  if (s.status === "fulfilled") {
138
- allMemories.push(...(s.value.memories ?? []), ...(s.value.resources ?? []));
253
+ const result = s.value.result ?? {};
254
+ const memories = result.memories ?? [];
255
+ const resources = result.resources ?? [];
256
+ allMemories.push(...memories, ...resources);
257
+ traceSearches.push({
258
+ resourceType: s.value.resourceType,
259
+ targetUriInput: s.value.targetUri,
260
+ limit: candidateLimit,
261
+ scoreThreshold: 0,
262
+ durationMs: s.value.durationMs,
263
+ total: result.total ?? memories.length + resources.length + (result.skills?.length ?? 0),
264
+ results: [
265
+ ...toTraceResults(memories, s.value.resourceType),
266
+ ...toTraceResults(resources, "resource"),
267
+ ...(result.skills ?? []).map((item) => ({
268
+ uri: item.uri,
269
+ resourceType: s.value.resourceType,
270
+ category: item.category,
271
+ score: item.score,
272
+ level: item.level,
273
+ abstractPreview: preview(item.abstract ?? item.overview, 240),
274
+ resultType: "skill",
275
+ })),
276
+ ].slice(0, cfg.traceRecallMaxResultsPerSearch),
277
+ });
139
278
  }
140
279
  else {
141
280
  logger.warn?.(`openviking: auto-recall search failed: ${String(s.reason)}`);
281
+ const failedIndex = traceSearches.length;
282
+ const search = searchPlan.searches[failedIndex - searchPlan.skipped.length];
283
+ traceSearches.push({
284
+ resourceType: search?.resourceType ?? "user",
285
+ targetUriInput: search?.targetUri,
286
+ limit: candidateLimit,
287
+ scoreThreshold: 0,
288
+ durationMs: 0,
289
+ total: 0,
290
+ results: [],
291
+ error: String(s.reason),
292
+ });
142
293
  }
143
294
  }
144
295
  const uniqueMemories = allMemories.filter((memory, index, self) => index === self.findIndex((m) => m.uri === memory.uri));
145
296
  const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
146
297
  const processed = postProcessMemories(leafOnly, {
147
298
  limit: candidateLimit,
148
- scoreThreshold: cfg.recallScoreThreshold,
299
+ scoreThreshold,
149
300
  });
150
- const memories = pickMemoriesForInjection(processed, cfg.recallLimit, queryText);
301
+ const memories = pickMemoriesForInjection(processed, recallLimit, queryText, scoreThreshold, queryConfig ? {
302
+ weights: queryConfig.rankingWeights,
303
+ categoryWeights: queryConfig.categoryWeights,
304
+ resourceTypeWeights: queryConfig.resourceTypeWeights,
305
+ } : undefined);
306
+ const recordTrace = async (injectedMemories, injectedCount, estimatedTokens) => {
307
+ const entry = {
308
+ schemaVersion: "1.0",
309
+ traceId: newTraceId(),
310
+ ts: Date.now(),
311
+ sessionId: params.sessionId,
312
+ sessionKey: params.sessionKey,
313
+ ovSessionId: params.ovSessionId,
314
+ agentId,
315
+ source: "auto_recall",
316
+ operationType: "semantic_find",
317
+ resourceTypes: searchPlan.resourceTypes,
318
+ trigger: {
319
+ rawUserTextPreview: params.rawUserTextPreview,
320
+ ...boundTraceQuery(queryText, cfg.traceRecallQueryMaxChars),
321
+ derivedKeywords: [],
322
+ queryTruncated: params.queryTruncated || queryText.length > cfg.traceRecallQueryMaxChars,
323
+ },
324
+ searches: traceSearches,
325
+ selected: injectedMemories.map((memory) => ({
326
+ uri: memory.uri,
327
+ resourceType: memory.uri.startsWith("viking://resources") ? "resource" : undefined,
328
+ category: memory.category,
329
+ score: memory.score,
330
+ abstractPreview: preview(memory.abstract ?? memory.overview, cfg.traceRecallPreviewChars),
331
+ injected: true,
332
+ })),
333
+ stats: {
334
+ candidateCount: allMemories.length,
335
+ selectedCount: injectedMemories.length,
336
+ injectedCount,
337
+ estimatedTokens,
338
+ },
339
+ };
340
+ // Trace persistence is diagnostic best-effort; never put JSONL flush latency on the auto-recall critical path.
341
+ params.traceRecorder?.record(entry);
342
+ };
151
343
  if (memories.length === 0) {
344
+ await recordTrace([], 0, 0);
152
345
  return { memoryCount: 0, estimatedTokens: 0 };
153
346
  }
154
347
  const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(memories, (uri) => client.read(uri, agentId), {
155
- recallPreferAbstract: cfg.recallPreferAbstract,
156
- recallMaxInjectedChars: cfg.recallMaxInjectedChars,
348
+ recallPreferAbstract,
349
+ recallMaxInjectedChars: maxInjectedChars,
350
+ includeUri: true,
157
351
  });
158
352
  if (memoryLines.length === 0) {
159
- verbose?.(`openviking: skipping auto-recall injection; no complete memories fit maxInjectedChars=${cfg.recallMaxInjectedChars}`);
353
+ verbose?.(`openviking: skipping auto-recall injection; no complete memories fit maxInjectedChars=${maxInjectedChars}`);
354
+ await recordTrace([], 0, 0);
160
355
  return { memoryCount: 0, estimatedTokens: 0 };
161
356
  }
162
357
  const block = buildRecallContextBlock(memoryLines);
163
- verbose?.(`openviking: injecting ${memoryLines.length} memories (${block.length} chars, ~${estimatedTokens} tokens, maxInjectedChars=${cfg.recallMaxInjectedChars})`);
358
+ verbose?.(`openviking: injecting ${memoryLines.length} memories (${block.length} chars, ~${estimatedTokens} tokens, maxInjectedChars=${maxInjectedChars})`);
164
359
  verbose?.(`openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`);
360
+ await recordTrace(memories.slice(0, memoryLines.length), memoryLines.length, estimatedTokens);
165
361
  return { block, memoryCount: memoryLines.length, estimatedTokens };
166
362
  })(), AUTO_RECALL_TIMEOUT_MS, "openviking: auto-recall search timeout");
167
363
  }