@openclaw/memory-lancedb 2026.5.31-beta.4 → 2026.6.1-beta.1
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/index.js +135 -53
- package/npm-shrinkwrap.json +2 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -89,6 +89,7 @@ const DEFAULT_TOOL_RECALL_TIMEOUT_MS = 15e3;
|
|
|
89
89
|
const DEFAULT_TOOL_RECALL_COOLDOWN_MS = 6e4;
|
|
90
90
|
const DEFAULT_AUTO_RECALL_OVERFETCH_LIMIT = 10;
|
|
91
91
|
const DEFAULT_AUTO_RECALL_RESULT_CAP = 3;
|
|
92
|
+
const DUPLICATE_SEARCH_LIMIT = 5;
|
|
92
93
|
function parsePositiveIntegerOption(value, flag) {
|
|
93
94
|
if (value === void 0) return;
|
|
94
95
|
const parsed = parseStrictPositiveInteger(value);
|
|
@@ -393,6 +394,9 @@ function sanitizeRecallMemoryText(text) {
|
|
|
393
394
|
if (!stripped.trim()) return null;
|
|
394
395
|
return looksLikeEnvelopeSludge(stripped) ? null : stripped;
|
|
395
396
|
}
|
|
397
|
+
async function findCleanDuplicateMemory(db, vector) {
|
|
398
|
+
return (await db.search(vector, DUPLICATE_SEARCH_LIMIT, .95)).find((result) => sanitizeRecallMemoryText(result.entry.text) !== null);
|
|
399
|
+
}
|
|
396
400
|
/**
|
|
397
401
|
* Explicit sentinel strings used by `sanitizeForMemoryCapture` to locate and
|
|
398
402
|
* surgically strip individual blocks. Canonical source:
|
|
@@ -419,10 +423,21 @@ const INBOUND_META_SENTINELS = [
|
|
|
419
423
|
"Nearby reply target window (untrusted, chronological, around replied-to message):",
|
|
420
424
|
"Chat history since last reply (untrusted, for context):"
|
|
421
425
|
];
|
|
426
|
+
const INBOUND_META_SENTINEL_LINE_RE = new RegExp(`^(?:${INBOUND_META_SENTINELS.map((sentinel) => sentinel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})[^\\n]*$`, "m");
|
|
422
427
|
const MESSAGE_TOOL_DELIVERY_HINTS = ["Delivery: to send a message, use the `message` tool.", "Delivery: Final assistant text is not automatically delivered in this run. Use the `message` tool to send user-visible output."];
|
|
423
428
|
const MESSAGE_TOOL_DELIVERY_HINT_RE = new RegExp(`^\\s*(?:${MESSAGE_TOOL_DELIVERY_HINTS.map((hint) => hint.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*$`, "m");
|
|
424
429
|
const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
425
430
|
const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
431
|
+
const HISTORY_CONTEXT_MARKERS = [
|
|
432
|
+
HISTORY_CONTEXT_MARKER,
|
|
433
|
+
"[Chat messages since your last reply — CONTEXT ONLY]",
|
|
434
|
+
"[Merged earlier messages — CONTEXT ONLY]"
|
|
435
|
+
];
|
|
436
|
+
const CURRENT_MESSAGE_MARKERS = [
|
|
437
|
+
CURRENT_MESSAGE_MARKER,
|
|
438
|
+
"[CURRENT MESSAGE — reply to this]",
|
|
439
|
+
"[CURRENT MESSAGE — reply using the context above]"
|
|
440
|
+
];
|
|
426
441
|
const ACTIVE_TURN_RECOVERY_RE = /active-turn-recovery/i;
|
|
427
442
|
/**
|
|
428
443
|
* Line-anchored pattern matching any inbound-meta block header injected by
|
|
@@ -436,15 +451,17 @@ const ACTIVE_TURN_RECOVERY_RE = /active-turn-recovery/i;
|
|
|
436
451
|
* so requiring `):` to terminate the line catches every real injection while
|
|
437
452
|
* sidestepping the false-positive risk.
|
|
438
453
|
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
454
|
+
* The producer does not truncate custom structured-context labels, so the
|
|
455
|
+
* label segment is newline-bound rather than length-bound. The expression uses
|
|
456
|
+
* only linear character classes; avoid nested wildcards here.
|
|
441
457
|
*/
|
|
442
|
-
const INBOUND_META_LABEL_RE = /^[^\n]
|
|
443
|
-
const INBOUND_META_LABEL_JSON_BLOCK_RE = /^[^\n]
|
|
458
|
+
const INBOUND_META_LABEL_RE = /^[^\n]+\((?:untrusted metadata|untrusted, for context|untrusted, nearest first|untrusted, chronological,[^\n)]{1,80})\):[ \t]*$/m;
|
|
459
|
+
const INBOUND_META_LABEL_JSON_BLOCK_RE = /^[^\n]+\((?:untrusted metadata|untrusted, for context|untrusted, nearest first|untrusted, chronological,[^\n)]{1,80})\):[ \t]*\n[ \t]*```json[ \t]*\n[\s\S]*?\n[ \t]*```[ \t]*\n?/gm;
|
|
444
460
|
const LEADING_CHRONOLOGICAL_CONTEXT_LABEL_RE = /^\s*[^\n]{1,100}\(untrusted, chronological,[^\n)]{1,80}\):[ \t]*(?:\n|$)/;
|
|
445
|
-
const BRACKETED_LINE_PREFIX_RE = /^\[[^\]\n]{1,500}\]\s/gm;
|
|
446
461
|
const BRACKETED_PREFIX_RE = /\[[^\]\n]{1,500}\]\s/g;
|
|
447
462
|
const LEADING_CURRENT_MESSAGE_CONTEXT_RE = /^\s*Current message:[ \t]*(?:\n|$)/;
|
|
463
|
+
const LEADING_CURRENT_MESSAGE_REPLY_LINE_RE = /^\s*\[Replying to:[^\n]{0,1000}\]\s*\n/;
|
|
464
|
+
const LEADING_CURRENT_MESSAGE_ID_SENDER_RE = /^#\d+\s+[^\n:]{1,100}:\s*/;
|
|
448
465
|
const UNTRUSTED_CONTEXT_HEADER_RE = /^Untrusted context \(metadata/m;
|
|
449
466
|
/**
|
|
450
467
|
* Matches JSON blobs that look like OpenClaw transport envelope metadata.
|
|
@@ -484,12 +501,11 @@ const ENVELOPE_JSON_LINE_RE = /^\s*\{\s*(?:\n\s*)?"(?:chat_id|message_id|reply_t
|
|
|
484
501
|
*/
|
|
485
502
|
const INBOUND_ENVELOPE_PREFIX_RE = /^\[([^\]\n]{0,300}?(?:\s\+(?:\d+[smhdwy]|just now)\b|\s[A-Za-z]{3}\s\d{4}-\d{2}-\d{2})[^\]\n]{0,200})\]\s/;
|
|
486
503
|
/**
|
|
487
|
-
* Marker-free leading envelope header
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* channel-id anchor.
|
|
504
|
+
* Marker-free leading envelope header. The elapsed/date marker regex above
|
|
505
|
+
* misses envelopes where `formatAgentEnvelope` drops every optional marker.
|
|
506
|
+
* Because channel labels can also be ordinary words, callers only accept this
|
|
507
|
+
* match after `matchKnownChannelMarkerFreeEnvelopePrefix` finds a stronger
|
|
508
|
+
* group/thread or body-sender signal.
|
|
493
509
|
*
|
|
494
510
|
* Anchoring on a known bundled/official channel prefix from
|
|
495
511
|
* `BUNDLED_CHAT_CHANNEL_ENVELOPE_PREFIXES` keeps the detector and formatter in
|
|
@@ -523,25 +539,35 @@ const INBOUND_ENVELOPE_KNOWN_CHANNEL_PREFIX_RE = ENVELOPE_KNOWN_CHANNEL_PATTERN
|
|
|
523
539
|
* regex bounded and matches realistic display names).
|
|
524
540
|
*/
|
|
525
541
|
const ENVELOPE_BODY_SENDER_PREFIX_RE = /^([^\n:]{1,120}):\s/;
|
|
542
|
+
const ENVELOPE_BODY_DIRECT_PREFIX = "(sender)";
|
|
526
543
|
const ENVELOPE_BODY_SELF_PREFIX = "(self)";
|
|
527
544
|
const SENDER_PREFIXED_ENVELOPE_CHANNEL_RE = /^(?:discord|imessage|line|mattermost|qqbot|signal|slack|telegram|whatsapp)(?:\s|$)/i;
|
|
528
545
|
const NON_DIRECT_ENVELOPE_HEADER_RE = /(?:^|\s)(?:#[^\s]+|group:[^\s]+|group\s+id:[^\s]+|room:[^\s]+|channel\s+id:[^\s]+|id:-[^\s]+|unknown-group|[^\s]+@g\.us)(?:\s|$)/i;
|
|
529
546
|
const USER_AUTHORED_BODY_LABEL_RE = /^(?:action|decision|fixme|note|question|reminder|todo)$/i;
|
|
547
|
+
function matchKnownChannelMarkerFreeEnvelopePrefix(text, options) {
|
|
548
|
+
const match = INBOUND_ENVELOPE_KNOWN_CHANNEL_PREFIX_RE?.exec(text);
|
|
549
|
+
if (!match) return null;
|
|
550
|
+
const headerInside = match[1] ?? "";
|
|
551
|
+
if (NON_DIRECT_ENVELOPE_HEADER_RE.test(headerInside)) return match;
|
|
552
|
+
const body = text.slice(match[0].length);
|
|
553
|
+
if (stripEnvelopeBodySenderPrefix(body, headerInside) !== body) return match;
|
|
554
|
+
return options?.allowAmbiguousDirect ? match : null;
|
|
555
|
+
}
|
|
530
556
|
/**
|
|
531
557
|
* Returns true if `text` looks like it contains OpenClaw-injected envelope or
|
|
532
558
|
* transport metadata that should never be persisted as a long-term memory.
|
|
533
559
|
*/
|
|
534
560
|
function looksLikeEnvelopeSludge(text) {
|
|
535
561
|
if (!text) return false;
|
|
536
|
-
if (INBOUND_META_LABEL_RE.test(text)) return true;
|
|
562
|
+
if (INBOUND_META_SENTINEL_LINE_RE.test(text) || INBOUND_META_LABEL_RE.test(text)) return true;
|
|
537
563
|
if (UNTRUSTED_CONTEXT_HEADER_RE.test(text)) return true;
|
|
538
564
|
if (MESSAGE_TOOL_DELIVERY_HINT_RE.test(text)) return true;
|
|
539
|
-
if (text.includes(
|
|
565
|
+
if (HISTORY_CONTEXT_MARKERS.some((marker) => text.includes(marker)) || CURRENT_MESSAGE_MARKERS.some((marker) => text.includes(marker))) return true;
|
|
540
566
|
if (ACTIVE_TURN_RECOVERY_RE.test(text)) return true;
|
|
541
567
|
if (MEDIA_ATTACHED_PATTERN_TEST.test(text)) return true;
|
|
542
568
|
if (ENVELOPE_JSON_LINE_RE.test(text)) return true;
|
|
543
569
|
if (INBOUND_ENVELOPE_PREFIX_RE.test(text)) return true;
|
|
544
|
-
if (
|
|
570
|
+
if (matchKnownChannelMarkerFreeEnvelopePrefix(text)) return true;
|
|
545
571
|
return false;
|
|
546
572
|
}
|
|
547
573
|
/**
|
|
@@ -567,9 +593,9 @@ function stripEnvelopeBodySenderPrefix(body, headerInside) {
|
|
|
567
593
|
const match = body.match(ENVELOPE_BODY_SENDER_PREFIX_RE);
|
|
568
594
|
if (!match) return body;
|
|
569
595
|
const label = match[1];
|
|
570
|
-
if (label === ENVELOPE_BODY_SELF_PREFIX) return body.slice(match[0].length);
|
|
596
|
+
if (label === ENVELOPE_BODY_SELF_PREFIX || label === ENVELOPE_BODY_DIRECT_PREFIX) return body.slice(match[0].length);
|
|
571
597
|
if (SENDER_PREFIXED_ENVELOPE_CHANNEL_RE.test(headerInside) && NON_DIRECT_ENVELOPE_HEADER_RE.test(headerInside) && !USER_AUTHORED_BODY_LABEL_RE.test(label)) return body.slice(match[0].length);
|
|
572
|
-
if (headerInside.split(/\s+/).includes(label)) return body.slice(match[0].length);
|
|
598
|
+
if (headerInside.split(/\s+/).includes(label) || headerInside.includes(label)) return body.slice(match[0].length);
|
|
573
599
|
return body;
|
|
574
600
|
}
|
|
575
601
|
function stripLeadingMessageToolDeliveryHints(text) {
|
|
@@ -596,27 +622,50 @@ function findFirstInboundEnvelopeIndex(text, options) {
|
|
|
596
622
|
if (text.slice(lineStart, index).includes("[Replying to:")) continue;
|
|
597
623
|
}
|
|
598
624
|
const candidate = text.slice(index);
|
|
599
|
-
if (INBOUND_ENVELOPE_PREFIX_RE.test(candidate) ||
|
|
625
|
+
if (INBOUND_ENVELOPE_PREFIX_RE.test(candidate) || matchKnownChannelMarkerFreeEnvelopePrefix(candidate, { allowAmbiguousDirect: options?.allowAmbiguousMarkerFree })) return index;
|
|
600
626
|
}
|
|
601
627
|
return -1;
|
|
602
628
|
}
|
|
603
629
|
function stripPendingHistoryContextBeforeCurrentMessage(text) {
|
|
604
630
|
const candidateText = text.trimStart();
|
|
605
|
-
if (!candidateText.startsWith(
|
|
606
|
-
const
|
|
607
|
-
if (
|
|
608
|
-
return candidateText.slice(
|
|
631
|
+
if (!HISTORY_CONTEXT_MARKERS.some((marker) => candidateText.startsWith(marker))) return text;
|
|
632
|
+
const currentMarker = findLastContextMarker(candidateText, CURRENT_MESSAGE_MARKERS);
|
|
633
|
+
if (!currentMarker) return text;
|
|
634
|
+
return candidateText.slice(currentMarker.index + currentMarker.marker.length);
|
|
609
635
|
}
|
|
610
636
|
function stripToCurrentMessageMarker(text) {
|
|
611
|
-
const
|
|
612
|
-
if (
|
|
613
|
-
return text.slice(
|
|
637
|
+
const currentMarker = findLastContextMarker(text, CURRENT_MESSAGE_MARKERS);
|
|
638
|
+
if (!currentMarker) return null;
|
|
639
|
+
return text.slice(currentMarker.index + currentMarker.marker.length);
|
|
640
|
+
}
|
|
641
|
+
function findLastContextMarker(text, markers) {
|
|
642
|
+
let result = null;
|
|
643
|
+
for (const marker of markers) {
|
|
644
|
+
const index = text.lastIndexOf(marker);
|
|
645
|
+
if (index !== -1 && (!result || index > result.index)) result = {
|
|
646
|
+
index,
|
|
647
|
+
marker
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return result;
|
|
614
651
|
}
|
|
615
652
|
function stripLeadingCurrentMessageContextBeforeEnvelope(text) {
|
|
616
653
|
const candidateText = text.trimStart();
|
|
617
654
|
if (!LEADING_CURRENT_MESSAGE_CONTEXT_RE.test(candidateText)) return text;
|
|
618
|
-
const envelopeIndex = findFirstInboundEnvelopeIndex(candidateText, {
|
|
619
|
-
|
|
655
|
+
const envelopeIndex = findFirstInboundEnvelopeIndex(candidateText, {
|
|
656
|
+
allowAmbiguousMarkerFree: true,
|
|
657
|
+
skipReplyQuoteLine: true
|
|
658
|
+
});
|
|
659
|
+
if (envelopeIndex === -1) {
|
|
660
|
+
let plainBody = candidateText.replace(LEADING_CURRENT_MESSAGE_CONTEXT_RE, "").trimStart();
|
|
661
|
+
for (let pass = 0; pass < 4; pass += 1) {
|
|
662
|
+
const replyLineMatch = plainBody.match(LEADING_CURRENT_MESSAGE_REPLY_LINE_RE);
|
|
663
|
+
if (!replyLineMatch) break;
|
|
664
|
+
plainBody = plainBody.slice(replyLineMatch[0].length).trimStart();
|
|
665
|
+
}
|
|
666
|
+
const currentMessagePrefixMatch = plainBody.match(LEADING_CURRENT_MESSAGE_ID_SENDER_RE);
|
|
667
|
+
return currentMessagePrefixMatch ? plainBody.slice(currentMessagePrefixMatch[0].length) : text;
|
|
668
|
+
}
|
|
620
669
|
return candidateText.slice(envelopeIndex);
|
|
621
670
|
}
|
|
622
671
|
function stripLeadingPlainTextMetadataBody(text) {
|
|
@@ -626,21 +675,15 @@ function stripLeadingPlainTextMetadataBody(text) {
|
|
|
626
675
|
const currentMessageBody = stripLeadingCurrentMessageContextBeforeEnvelope(candidateText);
|
|
627
676
|
return currentMessageBody === candidateText ? "" : currentMessageBody;
|
|
628
677
|
}
|
|
629
|
-
function stripLeadingInboundEnvelope(text) {
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
|
|
678
|
+
function stripLeadingInboundEnvelope(text, options) {
|
|
679
|
+
const strippedCandidate = stripLeadingCurrentMessageContextBeforeEnvelope(stripPendingHistoryContextBeforeCurrentMessage(stripLeadingMessageToolDeliveryHints(text)));
|
|
680
|
+
const candidateText = strippedCandidate.trimStart();
|
|
681
|
+
const allowAmbiguousMarkerFree = options?.allowAmbiguousMarkerFree || strippedCandidate !== text;
|
|
682
|
+
const envelopePrefixMatch = candidateText.match(INBOUND_ENVELOPE_PREFIX_RE) ?? matchKnownChannelMarkerFreeEnvelopePrefix(candidateText, { allowAmbiguousDirect: allowAmbiguousMarkerFree });
|
|
683
|
+
if (!envelopePrefixMatch) return strippedCandidate === text ? text : candidateText;
|
|
633
684
|
const headerInside = envelopePrefixMatch[1] ?? "";
|
|
634
685
|
return stripEnvelopeBodySenderPrefix(candidateText.slice(envelopePrefixMatch[0].length), headerInside);
|
|
635
686
|
}
|
|
636
|
-
function findFirstInboundEnvelopeLineIndex(text) {
|
|
637
|
-
for (const match of text.matchAll(BRACKETED_LINE_PREFIX_RE)) {
|
|
638
|
-
const index = match.index;
|
|
639
|
-
const candidate = text.slice(index);
|
|
640
|
-
if (INBOUND_ENVELOPE_PREFIX_RE.test(candidate) || INBOUND_ENVELOPE_KNOWN_CHANNEL_PREFIX_RE?.test(candidate)) return index;
|
|
641
|
-
}
|
|
642
|
-
return -1;
|
|
643
|
-
}
|
|
644
687
|
function stripLeadingChronologicalContextBlocks(text) {
|
|
645
688
|
let cleaned = text;
|
|
646
689
|
let remainingPasses = INBOUND_META_SENTINELS.length;
|
|
@@ -649,8 +692,27 @@ function stripLeadingChronologicalContextBlocks(text) {
|
|
|
649
692
|
const match = cleaned.match(LEADING_CHRONOLOGICAL_CONTEXT_LABEL_RE);
|
|
650
693
|
if (!match) return cleaned;
|
|
651
694
|
const afterLabel = cleaned.slice(match[0].length);
|
|
652
|
-
const
|
|
653
|
-
|
|
695
|
+
const bodyStart = afterLabel.search(/\S/);
|
|
696
|
+
if (bodyStart === -1) return "";
|
|
697
|
+
const bodyLineEnd = afterLabel.indexOf("\n", bodyStart);
|
|
698
|
+
const firstBodyLine = bodyLineEnd === -1 ? afterLabel.slice(bodyStart) : afterLabel.slice(bodyStart, bodyLineEnd);
|
|
699
|
+
let lineEnvelopeIndex = firstBodyLine.trimStart().startsWith("[") ? findFirstInboundEnvelopeIndex(firstBodyLine, {
|
|
700
|
+
allowAmbiguousMarkerFree: true,
|
|
701
|
+
skipReplyQuoteLine: true
|
|
702
|
+
}) : -1;
|
|
703
|
+
if (lineEnvelopeIndex === -1 && match[0].includes("selected for current message")) {
|
|
704
|
+
const inlineEnvelopeIndex = findFirstInboundEnvelopeIndex(firstBodyLine, {
|
|
705
|
+
allowAmbiguousMarkerFree: true,
|
|
706
|
+
skipReplyQuoteLine: true
|
|
707
|
+
});
|
|
708
|
+
const prefix = inlineEnvelopeIndex === -1 ? "" : firstBodyLine.slice(0, inlineEnvelopeIndex);
|
|
709
|
+
lineEnvelopeIndex = /^#\d+\s/.test(prefix.trimStart()) ? inlineEnvelopeIndex : -1;
|
|
710
|
+
}
|
|
711
|
+
const envelopeIndex = lineEnvelopeIndex === -1 ? -1 : bodyStart + lineEnvelopeIndex;
|
|
712
|
+
if (envelopeIndex === -1) {
|
|
713
|
+
const separatorMatch = /\n[ \t]*\n/.exec(afterLabel);
|
|
714
|
+
cleaned = separatorMatch ? afterLabel.slice(separatorMatch.index + separatorMatch[0].length) : "";
|
|
715
|
+
} else cleaned = afterLabel.slice(envelopeIndex);
|
|
654
716
|
if (!cleaned) return "";
|
|
655
717
|
}
|
|
656
718
|
return cleaned;
|
|
@@ -664,14 +726,24 @@ function sanitizeForMemoryCapture(text) {
|
|
|
664
726
|
if (!text) return "";
|
|
665
727
|
const MAX_SANITIZE_CHARS = 1e4;
|
|
666
728
|
let cleaned = text.length > MAX_SANITIZE_CHARS ? text.slice(0, MAX_SANITIZE_CHARS) : text;
|
|
729
|
+
let strippedInjectedContext = false;
|
|
667
730
|
cleaned = cleaned.replace(LEADING_TIMESTAMP_PREFIX_RE, "");
|
|
668
|
-
|
|
731
|
+
const afterDeliveryHints = stripLeadingMessageToolDeliveryHints(cleaned);
|
|
732
|
+
strippedInjectedContext ||= afterDeliveryHints !== cleaned;
|
|
733
|
+
cleaned = afterDeliveryHints;
|
|
734
|
+
const afterJsonMetaBlocks = cleaned.replace(INBOUND_META_LABEL_JSON_BLOCK_RE, "");
|
|
735
|
+
strippedInjectedContext ||= afterJsonMetaBlocks !== cleaned;
|
|
736
|
+
cleaned = afterJsonMetaBlocks;
|
|
669
737
|
for (const sentinel of INBOUND_META_SENTINELS) {
|
|
670
738
|
const escapedSentinel = sentinel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
671
739
|
const blockRe = new RegExp(`${escapedSentinel}\\s*\\n\\s*\`\`\`json\\s*\\n[\\s\\S]*?\\n\\s*\`\`\`\\s*\\n?`, "g");
|
|
672
|
-
|
|
740
|
+
const afterSentinelBlock = cleaned.replace(blockRe, "");
|
|
741
|
+
strippedInjectedContext ||= afterSentinelBlock !== cleaned;
|
|
742
|
+
cleaned = afterSentinelBlock;
|
|
673
743
|
}
|
|
674
|
-
|
|
744
|
+
const afterChronologicalContext = stripLeadingChronologicalContextBlocks(cleaned);
|
|
745
|
+
strippedInjectedContext ||= afterChronologicalContext !== cleaned;
|
|
746
|
+
cleaned = afterChronologicalContext;
|
|
675
747
|
for (let pass = 0; pass < INBOUND_META_SENTINELS.length + 1; pass += 1) {
|
|
676
748
|
let earliestMetaIndex = -1;
|
|
677
749
|
let earliestMetaRe = null;
|
|
@@ -699,15 +771,25 @@ function sanitizeForMemoryCapture(text) {
|
|
|
699
771
|
const lineEnd = cleaned.indexOf("\n");
|
|
700
772
|
const afterHeader = lineEnd === -1 ? "" : cleaned.slice(lineEnd + 1);
|
|
701
773
|
if (!afterHeader.trimStart().startsWith("```json")) {
|
|
702
|
-
|
|
774
|
+
const afterPlainTextMetadata = stripLeadingPlainTextMetadataBody(afterHeader);
|
|
775
|
+
strippedInjectedContext ||= afterPlainTextMetadata !== cleaned;
|
|
776
|
+
cleaned = afterPlainTextMetadata;
|
|
703
777
|
continue;
|
|
704
778
|
}
|
|
705
779
|
}
|
|
706
|
-
|
|
780
|
+
const afterMetaHeader = cleaned.replace(earliestMetaRe, "");
|
|
781
|
+
strippedInjectedContext ||= afterMetaHeader !== cleaned;
|
|
782
|
+
cleaned = afterMetaHeader;
|
|
707
783
|
}
|
|
784
|
+
const afterActiveMemoryContext = cleaned.replace(/^Untrusted context \(metadata[^\n]*\n<active_memory_plugin>[\s\S]*?<\/active_memory_plugin>\s*/gm, "");
|
|
785
|
+
strippedInjectedContext ||= afterActiveMemoryContext !== cleaned;
|
|
786
|
+
cleaned = afterActiveMemoryContext;
|
|
708
787
|
const untrustedLineMatch = /^Untrusted context \(metadata/m.exec(cleaned);
|
|
709
|
-
if (untrustedLineMatch)
|
|
710
|
-
|
|
788
|
+
if (untrustedLineMatch) {
|
|
789
|
+
strippedInjectedContext = true;
|
|
790
|
+
cleaned = cleaned.slice(0, untrustedLineMatch.index);
|
|
791
|
+
}
|
|
792
|
+
cleaned = stripLeadingInboundEnvelope(cleaned, { allowAmbiguousMarkerFree: strippedInjectedContext });
|
|
711
793
|
cleaned = cleaned.replace(MEDIA_ATTACHED_PATTERN, "");
|
|
712
794
|
cleaned = cleaned.replace(/<active_memory_plugin>[\s\S]*?<\/active_memory_plugin>/g, "");
|
|
713
795
|
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").replace(/[ \t]{2,}/g, " ").trim();
|
|
@@ -926,16 +1008,16 @@ var memory_lancedb_default = definePluginEntry({
|
|
|
926
1008
|
}
|
|
927
1009
|
};
|
|
928
1010
|
const vector = await embeddings.embed(text);
|
|
929
|
-
const existing = await db
|
|
930
|
-
if (existing
|
|
1011
|
+
const existing = await findCleanDuplicateMemory(db, vector);
|
|
1012
|
+
if (existing) return {
|
|
931
1013
|
content: [{
|
|
932
1014
|
type: "text",
|
|
933
|
-
text: `Similar memory already exists: "${existing
|
|
1015
|
+
text: `Similar memory already exists: "${existing.entry.text}"`
|
|
934
1016
|
}],
|
|
935
1017
|
details: {
|
|
936
1018
|
action: "duplicate",
|
|
937
|
-
existingId: existing
|
|
938
|
-
existingText: existing
|
|
1019
|
+
existingId: existing.entry.id,
|
|
1020
|
+
existingText: existing.entry.text
|
|
939
1021
|
}
|
|
940
1022
|
};
|
|
941
1023
|
const entry = await db.store({
|
|
@@ -1154,7 +1236,7 @@ var memory_lancedb_default = definePluginEntry({
|
|
|
1154
1236
|
if (capturableSeen > 3) continue;
|
|
1155
1237
|
const category = detectCategory(sanitized);
|
|
1156
1238
|
const vector = await embeddings.embed(sanitized);
|
|
1157
|
-
if (
|
|
1239
|
+
if (await findCleanDuplicateMemory(db, vector)) continue;
|
|
1158
1240
|
await db.store({
|
|
1159
1241
|
text: sanitized,
|
|
1160
1242
|
vector,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/memory-lancedb",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.6.1-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/memory-lancedb",
|
|
9
|
-
"version": "2026.
|
|
9
|
+
"version": "2026.6.1-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@lancedb/lancedb": "0.30.0",
|
|
12
12
|
"apache-arrow": "21.1.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/memory-lancedb",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.6.1-beta.1",
|
|
4
4
|
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"install": {
|
|
24
24
|
"npmSpec": "@openclaw/memory-lancedb",
|
|
25
25
|
"defaultChoice": "npm",
|
|
26
|
-
"minHostVersion": ">=2026.
|
|
26
|
+
"minHostVersion": ">=2026.5.31"
|
|
27
27
|
},
|
|
28
28
|
"compat": {
|
|
29
|
-
"pluginApi": ">=2026.
|
|
29
|
+
"pluginApi": ">=2026.6.1-beta.1"
|
|
30
30
|
},
|
|
31
31
|
"build": {
|
|
32
|
-
"openclawVersion": "2026.
|
|
32
|
+
"openclawVersion": "2026.6.1-beta.1"
|
|
33
33
|
},
|
|
34
34
|
"release": {
|
|
35
35
|
"bundleRuntimeDependencies": false,
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"README.md"
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"openclaw": ">=2026.
|
|
50
|
+
"openclaw": ">=2026.6.1-beta.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"openclaw": {
|