@@ -1092,6 +1092,94 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
10921092 event ( "session.execution.started" , { sessionID : CHILD } ) ,
10931093 ] ;
10941094
1095+ it . effect ( "settles a background subagent that fails, so no work is left pending" , ( ) =>
1096+ Effect . gen ( function * ( ) {
1097+ const { runtime, thread } = yield * resumed ( [
1098+ ...launchedAndRunning ,
1099+ // The parent turn ends, and the background child fails: it reports
1100+ // nothing back, so its call has to end with the failure.
1101+ event ( "session.execution.succeeded" , { sessionID : SESSION } ) ,
1102+ event ( "session.execution.failed" , {
1103+ sessionID : CHILD ,
1104+ error : { type : "provider.quota" , message : "Rate limit exceeded." } ,
1105+ } ) ,
1106+ ] ) ;
1107+ const watch = yield * watchCalls ( runtime , thread ) ;
1108+ yield * runtime . startTurn ( withLineage ( thread ) ) ;
1109+ yield * watch . until (
1110+ ( ) => watch . calls . get ( "Sleep" ) ?. status === "failed" && watch . state . roster ?. length === 0 ,
1111+ ) ;
1112+ assert . deepEqual ( watch . state . roster , [ ] ) ;
1113+ assert . isFalse ( yield * runtime . hasPendingBackgroundWorkForThread ! ( thread ) ) ;
1114+ } ) . pipe ( Effect . scoped ) ,
1115+ ) ;
1116+
1117+ it . effect ( "settles every call joined to a failed background subagent" , ( ) =>
1118+ Effect . gen ( function * ( ) {
1119+ const { runtime, thread } = yield * resumed ( [
1120+ ...launchedAndRunning ,
1121+ // A second call joins the running child, so its turn answers for both.
1122+ ...callAgain ( { background : true } ) ,
1123+ event ( "session.execution.succeeded" , { sessionID : SESSION } ) ,
1124+ event ( "session.execution.failed" , {
1125+ sessionID : CHILD ,
1126+ error : { type : "provider.quota" , message : "Rate limit exceeded." } ,
1127+ } ) ,
1128+ ] ) ;
1129+ const watch = yield * watchCalls ( runtime , thread ) ;
1130+ yield * runtime . startTurn ( withLineage ( thread ) ) ;
1131+ yield * watch . until (
1132+ ( ) =>
1133+ watch . calls . get ( "Sleep" ) ?. status === "failed" &&
1134+ watch . calls . get ( "Again" ) ?. status === "failed" &&
1135+ watch . state . roster ?. length === 0 ,
1136+ ) ;
1137+ assert . deepEqual ( watch . state . roster , [ ] ) ;
1138+ assert . isFalse ( yield * runtime . hasPendingBackgroundWorkForThread ! ( thread ) ) ;
1139+ } ) . pipe ( Effect . scoped ) ,
1140+ ) ;
1141+
1142+ it . effect ( "settles the background call a foreground call joined to a failed child" , ( ) =>
1143+ Effect . gen ( function * ( ) {
1144+ const again = { sessionID : SESSION , assistantMessageID : "msg_assistant" , id : "call-again" } ;
1145+ const { runtime, thread } = yield * resumed ( [
1146+ ...launchedAndRunning ,
1147+ // A foreground call joins the running background child. It is only
1148+ // marked background once its own tool call returns, so the child can
1149+ // fail while this call is still the foreground one.
1150+ event ( "session.tool.input.started" , { ...again , name : "subagent" } ) ,
1151+ event ( "session.tool.called" , {
1152+ ...again ,
1153+ name : "subagent" ,
1154+ input : { description : "Again" , prompt : "also this" , sessionID : CHILD } ,
1155+ executed : false ,
1156+ } ) ,
1157+ event ( "session.tool.progress" , {
1158+ ...again ,
1159+ metadata : { sessionID : CHILD , status : "running" } ,
1160+ } ) ,
1161+ out ( "session.update" , { sessionID : CHILD , permissions : "<any>" } ) ,
1162+ reply ( "session.update" , null ) ,
1163+ // The child fails while the joined call is still the foreground one.
1164+ event ( "session.execution.failed" , {
1165+ sessionID : CHILD ,
1166+ error : { type : "provider.quota" , message : "Rate limit exceeded." } ,
1167+ } ) ,
1168+ event ( "session.execution.succeeded" , { sessionID : SESSION } ) ,
1169+ ] ) ;
1170+ const watch = yield * watchCalls ( runtime , thread ) ;
1171+ yield * runtime . startTurn ( withLineage ( thread ) ) ;
1172+ yield * watch . until (
1173+ ( ) =>
1174+ watch . calls . get ( "Sleep" ) ?. status === "failed" &&
1175+ watch . calls . get ( "Again" ) ?. status === "failed" &&
1176+ watch . state . roster ?. length === 0 ,
1177+ ) ;
1178+ assert . deepEqual ( watch . state . roster , [ ] ) ;
1179+ assert . isFalse ( yield * runtime . hasPendingBackgroundWorkForThread ! ( thread ) ) ;
1180+ } ) . pipe ( Effect . scoped ) ,
1181+ ) ;
1182+
10951183 it . effect ( "settles every call that joined a running subagent with its one report" , ( ) =>
10961184 Effect . gen ( function * ( ) {
10971185 const { runtime, thread } = yield * resumed ( [
0 commit comments