1- import { describe , it , expect , vi , beforeEach , afterEach } from ' vitest'
2- import { Task } from ' ../core/task/Task'
3- import { ClineProvider } from ' ../core/webview/ClineProvider'
1+ import { describe , it , expect , vi , beforeEach , afterEach } from " vitest"
2+ import { Task } from " ../core/task/Task"
3+ import { ClineProvider } from " ../core/webview/ClineProvider"
44
55// Mock dependencies
6- vi . mock ( ' vscode' , ( ) => ( {
6+ vi . mock ( " vscode" , ( ) => ( {
77 window : {
88 showErrorMessage : vi . fn ( ) ,
99 showInformationMessage : vi . fn ( ) ,
@@ -22,26 +22,26 @@ vi.mock('vscode', () => ({
2222 EventEmitter : vi . fn ( ) ,
2323} ) )
2424
25- vi . mock ( ' @anthropic-ai/sdk' , ( ) => ( {
25+ vi . mock ( " @anthropic-ai/sdk" , ( ) => ( {
2626 Anthropic : vi . fn ( ) ,
2727} ) )
2828
29- vi . mock ( ' delay' , ( ) => ( {
29+ vi . mock ( " delay" , ( ) => ( {
3030 default : vi . fn ( ) ,
3131} ) )
3232
33- vi . mock ( ' axios' , ( ) => ( {
33+ vi . mock ( " axios" , ( ) => ( {
3434 default : {
3535 get : vi . fn ( ) ,
3636 post : vi . fn ( ) ,
3737 } ,
3838} ) )
3939
40- vi . mock ( ' p-wait-for' , ( ) => ( {
40+ vi . mock ( " p-wait-for" , ( ) => ( {
4141 default : vi . fn ( ) ,
4242} ) )
4343
44- describe ( ' Gray State Recovery' , ( ) => {
44+ describe ( " Gray State Recovery" , ( ) => {
4545 let mockTask : Partial < Task >
4646 let mockProvider : Partial < ClineProvider >
4747 let mockWebview : any
@@ -57,184 +57,138 @@ describe('Gray State Recovery', () => {
5757
5858 // Mock task with gray state scenario
5959 mockTask = {
60- taskId : ' test-task-123' ,
60+ taskId : " test-task-123" ,
6161 isStreaming : false ,
6262 isPaused : false ,
63- enableButtons : false ,
6463 abort : false ,
6564 resumePausedTask : vi . fn ( ) ,
6665 recursivelyMakeClineRequests : vi . fn ( ) ,
66+ say : vi . fn ( ) ,
6767 }
6868
6969 // Mock provider
7070 mockProvider = {
71- taskStack : [ mockTask as Task ] ,
72- currentTask : mockTask as Task ,
73- webview : mockWebview ,
71+ getCurrentCline : vi . fn ( ) . mockReturnValue ( mockTask as Task ) ,
7472 postMessageToWebview : vi . fn ( ) ,
7573 finishSubTask : vi . fn ( ) ,
76- recoverFromGrayState : vi . fn ( ) ,
74+ clearTask : vi . fn ( ) ,
75+ postStateToWebview : vi . fn ( ) ,
7776 }
7877 } )
7978
8079 afterEach ( ( ) => {
8180 vi . restoreAllMocks ( )
8281 } )
8382
84- describe ( ' Task.resumePausedTask error recovery' , ( ) => {
85- it ( ' should handle provider disconnection gracefully' , async ( ) => {
86- const mockError = new Error ( ' Provider disconnected' )
83+ describe ( " Task.resumePausedTask error recovery" , ( ) => {
84+ it ( " should handle provider disconnection gracefully" , async ( ) => {
85+ const mockError = new Error ( " Provider disconnected" )
8786 const resumeSpy = vi . fn ( ) . mockRejectedValue ( mockError )
8887 mockTask . resumePausedTask = resumeSpy
8988
9089 // Mock the actual implementation
9190 const task = mockTask as Task
92- task . resumePausedTask = async function ( ) {
91+ task . resumePausedTask = async function ( lastMessage : string ) {
9392 try {
9493 throw mockError
9594 } catch ( error ) {
96- console . warn ( ' [Task] Failed to resume paused task, attempting recovery:' , error )
97-
95+ console . warn ( " [Task] Failed to resume paused task, attempting recovery:" , error )
96+
9897 // Recovery mechanism: reset task state
9998 this . isStreaming = false
10099 this . isPaused = false
101- this . enableButtons = true
102-
103- // Add recovery message
104- const recoveryMessage = {
105- ts : Date . now ( ) ,
106- type : 'say' as const ,
107- say : 'error' as const ,
108- text : 'Task was interrupted but has been recovered. You can continue or start a new task.' ,
109- partial : false ,
100+
101+ // Add recovery message using the say method
102+ try {
103+ await this . say (
104+ "error" ,
105+ "Task was interrupted but has been recovered. You can continue or start a new task." ,
106+ )
107+ } catch ( sayError ) {
108+ console . warn ( "[Task] Failed to add recovery message:" , sayError )
110109 }
111-
112- // In real implementation, this would add to messages
113- console . log ( '[Task] Added recovery message:' , recoveryMessage )
114-
110+
115111 return
116112 }
117113 }
118114
119- await task . resumePausedTask ( )
115+ await task . resumePausedTask ( "test message" )
120116
121117 expect ( task . isStreaming ) . toBe ( false )
122118 expect ( task . isPaused ) . toBe ( false )
123- expect ( task . enableButtons ) . toBe ( true )
124119 } )
125120
126- it ( ' should handle API failures during task restoration' , async ( ) => {
127- const mockApiError = new Error ( ' API request failed' )
121+ it ( " should handle API failures during task restoration" , async ( ) => {
122+ const mockApiError = new Error ( " API request failed" )
128123 const recursiveSpy = vi . fn ( ) . mockRejectedValue ( mockApiError )
129124 mockTask . recursivelyMakeClineRequests = recursiveSpy
130125
131126 // Mock the actual implementation
132127 const task = mockTask as Task
133- task . recursivelyMakeClineRequests = async function ( ) {
128+ task . recursivelyMakeClineRequests = async function (
129+ userContent : any [ ] ,
130+ includeFileDetails ?: boolean ,
131+ ) : Promise < boolean > {
134132 try {
135133 throw mockApiError
136134 } catch ( error ) {
137- console . warn ( ' [Task] API request failed during task restoration, attempting recovery:' , error )
138-
139- // Recovery mechanism: enable user interaction
135+ console . warn ( " [Task] API request failed during task restoration, attempting recovery:" , error )
136+
137+ // Recovery mechanism: reset streaming state
140138 this . isStreaming = false
141- this . enableButtons = true
142-
143- // Add recovery message
144- const recoveryMessage = {
145- ts : Date . now ( ) ,
146- type : 'say' as const ,
147- say : 'error' as const ,
148- text : 'Connection was lost but the task has been recovered. Please try your request again.' ,
149- partial : false ,
139+
140+ // Add recovery message using the say method
141+ try {
142+ await this . say (
143+ "error" ,
144+ "Connection was lost but the task has been recovered. Please try your request again." ,
145+ )
146+ } catch ( sayError ) {
147+ console . warn ( "[Task] Failed to add recovery message:" , sayError )
150148 }
151-
152- console . log ( '[Task] Added API recovery message:' , recoveryMessage )
153- return
149+
150+ return false
154151 }
155152 }
156153
157- await task . recursivelyMakeClineRequests ( )
154+ const result = await task . recursivelyMakeClineRequests ( [ { type : "text" , text : "test" } ] )
158155
159156 expect ( task . isStreaming ) . toBe ( false )
160- expect ( task . enableButtons ) . toBe ( true )
157+ expect ( result ) . toBe ( false )
161158 } )
162159 } )
163160
164- describe ( 'ClineProvider.finishSubTask error recovery' , ( ) => {
165- it ( 'should handle subtask completion failures gracefully' , async ( ) => {
166- const mockError = new Error ( 'Subtask completion failed' )
167- const finishSpy = vi . fn ( ) . mockRejectedValue ( mockError )
168-
169- // Mock the actual implementation
161+ describe ( "ClineProvider.finishSubTask error recovery" , ( ) => {
162+ it ( "should handle subtask completion failures gracefully" , async ( ) => {
163+ const mockError = new Error ( "Subtask completion failed" )
164+
165+ // Mock the actual implementation to simulate the recovery behavior
170166 const provider = mockProvider as ClineProvider
171- provider . finishSubTask = async function ( ) {
167+ provider . finishSubTask = async function ( lastMessage : string ) {
172168 try {
169+ // Simulate the normal flow that would fail
170+ await this . getCurrentCline ( ) ?. resumePausedTask ( lastMessage )
173171 throw mockError
174172 } catch ( error ) {
175- console . warn ( '[ClineProvider] Failed to finish subtask, attempting recovery:' , error )
176-
177- // Recovery mechanism: attempt to recover from gray state
178- await this . recoverFromGrayState ?.( )
179-
180- return
181- }
182- }
173+ console . warn ( "[ClineProvider] Failed to finish subtask, attempting recovery:" , error )
183174
184- provider . recoverFromGrayState = async function ( ) {
185- console . log ( '[ClineProvider] Attempting gray state recovery...' )
186-
187- const currentTask = this . currentTask
188- if ( currentTask ) {
189- // Strategy 1: Force task resume
190- try {
191- currentTask . isStreaming = false
192- currentTask . enableButtons = true
193- currentTask . isPaused = false
194-
195- console . log ( '[ClineProvider] Gray state recovery: Reset task state' )
196-
197- // Strategy 2: Refresh UI state
198- this . postMessageToWebview ?.( {
199- type : 'state' ,
200- state : {
201- task : currentTask ,
202- enableButtons : true ,
203- isStreaming : false ,
204- }
205- } )
206-
207- console . log ( '[ClineProvider] Gray state recovery: Refreshed UI state' )
208-
209- } catch ( recoveryError ) {
210- console . error ( '[ClineProvider] Gray state recovery failed:' , recoveryError )
211-
212- // Strategy 3: Clear task as last resort
213- this . taskStack = [ ]
214- this . currentTask = undefined
215-
216- this . postMessageToWebview ?.( {
217- type : 'state' ,
218- state : {
219- task : undefined ,
220- enableButtons : true ,
221- isStreaming : false ,
222- }
223- } )
224-
225- console . log ( '[ClineProvider] Gray state recovery: Cleared task as last resort' )
226- }
175+ // Simulate recovery by calling clearTask (which is public)
176+ await this . clearTask ( )
177+
178+ return
227179 }
228180 }
229181
230- await provider . finishSubTask ( )
182+ // Test that finishSubTask handles errors gracefully
183+ await provider . finishSubTask ( "test message" )
231184
232- expect ( provider . recoverFromGrayState ) . toBeDefined ( )
185+ // Verify that clearTask was called (recovery mechanism)
186+ expect ( provider . clearTask ) . toHaveBeenCalled ( )
233187 } )
234188 } )
235189
236- describe ( ' Gray state detection' , ( ) => {
237- it ( ' should detect gray state conditions correctly' , ( ) => {
190+ describe ( " Gray state detection" , ( ) => {
191+ it ( " should detect gray state conditions correctly" , ( ) => {
238192 // Simulate gray state: task exists, not streaming, buttons disabled
239193 const hasTask = ! ! mockTask
240194 const hasMessages = true
@@ -247,7 +201,7 @@ describe('Gray State Recovery', () => {
247201 expect ( isInGrayState ) . toBe ( true )
248202 } )
249203
250- it ( ' should not detect gray state when buttons are enabled' , ( ) => {
204+ it ( " should not detect gray state when buttons are enabled" , ( ) => {
251205 const hasTask = ! ! mockTask
252206 const hasMessages = true
253207 const isStreaming = false
@@ -259,7 +213,7 @@ describe('Gray State Recovery', () => {
259213 expect ( isInGrayState ) . toBe ( false )
260214 } )
261215
262- it ( ' should not detect gray state when streaming' , ( ) => {
216+ it ( " should not detect gray state when streaming" , ( ) => {
263217 const hasTask = ! ! mockTask
264218 const hasMessages = true
265219 const isStreaming = true // Currently streaming
@@ -271,64 +225,51 @@ describe('Gray State Recovery', () => {
271225 expect ( isInGrayState ) . toBe ( false )
272226 } )
273227
274- it ( ' should not detect gray state when there is an active ask' , ( ) => {
228+ it ( " should not detect gray state when there is an active ask" , ( ) => {
275229 const hasTask = ! ! mockTask
276230 const hasMessages = true
277231 const isStreaming = false
278232 const enableButtons = false
279- const clineAsk = { type : ' tool' , tool : ' test' } // Active ask
233+ const clineAsk = { type : " tool" , tool : " test" } // Active ask
280234
281235 const isInGrayState = hasTask && hasMessages && ! isStreaming && ! enableButtons && ! clineAsk
282236
283237 expect ( isInGrayState ) . toBe ( false )
284238 } )
285239 } )
286240
287- describe ( ' Recovery strategies' , ( ) => {
288- it ( ' should attempt multiple recovery strategies in order' , async ( ) => {
241+ describe ( " Recovery strategies" , ( ) => {
242+ it ( " should attempt multiple recovery strategies in order" , async ( ) => {
289243 const provider = mockProvider as ClineProvider
290244 const strategies : string [ ] = [ ]
291245
292- provider . recoverFromGrayState = async function ( ) {
293- const currentTask = this . currentTask
246+ // Mock finishSubTask to simulate recovery strategies
247+ provider . finishSubTask = async function ( lastMessage : string ) {
248+ const currentTask = this . getCurrentCline ( )
294249 if ( currentTask ) {
295250 // Strategy 1: Force task resume
296251 try {
297- strategies . push ( ' force_resume' )
252+ strategies . push ( " force_resume" )
298253 currentTask . isStreaming = false
299- currentTask . enableButtons = true
300254 currentTask . isPaused = false
301-
255+
302256 // Strategy 2: Add recovery message
303- strategies . push ( ' add_recovery_message' )
304-
257+ strategies . push ( " add_recovery_message" )
258+
305259 // Strategy 3: Refresh UI state
306- strategies . push ( 'refresh_ui' )
307- this . postMessageToWebview ?.( {
308- type : 'state' ,
309- state : {
310- task : currentTask ,
311- enableButtons : true ,
312- isStreaming : false ,
313- }
314- } )
315-
260+ strategies . push ( "refresh_ui" )
261+ await this . postStateToWebview ( )
316262 } catch ( recoveryError ) {
317263 // Strategy 4: Clear task as last resort
318- strategies . push ( 'clear_task' )
319- this . taskStack = [ ]
320- this . currentTask = undefined
264+ strategies . push ( "clear_task" )
265+ await this . clearTask ( )
321266 }
322267 }
323268 }
324269
325- await provider . recoverFromGrayState ?. ( )
270+ await provider . finishSubTask ( "test message" )
326271
327- expect ( strategies ) . toEqual ( [
328- 'force_resume' ,
329- 'add_recovery_message' ,
330- 'refresh_ui'
331- ] )
272+ expect ( strategies ) . toEqual ( [ "force_resume" , "add_recovery_message" , "refresh_ui" ] )
332273 } )
333274 } )
334- } )
275+ } )
0 commit comments