diff --git a/src/cli/App.tsx b/src/cli/App.tsx index b34f1c5..00fb22a 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -2561,6 +2561,32 @@ ${recentCommits} ], ); + // Cancel all pending approvals - queue denials to send with next message + // Similar to interrupt flow during tool execution + const handleCancelApprovals = useCallback(() => { + if (pendingApprovals.length === 0) return; + + // Create denial results for all pending approvals and queue for next message + const denialResults = pendingApprovals.map((approval) => ({ + type: "approval" as const, + tool_call_id: approval.toolCallId, + approve: false, + reason: "User cancelled the approval", + })); + setQueuedApprovalResults(denialResults); + + // Mark the pending approval tool calls as cancelled in the buffers + markIncompleteToolsAsCancelled(buffersRef.current); + refreshDerived(); + + // Clear all approval state + setPendingApprovals([]); + setApprovalContexts([]); + setApprovalResults([]); + setAutoHandledResults([]); + setAutoDeniedApprovals([]); + }, [pendingApprovals, refreshDerived]); + const handleModelSelect = useCallback( async (modelId: string) => { setModelSelectorOpen(false); @@ -3447,6 +3473,7 @@ Plan file path: ${planFilePath}`; onApproveAll={handleApproveCurrent} onApproveAlways={handleApproveAlways} onDenyAll={handleDenyCurrent} + onCancel={handleCancelApprovals} /> )} diff --git a/src/cli/components/ApprovalDialogRich.tsx b/src/cli/components/ApprovalDialogRich.tsx index 880353c..c8687f8 100644 --- a/src/cli/components/ApprovalDialogRich.tsx +++ b/src/cli/components/ApprovalDialogRich.tsx @@ -19,6 +19,7 @@ type Props = { onApproveAll: () => void; onApproveAlways: (scope?: "project" | "session") => void; onDenyAll: (reason: string) => void; + onCancel?: () => void; // Cancel all approvals without sending to server }; type DynamicPreviewProps = { @@ -397,6 +398,7 @@ export const ApprovalDialog = memo(function ApprovalDialog({ onApproveAll, onApproveAlways, onDenyAll, + onCancel, }: Props) { const [selectedOption, setSelectedOption] = useState(0); const [isEnteringReason, setIsEnteringReason] = useState(false); @@ -453,6 +455,14 @@ export const ApprovalDialog = memo(function ApprovalDialog({ useInput((_input, key) => { if (isExecuting) return; + // Handle CTRL-C to cancel all approvals + if (key.ctrl && _input === "c") { + if (onCancel) { + onCancel(); + } + return; + } + if (isEnteringReason) { // When entering reason, only handle enter/escape if (key.return) {