()
| 2104 | prevDialogRef.current = focusedInputDialog; |
| 2105 | }, [focusedInputDialog, repinScroll]); |
| 2106 | function onCancel() { |
| 2107 | if (focusedInputDialog === 'elicitation') { |
| 2108 | // Elicitation dialog handles its own Escape, and closing it shouldn't affect any loading state. |
| 2109 | return; |
| 2110 | } |
| 2111 | logForDebugging(`[onCancel] focusedInputDialog=${focusedInputDialog} streamMode=${streamMode}`); |
| 2112 | |
| 2113 | // Pause proactive mode so the user gets control back. |
| 2114 | // It will resume when they submit their next input (see onSubmit). |
| 2115 | if (feature('PROACTIVE') || feature('KAIROS')) { |
| 2116 | proactiveModule?.pauseProactive(); |
| 2117 | } |
| 2118 | queryGuard.forceEnd(); |
| 2119 | skipIdleCheckRef.current = false; |
| 2120 | |
| 2121 | // Preserve partially-streamed text so the user can read what was |
| 2122 | // generated before pressing Esc. Pushed before resetLoadingState clears |
| 2123 | // streamingText, and before query.ts yields the async interrupt marker, |
| 2124 | // giving final order [user, partial-assistant, [Request interrupted by user]]. |
| 2125 | if (streamingText?.trim()) { |
| 2126 | setMessages(prev => [...prev, createAssistantMessage({ |
| 2127 | content: streamingText |
| 2128 | })]); |
| 2129 | } |
| 2130 | resetLoadingState(); |
| 2131 | |
| 2132 | // Clear any active token budget so the backstop doesn't fire on |
| 2133 | // a stale budget if the query generator hasn't exited yet. |
| 2134 | if (feature('TOKEN_BUDGET')) { |
| 2135 | snapshotOutputTokensForTurn(null); |
| 2136 | } |
| 2137 | if (focusedInputDialog === 'tool-permission') { |
| 2138 | // Tool use confirm handles the abort signal itself |
| 2139 | toolUseConfirmQueue[0]?.onAbort(); |
| 2140 | setToolUseConfirmQueue([]); |
| 2141 | } else if (focusedInputDialog === 'prompt') { |
| 2142 | // Reject all pending prompts and clear the queue |
| 2143 | for (const item of promptQueue) { |
| 2144 | item.reject(new Error('Prompt cancelled by user')); |
| 2145 | } |
| 2146 | setPromptQueue([]); |
| 2147 | abortController?.abort('user-cancel'); |
| 2148 | } else if (activeRemote.isRemoteMode) { |
| 2149 | // Remote mode: send interrupt signal to CCR |
| 2150 | activeRemote.cancelRequest(); |
| 2151 | } else { |
| 2152 | abortController?.abort('user-cancel'); |
| 2153 | } |
| 2154 | |
| 2155 | // Clear the controller so subsequent Escape presses don't see a stale |
| 2156 | // aborted signal. Without this, canCancelRunningTask is false (signal |
| 2157 | // defined but .aborted === true), so isActive becomes false if no other |
| 2158 | // activating conditions hold — leaving the Escape keybinding inactive. |
| 2159 | setAbortController(null); |
| 2160 | |
| 2161 | // forceEnd() skips the finally path — fire directly (aborted=true). |
| 2162 | void mrOnTurnComplete(messagesRef.current, true); |
| 2163 | } |
no test coverage detected