@@ -92,9 +92,38 @@ function createAssistantMessage(
9292 completed ?: boolean ;
9393 error ?: unknown ;
9494 summary ?: boolean ;
95+ finish ?: string ;
96+ stepFinishReason ?: string ;
9597 parts ?: Array < Record < string , unknown > > ;
9698 } = { } ,
9799) {
100+ const generatedParts : Array < Record < string , unknown > > = [ ] ;
101+ if ( text ) {
102+ generatedParts . push ( {
103+ id : "part-1" ,
104+ sessionID : "session-1" ,
105+ messageID : "assistant-1" ,
106+ type : "text" ,
107+ text,
108+ } ) ;
109+ }
110+ if ( options . stepFinishReason ) {
111+ generatedParts . push ( {
112+ id : "finish-1" ,
113+ sessionID : "session-1" ,
114+ messageID : "assistant-1" ,
115+ type : "step-finish" ,
116+ reason : options . stepFinishReason ,
117+ cost : 0 ,
118+ tokens : {
119+ input : 0 ,
120+ output : 0 ,
121+ reasoning : 0 ,
122+ cache : { read : 0 , write : 0 } ,
123+ } ,
124+ } ) ;
125+ }
126+
98127 return {
99128 info : {
100129 id : "assistant-1" ,
@@ -118,12 +147,9 @@ function createAssistantMessage(
118147 } ,
119148 error : options . error ,
120149 summary : options . summary ,
150+ finish : options . finish ,
121151 } ,
122- parts :
123- options . parts ??
124- ( text
125- ? [ { id : "part-1" , sessionID : "session-1" , messageID : "assistant-1" , type : "text" , text } ]
126- : [ ] ) ,
152+ parts : options . parts ?? generatedParts ,
127153 } ;
128154}
129155
@@ -314,7 +340,7 @@ describe("scheduled-task/executor", () => {
314340 } ) ;
315341 mocked . promptAsyncMock . mockResolvedValueOnce ( { data : undefined , error : null } ) ;
316342 mocked . messagesMock . mockResolvedValue ( {
317- data : [ createAssistantMessage ( "" , { completed : true } ) ] ,
343+ data : [ createAssistantMessage ( "" , { completed : true , stepFinishReason : "stop" } ) ] ,
318344 error : null ,
319345 } ) ;
320346
@@ -341,7 +367,8 @@ describe("scheduled-task/executor", () => {
341367 assistantMessage : expect . objectContaining ( {
342368 completed : true ,
343369 summary : false ,
344- parts : [ ] ,
370+ finish : "stop" ,
371+ parts : [ expect . objectContaining ( { type : "step-finish" , reason : "stop" } ) ] ,
345372 } ) ,
346373 } ) ,
347374 ) ;
@@ -391,6 +418,59 @@ describe("scheduled-task/executor", () => {
391418 expect ( mocked . deleteMock ) . toHaveBeenCalledWith ( { sessionID : "session-1" } ) ;
392419 } ) ;
393420
421+ it ( "waits for the final assistant response after completed tool-call turns" , async ( ) => {
422+ const { executeScheduledTask } = await import ( "../../src/scheduled-task/executor.js" ) ;
423+
424+ const toolCallTurn = createAssistantMessage ( "" , {
425+ completed : true ,
426+ stepFinishReason : "tool-calls" ,
427+ } ) ;
428+
429+ mocked . createMock . mockResolvedValueOnce ( {
430+ data : { id : "session-1" , directory : "D:\\Projects\\Repo" , title : "Scheduled task run" } ,
431+ error : null ,
432+ } ) ;
433+ mocked . promptAsyncMock . mockResolvedValueOnce ( { data : undefined , error : null } ) ;
434+ mocked . messagesMock
435+ . mockResolvedValueOnce ( { data : [ toolCallTurn ] , error : null } )
436+ . mockResolvedValueOnce ( { data : [ toolCallTurn ] , error : null } )
437+ . mockResolvedValueOnce ( { data : [ toolCallTurn ] , error : null } )
438+ . mockResolvedValueOnce ( { data : [ toolCallTurn ] , error : null } )
439+ . mockResolvedValueOnce ( {
440+ data : [
441+ toolCallTurn ,
442+ createAssistantMessage ( "SCHEDULED_TASK_FINAL_OK" , {
443+ completed : true ,
444+ stepFinishReason : "stop" ,
445+ } ) ,
446+ ] ,
447+ error : null ,
448+ } ) ;
449+ mocked . statusMock . mockResolvedValue ( {
450+ data : { "session-1" : { type : "busy" } } ,
451+ error : null ,
452+ } ) ;
453+
454+ vi . useFakeTimers ( ) ;
455+
456+ const resultPromise = executeScheduledTask ( createTask ( ) ) ;
457+
458+ await vi . advanceTimersByTimeAsync ( 8000 ) ;
459+
460+ await expect ( resultPromise ) . resolves . toMatchObject ( {
461+ status : "success" ,
462+ resultText : "SCHEDULED_TASK_FINAL_OK" ,
463+ errorMessage : null ,
464+ } ) ;
465+ expect ( mocked . messagesMock ) . toHaveBeenCalledTimes ( 5 ) ;
466+ expect ( mocked . statusMock ) . toHaveBeenCalledTimes ( 4 ) ;
467+ expect ( mocked . deleteMock ) . toHaveBeenCalledWith ( { sessionID : "session-1" } ) ;
468+ expect ( mocked . loggerWarnMock ) . not . toHaveBeenCalledWith (
469+ "[ScheduledTaskExecutor] Empty completed assistant response diagnostics" ,
470+ expect . anything ( ) ,
471+ ) ;
472+ } ) ;
473+
394474 it ( "ignores technical summary assistant messages when finding the scheduled task result" , async ( ) => {
395475 const { executeScheduledTask } = await import ( "../../src/scheduled-task/executor.js" ) ;
396476
0 commit comments