@@ -99,6 +99,85 @@ function prependPreviousSummaryForRedistill(params: {
9999 return [ buildPreviousSummaryMessage ( previousSummary ) , ...params . messages ] ;
100100}
101101
102+ type SessionBranchEntry = {
103+ type ?: unknown ;
104+ message ?: unknown ;
105+ customType ?: unknown ;
106+ content ?: unknown ;
107+ display ?: unknown ;
108+ details ?: unknown ;
109+ timestamp ?: unknown ;
110+ summary ?: unknown ;
111+ fromId ?: unknown ;
112+ } ;
113+
114+ function coerceTimestamp ( value : unknown ) : number {
115+ if ( typeof value === "number" && Number . isFinite ( value ) ) {
116+ return value ;
117+ }
118+ if ( typeof value === "string" ) {
119+ const parsed = Date . parse ( value ) ;
120+ if ( Number . isFinite ( parsed ) ) {
121+ return parsed ;
122+ }
123+ }
124+ return 0 ;
125+ }
126+
127+ function sessionBranchEntryToMessage ( entry : SessionBranchEntry ) : AgentMessage | undefined {
128+ if ( entry . type === "message" && entry . message && typeof entry . message === "object" ) {
129+ return entry . message as AgentMessage ;
130+ }
131+ if ( entry . type === "custom_message" ) {
132+ return {
133+ role : "custom" ,
134+ customType : typeof entry . customType === "string" ? entry . customType : "custom" ,
135+ content : entry . content ,
136+ display : entry . display !== false ,
137+ details : entry . details ,
138+ timestamp : coerceTimestamp ( entry . timestamp ) ,
139+ } as AgentMessage ;
140+ }
141+ if ( entry . type === "branch_summary" ) {
142+ return {
143+ role : "branchSummary" ,
144+ summary : typeof entry . summary === "string" ? entry . summary : "" ,
145+ fromId : typeof entry . fromId === "string" ? entry . fromId : "root" ,
146+ timestamp : coerceTimestamp ( entry . timestamp ) ,
147+ } as AgentMessage ;
148+ }
149+ return undefined ;
150+ }
151+
152+ function collectSessionBranchMessages ( sessionManager : unknown ) : AgentMessage [ ] {
153+ const getBranch = ( sessionManager as { getBranch ?: unknown } ) ?. getBranch ;
154+ if ( typeof getBranch !== "function" ) {
155+ return [ ] ;
156+ }
157+ let entries : unknown ;
158+ try {
159+ entries = getBranch . call ( sessionManager ) ;
160+ } catch {
161+ return [ ] ;
162+ }
163+ if ( ! Array . isArray ( entries ) ) {
164+ return [ ] ;
165+ }
166+ return entries
167+ . map ( ( entry ) =>
168+ entry && typeof entry === "object"
169+ ? sessionBranchEntryToMessage ( entry as SessionBranchEntry )
170+ : undefined ,
171+ )
172+ . filter ( ( message ) : message is AgentMessage => Boolean ( message ) ) ;
173+ }
174+
175+ function containsRealConversation ( messages : AgentMessage [ ] ) : boolean {
176+ return messages . some ( ( message , index , allMessages ) =>
177+ isRealConversationMessage ( message , allMessages , index ) ,
178+ ) ;
179+ }
180+
102181/**
103182 * Attempt provider-based summarization. Returns the summary string on success,
104183 * or `undefined` when the caller should fall back to built-in LLM summarization.
@@ -778,16 +857,26 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
778857 api . on ( "session_before_compact" , async ( event , ctx ) => {
779858 const { preparation, customInstructions : eventInstructions , signal } = event ;
780859 const rawTurnPrefixMessages = preparation . turnPrefixMessages ?? [ ] ;
781- const baseMessagesToSummarize = stripRuntimeContextCustomMessages (
860+ let baseMessagesToSummarize = stripRuntimeContextCustomMessages (
782861 preparation . messagesToSummarize ,
783862 ) ;
784- const baseTurnPrefixMessages = stripRuntimeContextCustomMessages ( rawTurnPrefixMessages ) ;
785- const hasRealSummarizable = baseMessagesToSummarize . some ( ( message , index , messages ) =>
786- isRealConversationMessage ( message , messages , index ) ,
787- ) ;
788- const hasRealTurnPrefix = baseTurnPrefixMessages . some ( ( message , index , messages ) =>
789- isRealConversationMessage ( message , messages , index ) ,
790- ) ;
863+ let baseTurnPrefixMessages = stripRuntimeContextCustomMessages ( rawTurnPrefixMessages ) ;
864+ let hasRealSummarizable = containsRealConversation ( baseMessagesToSummarize ) ;
865+ let hasRealTurnPrefix = containsRealConversation ( baseTurnPrefixMessages ) ;
866+ if ( ! hasRealSummarizable && ! hasRealTurnPrefix ) {
867+ const branchMessages = stripRuntimeContextCustomMessages (
868+ collectSessionBranchMessages ( ctx . sessionManager ) ,
869+ ) ;
870+ if ( containsRealConversation ( branchMessages ) ) {
871+ log . info (
872+ "Compaction safeguard: using session branch messages after compaction preparation omitted real conversation content." ,
873+ ) ;
874+ baseMessagesToSummarize = branchMessages ;
875+ baseTurnPrefixMessages = [ ] ;
876+ hasRealSummarizable = true ;
877+ hasRealTurnPrefix = false ;
878+ }
879+ }
791880 setCompactionSafeguardCancelReason ( ctx . sessionManager , undefined ) ;
792881 if ( ! hasRealSummarizable && ! hasRealTurnPrefix ) {
793882 // When there are no summarizable messages AND no real turn-prefix content,
0 commit comments