fix: 修复 buddy 命令 ESC 关闭后进入永久 loading 状态

CancelRequestHandler 先于 BuddyPanel 的 ESC handler 拦截按键,
仅清除面板但未 resolve processSlashCommand 中的 Promise,
导致 queryGuard 卡在 dispatching 状态。通过在 setToolJSX
中增加 onDismiss 回调,在面板被外部清除时同步 resolve Promise。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-21 19:23:37 +08:00
parent 722aa6c97a
commit b5525f63c6
4 changed files with 26 additions and 0 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 => {