@xdarkicex/openclaw-memory-libravdb 1.6.22 → 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.
@@ -445,6 +445,7 @@ function rankExactRecallCandidate(result, token) {
445
445
  }
446
446
  /**
447
447
  * Extracts the exact recall fact text starting at the token marker.
448
+ * Tool-call patterns are sanitized to prevent loop-priming.
448
449
  */
449
450
  function extractExactRecallFactText(text, token) {
450
451
  const markerStart = text.indexOf(token);
@@ -452,7 +453,8 @@ function extractExactRecallFactText(text, token) {
452
453
  return text.trim();
453
454
  const tail = text.slice(markerStart).trim();
454
455
  const factSentence = tail.match(/^[\s\S]*?\bmeans\b[\s\S]*?[.!?](?:\s|$)/i)?.[0]?.trim();
455
- return factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
456
+ const extracted = factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
457
+ return sanitizeToolCallPatterns(extracted);
456
458
  }
457
459
  /**
458
460
  * Escapes special characters in memory fact text for safe rendering.
@@ -468,6 +470,38 @@ function escapeMemoryFactText(text) {
468
470
  .replaceAll("\n", "
")
469
471
  .replaceAll("\t", "	");
470
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
+ }
471
505
  const TRUNCATION_MARKER = "...[truncated]";
472
506
  /**
473
507
  * Attempts to truncate an item to fit within token budget.
@@ -671,8 +705,9 @@ export function normalizeAssembleResult(result, sourceMessages) {
671
705
  }
672
706
  else {
673
707
  if (content.trim().length > 0) {
708
+ const sanitizedContent = sanitizeToolCallPatterns(content);
674
709
  const roleAttr = message.role ? ` role="${escapeMemoryFactText(message.role)}"` : "";
675
- 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>`);
676
711
  }
677
712
  }
678
713
  }
package/dist/index.js CHANGED
@@ -26891,11 +26891,35 @@ function extractExactRecallFactText(text, token) {
26891
26891
  if (markerStart < 0) return text.trim();
26892
26892
  const tail = text.slice(markerStart).trim();
26893
26893
  const factSentence = tail.match(/^[\s\S]*?\bmeans\b[\s\S]*?[.!?](?:\s|$)/i)?.[0]?.trim();
26894
- return factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
26894
+ const extracted = factSentence ?? tail.split("\n")[0]?.trim() ?? tail;
26895
+ return sanitizeToolCallPatterns(extracted);
26895
26896
  }
26896
26897
  function escapeMemoryFactText(text) {
26897
26898
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("\r", "&#13;").replaceAll("\n", "&#10;").replaceAll(" ", "&#9;");
26898
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
+ }
26899
26923
  var TRUNCATION_MARKER = "...[truncated]";
26900
26924
  function tryTruncateItem(rawText, tag, attributes, maxTokenBudget) {
26901
26925
  const tagOpen = attributes ? `<${tag}${attributes}>` : `<${tag}>`;
@@ -27075,8 +27099,9 @@ function normalizeAssembleResult(result, sourceMessages) {
27075
27099
  });
27076
27100
  } else {
27077
27101
  if (content.trim().length > 0) {
27102
+ const sanitizedContent = sanitizeToolCallPatterns(content);
27078
27103
  const roleAttr = message.role ? ` role="${escapeMemoryFactText(message.role)}"` : "";
27079
- 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>`);
27080
27105
  }
27081
27106
  }
27082
27107
  }
@@ -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.22",
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.22",
3
+ "version": "1.6.23",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",