@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 @@
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,10 +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";
4
5
  import { estimateTextTokens } from "./token-estimator.js";
5
6
  const AUTO_RECALL_TIMEOUT_MS = 5_000;
6
7
  const RECALL_QUERY_MAX_CHARS = 4_000;
7
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;
8
16
  export function prepareRecallQuery(rawText) {
9
17
  const sanitized = sanitizeUserTextForCapture(rawText).trim();
10
18
  const originalChars = sanitized.length;
@@ -119,8 +127,82 @@ export function buildRecallContextBlock(memoryLines) {
119
127
  "</relevant-memories>",
120
128
  ].join("\n");
121
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
+ }
122
203
  export async function buildAutoRecallContext(params) {
123
204
  const { cfg, client, agentId, queryText, logger, verbose } = params;
205
+ const queryConfig = params.queryConfig;
124
206
  if (!cfg.autoRecall || queryText.length < 5) {
125
207
  return { memoryCount: 0, estimatedTokens: 0 };
126
208
  }
@@ -130,58 +212,152 @@ export async function buildAutoRecallContext(params) {
130
212
  return { memoryCount: 0, estimatedTokens: 0 };
131
213
  }
132
214
  return withTimeout((async () => {
133
- const candidateLimit = Math.max(cfg.recallLimit * 4, 20);
134
- const autoRecallPromises = [
135
- client.find(queryText, {
136
- targetUri: "viking://user/memories",
137
- limit: candidateLimit,
138
- scoreThreshold: 0,
139
- }, agentId),
140
- client.find(queryText, {
141
- targetUri: "viking://agent/memories",
142
- limit: candidateLimit,
143
- scoreThreshold: 0,
144
- }, agentId),
145
- ];
146
- if (cfg.recallResources) {
147
- autoRecallPromises.push(client.find(queryText, {
148
- 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,
149
237
  limit: candidateLimit,
150
238
  scoreThreshold: 0,
151
- }, agentId));
152
- }
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
+ });
153
249
  const autoRecallSettled = await Promise.allSettled(autoRecallPromises);
154
250
  const allMemories = [];
155
251
  for (const s of autoRecallSettled) {
156
252
  if (s.status === "fulfilled") {
157
- 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
+ });
158
278
  }
159
279
  else {
160
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
+ });
161
293
  }
162
294
  }
163
295
  const uniqueMemories = allMemories.filter((memory, index, self) => index === self.findIndex((m) => m.uri === memory.uri));
164
296
  const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
165
297
  const processed = postProcessMemories(leafOnly, {
166
298
  limit: candidateLimit,
167
- scoreThreshold: cfg.recallScoreThreshold,
299
+ scoreThreshold,
168
300
  });
169
- 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
+ };
170
343
  if (memories.length === 0) {
344
+ await recordTrace([], 0, 0);
171
345
  return { memoryCount: 0, estimatedTokens: 0 };
172
346
  }
173
347
  const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(memories, (uri) => client.read(uri, agentId), {
174
- recallPreferAbstract: cfg.recallPreferAbstract,
175
- recallMaxInjectedChars: cfg.recallMaxInjectedChars,
348
+ recallPreferAbstract,
349
+ recallMaxInjectedChars: maxInjectedChars,
176
350
  includeUri: true,
177
351
  });
178
352
  if (memoryLines.length === 0) {
179
- 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);
180
355
  return { memoryCount: 0, estimatedTokens: 0 };
181
356
  }
182
357
  const block = buildRecallContextBlock(memoryLines);
183
- 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})`);
184
359
  verbose?.(`openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`);
360
+ await recordTrace(memories.slice(0, memoryLines.length), memoryLines.length, estimatedTokens);
185
361
  return { block, memoryCount: memoryLines.length, estimatedTokens };
186
362
  })(), AUTO_RECALL_TIMEOUT_MS, "openviking: auto-recall search timeout");
187
363
  }