@@ -192,4 +192,90 @@ describe("ClineProvider.removeClineFromStack() delegation awareness", () => {
192192 expect ( provider . getTaskWithId ) . not . toHaveBeenCalled ( )
193193 expect ( provider . updateTaskHistory ) . not . toHaveBeenCalled ( )
194194 } )
195+
196+ it ( "skips delegation repair when skipDelegationRepair option is true" , async ( ) => {
197+ const { provider, updateTaskHistory, getTaskWithId } = buildMockProvider ( {
198+ childTaskId : "child-1" ,
199+ parentTaskId : "parent-1" ,
200+ parentHistoryItem : {
201+ id : "parent-1" ,
202+ task : "Parent task" ,
203+ ts : 1000 ,
204+ number : 1 ,
205+ tokensIn : 0 ,
206+ tokensOut : 0 ,
207+ totalCost : 0 ,
208+ status : "delegated" ,
209+ awaitingChildId : "child-1" ,
210+ delegatedToId : "child-1" ,
211+ childIds : [ "child-1" ] ,
212+ } ,
213+ } )
214+
215+ // Call with skipDelegationRepair: true (as delegateParentAndOpenChild would)
216+ await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider , { skipDelegationRepair : true } )
217+
218+ // Stack should be empty after pop
219+ expect ( provider . clineStack ) . toHaveLength ( 0 )
220+
221+ // Parent lookup should NOT have been called — repair was skipped entirely
222+ expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
223+ expect ( updateTaskHistory ) . not . toHaveBeenCalled ( )
224+ } )
225+
226+ it ( "does NOT reset grandparent during A→B→C nested delegation transition" , async ( ) => {
227+ // Scenario: A delegated to B, B is now delegating to C.
228+ // delegateParentAndOpenChild() pops B via removeClineFromStack({ skipDelegationRepair: true }).
229+ // Grandparent A should remain "delegated" — its metadata must not be repaired.
230+ const grandparentHistory = {
231+ id : "task-A" ,
232+ task : "Grandparent task" ,
233+ ts : 1000 ,
234+ number : 1 ,
235+ tokensIn : 0 ,
236+ tokensOut : 0 ,
237+ totalCost : 0 ,
238+ status : "delegated" ,
239+ awaitingChildId : "task-B" ,
240+ delegatedToId : "task-B" ,
241+ childIds : [ "task-B" ] ,
242+ }
243+
244+ const taskB = {
245+ taskId : "task-B" ,
246+ instanceId : "inst-B" ,
247+ parentTaskId : "task-A" ,
248+ emit : vi . fn ( ) ,
249+ abortTask : vi . fn ( ) . mockResolvedValue ( undefined ) ,
250+ }
251+
252+ const getTaskWithId = vi . fn ( ) . mockImplementation ( async ( id : string ) => {
253+ if ( id === "task-A" ) {
254+ return { historyItem : { ...grandparentHistory } }
255+ }
256+ throw new Error ( "Task not found" )
257+ } )
258+ const updateTaskHistory = vi . fn ( ) . mockResolvedValue ( [ ] )
259+
260+ const provider = {
261+ clineStack : [ taskB ] as any [ ] ,
262+ taskEventListeners : new Map ( ) ,
263+ log : vi . fn ( ) ,
264+ getTaskWithId,
265+ updateTaskHistory,
266+ }
267+
268+ // Simulate what delegateParentAndOpenChild does: pop B with skipDelegationRepair
269+ await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider , { skipDelegationRepair : true } )
270+
271+ // B was popped
272+ expect ( provider . clineStack ) . toHaveLength ( 0 )
273+
274+ // Grandparent A should NOT have been looked up or modified
275+ expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
276+ expect ( updateTaskHistory ) . not . toHaveBeenCalled ( )
277+
278+ // Grandparent A's metadata remains intact (delegated, awaitingChildId: task-B)
279+ // The caller (delegateParentAndOpenChild) will update A to point to C separately.
280+ } )
195281} )
0 commit comments