@@ -840,6 +840,91 @@ describe("tool.task", () => {
840840 } ) ,
841841 )
842842
843+ background . instance ( "notifies the parent with a fallback when a background task error is blank" , ( ) =>
844+ Effect . gen ( function * ( ) {
845+ const { chat, assistant } = yield * seed ( )
846+ const injected = yield * Deferred . make < SessionPrompt . PromptInput > ( )
847+ let error = ""
848+ const fakeBackground : BackgroundJob . Interface = {
849+ list : ( ) => Effect . succeed ( [ ] ) ,
850+ get : ( ) => Effect . succeed ( undefined ) ,
851+ start : ( input ) =>
852+ Effect . succeed ( {
853+ id : input . id ?? "task" ,
854+ type : input . type ,
855+ title : input . title ,
856+ status : "running" ,
857+ started_at : 0 ,
858+ metadata : input . metadata ,
859+ } ) ,
860+ extend : ( ) => Effect . succeed ( false ) ,
861+ wait : ( ) => Effect . succeed ( { timedOut : false , info : { id : "task" , type : "task" , status : "error" , started_at : 0 , error } } ) ,
862+ waitForPromotion : ( ) => Effect . never ,
863+ promote : ( ) => Effect . succeed ( undefined ) ,
864+ cancel : ( ) => Effect . succeed ( undefined ) ,
865+ }
866+ const task = yield * TaskTool . pipe ( Effect . provideService ( BackgroundJob . Service , fakeBackground ) )
867+ const def = yield * task . init ( )
868+ const promptOps : TaskPromptOps = {
869+ cancel : ( ) => Effect . void ,
870+ resolvePromptParts : ( template ) => Effect . succeed ( [ { type : "text" as const , text : template } ] ) ,
871+ prompt : ( input ) => {
872+ if ( input . sessionID === chat . id ) return Deferred . succeed ( injected , input ) . pipe ( Effect . as ( reply ( input , "injected" ) ) )
873+ return Effect . succeed ( reply ( input , "done" ) )
874+ } ,
875+ }
876+ const execute = ( ) =>
877+ def . execute (
878+ {
879+ description : "inspect bug" ,
880+ prompt : "look into the cache key path" ,
881+ subagent_type : "general" ,
882+ background : true ,
883+ } ,
884+ {
885+ sessionID : chat . id ,
886+ messageID : assistant . id ,
887+ agent : "build" ,
888+ abort : new AbortController ( ) . signal ,
889+ extra : { promptOps } ,
890+ messages : [ ] ,
891+ metadata : ( ) => Effect . void ,
892+ ask : ( ) => Effect . void ,
893+ } ,
894+ )
895+
896+ yield * execute ( )
897+ const blank = yield * Deferred . await ( injected )
898+ expect ( blank . parts [ 0 ] ?. type ) . toBe ( "text" )
899+ if ( blank . parts [ 0 ] ?. type === "text" ) expect ( blank . parts [ 0 ] . text ) . toContain ( "Task failed" )
900+
901+ error = "real task error"
902+ const injectedReal = yield * Deferred . make < SessionPrompt . PromptInput > ( )
903+ const realPromptOps = { ...promptOps , prompt : ( input : SessionPrompt . PromptInput ) => Deferred . succeed ( injectedReal , input ) . pipe ( Effect . as ( reply ( input , "injected" ) ) ) }
904+ yield * def . execute (
905+ {
906+ description : "inspect bug" ,
907+ prompt : "look into the cache key path" ,
908+ subagent_type : "general" ,
909+ background : true ,
910+ } ,
911+ {
912+ sessionID : chat . id ,
913+ messageID : assistant . id ,
914+ agent : "build" ,
915+ abort : new AbortController ( ) . signal ,
916+ extra : { promptOps : realPromptOps } ,
917+ messages : [ ] ,
918+ metadata : ( ) => Effect . void ,
919+ ask : ( ) => Effect . void ,
920+ } ,
921+ )
922+ const real = yield * Deferred . await ( injectedReal )
923+ expect ( real . parts [ 0 ] ?. type ) . toBe ( "text" )
924+ if ( real . parts [ 0 ] ?. type === "text" ) expect ( real . parts [ 0 ] . text ) . toContain ( "real task error" )
925+ } ) ,
926+ )
927+
843928 background . instance ( "background task completion does not wait for the parent async prompt" , ( ) =>
844929 Effect . gen ( function * ( ) {
845930 const jobs = yield * BackgroundJob . Service
0 commit comments