@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.
@@ -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;
@@ -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
- return factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
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("&", "&amp;")
@@ -382,7 +470,42 @@ function escapeMemoryFactText(text) {
382
470
  .replaceAll("\n", "&#10;")
383
471
  .replaceAll("\t", "&#9;");
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(content)}</memory_item>`);
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
- return factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("\r", "&#13;").replaceAll("\n", "&#10;").replaceAll(" ", "&#9;");
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(content)}</memory_item>`);
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.3.0/node_modules/@xdarkicex/libravdb-contracts/gen/js/libravdb/ipc/v1/rpc_pb.js
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.3.0/node_modules/@xdarkicex/libravdb-contracts/gen/js/libravdb/ipc/v1/rpc_connect.js
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: {
@@ -2,7 +2,7 @@
2
2
  "id": "libravdb-memory",
3
3
  "name": "LibraVDB Memory",
4
4
  "description": "Persistent vector memory with three-tier hybrid scoring",
5
- "version": "1.6.21",
5
+ "version": "1.6.23",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.6.21",
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.3.0"
82
+ "@xdarkicex/libravdb-contracts": "^2.0.8"
83
83
  }
84
84
  }