@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
package/config.ts CHANGED
@@ -1,476 +1,819 @@
1
- import { getEnv } from "./runtime-utils.js";
2
-
3
- export type MemoryOpenVikingConfig = {
4
- mode?: "remote";
5
- baseUrl?: string;
6
- agent_prefix?: string;
7
- apiKey?: string;
8
- /** Advanced option. Only needed when explicitly sending tenant identity headers. With a user key the server derives identity from the key. */
9
- accountId?: string;
10
- /** Advanced option. Only needed when explicitly sending tenant identity headers. */
11
- userId?: string;
12
- /**
13
- * Canonical namespace policy. Must match the server-side account namespace
14
- * policy because current /system/status does not expose it.
15
- */
16
- isolateUserScopeByAgent?: boolean;
17
- isolateAgentScopeByUser?: boolean;
18
- /**
19
- * Deprecated compatibility alias for older hash-based agent space behavior.
20
- * Prefer isolateUserScopeByAgent / isolateAgentScopeByUser.
21
- */
22
- agentScopeMode?: "user_agent" | "agent";
23
- targetUri?: string;
24
- timeoutMs?: number;
25
- autoCapture?: boolean;
26
- captureMode?: "semantic" | "keyword";
27
- captureMaxLength?: number;
28
- autoRecall?: boolean;
29
- /** Include resources in auto-recall and default memory_recall search. Default false. */
30
- recallResources?: boolean;
31
- recallLimit?: number;
32
- recallScoreThreshold?: number;
33
- /** Maximum total characters injected by auto-recall. */
34
- recallMaxInjectedChars?: number;
35
- /** @deprecated Auto-recall no longer truncates individual memories. */
36
- recallMaxContentChars?: number;
37
- recallPreferAbstract?: boolean;
38
- /** @deprecated Use recallMaxInjectedChars. */
39
- recallTokenBudget?: number;
40
- commitTokenThreshold?: number;
41
- /**
42
- * WM v2: number of most-recent messages to keep live after an afterTurn
43
- * commit so the next turn still has immediate context. Forwarded to the
44
- * server as `keep_recent_count`. Default 10. The compact path ignores this
45
- * value and always passes 0.
46
- */
47
- commitKeepRecentCount?: number;
48
- bypassSessionPatterns?: string[];
49
- /**
50
- * When true (default), emit structured `openviking: diag {...}` lines (and any future
51
- * standard-diagnostics file writes) for assemble/afterTurn. Set false to disable.
52
- */
53
- emitStandardDiagnostics?: boolean;
54
- /** When true, log tenant routing for semantic find and session writes (messages/commit) to the plugin logger. */
55
- logFindRequests?: boolean;
56
- };
57
-
58
- const DEFAULT_BASE_URL = "http://127.0.0.1:1933";
59
- const DEFAULT_TARGET_URI = "viking://user/memories";
60
- const DEFAULT_TIMEOUT_MS = 15000;
61
- const DEFAULT_CAPTURE_MODE = "semantic";
62
- const DEFAULT_CAPTURE_MAX_LENGTH = 24000;
63
- const DEFAULT_RECALL_LIMIT = 6;
64
- const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
65
- const DEFAULT_RECALL_MAX_CONTENT_CHARS = 5000;
66
- const DEFAULT_RECALL_PREFER_ABSTRACT = false;
67
- const DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000;
68
- const DEFAULT_COMMIT_TOKEN_THRESHOLD = 20000;
69
- const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
70
- const DEFAULT_BYPASS_SESSION_PATTERNS: string[] = [];
71
- const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
72
- const DEFAULT_AGENT_PREFIX = "";
73
-
74
- function resolveAgentPrefix(configured: unknown): string {
75
- if (typeof configured === "string" && configured.trim()) {
76
- const trimmed = configured.trim();
77
- return trimmed === "default" ? DEFAULT_AGENT_PREFIX : trimmed;
78
- }
79
- return DEFAULT_AGENT_PREFIX;
80
- }
81
-
82
- function resolveEnvVars(value: string): string {
83
- return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
84
- const envValue = getEnv(envVar);
85
- if (!envValue) {
86
- throw new Error(`Environment variable ${envVar} is not set`);
87
- }
88
- return envValue;
89
- });
90
- }
91
-
92
- function toNumber(value: unknown, fallback: number): number {
93
- if (typeof value === "number" && Number.isFinite(value)) {
94
- return value;
95
- }
96
- if (typeof value === "string" && value.trim() !== "") {
97
- const parsed = Number(value);
98
- if (Number.isFinite(parsed)) {
99
- return parsed;
100
- }
101
- }
102
- return fallback;
103
- }
104
-
105
- function toStringArray(value: unknown, fallback: string[]): string[] {
106
- if (Array.isArray(value)) {
107
- return value
108
- .filter((entry): entry is string => typeof entry === "string")
109
- .map((entry) => entry.trim())
110
- .filter(Boolean);
111
- }
112
- if (typeof value === "string") {
113
- return value
114
- .split(/[,\n]/)
115
- .map((entry) => entry.trim())
116
- .filter(Boolean);
117
- }
118
- return fallback;
119
- }
120
-
121
- /** True when env is 1 / true / yes (case-insensitive). Used for debug flags without editing plugin JSON. */
122
- function envFlag(name: string): boolean {
123
- const v = getEnv(name);
124
- if (v == null || v === "") {
125
- return false;
126
- }
127
- const t = String(v).trim().toLowerCase();
128
- return t === "1" || t === "true" || t === "yes";
129
- }
130
-
131
- function assertAllowedKeys(value: Record<string, unknown>, allowed: string[], label: string) {
132
- const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
133
- if (unknown.length === 0) {
134
- return;
135
- }
136
- throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
137
- }
138
-
139
- function resolveDefaultBaseUrl(): string {
140
- const fromEnv = getEnv("OPENVIKING_BASE_URL") || getEnv("OPENVIKING_URL");
141
- if (fromEnv) {
142
- return fromEnv;
143
- }
144
- return DEFAULT_BASE_URL;
145
- }
146
-
147
- export const memoryOpenVikingConfigSchema = {
148
- parse(value: unknown): Required<MemoryOpenVikingConfig> {
149
- if (!value || typeof value !== "object" || Array.isArray(value)) {
150
- value = {};
151
- }
152
- const cfg = value as Record<string, unknown>;
153
- if ("agentId" in cfg) {
154
- if (!("agent_prefix" in cfg)) {
155
- cfg.agent_prefix = cfg.agentId;
156
- }
157
- delete cfg.agentId;
158
- }
159
- assertAllowedKeys(
160
- cfg,
161
- [
162
- "mode",
163
- "baseUrl",
164
- "agent_prefix",
165
- "agentId",
166
- "serverAuthMode",
167
- "apiKey",
168
- "accountId",
169
- "userId",
170
- "isolateUserScopeByAgent",
171
- "isolateAgentScopeByUser",
172
- "agentScopeMode",
173
- "targetUri",
174
- "timeoutMs",
175
- "autoCapture",
176
- "captureMode",
177
- "captureMaxLength",
178
- "autoRecall",
179
- "recallResources",
180
- "recallLimit",
181
- "recallScoreThreshold",
182
- "recallMaxInjectedChars",
183
- "recallMaxContentChars",
184
- "recallPreferAbstract",
185
- "recallTokenBudget",
186
- "commitTokenThreshold",
187
- "commitKeepRecentCount",
188
- "bypassSessionPatterns",
189
- "ingestReplyAssist",
190
- "ingestReplyAssistMinSpeakerTurns",
191
- "ingestReplyAssistMinChars",
192
- "ingestReplyAssistIgnoreSessionPatterns",
193
- "emitStandardDiagnostics",
194
- "logFindRequests",
195
- ],
196
- "openviking config",
197
- );
198
-
199
- const mode = "remote" as const;
200
- const rawBaseUrl = typeof cfg.baseUrl === "string" ? cfg.baseUrl : resolveDefaultBaseUrl();
201
- const resolvedBaseUrl = resolveEnvVars(rawBaseUrl).replace(/\/+$/, "");
202
- const rawApiKey = typeof cfg.apiKey === "string" ? cfg.apiKey : getEnv("OPENVIKING_API_KEY");
203
- const captureMode = cfg.captureMode;
204
- if (
205
- typeof captureMode !== "undefined" &&
206
- captureMode !== "semantic" &&
207
- captureMode !== "keyword"
208
- ) {
209
- throw new Error(`openviking captureMode must be "semantic" or "keyword"`);
210
- }
211
-
212
- const accountId =
213
- typeof cfg.accountId === "string" && cfg.accountId.trim()
214
- ? cfg.accountId.trim()
215
- : (getEnv("OPENVIKING_ACCOUNT_ID")?.trim() || "");
216
- const userId =
217
- typeof cfg.userId === "string" && cfg.userId.trim()
218
- ? cfg.userId.trim()
219
- : (getEnv("OPENVIKING_USER_ID")?.trim() || "");
220
-
221
- const hasExplicitAgentScopeMode =
222
- typeof cfg.agentScopeMode === "string" || getEnv("OPENVIKING_AGENT_SCOPE_MODE") !== undefined;
223
- const rawAgentScope = cfg.agentScopeMode ?? getEnv("OPENVIKING_AGENT_SCOPE_MODE");
224
- const agentScopeMode =
225
- rawAgentScope === "user_agent" ? "user_agent" as const : "agent" as const;
226
- const explicitIsolateUserScopeByAgent =
227
- typeof cfg.isolateUserScopeByAgent === "boolean"
228
- ? cfg.isolateUserScopeByAgent
229
- : undefined;
230
- const explicitIsolateAgentScopeByUser =
231
- typeof cfg.isolateAgentScopeByUser === "boolean"
232
- ? cfg.isolateAgentScopeByUser
233
- : undefined;
234
- const envIsolateUserScopeByAgent =
235
- explicitIsolateUserScopeByAgent === undefined &&
236
- getEnv("OPENVIKING_ISOLATE_USER_SCOPE_BY_AGENT") !== undefined
237
- ? envFlag("OPENVIKING_ISOLATE_USER_SCOPE_BY_AGENT")
238
- : undefined;
239
- const envIsolateAgentScopeByUser =
240
- explicitIsolateAgentScopeByUser === undefined &&
241
- getEnv("OPENVIKING_ISOLATE_AGENT_SCOPE_BY_USER") !== undefined
242
- ? envFlag("OPENVIKING_ISOLATE_AGENT_SCOPE_BY_USER")
243
- : undefined;
244
- const isolateUserScopeByAgent =
245
- explicitIsolateUserScopeByAgent ??
246
- envIsolateUserScopeByAgent ??
247
- false;
248
- const isolateAgentScopeByUser =
249
- explicitIsolateAgentScopeByUser ??
250
- envIsolateAgentScopeByUser ??
251
- (hasExplicitAgentScopeMode && agentScopeMode === "user_agent" ? true : false);
252
- const recallMaxInjectedChars = Math.max(
253
- 100,
254
- Math.min(
255
- 50000,
256
- Math.floor(
257
- toNumber(
258
- cfg.recallMaxInjectedChars,
259
- toNumber(cfg.recallTokenBudget, DEFAULT_RECALL_MAX_INJECTED_CHARS),
260
- ),
261
- ),
262
- ),
263
- );
264
-
265
- return {
266
- mode,
267
- baseUrl: resolvedBaseUrl,
268
- agent_prefix: resolveAgentPrefix(cfg.agent_prefix),
269
- apiKey: rawApiKey ? resolveEnvVars(rawApiKey) : "",
270
- accountId,
271
- userId,
272
- isolateUserScopeByAgent,
273
- isolateAgentScopeByUser,
274
- agentScopeMode,
275
- targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
276
- timeoutMs: Math.max(1000, Math.floor(toNumber(cfg.timeoutMs, DEFAULT_TIMEOUT_MS))),
277
- autoCapture: cfg.autoCapture !== false,
278
- captureMode: captureMode ?? DEFAULT_CAPTURE_MODE,
279
- captureMaxLength: Math.max(
280
- 200,
281
- Math.min(200_000, Math.floor(toNumber(cfg.captureMaxLength, DEFAULT_CAPTURE_MAX_LENGTH))),
282
- ),
283
- autoRecall: cfg.autoRecall !== false,
284
- recallResources: cfg.recallResources === true || envFlag("OPENVIKING_RECALL_RESOURCES"),
285
- recallLimit: Math.max(1, Math.floor(toNumber(cfg.recallLimit, DEFAULT_RECALL_LIMIT))),
286
- recallScoreThreshold: Math.min(
287
- 1,
288
- Math.max(0, toNumber(cfg.recallScoreThreshold, DEFAULT_RECALL_SCORE_THRESHOLD)),
289
- ),
290
- recallMaxContentChars: Math.max(
291
- 50,
292
- Math.min(10000, Math.floor(toNumber(cfg.recallMaxContentChars, DEFAULT_RECALL_MAX_CONTENT_CHARS))),
293
- ),
294
- recallPreferAbstract:
295
- typeof cfg.recallPreferAbstract === "boolean"
296
- ? cfg.recallPreferAbstract
297
- : DEFAULT_RECALL_PREFER_ABSTRACT,
298
- recallMaxInjectedChars,
299
- recallTokenBudget: recallMaxInjectedChars,
300
- commitTokenThreshold: Math.max(
301
- 0,
302
- Math.min(100_000, Math.floor(toNumber(cfg.commitTokenThreshold, DEFAULT_COMMIT_TOKEN_THRESHOLD))),
303
- ),
304
- commitKeepRecentCount: Math.max(
305
- 0,
306
- Math.min(
307
- 1_000,
308
- Math.floor(toNumber(cfg.commitKeepRecentCount, DEFAULT_COMMIT_KEEP_RECENT_COUNT)),
309
- ),
310
- ),
311
- bypassSessionPatterns: toStringArray(
312
- cfg.bypassSessionPatterns,
313
- toStringArray(
314
- cfg.ingestReplyAssistIgnoreSessionPatterns,
315
- DEFAULT_BYPASS_SESSION_PATTERNS,
316
- ),
317
- ),
318
- emitStandardDiagnostics:
319
- typeof cfg.emitStandardDiagnostics === "boolean"
320
- ? cfg.emitStandardDiagnostics
321
- : DEFAULT_EMIT_STANDARD_DIAGNOSTICS,
322
- logFindRequests:
323
- cfg.logFindRequests === true ||
324
- envFlag("OPENVIKING_LOG_ROUTING") ||
325
- envFlag("OPENVIKING_DEBUG"),
326
- };
327
- },
328
- uiHints: {
329
- baseUrl: {
330
- label: "OpenViking Base URL",
331
- placeholder: DEFAULT_BASE_URL,
332
- help: "HTTP URL when mode is remote (or use ${OPENVIKING_BASE_URL})",
333
- },
334
- agent_prefix: {
335
- label: "Agent Prefix",
336
- placeholder: "optional-prefix",
337
- help: 'Optional prefix for OpenViking X-OpenViking-Agent. Empty means use OpenClaw ctx.agentId directly. Non-empty values are prepended as "<prefix>_<ctx.agentId>" (sanitized to [a-zA-Z0-9_-]). If ctx.agentId is unavailable, OpenClaw default agent "main" is used.',
338
- },
339
- apiKey: {
340
- label: "OpenViking API Key",
341
- sensitive: true,
342
- placeholder: "${OPENVIKING_API_KEY}",
343
- help: "Optional API key for OpenViking server",
344
- },
345
- accountId: {
346
- label: "Account ID",
347
- placeholder: "(derived from API key)",
348
- help: "Advanced option. Tenant account ID. Only needed when explicitly sending identity headers, such as root-key or trusted deployments. With a user key the server derives identity from the key.",
349
- advanced: true,
350
- },
351
- userId: {
352
- label: "User ID",
353
- placeholder: "(derived from API key)",
354
- help: "Advanced option. Tenant user ID. Only needed when explicitly sending identity headers.",
355
- advanced: true,
356
- },
357
- isolateUserScopeByAgent: {
358
- label: "Isolate User Scope By Agent",
359
- placeholder: "false",
360
- help: "Canonical namespace policy. false (default): user alias expands to viking://user/<user_id>/... . true: expands to viking://user/<user_id>/agent/<agent_id>/... . Must match the server-side account namespace policy.",
361
- advanced: true,
362
- },
363
- isolateAgentScopeByUser: {
364
- label: "Isolate Agent Scope By User",
365
- placeholder: "false",
366
- help: "Canonical namespace policy. false (default): agent alias expands to viking://agent/<agent_id>/... . true: expands to viking://agent/<agent_id>/user/<user_id>/... . Must match the server-side account namespace policy.",
367
- advanced: true,
368
- },
369
- agentScopeMode: {
370
- label: "Deprecated Agent Scope Mode",
371
- placeholder: "agent",
372
- help: 'Deprecated compatibility alias for older routing behavior. Prefer isolateUserScopeByAgent / isolateAgentScopeByUser. Mapping: explicit "user_agent" => false/true, explicit "agent" => false/false. When fully unset, the plugin defaults to false/false to match the current server-side default policy.',
373
- advanced: true,
374
- },
375
- targetUri: {
376
- label: "Search Target URI",
377
- placeholder: DEFAULT_TARGET_URI,
378
- help: "Default OpenViking target URI for memory search",
379
- },
380
- timeoutMs: {
381
- label: "Request Timeout (ms)",
382
- placeholder: String(DEFAULT_TIMEOUT_MS),
383
- advanced: true,
384
- },
385
- autoCapture: {
386
- label: "Auto-Capture",
387
- help: "Extract memories from recent conversation messages via OpenViking sessions",
388
- },
389
- captureMode: {
390
- label: "Capture Mode",
391
- placeholder: DEFAULT_CAPTURE_MODE,
392
- advanced: true,
393
- help: '"semantic" captures all eligible user text and relies on OpenViking extraction; "keyword" uses trigger regex first.',
394
- },
395
- captureMaxLength: {
396
- label: "Capture Max Length",
397
- placeholder: String(DEFAULT_CAPTURE_MAX_LENGTH),
398
- advanced: true,
399
- help: "Maximum sanitized user text length allowed for auto-capture.",
400
- },
401
- autoRecall: {
402
- label: "Auto-Recall",
403
- help: "Inject relevant OpenViking memories into agent context",
404
- },
405
- recallResources: {
406
- label: "Recall Resources",
407
- help: "Include resources (viking://resources) in auto-recall and default memory_recall search. Enables account-level shared knowledge retrieval.",
408
- advanced: true,
409
- },
410
- recallLimit: {
411
- label: "Recall Limit",
412
- placeholder: String(DEFAULT_RECALL_LIMIT),
413
- advanced: true,
414
- },
415
- recallScoreThreshold: {
416
- label: "Recall Score Threshold",
417
- placeholder: String(DEFAULT_RECALL_SCORE_THRESHOLD),
418
- advanced: true,
419
- },
420
- recallMaxInjectedChars: {
421
- label: "Recall Max Injected Chars",
422
- placeholder: String(DEFAULT_RECALL_MAX_INJECTED_CHARS),
423
- advanced: true,
424
- help: "Maximum total characters for auto-recall memory injection. Complete memories that do not fit are skipped, not truncated.",
425
- },
426
- recallMaxContentChars: {
427
- label: "Deprecated Recall Max Content Chars",
428
- placeholder: String(DEFAULT_RECALL_MAX_CONTENT_CHARS),
429
- advanced: true,
430
- help: "Deprecated compatibility option and will be removed in a future release. Auto-recall now keeps individual memories intact and uses recallMaxInjectedChars.",
431
- },
432
- recallPreferAbstract: {
433
- label: "Recall Prefer Abstract",
434
- advanced: true,
435
- help: "Use memory abstract instead of fetching full content when abstract is available. Reduces token usage.",
436
- },
437
- recallTokenBudget: {
438
- label: "Deprecated Recall Token Budget",
439
- placeholder: String(DEFAULT_RECALL_MAX_INJECTED_CHARS),
440
- advanced: true,
441
- help: "Deprecated compatibility alias and will be removed in a future release. Use recallMaxInjectedChars.",
442
- },
443
- bypassSessionPatterns: {
444
- label: "Bypass Session Patterns",
445
- placeholder: "agent:*:cron:**",
446
- help: "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
447
- advanced: true,
448
- },
449
- commitTokenThreshold: {
450
- label: "Commit Token Threshold",
451
- placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD),
452
- advanced: true,
453
- help: "Minimum estimated pending tokens before auto-commit triggers. Set to 0 to commit every turn.",
454
- },
455
- commitKeepRecentCount: {
456
- label: "Commit Keep Recent Count",
457
- placeholder: String(DEFAULT_COMMIT_KEEP_RECENT_COUNT),
458
- advanced: true,
459
- help:
460
- "Number of most-recent messages to keep live after an afterTurn commit. " +
461
- "Forwarded as keep_recent_count to the server. Compact path always uses 0.",
462
- },
463
- emitStandardDiagnostics: {
464
- label: "Standard diagnostics (diag JSON lines)",
465
- advanced: true,
466
- help: "When enabled, emit structured openviking: diag {...} lines for assemble and afterTurn. Disable to reduce log noise.",
467
- },
468
- logFindRequests: {
469
- label: "Log find requests",
470
- help:
471
- "Log tenant routing: POST /api/v1/search/find (query, target_uri) and session POST .../messages + .../commit (sessionId, X-OpenViking-*). Never logs apiKey. " +
472
- "Or set env OPENVIKING_LOG_ROUTING=1 or OPENVIKING_DEBUG=1 (no JSON edit).",
473
- advanced: true,
474
- },
475
- },
476
- };
1
+ import { homedir } from "node:os";
2
+
3
+ import { getEnv } from "./runtime-utils.js";
4
+
5
+ export type MemoryOpenVikingConfig = {
6
+ mode?: "remote";
7
+ baseUrl?: string;
8
+ peer_role?: "none" | "assistant" | "person";
9
+ peer_prefix?: string;
10
+ apiKey?: string;
11
+ /** Advanced option. Only needed when explicitly sending tenant identity headers. With a user key the server derives identity from the key. */
12
+ accountId?: string;
13
+ /** Advanced option. Only needed when explicitly sending tenant identity headers. */
14
+ userId?: string;
15
+ targetUri?: string;
16
+ timeoutMs?: number;
17
+ autoCapture?: boolean;
18
+ captureMode?: "semantic" | "keyword";
19
+ captureMaxLength?: number;
20
+ autoRecall?: boolean;
21
+ /** Outer time budget for the whole auto-recall flow, including search, ranking, and reads. */
22
+ autoRecallTimeoutMs?: number;
23
+ /** Include resources in auto-recall and default memory_recall search. Default false. */
24
+ recallResources?: boolean;
25
+ recallLimit?: number;
26
+ recallScoreThreshold?: number;
27
+ /** Maximum total characters injected by auto-recall. */
28
+ recallMaxInjectedChars?: number;
29
+ /** @deprecated Auto-recall no longer truncates individual memories. */
30
+ recallMaxContentChars?: number;
31
+ recallPreferAbstract?: boolean;
32
+ /** @deprecated Use recallMaxInjectedChars. */
33
+ recallTokenBudget?: number;
34
+ commitTokenThreshold?: number;
35
+ /**
36
+ * WM v2: number of most-recent messages to keep live after an afterTurn
37
+ * commit so the next turn still has immediate context. Forwarded to the
38
+ * server as `keep_recent_count`. Default 10. The compact path ignores this
39
+ * value and always passes 0.
40
+ */
41
+ commitKeepRecentCount?: number;
42
+ bypassSessionPatterns?: string[];
43
+ /**
44
+ * When true (default), emit structured `openviking: diag {...}` lines (and any future
45
+ * standard-diagnostics file writes) for assemble/afterTurn. Set false to disable.
46
+ */
47
+ emitStandardDiagnostics?: boolean;
48
+ /** When true, log tenant routing for semantic find and session writes (messages/commit) to the plugin logger. */
49
+ logFindRequests?: boolean;
50
+ /** Enable recall trace recording. Default false. */
51
+ traceRecall?: boolean;
52
+ /** Persist recall traces to local JSONL files. Default false. */
53
+ traceRecallPersist?: boolean;
54
+ /** Directory for JSONL recall trace files. */
55
+ traceRecallDir?: string;
56
+ /** Number of days to retain persisted trace files. */
57
+ traceRecallRetentionDays?: number;
58
+ /** Number of recent persisted days to preload on startup. */
59
+ traceRecallLoadRecentDays?: number;
60
+ /** Maximum in-memory recall trace entries. */
61
+ traceRecallMaxEntries?: number;
62
+ /** Maximum candidate results stored per search in trace. */
63
+ traceRecallMaxResultsPerSearch?: number;
64
+ /** Preview character limit for persisted trace summaries. */
65
+ traceRecallPreviewChars?: number;
66
+ /** Maximum query characters preserved in trace. */
67
+ traceRecallQueryMaxChars?: number;
68
+ /** Maximum days to scan when querying persisted traces without explicit time bounds. */
69
+ traceRecallQueryMaxDays?: number;
70
+ /** Whether trace queries include full content by default. */
71
+ traceRecallIncludeContentByDefault?: boolean;
72
+ /** Whether raw user text preview may be persisted. Default false. */
73
+ traceRecallIncludeRawUserPreview?: boolean;
74
+ /** Auto-recall target resource types. Empty means the backward-compatible memory recall set. */
75
+ recallTargetTypes?: Array<"resource" | "user" | "agent"> | string;
76
+ /** Agent-visible add_resource tool is disabled by default; manual /add-resource remains available. */
77
+ enableAddResourceTool?: boolean;
78
+ /** Agent-visible tool allowlist. Supports exact tool names or groups such as "memory" and "resource_query". */
79
+ enabledTools?: string[] | string;
80
+ /** Agent-visible tool blocklist applied after enabledTools. Supports exact tool names or groups. */
81
+ disabledTools?: string[] | string;
82
+ /** Optional JSON file path for runtime query config overrides. Empty means in-memory only. */
83
+ runtimeQueryConfigPath?: string;
84
+ agentExperience?: {
85
+ enabled?: boolean;
86
+ recallLimit?: number;
87
+ scoreThreshold?: number;
88
+ maxInjectedChars?: number;
89
+ minQueryChars?: number;
90
+ };
91
+ };
92
+
93
+ /** Runtime config after memoryOpenVikingConfigSchema.parse() has applied defaults. */
94
+ export type ParsedMemoryOpenVikingConfig = Required<
95
+ Omit<MemoryOpenVikingConfig, "agentExperience" | "recallTargetTypes">
96
+ > & {
97
+ agentExperience: Required<NonNullable<MemoryOpenVikingConfig["agentExperience"]>>;
98
+ recallTargetTypes: Array<"resource" | "user" | "agent">;
99
+ };
100
+
101
+ const DEFAULT_BASE_URL = "http://127.0.0.1:1933";
102
+ const DEFAULT_TARGET_URI = "viking://user/memories";
103
+ const DEFAULT_TIMEOUT_MS = 15000;
104
+ const DEFAULT_CAPTURE_MODE = "semantic";
105
+ const DEFAULT_CAPTURE_MAX_LENGTH = 24000;
106
+ const DEFAULT_AUTO_RECALL_TIMEOUT_MS = 5000;
107
+ const DEFAULT_RECALL_LIMIT = 6;
108
+ const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
109
+ const DEFAULT_RECALL_MAX_CONTENT_CHARS = 5000;
110
+ const DEFAULT_RECALL_PREFER_ABSTRACT = false;
111
+ const DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000;
112
+ const DEFAULT_COMMIT_TOKEN_THRESHOLD = 20000;
113
+ const DEFAULT_COMMIT_KEEP_RECENT_COUNT = 10;
114
+ const DEFAULT_BYPASS_SESSION_PATTERNS: string[] = [];
115
+ const DEFAULT_EMIT_STANDARD_DIAGNOSTICS = false;
116
+ const DEFAULT_PEER_ROLE = "assistant" as const;
117
+ const DEFAULT_PEER_PREFIX = "";
118
+ const DEFAULT_TRACE_RECALL_DIR = "~/.openclaw/openviking/recall-traces";
119
+ const DEFAULT_TRACE_RECALL_RETENTION_DAYS = 14;
120
+ const DEFAULT_TRACE_RECALL_LOAD_RECENT_DAYS = 2;
121
+ const DEFAULT_TRACE_RECALL_MAX_ENTRIES = 1000;
122
+ const DEFAULT_TRACE_RECALL_MAX_RESULTS_PER_SEARCH = 20;
123
+ const DEFAULT_TRACE_RECALL_PREVIEW_CHARS = 240;
124
+ const DEFAULT_TRACE_RECALL_QUERY_MAX_CHARS = 4000;
125
+ const DEFAULT_TRACE_RECALL_QUERY_MAX_DAYS = 14;
126
+ const ALLOWED_RECALL_TARGET_TYPES = ["resource", "user", "agent"] as const;
127
+ const DEFAULT_RECALL_TARGET_TYPES = ["user", "agent"] as const;
128
+ type RecallTargetType = typeof ALLOWED_RECALL_TARGET_TYPES[number];
129
+ export const OPENVIKING_ADD_RESOURCE_TOOL_NAME = "add_resource" as const;
130
+ export const OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES = [
131
+ "add_skill",
132
+ "ov_search",
133
+ "ov_read",
134
+ "ov_multi_read",
135
+ "ov_list",
136
+ "memory_recall",
137
+ "ov_recall_trace",
138
+ "memory_store",
139
+ "memory_forget",
140
+ "ov_archive_search",
141
+ "ov_archive_expand",
142
+ "openviking_tool_result_read",
143
+ "openviking_tool_result_search",
144
+ "openviking_tool_result_list",
145
+ ] as const;
146
+ export const OPENVIKING_ALL_TOOL_NAMES = [
147
+ OPENVIKING_ADD_RESOURCE_TOOL_NAME,
148
+ ...OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES,
149
+ ] as const;
150
+ export type OpenVikingToolName = typeof OPENVIKING_ALL_TOOL_NAMES[number];
151
+ export const OPENVIKING_TOOL_GROUPS: Record<string, readonly OpenVikingToolName[]> = {
152
+ all: OPENVIKING_ALL_TOOL_NAMES,
153
+ default: OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES,
154
+ memory: ["memory_recall", "memory_store", "memory_forget"],
155
+ resource_query: ["ov_search", "ov_read", "ov_multi_read", "ov_list"],
156
+ import: ["add_resource", "add_skill"],
157
+ recall_trace: ["ov_recall_trace"],
158
+ archive: ["ov_archive_search", "ov_archive_expand"],
159
+ tool_result: [
160
+ "openviking_tool_result_read",
161
+ "openviking_tool_result_search",
162
+ "openviking_tool_result_list",
163
+ ],
164
+ };
165
+ const DEFAULT_AGENT_EXPERIENCE = {
166
+ enabled: false,
167
+ recallLimit: 3,
168
+ scoreThreshold: 0.35,
169
+ maxInjectedChars: 6000,
170
+ minQueryChars: 12,
171
+ };
172
+
173
+ function resolvePeerPrefix(configured: unknown): string {
174
+ if (typeof configured === "string" && configured.trim()) {
175
+ const trimmed = configured.trim();
176
+ return trimmed === "default" ? DEFAULT_PEER_PREFIX : trimmed;
177
+ }
178
+ return DEFAULT_PEER_PREFIX;
179
+ }
180
+
181
+ function resolvePeerRole(configured: unknown) {
182
+ if (typeof configured === "string") {
183
+ const role = configured.trim().toLowerCase();
184
+ if (role === "none" || role === "assistant" || role === "person") {
185
+ return role;
186
+ }
187
+ throw new Error(`openviking peer_role must be "none", "assistant", or "person"`);
188
+ }
189
+ if (configured !== undefined) {
190
+ throw new Error(`openviking peer_role must be "none", "assistant", or "person"`);
191
+ }
192
+ return DEFAULT_PEER_ROLE;
193
+ }
194
+
195
+ function resolveEnvVars(value: string): string {
196
+ return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
197
+ const envValue = getEnv(envVar);
198
+ if (!envValue) {
199
+ throw new Error(`Environment variable ${envVar} is not set`);
200
+ }
201
+ return envValue;
202
+ });
203
+ }
204
+
205
+ function expandHomeDir(value: string): string {
206
+ if (value === "~") {
207
+ return homedir();
208
+ }
209
+ if (value.startsWith("~/")) {
210
+ return `${homedir()}${value.slice(1)}`;
211
+ }
212
+ return value;
213
+ }
214
+
215
+ function toNumber(value: unknown, fallback: number): number {
216
+ if (typeof value === "number" && Number.isFinite(value)) {
217
+ return value;
218
+ }
219
+ if (typeof value === "string" && value.trim() !== "") {
220
+ const parsed = Number(value);
221
+ if (Number.isFinite(parsed)) {
222
+ return parsed;
223
+ }
224
+ }
225
+ return fallback;
226
+ }
227
+
228
+ function toStringArray(value: unknown, fallback: string[]): string[] {
229
+ if (Array.isArray(value)) {
230
+ return value
231
+ .filter((entry): entry is string => typeof entry === "string")
232
+ .map((entry) => entry.trim())
233
+ .filter(Boolean);
234
+ }
235
+ if (typeof value === "string") {
236
+ return value
237
+ .split(/[,\n]/)
238
+ .map((entry) => entry.trim())
239
+ .filter(Boolean);
240
+ }
241
+ return fallback;
242
+ }
243
+
244
+ function toIntegerInRange(value: unknown, fallback: number, min: number, max: number): number {
245
+ return Math.max(min, Math.min(max, Math.floor(toNumber(value, fallback))));
246
+ }
247
+
248
+ function normalizeRecallTargetTypes(value: unknown, includeResources = false): RecallTargetType[] {
249
+ const entries = toStringArray(value, [...DEFAULT_RECALL_TARGET_TYPES]);
250
+ const seen = new Set<RecallTargetType>();
251
+ const normalized: RecallTargetType[] = [];
252
+ const unknown: string[] = [];
253
+
254
+ for (const entry of entries) {
255
+ if ((ALLOWED_RECALL_TARGET_TYPES as readonly string[]).includes(entry)) {
256
+ const typed = entry as RecallTargetType;
257
+ if (!seen.has(typed)) {
258
+ seen.add(typed);
259
+ normalized.push(typed);
260
+ }
261
+ } else {
262
+ unknown.push(entry);
263
+ }
264
+ }
265
+
266
+ if (unknown.length > 0) {
267
+ throw new Error(`openviking recallTargetTypes contains unknown resource types: ${unknown.join(", ")}`);
268
+ }
269
+
270
+ const result = normalized.length > 0 ? normalized : [...DEFAULT_RECALL_TARGET_TYPES];
271
+ if (includeResources && !seen.has("resource")) {
272
+ result.push("resource");
273
+ }
274
+ return result;
275
+ }
276
+
277
+ function toRecord(value: unknown): Record<string, unknown> {
278
+ return value && typeof value === "object" && !Array.isArray(value)
279
+ ? value as Record<string, unknown>
280
+ : {};
281
+ }
282
+
283
+ function expandToolSelectors(value: unknown, fallback: string[], label: string): OpenVikingToolName[] {
284
+ const entries = toStringArray(value, fallback);
285
+ const seen = new Set<OpenVikingToolName>();
286
+ const normalized: OpenVikingToolName[] = [];
287
+ const unknown: string[] = [];
288
+
289
+ for (const rawEntry of entries) {
290
+ const entry = rawEntry.trim();
291
+ const group = OPENVIKING_TOOL_GROUPS[entry];
292
+ const tools = group ??
293
+ ((OPENVIKING_ALL_TOOL_NAMES as readonly string[]).includes(entry)
294
+ ? [entry as OpenVikingToolName]
295
+ : undefined);
296
+ if (!tools) {
297
+ unknown.push(entry);
298
+ continue;
299
+ }
300
+ for (const tool of tools) {
301
+ if (!seen.has(tool)) {
302
+ seen.add(tool);
303
+ normalized.push(tool);
304
+ }
305
+ }
306
+ }
307
+
308
+ if (unknown.length > 0) {
309
+ throw new Error(`openviking ${label} contains unknown tool selectors: ${unknown.join(", ")}`);
310
+ }
311
+ return normalized;
312
+ }
313
+
314
+ function normalizeEnabledTools(cfg: Record<string, unknown>): {
315
+ enabledTools: OpenVikingToolName[];
316
+ disabledTools: OpenVikingToolName[];
317
+ } {
318
+ const enableAddResourceTool = cfg.enableAddResourceTool === true;
319
+ const defaultTools = enableAddResourceTool
320
+ ? [OPENVIKING_ADD_RESOURCE_TOOL_NAME, ...OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES]
321
+ : [...OPENVIKING_DEFAULT_ENABLED_TOOL_NAMES];
322
+ const selected = expandToolSelectors(cfg.enabledTools, defaultTools, "enabledTools");
323
+ const disabled = expandToolSelectors(cfg.disabledTools, [], "disabledTools");
324
+ const disabledSet = new Set(disabled);
325
+ if (!enableAddResourceTool) {
326
+ disabledSet.add(OPENVIKING_ADD_RESOURCE_TOOL_NAME);
327
+ }
328
+ const enabledTools = selected.filter((tool) =>
329
+ !disabledSet.has(tool) &&
330
+ (tool !== OPENVIKING_ADD_RESOURCE_TOOL_NAME || enableAddResourceTool)
331
+ );
332
+
333
+ return {
334
+ enabledTools,
335
+ disabledTools: Array.from(disabledSet),
336
+ };
337
+ }
338
+
339
+ /** True when env is 1 / true / yes (case-insensitive). Used for debug flags without editing plugin JSON. */
340
+ function envFlag(name: string): boolean {
341
+ const v = getEnv(name);
342
+ if (v == null || v === "") {
343
+ return false;
344
+ }
345
+ const t = String(v).trim().toLowerCase();
346
+ return t === "1" || t === "true" || t === "yes";
347
+ }
348
+
349
+ function assertAllowedKeys(value: Record<string, unknown>, allowed: string[], label: string) {
350
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
351
+ if (unknown.length === 0) {
352
+ return;
353
+ }
354
+ throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
355
+ }
356
+
357
+ function resolveDefaultBaseUrl(): string {
358
+ const fromEnv = getEnv("OPENVIKING_BASE_URL") || getEnv("OPENVIKING_URL");
359
+ if (fromEnv) {
360
+ return fromEnv;
361
+ }
362
+ return DEFAULT_BASE_URL;
363
+ }
364
+
365
+ export const memoryOpenVikingConfigSchema = {
366
+ parse(value: unknown): ParsedMemoryOpenVikingConfig {
367
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
368
+ value = {};
369
+ }
370
+ const cfg = value as Record<string, unknown>;
371
+ assertAllowedKeys(
372
+ cfg,
373
+ [
374
+ "mode",
375
+ "baseUrl",
376
+ "peer_role",
377
+ "peer_prefix",
378
+ "serverAuthMode",
379
+ "apiKey",
380
+ "accountId",
381
+ "userId",
382
+ "targetUri",
383
+ "timeoutMs",
384
+ "autoCapture",
385
+ "captureMode",
386
+ "captureMaxLength",
387
+ "autoRecall",
388
+ "autoRecallTimeoutMs",
389
+ "recallResources",
390
+ "recallLimit",
391
+ "recallScoreThreshold",
392
+ "recallMaxInjectedChars",
393
+ "recallMaxContentChars",
394
+ "recallPreferAbstract",
395
+ "recallTokenBudget",
396
+ "commitTokenThreshold",
397
+ "commitKeepRecentCount",
398
+ "bypassSessionPatterns",
399
+ "ingestReplyAssist",
400
+ "ingestReplyAssistMinSpeakerTurns",
401
+ "ingestReplyAssistMinChars",
402
+ "ingestReplyAssistIgnoreSessionPatterns",
403
+ "emitStandardDiagnostics",
404
+ "logFindRequests",
405
+ "traceRecall",
406
+ "traceRecallPersist",
407
+ "traceRecallDir",
408
+ "traceRecallRetentionDays",
409
+ "traceRecallLoadRecentDays",
410
+ "traceRecallMaxEntries",
411
+ "traceRecallMaxResultsPerSearch",
412
+ "traceRecallPreviewChars",
413
+ "traceRecallQueryMaxChars",
414
+ "traceRecallQueryMaxDays",
415
+ "traceRecallIncludeContentByDefault",
416
+ "traceRecallIncludeRawUserPreview",
417
+ "recallTargetTypes",
418
+ "enableAddResourceTool",
419
+ "enabledTools",
420
+ "disabledTools",
421
+ "runtimeQueryConfigPath",
422
+ "agentExperience",
423
+ ],
424
+ "openviking config",
425
+ );
426
+ const agentExperienceRaw = toRecord(cfg.agentExperience);
427
+ assertAllowedKeys(
428
+ agentExperienceRaw,
429
+ ["enabled", "recallLimit", "scoreThreshold", "maxInjectedChars", "minQueryChars"],
430
+ "openviking config agentExperience",
431
+ );
432
+
433
+ const mode = "remote" as const;
434
+ const peerRole = resolvePeerRole(cfg.peer_role);
435
+ const peerPrefix = resolvePeerPrefix(cfg.peer_prefix);
436
+ const rawBaseUrl = typeof cfg.baseUrl === "string" ? cfg.baseUrl : resolveDefaultBaseUrl();
437
+ const resolvedBaseUrl = resolveEnvVars(rawBaseUrl).replace(/\/+$/, "");
438
+ const rawApiKey = typeof cfg.apiKey === "string" ? cfg.apiKey : getEnv("OPENVIKING_API_KEY");
439
+ const captureMode = cfg.captureMode;
440
+ if (
441
+ typeof captureMode !== "undefined" &&
442
+ captureMode !== "semantic" &&
443
+ captureMode !== "keyword"
444
+ ) {
445
+ throw new Error(`openviking captureMode must be "semantic" or "keyword"`);
446
+ }
447
+
448
+ const accountId =
449
+ typeof cfg.accountId === "string" && cfg.accountId.trim()
450
+ ? cfg.accountId.trim()
451
+ : (getEnv("OPENVIKING_ACCOUNT_ID")?.trim() || "");
452
+ const userId =
453
+ typeof cfg.userId === "string" && cfg.userId.trim()
454
+ ? cfg.userId.trim()
455
+ : (getEnv("OPENVIKING_USER_ID")?.trim() || "");
456
+
457
+ const recallMaxInjectedChars = Math.max(
458
+ 100,
459
+ Math.min(
460
+ 50000,
461
+ Math.floor(
462
+ toNumber(
463
+ cfg.recallMaxInjectedChars,
464
+ toNumber(cfg.recallTokenBudget, DEFAULT_RECALL_MAX_INJECTED_CHARS),
465
+ ),
466
+ ),
467
+ ),
468
+ );
469
+ const recallResources = cfg.recallResources === true || envFlag("OPENVIKING_RECALL_RESOURCES");
470
+ const recallTargetTypes = normalizeRecallTargetTypes(
471
+ cfg.recallTargetTypes,
472
+ !("recallTargetTypes" in cfg) && recallResources,
473
+ );
474
+ const { enabledTools, disabledTools } = normalizeEnabledTools(cfg);
475
+
476
+ return {
477
+ mode,
478
+ baseUrl: resolvedBaseUrl,
479
+ peer_role: peerRole,
480
+ peer_prefix: peerPrefix,
481
+ apiKey: rawApiKey ? resolveEnvVars(rawApiKey) : "",
482
+ accountId,
483
+ userId,
484
+ targetUri: typeof cfg.targetUri === "string" ? cfg.targetUri : DEFAULT_TARGET_URI,
485
+ timeoutMs: Math.max(1000, Math.floor(toNumber(cfg.timeoutMs, DEFAULT_TIMEOUT_MS))),
486
+ autoCapture: cfg.autoCapture !== false,
487
+ captureMode: captureMode ?? DEFAULT_CAPTURE_MODE,
488
+ captureMaxLength: Math.max(
489
+ 200,
490
+ Math.min(200_000, Math.floor(toNumber(cfg.captureMaxLength, DEFAULT_CAPTURE_MAX_LENGTH))),
491
+ ),
492
+ autoRecall: cfg.autoRecall !== false,
493
+ autoRecallTimeoutMs: Math.max(
494
+ 1000,
495
+ Math.min(300_000, Math.floor(toNumber(cfg.autoRecallTimeoutMs, DEFAULT_AUTO_RECALL_TIMEOUT_MS))),
496
+ ),
497
+ recallResources,
498
+ recallLimit: Math.max(1, Math.floor(toNumber(cfg.recallLimit, DEFAULT_RECALL_LIMIT))),
499
+ recallScoreThreshold: Math.min(
500
+ 1,
501
+ Math.max(0, toNumber(cfg.recallScoreThreshold, DEFAULT_RECALL_SCORE_THRESHOLD)),
502
+ ),
503
+ recallMaxContentChars: Math.max(
504
+ 50,
505
+ Math.min(10000, Math.floor(toNumber(cfg.recallMaxContentChars, DEFAULT_RECALL_MAX_CONTENT_CHARS))),
506
+ ),
507
+ recallPreferAbstract:
508
+ typeof cfg.recallPreferAbstract === "boolean"
509
+ ? cfg.recallPreferAbstract
510
+ : DEFAULT_RECALL_PREFER_ABSTRACT,
511
+ recallMaxInjectedChars,
512
+ recallTokenBudget: recallMaxInjectedChars,
513
+ commitTokenThreshold: Math.max(
514
+ 0,
515
+ Math.min(100_000, Math.floor(toNumber(cfg.commitTokenThreshold, DEFAULT_COMMIT_TOKEN_THRESHOLD))),
516
+ ),
517
+ commitKeepRecentCount: Math.max(
518
+ 0,
519
+ Math.min(
520
+ 1_000,
521
+ Math.floor(toNumber(cfg.commitKeepRecentCount, DEFAULT_COMMIT_KEEP_RECENT_COUNT)),
522
+ ),
523
+ ),
524
+ bypassSessionPatterns: toStringArray(
525
+ cfg.bypassSessionPatterns,
526
+ toStringArray(
527
+ cfg.ingestReplyAssistIgnoreSessionPatterns,
528
+ DEFAULT_BYPASS_SESSION_PATTERNS,
529
+ ),
530
+ ),
531
+ emitStandardDiagnostics:
532
+ typeof cfg.emitStandardDiagnostics === "boolean"
533
+ ? cfg.emitStandardDiagnostics
534
+ : DEFAULT_EMIT_STANDARD_DIAGNOSTICS,
535
+ logFindRequests:
536
+ cfg.logFindRequests === true ||
537
+ envFlag("OPENVIKING_LOG_ROUTING") ||
538
+ envFlag("OPENVIKING_DEBUG"),
539
+ traceRecall: cfg.traceRecall === true,
540
+ traceRecallPersist: cfg.traceRecallPersist === true,
541
+ traceRecallDir:
542
+ typeof cfg.traceRecallDir === "string" && cfg.traceRecallDir.trim()
543
+ ? expandHomeDir(cfg.traceRecallDir.trim())
544
+ : expandHomeDir(DEFAULT_TRACE_RECALL_DIR),
545
+ traceRecallRetentionDays: toIntegerInRange(
546
+ cfg.traceRecallRetentionDays,
547
+ DEFAULT_TRACE_RECALL_RETENTION_DAYS,
548
+ 1,
549
+ 3650,
550
+ ),
551
+ traceRecallLoadRecentDays: toIntegerInRange(
552
+ cfg.traceRecallLoadRecentDays,
553
+ DEFAULT_TRACE_RECALL_LOAD_RECENT_DAYS,
554
+ 0,
555
+ 3650,
556
+ ),
557
+ traceRecallMaxEntries: toIntegerInRange(
558
+ cfg.traceRecallMaxEntries,
559
+ DEFAULT_TRACE_RECALL_MAX_ENTRIES,
560
+ 1,
561
+ 1_000_000,
562
+ ),
563
+ traceRecallMaxResultsPerSearch: toIntegerInRange(
564
+ cfg.traceRecallMaxResultsPerSearch,
565
+ DEFAULT_TRACE_RECALL_MAX_RESULTS_PER_SEARCH,
566
+ 1,
567
+ 1_000,
568
+ ),
569
+ traceRecallPreviewChars: toIntegerInRange(
570
+ cfg.traceRecallPreviewChars,
571
+ DEFAULT_TRACE_RECALL_PREVIEW_CHARS,
572
+ 20,
573
+ 10_000,
574
+ ),
575
+ traceRecallQueryMaxChars: toIntegerInRange(
576
+ cfg.traceRecallQueryMaxChars,
577
+ DEFAULT_TRACE_RECALL_QUERY_MAX_CHARS,
578
+ 200,
579
+ 200_000,
580
+ ),
581
+ traceRecallQueryMaxDays: toIntegerInRange(
582
+ cfg.traceRecallQueryMaxDays,
583
+ DEFAULT_TRACE_RECALL_QUERY_MAX_DAYS,
584
+ 1,
585
+ 3650,
586
+ ),
587
+ traceRecallIncludeContentByDefault: cfg.traceRecallIncludeContentByDefault === true,
588
+ traceRecallIncludeRawUserPreview: cfg.traceRecallIncludeRawUserPreview === true,
589
+ recallTargetTypes,
590
+ enableAddResourceTool: cfg.enableAddResourceTool === true,
591
+ enabledTools,
592
+ disabledTools,
593
+ runtimeQueryConfigPath:
594
+ typeof cfg.runtimeQueryConfigPath === "string" && cfg.runtimeQueryConfigPath.trim()
595
+ ? expandHomeDir(cfg.runtimeQueryConfigPath.trim())
596
+ : "",
597
+ agentExperience: {
598
+ enabled:
599
+ typeof agentExperienceRaw.enabled === "boolean"
600
+ ? agentExperienceRaw.enabled
601
+ : DEFAULT_AGENT_EXPERIENCE.enabled,
602
+ recallLimit: Math.max(
603
+ 1,
604
+ Math.min(
605
+ 10,
606
+ Math.floor(toNumber(agentExperienceRaw.recallLimit, DEFAULT_AGENT_EXPERIENCE.recallLimit)),
607
+ ),
608
+ ),
609
+ scoreThreshold: Math.min(
610
+ 1,
611
+ Math.max(0, toNumber(agentExperienceRaw.scoreThreshold, DEFAULT_AGENT_EXPERIENCE.scoreThreshold)),
612
+ ),
613
+ maxInjectedChars: Math.max(
614
+ 500,
615
+ Math.min(
616
+ 50_000,
617
+ Math.floor(toNumber(agentExperienceRaw.maxInjectedChars, DEFAULT_AGENT_EXPERIENCE.maxInjectedChars)),
618
+ ),
619
+ ),
620
+ minQueryChars: Math.max(
621
+ 1,
622
+ Math.min(
623
+ 500,
624
+ Math.floor(toNumber(agentExperienceRaw.minQueryChars, DEFAULT_AGENT_EXPERIENCE.minQueryChars)),
625
+ ),
626
+ ),
627
+ },
628
+ };
629
+ },
630
+ uiHints: {
631
+ baseUrl: {
632
+ label: "OpenViking Base URL",
633
+ placeholder: DEFAULT_BASE_URL,
634
+ help: "HTTP URL when mode is remote (or use ${OPENVIKING_BASE_URL})",
635
+ },
636
+ peer_role: {
637
+ label: "Peer Role",
638
+ placeholder: DEFAULT_PEER_ROLE,
639
+ help: 'Controls which session messages get peer_id: "none", "assistant", or "person".',
640
+ },
641
+ peer_prefix: {
642
+ label: "Peer Prefix",
643
+ placeholder: "optional-prefix",
644
+ help: "Optional prefix applied to assistant peer_id values derived from OpenClaw runtime agent IDs.",
645
+ },
646
+ apiKey: {
647
+ label: "OpenViking API Key",
648
+ sensitive: true,
649
+ placeholder: "${OPENVIKING_API_KEY}",
650
+ help: "Optional API key for OpenViking server",
651
+ },
652
+ accountId: {
653
+ label: "Account ID",
654
+ placeholder: "(derived from API key)",
655
+ help: "Advanced option. Tenant account ID. Only needed when explicitly sending identity headers, such as root-key or trusted deployments. With a user key the server derives identity from the key.",
656
+ advanced: true,
657
+ },
658
+ userId: {
659
+ label: "User ID",
660
+ placeholder: "(derived from API key)",
661
+ help: "Advanced option. Tenant user ID. Only needed when explicitly sending identity headers.",
662
+ advanced: true,
663
+ },
664
+ targetUri: {
665
+ label: "Search Target URI",
666
+ placeholder: DEFAULT_TARGET_URI,
667
+ help: "Default OpenViking target URI for memory search",
668
+ },
669
+ timeoutMs: {
670
+ label: "Request Timeout (ms)",
671
+ placeholder: String(DEFAULT_TIMEOUT_MS),
672
+ advanced: true,
673
+ },
674
+ autoCapture: {
675
+ label: "Auto-Capture",
676
+ help: "Extract memories from recent conversation messages via OpenViking sessions",
677
+ },
678
+ captureMode: {
679
+ label: "Capture Mode",
680
+ placeholder: DEFAULT_CAPTURE_MODE,
681
+ advanced: true,
682
+ help: '"semantic" captures all eligible user text and relies on OpenViking extraction; "keyword" uses trigger regex first.',
683
+ },
684
+ captureMaxLength: {
685
+ label: "Capture Max Length",
686
+ placeholder: String(DEFAULT_CAPTURE_MAX_LENGTH),
687
+ advanced: true,
688
+ help: "Maximum sanitized user text length allowed for auto-capture.",
689
+ },
690
+ autoRecall: {
691
+ label: "Auto-Recall",
692
+ help: "Inject relevant OpenViking memories into agent context",
693
+ },
694
+ autoRecallTimeoutMs: {
695
+ label: "Auto-Recall Timeout (ms)",
696
+ placeholder: String(DEFAULT_AUTO_RECALL_TIMEOUT_MS),
697
+ advanced: true,
698
+ help: "Outer time budget for the whole auto-recall flow, including search, ranking, and memory reads.",
699
+ },
700
+ recallResources: {
701
+ label: "Recall Resources",
702
+ help: "Include resources (viking://resources) in auto-recall and default memory_recall search. Enables account-level shared knowledge retrieval.",
703
+ advanced: true,
704
+ },
705
+ recallTargetTypes: {
706
+ label: "Recall Target Types",
707
+ placeholder: "user,agent",
708
+ help: "Comma-separated auto-recall and default memory_recall targets: user, agent, resource. Session history is available through ov_archive_search and ov_archive_expand.",
709
+ advanced: true,
710
+ },
711
+ recallLimit: {
712
+ label: "Recall Limit",
713
+ placeholder: String(DEFAULT_RECALL_LIMIT),
714
+ advanced: true,
715
+ },
716
+ recallScoreThreshold: {
717
+ label: "Recall Score Threshold",
718
+ placeholder: String(DEFAULT_RECALL_SCORE_THRESHOLD),
719
+ advanced: true,
720
+ },
721
+ recallMaxInjectedChars: {
722
+ label: "Recall Max Injected Chars",
723
+ placeholder: String(DEFAULT_RECALL_MAX_INJECTED_CHARS),
724
+ advanced: true,
725
+ help: "Maximum total characters for auto-recall memory injection. Complete memories that do not fit are skipped, not truncated.",
726
+ },
727
+ recallMaxContentChars: {
728
+ label: "Deprecated Recall Max Content Chars",
729
+ placeholder: String(DEFAULT_RECALL_MAX_CONTENT_CHARS),
730
+ advanced: true,
731
+ help: "Deprecated compatibility option and will be removed in a future release. Auto-recall now keeps individual memories intact and uses recallMaxInjectedChars.",
732
+ },
733
+ recallPreferAbstract: {
734
+ label: "Recall Prefer Abstract",
735
+ advanced: true,
736
+ help: "Use memory abstract instead of fetching full content when abstract is available. Reduces token usage.",
737
+ },
738
+ recallTokenBudget: {
739
+ label: "Deprecated Recall Token Budget",
740
+ placeholder: String(DEFAULT_RECALL_MAX_INJECTED_CHARS),
741
+ advanced: true,
742
+ help: "Deprecated compatibility alias and will be removed in a future release. Use recallMaxInjectedChars.",
743
+ },
744
+ bypassSessionPatterns: {
745
+ label: "Bypass Session Patterns",
746
+ placeholder: "agent:*:cron:**",
747
+ help: "Completely bypass OpenViking for matching session keys. Use * within one segment and ** across segments.",
748
+ advanced: true,
749
+ },
750
+ commitTokenThreshold: {
751
+ label: "Commit Token Threshold",
752
+ placeholder: String(DEFAULT_COMMIT_TOKEN_THRESHOLD),
753
+ advanced: true,
754
+ help: "Minimum estimated pending tokens before auto-commit triggers. Set to 0 to commit every turn.",
755
+ },
756
+ commitKeepRecentCount: {
757
+ label: "Commit Keep Recent Count",
758
+ placeholder: String(DEFAULT_COMMIT_KEEP_RECENT_COUNT),
759
+ advanced: true,
760
+ help:
761
+ "Number of most-recent messages to keep live after an afterTurn commit. " +
762
+ "Forwarded as keep_recent_count to the server. Compact path always uses 0.",
763
+ },
764
+ emitStandardDiagnostics: {
765
+ label: "Standard diagnostics (diag JSON lines)",
766
+ advanced: true,
767
+ help: "When enabled, emit structured openviking: diag {...} lines for assemble and afterTurn. Disable to reduce log noise.",
768
+ },
769
+ logFindRequests: {
770
+ label: "Log find requests",
771
+ help:
772
+ "Log tenant routing: POST /api/v1/search/find (query, target_uri) and session POST .../messages + .../commit (sessionId, X-OpenViking-*). Never logs apiKey. " +
773
+ "Or set env OPENVIKING_LOG_ROUTING=1 or OPENVIKING_DEBUG=1 (no JSON edit).",
774
+ advanced: true,
775
+ },
776
+ traceRecall: {
777
+ label: "Trace Recall",
778
+ placeholder: "false",
779
+ help: "Enable best-effort recall trace recording for debugging recall and search decisions.",
780
+ advanced: true,
781
+ },
782
+ traceRecallPersist: {
783
+ label: "Persist Recall Trace",
784
+ placeholder: "false",
785
+ help: "Persist recall traces to local JSONL files. Disabled by default.",
786
+ advanced: true,
787
+ },
788
+ traceRecallDir: {
789
+ label: "Recall Trace Directory",
790
+ placeholder: DEFAULT_TRACE_RECALL_DIR,
791
+ help: "Directory for persisted recall trace JSONL files.",
792
+ advanced: true,
793
+ },
794
+ enableAddResourceTool: {
795
+ label: "Enable Add Resource Tool",
796
+ placeholder: "false",
797
+ help: "Disabled by default so search and read flows cannot call add_resource. Set true only when agents should import resources; manual /add-resource remains available.",
798
+ advanced: true,
799
+ },
800
+ enabledTools: {
801
+ label: "Enabled Tools",
802
+ placeholder: "default",
803
+ help: "Agent-visible tool allowlist. Accepts tool names or groups: default, all, memory, resource_query, import, recall_trace, archive, tool_result. add_resource also requires enableAddResourceTool=true.",
804
+ advanced: true,
805
+ },
806
+ disabledTools: {
807
+ label: "Disabled Tools",
808
+ placeholder: "memory",
809
+ help: "Agent-visible tool blocklist applied after enabledTools. Accepts the same tool names or groups.",
810
+ advanced: true,
811
+ },
812
+ runtimeQueryConfigPath: {
813
+ label: "Runtime Query Config Path",
814
+ placeholder: "~/.openclaw/openviking/runtime-query-config.json",
815
+ help: "Optional JSON file for /ov-query-config runtime overrides. Empty keeps overrides in memory only.",
816
+ advanced: true,
817
+ },
818
+ },
819
+ };