@@ -100,14 +100,15 @@ function stubOps(opts?: {
100100 onPrompt ?: ( input : SessionPrompt . PromptInput ) => void
101101 text ?: string
102102 error ?: NonNullable < SessionV1 . Assistant [ "error" ] >
103+ toolError ?: string
103104} ) : TaskPromptOps {
104105 return {
105106 cancel : ( ) => Effect . void ,
106107 resolvePromptParts : ( template ) => Effect . succeed ( [ { type : "text" as const , text : template } ] ) ,
107108 prompt : ( input ) =>
108109 Effect . sync ( ( ) => {
109110 opts ?. onPrompt ?.( input )
110- return reply ( input , opts ?. text ?? "done" , opts ?. error )
111+ return reply ( input , opts ?. text ?? "done" , opts ?. error , opts ?. toolError )
111112 } ) ,
112113 }
113114}
@@ -116,6 +117,7 @@ function reply(
116117 input : SessionPrompt . PromptInput ,
117118 text : string ,
118119 error ?: NonNullable < SessionV1 . Assistant [ "error" ] > ,
120+ toolError ?: string ,
119121) : SessionV1 . WithParts {
120122 const id = MessageID . ascending ( )
121123 return {
@@ -143,6 +145,24 @@ function reply(
143145 type : "text" ,
144146 text,
145147 } ,
148+ ...( toolError
149+ ? [
150+ {
151+ id : PartID . ascending ( ) ,
152+ messageID : id ,
153+ sessionID : input . sessionID ,
154+ type : "tool" as const ,
155+ tool : "read" ,
156+ callID : "call-1" ,
157+ state : {
158+ status : "error" as const ,
159+ input : { filePath : "/external" } ,
160+ error : toolError ,
161+ time : { start : Date . now ( ) , end : Date . now ( ) } ,
162+ } ,
163+ } ,
164+ ]
165+ : [ ] ) ,
146166 ] ,
147167 }
148168}
@@ -307,6 +327,50 @@ describe("tool.task", () => {
307327 } ) ,
308328 )
309329
330+ it . instance ( "execute surfaces terminal child tool errors with a resumable task_id" , ( ) =>
331+ Effect . gen ( function * ( ) {
332+ const sessions = yield * Session . Service
333+ const { chat, assistant } = yield * seed ( )
334+ const tool = yield * TaskTool
335+ const def = yield * tool . init ( )
336+
337+ const exit = yield * def
338+ . execute (
339+ {
340+ description : "inspect external directory" ,
341+ prompt : "read the external directory" ,
342+ subagent_type : "general" ,
343+ } ,
344+ {
345+ sessionID : chat . id ,
346+ messageID : assistant . id ,
347+ agent : "build" ,
348+ abort : new AbortController ( ) . signal ,
349+ extra : {
350+ promptOps : stubOps ( {
351+ text : "I will inspect the directory." ,
352+ toolError : "The user rejected permission to use this specific tool call." ,
353+ } ) ,
354+ } ,
355+ messages : [ ] ,
356+ metadata : ( ) => Effect . void ,
357+ ask : ( ) => Effect . void ,
358+ } ,
359+ )
360+ . pipe ( Effect . exit )
361+
362+ expect ( Exit . isFailure ( exit ) ) . toBe ( true )
363+ if ( Exit . isSuccess ( exit ) ) throw new Error ( "expected task failure" )
364+ const child = ( yield * sessions . children ( chat . id ) ) [ 0 ]
365+ const failure = Cause . squash ( exit . cause )
366+ expect ( failure ) . toBeInstanceOf ( Error )
367+ if ( ! ( failure instanceof Error ) ) throw new Error ( "expected Error defect" )
368+ expect ( failure . message ) . toBe (
369+ `Subagent failed (task_id: ${ child ?. id } ): The user rejected permission to use this specific tool call.` ,
370+ )
371+ } ) ,
372+ )
373+
310374 it . instance ( "execute asks by default and skips checks when bypassed" , ( ) =>
311375 Effect . gen ( function * ( ) {
312376 const { chat, assistant } = yield * seed ( )
0 commit comments