@@ -29,6 +29,9 @@ const svc = {
2929 updatePart < T extends MessageV2 . Part > ( part : T ) {
3030 return run ( SessionNs . Service . use ( ( svc ) => svc . updatePart ( part ) ) )
3131 } ,
32+ fork ( input : { sessionID : SessionID ; messageID ?: MessageID } ) {
33+ return run ( SessionNs . Service . use ( ( svc ) => svc . fork ( input ) ) )
34+ } ,
3235}
3336
3437async function fill ( sessionID : SessionID , count : number , time = ( i : number ) => Date . now ( ) + i ) {
@@ -837,6 +840,72 @@ describe("MessageV2.filterCompacted", () => {
837840 } )
838841 } )
839842
843+ test ( "fork remaps compaction tail_start_id for filterCompacted" , async ( ) => {
844+ await Instance . provide ( {
845+ directory : root ,
846+ fn : async ( ) => {
847+ const session = await svc . create ( { } )
848+
849+ const u1 = await addUser ( session . id , "first" )
850+ const a1 = await addAssistant ( session . id , u1 , { finish : "end_turn" } )
851+ await svc . updatePart ( {
852+ id : PartID . ascending ( ) ,
853+ sessionID : session . id ,
854+ messageID : a1 ,
855+ type : "text" ,
856+ text : "first reply" ,
857+ } )
858+
859+ const u2 = await addUser ( session . id , "second" )
860+ const a2 = await addAssistant ( session . id , u2 , { finish : "end_turn" } )
861+ await svc . updatePart ( {
862+ id : PartID . ascending ( ) ,
863+ sessionID : session . id ,
864+ messageID : a2 ,
865+ type : "text" ,
866+ text : "second reply" ,
867+ } )
868+
869+ const c1 = await addUser ( session . id )
870+ await addCompactionPart ( session . id , c1 , u2 )
871+ const s1 = await addAssistant ( session . id , c1 , { summary : true , finish : "end_turn" } )
872+ await svc . updatePart ( {
873+ id : PartID . ascending ( ) ,
874+ sessionID : session . id ,
875+ messageID : s1 ,
876+ type : "text" ,
877+ text : "summary" ,
878+ } )
879+
880+ const u3 = await addUser ( session . id , "third" )
881+ const a3 = await addAssistant ( session . id , u3 , { finish : "end_turn" } )
882+ await svc . updatePart ( {
883+ id : PartID . ascending ( ) ,
884+ sessionID : session . id ,
885+ messageID : a3 ,
886+ type : "text" ,
887+ text : "third reply" ,
888+ } )
889+
890+ const parentFiltered = MessageV2 . filterCompacted ( MessageV2 . stream ( session . id ) )
891+ expect ( parentFiltered . map ( ( item ) => item . info . id ) ) . toEqual ( [ u2 , a2 , c1 , s1 , u3 , a3 ] )
892+
893+ const forked = await svc . fork ( { sessionID : session . id } )
894+ const childFiltered = MessageV2 . filterCompacted ( MessageV2 . stream ( forked . id ) )
895+ expect ( childFiltered ) . toHaveLength ( parentFiltered . length )
896+
897+ const tailPart = childFiltered . flatMap ( ( m ) => m . parts ) . find ( ( p ) => p . type === "compaction" )
898+ expect ( tailPart ?. type ) . toBe ( "compaction" )
899+ if ( ! tailPart || tailPart . type !== "compaction" ) throw new Error ( "Expected forked compaction part" )
900+ expect ( tailPart . tail_start_id ) . toBeDefined ( )
901+ expect ( childFiltered . some ( ( m ) => m . info . id === tailPart . tail_start_id ) ) . toBe ( true )
902+
903+ await svc . remove ( forked . id )
904+ await svc . remove ( session . id )
905+ } ,
906+ } )
907+ } )
908+
840909 test ( "retains an assistant tail when compaction starts inside a turn" , async ( ) => {
841910 await Instance . provide ( {
842911 directory : root ,
0 commit comments