@xdarkicex/openclaw-memory-libravdb 1.6.21 → 1.6.23
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/dist/context-engine.d.ts +13 -0
- package/dist/context-engine.js +153 -2
- package/dist/index.js +57 -5
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/dist/context-engine.d.ts
CHANGED
|
@@ -26,22 +26,32 @@ type OpenClawCompatibleCompactResult = {
|
|
|
26
26
|
reason?: string;
|
|
27
27
|
result?: {
|
|
28
28
|
summary?: string;
|
|
29
|
+
summaryText?: string;
|
|
29
30
|
firstKeptEntryId?: string;
|
|
30
31
|
tokensBefore: number;
|
|
31
32
|
tokensAfter?: number;
|
|
32
33
|
details?: unknown;
|
|
33
34
|
};
|
|
34
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes a single kernel message into the kernel-compatible format.
|
|
38
|
+
*/
|
|
35
39
|
export declare function normalizeKernelMessage(message: {
|
|
36
40
|
role: string;
|
|
37
41
|
content: unknown;
|
|
38
42
|
id?: string;
|
|
39
43
|
}): KernelCompatibleMessage;
|
|
44
|
+
/**
|
|
45
|
+
* Normalizes an array of kernel messages.
|
|
46
|
+
*/
|
|
40
47
|
export declare function normalizeKernelMessages(messages: Array<{
|
|
41
48
|
role: string;
|
|
42
49
|
content: unknown;
|
|
43
50
|
id?: string;
|
|
44
51
|
}>): KernelCompatibleMessage[];
|
|
52
|
+
/**
|
|
53
|
+
* Normalizes a compact result into the OpenClaw-compatible assemble result format.
|
|
54
|
+
*/
|
|
45
55
|
export declare function normalizeAssembleResult(result: {
|
|
46
56
|
messages?: Array<{
|
|
47
57
|
role: string;
|
|
@@ -52,6 +62,9 @@ export declare function normalizeAssembleResult(result: {
|
|
|
52
62
|
systemPromptAddition?: string;
|
|
53
63
|
debug?: AssembleContextInternalResponse["debug"];
|
|
54
64
|
}, sourceMessages?: OpenClawCompatibleMessage[]): OpenClawCompatibleAssembleResult;
|
|
65
|
+
/**
|
|
66
|
+
* Builds the context engine factory with the given client getter.
|
|
67
|
+
*/
|
|
55
68
|
export declare function buildContextEngineFactory(runtime: PluginRuntime, cfg: PluginConfig, logger?: LoggerLike): {
|
|
56
69
|
info: {
|
|
57
70
|
id: string;
|
package/dist/context-engine.js
CHANGED
|
@@ -24,6 +24,10 @@ function requireSessionId(sessionId, operation) {
|
|
|
24
24
|
}
|
|
25
25
|
throw new Error(`LibraVDB ${operation} requires a non-empty sessionId; refusing ambiguous request.`);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes a compact session response into the OpenClaw-compatible format.
|
|
29
|
+
* Handles missing/undefined fields and provides sensible defaults.
|
|
30
|
+
*/
|
|
27
31
|
function normalizeCompactResult(response, options = {}) {
|
|
28
32
|
const didCompact = response?.didCompact === true;
|
|
29
33
|
const tokensBefore = normalizeCurrentTokenCount(options.tokensBefore) ?? 0;
|
|
@@ -35,6 +39,9 @@ function normalizeCompactResult(response, options = {}) {
|
|
|
35
39
|
? response.summaryMethod
|
|
36
40
|
: undefined,
|
|
37
41
|
meanConfidence: typeof response?.meanConfidence === "number" ? response.meanConfidence : undefined,
|
|
42
|
+
summaryText: typeof response?.summaryText === "string" && response.summaryText.length > 0
|
|
43
|
+
? response.summaryText
|
|
44
|
+
: undefined,
|
|
38
45
|
};
|
|
39
46
|
return {
|
|
40
47
|
ok: true,
|
|
@@ -43,10 +50,14 @@ function normalizeCompactResult(response, options = {}) {
|
|
|
43
50
|
result: {
|
|
44
51
|
tokensBefore,
|
|
45
52
|
...(details.summaryMethod ? { summary: details.summaryMethod } : {}),
|
|
53
|
+
...(details.summaryText ? { summaryText: details.summaryText } : {}),
|
|
46
54
|
details,
|
|
47
55
|
},
|
|
48
56
|
};
|
|
49
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Converts a kernel block to its string representation.
|
|
60
|
+
*/
|
|
50
61
|
function stringifyKernelBlock(block) {
|
|
51
62
|
if (!block || typeof block !== "object") {
|
|
52
63
|
return "";
|
|
@@ -80,6 +91,9 @@ function stringifyKernelBlock(block) {
|
|
|
80
91
|
return typeof record.text === "string" ? record.text : "";
|
|
81
92
|
}
|
|
82
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Normalizes kernel content (string or block array) to a flat string.
|
|
96
|
+
*/
|
|
83
97
|
function normalizeKernelContent(content) {
|
|
84
98
|
if (typeof content === "string") {
|
|
85
99
|
return content;
|
|
@@ -89,6 +103,9 @@ function normalizeKernelContent(content) {
|
|
|
89
103
|
}
|
|
90
104
|
return content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n");
|
|
91
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Approximates token count for a text string.
|
|
108
|
+
*/
|
|
92
109
|
function approximateTokenCount(text) {
|
|
93
110
|
if (typeof text === "string") {
|
|
94
111
|
return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
|
|
@@ -98,13 +115,22 @@ function approximateTokenCount(text) {
|
|
|
98
115
|
}
|
|
99
116
|
return Math.ceil(normalizeKernelContent(text).length / APPROX_CHARS_PER_TOKEN);
|
|
100
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Approximates tokens for a single message including wrapper overhead.
|
|
120
|
+
*/
|
|
101
121
|
function approximateMessageTokens(message) {
|
|
102
122
|
// Approximate per-message wrapper overhead so trimming is conservative.
|
|
103
123
|
return approximateTokenCount(message.content) + 8;
|
|
104
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Sums approximate tokens across an array of messages.
|
|
127
|
+
*/
|
|
105
128
|
function approximateMessagesTokens(messages) {
|
|
106
129
|
return messages.reduce((sum, message) => sum + approximateMessageTokens(message), 0);
|
|
107
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Selects messages after the pre-prompt boundary for after-turn processing.
|
|
133
|
+
*/
|
|
108
134
|
function selectAfterTurnMessages(messages, prePromptMessageCount, logger) {
|
|
109
135
|
if (typeof prePromptMessageCount !== "number" ||
|
|
110
136
|
!Number.isFinite(prePromptMessageCount) ||
|
|
@@ -139,6 +165,9 @@ function normalizeTokenBudget(tokenBudget) {
|
|
|
139
165
|
}
|
|
140
166
|
return Math.max(1, Math.floor(tokenBudget));
|
|
141
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Resolves the effective assemble budget from token budget configuration.
|
|
170
|
+
*/
|
|
142
171
|
function resolveEffectiveAssembleBudget(tokenBudget) {
|
|
143
172
|
const normalized = normalizeTokenBudget(tokenBudget) ?? 1;
|
|
144
173
|
const proportionalHeadroom = Math.max(1, Math.floor(normalized * ASSEMBLE_BUDGET_HEADROOM_FRACTION));
|
|
@@ -151,6 +180,9 @@ function normalizeThresholdFraction(fraction) {
|
|
|
151
180
|
}
|
|
152
181
|
return Math.min(0.99, Math.max(0.05, fraction));
|
|
153
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolves the dynamic compaction threshold from budget and threshold params.
|
|
185
|
+
*/
|
|
154
186
|
function resolveDynamicCompactThreshold(tokenBudget, compactThreshold, compactionThresholdFraction) {
|
|
155
187
|
if (typeof compactThreshold === "number" && Number.isFinite(compactThreshold) && compactThreshold > 0) {
|
|
156
188
|
return Math.max(1, Math.floor(compactThreshold));
|
|
@@ -173,18 +205,30 @@ function resolvePredictiveCompactionTarget(params) {
|
|
|
173
205
|
? belowThresholdTarget
|
|
174
206
|
: Math.max(1, currentTokenCount - 1);
|
|
175
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Reads a runtime numeric config value with fallback defaults.
|
|
210
|
+
*/
|
|
176
211
|
function readRuntimeNumber(runtimeContext, key) {
|
|
177
212
|
const value = runtimeContext?.[key];
|
|
178
213
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
179
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Checks if manual compaction was explicitly requested via runtime context.
|
|
217
|
+
*/
|
|
180
218
|
function isManualCompactionRequested(runtimeContext) {
|
|
181
219
|
return runtimeContext?.manualCompaction === true;
|
|
182
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Logs a predictive compaction attempt with phase and sizing info.
|
|
223
|
+
*/
|
|
183
224
|
function logPredictiveCompactionAttempt(params) {
|
|
184
225
|
params.logger.info?.(`LibraVDB predictive compaction trigger phase=${params.phase} sessionId=${params.sessionId} ` +
|
|
185
226
|
`currentTokenCount=${params.currentTokenCount} threshold=${params.threshold} ` +
|
|
186
227
|
`targetSize=${params.targetSize} tokenBudget=${params.tokenBudget ?? "unknown"}`);
|
|
187
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Logs the outcome of a predictive compaction attempt.
|
|
231
|
+
*/
|
|
188
232
|
function logPredictiveCompactionOutcome(params) {
|
|
189
233
|
const message = `LibraVDB predictive compaction ${params.compacted ? "completed" : "did not compact"} ` +
|
|
190
234
|
`phase=${params.phase} sessionId=${params.sessionId} currentTokenCount=${params.currentTokenCount} ` +
|
|
@@ -196,6 +240,9 @@ function logPredictiveCompactionOutcome(params) {
|
|
|
196
240
|
}
|
|
197
241
|
params.logger.warn?.(message);
|
|
198
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Truncates content to fit within token budget, preserving the tail.
|
|
245
|
+
*/
|
|
199
246
|
function truncateContentToTokenBudget(content, tokenBudget) {
|
|
200
247
|
if (tokenBudget <= 0)
|
|
201
248
|
return "";
|
|
@@ -206,6 +253,9 @@ function truncateContentToTokenBudget(content, tokenBudget) {
|
|
|
206
253
|
// Keep the tail so recent tool output / latest answer content is preserved.
|
|
207
254
|
return normalized.slice(normalized.length - maxChars);
|
|
208
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Trims messages from the end to fit within token budget.
|
|
258
|
+
*/
|
|
209
259
|
function trimMessagesToBudget(messages, tokenBudget) {
|
|
210
260
|
if (tokenBudget <= 0 || messages.length === 0) {
|
|
211
261
|
return [];
|
|
@@ -235,6 +285,9 @@ function trimMessagesToBudget(messages, tokenBudget) {
|
|
|
235
285
|
}
|
|
236
286
|
return [{ ...last, content: truncated }];
|
|
237
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Bounds after-turn messages for ingest, trimming if over max tokens.
|
|
290
|
+
*/
|
|
238
291
|
function boundAfterTurnMessagesForIngest(messages, logger, sessionId) {
|
|
239
292
|
const estimatedTokens = approximateMessagesTokens(messages);
|
|
240
293
|
if (estimatedTokens <= AFTER_TURN_INGEST_MAX_TOKENS) {
|
|
@@ -247,6 +300,9 @@ function boundAfterTurnMessagesForIngest(messages, logger, sessionId) {
|
|
|
247
300
|
`forwardedMessages=${bounded.length}`);
|
|
248
301
|
return bounded;
|
|
249
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Enforces token budget invariant by trimming messages and system prompt.
|
|
305
|
+
*/
|
|
250
306
|
function enforceTokenBudgetInvariant(result, tokenBudget) {
|
|
251
307
|
if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
|
|
252
308
|
return result;
|
|
@@ -286,6 +342,9 @@ function buildBudgetFallbackContext(messages, tokenBudget) {
|
|
|
286
342
|
promptAuthority: PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW,
|
|
287
343
|
};
|
|
288
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Resolves token count for predictive compaction from messages and prompt.
|
|
347
|
+
*/
|
|
289
348
|
function resolvePredictiveCompactionTokenCount(args) {
|
|
290
349
|
const currentTokenCount = normalizeCurrentTokenCount(args.currentTokenCount);
|
|
291
350
|
const sourcePressureEstimate = normalizeCurrentTokenCount(approximateMessagesTokens(args.messages) + approximateTokenCount(args.prompt ?? ""));
|
|
@@ -297,6 +356,9 @@ function resolvePredictiveCompactionTokenCount(args) {
|
|
|
297
356
|
}
|
|
298
357
|
return Math.max(currentTokenCount, sourcePressureEstimate);
|
|
299
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Resolves token count for after-turn predictive compaction.
|
|
361
|
+
*/
|
|
300
362
|
function resolveAfterTurnPredictiveCompactionTokenCount(args) {
|
|
301
363
|
const currentTokenCount = normalizeCurrentTokenCount(args.currentTokenCount);
|
|
302
364
|
const forwardedMessageTokens = normalizeCurrentTokenCount(approximateMessagesTokens(args.messages));
|
|
@@ -308,6 +370,9 @@ function resolveAfterTurnPredictiveCompactionTokenCount(args) {
|
|
|
308
370
|
}
|
|
309
371
|
return Math.max(currentTokenCount, forwardedMessageTokens);
|
|
310
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Normalizes a single kernel message into the kernel-compatible format.
|
|
375
|
+
*/
|
|
311
376
|
export function normalizeKernelMessage(message) {
|
|
312
377
|
return {
|
|
313
378
|
role: message.role,
|
|
@@ -315,9 +380,15 @@ export function normalizeKernelMessage(message) {
|
|
|
315
380
|
...(typeof message.id === "string" ? { id: message.id } : {}),
|
|
316
381
|
};
|
|
317
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Normalizes an array of kernel messages.
|
|
385
|
+
*/
|
|
318
386
|
export function normalizeKernelMessages(messages) {
|
|
319
387
|
return messages.map((message) => normalizeKernelMessage(message));
|
|
320
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Extracts tokens for exact recall matching from text.
|
|
391
|
+
*/
|
|
321
392
|
function extractExactRecallTokens(text) {
|
|
322
393
|
const tokens = new Set();
|
|
323
394
|
for (const m of text.matchAll(STRUCTURED_MARKER_RE)) {
|
|
@@ -339,17 +410,26 @@ function extractExactRecallTokens(text) {
|
|
|
339
410
|
}
|
|
340
411
|
return Array.from(tokens).slice(0, EXACT_RECALL_MAX_TOKENS);
|
|
341
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Checks if text is an exact recall fact containing the token.
|
|
415
|
+
*/
|
|
342
416
|
function isExactRecallFact(text, token) {
|
|
343
417
|
return (text.includes(token) &&
|
|
344
418
|
/\bmeans\b/i.test(text) &&
|
|
345
419
|
!isQuestionShapedRecallCandidate(text));
|
|
346
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Checks if text appears to be a question-shaped recall candidate.
|
|
423
|
+
*/
|
|
347
424
|
function isQuestionShapedRecallCandidate(text) {
|
|
348
425
|
const normalized = text.trim();
|
|
349
426
|
return (normalized.includes("?") ||
|
|
350
427
|
/\bwhat\s+does\b/i.test(normalized) ||
|
|
351
428
|
/^\s*(?:who|what|when|where|why|how)\b/i.test(normalized));
|
|
352
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Ranks a recall candidate by relevance to the token.
|
|
432
|
+
*/
|
|
353
433
|
function rankExactRecallCandidate(result, token) {
|
|
354
434
|
if (typeof result.text !== "string" || !result.text.includes(token)) {
|
|
355
435
|
return Number.NEGATIVE_INFINITY;
|
|
@@ -363,14 +443,22 @@ function rankExactRecallCandidate(result, token) {
|
|
|
363
443
|
rank -= 25;
|
|
364
444
|
return rank;
|
|
365
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Extracts the exact recall fact text starting at the token marker.
|
|
448
|
+
* Tool-call patterns are sanitized to prevent loop-priming.
|
|
449
|
+
*/
|
|
366
450
|
function extractExactRecallFactText(text, token) {
|
|
367
451
|
const markerStart = text.indexOf(token);
|
|
368
452
|
if (markerStart < 0)
|
|
369
453
|
return text.trim();
|
|
370
454
|
const tail = text.slice(markerStart).trim();
|
|
371
455
|
const factSentence = tail.match(/^[\s\S]*?\bmeans\b[\s\S]*?[.!?](?:\s|$)/i)?.[0]?.trim();
|
|
372
|
-
|
|
456
|
+
const extracted = factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
|
|
457
|
+
return sanitizeToolCallPatterns(extracted);
|
|
373
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Escapes special characters in memory fact text for safe rendering.
|
|
461
|
+
*/
|
|
374
462
|
function escapeMemoryFactText(text) {
|
|
375
463
|
return text
|
|
376
464
|
.replaceAll("&", "&")
|
|
@@ -382,7 +470,42 @@ function escapeMemoryFactText(text) {
|
|
|
382
470
|
.replaceAll("\n", " ")
|
|
383
471
|
.replaceAll("\t", "	");
|
|
384
472
|
}
|
|
473
|
+
// Tool-call pattern detection for sanitization
|
|
474
|
+
const TOOL_CALL_BRACKET_RE = /\[tool:([^\]]+)\]/gi;
|
|
475
|
+
const TOOL_CALL_JSON_RE = /\{\s*"name"\s*:\s*"([^"]+)"[^}]*\}/g;
|
|
476
|
+
const TOOL_RESULT_ANNOTATION_RE = /\[tool:[^\]]+\](?:\s*[^{\[]*)?/g;
|
|
477
|
+
/**
|
|
478
|
+
* Sanitizes text that may contain tool-call syntax to prevent loop-priming.
|
|
479
|
+
* Replaces executable-looking patterns with neutral summaries rather than
|
|
480
|
+
* replaying them verbatim, so the model cannot pattern-match and repeat them.
|
|
481
|
+
*/
|
|
482
|
+
function sanitizeToolCallPatterns(text) {
|
|
483
|
+
let sanitized = text;
|
|
484
|
+
// Replace [tool:name] patterns with a neutral summary
|
|
485
|
+
sanitized = sanitized.replace(TOOL_CALL_BRACKET_RE, (_match, toolName) => {
|
|
486
|
+
return `[historical tool call: ${toolName}]`;
|
|
487
|
+
});
|
|
488
|
+
// Replace JSON tool-call objects with a neutral summary
|
|
489
|
+
sanitized = sanitized.replace(TOOL_CALL_JSON_RE, (_match, toolName) => {
|
|
490
|
+
return `[historical tool call: ${toolName}]`;
|
|
491
|
+
});
|
|
492
|
+
// Replace remaining tool-result annotations
|
|
493
|
+
sanitized = sanitized.replace(TOOL_RESULT_ANNOTATION_RE, "[historical tool call]");
|
|
494
|
+
// Detect and summarize repeated tool calls (loop indicator)
|
|
495
|
+
const toolCallCount = (sanitized.match(/\[historical tool call:\s*([^\]]+)\]/gi) || []).length;
|
|
496
|
+
if (toolCallCount > 2) {
|
|
497
|
+
const uniqueTools = new Set([...sanitized.matchAll(/\[historical tool call:\s*([^\]]+)\]/gi)].map((m) => m[1]));
|
|
498
|
+
if (uniqueTools.size === 1) {
|
|
499
|
+
// Single tool repeated multiple times — likely a loop, summarize aggressively
|
|
500
|
+
sanitized = `[Historical tool activity: repeated ${[...uniqueTools][0]} call ${toolCallCount} times. Do not repeat this pattern.]`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return sanitized;
|
|
504
|
+
}
|
|
385
505
|
const TRUNCATION_MARKER = "...[truncated]";
|
|
506
|
+
/**
|
|
507
|
+
* Attempts to truncate an item to fit within token budget.
|
|
508
|
+
*/
|
|
386
509
|
function tryTruncateItem(rawText, tag, attributes, maxTokenBudget) {
|
|
387
510
|
const tagOpen = attributes ? `<${tag}${attributes}>` : `<${tag}>`;
|
|
388
511
|
const tagClose = `</${tag}>`;
|
|
@@ -404,6 +527,9 @@ function tryTruncateItem(rawText, tag, attributes, maxTokenBudget) {
|
|
|
404
527
|
return null;
|
|
405
528
|
return `${tagOpen}${escaped}${TRUNCATION_MARKER}${tagClose}`;
|
|
406
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Builds a wrapped section adaptively injecting items within token budget.
|
|
532
|
+
*/
|
|
407
533
|
function adaptivelyBuildWrappedSection(wrapperOpen, instruction, wrapperClose, items, availableTokenBudget) {
|
|
408
534
|
if (items.length === 0 || availableTokenBudget <= 0)
|
|
409
535
|
return null;
|
|
@@ -439,20 +565,32 @@ function adaptivelyBuildWrappedSection(wrapperOpen, instruction, wrapperClose, i
|
|
|
439
565
|
const sectionTokens = approximateTokenCount(sectionText);
|
|
440
566
|
return { text: sectionText, tokens: sectionTokens, injectedCount };
|
|
441
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Builds a single item element with escaped text content.
|
|
570
|
+
*/
|
|
442
571
|
function buildItemElement(item) {
|
|
443
572
|
return item.attributes
|
|
444
573
|
? `<${item.tag}${item.attributes}>${escapeMemoryFactText(item.rawText)}</${item.tag}>`
|
|
445
574
|
: `<${item.tag}>${escapeMemoryFactText(item.rawText)}</${item.tag}>`;
|
|
446
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Appends a system prompt addition to existing content.
|
|
578
|
+
*/
|
|
447
579
|
function appendSystemPromptAddition(existing, addition) {
|
|
448
580
|
const trimmedExisting = existing.trim();
|
|
449
581
|
if (trimmedExisting.length === 0)
|
|
450
582
|
return addition;
|
|
451
583
|
return `${trimmedExisting}\n\n${addition}`;
|
|
452
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Checks if messages contain a replay-safe user turn with content.
|
|
587
|
+
*/
|
|
453
588
|
function hasReplaySafeUserTurn(messages) {
|
|
454
589
|
return messages.some((message) => message.role === "user" && normalizeKernelContent(message.content).trim().length > 0);
|
|
455
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Finds the last replay-safe user message from the array.
|
|
593
|
+
*/
|
|
456
594
|
function findLastReplaySafeUserMessage(messages) {
|
|
457
595
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
458
596
|
const candidate = messages[index];
|
|
@@ -469,6 +607,9 @@ function findLastReplaySafeUserMessage(messages) {
|
|
|
469
607
|
}
|
|
470
608
|
return null;
|
|
471
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* Truncates system prompt addition to fit within token budget.
|
|
612
|
+
*/
|
|
472
613
|
function truncateSystemPromptAdditionToTokenBudget(value, tokenBudget) {
|
|
473
614
|
if (tokenBudget <= 0)
|
|
474
615
|
return "";
|
|
@@ -477,6 +618,9 @@ function truncateSystemPromptAdditionToTokenBudget(value, tokenBudget) {
|
|
|
477
618
|
return value;
|
|
478
619
|
return value.slice(0, maxChars);
|
|
479
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Ensures assemble result has a replay-safe user turn, reinjecting if needed.
|
|
623
|
+
*/
|
|
480
624
|
function ensureReplaySafeUserTurn(assembled, sourceMessages, logger, tokenBudget) {
|
|
481
625
|
if (hasReplaySafeUserTurn(assembled.messages))
|
|
482
626
|
return assembled;
|
|
@@ -529,6 +673,9 @@ function ensureReplaySafeUserTurn(assembled, sourceMessages, logger, tokenBudget
|
|
|
529
673
|
estimatedTokens: baseEstimatedTokens + approximateMessageTokens(fallbackUser),
|
|
530
674
|
};
|
|
531
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Normalizes a compact result into the OpenClaw-compatible assemble result format.
|
|
678
|
+
*/
|
|
532
679
|
export function normalizeAssembleResult(result, sourceMessages) {
|
|
533
680
|
let systemPromptAddition = typeof result.systemPromptAddition === "string" ? result.systemPromptAddition : "";
|
|
534
681
|
const messages = [];
|
|
@@ -558,8 +705,9 @@ export function normalizeAssembleResult(result, sourceMessages) {
|
|
|
558
705
|
}
|
|
559
706
|
else {
|
|
560
707
|
if (content.trim().length > 0) {
|
|
708
|
+
const sanitizedContent = sanitizeToolCallPatterns(content);
|
|
561
709
|
const roleAttr = message.role ? ` role="${escapeMemoryFactText(message.role)}"` : "";
|
|
562
|
-
extractedMemoryItems.push(`<memory_item source="recalled"${roleAttr} provenance="durable_memory">${escapeMemoryFactText(
|
|
710
|
+
extractedMemoryItems.push(`<memory_item source="recalled"${roleAttr} provenance="durable_memory">${escapeMemoryFactText(sanitizedContent)}</memory_item>`);
|
|
563
711
|
}
|
|
564
712
|
}
|
|
565
713
|
}
|
|
@@ -576,6 +724,9 @@ export function normalizeAssembleResult(result, sourceMessages) {
|
|
|
576
724
|
...(result.debug != null ? { debug: result.debug } : {}),
|
|
577
725
|
};
|
|
578
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Builds the context engine factory with the given client getter.
|
|
729
|
+
*/
|
|
579
730
|
export function buildContextEngineFactory(runtime, cfg, logger = console) {
|
|
580
731
|
const predictiveContextCache = new Map();
|
|
581
732
|
const PREDICTIVE_CACHE_MAX_SIZE = 100;
|
package/dist/index.js
CHANGED
|
@@ -26571,7 +26571,8 @@ function normalizeCompactResult(response, options = {}) {
|
|
|
26571
26571
|
clustersDeclined: typeof response?.clustersDeclined === "number" ? response.clustersDeclined : void 0,
|
|
26572
26572
|
turnsRemoved: typeof response?.turnsRemoved === "number" ? response.turnsRemoved : void 0,
|
|
26573
26573
|
summaryMethod: typeof response?.summaryMethod === "string" && response.summaryMethod.length > 0 ? response.summaryMethod : void 0,
|
|
26574
|
-
meanConfidence: typeof response?.meanConfidence === "number" ? response.meanConfidence : void 0
|
|
26574
|
+
meanConfidence: typeof response?.meanConfidence === "number" ? response.meanConfidence : void 0,
|
|
26575
|
+
summaryText: typeof response?.summaryText === "string" && response.summaryText.length > 0 ? response.summaryText : void 0
|
|
26575
26576
|
};
|
|
26576
26577
|
return {
|
|
26577
26578
|
ok: true,
|
|
@@ -26580,6 +26581,7 @@ function normalizeCompactResult(response, options = {}) {
|
|
|
26580
26581
|
result: {
|
|
26581
26582
|
tokensBefore,
|
|
26582
26583
|
...details.summaryMethod ? { summary: details.summaryMethod } : {},
|
|
26584
|
+
...details.summaryText ? { summaryText: details.summaryText } : {},
|
|
26583
26585
|
details
|
|
26584
26586
|
}
|
|
26585
26587
|
};
|
|
@@ -26889,11 +26891,35 @@ function extractExactRecallFactText(text, token) {
|
|
|
26889
26891
|
if (markerStart < 0) return text.trim();
|
|
26890
26892
|
const tail = text.slice(markerStart).trim();
|
|
26891
26893
|
const factSentence = tail.match(/^[\s\S]*?\bmeans\b[\s\S]*?[.!?](?:\s|$)/i)?.[0]?.trim();
|
|
26892
|
-
|
|
26894
|
+
const extracted = factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
|
|
26895
|
+
return sanitizeToolCallPatterns(extracted);
|
|
26893
26896
|
}
|
|
26894
26897
|
function escapeMemoryFactText(text) {
|
|
26895
26898
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", "	");
|
|
26896
26899
|
}
|
|
26900
|
+
var TOOL_CALL_BRACKET_RE = /\[tool:([^\]]+)\]/gi;
|
|
26901
|
+
var TOOL_CALL_JSON_RE = /\{\s*"name"\s*:\s*"([^"]+)"[^}]*\}/g;
|
|
26902
|
+
var TOOL_RESULT_ANNOTATION_RE = /\[tool:[^\]]+\](?:\s*[^{\[]*)?/g;
|
|
26903
|
+
function sanitizeToolCallPatterns(text) {
|
|
26904
|
+
let sanitized = text;
|
|
26905
|
+
sanitized = sanitized.replace(TOOL_CALL_BRACKET_RE, (_match, toolName) => {
|
|
26906
|
+
return `[historical tool call: ${toolName}]`;
|
|
26907
|
+
});
|
|
26908
|
+
sanitized = sanitized.replace(TOOL_CALL_JSON_RE, (_match, toolName) => {
|
|
26909
|
+
return `[historical tool call: ${toolName}]`;
|
|
26910
|
+
});
|
|
26911
|
+
sanitized = sanitized.replace(TOOL_RESULT_ANNOTATION_RE, "[historical tool call]");
|
|
26912
|
+
const toolCallCount = (sanitized.match(/\[historical tool call:\s*([^\]]+)\]/gi) || []).length;
|
|
26913
|
+
if (toolCallCount > 2) {
|
|
26914
|
+
const uniqueTools = new Set(
|
|
26915
|
+
[...sanitized.matchAll(/\[historical tool call:\s*([^\]]+)\]/gi)].map((m) => m[1])
|
|
26916
|
+
);
|
|
26917
|
+
if (uniqueTools.size === 1) {
|
|
26918
|
+
sanitized = `[Historical tool activity: repeated ${[...uniqueTools][0]} call ${toolCallCount} times. Do not repeat this pattern.]`;
|
|
26919
|
+
}
|
|
26920
|
+
}
|
|
26921
|
+
return sanitized;
|
|
26922
|
+
}
|
|
26897
26923
|
var TRUNCATION_MARKER = "...[truncated]";
|
|
26898
26924
|
function tryTruncateItem(rawText, tag, attributes, maxTokenBudget) {
|
|
26899
26925
|
const tagOpen = attributes ? `<${tag}${attributes}>` : `<${tag}>`;
|
|
@@ -27073,8 +27099,9 @@ function normalizeAssembleResult(result, sourceMessages) {
|
|
|
27073
27099
|
});
|
|
27074
27100
|
} else {
|
|
27075
27101
|
if (content.trim().length > 0) {
|
|
27102
|
+
const sanitizedContent = sanitizeToolCallPatterns(content);
|
|
27076
27103
|
const roleAttr = message.role ? ` role="${escapeMemoryFactText(message.role)}"` : "";
|
|
27077
|
-
extractedMemoryItems.push(`<memory_item source="recalled"${roleAttr} provenance="durable_memory">${escapeMemoryFactText(
|
|
27104
|
+
extractedMemoryItems.push(`<memory_item source="recalled"${roleAttr} provenance="durable_memory">${escapeMemoryFactText(sanitizedContent)}</memory_item>`);
|
|
27078
27105
|
}
|
|
27079
27106
|
}
|
|
27080
27107
|
}
|
|
@@ -27619,7 +27646,7 @@ import path2 from "node:path";
|
|
|
27619
27646
|
var proxy_exports = {};
|
|
27620
27647
|
__reExport(proxy_exports, __toESM(require_cjs(), 1));
|
|
27621
27648
|
|
|
27622
|
-
// node_modules/.pnpm/@xdarkicex+libravdb-contracts@0.
|
|
27649
|
+
// node_modules/.pnpm/@xdarkicex+libravdb-contracts@2.0.8/node_modules/@xdarkicex/libravdb-contracts/gen/js/libravdb/ipc/v1/rpc_pb.js
|
|
27623
27650
|
var IngestMode;
|
|
27624
27651
|
(function(IngestMode2) {
|
|
27625
27652
|
IngestMode2[IngestMode2["REPLACE"] = 0] = "REPLACE";
|
|
@@ -28962,6 +28989,10 @@ var CompactSessionResponse = class _CompactSessionResponse extends proxy_exports
|
|
|
28962
28989
|
* @generated from field: double mean_confidence = 6;
|
|
28963
28990
|
*/
|
|
28964
28991
|
meanConfidence = 0;
|
|
28992
|
+
/**
|
|
28993
|
+
* @generated from field: string summary_text = 7;
|
|
28994
|
+
*/
|
|
28995
|
+
summaryText = "";
|
|
28965
28996
|
constructor(data) {
|
|
28966
28997
|
super();
|
|
28967
28998
|
proxy_exports.proto3.util.initPartial(data, this);
|
|
@@ -29010,6 +29041,13 @@ var CompactSessionResponse = class _CompactSessionResponse extends proxy_exports
|
|
|
29010
29041
|
kind: "scalar",
|
|
29011
29042
|
T: 1
|
|
29012
29043
|
/* ScalarType.DOUBLE */
|
|
29044
|
+
},
|
|
29045
|
+
{
|
|
29046
|
+
no: 7,
|
|
29047
|
+
name: "summary_text",
|
|
29048
|
+
kind: "scalar",
|
|
29049
|
+
T: 9
|
|
29050
|
+
/* ScalarType.STRING */
|
|
29013
29051
|
}
|
|
29014
29052
|
]);
|
|
29015
29053
|
static fromBinary(bytes, options) {
|
|
@@ -30513,6 +30551,13 @@ var CompactSessionRequest = class _CompactSessionRequest extends proxy_exports.M
|
|
|
30513
30551
|
* @generated from field: int32 current_token_count = 7;
|
|
30514
30552
|
*/
|
|
30515
30553
|
currentTokenCount = 0;
|
|
30554
|
+
/**
|
|
30555
|
+
* Model's context window size in tokens. Enables daemon-side 80% threshold
|
|
30556
|
+
* calculation when client doesn't send force=true.
|
|
30557
|
+
*
|
|
30558
|
+
* @generated from field: int32 context_window_size = 8;
|
|
30559
|
+
*/
|
|
30560
|
+
contextWindowSize = 0;
|
|
30516
30561
|
constructor(data) {
|
|
30517
30562
|
super();
|
|
30518
30563
|
proxy_exports.proto3.util.initPartial(data, this);
|
|
@@ -30568,6 +30613,13 @@ var CompactSessionRequest = class _CompactSessionRequest extends proxy_exports.M
|
|
|
30568
30613
|
kind: "scalar",
|
|
30569
30614
|
T: 5
|
|
30570
30615
|
/* ScalarType.INT32 */
|
|
30616
|
+
},
|
|
30617
|
+
{
|
|
30618
|
+
no: 8,
|
|
30619
|
+
name: "context_window_size",
|
|
30620
|
+
kind: "scalar",
|
|
30621
|
+
T: 5
|
|
30622
|
+
/* ScalarType.INT32 */
|
|
30571
30623
|
}
|
|
30572
30624
|
]);
|
|
30573
30625
|
static fromBinary(bytes, options) {
|
|
@@ -36312,7 +36364,7 @@ function createGrpcTransport(options) {
|
|
|
36312
36364
|
return createTransport(validateNodeTransportOptions(options));
|
|
36313
36365
|
}
|
|
36314
36366
|
|
|
36315
|
-
// node_modules/.pnpm/@xdarkicex+libravdb-contracts@0.
|
|
36367
|
+
// node_modules/.pnpm/@xdarkicex+libravdb-contracts@2.0.8/node_modules/@xdarkicex/libravdb-contracts/gen/js/libravdb/ipc/v1/rpc_connect.js
|
|
36316
36368
|
var LibravDB = {
|
|
36317
36369
|
typeName: "libravdb.ipc.v1.LibravDB",
|
|
36318
36370
|
methods: {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdarkicex/openclaw-memory-libravdb",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -79,6 +79,6 @@
|
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@connectrpc/connect": "^1.7.0",
|
|
81
81
|
"@connectrpc/connect-node": "^1.7.0",
|
|
82
|
-
"@xdarkicex/libravdb-contracts": "^0.
|
|
82
|
+
"@xdarkicex/libravdb-contracts": "^2.0.8"
|
|
83
83
|
}
|
|
84
84
|
}
|