@@ -768,7 +768,7 @@ describe("createPruningCompactor — summarize receives the workflow context (CL
768768 } ) ;
769769} ) ;
770770
771- describe ( "createPruningCompactor — prefix-stable summaries (CL-6914 )" , ( ) => {
771+ describe ( "createPruningCompactor — consolidated handoff (CL-7521 )" , ( ) => {
772772 function firstText ( turn : ConversationTurn ) : string {
773773 const block = turn . content . find ( ( b ) => b . type === "text" ) ;
774774 return block !== undefined && block . type === "text" ? block . text : "" ;
@@ -795,7 +795,7 @@ describe("createPruningCompactor — prefix-stable summaries (CL-6914)", () => {
795795 return [ ...base , ...extra ] ;
796796 }
797797
798- test ( "second apply leaves output[0] bytes identical and appends a later summary " , async ( ) => {
798+ test ( "second apply replaces the prior summary instead of accumulating " , async ( ) => {
799799 const compactor = createPruningCompactor ( {
800800 keepRecentTurns : 2 ,
801801 summaryMaxChars : 500 ,
@@ -808,17 +808,68 @@ describe("createPruningCompactor — prefix-stable summaries (CL-6914)", () => {
808808 await compactor . apply ( grow ( output1 , 16 , "round2" ) , mockStrategyCtx )
809809 ) . output ;
810810
811- expect ( firstText ( defined ( output2 [ 0 ] ) ) ) . toBe ( firstText ( defined ( output1 [ 0 ] ) ) ) ;
812- expect ( output2 [ 0 ] ) . toBe ( output1 [ 0 ] ) ;
813- const summaries = compactedTurns ( output2 ) ;
814- expect ( summaries . length ) . toBeGreaterThanOrEqual ( 2 ) ;
815- expect ( output2 . indexOf ( defined ( summaries [ 1 ] ) ) ) . toBeGreaterThan ( 0 ) ;
811+ expect ( output2 [ 0 ] ) . not . toBe ( output1 [ 0 ] ) ;
812+ expect ( compactedTurns ( output2 ) ) . toHaveLength ( 1 ) ;
813+ expect ( firstText ( defined ( output2 [ 0 ] ) ) ) . toContain ( COMPACTED_PREFIX ) ;
816814 expect ( hasConsecutiveSameRole ( output2 ) ) . toBe ( false ) ;
815+ expect ( allText ( output2 ) ) . toContain ( "round1 0" ) ;
816+ } ) ;
817+
818+ test ( "second apply keeps the initiating task as its own user turn" , async ( ) => {
819+ const compactor = createPruningCompactor ( {
820+ keepRecentTurns : 2 ,
821+ maxAnchorTurns : 1 ,
822+ summaryMaxChars : 500 ,
823+ } ) ;
824+ const goal = "GOAL: migrate the auth module to opaque tokens" ;
825+ const turns : ConversationTurn [ ] = [
826+ makeTurn ( { role : "user" , content : [ { type : "text" , text : goal } ] } ) ,
827+ ] ;
828+ for ( let i = 0 ; i < 8 ; i ++ ) {
829+ turns . push (
830+ makeTurn ( {
831+ role : "assistant" ,
832+ content : [ { type : "text" , text : `step ${ i } ` } ] ,
833+ } ) ,
834+ ) ;
835+ }
836+ turns . push (
837+ makeTurn ( {
838+ role : "user" ,
839+ content : [ { type : "text" , text : "also handle refresh" } ] ,
840+ } ) ,
841+ ) ;
842+ turns . push (
843+ makeTurn ( {
844+ role : "assistant" ,
845+ content : [ { type : "text" , text : "recent reply" } ] ,
846+ } ) ,
847+ ) ;
848+ turns . push (
849+ makeTurn ( { role : "user" , content : [ { type : "text" , text : "recent ask" } ] } ) ,
850+ ) ;
851+
852+ const output1 = ( await compactor . apply ( turns , mockStrategyCtx ) ) . output ;
853+ expect (
854+ output1 . some (
855+ ( t ) =>
856+ t . role === "user" &&
857+ t . content . some ( ( b ) => b . type === "text" && b . text === goal ) ,
858+ ) ,
859+ ) . toBe ( true ) ;
860+
861+ const output2 = (
862+ await compactor . apply ( grow ( output1 , 16 , "round2" ) , mockStrategyCtx )
863+ ) . output ;
864+ expect ( compactedTurns ( output2 ) ) . toHaveLength ( 1 ) ;
817865 expect (
818866 output2 . some (
819- ( t ) => t . role === "assistant" && firstText ( t ) === COMPACT_SPACER_TEXT ,
867+ ( t ) =>
868+ t . role === "user" &&
869+ t . content . some ( ( b ) => b . type === "text" && b . text === goal ) ,
820870 ) ,
821871 ) . toBe ( true ) ;
872+ expect ( hasConsecutiveSameRole ( output2 ) ) . toBe ( false ) ;
822873 } ) ;
823874
824875 test ( "harness spacer is stamped with the reserved producer id and a visible sentinel" , async ( ) => {
@@ -840,116 +891,26 @@ describe("createPruningCompactor — prefix-stable summaries (CL-6914)", () => {
840891 expect ( COMPACT_SPACER_TEXT ) . not . toBe ( LEGACY_COMPACT_SPACER_TEXT ) ;
841892 } ) ;
842893
843- test ( "frozen prefix does not absorb a model-emitted spacer" , async ( ) => {
844- const compactor = createPruningCompactor ( {
845- keepRecentTurns : 2 ,
846- summaryMaxChars : 500 ,
847- } ) ;
848- const output1 = (
849- await compactor . apply ( grow ( [ ] , 16 , "round1" ) , mockStrategyCtx )
850- ) . output ;
851- const summary = output1 . find ( ( t ) =>
852- firstText ( t ) . startsWith ( COMPACTED_PREFIX ) ,
853- ) ;
854- expect ( summary ) . toBeDefined ( ) ;
894+ test ( "model-emitted spacer is not treated as a harness spacer" , async ( ) => {
855895 const echo = makeTurn ( {
856896 role : "assistant" ,
857897 model : "omen-alpha" ,
858898 content : [ { type : "text" , text : LEGACY_COMPACT_SPACER_TEXT } ] ,
859899 } ) ;
860- const output2 = (
861- await compactor . apply (
862- grow ( [ defined ( summary ) , echo ] , 16 , "round2" ) ,
863- mockStrategyCtx ,
864- )
865- ) . output ;
866-
867- let frozenLen = 0 ;
868- while (
869- frozenLen < output2 . length &&
870- firstText ( defined ( output2 [ frozenLen ] ) ) . startsWith ( COMPACTED_PREFIX )
871- ) {
872- frozenLen ++ ;
873- if (
874- frozenLen < output2 . length &&
875- isHarnessCompactSpacer ( defined ( output2 [ frozenLen ] ) )
876- )
877- frozenLen ++ ;
878- }
879- expect ( output2 . slice ( 0 , frozenLen ) ) . not . toContain ( echo ) ;
880- expect ( isHarnessCompactSpacer ( echo ) ) . toBe ( false ) ;
881- const harness = output2 . find ( isHarnessCompactSpacer ) ;
882- expect ( harness ) . toBeDefined ( ) ;
883- expect ( defined ( harness ) . model ) . toBe ( HARNESS_COMPACT_SPACER_MODEL ) ;
884- expect ( firstText ( defined ( harness ) ) ) . toBe ( COMPACT_SPACER_TEXT ) ;
885- expect ( harness ) . not . toBe ( echo ) ;
886- } ) ;
887-
888- test ( "frozen prefix does not absorb a model-stamped new-sentinel echo" , async ( ) => {
889- const compactor = createPruningCompactor ( {
890- keepRecentTurns : 2 ,
891- summaryMaxChars : 500 ,
892- } ) ;
893- const output1 = (
894- await compactor . apply ( grow ( [ ] , 16 , "round1" ) , mockStrategyCtx )
895- ) . output ;
896- const summary = output1 . find ( ( t ) =>
897- firstText ( t ) . startsWith ( COMPACTED_PREFIX ) ,
898- ) ;
899- expect ( summary ) . toBeDefined ( ) ;
900- const echo = makeTurn ( {
900+ const stamped = makeTurn ( {
901901 role : "assistant" ,
902902 model : "omen-alpha" ,
903903 content : [ { type : "text" , text : COMPACT_SPACER_TEXT } ] ,
904904 } ) ;
905- const output2 = (
906- await compactor . apply (
907- grow ( [ defined ( summary ) , echo ] , 16 , "round2" ) ,
908- mockStrategyCtx ,
909- )
910- ) . output ;
911-
912- let frozenLen = 0 ;
913- while (
914- frozenLen < output2 . length &&
915- firstText ( defined ( output2 [ frozenLen ] ) ) . startsWith ( COMPACTED_PREFIX )
916- ) {
917- frozenLen ++ ;
918- if (
919- frozenLen < output2 . length &&
920- isHarnessCompactSpacer ( defined ( output2 [ frozenLen ] ) )
921- )
922- frozenLen ++ ;
923- }
924- expect ( output2 . slice ( 0 , frozenLen ) ) . not . toContain ( echo ) ;
925905 expect ( isHarnessCompactSpacer ( echo ) ) . toBe ( false ) ;
926- const harness = output2 . find ( isHarnessCompactSpacer ) ;
927- expect ( harness ) . toBeDefined ( ) ;
928- expect ( defined ( harness ) . model ) . toBe ( HARNESS_COMPACT_SPACER_MODEL ) ;
929- expect ( firstText ( defined ( harness ) ) ) . toBe ( COMPACT_SPACER_TEXT ) ;
930- expect ( harness ) . not . toBe ( echo ) ;
906+ expect ( isHarnessCompactSpacer ( stamped ) ) . toBe ( false ) ;
931907 } ) ;
932908
933- test ( "legacy harness spacer without model still freezes" , async ( ) => {
934- const compactor = createPruningCompactor ( {
935- keepRecentTurns : 2 ,
936- summaryMaxChars : 500 ,
937- } ) ;
938- const output1 = (
939- await compactor . apply ( grow ( [ ] , 16 , "round1" ) , mockStrategyCtx )
940- ) . output ;
941- const summary = output1 . find ( ( t ) =>
942- firstText ( t ) . startsWith ( COMPACTED_PREFIX ) ,
943- ) ;
944- expect ( summary ) . toBeDefined ( ) ;
909+ test ( "legacy harness spacer without model is still recognized" , ( ) => {
945910 const legacySpacer = makeTurn ( {
946911 role : "assistant" ,
947912 content : [ { type : "text" , text : LEGACY_COMPACT_SPACER_TEXT } ] ,
948913 } ) ;
949- const grown = grow ( [ defined ( summary ) , legacySpacer ] , 16 , "round2" ) ;
950- const output2 = ( await compactor . apply ( grown , mockStrategyCtx ) ) . output ;
951- expect ( output2 [ 0 ] ) . toBe ( summary ) ;
952- expect ( output2 [ 1 ] ) . toBe ( legacySpacer ) ;
953914 expect ( isHarnessCompactSpacer ( legacySpacer ) ) . toBe ( true ) ;
954915 } ) ;
955916
@@ -982,7 +943,7 @@ describe("createPruningCompactor — prefix-stable summaries (CL-6914)", () => {
982943 expect ( result . record . reason ) . toBe ( "no compaction needed" ) ;
983944 } ) ;
984945
985- test ( "failing then succeeding summarizer does not rewrite output[0] " , async ( ) => {
946+ test ( "failing summarizer keeps prior context; a later success writes one handoff " , async ( ) => {
986947 const source : InferenceSource = {
987948 id : "test" ,
988949 provider : "openai" ,
@@ -1005,22 +966,20 @@ describe("createPruningCompactor — prefix-stable summaries (CL-6914)", () => {
1005966 summarize,
1006967 } ) ;
1007968 const turns = grow ( [ ] , 16 , "fail" ) ;
1008- const output1 = ( await compactor . apply ( turns , mockStrategyCtx ) ) . output ;
1009- expect ( firstText ( defined ( output1 [ 0 ] ) ) ) . toContain ( "Turns compacted:" ) ;
1010- expect ( firstText ( defined ( output1 [ 0 ] ) ) ) . not . toContain (
1011- "UNIQUE_SUCCESS_SUMMARY" ,
1012- ) ;
1013- expect ( firstText ( defined ( output1 [ 0 ] ) ) ) . toContain (
1014- "Model summary unavailable" ,
969+ const result1 = await compactor . apply ( turns , mockStrategyCtx ) ;
970+ expect ( result1 . output ) . toBe ( turns ) ;
971+ expect ( result1 . record . reason ) . toBe ( "summarize failed" ) ;
972+ expect ( firstText ( defined ( result1 . output [ 0 ] ) ) ) . not . toContain ( COMPACTED_PREFIX ) ;
973+
974+ const result2 = await compactor . apply (
975+ grow ( result1 . output , 16 , "ok" ) ,
976+ mockStrategyCtx ,
1015977 ) ;
1016-
1017- const output2 = (
1018- await compactor . apply ( grow ( output1 , 16 , "ok" ) , mockStrategyCtx )
1019- ) . output ;
1020- expect ( firstText ( defined ( output2 [ 0 ] ) ) ) . toBe ( firstText ( defined ( output1 [ 0 ] ) ) ) ;
1021- expect ( allText ( output2 ) ) . toContain ( "UNIQUE_SUCCESS_SUMMARY" ) ;
1022- expect ( hasConsecutiveSameRole ( output2 ) ) . toBe ( false ) ;
978+ expect ( compactedTurns ( result2 . output ) ) . toHaveLength ( 1 ) ;
979+ expect ( allText ( result2 . output ) ) . toContain ( "UNIQUE_SUCCESS_SUMMARY" ) ;
980+ expect ( hasConsecutiveSameRole ( result2 . output ) ) . toBe ( false ) ;
1023981 } ) ;
982+ }
1024983} ) ;
1025984
1026985describe ( "buildContextEnvelope" , ( ) => {
0 commit comments