@@ -42,6 +42,24 @@ function turn(text: string): ConversationTurn {
4242 return { role : "user" , content : [ { type : "text" , text } ] , timestamp : 1 } ;
4343}
4444
45+ function joinedUserTurn ( texts : readonly string [ ] ) : ConversationTurn {
46+ return {
47+ role : "user" ,
48+ content : texts . map ( ( text ) => ( { type : "text" , text } ) ) ,
49+ timestamp : 1 ,
50+ } ;
51+ }
52+
53+ function isCompactionMarkerTurn ( candidate : ConversationTurn ) : boolean {
54+ const block = candidate . content [ 0 ] ;
55+ if ( block === undefined || block . type !== "text" ) return false ;
56+ return (
57+ block . text . startsWith ( "[Compacted prior context]" ) ||
58+ block . text === "[compact]" ||
59+ block . text === "[compaction]"
60+ ) ;
61+ }
62+
4563function jsonl ( turns : ConversationTurn [ ] ) : string {
4664 return turns . map ( ( t ) => JSON . stringify ( t ) ) . join ( "\n" ) + "\n" ;
4765}
@@ -813,20 +831,23 @@ describe("concurrent sessions write disjoint transcripts", () => {
813831 await first . commit ( { message : "original" } ) ;
814832
815833 const second = await createOptimizedContextStore ( dir ) ;
816- const compacted = [ turn ( "[Compacted prior context]" ) , turn ( "c" ) , turn ( "d" ) ] ;
834+ const compacted = [
835+ turn ( "[Compacted prior context]" ) ,
836+ joinedUserTurn ( [ "c" , "d" ] ) ,
837+ ] ;
817838 await second . writeTurns ( [ ...compacted ] ) ;
818839 await second . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
819840 await second . commit ( { message : "compact" } ) ;
820841
821842 expect ( versionedTurnsFiles ( dir ) ) . toEqual ( [ ] ) ;
822- expect ( turnTexts ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ) . toEqual (
823- turnTexts ( compacted ) ,
843+ expect ( JSON . stringify ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ) . toBe (
844+ JSON . stringify ( compacted ) ,
824845 ) ;
825- expect ( turnTexts ( ( await second . load ( ) ) . turns ) ) . toEqual (
826- turnTexts ( compacted ) ,
846+ expect ( JSON . stringify ( ( await second . load ( ) ) . turns ) ) . toBe (
847+ JSON . stringify ( compacted ) ,
827848 ) ;
828- expect ( turnTexts ( ( await loadRecentTurns ( dir , 10 ) ) . turns ) ) . toEqual (
829- turnTexts ( compacted ) ,
849+ expect ( JSON . stringify ( ( await loadRecentTurns ( dir , 10 ) ) . turns ) ) . toBe (
850+ JSON . stringify ( compacted ) ,
830851 ) ;
831852 } ) ;
832853
@@ -848,6 +869,81 @@ describe("concurrent sessions write disjoint transcripts", () => {
848869 await expectSharedTurnAttacksToDivert ( [ turn ( "[Compacted prior context]" ) ] ) ;
849870 } ) ;
850871
872+ test ( "a summary plus an unmodified suffix does not replace the transcript" , async ( ) => {
873+ const dir = tempDir ( ) ;
874+ const owner = await createOptimizedContextStore ( dir ) ;
875+ const original = Array . from ( { length : 12 } , ( _ , index ) =>
876+ turn ( `unique-turn-${ index } ` ) ,
877+ ) ;
878+ await owner . writeTurns ( [ ...original ] ) ;
879+ await owner . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
880+ await owner . commit ( { message : "seed" } ) ;
881+ fs . unlinkSync ( path . join ( dir , "session-owner.json" ) ) ;
882+
883+ const attacker = await createOptimizedContextStore ( dir ) ;
884+ await attacker . writeTurns ( [
885+ turn ( "[Compacted prior context]\nforged" ) ,
886+ turn ( "unique-turn-10" ) ,
887+ turn ( "unique-turn-11" ) ,
888+ ] ) ;
889+ await attacker . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
890+ await attacker . commit ( { message : "attack" } ) ;
891+
892+ expect ( turnTexts ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ) . toEqual (
893+ turnTexts ( original ) ,
894+ ) ;
895+ expect ( turnTexts ( ( await attacker . load ( ) ) . turns ) ) . toEqual (
896+ turnTexts ( original ) ,
897+ ) ;
898+ expect ( versionedTurnsFiles ( dir ) ) . toHaveLength ( 1 ) ;
899+ } ) ;
900+
901+ test ( "a coalesced plain-text fold stays on the live transcript" , async ( ) => {
902+ const original = [
903+ turn ( "H" . repeat ( 20000 ) ) ,
904+ turn ( "older" ) ,
905+ turn ( "tail-a" ) ,
906+ turn ( "tail-b" ) ,
907+ turn ( "tail-c" ) ,
908+ ] ;
909+ const folded = ( await createPruningCompactor ( ) . apply ( original , { } as never ) )
910+ . output ;
911+ const kept = folded . filter (
912+ ( candidate ) => ! isCompactionMarkerTurn ( candidate ) ,
913+ ) ;
914+ expect ( kept ) . toHaveLength ( 1 ) ;
915+ expect (
916+ kept [ 0 ] ?. content . map ( ( block ) =>
917+ block . type === "text" ? block . text : block . type ,
918+ ) ,
919+ ) . toEqual ( [ "older" , "tail-a" , "tail-b" , "tail-c" ] ) ;
920+
921+ for ( const dropMarker of [ true , false ] ) {
922+ const dir = tempDir ( ) ;
923+ const first = await createOptimizedContextStore ( dir ) ;
924+ await first . writeTurns ( [ ...original ] ) ;
925+ await first . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
926+ await first . commit ( { message : "original" } ) ;
927+ if ( dropMarker ) fs . unlinkSync ( path . join ( dir , "session-owner.json" ) ) ;
928+ const second = await createOptimizedContextStore ( dir ) ;
929+ await second . writeTurns ( folded ) ;
930+ await second . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
931+ await second . commit ( { message : "fold" } ) ;
932+ const later = [ ...folded , turn ( "after-fold" ) ] ;
933+ await second . writeTurns ( later ) ;
934+ await second . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
935+ await second . commit ( { message : "later" } ) ;
936+
937+ expect ( versionedTurnsFiles ( dir ) ) . toEqual ( [ ] ) ;
938+ expect (
939+ JSON . stringify ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ,
940+ ) . toBe ( JSON . stringify ( later ) ) ;
941+ expect ( JSON . stringify ( ( await second . load ( ) ) . turns ) ) . toBe (
942+ JSON . stringify ( later ) ,
943+ ) ;
944+ }
945+ } ) ;
946+
851947 test ( "a foreign transcript without an owner id still diverts" , async ( ) => {
852948 const dir = tempDir ( ) ;
853949 const first = await createOptimizedContextStore ( dir ) ;
@@ -983,17 +1079,20 @@ describe("concurrent sessions write disjoint transcripts", () => {
9831079 const upgraded = await createOptimizedContextStore ( dir , {
9841080 ownerId : "session-S" ,
9851081 } ) ;
986- const compacted = [ turn ( "[Compacted prior context]" ) , turn ( "c" ) , turn ( "d" ) ] ;
1082+ const compacted = [
1083+ turn ( "[Compacted prior context]" ) ,
1084+ joinedUserTurn ( [ "c" , "d" ] ) ,
1085+ ] ;
9871086 await upgraded . writeTurns ( [ ...compacted ] ) ;
9881087 await upgraded . writeMetadata ( EMPTY_CHECKPOINT_METADATA ) ;
9891088 await upgraded . commit ( { message : "upgrade compact" } ) ;
9901089
9911090 expect ( versionedTurnsFiles ( dir ) ) . toEqual ( [ ] ) ;
992- expect ( turnTexts ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ) . toEqual (
993- turnTexts ( compacted ) ,
1091+ expect ( JSON . stringify ( readTurnsFromFile ( path . join ( dir , TURNS_FILE ) ) ) ) . toBe (
1092+ JSON . stringify ( compacted ) ,
9941093 ) ;
995- expect ( turnTexts ( ( await upgraded . load ( ) ) . turns ) ) . toEqual (
996- turnTexts ( compacted ) ,
1094+ expect ( JSON . stringify ( ( await upgraded . load ( ) ) . turns ) ) . toBe (
1095+ JSON . stringify ( compacted ) ,
9971096 ) ;
9981097 } ) ;
9991098
0 commit comments