11// npx vitest run __tests__/removeClineFromStack-delegation.spec.ts
22
3- import { describe , it , expect , vi } from "vitest"
3+ import { describe , it , expect , vi , type MockedFunction } from "vitest"
44import { ClineProvider } from "../core/webview/ClineProvider"
55import { TaskRegistry } from "../core/task/TaskRegistry"
6+ import { type Task } from "../core/task/Task"
67import { makeProviderStub } from "./helpers/provider-stub"
78
9+ type MockTask = Pick < Task , "taskId" | "instanceId" > &
10+ Partial < Pick < Task , "parentTaskId" | "abort" | "abandoned" > > & {
11+ emit : ReturnType < typeof vi . fn >
12+ abortTask : ReturnType < typeof vi . fn >
13+ }
14+
15+ type PrivateClineProviderMethods = {
16+ removeClineFromStack : ( this : ClineProvider ) => ReturnType < ClineProvider [ "removeClineFromStack" ] >
17+ markDelegatedChildInterrupted : (
18+ this : ClineProvider ,
19+ ...args : Parameters < ClineProvider [ "markDelegatedChildInterrupted" ] >
20+ ) => ReturnType < ClineProvider [ "markDelegatedChildInterrupted" ] >
21+ evictCurrentTask : ( this : ClineProvider ) => ReturnType < ClineProvider [ "evictCurrentTask" ] >
22+ }
23+
24+ const privateClineProvider = ClineProvider . prototype as unknown as PrivateClineProviderMethods
25+
826// After the refactor: removeClineFromStack() is pure lifecycle — it removes the focused task, aborts, and
927// cleans up listeners. It does NOT mutate delegation metadata. All delegated→active
1028// transitions are owned by reopenParentFromDelegation() (normal child completion) or
@@ -13,7 +31,7 @@ import { makeProviderStub } from "./helpers/provider-stub"
1331function buildMockProvider ( opts : {
1432 childTaskId : string
1533 parentTaskId ?: string
16- parentHistoryItem ?: Record < string , any >
34+ parentHistoryItem ?: Record < string , unknown >
1735 childStatus ?: string
1836} ) {
1937 const childTask = {
@@ -32,13 +50,13 @@ function buildMockProvider(opts: {
3250 throw new Error ( "Task not found" )
3351 } )
3452
35- const taskHistoryStoreData : Record < string , any > = { }
53+ const taskHistoryStoreData : Record < string , unknown > = { }
3654 if ( opts . childStatus ) {
3755 taskHistoryStoreData [ opts . childTaskId ] = { status : opts . childStatus }
3856 }
3957
4058 const provider = makeProviderStub ( {
41- clineStack : [ childTask ] as any [ ] ,
59+ clineStack : [ childTask ] as unknown as Task [ ] ,
4260 taskEventListeners : new Map ( ) ,
4361 log : vi . fn ( ) ,
4462 getTaskWithId,
@@ -54,7 +72,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
5472 const { provider, childTask } = buildMockProvider ( { childTaskId : "child-1" } )
5573 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 1 )
5674
57- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
75+ await privateClineProvider . removeClineFromStack . call ( provider )
5876
5977 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 0 )
6078 expect ( childTask . abortTask ) . toHaveBeenCalledWith ( true )
@@ -75,15 +93,15 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
7593 abortTask : vi . fn ( ) . mockResolvedValue ( undefined ) ,
7694 }
7795 const provider = makeProviderStub ( {
78- tasks : [ focusedTask , topTask ] as any [ ] ,
96+ tasks : [ focusedTask , topTask ] as unknown as Task [ ] ,
7997 taskEventListeners : new Map ( ) ,
8098 log : vi . fn ( ) ,
8199 getTaskWithId : vi . fn ( ) ,
82100 updateTaskHistory : vi . fn ( ) ,
83101 } )
84102 provider [ "taskRegistry" ] . setCurrent ( "focused-1" )
85103
86- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
104+ await privateClineProvider . removeClineFromStack . call ( provider )
87105
88106 expect ( provider [ "taskRegistry" ] . taskIds ) . toEqual ( [ "top-1" ] )
89107 expect ( provider [ "taskRegistry" ] . current ) . toBe ( topTask )
@@ -103,7 +121,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
103121 } ,
104122 } )
105123
106- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
124+ await privateClineProvider . removeClineFromStack . call ( provider )
107125
108126 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 0 )
109127 // Navigation/disposal must never silently flip the parent to active
@@ -123,7 +141,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
123141 childStatus : "interrupted" ,
124142 } )
125143
126- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
144+ await privateClineProvider . removeClineFromStack . call ( provider )
127145
128146 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 0 )
129147 expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
@@ -135,7 +153,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
135153 childTaskId : "standalone-1" ,
136154 } )
137155
138- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
156+ await privateClineProvider . removeClineFromStack . call ( provider )
139157
140158 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 0 )
141159 expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
@@ -144,14 +162,14 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
144162
145163 it ( "handles empty stack gracefully" , async ( ) => {
146164 const provider = makeProviderStub ( {
147- clineStack : [ ] as any [ ] ,
165+ clineStack : [ ] as Task [ ] ,
148166 taskEventListeners : new Map ( ) ,
149167 log : vi . fn ( ) ,
150168 getTaskWithId : vi . fn ( ) ,
151169 updateTaskHistory : vi . fn ( ) ,
152170 } )
153171
154- await expect ( ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider ) ) . resolves . not . toThrow ( )
172+ await expect ( privateClineProvider . removeClineFromStack . call ( provider ) ) . resolves . not . toThrow ( )
155173
156174 expect ( provider [ "getTaskWithId" ] ) . not . toHaveBeenCalled ( )
157175 expect ( provider [ "updateTaskHistory" ] ) . not . toHaveBeenCalled ( )
@@ -190,7 +208,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
190208 const postMessageToWebview = vi . fn ( ) . mockResolvedValue ( undefined )
191209
192210 const provider = makeProviderStub ( {
193- clineStack : [ ] as any [ ] ,
211+ clineStack : [ ] as Task [ ] ,
194212 taskEventListeners : new Map ( ) ,
195213 log : vi . fn ( ) ,
196214 getTaskWithId,
@@ -201,7 +219,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
201219 } ,
202220 } )
203221
204- await ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
222+ await privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
205223 childTaskId,
206224 parentTaskId,
207225 } )
@@ -234,7 +252,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
234252 const updateTaskHistory = vi . fn ( ) . mockResolvedValue ( [ ] )
235253
236254 const provider = makeProviderStub ( {
237- clineStack : [ ] as any [ ] ,
255+ clineStack : [ ] as Task [ ] ,
238256 taskEventListeners : new Map ( ) ,
239257 log : vi . fn ( ) ,
240258 getTaskWithId : vi . fn ( ) ,
@@ -244,7 +262,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
244262 } ,
245263 } )
246264
247- await ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
265+ await privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
248266 childTaskId,
249267 parentTaskId,
250268 } )
@@ -266,7 +284,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
266284 } )
267285
268286 const provider = makeProviderStub ( {
269- clineStack : [ ] as any [ ] ,
287+ clineStack : [ ] as Task [ ] ,
270288 taskEventListeners : new Map ( ) ,
271289 log : vi . fn ( ) ,
272290 getTaskWithId,
@@ -276,7 +294,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
276294 } ,
277295 } )
278296
279- await ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
297+ await privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
280298 childTaskId,
281299 parentTaskId,
282300 } )
@@ -305,7 +323,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
305323 delegationTransitionLocks : new Map ( ) ,
306324 } )
307325 const provider = makeProviderStub ( {
308- clineStack : [ ] as any [ ] ,
326+ clineStack : [ ] as Task [ ] ,
309327 taskEventListeners : new Map ( ) ,
310328 log : vi . fn ( ) ,
311329 getTaskWithId,
@@ -330,7 +348,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
330348 } ,
331349 } )
332350
333- await ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
351+ await privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
334352 childTaskId,
335353 parentTaskId,
336354 } )
@@ -347,7 +365,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
347365 const getTaskWithId = vi . fn ( ) . mockRejectedValue ( new Error ( "store unavailable" ) )
348366
349367 const provider = makeProviderStub ( {
350- clineStack : [ ] as any [ ] ,
368+ clineStack : [ ] as Task [ ] ,
351369 taskEventListeners : new Map ( ) ,
352370 log,
353371 getTaskWithId,
@@ -358,7 +376,7 @@ describe("ClineProvider.markDelegatedChildInterrupted() — live eviction path",
358376 } )
359377
360378 await expect (
361- ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
379+ privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
362380 childTaskId,
363381 parentTaskId,
364382 } ) ,
@@ -406,7 +424,7 @@ describe("createTaskWithHistoryItem() navigation — does not mutate delegation
406424 const markDelegatedChildInterrupted = vi . fn ( ) . mockResolvedValue ( undefined )
407425
408426 const provider = makeProviderStub ( {
409- clineStack : [ childTask ] as any [ ] ,
427+ clineStack : [ childTask ] as unknown as Task [ ] ,
410428 taskEventListeners : new Map ( ) ,
411429 log : vi . fn ( ) ,
412430 getTaskWithId,
@@ -425,7 +443,7 @@ describe("createTaskWithHistoryItem() navigation — does not mutate delegation
425443 // Simulate the navigation logic from createTaskWithHistoryItem:
426444 // when the target is a delegated parent and current task is its interrupted child,
427445 // removeClineFromStack must NOT repair parent to active.
428- await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
446+ await privateClineProvider . removeClineFromStack . call ( provider )
429447
430448 // Parent must stay delegated — no write at all
431449 expect ( updateTaskHistory ) . not . toHaveBeenCalledWith ( expect . objectContaining ( { id : parentTaskId } ) )
@@ -460,7 +478,7 @@ describe("createTaskWithHistoryItem() navigation — does not mutate delegation
460478 const postMessageToWebview = vi . fn ( ) . mockResolvedValue ( undefined )
461479
462480 const provider = makeProviderStub ( {
463- clineStack : [ ] as any [ ] ,
481+ clineStack : [ ] as Task [ ] ,
464482 taskEventListeners : new Map ( ) ,
465483 log : vi . fn ( ) ,
466484 getTaskWithId,
@@ -476,7 +494,7 @@ describe("createTaskWithHistoryItem() navigation — does not mutate delegation
476494 } ,
477495 } )
478496
479- await ( ClineProvider . prototype as any ) . markDelegatedChildInterrupted . call ( provider , {
497+ await privateClineProvider . markDelegatedChildInterrupted . call ( provider , {
480498 childTaskId,
481499 parentTaskId,
482500 } )
@@ -512,15 +530,15 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
512530 const markDelegatedChildInterrupted = vi . fn ( ) . mockResolvedValue ( undefined )
513531
514532 const provider = makeProviderStub ( {
515- clineStack : [ childTask ] as any [ ] ,
533+ clineStack : [ childTask ] as unknown as Task [ ] ,
516534 taskEventListeners : new Map ( ) ,
517535 getCurrentTask : vi . fn ( ( ) => childTask ) ,
518536 taskHistoryStore : { get : vi . fn ( ( id : string ) => ( id === childTaskId ? childHistoryItem : undefined ) ) } ,
519537 markDelegatedChildInterrupted,
520538 log : vi . fn ( ) ,
521539 } )
522540
523- await ( ClineProvider . prototype as any ) . evictCurrentTask . call ( provider )
541+ await privateClineProvider . evictCurrentTask . call ( provider )
524542
525543 expect ( provider [ "taskRegistry" ] . length ) . toBe ( 0 )
526544 expect ( markDelegatedChildInterrupted ) . toHaveBeenCalledWith ( { childTaskId, parentTaskId } )
@@ -530,15 +548,15 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
530548 const markDelegatedChildInterrupted = vi . fn ( )
531549
532550 const provider = makeProviderStub ( {
533- clineStack : [ ] as any [ ] ,
551+ clineStack : [ ] as Task [ ] ,
534552 taskEventListeners : new Map ( ) ,
535553 getCurrentTask : vi . fn ( ( ) => undefined ) ,
536554 taskHistoryStore : { get : vi . fn ( ( ) => undefined ) } ,
537555 markDelegatedChildInterrupted,
538556 log : vi . fn ( ) ,
539557 } )
540558
541- await ( ClineProvider . prototype as any ) . evictCurrentTask . call ( provider )
559+ await privateClineProvider . evictCurrentTask . call ( provider )
542560
543561 expect ( markDelegatedChildInterrupted ) . not . toHaveBeenCalled ( )
544562 } )
@@ -554,7 +572,7 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
554572 const markDelegatedChildInterrupted = vi . fn ( )
555573
556574 const provider = makeProviderStub ( {
557- clineStack : [ childTask ] as any [ ] ,
575+ clineStack : [ childTask ] as unknown as Task [ ] ,
558576 taskEventListeners : new Map ( ) ,
559577 getCurrentTask : vi . fn ( ( ) => childTask ) ,
560578 taskHistoryStore : {
@@ -564,7 +582,7 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
564582 log : vi . fn ( ) ,
565583 } )
566584
567- await ( ClineProvider . prototype as any ) . evictCurrentTask . call ( provider )
585+ await privateClineProvider . evictCurrentTask . call ( provider )
568586
569587 expect ( markDelegatedChildInterrupted ) . not . toHaveBeenCalled ( )
570588 } )
@@ -584,7 +602,7 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
584602 const markDelegatedChildInterrupted = vi . fn ( ) . mockRejectedValue ( new Error ( "lock contention" ) )
585603
586604 const provider = makeProviderStub ( {
587- clineStack : [ childTask ] as any [ ] ,
605+ clineStack : [ childTask ] as unknown as Task [ ] ,
588606 taskEventListeners : new Map ( ) ,
589607 getCurrentTask : vi . fn ( ( ) => childTask ) ,
590608 taskHistoryStore : {
@@ -594,14 +612,14 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
594612 log : vi . fn ( ) ,
595613 } )
596614
597- await expect ( ( ClineProvider . prototype as any ) . evictCurrentTask . call ( provider ) ) . rejects . toThrow (
598- "lock contention" ,
599- )
615+ await expect ( privateClineProvider . evictCurrentTask . call ( provider ) ) . rejects . toThrow ( "lock contention" )
600616 } )
601617} )
602618
603619describe ( "onTaskCompleted callback — writes completed status before re-emitting" , ( ) => {
604- function buildCallbackProvider ( taskHistoryStoreGet : ( id : string ) => any ) {
620+ type CallbackHistoryItem = { id : string ; status ?: string ; [ key : string ] : unknown }
621+
622+ function buildCallbackProvider ( taskHistoryStoreGet : ( id : string ) => CallbackHistoryItem | undefined ) {
605623 const updateTaskHistory = vi . fn ( ) . mockResolvedValue ( [ ] )
606624 const emit = vi . fn ( )
607625 const log = vi . fn ( )
@@ -616,7 +634,7 @@ describe("onTaskCompleted callback — writes completed status before re-emittin
616634 listeners [ event ] = listeners [ event ] || [ ]
617635 listeners [ event ] . push ( fn )
618636 } ) ,
619- emit : vi . fn ( ( event : string , ...args : any [ ] ) => {
637+ emit : vi . fn ( ( event : string , ...args : unknown [ ] ) => {
620638 listeners [ event ] ?. forEach ( ( fn ) => fn ( ...args ) )
621639 } ) ,
622640 }
@@ -692,7 +710,7 @@ describe("onTaskCompleted callback — writes completed status before re-emittin
692710 const { onTaskCompleted, updateTaskHistory, log } = buildCallbackProvider ( ( id ) =>
693711 id === "task-1" ? existingItem : undefined ,
694712 )
695- ; ( updateTaskHistory as any ) . mockRejectedValue ( new Error ( "disk full" ) )
713+ vi . mocked ( updateTaskHistory ) . mockRejectedValue ( new Error ( "disk full" ) )
696714
697715 await expect ( onTaskCompleted ( "task-1" ) ) . resolves . not . toThrow ( )
698716
0 commit comments