@@ -59,6 +59,12 @@ export async function activate(context: vscode.ExtensionContext) {
5959 context . subscriptions . push ( outputChannel )
6060 outputChannel . appendLine ( `${ Package . name } extension activated - ${ JSON . stringify ( Package ) } ` )
6161
62+ // Set up global error handlers for crash recovery
63+ setupGlobalErrorHandlers ( context , outputChannel )
64+
65+ // Check for crash recovery
66+ await checkForCrashRecovery ( context , outputChannel )
67+
6268 // Migrate old settings to new
6369 await migrateSettings ( context , outputChannel )
6470
@@ -218,3 +224,267 @@ export async function deactivate() {
218224 TelemetryService . instance . shutdown ( )
219225 TerminalRegistry . cleanup ( )
220226}
227+
228+ // Global error handlers for crash recovery
229+ function setupGlobalErrorHandlers ( context : vscode . ExtensionContext , outputChannel : vscode . OutputChannel ) {
230+ // Handle uncaught exceptions
231+ process . on ( "uncaughtException" , async ( error : Error ) => {
232+ const errorMessage = `[CRASH] Uncaught Exception: ${ error . message } \nStack: ${ error . stack } `
233+ outputChannel . appendLine ( errorMessage )
234+ console . error ( errorMessage )
235+
236+ // Save crash information
237+ await saveCrashInfo ( context , error , "uncaughtException" )
238+
239+ // Attempt to save current task state
240+ await saveTaskStateOnCrash ( context )
241+
242+ // Log telemetry
243+ // Log crash telemetry
244+ try {
245+ if ( TelemetryService . hasInstance ( ) ) {
246+ TelemetryService . instance . captureEvent ( "extension_crash" as any , {
247+ type : "uncaughtException" ,
248+ error : error . message ,
249+ stack : error . stack ,
250+ platform : process . platform ,
251+ } )
252+ }
253+ } catch ( e ) {
254+ console . error ( "Failed to log telemetry:" , e )
255+ }
256+
257+ // Show user-friendly error message
258+ vscode . window
259+ . showErrorMessage (
260+ "Roo Code encountered an unexpected error. Your work has been saved. Please restart VS Code." ,
261+ "Restart VS Code" ,
262+ )
263+ . then ( ( selection ) => {
264+ if ( selection === "Restart VS Code" ) {
265+ vscode . commands . executeCommand ( "workbench.action.reloadWindow" )
266+ }
267+ } )
268+ } )
269+
270+ // Handle unhandled promise rejections
271+ process . on ( "unhandledRejection" , async ( reason : any , promise : Promise < any > ) => {
272+ const errorMessage = `[CRASH] Unhandled Promise Rejection: ${ reason } \nPromise: ${ promise } `
273+ outputChannel . appendLine ( errorMessage )
274+ console . error ( errorMessage )
275+
276+ // Save crash information
277+ await saveCrashInfo ( context , reason , "unhandledRejection" )
278+
279+ // Attempt to save current task state
280+ await saveTaskStateOnCrash ( context )
281+
282+ // Log telemetry
283+ // Log crash telemetry
284+ try {
285+ if ( TelemetryService . hasInstance ( ) ) {
286+ TelemetryService . instance . captureEvent ( "extension_crash" as any , {
287+ type : "unhandledRejection" ,
288+ reason : String ( reason ) ,
289+ platform : process . platform ,
290+ } )
291+ }
292+ } catch ( e ) {
293+ console . error ( "Failed to log telemetry:" , e )
294+ }
295+ } )
296+
297+ // Windows-specific error handling
298+ if ( process . platform === "win32" ) {
299+ // Handle Windows-specific errors
300+ process . on ( "SIGTERM" , async ( ) => {
301+ outputChannel . appendLine ( "[CRASH] Received SIGTERM signal (Windows termination)" )
302+ await saveCrashInfo ( context , new Error ( "SIGTERM received" ) , "SIGTERM" )
303+ await saveTaskStateOnCrash ( context )
304+ } )
305+
306+ process . on ( "SIGINT" , async ( ) => {
307+ outputChannel . appendLine ( "[CRASH] Received SIGINT signal (Windows interruption)" )
308+ await saveCrashInfo ( context , new Error ( "SIGINT received" ) , "SIGINT" )
309+ await saveTaskStateOnCrash ( context )
310+ } )
311+
312+ // Handle Windows-specific exit events
313+ process . on ( "exit" , async ( code ) => {
314+ if ( code !== 0 ) {
315+ outputChannel . appendLine ( `[CRASH] Process exiting with code ${ code } ` )
316+ await saveCrashInfo ( context , new Error ( `Process exit with code ${ code } ` ) , "exit" )
317+ await saveTaskStateOnCrash ( context )
318+ }
319+ } )
320+
321+ // Handle Windows-specific errors that might cause crashes
322+ process . on ( "uncaughtExceptionMonitor" , ( error : Error , origin : string ) => {
323+ // This event is emitted before uncaughtException, useful for logging
324+ outputChannel . appendLine ( `[CRASH] Uncaught exception monitor: ${ error . message } from ${ origin } ` )
325+
326+ // Check for Windows-specific error patterns
327+ if ( error . message . includes ( "EPERM" ) || error . message . includes ( "EACCES" ) ) {
328+ outputChannel . appendLine ( "[CRASH] Windows permission error detected" )
329+ } else if ( error . message . includes ( "ENOENT" ) ) {
330+ outputChannel . appendLine ( "[CRASH] Windows file not found error detected" )
331+ } else if ( error . message . includes ( "spawn" ) || error . message . includes ( "ENOBUFS" ) ) {
332+ outputChannel . appendLine ( "[CRASH] Windows process spawn error detected" )
333+ }
334+ } )
335+ }
336+ }
337+
338+ // Save crash information for recovery
339+ async function saveCrashInfo ( context : vscode . ExtensionContext , error : any , type : string ) {
340+ try {
341+ const crashInfo = {
342+ timestamp : new Date ( ) . toISOString ( ) ,
343+ type,
344+ error :
345+ error instanceof Error
346+ ? {
347+ message : error . message ,
348+ stack : error . stack ,
349+ name : error . name ,
350+ }
351+ : String ( error ) ,
352+ platform : process . platform ,
353+ vscodeVersion : vscode . version ,
354+ extensionVersion : context . extension ?. packageJSON ?. version ,
355+ }
356+
357+ await context . globalState . update ( "lastCrashInfo" , crashInfo )
358+ await context . globalState . update ( "hasCrashRecovery" , true )
359+ } catch ( e ) {
360+ console . error ( "Failed to save crash info:" , e )
361+ }
362+ }
363+
364+ // Save current task state on crash
365+ async function saveTaskStateOnCrash ( context : vscode . ExtensionContext ) {
366+ try {
367+ const provider = ClineProvider . getVisibleInstance ( )
368+ if ( provider ) {
369+ const currentTask = provider . getCurrentCline ( )
370+ if ( currentTask ) {
371+ // Save current task state
372+ // Force save current state
373+ await provider . postStateToWebview ( )
374+
375+ // Save task recovery info
376+ const recoveryInfo = {
377+ taskId : currentTask . taskId ,
378+ parentTaskId : currentTask . parentTask ?. taskId ,
379+ taskStack : provider . getCurrentTaskStack ( ) ,
380+ timestamp : new Date ( ) . toISOString ( ) ,
381+ }
382+
383+ await context . globalState . update ( "taskRecoveryInfo" , recoveryInfo )
384+ outputChannel . appendLine ( `[CRASH] Saved task recovery info for task ${ currentTask . taskId } ` )
385+ }
386+ }
387+ } catch ( e ) {
388+ console . error ( "Failed to save task state on crash:" , e )
389+ }
390+ }
391+
392+ // Check for crash recovery on startup
393+ async function checkForCrashRecovery ( context : vscode . ExtensionContext , outputChannel : vscode . OutputChannel ) {
394+ try {
395+ const hasCrashRecovery = context . globalState . get < boolean > ( "hasCrashRecovery" )
396+ const lastCrashInfo = context . globalState . get < any > ( "lastCrashInfo" )
397+ const taskRecoveryInfo = context . globalState . get < any > ( "taskRecoveryInfo" )
398+
399+ if ( hasCrashRecovery && lastCrashInfo ) {
400+ outputChannel . appendLine (
401+ `[RECOVERY] Detected previous crash: ${ lastCrashInfo . type } at ${ lastCrashInfo . timestamp } ` ,
402+ )
403+
404+ // Clear the crash flag
405+ await context . globalState . update ( "hasCrashRecovery" , false )
406+
407+ // Show recovery notification with Windows-specific messaging if applicable
408+ const isWindows = process . platform === "win32"
409+ const crashMessage =
410+ isWindows && ( lastCrashInfo . type === "SIGTERM" || lastCrashInfo . type === "SIGINT" )
411+ ? "Roo Code was terminated unexpectedly on Windows. Would you like to restore your last session?"
412+ : "Roo Code recovered from a previous crash. Would you like to restore your last session?"
413+
414+ const selection = await vscode . window . showInformationMessage ( crashMessage , "Restore Session" , "Start Fresh" )
415+
416+ if ( selection === "Restore Session" && taskRecoveryInfo ) {
417+ outputChannel . appendLine ( `[RECOVERY] Attempting to restore task ${ taskRecoveryInfo . taskId } ` )
418+
419+ // Delay to ensure extension is fully initialized
420+ setTimeout ( async ( ) => {
421+ try {
422+ const provider = ClineProvider . getVisibleInstance ( )
423+ if ( provider && taskRecoveryInfo . taskId ) {
424+ // Check if this was a subtask
425+ if ( taskRecoveryInfo . parentTaskId ) {
426+ outputChannel . appendLine (
427+ `[RECOVERY] Detected subtask recovery. Parent task: ${ taskRecoveryInfo . parentTaskId } ` ,
428+ )
429+
430+ // First, try to restore the parent task
431+ try {
432+ await provider . showTaskWithId ( taskRecoveryInfo . parentTaskId )
433+ outputChannel . appendLine (
434+ `[RECOVERY] Restored parent task ${ taskRecoveryInfo . parentTaskId } ` ,
435+ )
436+
437+ // Then show information about the subtask that was interrupted
438+ vscode . window
439+ . showInformationMessage (
440+ `Restored to parent task. The subtask that was running during the crash has been saved and can be resumed.` ,
441+ "View Subtask" ,
442+ )
443+ . then ( async ( selection ) => {
444+ if ( selection === "View Subtask" ) {
445+ // Show the subtask that was interrupted
446+ await provider . showTaskWithId ( taskRecoveryInfo . taskId )
447+ }
448+ } )
449+ } catch ( parentError ) {
450+ // If parent task can't be restored, just restore the subtask
451+ outputChannel . appendLine (
452+ `[RECOVERY] Failed to restore parent task, restoring subtask instead` ,
453+ )
454+ await provider . showTaskWithId ( taskRecoveryInfo . taskId )
455+
456+ vscode . window . showInformationMessage (
457+ `Restored subtask from before the crash. The parent task context may need to be re-established.` ,
458+ )
459+ }
460+ } else {
461+ // Regular task recovery
462+ await provider . showTaskWithId ( taskRecoveryInfo . taskId )
463+
464+ vscode . window . showInformationMessage ( `Restored task from before the crash.` )
465+ }
466+
467+ // If there was a task stack, log it for debugging
468+ if ( taskRecoveryInfo . taskStack && taskRecoveryInfo . taskStack . length > 1 ) {
469+ outputChannel . appendLine (
470+ `[RECOVERY] Task stack at crash: ${ taskRecoveryInfo . taskStack . join ( " -> " ) } ` ,
471+ )
472+ }
473+ }
474+ } catch ( e ) {
475+ console . error ( "Failed to restore task:" , e )
476+ vscode . window . showErrorMessage (
477+ "Could not restore the previous task, but your work has been saved." ,
478+ )
479+ }
480+ } , 2000 )
481+ }
482+
483+ // Clear recovery info
484+ await context . globalState . update ( "taskRecoveryInfo" , undefined )
485+ await context . globalState . update ( "lastCrashInfo" , undefined )
486+ }
487+ } catch ( e ) {
488+ console . error ( "Error checking for crash recovery:" , e )
489+ }
490+ }
0 commit comments