@@ -5,6 +5,10 @@ import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
55import { setDefaultSuiteTimeout } from "./test-utils"
66import { sleep , waitFor , waitUntilCompleted } from "./utils"
77import {
8+ SCHED_COMPLETED_PROMPT ,
9+ SCHED_COMPLETED_RESULT ,
10+ SCHED_STANDALONE_FOLLOWUP_ANSWER ,
11+ SCHED_STANDALONE_PROMPT ,
812 SUBTASK_ABANDON_CHILD_FOLLOWUP_ANSWER ,
913 SUBTASK_ABANDON_PARENT_PROMPT ,
1014 SUBTASK_API_HANG_CHILD_MARKER ,
@@ -945,4 +949,139 @@ suite("Roo Code Subtasks", function () {
945949 await waitFor ( ( ) => api . getCurrentTaskStack ( ) . length === 0 ) . catch ( ( ) => { } )
946950 }
947951 } )
952+
953+ // TaskScheduler regression: resumeTask on a completed task must show resume_completed_task ask.
954+ // Before the CodeRabbit fix, createTaskWithHistoryItem bypassed the scheduler and called
955+ // Task.run() via the constructor's startTask: true default, causing run() to call startTask()
956+ // (clearing history) instead of resumeTaskFromHistory().
957+ test ( "resumeTask on a completed task presents resume_completed_task ask" , async ( ) => {
958+ const api = globalThis . api
959+ const asks : Record < string , ClineMessage [ ] > = { }
960+ const says : Record < string , ClineMessage [ ] > = { }
961+
962+ const messageHandler = ( { taskId, message } : { taskId : string ; message : ClineMessage } ) => {
963+ if ( message . type === "ask" ) {
964+ asks [ taskId ] = asks [ taskId ] || [ ]
965+ asks [ taskId ] . push ( message )
966+ }
967+ if ( message . type === "say" && message . partial === false ) {
968+ says [ taskId ] = says [ taskId ] || [ ]
969+ says [ taskId ] . push ( message )
970+ }
971+ }
972+
973+ api . on ( RooCodeEventName . Message , messageHandler )
974+
975+ try {
976+ // Run a task to completion.
977+ const taskId = await waitUntilCompleted ( {
978+ api,
979+ start : ( ) =>
980+ api . startNewTask ( {
981+ configuration : {
982+ mode : "ask" ,
983+ autoApprovalEnabled : true ,
984+ enableCheckpoints : false ,
985+ } ,
986+ text : SCHED_COMPLETED_PROMPT ,
987+ } ) ,
988+ } )
989+
990+ assert . strictEqual (
991+ says [ taskId ] ?. find ( ( { say } ) => say === "completion_result" ) ?. text ?. trim ( ) ,
992+ SCHED_COMPLETED_RESULT ,
993+ "Task should complete with expected result" ,
994+ )
995+
996+ // Re-open it via resumeTask — should hit resumeTaskFromHistory(), showing resume_completed_task.
997+ await api . resumeTask ( taskId )
998+
999+ await waitFor (
1000+ ( ) => asks [ taskId ] ?. some ( ( { type, ask } ) => type === "ask" && ask === "resume_completed_task" ) ?? false ,
1001+ )
1002+ } finally {
1003+ api . off ( RooCodeEventName . Message , messageHandler )
1004+ while ( api . getCurrentTaskStack ( ) . length > 0 ) {
1005+ await api . clearCurrentTask ( )
1006+ }
1007+ await sleep ( 500 )
1008+ }
1009+ } )
1010+
1011+ // TaskScheduler regression: resumeTask on an interrupted standalone task must show resume_task
1012+ // ask and allow the task to complete normally via the scheduler slot.
1013+ test ( "resumeTask on an interrupted standalone task presents resume_task ask and completes" , async ( ) => {
1014+ const api = globalThis . api
1015+ const asks : Record < string , ClineMessage [ ] > = { }
1016+ const says : Record < string , ClineMessage [ ] > = { }
1017+
1018+ const messageHandler = ( { taskId, message } : { taskId : string ; message : ClineMessage } ) => {
1019+ if ( message . type === "ask" ) {
1020+ asks [ taskId ] = asks [ taskId ] || [ ]
1021+ asks [ taskId ] . push ( message )
1022+ }
1023+ if ( message . type === "say" && message . partial === false ) {
1024+ says [ taskId ] = says [ taskId ] || [ ]
1025+ says [ taskId ] . push ( message )
1026+ }
1027+ }
1028+
1029+ api . on ( RooCodeEventName . Message , messageHandler )
1030+
1031+ let taskId : string | undefined
1032+
1033+ try {
1034+ taskId = await api . startNewTask ( {
1035+ configuration : {
1036+ mode : "ask" ,
1037+ autoApprovalEnabled : true ,
1038+ enableCheckpoints : false ,
1039+ } ,
1040+ text : SCHED_STANDALONE_PROMPT ,
1041+ } )
1042+
1043+ // Wait until the task pauses at the follow-up question.
1044+ await waitFor ( ( ) => asks [ taskId ! ] ?. some ( ( { type, ask } ) => type === "ask" && ask === "followup" ) ?? false )
1045+
1046+ // Cancel it — the task becomes interrupted.
1047+ await api . cancelCurrentTask ( )
1048+
1049+ await waitFor (
1050+ ( ) => asks [ taskId ! ] ?. some ( ( { type, ask } ) => type === "ask" && ask === "resume_task" ) ?? false ,
1051+ )
1052+
1053+ // Resume via scheduler path (createTaskWithHistoryItem).
1054+ const askCountBeforeResume = asks [ taskId ! ] ?. length ?? 0
1055+ await api . resumeTask ( taskId ! )
1056+
1057+ await waitFor ( ( ) =>
1058+ ( asks [ taskId ! ] ?? [ ] )
1059+ . slice ( askCountBeforeResume )
1060+ . some ( ( { type, ask } ) => type === "ask" && ask === "resume_task" ) ,
1061+ )
1062+
1063+ // Sending the answer both acknowledges the resume_task ask and answers the pending
1064+ // follow-up question from the original task, completing the task.
1065+ const completedTaskId = await waitUntilCompleted ( {
1066+ api,
1067+ start : async ( ) => {
1068+ await api . sendMessage ( SCHED_STANDALONE_FOLLOWUP_ANSWER )
1069+ return taskId !
1070+ } ,
1071+ } )
1072+
1073+ assert . strictEqual ( completedTaskId , taskId , "The resumed standalone task should complete" )
1074+ assert . strictEqual (
1075+ says [ taskId ! ] ?. find ( ( { say } ) => say === "completion_result" ) ?. text ?. trim ( ) ,
1076+ SCHED_STANDALONE_FOLLOWUP_ANSWER ,
1077+ "Task should complete with the follow-up answer as result" ,
1078+ )
1079+ } finally {
1080+ api . off ( RooCodeEventName . Message , messageHandler )
1081+ while ( api . getCurrentTaskStack ( ) . length > 0 ) {
1082+ await api . clearCurrentTask ( )
1083+ }
1084+ await sleep ( 500 )
1085+ }
1086+ } )
9481087} )
0 commit comments