@@ -232,6 +232,14 @@ async function lastCompactionPart(sessionID: SessionID) {
232232 ?. parts . find ( ( item ) : item is MessageV2 . CompactionPart => item . type === "compaction" )
233233}
234234
235+ function readLastCompactionPart ( sessionID : SessionID ) {
236+ return SessionNs . Service . use ( ( ssn ) => ssn . messages ( { sessionID } ) ) . pipe (
237+ Effect . map ( ( messages ) =>
238+ messages . at ( - 2 ) ?. parts . find ( ( item ) : item is MessageV2 . CompactionPart => item . type === "compaction" ) ,
239+ ) ,
240+ )
241+ }
242+
235243function fake (
236244 input : Parameters < SessionProcessorModule . SessionProcessor . Interface [ "create" ] > [ 0 ] ,
237245 result : "continue" | "compact" ,
@@ -300,6 +308,30 @@ const env = Layer.mergeAll(
300308
301309const it = testEffect ( env )
302310
311+ const processEnv = Layer . mergeAll ( SessionNs . defaultLayer , CrossSpawnSpawner . defaultLayer )
312+ const itProcess = testEffect ( processEnv )
313+
314+ function compactionProcessLayer ( options ?: {
315+ result ?: "continue" | "compact"
316+ plugin ?: Layer . Layer < Plugin . Service >
317+ provider ?: ReturnType < typeof ProviderTest . fake >
318+ config ?: Layer . Layer < Config . Service >
319+ } ) {
320+ const bus = Bus . layer
321+ return SessionCompaction . layer . pipe (
322+ Layer . provideMerge (
323+ Layer . mergeAll (
324+ ( options ?. provider ?? wide ( ) ) . layer ,
325+ layer ( options ?. result ?? "continue" ) ,
326+ Agent . defaultLayer ,
327+ options ?. plugin ?? Plugin . defaultLayer ,
328+ bus ,
329+ options ?. config ?? Config . defaultLayer ,
330+ ) ,
331+ ) ,
332+ )
333+ }
334+
303335function llm ( ) {
304336 const queue : Array <
305337 Stream . Stream < LLM . Event , unknown > | ( ( input : LLM . StreamInput ) => Stream . Stream < LLM . Event , unknown > )
@@ -911,43 +943,33 @@ describe("session.compaction.process", () => {
911943 } ) ,
912944 )
913945
914- test ( "marks summary message as errored on compact result" , async ( ) => {
915- await using tmp = await tmpdir ( )
916- await WithInstance . provide ( {
917- directory : tmp . path ,
918- fn : async ( ) => {
919- const session = await svc . create ( { } )
920- const msg = await user ( session . id , "hello" )
921- const rt = runtime ( "compact" , Plugin . defaultLayer , wide ( ) )
922- try {
923- const msgs = await svc . messages ( { sessionID : session . id } )
924- const result = await rt . runPromise (
925- SessionCompaction . Service . use ( ( svc ) =>
926- svc . process ( {
927- parentID : msg . id ,
928- messages : msgs ,
929- sessionID : session . id ,
930- auto : false ,
931- } ) ,
932- ) ,
933- )
946+ itProcess . instance (
947+ "marks summary message as errored on compact result" ,
948+ Effect . gen ( function * ( ) {
949+ const ssn = yield * SessionNs . Service
950+ const session = yield * ssn . create ( { } )
951+ const msg = yield * createUserMessage ( session . id , "hello" )
952+ const msgs = yield * ssn . messages ( { sessionID : session . id } )
934953
935- const summary = ( await svc . messages ( { sessionID : session . id } ) ) . find (
936- ( msg ) => msg . info . role === "assistant" && msg . info . summary ,
937- )
954+ const result = yield * SessionCompaction . use . process ( {
955+ parentID : msg . id ,
956+ messages : msgs ,
957+ sessionID : session . id ,
958+ auto : false ,
959+ } )
938960
939- expect ( result ) . toBe ( "stop" )
940- expect ( summary ? .info . role ) . toBe ( "assistant" )
941- if ( summary ?. info . role === "assistant" ) {
942- expect ( summary . info . finish ) . toBe ( "error" )
943- expect ( JSON . stringify ( summary . info . error ) ) . toContain ( "Session too large to compact ")
944- }
945- } finally {
946- await rt . dispose ( )
947- }
948- } ,
949- } )
950- } )
961+ const summary = ( yield * ssn . messages ( { sessionID : session . id } ) ) . find (
962+ ( msg ) => msg . info . role === "assistant" && msg . info . summary ,
963+ )
964+
965+ expect ( result ) . toBe ( "stop ")
966+ expect ( summary ?. info . role ) . toBe ( "assistant" )
967+ if ( summary ?. info . role === "assistant" ) {
968+ expect ( summary . info . finish ) . toBe ( "error" )
969+ expect ( JSON . stringify ( summary . info . error ) ) . toContain ( "Session too large to compact" )
970+ }
971+ } ) . pipe ( Effect . provide ( compactionProcessLayer ( { result : "compact" } ) ) ) ,
972+ )
951973
952974 it . instance (
953975 "adds synthetic continue prompt when auto is enabled" ,
@@ -980,94 +1002,57 @@ describe("session.compaction.process", () => {
9801002 } ) ,
9811003 )
9821004
983- test ( "persists tail_start_id for retained recent turns" , async ( ) => {
984- await using tmp = await tmpdir ( )
985- await WithInstance . provide ( {
986- directory : tmp . path ,
987- fn : async ( ) => {
988- const session = await svc . create ( { } )
989- await user ( session . id , "first" )
990- const keep = await user ( session . id , "second" )
991- await user ( session . id , "third" )
992- await SessionCompaction . create ( {
993- sessionID : session . id ,
994- agent : "build" ,
995- model : ref ,
996- auto : false ,
997- } )
1005+ itProcess . instance (
1006+ "persists tail_start_id for retained recent turns" ,
1007+ Effect . gen ( function * ( ) {
1008+ const ssn = yield * SessionNs . Service
1009+ const session = yield * ssn . create ( { } )
1010+ yield * createUserMessage ( session . id , "first" )
1011+ const keep = yield * createUserMessage ( session . id , "second" )
1012+ yield * createUserMessage ( session . id , "third" )
1013+ yield * SessionCompaction . use . create ( { sessionID : session . id , agent : "build" , model : ref , auto : false } )
9981014
999- const rt = runtime (
1000- "continue" ,
1001- Plugin . defaultLayer ,
1002- wide ( ) ,
1003- cfg ( { tail_turns : 2 , preserve_recent_tokens : 10_000 } ) ,
1004- )
1005- try {
1006- const msgs = await svc . messages ( { sessionID : session . id } )
1007- const parent = msgs . at ( - 1 ) ?. info . id
1008- expect ( parent ) . toBeTruthy ( )
1009- await rt . runPromise (
1010- SessionCompaction . Service . use ( ( svc ) =>
1011- svc . process ( {
1012- parentID : parent ! ,
1013- messages : msgs ,
1014- sessionID : session . id ,
1015- auto : false ,
1016- } ) ,
1017- ) ,
1018- )
1015+ const msgs = yield * ssn . messages ( { sessionID : session . id } )
1016+ const parent = msgs . at ( - 1 ) ?. info . id
1017+ expect ( parent ) . toBeTruthy ( )
1018+ yield * SessionCompaction . use . process ( {
1019+ parentID : parent ! ,
1020+ messages : msgs ,
1021+ sessionID : session . id ,
1022+ auto : false ,
1023+ } )
10191024
1020- const part = await lastCompactionPart ( session . id )
1021- expect ( part ?. type ) . toBe ( "compaction" )
1022- expect ( part ?. tail_start_id ) . toBe ( keep . id )
1023- } finally {
1024- await rt . dispose ( )
1025- }
1026- } ,
1027- } )
1028- } )
1025+ const part = yield * readLastCompactionPart ( session . id )
1026+ expect ( part ?. type ) . toBe ( "compaction" )
1027+ expect ( part ?. tail_start_id ) . toBe ( keep . id )
1028+ } ) . pipe ( Effect . provide ( compactionProcessLayer ( { config : cfg ( { tail_turns : 2 , preserve_recent_tokens : 10_000 } ) } ) ) ) ,
1029+ )
10291030
1030- test ( "shrinks retained tail to fit preserve token budget" , async ( ) => {
1031- await using tmp = await tmpdir ( )
1032- await WithInstance . provide ( {
1033- directory : tmp . path ,
1034- fn : async ( ) => {
1035- const session = await svc . create ( { } )
1036- await user ( session . id , "first" )
1037- await user ( session . id , "x" . repeat ( 2_000 ) )
1038- const keep = await user ( session . id , "tiny" )
1039- await SessionCompaction . create ( {
1040- sessionID : session . id ,
1041- agent : "build" ,
1042- model : ref ,
1043- auto : false ,
1044- } )
1031+ itProcess . instance (
1032+ "shrinks retained tail to fit preserve token budget" ,
1033+ Effect . gen ( function * ( ) {
1034+ const ssn = yield * SessionNs . Service
1035+ const session = yield * ssn . create ( { } )
1036+ yield * createUserMessage ( session . id , "first" )
1037+ yield * createUserMessage ( session . id , "x" . repeat ( 2_000 ) )
1038+ const keep = yield * createUserMessage ( session . id , "tiny" )
1039+ yield * SessionCompaction . use . create ( { sessionID : session . id , agent : "build" , model : ref , auto : false } )
10451040
1046- const rt = runtime ( "continue" , Plugin . defaultLayer , wide ( ) , cfg ( { tail_turns : 2 , preserve_recent_tokens : 100 } ) )
1047- try {
1048- const msgs = await svc . messages ( { sessionID : session . id } )
1049- const parent = msgs . at ( - 1 ) ?. info . id
1050- expect ( parent ) . toBeTruthy ( )
1051- await rt . runPromise (
1052- SessionCompaction . Service . use ( ( svc ) =>
1053- svc . process ( {
1054- parentID : parent ! ,
1055- messages : msgs ,
1056- sessionID : session . id ,
1057- auto : false ,
1058- } ) ,
1059- ) ,
1060- )
1041+ const msgs = yield * ssn . messages ( { sessionID : session . id } )
1042+ const parent = msgs . at ( - 1 ) ?. info . id
1043+ expect ( parent ) . toBeTruthy ( )
1044+ yield * SessionCompaction . use . process ( {
1045+ parentID : parent ! ,
1046+ messages : msgs ,
1047+ sessionID : session . id ,
1048+ auto : false ,
1049+ } )
10611050
1062- const part = await lastCompactionPart ( session . id )
1063- expect ( part ?. type ) . toBe ( "compaction" )
1064- expect ( part ?. tail_start_id ) . toBe ( keep . id )
1065- } finally {
1066- await rt . dispose ( )
1067- }
1068- } ,
1069- } )
1070- } )
1051+ const part = yield * readLastCompactionPart ( session . id )
1052+ expect ( part ?. type ) . toBe ( "compaction" )
1053+ expect ( part ?. tail_start_id ) . toBe ( keep . id )
1054+ } ) . pipe ( Effect . provide ( compactionProcessLayer ( { config : cfg ( { tail_turns : 2 , preserve_recent_tokens : 100 } ) } ) ) ) ,
1055+ )
10711056
10721057 test ( "falls back to full summary when even one recent turn exceeds preserve token budget" , async ( ) => {
10731058 await using tmp = await tmpdir ( { git : true } )
0 commit comments