@@ -631,31 +631,22 @@ it.live(
631631 const ready = defer < void > ( )
632632 const aborted = defer < void > ( )
633633 const registry = yield * ToolRegistry . Service
634- const init = registry . named . task . init
635- registry . named . task . init = async ( ) => ( {
636- description : "task" ,
637- parameters : z . object ( {
638- description : z . string ( ) ,
639- prompt : z . string ( ) ,
640- subagent_type : z . string ( ) ,
641- task_id : z . string ( ) . optional ( ) ,
642- command : z . string ( ) . optional ( ) ,
643- } ) ,
644- execute : async ( _args , ctx ) => {
645- ready . resolve ( )
646- ctx . abort . addEventListener ( "abort" , ( ) => aborted . resolve ( ) , { once : true } )
647- await new Promise < void > ( ( ) => { } )
648- return {
649- title : "" ,
650- metadata : {
651- sessionId : SessionID . make ( "task" ) ,
652- model : ref ,
653- } ,
654- output : "" ,
655- }
656- } ,
657- } )
658- yield * Effect . addFinalizer ( ( ) => Effect . sync ( ( ) => void ( registry . named . task . init = init ) ) )
634+ const { task } = yield * registry . named ( )
635+ const original = task . execute
636+ task . execute = async ( _args , ctx ) => {
637+ ready . resolve ( )
638+ ctx . abort . addEventListener ( "abort" , ( ) => aborted . resolve ( ) , { once : true } )
639+ await new Promise < void > ( ( ) => { } )
640+ return {
641+ title : "" ,
642+ metadata : {
643+ sessionId : SessionID . make ( "task" ) ,
644+ model : ref ,
645+ } ,
646+ output : "" ,
647+ }
648+ }
649+ yield * Effect . addFinalizer ( ( ) => Effect . sync ( ( ) => void ( task . execute = original ) ) )
659650
660651 const { prompt, chat } = yield * boot ( )
661652 const msg = yield * user ( chat . id , "hello" )
@@ -1240,3 +1231,109 @@ unix(
12401231 ) ,
12411232 30_000 ,
12421233)
1234+
1235+ // Abort signal propagation tests for inline tool execution
1236+
1237+ /** Override a tool's execute to hang until aborted. Returns ready/aborted defers and a finalizer. */
1238+ function hangUntilAborted ( tool : { execute : ( ...args : any [ ] ) => any } ) {
1239+ const ready = defer < void > ( )
1240+ const aborted = defer < void > ( )
1241+ const original = tool . execute
1242+ tool . execute = async ( _args : any , ctx : any ) => {
1243+ ready . resolve ( )
1244+ ctx . abort . addEventListener ( "abort" , ( ) => aborted . resolve ( ) , { once : true } )
1245+ await new Promise < void > ( ( ) => { } )
1246+ return { title : "" , metadata : { } , output : "" }
1247+ }
1248+ const restore = Effect . addFinalizer ( ( ) => Effect . sync ( ( ) => void ( tool . execute = original ) ) )
1249+ return { ready, aborted, restore }
1250+ }
1251+
1252+ it . live (
1253+ "interrupt propagates abort signal to read tool via file part (text/plain)" ,
1254+ ( ) =>
1255+ provideTmpdirInstance (
1256+ ( dir ) =>
1257+ Effect . gen ( function * ( ) {
1258+ const registry = yield * ToolRegistry . Service
1259+ const { read } = yield * registry . named ( )
1260+ const { ready, aborted, restore } = hangUntilAborted ( read )
1261+ yield * restore
1262+
1263+ const prompt = yield * SessionPrompt . Service
1264+ const sessions = yield * Session . Service
1265+ const chat = yield * sessions . create ( { title : "Abort Test" } )
1266+
1267+ const testFile = path . join ( dir , "test.txt" )
1268+ yield * Effect . promise ( ( ) => Bun . write ( testFile , "hello world" ) )
1269+
1270+ const fiber = yield * prompt
1271+ . prompt ( {
1272+ sessionID : chat . id ,
1273+ agent : "build" ,
1274+ parts : [
1275+ { type : "text" , text : "read this" } ,
1276+ { type : "file" , url : `file://${ testFile } ` , filename : "test.txt" , mime : "text/plain" } ,
1277+ ] ,
1278+ } )
1279+ . pipe ( Effect . forkChild )
1280+
1281+ yield * Effect . promise ( ( ) => ready . promise )
1282+ yield * Fiber . interrupt ( fiber )
1283+
1284+ yield * Effect . promise ( ( ) =>
1285+ Promise . race ( [
1286+ aborted . promise ,
1287+ new Promise < void > ( ( _ , reject ) =>
1288+ setTimeout ( ( ) => reject ( new Error ( "abort signal not propagated within 2s" ) ) , 2_000 ) ,
1289+ ) ,
1290+ ] ) ,
1291+ )
1292+ } ) ,
1293+ { git : true , config : cfg } ,
1294+ ) ,
1295+ 30_000 ,
1296+ )
1297+
1298+ it . live (
1299+ "interrupt propagates abort signal to read tool via file part (directory)" ,
1300+ ( ) =>
1301+ provideTmpdirInstance (
1302+ ( dir ) =>
1303+ Effect . gen ( function * ( ) {
1304+ const registry = yield * ToolRegistry . Service
1305+ const { read } = yield * registry . named ( )
1306+ const { ready, aborted, restore } = hangUntilAborted ( read )
1307+ yield * restore
1308+
1309+ const prompt = yield * SessionPrompt . Service
1310+ const sessions = yield * Session . Service
1311+ const chat = yield * sessions . create ( { title : "Abort Test" } )
1312+
1313+ const fiber = yield * prompt
1314+ . prompt ( {
1315+ sessionID : chat . id ,
1316+ agent : "build" ,
1317+ parts : [
1318+ { type : "text" , text : "read this" } ,
1319+ { type : "file" , url : `file://${ dir } ` , filename : "dir" , mime : "application/x-directory" } ,
1320+ ] ,
1321+ } )
1322+ . pipe ( Effect . forkChild )
1323+
1324+ yield * Effect . promise ( ( ) => ready . promise )
1325+ yield * Fiber . interrupt ( fiber )
1326+
1327+ yield * Effect . promise ( ( ) =>
1328+ Promise . race ( [
1329+ aborted . promise ,
1330+ new Promise < void > ( ( _ , reject ) =>
1331+ setTimeout ( ( ) => reject ( new Error ( "abort signal not propagated within 2s" ) ) , 2_000 ) ,
1332+ ) ,
1333+ ] ) ,
1334+ )
1335+ } ) ,
1336+ { git : true , config : cfg } ,
1337+ ) ,
1338+ 30_000 ,
1339+ )
0 commit comments