@@ -702,35 +702,67 @@ export function createTaskContinuation(
702702 }
703703
704704 const handleSessionError = async ( sessionID : string , event ?: LoopEvent ) : Promise < void > => {
705+ // Clear any pending countdown IMMEDIATELY to prevent race conditions
706+ const existingTimeout = pendingCountdowns . get ( sessionID )
707+ if ( existingTimeout ) {
708+ clearTimeout ( existingTimeout )
709+ pendingCountdowns . delete ( sessionID )
710+ }
711+
705712 // Set error cooldown
706713 errorCooldowns . set ( sessionID , Date . now ( ) )
707714
708715 // Check if this is an interruption (ESC key pressed)
716+ // Handle both Error instances and object structures like { name: "MessageAbortedError", data: {...} }
709717 const error = event ?. properties ?. error
710- const isInterruption =
711- error instanceof Error &&
712- ( error . message ?. includes ( "aborted" ) ||
718+ let isInterruption = false
719+ let errorMessage = "Unknown error"
720+
721+ if ( error instanceof Error ) {
722+ // Standard Error instance
723+ errorMessage = error . message
724+ isInterruption =
725+ error . message ?. includes ( "aborted" ) ||
713726 error . message ?. includes ( "cancelled" ) ||
714727 error . message ?. includes ( "interrupted" ) ||
715728 error . name === "AbortError" ||
716- error . name === "CancellationError" )
729+ error . name === "CancellationError"
730+ } else if ( typeof error === "object" && error !== null ) {
731+ // Object structure like { name: "MessageAbortedError", data: { message: "..." } }
732+ const errorObj = error as { name ?: string ; data ?: { message ?: string } }
733+ errorMessage = errorObj . data ?. message || errorObj . name || "Object error"
734+ isInterruption = ! ! (
735+ errorObj . name ?. includes ( "Abort" ) ||
736+ errorObj . name ?. includes ( "Cancel" ) ||
737+ errorObj . name ?. includes ( "Interrupt" ) ||
738+ errorObj . data ?. message ?. includes ( "aborted" ) ||
739+ errorObj . data ?. message ?. includes ( "cancelled" ) ||
740+ errorObj . data ?. message ?. includes ( "interrupted" )
741+ )
742+ }
717743
718744 if ( isInterruption ) {
719- // Set a longer cooldown after interruption to prevent rapid continuations
720- // This prevents the continuation from firing right after user interruption
721745 if ( typeof logger !== "undefined" && logger ) {
722- logger . debug ( "Session interruption detected, setting extended cooldown " , {
746+ logger . debug ( "Session interruption detected" , {
723747 sessionID,
724- errorMessage : error instanceof Error ? error . message : String ( error ) ,
748+ errorName : error instanceof Error ? error . name : ( error as { name ?: string } ) ?. name ,
749+ errorMessage,
725750 } )
726751 }
727- }
728752
729- // Clear any pending countdown
730- const existingTimeout = pendingCountdowns . get ( sessionID )
731- if ( existingTimeout ) {
732- clearTimeout ( existingTimeout )
733- pendingCountdowns . delete ( sessionID )
753+ // Show toast notification to acknowledge the interruption
754+ try {
755+ await ctx . client . tui . showToast ( {
756+ body : {
757+ title : "Session Interrupted" ,
758+ message : "Task continuation paused due to interruption" ,
759+ variant : "warning" ,
760+ duration : 2000 ,
761+ } ,
762+ } )
763+ } catch {
764+ // Ignore toast errors
765+ }
734766 }
735767 }
736768
@@ -768,8 +800,74 @@ export function createTaskContinuation(
768800 // Clear error cooldown on user message
769801 errorCooldowns . delete ( sessionID )
770802
771- // Check if this is an actual new user message (not a re-processed message )
803+ // Check if this message contains an error (interruption detection )
772804 const info = event ?. properties ?. info
805+ const messageError = ( info as { error ?: unknown } ) ?. error
806+ if ( messageError ) {
807+ // Check if this is an interruption error
808+ let isInterruption = false
809+ let errorName = ""
810+ let errorMessage = ""
811+
812+ if ( messageError instanceof Error ) {
813+ errorName = messageError . name
814+ errorMessage = messageError . message
815+ isInterruption =
816+ messageError . message ?. includes ( "aborted" ) ||
817+ messageError . message ?. includes ( "cancelled" ) ||
818+ messageError . message ?. includes ( "interrupted" ) ||
819+ messageError . name === "AbortError" ||
820+ messageError . name === "CancellationError"
821+ } else if ( typeof messageError === "object" && messageError !== null ) {
822+ const errorObj = messageError as { name ?: string ; data ?: { message ?: string } }
823+ errorName = errorObj . name || ""
824+ errorMessage = errorObj . data ?. message || ""
825+ isInterruption = ! ! (
826+ errorObj . name ?. includes ( "Abort" ) ||
827+ errorObj . name ?. includes ( "Cancel" ) ||
828+ errorObj . name ?. includes ( "Interrupt" ) ||
829+ errorObj . data ?. message ?. includes ( "aborted" ) ||
830+ errorObj . data ?. message ?. includes ( "cancelled" ) ||
831+ errorObj . data ?. message ?. includes ( "interrupted" )
832+ )
833+ }
834+
835+ if ( isInterruption ) {
836+ // Cancel any pending countdown due to interruption
837+ const existingTimeout = pendingCountdowns . get ( sessionID )
838+ if ( existingTimeout ) {
839+ clearTimeout ( existingTimeout )
840+ pendingCountdowns . delete ( sessionID )
841+ }
842+
843+ // Set error cooldown
844+ errorCooldowns . set ( sessionID , Date . now ( ) )
845+
846+ if ( typeof logger !== "undefined" && logger ) {
847+ logger . debug ( "Message interruption detected" , {
848+ sessionID,
849+ errorName,
850+ errorMessage,
851+ } )
852+ }
853+
854+ // Show toast notification
855+ try {
856+ await ctx . client . tui . showToast ( {
857+ body : {
858+ title : "Session Interrupted" ,
859+ message : "Task continuation paused due to interruption" ,
860+ variant : "warning" ,
861+ duration : 2000 ,
862+ } ,
863+ } )
864+ } catch {
865+ // Ignore toast errors
866+ }
867+ }
868+ }
869+
870+ // Check if this is an actual new user message (not a re-processed message)
773871 const messageID = ( info as { id ?: string } ) ?. id
774872 const role = ( info as { role ?: string } ) ?. role
775873 const summary = ( info as { summary ?: unknown } ) ?. summary
0 commit comments