@@ -370,59 +370,103 @@ export function getMessagesSinceLastSummary(messages: ApiMessage[]): ApiMessage[
370370 * Filters the API conversation history to get the "effective" messages to send to the API.
371371 * Messages with a condenseParent that points to an existing summary are filtered out,
372372 * as they have been replaced by that summary.
373+ * Messages with a truncationParent that points to an existing truncation marker are also filtered out,
374+ * as they have been hidden by sliding window truncation.
373375 *
374- * This allows non-destructive condensing where messages are tagged but not deleted,
375- * enabling accurate rewind operations while still sending condensed history to the API.
376+ * This allows non-destructive condensing and truncation where messages are tagged but not deleted,
377+ * enabling accurate rewind operations while still sending condensed/truncated history to the API.
376378 *
377379 * @param messages - The full API conversation history including tagged messages
378380 * @returns The filtered history that should be sent to the API
379381 */
380382export function getEffectiveApiHistory ( messages : ApiMessage [ ] ) : ApiMessage [ ] {
381383 // Collect all condenseIds of summaries that exist in the current history
382384 const existingSummaryIds = new Set < string > ( )
385+ // Collect all truncationIds of truncation markers that exist in the current history
386+ const existingTruncationIds = new Set < string > ( )
387+
383388 for ( const msg of messages ) {
384389 if ( msg . isSummary && msg . condenseId ) {
385390 existingSummaryIds . add ( msg . condenseId )
386391 }
392+ if ( msg . isTruncationMarker && msg . truncationId ) {
393+ existingTruncationIds . add ( msg . truncationId )
394+ }
387395 }
388396
389397 // Filter out messages whose condenseParent points to an existing summary
390- // Messages with orphaned condenseParent (summary was deleted) are included
398+ // or whose truncationParent points to an existing truncation marker.
399+ // Messages with orphaned parents (summary/marker was deleted) are included
391400 return messages . filter ( ( msg ) => {
392- if ( ! msg . condenseParent ) {
393- return true // No parent, always include
401+ // Filter out condensed messages if their summary exists
402+ if ( msg . condenseParent && existingSummaryIds . has ( msg . condenseParent ) ) {
403+ return false
404+ }
405+ // Filter out truncated messages if their truncation marker exists
406+ if ( msg . truncationParent && existingTruncationIds . has ( msg . truncationParent ) ) {
407+ return false
394408 }
395- // Include if the parent summary no longer exists (was deleted by rewind)
396- return ! existingSummaryIds . has ( msg . condenseParent )
409+ return true
397410 } )
398411}
399412
400413/**
401- * Cleans up orphaned condenseParent references after a truncation operation (rewind/delete).
402- * When a summary message is deleted, messages that were tagged with its condenseId
403- * should have their condenseParent cleared so they become active again.
414+ * Cleans up orphaned condenseParent and truncationParent references after a truncation operation (rewind/delete).
415+ * When a summary message or truncation marker is deleted, messages that were tagged with its ID
416+ * should have their parent reference cleared so they become active again.
404417 *
405418 * This function should be called after any operation that truncates the API history
406- * to ensure messages are properly restored when their summary is deleted.
419+ * to ensure messages are properly restored when their summary or truncation marker is deleted.
407420 *
408421 * @param messages - The API conversation history after truncation
409- * @returns The cleaned history with orphaned condenseParent fields cleared
422+ * @returns The cleaned history with orphaned condenseParent and truncationParent fields cleared
410423 */
411424export function cleanupAfterTruncation ( messages : ApiMessage [ ] ) : ApiMessage [ ] {
412425 // Collect all condenseIds of summaries that still exist
413426 const existingSummaryIds = new Set < string > ( )
427+ // Collect all truncationIds of truncation markers that still exist
428+ const existingTruncationIds = new Set < string > ( )
429+
414430 for ( const msg of messages ) {
415431 if ( msg . isSummary && msg . condenseId ) {
416432 existingSummaryIds . add ( msg . condenseId )
417433 }
434+ if ( msg . isTruncationMarker && msg . truncationId ) {
435+ existingTruncationIds . add ( msg . truncationId )
436+ }
418437 }
419438
420- // Clear condenseParent for messages whose summary was deleted
439+ // Clear orphaned parent references for messages whose summary or truncation marker was deleted
421440 return messages . map ( ( msg ) => {
441+ let needsUpdate = false
442+ const updates : Partial < ApiMessage > = { }
443+
444+ // Check for orphaned condenseParent
422445 if ( msg . condenseParent && ! existingSummaryIds . has ( msg . condenseParent ) ) {
423- // Summary was deleted, restore this message by clearing the parent reference
424- const { condenseParent, ...rest } = msg
425- return rest as ApiMessage
446+ needsUpdate = true
447+ }
448+
449+ // Check for orphaned truncationParent
450+ if ( msg . truncationParent && ! existingTruncationIds . has ( msg . truncationParent ) ) {
451+ needsUpdate = true
452+ }
453+
454+ if ( needsUpdate ) {
455+ // Create a new object without orphaned parent references
456+ const { condenseParent, truncationParent, ...rest } = msg
457+ const result : ApiMessage = rest as ApiMessage
458+
459+ // Keep condenseParent if its summary still exists
460+ if ( condenseParent && existingSummaryIds . has ( condenseParent ) ) {
461+ result . condenseParent = condenseParent
462+ }
463+
464+ // Keep truncationParent if its truncation marker still exists
465+ if ( truncationParent && existingTruncationIds . has ( truncationParent ) ) {
466+ result . truncationParent = truncationParent
467+ }
468+
469+ return result
426470 }
427471 return msg
428472 } )
0 commit comments