@@ -2729,7 +2729,10 @@ const memoryLanceDBProPlugin = {
27292729 : [ ] ;
27302730 const previousSeenCount = autoCaptureSeenTextCount . get ( sessionKey ) ?? 0 ;
27312731 // [Fix #2] Cumulative counting: accumulate across events, not per-event overwrite
2732- const currentCumulativeCount = previousSeenCount + eligibleTexts . length ;
2732+ // [Fix-Must2] Count only texts that are genuinely NEW to this event (newTexts),
2733+ // not the full eligibleTexts. This prevents double-counting when agent_end
2734+ // delivers full history: eligibleTexts = full history, but newTexts = only new ones.
2735+ // Computed AFTER newTexts is determined to avoid TDZ.
27332736 let newTexts = eligibleTexts ;
27342737 if ( pendingIngressTexts . length > 0 ) {
27352738 // [Fix #3] Use pendingIngressTexts as-is (REPLACE, not APPEND).
@@ -2738,12 +2741,18 @@ const memoryLanceDBProPlugin = {
27382741 // event-scoped; (3) APPEND causes deduplication issues when the same text
27392742 // appears in both pendingIngressTexts and eligibleTexts (after prefix stripping).
27402743 newTexts = pendingIngressTexts ;
2741- if ( conversationKey ) {
2742- autoCapturePendingIngressTexts . delete ( conversationKey ) ; // [Fix #8] Clear consumed pending texts to prevent re-consumption
2743- }
2744+ // [Fix #8] Clear consumed pending texts to prevent re-consumption
2745+ // [Fix-Must5] conversationKey MUST be valid here — if it's falsy, something is wrong upstream.
2746+ if ( ! conversationKey ) throw new Error ( "autoCapturePendingIngressTexts consumed with falsy conversationKey" ) ;
2747+ autoCapturePendingIngressTexts . delete ( conversationKey ) ;
27442748 } else if ( previousSeenCount > 0 && eligibleTexts . length > previousSeenCount ) {
27452749 newTexts = eligibleTexts . slice ( previousSeenCount ) ;
27462750 }
2751+ // [Fix-Must2] Count only texts new to this event.
2752+ // newTexts.length >= previousSeenCount always (dedup ensures no text counted twice).
2753+ // The increment is therefore newTexts.length - previousSeenCount.
2754+ const newTextsCount = Math . max ( 0 , newTexts . length - previousSeenCount ) ;
2755+ const currentCumulativeCount = previousSeenCount + newTextsCount ;
27472756 autoCaptureSeenTextCount . set ( sessionKey , currentCumulativeCount ) ;
27482757 pruneMapIfOver ( autoCaptureSeenTextCount , AUTO_CAPTURE_MAP_MAX_ENTRIES ) ;
27492758
@@ -2861,13 +2870,14 @@ const memoryLanceDBProPlugin = {
28612870 return ; // Do not fall through to regex fallback when smart extraction is configured
28622871 }
28632872 extractionRateLimiter . recordExtraction ( ) ;
2873+ // [Fix-Must1] Always reset counter after any extraction attempt (not just on created/merged).
2874+ // This prevents the retry spiral when all candidates are deduplicated (created=0, merged=0):
2875+ // without reset, counter stays high -> next agent_end re-triggers -> same dedupe -> infinite loop.
2876+ autoCaptureSeenTextCount . set ( sessionKey , 0 ) ;
28642877 if ( stats . created > 0 || stats . merged > 0 ) {
28652878 api . logger . info (
28662879 `memory-lancedb-pro: smart-extracted ${ stats . created } created, ${ stats . merged } merged, ${ stats . skipped } skipped for agent ${ agentId } `
28672880 ) ;
2868- // [Fix #9] Reset counter only on successful extraction.
2869- // Counter is NOT reset on failure — the same window will re-accumulate toward the next trigger.
2870- autoCaptureSeenTextCount . set ( sessionKey , 0 ) ;
28712881 return ; // Smart extraction handled everything
28722882 }
28732883
0 commit comments