@@ -98,7 +98,11 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => {
9898 it ( "calls child.start() only after parent metadata is persisted (no race condition)" , async ( ) => {
9999 const callOrder : string [ ] = [ ]
100100
101- const parentTask = { taskId : "parent-1" , emit : vi . fn ( ) } as any
101+ const parentTask = {
102+ taskId : "parent-1" ,
103+ emit : vi . fn ( ) ,
104+ getTaskMode : vi . fn ( ) . mockResolvedValue ( "architect" ) ,
105+ } as any
102106 const childStart = vi . fn ( ( ) => callOrder . push ( "child.start" ) )
103107
104108 const updateTaskHistory = vi . fn ( async ( ) => {
@@ -143,25 +147,32 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => {
143147 expect ( callOrder ) . toEqual ( [ "createTask" , "updateTaskHistory" , "child.start" ] )
144148 } )
145149
146- it ( "does not start the child when parent delegation metadata cannot be persisted" , async ( ) => {
147- const parentTask = { taskId : "parent-1" , emit : vi . fn ( ) } as any
150+ it ( "removes the paused child when parent delegation metadata cannot be persisted" , async ( ) => {
151+ const parentTask = {
152+ taskId : "parent-1" ,
153+ emit : vi . fn ( ) ,
154+ getTaskMode : vi . fn ( ) . mockResolvedValue ( "architect" ) ,
155+ } as any
148156 const childStart = vi . fn ( )
149157 const persistError = new Error ( "history write failed" )
150158
151159 const providerEmit = vi . fn ( )
152160 const updateTaskHistory = vi . fn ( ) . mockRejectedValue ( persistError )
153161 const removeClineFromStack = vi . fn ( ) . mockResolvedValue ( undefined )
162+ const deleteTaskFromState = vi . fn ( ) . mockResolvedValue ( undefined )
154163 const createTask = vi . fn ( ) . mockResolvedValue ( { taskId : "child-1" , start : childStart } )
164+ const createTaskWithHistoryItem = vi . fn ( ) . mockResolvedValue ( undefined )
155165 const handleModeSwitch = vi . fn ( ) . mockResolvedValue ( undefined )
166+ const parentHistory = {
167+ id : "parent-1" ,
168+ task : "Parent" ,
169+ tokensIn : 0 ,
170+ tokensOut : 0 ,
171+ totalCost : 0 ,
172+ childIds : [ ] ,
173+ }
156174 const getTaskWithId = vi . fn ( ) . mockResolvedValue ( {
157- historyItem : {
158- id : "parent-1" ,
159- task : "Parent" ,
160- tokensIn : 0 ,
161- tokensOut : 0 ,
162- totalCost : 0 ,
163- childIds : [ ] ,
164- } ,
175+ historyItem : parentHistory ,
165176 } )
166177
167178 const provider = {
@@ -171,6 +182,8 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => {
171182 createTask,
172183 getTaskWithId,
173184 updateTaskHistory,
185+ deleteTaskFromState,
186+ createTaskWithHistoryItem,
174187 handleModeSwitch,
175188 log : vi . fn ( ) ,
176189 } as unknown as ClineProvider
@@ -190,6 +203,72 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => {
190203 startTask : false ,
191204 } )
192205 expect ( childStart ) . not . toHaveBeenCalled ( )
206+ expect ( removeClineFromStack ) . toHaveBeenNthCalledWith ( 1 , { skipDelegationRepair : true } )
207+ expect ( removeClineFromStack ) . toHaveBeenNthCalledWith ( 2 , { skipDelegationRepair : true } )
208+ expect ( deleteTaskFromState ) . toHaveBeenCalledWith ( "child-1" )
209+ expect ( createTaskWithHistoryItem ) . toHaveBeenCalledWith (
210+ { ...parentHistory , mode : "architect" } ,
211+ { startTask : false } ,
212+ )
193213 expect ( providerEmit ) . not . toHaveBeenCalledWith ( RooCodeEventName . TaskDelegated , "parent-1" , "child-1" )
194214 } )
215+
216+ it ( "logs rollback cleanup failures while preserving the parent metadata error" , async ( ) => {
217+ const parentTask = {
218+ taskId : "parent-1" ,
219+ emit : vi . fn ( ) ,
220+ getTaskMode : vi . fn ( ) . mockRejectedValue ( new Error ( "mode unavailable" ) ) ,
221+ } as any
222+ const childStart = vi . fn ( )
223+ const persistError = new Error ( "history write failed" )
224+ const log = vi . fn ( )
225+
226+ const removeClineFromStack = vi
227+ . fn ( )
228+ . mockResolvedValueOnce ( undefined )
229+ . mockRejectedValueOnce ( new Error ( "child stack cleanup failed" ) )
230+ const deleteTaskFromState = vi . fn ( ) . mockRejectedValue ( new Error ( "child history cleanup failed" ) )
231+ const createTaskWithHistoryItem = vi . fn ( ) . mockRejectedValue ( new Error ( "parent rehydrate failed" ) )
232+ const parentHistory = {
233+ id : "parent-1" ,
234+ task : "Parent" ,
235+ tokensIn : 0 ,
236+ tokensOut : 0 ,
237+ totalCost : 0 ,
238+ childIds : [ ] ,
239+ mode : "ask" ,
240+ }
241+
242+ const provider = {
243+ emit : vi . fn ( ) ,
244+ getCurrentTask : vi . fn ( ( ) => parentTask ) ,
245+ removeClineFromStack,
246+ createTask : vi . fn ( ) . mockResolvedValue ( { taskId : "child-1" , start : childStart } ) ,
247+ getTaskWithId : vi . fn ( ) . mockResolvedValue ( { historyItem : parentHistory } ) ,
248+ updateTaskHistory : vi . fn ( ) . mockRejectedValue ( persistError ) ,
249+ deleteTaskFromState,
250+ createTaskWithHistoryItem,
251+ handleModeSwitch : vi . fn ( ) . mockResolvedValue ( undefined ) ,
252+ log,
253+ } as unknown as ClineProvider
254+
255+ await expect (
256+ ( ClineProvider . prototype as any ) . delegateParentAndOpenChild . call ( provider , {
257+ parentTaskId : "parent-1" ,
258+ message : "Do something" ,
259+ initialTodos : [ ] ,
260+ mode : "code" ,
261+ } ) ,
262+ ) . rejects . toThrow ( "history write failed" )
263+
264+ expect ( childStart ) . not . toHaveBeenCalled ( )
265+ expect ( deleteTaskFromState ) . toHaveBeenCalledWith ( "child-1" )
266+ expect ( createTaskWithHistoryItem ) . toHaveBeenCalledWith ( parentHistory , { startTask : false } )
267+ expect ( log ) . toHaveBeenCalledWith ( expect . stringContaining ( "Failed to capture parent mode" ) )
268+ expect ( log ) . toHaveBeenCalledWith (
269+ expect . stringContaining ( "Failed to remove child child-1 after parent metadata" ) ,
270+ )
271+ expect ( log ) . toHaveBeenCalledWith ( expect . stringContaining ( "Failed to remove child child-1 history" ) )
272+ expect ( log ) . toHaveBeenCalledWith ( expect . stringContaining ( "Failed to rehydrate parent parent-1" ) )
273+ } )
195274} )
0 commit comments