@@ -2272,6 +2272,10 @@ function createProjectContext(opts) {
22722272 }
22732273 session . history = session . history . slice ( 0 , trimTo ) ;
22742274 session . messageUUIDs = session . messageUUIDs . slice ( 0 , targetIdx ) ;
2275+ // Reset digest checkpoint if it points past the trimmed history
2276+ if ( typeof session . _dmLastDigestedIndex === "number" && session . _dmLastDigestedIndex > trimTo ) {
2277+ session . _dmLastDigestedIndex = trimTo ;
2278+ }
22752279 }
22762280
22772281 var kept = session . messageUUIDs ;
@@ -3983,12 +3987,19 @@ function createProjectContext(opts) {
39833987 } catch ( e ) { }
39843988
39853989 // Combined gate + digest in one prompt (saves a full round-trip vs separate gate)
3986- var prompt = [
3990+ var promptParts = [
39873991 "[SYSTEM: Memory Gate + Digest]" ,
39883992 "You are a memory system for an AI Mate (role: " + ( mateRole || "assistant" ) + ")." ,
39893993 "" ,
3990- "Conversation (" + job . type + "):" ,
3991- job . conversationContent ,
3994+ ] ;
3995+ if ( job . priorSummary ) {
3996+ promptParts . push ( "Prior conversation context (memory summary so far):" ) ;
3997+ promptParts . push ( job . priorSummary ) ;
3998+ promptParts . push ( "" ) ;
3999+ }
4000+ promptParts . push ( "Conversation (" + job . type + "):" ) ;
4001+ promptParts . push ( job . conversationContent ) ;
4002+ var prompt = promptParts . concat ( [
39924003 "" ,
39934004 "STEP 1: Should this be saved to memory?" ,
39944005 'Answer "no" ONLY if the entire conversation is trivial (e.g. just "hi"/"hello").' ,
@@ -4034,7 +4045,7 @@ function createProjectContext(opts) {
40344045
40354046 if ( digestObj && digestObj . skip ) {
40364047 console . log ( "[digest-worker] Gate declined for " + job . mateId ) ;
4037- if ( job . onDone ) job . onDone ( ) ;
4048+ if ( job . onDone ) job . onDone ( null ) ;
40384049 processDigestQueue ( ) ;
40394050 return ;
40404051 }
@@ -4072,9 +4083,14 @@ function createProjectContext(opts) {
40724083 }
40734084 }
40744085
4075- updateMemorySummary ( job . mateCtx , job . mateId , digestObj ) ;
4076- maybeSynthesizeUserProfile ( job . mateCtx , job . mateId ) ;
4077- if ( job . onDone ) job . onDone ( ) ;
4086+ // Skip summary update for failed parses — the fallback digest would degrade quality
4087+ if ( digestObj . topic !== "parse_failed" ) {
4088+ updateMemorySummary ( job . mateCtx , job . mateId , digestObj ) ;
4089+ maybeSynthesizeUserProfile ( job . mateCtx , job . mateId ) ;
4090+ if ( job . onDone ) job . onDone ( null ) ;
4091+ } else {
4092+ if ( job . onDone ) job . onDone ( new Error ( "parse_failed" ) ) ;
4093+ }
40784094 processDigestQueue ( ) ;
40794095 }
40804096
@@ -4096,7 +4112,7 @@ function createProjectContext(opts) {
40964112 console . error ( "[digest-worker] Error:" , err ) ;
40974113 _digestWorker = null ;
40984114 _digestWorkerTurns = 0 ;
4099- if ( job . onDone ) job . onDone ( ) ;
4115+ if ( job . onDone ) job . onDone ( err ) ;
41004116 processDigestQueue ( ) ;
41014117 } ,
41024118 } ) ;
@@ -4112,11 +4128,11 @@ function createProjectContext(opts) {
41124128 onError : function ( err ) {
41134129 console . error ( "[digest-worker] Create error:" , err ) ;
41144130 _digestWorker = null ;
4115- if ( job . onDone ) job . onDone ( ) ;
4131+ if ( job . onDone ) job . onDone ( err ) ;
41164132 processDigestQueue ( ) ;
41174133 } ,
4118- } ) . then ( function ( ws ) { _digestWorker = ws ; _digestWorkerTurns = 1 ; } ) . catch ( function ( ) {
4119- if ( job . onDone ) job . onDone ( ) ;
4134+ } ) . then ( function ( ws ) { _digestWorker = ws ; _digestWorkerTurns = 1 ; } ) . catch ( function ( err ) {
4135+ if ( job . onDone ) job . onDone ( err || new Error ( "digest worker creation failed" ) ) ;
41204136 processDigestQueue ( ) ;
41214137 } ) ;
41224138 }
@@ -4153,19 +4169,37 @@ function createProjectContext(opts) {
41534169 } ) ;
41544170 }
41554171
4156- // Digest DM turn for mate projects - uses shared digest worker
4172+ // Digest DM turn for mate projects - uses shared digest worker.
4173+ // Delta-based: only collects new turns since the last successful digest.
4174+ // Concurrency debounce: turns that arrive while a digest is in-flight
4175+ // are naturally batched into the next flush.
41574176 var _dmDigestPending = false ;
41584177 function digestDmTurn ( session , responsePreview ) {
41594178 if ( ! isMate || _dmDigestPending ) return ;
41604179 var mateId = path . basename ( cwd ) ;
41614180 var mateCtx = matesModule . buildMateCtx ( projectOwnerId ) ;
41624181 if ( ! matesModule . isMate ( mateCtx , mateId ) ) return ;
41634182
4164- // Collect full conversation from session history (all user + mate turns)
4183+ // Track digest index per session so switching sessions doesn't misalign.
4184+ // On resumed sessions (after restart), recover index from the last
4185+ // digest_checkpoint entry in history so undigested turns aren't lost.
4186+ if ( typeof session . _dmLastDigestedIndex !== "number" ) {
4187+ session . _dmLastDigestedIndex = 0 ;
4188+ for ( var ci = session . history . length - 1 ; ci >= 0 ; ci -- ) {
4189+ if ( session . history [ ci ] . type === "digest_checkpoint" ) {
4190+ session . _dmLastDigestedIndex = session . history [ ci ] . digestedIndex ;
4191+ break ;
4192+ }
4193+ }
4194+ }
4195+
4196+ // Collect only new turns since the last successful digest
41654197 var conversationParts = [ ] ;
41664198 var totalLen = 0 ;
41674199 var CONV_CAP = 6000 ;
4168- for ( var hi = 0 ; hi < session . history . length ; hi ++ ) {
4200+ var startIndex = session . _dmLastDigestedIndex ;
4201+ for ( var hi = startIndex ; hi < session . history . length ; hi ++ ) {
4202+ if ( totalLen >= CONV_CAP ) break ;
41694203 var entry = session . history [ hi ] ;
41704204 if ( entry . type === "user_message" && entry . text ) {
41714205 var uText = entry . text ;
@@ -4182,8 +4216,8 @@ function createProjectContext(opts) {
41824216 conversationParts . push ( "Mate: " + aText ) ;
41834217 totalLen += aText . length ;
41844218 }
4185- if ( totalLen >= CONV_CAP ) break ;
41864219 }
4220+ // If the latest response hasn't landed in history yet, append it
41874221 var lastResponseText = responsePreview || "" ;
41884222 if ( lastResponseText && conversationParts . length > 0 ) {
41894223 var lastPart = conversationParts [ conversationParts . length - 1 ] ;
@@ -4209,14 +4243,33 @@ function createProjectContext(opts) {
42094243 } ) ;
42104244 }
42114245
4246+ // Read existing summary to give the digest worker context for delta content
4247+ var priorSummary = "" ;
4248+ try {
4249+ if ( fs . existsSync ( summaryFile ) ) {
4250+ priorSummary = fs . readFileSync ( summaryFile , "utf8" ) . trim ( ) ;
4251+ }
4252+ } catch ( e ) { }
4253+
42124254 _dmDigestPending = true ;
4255+ var snapshotIndex = session . history . length ;
42134256
42144257 enqueueDigest ( {
42154258 mateCtx : mateCtx ,
42164259 mateId : mateId ,
42174260 type : "dm" ,
4261+ priorSummary : priorSummary || "" ,
42184262 conversationContent : conversationParts . join ( "\n" ) ,
4219- onDone : function ( ) { _dmDigestPending = false ; } ,
4263+ onDone : function ( err ) {
4264+ if ( ! err ) {
4265+ session . _dmLastDigestedIndex = snapshotIndex ;
4266+ // Persist checkpoint so resumed sessions know where to continue
4267+ var checkpoint = { type : "digest_checkpoint" , digestedIndex : snapshotIndex } ;
4268+ session . history . push ( checkpoint ) ;
4269+ sm . appendToSessionFile ( session , checkpoint ) ;
4270+ }
4271+ _dmDigestPending = false ;
4272+ } ,
42204273 } ) ;
42214274 }
42224275
0 commit comments