diff --git a/src/Tool.ts b/src/Tool.ts index c8c7a9895..a083d5591 100644 --- a/src/Tool.ts +++ b/src/Tool.ts @@ -112,6 +112,9 @@ export type SetToolJSXFn = ( isImmediate?: boolean /** Set to true to clear a local JSX command (e.g., from its onDone callback) */ clearLocalJSX?: boolean + /** Called when the panel is dismissed externally (e.g. ESC via CancelRequestHandler). + * Must resolve the underlying Promise in processSlashCommand.tsx. */ + onDismiss?: () => void } | null, ) => void diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index 990b661ac..ad0e0a00d 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -1288,6 +1288,9 @@ export function REPL({ shouldContinueAnimation?: true; showSpinner?: boolean; isLocalJSXCommand: true; + /** Called when the panel is dismissed externally (ESC via CancelRequestHandler). + * Resolves the underlying Promise in processSlashCommand.tsx. */ + onDismiss?: () => void; } | null>(null); // Wrapper for setToolJSX that preserves local JSX commands (like /btw). @@ -1308,6 +1311,7 @@ export function REPL({ showSpinner?: boolean; isLocalJSXCommand?: boolean; clearLocalJSX?: boolean; + onDismiss?: () => void; } | null, ) => { // If setting a local JSX command, store it in the ref @@ -1322,6 +1326,9 @@ export function REPL({ if (localJSXCommandRef.current) { // Allow clearing only if explicitly requested (from onDone callbacks) if (args?.clearLocalJSX) { + // Notify the command that its panel was dismissed externally (e.g. ESC) + // so it can resolve the underlying Promise and unblock executeUserInput. + localJSXCommandRef.current.onDismiss?.(); localJSXCommandRef.current = null; setToolJSXInternal(null); return; diff --git a/src/utils/handlePromptSubmit.ts b/src/utils/handlePromptSubmit.ts index 97b05758f..4b219f470 100644 --- a/src/utils/handlePromptSubmit.ts +++ b/src/utils/handlePromptSubmit.ts @@ -313,6 +313,15 @@ export async function handlePromptSubmit( shouldHidePromptInput: false, isLocalJSXCommand: true, isImmediate: true, + onDismiss: () => { + if (doneWasCalled) return + doneWasCalled = true + setToolJSX({ + jsx: null, + shouldHidePromptInput: false, + clearLocalJSX: true, + }) + }, }) } return diff --git a/src/utils/processUserInput/processSlashCommand.tsx b/src/utils/processUserInput/processSlashCommand.tsx index 6ee4bfe93..0225e5354 100644 --- a/src/utils/processUserInput/processSlashCommand.tsx +++ b/src/utils/processUserInput/processSlashCommand.tsx @@ -839,6 +839,13 @@ async function getMessagesForSlashCommand( showSpinner: false, isLocalJSXCommand: true, isImmediate: command.immediate === true, + // When the panel is dismissed externally (ESC via CancelRequestHandler), + // resolve the Promise so executeUserInput doesn't hang forever. + onDismiss: () => { + if (doneWasCalled) return + doneWasCalled = true + void resolve({ messages: [], shouldQuery: false, command }) + }, }) }) .catch(e => {