@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/auto-recall.ts
CHANGED
|
@@ -1,257 +1,257 @@
|
|
|
1
|
-
import type { FindResult, FindResultItem, OpenVikingClient } from "./client.js";
|
|
2
|
-
import type { MemoryOpenVikingConfig } from "./config.js";
|
|
3
|
-
import {
|
|
4
|
-
pickMemoriesForInjection,
|
|
5
|
-
postProcessMemories,
|
|
6
|
-
summarizeInjectionMemories,
|
|
7
|
-
toJsonLog,
|
|
8
|
-
} from "./memory-ranking.js";
|
|
9
|
-
import { quickRecallPrecheck, withTimeout } from "./process-manager.js";
|
|
10
|
-
import { sanitizeUserTextForCapture } from "./text-utils.js";
|
|
11
|
-
|
|
12
|
-
const AUTO_RECALL_TIMEOUT_MS = 5_000;
|
|
13
|
-
const RECALL_QUERY_MAX_CHARS = 4_000;
|
|
14
|
-
export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
|
|
15
|
-
|
|
16
|
-
type Logger = {
|
|
17
|
-
info: (msg: string) => void;
|
|
18
|
-
warn?: (msg: string) => void;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type PreparedRecallQuery = {
|
|
22
|
-
query: string;
|
|
23
|
-
truncated: boolean;
|
|
24
|
-
originalChars: number;
|
|
25
|
-
finalChars: number;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export function prepareRecallQuery(rawText: string): PreparedRecallQuery {
|
|
29
|
-
const sanitized = sanitizeUserTextForCapture(rawText).trim();
|
|
30
|
-
const originalChars = sanitized.length;
|
|
31
|
-
|
|
32
|
-
if (!sanitized) {
|
|
33
|
-
return {
|
|
34
|
-
query: "",
|
|
35
|
-
truncated: false,
|
|
36
|
-
originalChars: 0,
|
|
37
|
-
finalChars: 0,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const query =
|
|
42
|
-
sanitized.length > RECALL_QUERY_MAX_CHARS
|
|
43
|
-
? sanitized.slice(0, RECALL_QUERY_MAX_CHARS).trim()
|
|
44
|
-
: sanitized;
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
query,
|
|
48
|
-
truncated: sanitized.length > RECALL_QUERY_MAX_CHARS,
|
|
49
|
-
originalChars,
|
|
50
|
-
finalChars: query.length,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Estimate token count using chars/4 heuristic for diagnostics. */
|
|
55
|
-
export function estimateTokenCount(text: string): number {
|
|
56
|
-
if (!text) return 0;
|
|
57
|
-
return Math.ceil(text.length / 4);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type BuildMemoryLinesOptions = {
|
|
61
|
-
recallPreferAbstract: boolean;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
async function resolveMemoryContent(
|
|
65
|
-
item: FindResultItem,
|
|
66
|
-
readFn: (uri: string) => Promise<string>,
|
|
67
|
-
options: BuildMemoryLinesOptions,
|
|
68
|
-
): Promise<string> {
|
|
69
|
-
let content: string;
|
|
70
|
-
|
|
71
|
-
if (options.recallPreferAbstract && item.abstract?.trim()) {
|
|
72
|
-
content = item.abstract.trim();
|
|
73
|
-
} else if (item.level === 2) {
|
|
74
|
-
try {
|
|
75
|
-
const fullContent = await readFn(item.uri);
|
|
76
|
-
content =
|
|
77
|
-
fullContent && typeof fullContent === "string" && fullContent.trim()
|
|
78
|
-
? fullContent.trim()
|
|
79
|
-
: (item.abstract?.trim() || item.uri);
|
|
80
|
-
} catch {
|
|
81
|
-
content = item.abstract?.trim() || item.uri;
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
content = item.abstract?.trim() || item.uri;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return content;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export async function buildMemoryLines(
|
|
91
|
-
memories: FindResultItem[],
|
|
92
|
-
readFn: (uri: string) => Promise<string>,
|
|
93
|
-
options: BuildMemoryLinesOptions,
|
|
94
|
-
): Promise<string[]> {
|
|
95
|
-
const lines: string[] = [];
|
|
96
|
-
for (const item of memories) {
|
|
97
|
-
const content = await resolveMemoryContent(item, readFn, options);
|
|
98
|
-
lines.push(`- [${item.category ?? "memory"}] ${content}`);
|
|
99
|
-
}
|
|
100
|
-
return lines;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export type BuildMemoryLinesWithBudgetOptions = BuildMemoryLinesOptions & {
|
|
104
|
-
recallMaxInjectedChars?: number;
|
|
105
|
-
recallTokenBudget?: number;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Build memory lines with a character budget constraint.
|
|
110
|
-
*
|
|
111
|
-
* Individual memories are never truncated. A memory that cannot fit within the
|
|
112
|
-
* remaining character budget is skipped so only complete memory entries are
|
|
113
|
-
* injected.
|
|
114
|
-
*/
|
|
115
|
-
export async function buildMemoryLinesWithBudget(
|
|
116
|
-
memories: FindResultItem[],
|
|
117
|
-
readFn: (uri: string) => Promise<string>,
|
|
118
|
-
options: BuildMemoryLinesWithBudgetOptions,
|
|
119
|
-
): Promise<{ lines: string[]; estimatedTokens: number }> {
|
|
120
|
-
const charBudget = options.recallMaxInjectedChars ?? options.recallTokenBudget ?? 0;
|
|
121
|
-
const lines: string[] = [];
|
|
122
|
-
let totalTokens = 0;
|
|
123
|
-
let totalChars = 0;
|
|
124
|
-
|
|
125
|
-
for (const item of memories) {
|
|
126
|
-
if (totalChars >= charBudget) {
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const content = await resolveMemoryContent(item, readFn, options);
|
|
131
|
-
const line = `- [${item.category ?? "memory"}] ${content}`;
|
|
132
|
-
const separatorChars = lines.length > 0 ? 1 : 0;
|
|
133
|
-
const projectedChars = totalChars + separatorChars + line.length;
|
|
134
|
-
|
|
135
|
-
if (projectedChars > charBudget) {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const lineTokens = estimateTokenCount(line);
|
|
140
|
-
|
|
141
|
-
lines.push(line);
|
|
142
|
-
totalTokens += lineTokens;
|
|
143
|
-
totalChars = projectedChars;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return { lines, estimatedTokens: totalTokens };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function buildRecallContextBlock(memoryLines: string[]): string {
|
|
150
|
-
return [
|
|
151
|
-
"<relevant-memories>",
|
|
152
|
-
AUTO_RECALL_SOURCE_MARKER,
|
|
153
|
-
"The following OpenViking memories may be relevant:",
|
|
154
|
-
...memoryLines,
|
|
155
|
-
"</relevant-memories>",
|
|
156
|
-
].join("\n");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function buildAutoRecallContext(params: {
|
|
160
|
-
cfg: Required<MemoryOpenVikingConfig>;
|
|
161
|
-
client: OpenVikingClient;
|
|
162
|
-
agentId: string;
|
|
163
|
-
queryText: string;
|
|
164
|
-
logger: Logger;
|
|
165
|
-
verbose?: (message: string) => void;
|
|
166
|
-
}): Promise<{ block?: string; memoryCount: number; estimatedTokens: number }> {
|
|
167
|
-
const { cfg, client, agentId, queryText, logger, verbose } = params;
|
|
168
|
-
|
|
169
|
-
if (!cfg.autoRecall || queryText.length < 5) {
|
|
170
|
-
return { memoryCount: 0, estimatedTokens: 0 };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const precheck = await quickRecallPrecheck(cfg.baseUrl);
|
|
174
|
-
if (!precheck.ok) {
|
|
175
|
-
verbose?.(`openviking: skipping auto-recall because precheck failed (${precheck.reason})`);
|
|
176
|
-
return { memoryCount: 0, estimatedTokens: 0 };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return withTimeout(
|
|
180
|
-
(async () => {
|
|
181
|
-
const candidateLimit = Math.max(cfg.recallLimit * 4, 20);
|
|
182
|
-
const autoRecallPromises: Promise<FindResult>[] = [
|
|
183
|
-
client.find(queryText, {
|
|
184
|
-
targetUri: "viking://user/memories",
|
|
185
|
-
limit: candidateLimit,
|
|
186
|
-
scoreThreshold: 0,
|
|
187
|
-
}, agentId),
|
|
188
|
-
client.find(queryText, {
|
|
189
|
-
targetUri: "viking://agent/memories",
|
|
190
|
-
limit: candidateLimit,
|
|
191
|
-
scoreThreshold: 0,
|
|
192
|
-
}, agentId),
|
|
193
|
-
];
|
|
194
|
-
if (cfg.recallResources) {
|
|
195
|
-
autoRecallPromises.push(
|
|
196
|
-
client.find(queryText, {
|
|
197
|
-
targetUri: "viking://resources",
|
|
198
|
-
limit: candidateLimit,
|
|
199
|
-
scoreThreshold: 0,
|
|
200
|
-
}, agentId),
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
const autoRecallSettled = await Promise.allSettled(autoRecallPromises);
|
|
204
|
-
|
|
205
|
-
const allMemories: FindResultItem[] = [];
|
|
206
|
-
for (const s of autoRecallSettled) {
|
|
207
|
-
if (s.status === "fulfilled") {
|
|
208
|
-
allMemories.push(...(s.value.memories ?? []), ...(s.value.resources ?? []));
|
|
209
|
-
} else {
|
|
210
|
-
logger.warn?.(`openviking: auto-recall search failed: ${String(s.reason)}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const uniqueMemories = allMemories.filter((memory, index, self) =>
|
|
215
|
-
index === self.findIndex((m) => m.uri === memory.uri)
|
|
216
|
-
);
|
|
217
|
-
const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
|
|
218
|
-
const processed = postProcessMemories(leafOnly, {
|
|
219
|
-
limit: candidateLimit,
|
|
220
|
-
scoreThreshold: cfg.recallScoreThreshold,
|
|
221
|
-
});
|
|
222
|
-
const memories = pickMemoriesForInjection(processed, cfg.recallLimit, queryText);
|
|
223
|
-
|
|
224
|
-
if (memories.length === 0) {
|
|
225
|
-
return { memoryCount: 0, estimatedTokens: 0 };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(
|
|
229
|
-
memories,
|
|
230
|
-
(uri) => client.read(uri, agentId),
|
|
231
|
-
{
|
|
232
|
-
recallPreferAbstract: cfg.recallPreferAbstract,
|
|
233
|
-
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
234
|
-
},
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
if (memoryLines.length === 0) {
|
|
238
|
-
verbose?.(
|
|
239
|
-
`openviking: skipping auto-recall injection; no complete memories fit maxInjectedChars=${cfg.recallMaxInjectedChars}`,
|
|
240
|
-
);
|
|
241
|
-
return { memoryCount: 0, estimatedTokens: 0 };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const block = buildRecallContextBlock(memoryLines);
|
|
245
|
-
verbose?.(
|
|
246
|
-
`openviking: injecting ${memoryLines.length} memories (${block.length} chars, ~${estimatedTokens} tokens, maxInjectedChars=${cfg.recallMaxInjectedChars})`,
|
|
247
|
-
);
|
|
248
|
-
verbose?.(
|
|
249
|
-
`openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`,
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
return { block, memoryCount: memoryLines.length, estimatedTokens };
|
|
253
|
-
})(),
|
|
254
|
-
AUTO_RECALL_TIMEOUT_MS,
|
|
255
|
-
"openviking: auto-recall search timeout",
|
|
256
|
-
);
|
|
257
|
-
}
|
|
1
|
+
import type { FindResult, FindResultItem, OpenVikingClient } from "./client.js";
|
|
2
|
+
import type { MemoryOpenVikingConfig } from "./config.js";
|
|
3
|
+
import {
|
|
4
|
+
pickMemoriesForInjection,
|
|
5
|
+
postProcessMemories,
|
|
6
|
+
summarizeInjectionMemories,
|
|
7
|
+
toJsonLog,
|
|
8
|
+
} from "./memory-ranking.js";
|
|
9
|
+
import { quickRecallPrecheck, withTimeout } from "./process-manager.js";
|
|
10
|
+
import { sanitizeUserTextForCapture } from "./text-utils.js";
|
|
11
|
+
|
|
12
|
+
const AUTO_RECALL_TIMEOUT_MS = 5_000;
|
|
13
|
+
const RECALL_QUERY_MAX_CHARS = 4_000;
|
|
14
|
+
export const AUTO_RECALL_SOURCE_MARKER = "Source: openviking-auto-recall";
|
|
15
|
+
|
|
16
|
+
type Logger = {
|
|
17
|
+
info: (msg: string) => void;
|
|
18
|
+
warn?: (msg: string) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type PreparedRecallQuery = {
|
|
22
|
+
query: string;
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
originalChars: number;
|
|
25
|
+
finalChars: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function prepareRecallQuery(rawText: string): PreparedRecallQuery {
|
|
29
|
+
const sanitized = sanitizeUserTextForCapture(rawText).trim();
|
|
30
|
+
const originalChars = sanitized.length;
|
|
31
|
+
|
|
32
|
+
if (!sanitized) {
|
|
33
|
+
return {
|
|
34
|
+
query: "",
|
|
35
|
+
truncated: false,
|
|
36
|
+
originalChars: 0,
|
|
37
|
+
finalChars: 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const query =
|
|
42
|
+
sanitized.length > RECALL_QUERY_MAX_CHARS
|
|
43
|
+
? sanitized.slice(0, RECALL_QUERY_MAX_CHARS).trim()
|
|
44
|
+
: sanitized;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
query,
|
|
48
|
+
truncated: sanitized.length > RECALL_QUERY_MAX_CHARS,
|
|
49
|
+
originalChars,
|
|
50
|
+
finalChars: query.length,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Estimate token count using chars/4 heuristic for diagnostics. */
|
|
55
|
+
export function estimateTokenCount(text: string): number {
|
|
56
|
+
if (!text) return 0;
|
|
57
|
+
return Math.ceil(text.length / 4);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type BuildMemoryLinesOptions = {
|
|
61
|
+
recallPreferAbstract: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
async function resolveMemoryContent(
|
|
65
|
+
item: FindResultItem,
|
|
66
|
+
readFn: (uri: string) => Promise<string>,
|
|
67
|
+
options: BuildMemoryLinesOptions,
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
let content: string;
|
|
70
|
+
|
|
71
|
+
if (options.recallPreferAbstract && item.abstract?.trim()) {
|
|
72
|
+
content = item.abstract.trim();
|
|
73
|
+
} else if (item.level === 2) {
|
|
74
|
+
try {
|
|
75
|
+
const fullContent = await readFn(item.uri);
|
|
76
|
+
content =
|
|
77
|
+
fullContent && typeof fullContent === "string" && fullContent.trim()
|
|
78
|
+
? fullContent.trim()
|
|
79
|
+
: (item.abstract?.trim() || item.uri);
|
|
80
|
+
} catch {
|
|
81
|
+
content = item.abstract?.trim() || item.uri;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
content = item.abstract?.trim() || item.uri;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function buildMemoryLines(
|
|
91
|
+
memories: FindResultItem[],
|
|
92
|
+
readFn: (uri: string) => Promise<string>,
|
|
93
|
+
options: BuildMemoryLinesOptions,
|
|
94
|
+
): Promise<string[]> {
|
|
95
|
+
const lines: string[] = [];
|
|
96
|
+
for (const item of memories) {
|
|
97
|
+
const content = await resolveMemoryContent(item, readFn, options);
|
|
98
|
+
lines.push(`- [${item.category ?? "memory"}] ${content}`);
|
|
99
|
+
}
|
|
100
|
+
return lines;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type BuildMemoryLinesWithBudgetOptions = BuildMemoryLinesOptions & {
|
|
104
|
+
recallMaxInjectedChars?: number;
|
|
105
|
+
recallTokenBudget?: number;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build memory lines with a character budget constraint.
|
|
110
|
+
*
|
|
111
|
+
* Individual memories are never truncated. A memory that cannot fit within the
|
|
112
|
+
* remaining character budget is skipped so only complete memory entries are
|
|
113
|
+
* injected.
|
|
114
|
+
*/
|
|
115
|
+
export async function buildMemoryLinesWithBudget(
|
|
116
|
+
memories: FindResultItem[],
|
|
117
|
+
readFn: (uri: string) => Promise<string>,
|
|
118
|
+
options: BuildMemoryLinesWithBudgetOptions,
|
|
119
|
+
): Promise<{ lines: string[]; estimatedTokens: number }> {
|
|
120
|
+
const charBudget = options.recallMaxInjectedChars ?? options.recallTokenBudget ?? 0;
|
|
121
|
+
const lines: string[] = [];
|
|
122
|
+
let totalTokens = 0;
|
|
123
|
+
let totalChars = 0;
|
|
124
|
+
|
|
125
|
+
for (const item of memories) {
|
|
126
|
+
if (totalChars >= charBudget) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const content = await resolveMemoryContent(item, readFn, options);
|
|
131
|
+
const line = `- [${item.category ?? "memory"}] ${content}`;
|
|
132
|
+
const separatorChars = lines.length > 0 ? 1 : 0;
|
|
133
|
+
const projectedChars = totalChars + separatorChars + line.length;
|
|
134
|
+
|
|
135
|
+
if (projectedChars > charBudget) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lineTokens = estimateTokenCount(line);
|
|
140
|
+
|
|
141
|
+
lines.push(line);
|
|
142
|
+
totalTokens += lineTokens;
|
|
143
|
+
totalChars = projectedChars;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { lines, estimatedTokens: totalTokens };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function buildRecallContextBlock(memoryLines: string[]): string {
|
|
150
|
+
return [
|
|
151
|
+
"<relevant-memories>",
|
|
152
|
+
AUTO_RECALL_SOURCE_MARKER,
|
|
153
|
+
"The following OpenViking memories may be relevant:",
|
|
154
|
+
...memoryLines,
|
|
155
|
+
"</relevant-memories>",
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function buildAutoRecallContext(params: {
|
|
160
|
+
cfg: Required<MemoryOpenVikingConfig>;
|
|
161
|
+
client: OpenVikingClient;
|
|
162
|
+
agentId: string;
|
|
163
|
+
queryText: string;
|
|
164
|
+
logger: Logger;
|
|
165
|
+
verbose?: (message: string) => void;
|
|
166
|
+
}): Promise<{ block?: string; memoryCount: number; estimatedTokens: number }> {
|
|
167
|
+
const { cfg, client, agentId, queryText, logger, verbose } = params;
|
|
168
|
+
|
|
169
|
+
if (!cfg.autoRecall || queryText.length < 5) {
|
|
170
|
+
return { memoryCount: 0, estimatedTokens: 0 };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const precheck = await quickRecallPrecheck(cfg.baseUrl);
|
|
174
|
+
if (!precheck.ok) {
|
|
175
|
+
verbose?.(`openviking: skipping auto-recall because precheck failed (${precheck.reason})`);
|
|
176
|
+
return { memoryCount: 0, estimatedTokens: 0 };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return withTimeout(
|
|
180
|
+
(async () => {
|
|
181
|
+
const candidateLimit = Math.max(cfg.recallLimit * 4, 20);
|
|
182
|
+
const autoRecallPromises: Promise<FindResult>[] = [
|
|
183
|
+
client.find(queryText, {
|
|
184
|
+
targetUri: "viking://user/memories",
|
|
185
|
+
limit: candidateLimit,
|
|
186
|
+
scoreThreshold: 0,
|
|
187
|
+
}, agentId),
|
|
188
|
+
client.find(queryText, {
|
|
189
|
+
targetUri: "viking://agent/memories",
|
|
190
|
+
limit: candidateLimit,
|
|
191
|
+
scoreThreshold: 0,
|
|
192
|
+
}, agentId),
|
|
193
|
+
];
|
|
194
|
+
if (cfg.recallResources) {
|
|
195
|
+
autoRecallPromises.push(
|
|
196
|
+
client.find(queryText, {
|
|
197
|
+
targetUri: "viking://resources",
|
|
198
|
+
limit: candidateLimit,
|
|
199
|
+
scoreThreshold: 0,
|
|
200
|
+
}, agentId),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const autoRecallSettled = await Promise.allSettled(autoRecallPromises);
|
|
204
|
+
|
|
205
|
+
const allMemories: FindResultItem[] = [];
|
|
206
|
+
for (const s of autoRecallSettled) {
|
|
207
|
+
if (s.status === "fulfilled") {
|
|
208
|
+
allMemories.push(...(s.value.memories ?? []), ...(s.value.resources ?? []));
|
|
209
|
+
} else {
|
|
210
|
+
logger.warn?.(`openviking: auto-recall search failed: ${String(s.reason)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const uniqueMemories = allMemories.filter((memory, index, self) =>
|
|
215
|
+
index === self.findIndex((m) => m.uri === memory.uri)
|
|
216
|
+
);
|
|
217
|
+
const leafOnly = uniqueMemories.filter((m) => !m.level || m.level === 2);
|
|
218
|
+
const processed = postProcessMemories(leafOnly, {
|
|
219
|
+
limit: candidateLimit,
|
|
220
|
+
scoreThreshold: cfg.recallScoreThreshold,
|
|
221
|
+
});
|
|
222
|
+
const memories = pickMemoriesForInjection(processed, cfg.recallLimit, queryText);
|
|
223
|
+
|
|
224
|
+
if (memories.length === 0) {
|
|
225
|
+
return { memoryCount: 0, estimatedTokens: 0 };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { lines: memoryLines, estimatedTokens } = await buildMemoryLinesWithBudget(
|
|
229
|
+
memories,
|
|
230
|
+
(uri) => client.read(uri, agentId),
|
|
231
|
+
{
|
|
232
|
+
recallPreferAbstract: cfg.recallPreferAbstract,
|
|
233
|
+
recallMaxInjectedChars: cfg.recallMaxInjectedChars,
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (memoryLines.length === 0) {
|
|
238
|
+
verbose?.(
|
|
239
|
+
`openviking: skipping auto-recall injection; no complete memories fit maxInjectedChars=${cfg.recallMaxInjectedChars}`,
|
|
240
|
+
);
|
|
241
|
+
return { memoryCount: 0, estimatedTokens: 0 };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const block = buildRecallContextBlock(memoryLines);
|
|
245
|
+
verbose?.(
|
|
246
|
+
`openviking: injecting ${memoryLines.length} memories (${block.length} chars, ~${estimatedTokens} tokens, maxInjectedChars=${cfg.recallMaxInjectedChars})`,
|
|
247
|
+
);
|
|
248
|
+
verbose?.(
|
|
249
|
+
`openviking: inject-detail ${toJsonLog({ count: memories.length, memories: summarizeInjectionMemories(memories) })}`,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return { block, memoryCount: memoryLines.length, estimatedTokens };
|
|
253
|
+
})(),
|
|
254
|
+
AUTO_RECALL_TIMEOUT_MS,
|
|
255
|
+
"openviking: auto-recall search timeout",
|
|
256
|
+
);
|
|
257
|
+
}
|