@openviking/openclaw-plugin 2026.5.21 → 2026.5.26
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.
- package/INSTALL-AGENT.md +355 -355
- package/INSTALL-ZH.md +345 -345
- package/INSTALL.md +362 -362
- package/README.md +338 -338
- package/auto-recall.ts +257 -257
- package/client.ts +939 -939
- package/commands/setup.ts +1002 -1002
- package/config.ts +476 -476
- package/context-engine.ts +1737 -1737
- package/dist/index.js +18 -18
- package/index.ts +1985 -1985
- package/install-manifest.json +47 -47
- package/memory-ranking.ts +267 -267
- package/openclaw.plugin.json +335 -335
- package/package.json +76 -76
- package/process-manager.ts +45 -45
- package/runtime-utils.ts +5 -5
- package/session-transcript-repair.ts +530 -530
- package/skills/install-openviking-memory/SKILL.md +665 -665
- package/text-utils.ts +507 -507
- package/tool-call-id.ts +331 -331
package/config.ts
CHANGED
|
@@ -1,476 +1,476 @@
|
|
|
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 { 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
|
+
};
|