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 isImmediate?: boolean
/** Set to true to clear a local JSX command (e.g., from its onDone callback) */ /** Set to true to clear a local JSX command (e.g., from its onDone callback) */
clearLocalJSX?: boolean 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, } | null,
) => void ) => void

View File

@@ -1288,6 +1288,9 @@ export function REPL({
shouldContinueAnimation?: true; shouldContinueAnimation?: true;
showSpinner?: boolean; showSpinner?: boolean;
isLocalJSXCommand: true; isLocalJSXCommand: true;
/** Called when the panel is dismissed externally (ESC via CancelRequestHandler).
* Resolves the underlying Promise in processSlashCommand.tsx. */
onDismiss?: () => void;
} | null>(null); } | null>(null);
// Wrapper for setToolJSX that preserves local JSX commands (like /btw). // Wrapper for setToolJSX that preserves local JSX commands (like /btw).
@@ -1308,6 +1311,7 @@ export function REPL({
showSpinner?: boolean; showSpinner?: boolean;
isLocalJSXCommand?: boolean; isLocalJSXCommand?: boolean;
clearLocalJSX?: boolean; clearLocalJSX?: boolean;
onDismiss?: () => void;
} | null, } | null,
) => { ) => {
// If setting a local JSX command, store it in the ref // If setting a local JSX command, store it in the ref
@@ -1322,6 +1326,9 @@ export function REPL({
if (localJSXCommandRef.current) { if (localJSXCommandRef.current) {
// Allow clearing only if explicitly requested (from onDone callbacks) // Allow clearing only if explicitly requested (from onDone callbacks)
if (args?.clearLocalJSX) { 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; localJSXCommandRef.current = null;
setToolJSXInternal(null); setToolJSXInternal(null);
return; return;

View File

@@ -313,6 +313,15 @@ export async function handlePromptSubmit(
shouldHidePromptInput: false, shouldHidePromptInput: false,
isLocalJSXCommand: true, isLocalJSXCommand: true,
isImmediate: true, isImmediate: true,
onDismiss: () => {
if (doneWasCalled) return
doneWasCalled = true
setToolJSX({
jsx: null,
shouldHidePromptInput: false,
clearLocalJSX: true,
})
},
}) })
} }
return return

View File

@@ -839,6 +839,13 @@ async function getMessagesForSlashCommand(
showSpinner: false, showSpinner: false,
isLocalJSXCommand: true, isLocalJSXCommand: true,
isImmediate: command.immediate === 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 => { .catch(e => {