From b5525f63c63f736b582944ea6de48c5b1a8fc837 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Tue, 21 Apr 2026 19:23:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20buddy=20=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=20ESC=20=E5=85=B3=E9=97=AD=E5=90=8E=E8=BF=9B=E5=85=A5?= =?UTF-8?q?=E6=B0=B8=E4=B9=85=20loading=20=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CancelRequestHandler 先于 BuddyPanel 的 ESC handler 拦截按键, 仅清除面板但未 resolve processSlashCommand 中的 Promise, 导致 queryGuard 卡在 dispatching 状态。通过在 setToolJSX 中增加 onDismiss 回调,在面板被外部清除时同步 resolve Promise。 Co-Authored-By: Claude Opus 4.6 --- src/Tool.ts | 3 +++ src/screens/REPL.tsx | 7 +++++++ src/utils/handlePromptSubmit.ts | 9 +++++++++ src/utils/processUserInput/processSlashCommand.tsx | 7 +++++++ 4 files changed, 26 insertions(+) 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 => {