Skip to content
This repository was archived by the owner on Feb 20, 2026. It is now read-only.

Commit 8cb581e

Browse files
committed
fix: enhance error handling for MessageAbortedError and add toast notification
- Fixed error detection to handle object structure { name: "MessageAbortedError", data: {...} } - Cancel countdown IMMEDIATELY in error handler to prevent race conditions - Added toast notification when interruption is detected - Handle errors in message.updated events (assistant messages with errors) - Improved logging with error name and message
1 parent aea7d05 commit 8cb581e

1 file changed

Lines changed: 113 additions & 15 deletions

File tree

task-continuation.ts

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)