fix(hooks): fix TODO continuation abort handling with timer-based approach
Replace blocking await with non-blocking timer scheduling to handle race condition between session.idle and session.error events. When ESC abort occurs, session.error immediately cancels the pending timer, preventing unwanted continuation prompts. Changes: - Add pendingTimers Map to track scheduled continuation checks - Cancel timer on session.error (especially abort cases) - Cancel timer on message.updated and session.deleted for cleanup - Reduce delay to 200ms for faster response - Maintain existing Set-based flag logic for compatibility This fixes the issue where ESC abort would not prevent continuation prompts due to event ordering (idle before error)
This commit is contained in:
@@ -36,6 +36,7 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
const remindedSessions = new Set<string>()
|
||||
const interruptedSessions = new Set<string>()
|
||||
const errorSessions = new Set<string>()
|
||||
const pendingTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
return async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
||||
const props = event.properties as Record<string, unknown> | undefined
|
||||
@@ -47,6 +48,13 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
if (detectInterrupt(props?.error)) {
|
||||
interruptedSessions.add(sessionID)
|
||||
}
|
||||
|
||||
// Cancel pending continuation if error occurs
|
||||
const timer = pendingTimers.get(sessionID)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
pendingTimers.delete(sessionID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -55,8 +63,15 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
const sessionID = props?.sessionID as string | undefined
|
||||
if (!sessionID) return
|
||||
|
||||
// Wait for potential session.error events to be processed first
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
// Cancel any existing timer to debounce
|
||||
const existingTimer = pendingTimers.get(sessionID)
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer)
|
||||
}
|
||||
|
||||
// Schedule continuation check
|
||||
const timer = setTimeout(async () => {
|
||||
pendingTimers.delete(sessionID)
|
||||
|
||||
const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID)
|
||||
|
||||
@@ -95,7 +110,7 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
|
||||
remindedSessions.add(sessionID)
|
||||
|
||||
// Re-check if abort occurred during the delay
|
||||
// Re-check if abort occurred during the delay/fetch
|
||||
if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
|
||||
remindedSessions.delete(sessionID)
|
||||
return
|
||||
@@ -117,6 +132,9 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
} catch {
|
||||
remindedSessions.delete(sessionID)
|
||||
}
|
||||
}, 200)
|
||||
|
||||
pendingTimers.set(sessionID, timer)
|
||||
}
|
||||
|
||||
if (event.type === "message.updated") {
|
||||
@@ -124,6 +142,13 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
const sessionID = info?.sessionID as string | undefined
|
||||
if (sessionID && info?.role === "user") {
|
||||
remindedSessions.delete(sessionID)
|
||||
|
||||
// Cancel pending continuation on user interaction
|
||||
const timer = pendingTimers.get(sessionID)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
pendingTimers.delete(sessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +158,13 @@ export function createTodoContinuationEnforcer(ctx: PluginInput) {
|
||||
remindedSessions.delete(sessionInfo.id)
|
||||
interruptedSessions.delete(sessionInfo.id)
|
||||
errorSessions.delete(sessionInfo.id)
|
||||
|
||||
// Cancel pending continuation
|
||||
const timer = pendingTimers.get(sessionInfo.id)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
pendingTimers.delete(sessionInfo.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user