2025-09-06 01:39:02 -04:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-09-12 22:42:17 -07:00
import {
useMemo ,
useState ,
useCallback ,
useEffect ,
useRef ,
useLayoutEffect ,
} from 'react' ;
2025-09-06 01:39:02 -04:00
import { type DOMElement , measureElement } from 'ink' ;
import { App } from './App.js' ;
import { AppContext } from './contexts/AppContext.js' ;
import { UIStateContext , type UIState } from './contexts/UIStateContext.js' ;
import {
UIActionsContext ,
type UIActions ,
} from './contexts/UIActionsContext.js' ;
import { ConfigContext } from './contexts/ConfigContext.js' ;
import {
type HistoryItem ,
ToolCallStatus ,
type HistoryItemWithoutId ,
2026-01-21 16:16:30 -05:00
type HistoryItemToolGroup ,
2025-09-06 01:39:02 -04:00
AuthState ,
2026-01-30 09:57:34 -05:00
type ConfirmationRequest ,
2025-09-06 01:39:02 -04:00
} from './types.js' ;
2025-09-07 16:17:20 -04:00
import { MessageType , StreamingState } from './types.js' ;
2026-01-21 16:16:30 -05:00
import { ToolActionsProvider } from './contexts/ToolActionsContext.js' ;
2025-09-06 01:39:02 -04:00
import {
type EditorType ,
type Config ,
2025-09-18 15:23:24 -04:00
type IdeInfo ,
2025-09-06 01:39:02 -04:00
type IdeContext ,
2025-09-08 16:19:52 -04:00
type UserTierId ,
2025-10-23 14:14:14 -04:00
type UserFeedbackPayload ,
2026-01-22 10:30:44 -08:00
type AgentDefinition ,
2025-09-08 16:19:52 -04:00
IdeClient ,
2025-09-11 11:22:20 -07:00
ideContextStore ,
2025-09-06 01:39:02 -04:00
getErrorMessage ,
getAllGeminiMdFilenames ,
AuthType ,
clearCachedCredentialFile ,
2025-11-10 18:31:00 -07:00
type ResumedSessionData ,
2025-10-22 17:39:27 -04:00
recordExitFail ,
2025-09-11 13:27:27 -07:00
ShellExecutionService ,
2025-10-29 18:58:08 -07:00
saveApiKey ,
2025-10-21 16:35:22 -04:00
debugLogger ,
2025-10-23 14:14:14 -04:00
coreEvents ,
CoreEvent ,
2025-11-07 09:07:25 -08:00
refreshServerHierarchicalMemory ,
type MemoryChangedPayload ,
2025-11-21 08:31:47 -08:00
writeToStdout ,
disableMouseEvents ,
enterAlternateScreen ,
enableMouseEvents ,
disableLineWrapping ,
shouldEnterAlternateScreen ,
2025-12-01 10:06:13 -08:00
startupProfiler ,
2025-12-03 09:04:13 -08:00
SessionStartSource ,
SessionEndReason ,
2025-12-09 22:25:22 -05:00
generateSummary ,
2026-01-30 09:57:34 -05:00
type ConsentRequestPayload ,
2026-01-26 19:49:32 +00:00
type AgentsDiscoveredPayload ,
2026-01-26 06:31:19 -08:00
ChangeAuthRequestedError ,
2025-09-06 01:39:02 -04:00
} from '@google/gemini-cli-core' ;
import { validateAuthMethod } from '../config/auth.js' ;
import process from 'node:process' ;
import { useHistory } from './hooks/useHistoryManager.js' ;
2025-09-17 14:32:46 -07:00
import { useMemoryMonitor } from './hooks/useMemoryMonitor.js' ;
2025-09-06 01:39:02 -04:00
import { useThemeCommand } from './hooks/useThemeCommand.js' ;
import { useAuthCommand } from './auth/useAuth.js' ;
2025-09-08 16:19:52 -04:00
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js' ;
2025-09-06 01:39:02 -04:00
import { useEditorSettings } from './hooks/useEditorSettings.js' ;
import { useSettingsCommand } from './hooks/useSettingsCommand.js' ;
2025-09-23 12:50:09 -04:00
import { useModelCommand } from './hooks/useModelCommand.js' ;
2025-09-06 01:39:02 -04:00
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js' ;
import { useVimMode } from './contexts/VimModeContext.js' ;
import { useConsoleMessages } from './hooks/useConsoleMessages.js' ;
import { useTerminalSize } from './hooks/useTerminalSize.js' ;
2025-09-17 13:17:50 -07:00
import { calculatePromptWidths } from './components/InputPrompt.js' ;
2025-11-20 10:44:02 -08:00
import { useApp , useStdout , useStdin } from 'ink' ;
2025-10-09 19:27:20 -07:00
import { calculateMainAreaWidth } from './utils/ui-sizing.js' ;
2025-09-06 01:39:02 -04:00
import ansiEscapes from 'ansi-escapes' ;
import * as fs from 'node:fs' ;
2025-09-28 03:48:24 +08:00
import { basename } from 'node:path' ;
2026-01-12 17:18:14 -08:00
import { computeTerminalTitle } from '../utils/windowTitle.js' ;
2025-09-06 01:39:02 -04:00
import { useTextBuffer } from './components/shared/text-buffer.js' ;
import { useLogger } from './hooks/useLogger.js' ;
import { useGeminiStream } from './hooks/useGeminiStream.js' ;
2026-01-30 09:53:09 -08:00
import { type BackgroundShell } from './hooks/shellCommandProcessor.js' ;
2025-09-06 01:39:02 -04:00
import { useVim } from './hooks/vim.js' ;
2025-11-11 07:50:11 -08:00
import { type LoadableSettingScope , SettingScope } from '../config/settings.js' ;
2025-09-06 01:39:02 -04:00
import { type InitializationResult } from '../core/initializer.js' ;
import { useFocus } from './hooks/useFocus.js' ;
import { useKeypress , type Key } from './hooks/useKeypress.js' ;
import { keyMatchers , Command } from './keyMatchers.js' ;
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js' ;
2026-01-21 14:31:24 -08:00
import { useShellInactivityStatus } from './hooks/useShellInactivityStatus.js' ;
2025-09-06 01:39:02 -04:00
import { useFolderTrust } from './hooks/useFolderTrust.js' ;
import { useIdeTrustListener } from './hooks/useIdeTrustListener.js' ;
import { type IdeIntegrationNudgeResult } from './IdeIntegrationNudge.js' ;
import { appEvents , AppEvent } from '../utils/events.js' ;
import { type UpdateObject } from './utils/updateCheck.js' ;
import { setUpdateHandler } from '../utils/handleAutoUpdate.js' ;
import { registerCleanup , runExitCleanup } from '../utils/cleanup.js' ;
2025-11-21 08:31:47 -08:00
import { RELAUNCH_EXIT_CODE } from '../utils/processUtils.js' ;
2025-11-25 11:54:09 -07:00
import type { SessionInfo } from '../utils/sessionUtils.js' ;
2025-09-06 01:39:02 -04:00
import { useMessageQueue } from './hooks/useMessageQueue.js' ;
2026-01-23 18:32:06 -05:00
import { useMcpStatus } from './hooks/useMcpStatus.js' ;
2026-01-21 10:19:47 -05:00
import { useApprovalModeIndicator } from './hooks/useApprovalModeIndicator.js' ;
2025-09-06 01:39:02 -04:00
import { useSessionStats } from './contexts/SessionContext.js' ;
import { useGitBranchName } from './hooks/useGitBranchName.js' ;
2025-10-23 11:39:36 -07:00
import {
useConfirmUpdateRequests ,
useExtensionUpdates ,
} from './hooks/useExtensionUpdates.js' ;
2025-09-20 10:59:37 -07:00
import { ShellFocusContext } from './contexts/ShellFocusContext.js' ;
2025-10-28 09:04:30 -07:00
import { type ExtensionManager } from '../config/extension-manager.js' ;
2025-10-23 11:39:36 -07:00
import { requestConsentInteractive } from '../config/extensions/consent.js' ;
2025-11-25 11:54:09 -07:00
import { useSessionBrowser } from './hooks/useSessionBrowser.js' ;
import { useSessionResume } from './hooks/useSessionResume.js' ;
2025-11-14 19:06:30 -08:00
import { useIncludeDirsTrust } from './hooks/useIncludeDirsTrust.js' ;
import { isWorkspaceTrusted } from '../config/trustedFolders.js' ;
2025-11-11 07:50:11 -08:00
import { useAlternateBuffer } from './hooks/useAlternateBuffer.js' ;
import { useSettings } from './contexts/SettingsContext.js' ;
2025-12-18 10:36:48 -08:00
import { terminalCapabilityManager } from './utils/terminalCapabilityManager.js' ;
2025-12-01 06:21:53 +05:30
import { useInputHistoryStore } from './hooks/useInputHistoryStore.js' ;
2025-12-03 09:08:32 -08:00
import { useBanner } from './hooks/useBanner.js' ;
2026-01-06 15:52:12 -05:00
import { useHookDisplayState } from './hooks/useHookDisplayState.js' ;
2026-01-30 09:53:09 -08:00
import { useBackgroundShellManager } from './hooks/useBackgroundShellManager.js' ;
2026-01-06 15:52:12 -05:00
import {
WARNING_PROMPT_DURATION_MS ,
QUEUE_ERROR_DISPLAY_DURATION_MS ,
} from './constants.js' ;
2026-01-12 15:39:08 -05:00
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js' ;
2026-01-26 19:49:32 +00:00
import { NewAgentsChoice } from './components/NewAgentsNotification.js' ;
2026-01-23 18:32:06 -05:00
import { isSlashCommand } from './utils/commandUtils.js' ;
2026-02-02 16:39:17 -08:00
import { useTerminalTheme } from './hooks/useTerminalTheme.js' ;
2026-02-05 10:54:46 -08:00
import { isITerm2 } from './utils/terminalUtils.js' ;
2025-09-06 01:39:02 -04:00
function isToolExecuting ( pendingHistoryItems : HistoryItemWithoutId [ ] ) {
return pendingHistoryItems . some ( ( item ) = > {
if ( item && item . type === 'tool_group' ) {
return item . tools . some (
( tool ) = > ToolCallStatus . Executing === tool . status ,
) ;
}
return false ;
} ) ;
}
2026-01-24 01:07:22 +05:30
function isToolAwaitingConfirmation (
pendingHistoryItems : HistoryItemWithoutId [ ] ,
) {
return pendingHistoryItems
. filter ( ( item ) : item is HistoryItemToolGroup = > item . type === 'tool_group' )
. some ( ( item ) = >
item . tools . some ( ( tool ) = > ToolCallStatus . Confirming === tool . status ) ,
) ;
}
2025-09-06 01:39:02 -04:00
interface AppContainerProps {
config : Config ;
startupWarnings? : string [ ] ;
version : string ;
initializationResult : InitializationResult ;
2025-11-10 18:31:00 -07:00
resumedSessionData? : ResumedSessionData ;
2025-09-06 01:39:02 -04:00
}
2025-09-11 13:27:27 -07:00
/ * *
* The fraction of the terminal width to allocate to the shell .
* This provides horizontal padding .
* /
const SHELL_WIDTH_FRACTION = 0.89 ;
/ * *
* The number of lines to subtract from the available terminal height
* for the shell . This provides vertical padding and space for other UI elements .
* /
const SHELL_HEIGHT_PADDING = 10 ;
2025-09-06 01:39:02 -04:00
export const AppContainer = ( props : AppContainerProps ) = > {
2025-11-11 07:50:11 -08:00
const { config , initializationResult , resumedSessionData } = props ;
2026-01-29 00:07:52 -05:00
const settings = useSettings ( ) ;
2025-11-19 09:32:13 -07:00
const historyManager = useHistory ( {
chatRecordingService : config.getGeminiClient ( ) ? . getChatRecordingService ( ) ,
} ) ;
2026-01-29 00:07:52 -05:00
2025-09-17 14:32:46 -07:00
useMemoryMonitor ( historyManager ) ;
2025-11-11 07:50:11 -08:00
const isAlternateBuffer = useAlternateBuffer ( ) ;
2025-09-06 01:39:02 -04:00
const [ corgiMode , setCorgiMode ] = useState ( false ) ;
const [ debugMessage , setDebugMessage ] = useState < string > ( '' ) ;
const [ quittingMessages , setQuittingMessages ] = useState <
HistoryItem [ ] | null
> ( null ) ;
const [ showPrivacyNotice , setShowPrivacyNotice ] = useState < boolean > ( false ) ;
const [ themeError , setThemeError ] = useState < string | null > (
initializationResult . themeError ,
) ;
const [ isProcessing , setIsProcessing ] = useState < boolean > ( false ) ;
2025-09-20 10:59:37 -07:00
const [ embeddedShellFocused , setEmbeddedShellFocused ] = useState ( false ) ;
2025-10-07 10:28:35 -07:00
const [ showDebugProfiler , setShowDebugProfiler ] = useState ( false ) ;
2025-11-14 19:06:30 -08:00
const [ customDialog , setCustomDialog ] = useState < React.ReactNode | null > (
null ,
) ;
2025-11-03 13:41:58 -08:00
const [ copyModeEnabled , setCopyModeEnabled ] = useState ( false ) ;
2025-11-20 13:54:16 +08:00
const [ pendingRestorePrompt , setPendingRestorePrompt ] = useState ( false ) ;
2026-01-30 09:53:09 -08:00
const toggleBackgroundShellRef = useRef < ( ) = > void > ( ( ) = > { } ) ;
const isBackgroundShellVisibleRef = useRef < boolean > ( false ) ;
const backgroundShellsRef = useRef < Map < number , BackgroundShell > > ( new Map ( ) ) ;
2026-01-16 15:24:53 -05:00
const [ adminSettingsChanged , setAdminSettingsChanged ] = useState ( false ) ;
2025-09-11 13:27:27 -07:00
2025-09-06 01:39:02 -04:00
const [ shellModeActive , setShellModeActive ] = useState ( false ) ;
const [ modelSwitchedFromQuotaError , setModelSwitchedFromQuotaError ] =
useState < boolean > ( false ) ;
const [ historyRemountKey , setHistoryRemountKey ] = useState ( 0 ) ;
2026-01-05 15:12:51 -08:00
const [ settingsNonce , setSettingsNonce ] = useState ( 0 ) ;
2026-01-06 15:52:12 -05:00
const activeHooks = useHookDisplayState ( ) ;
2025-09-06 01:39:02 -04:00
const [ updateInfo , setUpdateInfo ] = useState < UpdateObject | null > ( null ) ;
const [ isTrustedFolder , setIsTrustedFolder ] = useState < boolean | undefined > (
2026-02-03 14:53:31 -08:00
( ) = > isWorkspaceTrusted ( settings . merged ) . isTrusted ,
2025-09-06 01:39:02 -04:00
) ;
2025-09-18 14:49:47 -07:00
2025-10-15 22:32:50 +05:30
const [ queueErrorMessage , setQueueErrorMessage ] = useState < string | null > (
null ,
) ;
2026-01-26 19:49:32 +00:00
const [ newAgents , setNewAgents ] = useState < AgentDefinition [ ] | null > ( null ) ;
2025-11-18 12:01:16 -05:00
const [ defaultBannerText , setDefaultBannerText ] = useState ( '' ) ;
const [ warningBannerText , setWarningBannerText ] = useState ( '' ) ;
const [ bannerVisible , setBannerVisible ] = useState ( true ) ;
2025-12-03 09:08:32 -08:00
const bannerData = useMemo (
( ) = > ( {
defaultText : defaultBannerText ,
warningText : warningBannerText ,
} ) ,
[ defaultBannerText , warningBannerText ] ,
) ;
2026-02-06 13:02:57 -05:00
const { bannerText } = useBanner ( bannerData ) ;
2025-12-03 09:08:32 -08:00
2025-10-28 09:04:30 -07:00
const extensionManager = config . getExtensionLoader ( ) as ExtensionManager ;
// We are in the interactive CLI, update how we request consent and settings.
extensionManager . setRequestConsent ( ( description ) = >
requestConsentInteractive ( description , addConfirmUpdateExtensionRequest ) ,
2025-10-20 16:15:23 -07:00
) ;
2025-10-28 09:04:30 -07:00
extensionManager . setRequestSetting ( ) ;
2025-10-23 11:39:36 -07:00
const { addConfirmUpdateExtensionRequest , confirmUpdateExtensionRequests } =
useConfirmUpdateRequests ( ) ;
2025-09-29 14:19:19 -07:00
const {
extensionsUpdateState ,
2025-10-01 14:53:15 -07:00
extensionsUpdateStateInternal ,
dispatchExtensionStateUpdate ,
2025-10-30 11:05:49 -07:00
} = useExtensionUpdates (
extensionManager ,
historyManager . addItem ,
config . getEnableExtensionReloading ( ) ,
) ;
2025-09-08 16:19:52 -04:00
2025-09-22 11:45:02 -07:00
const [ isPermissionsDialogOpen , setPermissionsDialogOpen ] = useState ( false ) ;
2025-11-14 14:41:53 -08:00
const [ permissionsDialogProps , setPermissionsDialogProps ] = useState < {
targetDirectory? : string ;
} | null > ( null ) ;
2025-09-22 11:45:02 -07:00
const openPermissionsDialog = useCallback (
2025-11-14 14:41:53 -08:00
( props ? : { targetDirectory? : string } ) = > {
setPermissionsDialogOpen ( true ) ;
setPermissionsDialogProps ( props ? ? null ) ;
} ,
2025-09-22 11:45:02 -07:00
[ ] ,
) ;
2025-11-14 14:41:53 -08:00
const closePermissionsDialog = useCallback ( ( ) = > {
setPermissionsDialogOpen ( false ) ;
setPermissionsDialogProps ( null ) ;
} , [ ] ) ;
2025-09-22 11:45:02 -07:00
2026-01-22 10:30:44 -08:00
const [ isAgentConfigDialogOpen , setIsAgentConfigDialogOpen ] = useState ( false ) ;
const [ selectedAgentName , setSelectedAgentName ] = useState <
string | undefined
> ( ) ;
const [ selectedAgentDisplayName , setSelectedAgentDisplayName ] = useState <
string | undefined
> ( ) ;
const [ selectedAgentDefinition , setSelectedAgentDefinition ] = useState <
AgentDefinition | undefined
> ( ) ;
const openAgentConfigDialog = useCallback (
( name : string , displayName : string , definition : AgentDefinition ) = > {
setSelectedAgentName ( name ) ;
setSelectedAgentDisplayName ( displayName ) ;
setSelectedAgentDefinition ( definition ) ;
setIsAgentConfigDialogOpen ( true ) ;
} ,
[ ] ,
) ;
const closeAgentConfigDialog = useCallback ( ( ) = > {
setIsAgentConfigDialogOpen ( false ) ;
setSelectedAgentName ( undefined ) ;
setSelectedAgentDisplayName ( undefined ) ;
setSelectedAgentDefinition ( undefined ) ;
} , [ ] ) ;
2025-10-07 10:28:35 -07:00
const toggleDebugProfiler = useCallback (
( ) = > setShowDebugProfiler ( ( prev ) = > ! prev ) ,
[ ] ,
) ;
2025-12-22 10:18:51 -05:00
const [ currentModel , setCurrentModel ] = useState ( config . getModel ( ) ) ;
2025-09-08 16:19:52 -04:00
2025-09-06 01:39:02 -04:00
const [ userTier , setUserTier ] = useState < UserTierId | undefined > ( undefined ) ;
2025-09-08 16:37:36 -07:00
const [ isConfigInitialized , setConfigInitialized ] = useState ( false ) ;
2025-09-06 01:39:02 -04:00
const logger = useLogger ( config . storage ) ;
2025-12-01 06:21:53 +05:30
const { inputHistory , addInput , initializeFromLogger } =
useInputHistoryStore ( ) ;
2025-09-06 01:39:02 -04:00
// Terminal and layout hooks
const { columns : terminalWidth , rows : terminalHeight } = useTerminalSize ( ) ;
const { stdin , setRawMode } = useStdin ( ) ;
const { stdout } = useStdout ( ) ;
2025-11-20 10:44:02 -08:00
const app = useApp ( ) ;
2025-09-06 01:39:02 -04:00
// Additional hooks moved from App.tsx
const { stats : sessionStats } = useSessionStats ( ) ;
const branchName = useGitBranchName ( config . getTargetDir ( ) ) ;
// Layout measurements
const mainControlsRef = useRef < DOMElement > ( null ) ;
2025-10-10 13:18:38 -07:00
// For performance profiling only
const rootUiRef = useRef < DOMElement > ( null ) ;
2025-09-28 03:48:24 +08:00
const lastTitleRef = useRef < string | null > ( null ) ;
2025-09-06 01:39:02 -04:00
const staticExtraHeight = 3 ;
useEffect ( ( ) = > {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-09-08 16:37:36 -07:00
( async ( ) = > {
// Note: the program will not work if this fails so let errors be
// handled by the global catch.
await config . initialize ( ) ;
setConfigInitialized ( true ) ;
2025-12-01 10:06:13 -08:00
startupProfiler . flush ( config ) ;
2025-12-03 09:04:13 -08:00
2026-01-09 22:17:54 +05:30
const sessionStartSource = resumedSessionData
? SessionStartSource . Resume
: SessionStartSource . Startup ;
const result = await config
. getHookSystem ( )
? . fireSessionStartEvent ( sessionStartSource ) ;
2026-01-05 13:27:53 -08:00
2026-01-20 08:55:43 +05:30
if ( result ) {
if ( result . systemMessage ) {
2026-01-09 22:17:54 +05:30
historyManager . addItem (
{
type : MessageType . INFO ,
2026-01-20 08:55:43 +05:30
text : result.systemMessage ,
2026-01-09 22:17:54 +05:30
} ,
Date . now ( ) ,
) ;
}
2026-01-05 13:27:53 -08:00
2026-01-20 08:55:43 +05:30
const additionalContext = result . getAdditionalContext ( ) ;
2026-01-09 22:17:54 +05:30
const geminiClient = config . getGeminiClient ( ) ;
if ( additionalContext && geminiClient ) {
await geminiClient . addHistory ( {
role : 'user' ,
2026-01-21 21:11:45 -05:00
parts : [
{ text : ` <hook_context> ${ additionalContext } </hook_context> ` } ,
] ,
2026-01-09 22:17:54 +05:30
} ) ;
2026-01-05 13:27:53 -08:00
}
2025-12-03 09:04:13 -08:00
}
2025-12-09 22:25:22 -05:00
// Fire-and-forget: generate summary for previous session in background
generateSummary ( config ) . catch ( ( e ) = > {
debugLogger . warn ( 'Background summary generation failed:' , e ) ;
} ) ;
2025-09-08 16:37:36 -07:00
} ) ( ) ;
2025-09-06 01:39:02 -04:00
registerCleanup ( async ( ) = > {
2025-11-03 13:41:58 -08:00
// Turn off mouse scroll.
disableMouseEvents ( ) ;
2026-01-30 09:53:09 -08:00
// Kill all background shells
for ( const pid of backgroundShellsRef . current . keys ( ) ) {
ShellExecutionService . kill ( pid ) ;
}
2025-09-06 01:39:02 -04:00
const ideClient = await IdeClient . getInstance ( ) ;
await ideClient . disconnect ( ) ;
2025-12-03 09:04:13 -08:00
// Fire SessionEnd hook on cleanup (only if hooks are enabled)
2026-01-09 22:17:54 +05:30
await config ? . getHookSystem ( ) ? . fireSessionEndEvent ( SessionEndReason . Exit ) ;
2025-09-06 01:39:02 -04:00
} ) ;
2026-01-05 13:27:53 -08:00
// Disable the dependencies check here. historyManager gets flagged
// but we don't want to react to changes to it because each new history
// item, including the ones from the start session hook will cause a
// re-render and an error when we try to reload config.
//
// eslint-disable-next-line react-hooks/exhaustive-deps
2025-12-03 09:04:13 -08:00
} , [ config , resumedSessionData ] ) ;
2025-09-06 01:39:02 -04:00
2025-09-08 16:37:36 -07:00
useEffect (
( ) = > setUpdateHandler ( historyManager . addItem , setUpdateInfo ) ,
[ historyManager . addItem ] ,
) ;
2025-09-06 01:39:02 -04:00
2025-11-03 14:59:51 -05:00
// Subscribe to fallback mode and model changes from core
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
2025-12-17 09:43:21 -08:00
const handleModelChanged = ( ) = > {
setCurrentModel ( config . getModel ( ) ) ;
2025-11-03 14:59:51 -05:00
} ;
coreEvents . on ( CoreEvent . ModelChanged , handleModelChanged ) ;
2025-10-27 15:33:12 -07:00
return ( ) = > {
2025-11-03 14:59:51 -05:00
coreEvents . off ( CoreEvent . ModelChanged , handleModelChanged ) ;
2025-10-27 15:33:12 -07:00
} ;
2025-12-22 10:18:51 -05:00
} , [ config ] ) ;
2025-09-06 01:39:02 -04:00
2026-01-05 15:12:51 -08:00
useEffect ( ( ) = > {
const handleSettingsChanged = ( ) = > {
setSettingsNonce ( ( prev ) = > prev + 1 ) ;
} ;
2026-01-16 15:24:53 -05:00
const handleAdminSettingsChanged = ( ) = > {
setAdminSettingsChanged ( true ) ;
} ;
2026-01-26 19:49:32 +00:00
const handleAgentsDiscovered = ( payload : AgentsDiscoveredPayload ) = > {
setNewAgents ( payload . agents ) ;
} ;
2026-01-05 15:12:51 -08:00
coreEvents . on ( CoreEvent . SettingsChanged , handleSettingsChanged ) ;
2026-01-16 15:24:53 -05:00
coreEvents . on ( CoreEvent . AdminSettingsChanged , handleAdminSettingsChanged ) ;
2026-01-26 19:49:32 +00:00
coreEvents . on ( CoreEvent . AgentsDiscovered , handleAgentsDiscovered ) ;
2026-01-05 15:12:51 -08:00
return ( ) = > {
coreEvents . off ( CoreEvent . SettingsChanged , handleSettingsChanged ) ;
2026-01-16 15:24:53 -05:00
coreEvents . off (
CoreEvent . AdminSettingsChanged ,
handleAdminSettingsChanged ,
) ;
2026-01-26 19:49:32 +00:00
coreEvents . off ( CoreEvent . AgentsDiscovered , handleAgentsDiscovered ) ;
2026-01-05 15:12:51 -08:00
} ;
} , [ ] ) ;
2025-11-20 10:44:02 -08:00
const { consoleMessages , clearConsoleMessages : clearConsoleMessagesState } =
useConsoleMessages ( ) ;
2025-09-06 01:39:02 -04:00
2025-10-09 19:27:20 -07:00
const mainAreaWidth = calculateMainAreaWidth ( terminalWidth , settings ) ;
2025-09-17 13:17:50 -07:00
// Derive widths for InputPrompt using shared helper
const { inputWidth , suggestionsWidth } = useMemo ( ( ) = > {
const { inputWidth , suggestionsWidth } =
2025-10-09 19:27:20 -07:00
calculatePromptWidths ( mainAreaWidth ) ;
2025-09-17 13:17:50 -07:00
return { inputWidth , suggestionsWidth } ;
2025-10-09 19:27:20 -07:00
} , [ mainAreaWidth ] ) ;
2025-09-06 01:39:02 -04:00
const staticAreaMaxItemHeight = Math . max ( terminalHeight * 4 , 100 ) ;
const isValidPath = useCallback ( ( filePath : string ) : boolean = > {
try {
return fs . existsSync ( filePath ) && fs . statSync ( filePath ) . isFile ( ) ;
} catch ( _e ) {
return false ;
}
} , [ ] ) ;
2026-01-13 16:55:07 -08:00
const getPreferredEditor = useCallback (
2026-01-15 09:26:10 -08:00
( ) = > settings . merged . general . preferredEditor as EditorType ,
[ settings . merged . general . preferredEditor ] ,
2026-01-13 16:55:07 -08:00
) ;
2025-09-06 01:39:02 -04:00
const buffer = useTextBuffer ( {
initialText : '' ,
viewport : { height : 10 , width : inputWidth } ,
stdin ,
setRawMode ,
isValidPath ,
shellModeActive ,
2026-01-13 16:55:07 -08:00
getPreferredEditor ,
2025-09-06 01:39:02 -04:00
} ) ;
2026-01-30 16:11:14 -08:00
const bufferRef = useRef ( buffer ) ;
useEffect ( ( ) = > {
bufferRef . current = buffer ;
} , [ buffer ] ) ;
const stableSetText = useCallback ( ( text : string ) = > {
bufferRef . current . setText ( text ) ;
} , [ ] ) ;
2025-09-06 01:39:02 -04:00
2025-12-01 06:21:53 +05:30
// Initialize input history from logger (past sessions)
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-12-01 06:21:53 +05:30
initializeFromLogger ( logger ) ;
} , [ logger , initializeFromLogger ] ) ;
2025-09-06 01:39:02 -04:00
const refreshStatic = useCallback ( ( ) = > {
2025-11-11 07:50:11 -08:00
if ( ! isAlternateBuffer ) {
2025-11-03 13:41:58 -08:00
stdout . write ( ansiEscapes . clearTerminal ) ;
}
2025-09-06 01:39:02 -04:00
setHistoryRemountKey ( ( prev ) = > prev + 1 ) ;
2025-11-20 10:44:02 -08:00
} , [ setHistoryRemountKey , isAlternateBuffer , stdout ] ) ;
2025-12-03 09:08:32 -08:00
2025-11-19 11:37:30 -08:00
const handleEditorClose = useCallback ( ( ) = > {
2025-11-21 08:31:47 -08:00
if (
shouldEnterAlternateScreen ( isAlternateBuffer , config . getScreenReader ( ) )
) {
2025-11-20 10:44:02 -08:00
// The editor may have exited alternate buffer mode so we need to
// enter it again to be safe.
2025-11-21 08:31:47 -08:00
enterAlternateScreen ( ) ;
2025-11-20 10:44:02 -08:00
enableMouseEvents ( ) ;
2025-11-21 08:31:47 -08:00
disableLineWrapping ( ) ;
2025-11-20 10:44:02 -08:00
app . rerender ( ) ;
}
2026-01-05 14:46:23 -08:00
terminalCapabilityManager . enableSupportedModes ( ) ;
2025-11-19 11:37:30 -08:00
refreshStatic ( ) ;
2025-11-21 08:31:47 -08:00
} , [ refreshStatic , isAlternateBuffer , app , config ] ) ;
2025-11-20 10:44:02 -08:00
2026-02-05 21:52:41 +01:00
const [ editorError , setEditorError ] = useState < string | null > ( null ) ;
const {
isEditorDialogOpen ,
openEditorDialog ,
handleEditorSelect ,
exitEditorDialog ,
} = useEditorSettings ( settings , setEditorError , historyManager . addItem ) ;
2025-11-20 10:44:02 -08:00
useEffect ( ( ) = > {
coreEvents . on ( CoreEvent . ExternalEditorClosed , handleEditorClose ) ;
2026-02-05 21:52:41 +01:00
coreEvents . on ( CoreEvent . RequestEditorSelection , openEditorDialog ) ;
2025-11-20 10:44:02 -08:00
return ( ) = > {
coreEvents . off ( CoreEvent . ExternalEditorClosed , handleEditorClose ) ;
2026-02-05 21:52:41 +01:00
coreEvents . off ( CoreEvent . RequestEditorSelection , openEditorDialog ) ;
2025-11-20 10:44:02 -08:00
} ;
2026-02-05 21:52:41 +01:00
} , [ handleEditorClose , openEditorDialog ] ) ;
2025-11-19 11:37:30 -08:00
2025-12-03 09:08:32 -08:00
useEffect ( ( ) = > {
if (
2026-01-15 09:26:10 -08:00
! ( settings . merged . ui . hideBanner || config . getScreenReader ( ) ) &&
2025-12-03 09:08:32 -08:00
bannerVisible &&
bannerText
) {
// The header should show a banner but the Header is rendered in static
// so we must trigger a static refresh for it to be visible.
refreshStatic ( ) ;
}
} , [ bannerVisible , bannerText , settings , config , refreshStatic ] ) ;
2026-02-05 21:52:41 +01:00
const { isSettingsDialogOpen , openSettingsDialog , closeSettingsDialog } =
useSettingsCommand ( ) ;
2025-09-06 01:39:02 -04:00
const {
isThemeDialogOpen ,
openThemeDialog ,
2025-10-20 10:50:09 -07:00
closeThemeDialog ,
2025-09-06 01:39:02 -04:00
handleThemeSelect ,
handleThemeHighlight ,
} = useThemeCommand (
settings ,
setThemeError ,
historyManager . addItem ,
initializationResult . themeError ,
) ;
2026-02-02 16:39:17 -08:00
// Poll for terminal background color changes to auto-switch theme
useTerminalTheme ( handleThemeSelect , config ) ;
2025-10-29 18:58:08 -07:00
const {
authState ,
setAuthState ,
authError ,
onAuthError ,
apiKeyDefaultValue ,
reloadApiKey ,
2026-01-26 06:31:19 -08:00
} = useAuthCommand ( settings , config , initializationResult . authError ) ;
2026-01-12 15:39:08 -05:00
const [ authContext , setAuthContext ] = useState < { requiresRestart? : boolean } > (
{ } ,
) ;
useEffect ( ( ) = > {
if ( authState === AuthState . Authenticated && authContext . requiresRestart ) {
setAuthState ( AuthState . AwaitingGoogleLoginRestart ) ;
setAuthContext ( { } ) ;
}
} , [ authState , authContext , setAuthState ] ) ;
2025-09-06 01:39:02 -04:00
2026-01-20 16:23:01 -08:00
const {
proQuotaRequest ,
handleProQuotaChoice ,
validationRequest ,
handleValidationChoice ,
} = useQuotaAndFallback ( {
2025-09-08 16:19:52 -04:00
config ,
historyManager ,
userTier ,
setModelSwitchedFromQuotaError ,
2026-01-26 06:31:19 -08:00
onShowAuthSelection : ( ) = > setAuthState ( AuthState . Updating ) ,
2025-09-08 16:19:52 -04:00
} ) ;
2025-09-06 01:39:02 -04:00
// Derive auth state variables for backward compatibility with UIStateContext
const isAuthDialogOpen = authState === AuthState . Updating ;
const isAuthenticating = authState === AuthState . Unauthenticated ;
2025-11-10 18:31:00 -07:00
// Session browser and resume functionality
const isGeminiClientInitialized = config . getGeminiClient ( ) ? . isInitialized ( ) ;
2026-01-26 19:59:20 +03:00
const { loadHistoryForResume , isResuming } = useSessionResume ( {
2025-11-10 18:31:00 -07:00
config ,
historyManager ,
refreshStatic ,
isGeminiClientInitialized ,
setQuittingMessages ,
resumedSessionData ,
isAuthenticating ,
} ) ;
2025-11-25 11:54:09 -07:00
const {
isSessionBrowserOpen ,
openSessionBrowser ,
closeSessionBrowser ,
handleResumeSession ,
handleDeleteSession : handleDeleteSessionSync ,
} = useSessionBrowser ( config , loadHistoryForResume ) ;
// Wrap handleDeleteSession to return a Promise for UIActions interface
const handleDeleteSession = useCallback (
async ( session : SessionInfo ) : Promise < void > = > {
handleDeleteSessionSync ( session ) ;
} ,
[ handleDeleteSessionSync ] ,
) ;
2025-11-10 18:31:00 -07:00
2025-09-06 01:39:02 -04:00
// Create handleAuthSelect wrapper for backward compatibility
const handleAuthSelect = useCallback (
2025-11-05 11:36:07 -08:00
async ( authType : AuthType | undefined , scope : LoadableSettingScope ) = > {
2025-09-06 01:39:02 -04:00
if ( authType ) {
2026-01-12 15:39:08 -05:00
if ( authType === AuthType . LOGIN_WITH_GOOGLE ) {
setAuthContext ( { requiresRestart : true } ) ;
} else {
setAuthContext ( { } ) ;
}
2025-09-06 01:39:02 -04:00
await clearCachedCredentialFile ( ) ;
settings . setValue ( scope , 'security.auth.selectedType' , authType ) ;
try {
await config . refreshAuth ( authType ) ;
setAuthState ( AuthState . Authenticated ) ;
} catch ( e ) {
2026-01-26 06:31:19 -08:00
if ( e instanceof ChangeAuthRequestedError ) {
return ;
}
2025-09-06 01:39:02 -04:00
onAuthError (
` Failed to authenticate: ${ e instanceof Error ? e.message : String ( e ) } ` ,
) ;
return ;
}
if (
authType === AuthType . LOGIN_WITH_GOOGLE &&
config . isBrowserLaunchSuppressed ( )
) {
await runExitCleanup ( ) ;
2025-11-21 08:31:47 -08:00
writeToStdout ( `
2025-09-06 01:39:02 -04:00
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
2025-11-21 08:31:47 -08:00
Logging in with Google . . . Restarting Gemini CLI to continue .
2025-09-06 01:39:02 -04:00
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
` );
2025-11-21 08:31:47 -08:00
process . exit ( RELAUNCH_EXIT_CODE ) ;
2025-09-06 01:39:02 -04:00
}
}
setAuthState ( AuthState . Authenticated ) ;
} ,
2026-01-12 15:39:08 -05:00
[ settings , config , setAuthState , onAuthError , setAuthContext ] ,
2025-09-06 01:39:02 -04:00
) ;
2025-10-29 18:58:08 -07:00
const handleApiKeySubmit = useCallback (
async ( apiKey : string ) = > {
try {
2025-11-03 15:07:22 -08:00
onAuthError ( null ) ;
2025-10-29 18:58:08 -07:00
if ( ! apiKey . trim ( ) && apiKey . length > 1 ) {
onAuthError (
'API key cannot be empty string with length greater than 1.' ,
) ;
return ;
}
await saveApiKey ( apiKey ) ;
await reloadApiKey ( ) ;
await config . refreshAuth ( AuthType . USE_GEMINI ) ;
setAuthState ( AuthState . Authenticated ) ;
} catch ( e ) {
onAuthError (
` Failed to save API key: ${ e instanceof Error ? e.message : String ( e ) } ` ,
) ;
}
} ,
[ setAuthState , onAuthError , reloadApiKey , config ] ,
) ;
const handleApiKeyCancel = useCallback ( ( ) = > {
// Go back to auth method selection
setAuthState ( AuthState . Updating ) ;
} , [ setAuthState ] ) ;
2025-09-06 01:39:02 -04:00
// Sync user tier from config when authentication changes
useEffect ( ( ) = > {
// Only sync when not currently authenticating
if ( authState === AuthState . Authenticated ) {
2025-09-07 13:00:03 -07:00
setUserTier ( config . getUserTier ( ) ) ;
2025-09-06 01:39:02 -04:00
}
} , [ config , authState ] ) ;
// Check for enforced auth type mismatch
useEffect ( ( ) = > {
if (
2026-01-15 09:26:10 -08:00
settings . merged . security . auth . enforcedType &&
settings . merged . security . auth . selectedType &&
settings . merged . security . auth . enforcedType !==
settings . merged . security . auth . selectedType
2025-09-06 01:39:02 -04:00
) {
onAuthError (
2026-01-15 09:26:10 -08:00
` Authentication is enforced to be ${ settings . merged . security . auth . enforcedType } , but you are currently using ${ settings . merged . security . auth . selectedType } . ` ,
2025-09-06 01:39:02 -04:00
) ;
} else if (
2026-01-15 09:26:10 -08:00
settings . merged . security . auth . selectedType &&
! settings . merged . security . auth . useExternal
2025-09-06 01:39:02 -04:00
) {
2025-12-12 17:50:21 -08:00
// We skip validation for Gemini API key here because it might be stored
// in the keychain, which we can't check synchronously.
// The useAuth hook handles validation for this case.
if ( settings . merged . security . auth . selectedType === AuthType . USE_GEMINI ) {
return ;
}
2025-09-06 01:39:02 -04:00
const error = validateAuthMethod (
settings . merged . security . auth . selectedType ,
) ;
if ( error ) {
onAuthError ( error ) ;
}
}
} , [
2026-01-15 09:26:10 -08:00
settings . merged . security . auth . selectedType ,
settings . merged . security . auth . enforcedType ,
settings . merged . security . auth . useExternal ,
2025-09-06 01:39:02 -04:00
onAuthError ,
] ) ;
2025-09-23 12:50:09 -04:00
const { isModelDialogOpen , openModelDialog , closeModelDialog } =
useModelCommand ( ) ;
2025-09-06 01:39:02 -04:00
const { toggleVimEnabled } = useVimMode ( ) ;
2026-01-30 09:53:09 -08:00
const setIsBackgroundShellListOpenRef = useRef < ( open : boolean ) = > void > (
( ) = > { } ,
) ;
2026-02-06 11:33:39 -08:00
const [ shortcutsHelpVisible , setShortcutsHelpVisible ] = useState ( false ) ;
2026-01-30 09:53:09 -08:00
2025-09-06 01:39:02 -04:00
const slashCommandActions = useMemo (
( ) = > ( {
openAuthDialog : ( ) = > setAuthState ( AuthState . Updating ) ,
openThemeDialog ,
openEditorDialog ,
openPrivacyNotice : ( ) = > setShowPrivacyNotice ( true ) ,
openSettingsDialog ,
2025-11-25 11:54:09 -07:00
openSessionBrowser ,
2025-09-23 12:50:09 -04:00
openModelDialog ,
2026-01-22 10:30:44 -08:00
openAgentConfigDialog ,
2025-09-22 11:45:02 -07:00
openPermissionsDialog ,
2025-09-06 01:39:02 -04:00
quit : ( messages : HistoryItem [ ] ) = > {
setQuittingMessages ( messages ) ;
setTimeout ( async ( ) = > {
await runExitCleanup ( ) ;
process . exit ( 0 ) ;
} , 100 ) ;
} ,
setDebugMessage ,
toggleCorgiMode : ( ) = > setCorgiMode ( ( prev ) = > ! prev ) ,
2025-10-07 10:28:35 -07:00
toggleDebugProfiler ,
2025-10-01 14:53:15 -07:00
dispatchExtensionStateUpdate ,
2025-09-29 14:19:19 -07:00
addConfirmUpdateExtensionRequest ,
2026-01-30 09:53:09 -08:00
toggleBackgroundShell : ( ) = > {
toggleBackgroundShellRef . current ( ) ;
if ( ! isBackgroundShellVisibleRef . current ) {
setEmbeddedShellFocused ( true ) ;
if ( backgroundShellsRef . current . size > 1 ) {
setIsBackgroundShellListOpenRef . current ( true ) ;
} else {
setIsBackgroundShellListOpenRef . current ( false ) ;
}
}
} ,
2026-02-06 11:33:39 -08:00
toggleShortcutsHelp : ( ) = > setShortcutsHelpVisible ( ( visible ) = > ! visible ) ,
2026-01-30 16:11:14 -08:00
setText : stableSetText ,
2025-09-06 01:39:02 -04:00
} ) ,
[
setAuthState ,
openThemeDialog ,
openEditorDialog ,
openSettingsDialog ,
2025-11-25 11:54:09 -07:00
openSessionBrowser ,
2025-09-23 12:50:09 -04:00
openModelDialog ,
2026-01-22 10:30:44 -08:00
openAgentConfigDialog ,
2025-09-06 01:39:02 -04:00
setQuittingMessages ,
setDebugMessage ,
setShowPrivacyNotice ,
setCorgiMode ,
2025-10-01 14:53:15 -07:00
dispatchExtensionStateUpdate ,
2025-09-22 11:45:02 -07:00
openPermissionsDialog ,
2025-09-29 14:19:19 -07:00
addConfirmUpdateExtensionRequest ,
2025-10-07 10:28:35 -07:00
toggleDebugProfiler ,
2026-02-06 11:33:39 -08:00
setShortcutsHelpVisible ,
2026-01-30 16:11:14 -08:00
stableSetText ,
2025-09-06 01:39:02 -04:00
] ,
) ;
const {
handleSlashCommand ,
slashCommands ,
pendingHistoryItems : pendingSlashCommandHistoryItems ,
commandContext ,
2026-01-30 09:57:34 -05:00
confirmationRequest : commandConfirmationRequest ,
2025-09-06 01:39:02 -04:00
} = useSlashCommandProcessor (
config ,
settings ,
historyManager . addItem ,
historyManager . clearItems ,
historyManager . loadHistory ,
refreshStatic ,
toggleVimEnabled ,
setIsProcessing ,
slashCommandActions ,
2025-10-01 14:53:15 -07:00
extensionsUpdateStateInternal ,
2025-09-11 12:59:38 -07:00
isConfigInitialized ,
2025-11-18 12:01:16 -05:00
setBannerVisible ,
2025-11-14 19:06:30 -08:00
setCustomDialog ,
2025-09-06 01:39:02 -04:00
) ;
2026-01-30 09:57:34 -05:00
const [ authConsentRequest , setAuthConsentRequest ] =
useState < ConfirmationRequest | null > ( null ) ;
useEffect ( ( ) = > {
const handleConsentRequest = ( payload : ConsentRequestPayload ) = > {
setAuthConsentRequest ( {
prompt : payload.prompt ,
onConfirm : ( confirmed : boolean ) = > {
setAuthConsentRequest ( null ) ;
payload . onConfirm ( confirmed ) ;
} ,
} ) ;
} ;
coreEvents . on ( CoreEvent . ConsentRequest , handleConsentRequest ) ;
return ( ) = > {
coreEvents . off ( CoreEvent . ConsentRequest , handleConsentRequest ) ;
} ;
} , [ ] ) ;
2025-09-06 01:39:02 -04:00
const performMemoryRefresh = useCallback ( async ( ) = > {
historyManager . addItem (
{
type : MessageType . INFO ,
text : 'Refreshing hierarchical memory (GEMINI.md or other context files)...' ,
} ,
Date . now ( ) ,
) ;
try {
2025-11-07 09:07:25 -08:00
const { memoryContent , fileCount } =
await refreshServerHierarchicalMemory ( config ) ;
2025-09-06 01:39:02 -04:00
historyManager . addItem (
{
type : MessageType . INFO ,
text : ` Memory refreshed successfully. ${
memoryContent . length > 0
? ` Loaded ${ memoryContent . length } characters from ${ fileCount } file(s). `
: 'No memory content found.'
} ` ,
} ,
Date . now ( ) ,
) ;
if ( config . getDebugMode ( ) ) {
2025-10-21 16:35:22 -04:00
debugLogger . log (
2025-09-06 01:39:02 -04:00
` [DEBUG] Refreshed memory content in config: ${ memoryContent . substring (
0 ,
200 ,
) } . . . ` ,
) ;
}
} catch ( error ) {
const errorMessage = getErrorMessage ( error ) ;
historyManager . addItem (
{
type : MessageType . ERROR ,
text : ` Error refreshing memory: ${ errorMessage } ` ,
} ,
Date . now ( ) ,
) ;
2025-10-27 11:35:16 -07:00
debugLogger . warn ( 'Error refreshing memory:' , error ) ;
2025-09-06 01:39:02 -04:00
}
2025-11-07 09:07:25 -08:00
} , [ config , historyManager ] ) ;
2025-09-06 01:39:02 -04:00
2025-11-19 11:11:36 +08:00
const cancelHandlerRef = useRef < ( shouldRestorePrompt? : boolean ) = > void > (
( ) = > { } ,
) ;
2025-09-06 01:39:02 -04:00
2025-11-19 11:11:36 +08:00
const onCancelSubmit = useCallback ( ( shouldRestorePrompt? : boolean ) = > {
2025-11-20 13:54:16 +08:00
if ( shouldRestorePrompt ) {
setPendingRestorePrompt ( true ) ;
} else {
setPendingRestorePrompt ( false ) ;
cancelHandlerRef . current ( false ) ;
}
2025-10-23 09:35:32 -07:00
} , [ ] ) ;
2025-11-20 13:54:16 +08:00
useEffect ( ( ) = > {
if ( pendingRestorePrompt ) {
const lastHistoryUserMsg = historyManager . history . findLast (
( h ) = > h . type === 'user' ,
) ;
2025-12-01 06:21:53 +05:30
const lastUserMsg = inputHistory . at ( - 1 ) ;
2025-11-20 13:54:16 +08:00
if (
! lastHistoryUserMsg ||
( typeof lastHistoryUserMsg . text === 'string' &&
lastHistoryUserMsg . text === lastUserMsg )
) {
cancelHandlerRef . current ( true ) ;
setPendingRestorePrompt ( false ) ;
}
}
2025-12-01 06:21:53 +05:30
} , [ pendingRestorePrompt , inputHistory , historyManager . history ] ) ;
2025-11-20 13:54:16 +08:00
2025-09-06 01:39:02 -04:00
const {
streamingState ,
submitQuery ,
initError ,
pendingHistoryItems : pendingGeminiHistoryItems ,
thought ,
cancelOngoingRequest ,
2026-01-21 14:31:24 -08:00
pendingToolCalls ,
2025-09-14 20:20:21 -07:00
handleApprovalModeChange ,
2025-09-11 13:27:27 -07:00
activePtyId ,
2025-09-10 22:20:13 -07:00
loopDetectionConfirmationRequest ,
2025-11-21 12:19:34 -05:00
lastOutputTime ,
2026-01-30 09:53:09 -08:00
backgroundShellCount ,
isBackgroundShellVisible ,
toggleBackgroundShell ,
backgroundCurrentShell ,
backgroundShells ,
dismissBackgroundShell ,
2026-01-13 23:03:19 -05:00
retryStatus ,
2025-09-06 01:39:02 -04:00
} = useGeminiStream (
config . getGeminiClient ( ) ,
historyManager . history ,
historyManager . addItem ,
config ,
settings ,
setDebugMessage ,
handleSlashCommand ,
shellModeActive ,
2025-10-23 09:35:32 -07:00
getPreferredEditor ,
2025-09-06 01:39:02 -04:00
onAuthError ,
performMemoryRefresh ,
modelSwitchedFromQuotaError ,
setModelSwitchedFromQuotaError ,
2025-10-23 09:35:32 -07:00
onCancelSubmit ,
2025-09-20 10:59:37 -07:00
setEmbeddedShellFocused ,
2025-09-11 13:27:27 -07:00
terminalWidth ,
terminalHeight ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2025-09-06 01:39:02 -04:00
) ;
2026-01-30 09:53:09 -08:00
toggleBackgroundShellRef . current = toggleBackgroundShell ;
isBackgroundShellVisibleRef . current = isBackgroundShellVisible ;
backgroundShellsRef . current = backgroundShells ;
const {
activeBackgroundShellPid ,
setIsBackgroundShellListOpen ,
isBackgroundShellListOpen ,
setActiveBackgroundShellPid ,
backgroundShellHeight ,
} = useBackgroundShellManager ( {
backgroundShells ,
backgroundShellCount ,
isBackgroundShellVisible ,
activePtyId ,
embeddedShellFocused ,
setEmbeddedShellFocused ,
terminalHeight ,
} ) ;
setIsBackgroundShellListOpenRef . current = setIsBackgroundShellListOpen ;
2026-01-12 15:30:12 -08:00
const lastOutputTimeRef = useRef ( 0 ) ;
2026-01-30 09:53:09 -08:00
2026-01-12 15:30:12 -08:00
useEffect ( ( ) = > {
lastOutputTimeRef . current = lastOutputTime ;
} , [ lastOutputTime ] ) ;
2026-01-21 14:31:24 -08:00
const { shouldShowFocusHint , inactivityStatus } = useShellInactivityStatus ( {
activePtyId ,
2026-01-13 06:19:53 -08:00
lastOutputTime ,
2026-01-21 14:31:24 -08:00
streamingState ,
pendingToolCalls ,
embeddedShellFocused ,
isInteractiveShellEnabled : config.isInteractiveShellEnabled ( ) ,
} ) ;
const shouldShowActionRequiredTitle = inactivityStatus === 'action_required' ;
const shouldShowSilentWorkingTitle = inactivityStatus === 'silent_working' ;
2026-01-12 17:18:14 -08:00
2025-09-14 20:20:21 -07:00
// Auto-accept indicator
2026-01-21 10:19:47 -05:00
const showApprovalModeIndicator = useApprovalModeIndicator ( {
2025-09-14 20:20:21 -07:00
config ,
addItem : historyManager.addItem ,
onApprovalModeChange : handleApprovalModeChange ,
2026-01-12 15:30:12 -08:00
isActive : ! embeddedShellFocused ,
2025-09-14 20:20:21 -07:00
} ) ;
2026-01-23 18:32:06 -05:00
const { isMcpReady } = useMcpStatus ( config ) ;
2025-10-16 17:04:13 -07:00
const {
messageQueue ,
addMessage ,
clearQueue ,
getQueuedMessagesText ,
popAllMessages ,
} = useMessageQueue ( {
isConfigInitialized ,
streamingState ,
submitQuery ,
2026-01-23 18:32:06 -05:00
isMcpReady ,
2025-10-16 17:04:13 -07:00
} ) ;
2025-09-06 01:39:02 -04:00
2025-11-19 11:11:36 +08:00
cancelHandlerRef . current = useCallback (
( shouldRestorePrompt : boolean = true ) = > {
const pendingHistoryItems = [
. . . pendingSlashCommandHistoryItems ,
. . . pendingGeminiHistoryItems ,
] ;
2026-01-24 01:07:22 +05:30
if ( isToolAwaitingConfirmation ( pendingHistoryItems ) ) {
return ; // Don't clear - user may be composing a follow-up message
}
2025-11-19 11:11:36 +08:00
if ( isToolExecuting ( pendingHistoryItems ) ) {
2026-01-24 01:07:22 +05:30
buffer . setText ( '' ) ; // Clear for Ctrl+C cancellation
2025-11-19 11:11:36 +08:00
return ;
}
2025-09-06 01:39:02 -04:00
2025-12-01 06:21:53 +05:30
const lastUserMessage = inputHistory . at ( - 1 ) ;
2025-11-19 11:11:36 +08:00
let textToSet = shouldRestorePrompt ? lastUserMessage || '' : '' ;
2025-09-06 01:39:02 -04:00
2025-11-19 11:11:36 +08:00
const queuedText = getQueuedMessagesText ( ) ;
if ( queuedText ) {
textToSet = textToSet ? ` ${ textToSet } \ n \ n ${ queuedText } ` : queuedText ;
clearQueue ( ) ;
}
2025-09-06 01:39:02 -04:00
2025-11-19 11:11:36 +08:00
if ( textToSet || ! shouldRestorePrompt ) {
buffer . setText ( textToSet ) ;
}
} ,
[
buffer ,
2025-12-01 06:21:53 +05:30
inputHistory ,
2025-11-19 11:11:36 +08:00
getQueuedMessagesText ,
clearQueue ,
pendingSlashCommandHistoryItems ,
pendingGeminiHistoryItems ,
] ,
) ;
2025-09-06 01:39:02 -04:00
const handleFinalSubmit = useCallback (
( submittedValue : string ) = > {
2026-01-23 18:32:06 -05:00
const isSlash = isSlashCommand ( submittedValue . trim ( ) ) ;
const isIdle = streamingState === StreamingState . Idle ;
if ( isSlash || ( isIdle && isMcpReady ) ) {
void submitQuery ( submittedValue ) ;
} else {
// Check messageQueue.length === 0 to only notify on the first queued item
if ( isIdle && ! isMcpReady && messageQueue . length === 0 ) {
coreEvents . emitFeedback (
'info' ,
'Waiting for MCP servers to initialize... Slash commands are still available and prompts will be queued.' ,
) ;
}
addMessage ( submittedValue ) ;
}
2025-12-01 06:21:53 +05:30
addInput ( submittedValue ) ; // Track input for up-arrow history
2025-09-06 01:39:02 -04:00
} ,
2026-01-23 18:32:06 -05:00
[
addMessage ,
addInput ,
submitQuery ,
isMcpReady ,
streamingState ,
messageQueue . length ,
] ,
2025-09-06 01:39:02 -04:00
) ;
const handleClearScreen = useCallback ( ( ) = > {
historyManager . clearItems ( ) ;
clearConsoleMessagesState ( ) ;
refreshStatic ( ) ;
2025-12-29 15:46:10 -05:00
} , [ historyManager , clearConsoleMessagesState , refreshStatic ] ) ;
2025-09-06 01:39:02 -04:00
const { handleInput : vimHandleInput } = useVim ( buffer , handleFinalSubmit ) ;
2025-09-07 16:17:20 -04:00
/ * *
* Determines if the input prompt should be active and accept user input .
* Input is disabled during :
* - Initialization errors
* - Slash command processing
* - Tool confirmations ( WaitingForConfirmation state )
* - Any future streaming states not explicitly allowed
* /
const isInputActive =
2026-01-23 20:32:35 -05:00
isConfigInitialized &&
2025-09-07 16:17:20 -04:00
! initError &&
! isProcessing &&
2026-01-26 19:59:20 +03:00
! isResuming &&
2025-10-14 18:15:57 -07:00
! ! slashCommands &&
2025-09-07 16:17:20 -04:00
( streamingState === StreamingState . Idle ||
streamingState === StreamingState . Responding ) &&
2025-09-08 16:19:52 -04:00
! proQuotaRequest ;
2025-09-06 01:39:02 -04:00
2025-09-12 22:42:17 -07:00
const [ controlsHeight , setControlsHeight ] = useState ( 0 ) ;
useLayoutEffect ( ( ) = > {
2025-09-06 01:39:02 -04:00
if ( mainControlsRef . current ) {
const fullFooterMeasurement = measureElement ( mainControlsRef . current ) ;
2025-12-01 17:33:03 -08:00
if (
fullFooterMeasurement . height > 0 &&
fullFooterMeasurement . height !== controlsHeight
) {
2025-09-12 22:42:17 -07:00
setControlsHeight ( fullFooterMeasurement . height ) ;
}
2025-09-06 01:39:02 -04:00
}
2025-12-01 17:33:03 -08:00
} , [ buffer , terminalWidth , terminalHeight , controlsHeight ] ) ;
2025-09-12 22:42:17 -07:00
// Compute available terminal height based on controls measurement
2025-09-18 02:51:46 +09:00
const availableTerminalHeight = Math . max (
0 ,
2026-01-30 09:53:09 -08:00
terminalHeight -
controlsHeight -
staticExtraHeight -
2 -
backgroundShellHeight ,
2025-09-18 02:51:46 +09:00
) ;
2025-09-06 01:39:02 -04:00
2025-09-11 13:27:27 -07:00
config . setShellExecutionConfig ( {
terminalWidth : Math.floor ( terminalWidth * SHELL_WIDTH_FRACTION ) ,
2025-09-12 22:42:17 -07:00
terminalHeight : Math.max (
Math . floor ( availableTerminalHeight - SHELL_HEIGHT_PADDING ) ,
1 ,
) ,
2026-01-15 09:26:10 -08:00
pager : settings.merged.tools.shell.pager ,
showColor : settings.merged.tools.shell.showColor ,
2025-12-22 19:18:27 -08:00
sanitizationConfig : config.sanitizationConfig ,
2025-09-11 13:27:27 -07:00
} ) ;
2025-09-06 01:39:02 -04:00
const isFocused = useFocus ( ) ;
// Context file names computation
const contextFileNames = useMemo ( ( ) = > {
2026-01-15 09:26:10 -08:00
const fromSettings = settings . merged . context . fileName ;
2025-09-06 01:39:02 -04:00
return fromSettings
? Array . isArray ( fromSettings )
? fromSettings
: [ fromSettings ]
: getAllGeminiMdFilenames ( ) ;
2026-01-15 09:26:10 -08:00
} , [ settings . merged . context . fileName ] ) ;
2025-09-06 01:39:02 -04:00
// Initial prompt handling
const initialPrompt = useMemo ( ( ) = > config . getQuestion ( ) , [ config ] ) ;
const initialPromptSubmitted = useRef ( false ) ;
const geminiClient = config . getGeminiClient ( ) ;
2025-09-11 13:27:27 -07:00
useEffect ( ( ) = > {
if ( activePtyId ) {
2025-11-05 11:53:03 -05:00
try {
ShellExecutionService . resizePty (
activePtyId ,
Math . floor ( terminalWidth * SHELL_WIDTH_FRACTION ) ,
Math . max (
Math . floor ( availableTerminalHeight - SHELL_HEIGHT_PADDING ) ,
1 ,
) ,
) ;
} catch ( e ) {
// This can happen in a race condition where the pty exits
// right before we try to resize it.
if (
! (
e instanceof Error &&
e . message . includes ( 'Cannot resize a pty that has already exited' )
)
) {
throw e ;
}
}
2025-09-11 13:27:27 -07:00
}
2025-09-12 22:42:17 -07:00
} , [ terminalWidth , availableTerminalHeight , activePtyId ] ) ;
2025-09-11 13:27:27 -07:00
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
if (
initialPrompt &&
2025-09-08 16:37:36 -07:00
isConfigInitialized &&
2025-09-06 01:39:02 -04:00
! initialPromptSubmitted . current &&
! isAuthenticating &&
! isAuthDialogOpen &&
! isThemeDialogOpen &&
! isEditorDialogOpen &&
! showPrivacyNotice &&
geminiClient ? . isInitialized ? . ( )
) {
handleFinalSubmit ( initialPrompt ) ;
initialPromptSubmitted . current = true ;
}
} , [
initialPrompt ,
2025-09-08 16:37:36 -07:00
isConfigInitialized ,
2025-09-06 01:39:02 -04:00
handleFinalSubmit ,
isAuthenticating ,
isAuthDialogOpen ,
isThemeDialogOpen ,
isEditorDialogOpen ,
showPrivacyNotice ,
geminiClient ,
] ) ;
const [ idePromptAnswered , setIdePromptAnswered ] = useState ( false ) ;
2025-09-18 15:23:24 -04:00
const [ currentIDE , setCurrentIDE ] = useState < IdeInfo | null > ( null ) ;
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
const getIde = async ( ) = > {
const ideClient = await IdeClient . getInstance ( ) ;
const currentIde = ideClient . getCurrentIde ( ) ;
setCurrentIDE ( currentIde || null ) ;
} ;
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-09-06 01:39:02 -04:00
getIde ( ) ;
} , [ ] ) ;
const shouldShowIdePrompt = Boolean (
currentIDE &&
! config . getIdeMode ( ) &&
2026-01-15 09:26:10 -08:00
! settings . merged . ide . hasSeenNudge &&
2025-09-06 01:39:02 -04:00
! idePromptAnswered ,
) ;
const [ showErrorDetails , setShowErrorDetails ] = useState < boolean > ( false ) ;
2025-10-17 21:10:57 -07:00
const [ showFullTodos , setShowFullTodos ] = useState < boolean > ( false ) ;
2025-10-16 11:23:36 -07:00
const [ renderMarkdown , setRenderMarkdown ] = useState < boolean > ( true ) ;
2025-09-06 01:39:02 -04:00
2025-10-22 17:39:27 -04:00
const [ ctrlCPressCount , setCtrlCPressCount ] = useState ( 0 ) ;
2025-09-06 01:39:02 -04:00
const ctrlCTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-10-22 17:39:27 -04:00
const [ ctrlDPressCount , setCtrlDPressCount ] = useState ( 0 ) ;
2025-09-06 01:39:02 -04:00
const ctrlDTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
const [ constrainHeight , setConstrainHeight ] = useState < boolean > ( true ) ;
const [ ideContextState , setIdeContextState ] = useState <
IdeContext | undefined
> ( ) ;
const [ showEscapePrompt , setShowEscapePrompt ] = useState ( false ) ;
const [ showIdeRestartPrompt , setShowIdeRestartPrompt ] = useState ( false ) ;
2025-11-14 16:02:28 -08:00
const [ warningMessage , setWarningMessage ] = useState < string | null > ( null ) ;
2025-09-06 01:39:02 -04:00
const { isFolderTrustDialogOpen , handleFolderTrustSelect , isRestarting } =
2025-10-08 22:17:58 -07:00
useFolderTrust ( settings , setIsTrustedFolder , historyManager . addItem ) ;
2025-09-29 13:54:12 -07:00
const {
needsRestart : ideNeedsRestart ,
restartReason : ideTrustRestartReason ,
} = useIdeTrustListener ( ) ;
2025-09-06 01:39:02 -04:00
const isInitialMount = useRef ( true ) ;
2025-11-14 19:06:30 -08:00
useIncludeDirsTrust ( config , isTrustedFolder , historyManager , setCustomDialog ) ;
2026-01-12 15:30:12 -08:00
const warningTimeoutRef = useRef < NodeJS.Timeout | null > ( null ) ;
const tabFocusTimeoutRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-11-14 16:02:28 -08:00
2026-01-12 15:30:12 -08:00
const handleWarning = useCallback ( ( message : string ) = > {
setWarningMessage ( message ) ;
if ( warningTimeoutRef . current ) {
clearTimeout ( warningTimeoutRef . current ) ;
}
warningTimeoutRef . current = setTimeout ( ( ) = > {
setWarningMessage ( null ) ;
} , WARNING_PROMPT_DURATION_MS ) ;
} , [ ] ) ;
2025-11-14 16:02:28 -08:00
2026-02-06 10:36:14 -08:00
// Handle timeout cleanup on unmount
useEffect (
( ) = > ( ) = > {
if ( warningTimeoutRef . current ) {
clearTimeout ( warningTimeoutRef . current ) ;
}
if ( tabFocusTimeoutRef . current ) {
clearTimeout ( tabFocusTimeoutRef . current ) ;
}
} ,
[ ] ,
) ;
2026-01-12 15:30:12 -08:00
useEffect ( ( ) = > {
2025-11-14 16:02:28 -08:00
const handlePasteTimeout = ( ) = > {
handleWarning ( 'Paste Timed out. Possibly due to slow connection.' ) ;
} ;
appEvents . on ( AppEvent . PasteTimeout , handlePasteTimeout ) ;
2025-11-14 12:02:15 -08:00
return ( ) = > {
2025-11-14 16:02:28 -08:00
appEvents . off ( AppEvent . PasteTimeout , handlePasteTimeout ) ;
2025-11-14 12:02:15 -08:00
} ;
2026-01-12 15:30:12 -08:00
} , [ handleWarning ] ) ;
2025-11-14 12:02:15 -08:00
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
if ( ideNeedsRestart ) {
// IDE trust changed, force a restart.
setShowIdeRestartPrompt ( true ) ;
}
} , [ ideNeedsRestart ] ) ;
2025-10-15 22:32:50 +05:30
useEffect ( ( ) = > {
if ( queueErrorMessage ) {
const timer = setTimeout ( ( ) = > {
setQueueErrorMessage ( null ) ;
} , QUEUE_ERROR_DISPLAY_DURATION_MS ) ;
return ( ) = > clearTimeout ( timer ) ;
}
return undefined ;
} , [ queueErrorMessage , setQueueErrorMessage ] ) ;
2025-09-06 01:39:02 -04:00
useEffect ( ( ) = > {
if ( isInitialMount . current ) {
isInitialMount . current = false ;
return ;
}
const handler = setTimeout ( ( ) = > {
refreshStatic ( ) ;
} , 300 ) ;
return ( ) = > {
clearTimeout ( handler ) ;
} ;
} , [ terminalWidth , refreshStatic ] ) ;
useEffect ( ( ) = > {
2025-09-11 11:22:20 -07:00
const unsubscribe = ideContextStore . subscribe ( setIdeContextState ) ;
setIdeContextState ( ideContextStore . get ( ) ) ;
2025-09-06 01:39:02 -04:00
return unsubscribe ;
} , [ ] ) ;
useEffect ( ( ) = > {
const openDebugConsole = ( ) = > {
setShowErrorDetails ( true ) ;
setConstrainHeight ( false ) ;
} ;
appEvents . on ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
return ( ) = > {
appEvents . off ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
} ;
2025-11-20 10:44:02 -08:00
} , [ config ] ) ;
2025-09-06 01:39:02 -04:00
2025-10-22 17:39:27 -04:00
useEffect ( ( ) = > {
if ( ctrlCTimerRef . current ) {
clearTimeout ( ctrlCTimerRef . current ) ;
ctrlCTimerRef . current = null ;
}
if ( ctrlCPressCount > 2 ) {
recordExitFail ( config ) ;
}
if ( ctrlCPressCount > 1 ) {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-11-19 09:32:13 -07:00
handleSlashCommand ( '/quit' , undefined , undefined , false ) ;
2026-01-30 16:11:14 -08:00
} else if ( ctrlCPressCount > 0 ) {
2025-10-22 17:39:27 -04:00
ctrlCTimerRef . current = setTimeout ( ( ) = > {
setCtrlCPressCount ( 0 ) ;
ctrlCTimerRef . current = null ;
2025-11-14 12:02:15 -08:00
} , WARNING_PROMPT_DURATION_MS ) ;
2025-10-22 17:39:27 -04:00
}
} , [ ctrlCPressCount , config , setCtrlCPressCount , handleSlashCommand ] ) ;
useEffect ( ( ) = > {
if ( ctrlDTimerRef . current ) {
clearTimeout ( ctrlDTimerRef . current ) ;
ctrlCTimerRef . current = null ;
}
if ( ctrlDPressCount > 2 ) {
recordExitFail ( config ) ;
}
if ( ctrlDPressCount > 1 ) {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-11-19 09:32:13 -07:00
handleSlashCommand ( '/quit' , undefined , undefined , false ) ;
2026-01-30 16:11:14 -08:00
} else if ( ctrlDPressCount > 0 ) {
2025-10-22 17:39:27 -04:00
ctrlDTimerRef . current = setTimeout ( ( ) = > {
setCtrlDPressCount ( 0 ) ;
ctrlDTimerRef . current = null ;
2025-11-14 12:02:15 -08:00
} , WARNING_PROMPT_DURATION_MS ) ;
2025-10-22 17:39:27 -04:00
}
} , [ ctrlDPressCount , config , setCtrlDPressCount , handleSlashCommand ] ) ;
2025-09-06 01:39:02 -04:00
const handleEscapePromptChange = useCallback ( ( showPrompt : boolean ) = > {
setShowEscapePrompt ( showPrompt ) ;
} , [ ] ) ;
const handleIdePromptComplete = useCallback (
( result : IdeIntegrationNudgeResult ) = > {
if ( result . userSelection === 'yes' ) {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-09-06 01:39:02 -04:00
handleSlashCommand ( '/ide install' ) ;
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
} else if ( result . userSelection === 'dismiss' ) {
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
}
setIdePromptAnswered ( true ) ;
} ,
[ handleSlashCommand , settings ] ,
) ;
2026-01-21 14:31:24 -08:00
const { elapsedTime , currentLoadingPhrase } = useLoadingIndicator ( {
2025-09-22 21:00:11 +05:30
streamingState ,
2026-01-21 14:31:24 -08:00
shouldShowFocusHint ,
2026-01-13 23:03:19 -05:00
retryStatus ,
2026-01-21 14:31:24 -08:00
} ) ;
2025-09-06 01:39:02 -04:00
const handleGlobalKeypress = useCallback (
2026-01-30 16:11:14 -08:00
( key : Key ) : boolean = > {
2025-11-03 13:41:58 -08:00
if ( copyModeEnabled ) {
setCopyModeEnabled ( false ) ;
enableMouseEvents ( ) ;
// We don't want to process any other keys if we're in copy mode.
2026-01-27 14:26:00 -08:00
return true ;
2025-11-03 13:41:58 -08:00
}
2025-09-06 01:39:02 -04:00
// Debug log keystrokes if enabled
2026-01-15 09:26:10 -08:00
if ( settings . merged . general . debugKeystrokeLogging ) {
2025-10-21 16:35:22 -04:00
debugLogger . log ( '[DEBUG] Keystroke:' , JSON . stringify ( key ) ) ;
2025-09-06 01:39:02 -04:00
}
2025-11-11 07:50:11 -08:00
if ( isAlternateBuffer && keyMatchers [ Command . TOGGLE_COPY_MODE ] ( key ) ) {
2025-11-03 13:41:58 -08:00
setCopyModeEnabled ( true ) ;
disableMouseEvents ( ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-11-03 13:41:58 -08:00
}
2025-09-19 14:52:29 +08:00
if ( keyMatchers [ Command . QUIT ] ( key ) ) {
2025-10-22 17:39:27 -04:00
// If the user presses Ctrl+C, we want to cancel any ongoing requests.
// This should happen regardless of the count.
cancelOngoingRequest ? . ( ) ;
2025-09-19 14:52:29 +08:00
2025-10-22 17:39:27 -04:00
setCtrlCPressCount ( ( prev ) = > prev + 1 ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-09-19 14:52:29 +08:00
} else if ( keyMatchers [ Command . EXIT ] ( key ) ) {
2025-10-22 17:39:27 -04:00
setCtrlDPressCount ( ( prev ) = > prev + 1 ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-09-06 01:39:02 -04:00
}
let enteringConstrainHeightMode = false ;
if ( ! constrainHeight ) {
enteringConstrainHeightMode = true ;
setConstrainHeight ( true ) ;
}
if ( keyMatchers [ Command . SHOW_ERROR_DETAILS ] ( key ) ) {
setShowErrorDetails ( ( prev ) = > ! prev ) ;
2026-01-27 14:26:00 -08:00
return true ;
2026-01-28 14:57:27 -08:00
} else if ( keyMatchers [ Command . SUSPEND_APP ] ( key ) ) {
2026-02-05 10:54:46 -08:00
const undoMessage = isITerm2 ( )
? 'Undo has been moved to Option + Z'
: 'Undo has been moved to Alt/Option + Z or Cmd + Z' ;
handleWarning ( undoMessage ) ;
2026-01-28 14:57:27 -08:00
return true ;
2025-10-17 21:10:57 -07:00
} else if ( keyMatchers [ Command . SHOW_FULL_TODOS ] ( key ) ) {
setShowFullTodos ( ( prev ) = > ! prev ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-10-16 11:23:36 -07:00
} else if ( keyMatchers [ Command . TOGGLE_MARKDOWN ] ( key ) ) {
setRenderMarkdown ( ( prev ) = > {
const newValue = ! prev ;
// Force re-render of static content
refreshStatic ( ) ;
return newValue ;
} ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-09-06 01:39:02 -04:00
} else if (
2026-01-13 12:07:55 -08:00
keyMatchers [ Command . SHOW_IDE_CONTEXT_DETAIL ] ( key ) &&
2025-09-06 01:39:02 -04:00
config . getIdeMode ( ) &&
ideContextState
) {
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-09-06 01:39:02 -04:00
handleSlashCommand ( '/ide status' ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-09-06 01:39:02 -04:00
} else if (
keyMatchers [ Command . SHOW_MORE_LINES ] ( key ) &&
! enteringConstrainHeightMode
) {
setConstrainHeight ( false ) ;
2026-01-27 14:26:00 -08:00
return true ;
2026-01-12 15:30:12 -08:00
} else if (
2026-02-06 10:36:14 -08:00
( keyMatchers [ Command . FOCUS_SHELL_INPUT ] ( key ) ||
keyMatchers [ Command . UNFOCUS_BACKGROUND_SHELL_LIST ] ( key ) ) &&
2026-01-30 16:11:14 -08:00
( activePtyId || ( isBackgroundShellVisible && backgroundShells . size > 0 ) )
2026-01-12 15:30:12 -08:00
) {
2026-01-30 09:53:09 -08:00
if ( embeddedShellFocused ) {
2026-02-06 10:36:14 -08:00
const capturedTime = lastOutputTimeRef . current ;
if ( tabFocusTimeoutRef . current )
clearTimeout ( tabFocusTimeoutRef . current ) ;
tabFocusTimeoutRef . current = setTimeout ( ( ) = > {
if ( lastOutputTimeRef . current === capturedTime ) {
setEmbeddedShellFocused ( false ) ;
} else {
handleWarning ( 'Use Shift+Tab to unfocus' ) ;
}
} , 150 ) ;
return false ;
2026-01-30 09:53:09 -08:00
}
2026-02-06 10:36:14 -08:00
const isIdle = Date . now ( ) - lastOutputTimeRef . current >= 100 ;
if ( isIdle && ! activePtyId && ! isBackgroundShellVisible ) {
if ( tabFocusTimeoutRef . current )
2026-01-12 15:30:12 -08:00
clearTimeout ( tabFocusTimeoutRef . current ) ;
2026-01-30 09:53:09 -08:00
toggleBackgroundShell ( ) ;
2026-02-06 10:36:14 -08:00
setEmbeddedShellFocused ( true ) ;
if ( backgroundShells . size > 1 ) setIsBackgroundShellListOpen ( true ) ;
2026-01-27 14:26:00 -08:00
return true ;
2025-09-11 13:27:27 -07:00
}
2026-01-30 09:53:09 -08:00
setEmbeddedShellFocused ( true ) ;
return true ;
2026-02-06 10:36:14 -08:00
} else if (
keyMatchers [ Command . UNFOCUS_SHELL_INPUT ] ( key ) ||
keyMatchers [ Command . UNFOCUS_BACKGROUND_SHELL ] ( key )
) {
if ( embeddedShellFocused ) {
setEmbeddedShellFocused ( false ) ;
return true ;
}
return false ;
2026-01-30 09:53:09 -08:00
} else if ( keyMatchers [ Command . TOGGLE_BACKGROUND_SHELL ] ( key ) ) {
if ( activePtyId ) {
backgroundCurrentShell ( ) ;
// After backgrounding, we explicitly do NOT show or focus the background UI.
} else {
2026-02-06 10:36:14 -08:00
toggleBackgroundShell ( ) ;
// Toggle focus based on intent: if we were hiding, unfocus; if showing, focus.
if ( ! isBackgroundShellVisible && backgroundShells . size > 0 ) {
2026-01-30 09:53:09 -08:00
setEmbeddedShellFocused ( true ) ;
2026-02-06 10:36:14 -08:00
if ( backgroundShells . size > 1 ) {
setIsBackgroundShellListOpen ( true ) ;
2026-01-30 09:53:09 -08:00
}
2026-02-06 10:36:14 -08:00
} else {
setEmbeddedShellFocused ( false ) ;
2026-01-30 09:53:09 -08:00
}
}
return true ;
} else if ( keyMatchers [ Command . TOGGLE_BACKGROUND_SHELL_LIST ] ( key ) ) {
if ( backgroundShells . size > 0 && isBackgroundShellVisible ) {
if ( ! embeddedShellFocused ) {
setEmbeddedShellFocused ( true ) ;
}
setIsBackgroundShellListOpen ( true ) ;
}
2026-01-27 14:26:00 -08:00
return true ;
2025-09-06 01:39:02 -04:00
}
2026-01-27 14:26:00 -08:00
return false ;
2025-09-06 01:39:02 -04:00
} ,
[
constrainHeight ,
setConstrainHeight ,
setShowErrorDetails ,
config ,
ideContextState ,
2025-10-22 17:39:27 -04:00
setCtrlCPressCount ,
setCtrlDPressCount ,
2025-09-06 01:39:02 -04:00
handleSlashCommand ,
cancelOngoingRequest ,
2025-09-11 13:27:27 -07:00
activePtyId ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2026-01-15 09:26:10 -08:00
settings . merged . general . debugKeystrokeLogging ,
2025-10-16 11:23:36 -07:00
refreshStatic ,
2025-11-03 13:41:58 -08:00
setCopyModeEnabled ,
copyModeEnabled ,
2025-11-11 07:50:11 -08:00
isAlternateBuffer ,
2026-01-30 09:53:09 -08:00
backgroundCurrentShell ,
toggleBackgroundShell ,
backgroundShells ,
isBackgroundShellVisible ,
setIsBackgroundShellListOpen ,
lastOutputTimeRef ,
tabFocusTimeoutRef ,
2026-01-12 15:30:12 -08:00
handleWarning ,
2025-09-06 01:39:02 -04:00
] ,
) ;
2026-02-06 10:36:14 -08:00
useKeypress ( handleGlobalKeypress , { isActive : true , priority : true } ) ;
2025-09-06 01:39:02 -04:00
2025-09-28 03:48:24 +08:00
useEffect ( ( ) = > {
2026-01-12 17:18:14 -08:00
// Respect hideWindowTitle settings
2026-01-15 09:26:10 -08:00
if ( settings . merged . ui . hideWindowTitle ) return ;
2026-01-12 17:18:14 -08:00
const paddedTitle = computeTerminalTitle ( {
streamingState ,
thoughtSubject : thought?.subject ,
2026-01-30 09:57:34 -05:00
isConfirming :
! ! commandConfirmationRequest || shouldShowActionRequiredTitle ,
2026-01-21 14:31:24 -08:00
isSilentWorking : shouldShowSilentWorkingTitle ,
2026-01-12 17:18:14 -08:00
folderName : basename ( config . getTargetDir ( ) ) ,
2026-01-15 09:26:10 -08:00
showThoughts : ! ! settings . merged . ui . showStatusInTitle ,
useDynamicTitle : settings.merged.ui.dynamicWindowTitle ,
2026-01-12 17:18:14 -08:00
} ) ;
2025-09-28 03:48:24 +08:00
// Only update the title if it's different from the last value we set
if ( lastTitleRef . current !== paddedTitle ) {
lastTitleRef . current = paddedTitle ;
2026-01-12 20:14:01 -08:00
stdout . write ( ` \ x1b]0; ${ paddedTitle } \ x07 ` ) ;
2025-09-28 03:48:24 +08:00
}
// Note: We don't need to reset the window title on exit because Gemini CLI is already doing that elsewhere
} , [
streamingState ,
thought ,
2026-01-30 09:57:34 -05:00
commandConfirmationRequest ,
2026-01-21 14:31:24 -08:00
shouldShowActionRequiredTitle ,
shouldShowSilentWorkingTitle ,
2026-01-15 09:26:10 -08:00
settings . merged . ui . showStatusInTitle ,
settings . merged . ui . dynamicWindowTitle ,
settings . merged . ui . hideWindowTitle ,
2026-01-12 17:18:14 -08:00
config ,
2025-09-28 03:48:24 +08:00
stdout ,
] ) ;
2025-10-23 14:14:14 -04:00
useEffect ( ( ) = > {
const handleUserFeedback = ( payload : UserFeedbackPayload ) = > {
let type : MessageType ;
switch ( payload . severity ) {
case 'error' :
type = MessageType . ERROR ;
break ;
case 'warning' :
type = MessageType . WARNING ;
break ;
case 'info' :
type = MessageType . INFO ;
break ;
default :
throw new Error (
` Unexpected severity for user feedback: ${ payload . severity } ` ,
) ;
}
historyManager . addItem (
{
type ,
text : payload.message ,
} ,
Date . now ( ) ,
) ;
// If there is an attached error object, log it to the debug drawer.
if ( payload . error ) {
debugLogger . warn (
` [Feedback Details for " ${ payload . message } "] ` ,
payload . error ,
) ;
}
} ;
coreEvents . on ( CoreEvent . UserFeedback , handleUserFeedback ) ;
// Flush any messages that happened during startup before this component
// mounted.
2025-11-20 10:44:02 -08:00
coreEvents . drainBacklogs ( ) ;
2025-10-23 14:14:14 -04:00
return ( ) = > {
coreEvents . off ( CoreEvent . UserFeedback , handleUserFeedback ) ;
} ;
} , [ historyManager ] ) ;
2025-09-06 01:39:02 -04:00
const filteredConsoleMessages = useMemo ( ( ) = > {
if ( config . getDebugMode ( ) ) {
return consoleMessages ;
}
return consoleMessages . filter ( ( msg ) = > msg . type !== 'debug' ) ;
} , [ consoleMessages , config ] ) ;
// Computed values
const errorCount = useMemo (
( ) = >
filteredConsoleMessages
. filter ( ( msg ) = > msg . type === 'error' )
. reduce ( ( total , msg ) = > total + msg . count , 0 ) ,
[ filteredConsoleMessages ] ,
) ;
const nightly = props . version . includes ( 'nightly' ) ;
2025-09-10 22:20:13 -07:00
const dialogsVisible =
shouldShowIdePrompt ||
isFolderTrustDialogOpen ||
2026-01-16 15:24:53 -05:00
adminSettingsChanged ||
2026-01-30 09:57:34 -05:00
! ! commandConfirmationRequest ||
! ! authConsentRequest ||
2025-11-14 19:06:30 -08:00
! ! customDialog ||
2025-09-29 14:19:19 -07:00
confirmUpdateExtensionRequests . length > 0 ||
2025-09-10 22:20:13 -07:00
! ! loopDetectionConfirmationRequest ||
isThemeDialogOpen ||
isSettingsDialogOpen ||
2025-09-23 12:50:09 -04:00
isModelDialogOpen ||
2026-01-22 10:30:44 -08:00
isAgentConfigDialogOpen ||
2025-09-22 11:45:02 -07:00
isPermissionsDialogOpen ||
2025-09-10 22:20:13 -07:00
isAuthenticating ||
isAuthDialogOpen ||
isEditorDialogOpen ||
showPrivacyNotice ||
2025-09-29 13:54:12 -07:00
showIdeRestartPrompt ||
2025-10-29 18:58:08 -07:00
! ! proQuotaRequest ||
2026-01-20 16:23:01 -08:00
! ! validationRequest ||
2025-11-25 11:54:09 -07:00
isSessionBrowserOpen ||
2026-01-26 19:49:32 +00:00
authState === AuthState . AwaitingApiKeyInput ||
! ! newAgents ;
2025-09-06 01:39:02 -04:00
const pendingHistoryItems = useMemo (
( ) = > [ . . . pendingSlashCommandHistoryItems , . . . pendingGeminiHistoryItems ] ,
[ pendingSlashCommandHistoryItems , pendingGeminiHistoryItems ] ,
) ;
2026-01-21 16:16:30 -05:00
const allToolCalls = useMemo (
( ) = >
pendingHistoryItems
. filter (
( item ) : item is HistoryItemToolGroup = > item . type === 'tool_group' ,
)
. flatMap ( ( item ) = > item . tools ) ,
[ pendingHistoryItems ] ,
) ;
2025-11-07 09:07:25 -08:00
const [ geminiMdFileCount , setGeminiMdFileCount ] = useState < number > (
config . getGeminiMdFileCount ( ) ,
) ;
useEffect ( ( ) = > {
const handleMemoryChanged = ( result : MemoryChangedPayload ) = > {
setGeminiMdFileCount ( result . fileCount ) ;
} ;
coreEvents . on ( CoreEvent . MemoryChanged , handleMemoryChanged ) ;
return ( ) = > {
coreEvents . off ( CoreEvent . MemoryChanged , handleMemoryChanged ) ;
} ;
} , [ ] ) ;
2025-11-18 12:01:16 -05:00
useEffect ( ( ) = > {
let isMounted = true ;
const fetchBannerTexts = async ( ) = > {
const [ defaultBanner , warningBanner ] = await Promise . all ( [
2026-02-06 13:02:57 -05:00
// TODO: temporarily disabling the banner, it will be re-added.
'' ,
2025-11-18 12:01:16 -05:00
config . getBannerTextCapacityIssues ( ) ,
] ) ;
if ( isMounted ) {
setDefaultBannerText ( defaultBanner ) ;
setWarningBannerText ( warningBanner ) ;
setBannerVisible ( true ) ;
}
} ;
2025-12-05 16:12:49 -08:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2025-11-18 12:01:16 -05:00
fetchBannerTexts ( ) ;
return ( ) = > {
isMounted = false ;
} ;
} , [ config , refreshStatic ] ) ;
2025-09-06 01:39:02 -04:00
const uiState : UIState = useMemo (
( ) = > ( {
history : historyManager.history ,
2025-09-22 11:45:02 -07:00
historyManager ,
2025-09-06 01:39:02 -04:00
isThemeDialogOpen ,
themeError ,
isAuthenticating ,
2025-09-08 16:37:36 -07:00
isConfigInitialized ,
2025-09-06 01:39:02 -04:00
authError ,
isAuthDialogOpen ,
2025-10-29 18:58:08 -07:00
isAwaitingApiKeyInput : authState === AuthState . AwaitingApiKeyInput ,
apiKeyDefaultValue ,
2025-09-06 01:39:02 -04:00
editorError ,
isEditorDialogOpen ,
showPrivacyNotice ,
corgiMode ,
debugMessage ,
quittingMessages ,
isSettingsDialogOpen ,
2025-11-25 11:54:09 -07:00
isSessionBrowserOpen ,
2025-09-23 12:50:09 -04:00
isModelDialogOpen ,
2026-01-22 10:30:44 -08:00
isAgentConfigDialogOpen ,
selectedAgentName ,
selectedAgentDisplayName ,
selectedAgentDefinition ,
2025-09-22 11:45:02 -07:00
isPermissionsDialogOpen ,
2025-11-14 14:41:53 -08:00
permissionsDialogProps ,
2025-09-06 01:39:02 -04:00
slashCommands ,
pendingSlashCommandHistoryItems ,
commandContext ,
2026-01-30 09:57:34 -05:00
commandConfirmationRequest ,
authConsentRequest ,
2025-09-29 14:19:19 -07:00
confirmUpdateExtensionRequests ,
2025-09-10 22:20:13 -07:00
loopDetectionConfirmationRequest ,
2025-09-06 01:39:02 -04:00
geminiMdFileCount ,
streamingState ,
initError ,
pendingGeminiHistoryItems ,
thought ,
shellModeActive ,
2025-12-01 06:21:53 +05:30
userMessages : inputHistory ,
2025-09-06 01:39:02 -04:00
buffer ,
inputWidth ,
suggestionsWidth ,
isInputActive ,
2026-01-26 19:59:20 +03:00
isResuming ,
2025-09-06 01:39:02 -04:00
shouldShowIdePrompt ,
isFolderTrustDialogOpen : isFolderTrustDialogOpen ? ? false ,
isTrustedFolder ,
constrainHeight ,
showErrorDetails ,
2025-10-17 21:10:57 -07:00
showFullTodos ,
2025-09-06 01:39:02 -04:00
filteredConsoleMessages ,
ideContextState ,
2025-10-16 11:23:36 -07:00
renderMarkdown ,
2025-10-22 17:39:27 -04:00
ctrlCPressedOnce : ctrlCPressCount >= 1 ,
ctrlDPressedOnce : ctrlDPressCount >= 1 ,
2025-09-06 01:39:02 -04:00
showEscapePrompt ,
2026-02-06 11:33:39 -08:00
shortcutsHelpVisible ,
2025-09-06 01:39:02 -04:00
isFocused ,
elapsedTime ,
currentLoadingPhrase ,
historyRemountKey ,
2026-01-06 15:52:12 -05:00
activeHooks ,
2025-09-06 01:39:02 -04:00
messageQueue ,
2025-10-15 22:32:50 +05:30
queueErrorMessage ,
2026-01-21 10:19:47 -05:00
showApprovalModeIndicator ,
2025-09-06 01:39:02 -04:00
currentModel ,
userTier ,
2025-09-08 16:19:52 -04:00
proQuotaRequest ,
2026-01-20 16:23:01 -08:00
validationRequest ,
2025-09-06 01:39:02 -04:00
contextFileNames ,
errorCount ,
availableTerminalHeight ,
mainAreaWidth ,
staticAreaMaxItemHeight ,
staticExtraHeight ,
dialogsVisible ,
pendingHistoryItems ,
nightly ,
branchName ,
sessionStats ,
terminalWidth ,
terminalHeight ,
mainControlsRef ,
2025-10-10 13:18:38 -07:00
rootUiRef ,
2025-09-06 01:39:02 -04:00
currentIDE ,
updateInfo ,
showIdeRestartPrompt ,
2025-09-29 13:54:12 -07:00
ideTrustRestartReason ,
2025-09-06 01:39:02 -04:00
isRestarting ,
2025-09-12 09:20:04 -07:00
extensionsUpdateState ,
2025-09-11 13:27:27 -07:00
activePtyId ,
2026-01-30 09:53:09 -08:00
backgroundShellCount ,
isBackgroundShellVisible ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2025-10-07 10:28:35 -07:00
showDebugProfiler ,
2025-11-14 19:06:30 -08:00
customDialog ,
2025-11-03 13:41:58 -08:00
copyModeEnabled ,
2025-11-14 16:02:28 -08:00
warningMessage ,
2025-12-03 09:08:32 -08:00
bannerData ,
2025-11-18 12:01:16 -05:00
bannerVisible ,
2025-12-18 10:36:48 -08:00
terminalBackgroundColor : config.getTerminalBackground ( ) ,
2026-01-05 15:12:51 -08:00
settingsNonce ,
2026-01-30 09:53:09 -08:00
backgroundShells ,
activeBackgroundShellPid ,
backgroundShellHeight ,
isBackgroundShellListOpen ,
2026-01-16 15:24:53 -05:00
adminSettingsChanged ,
2026-01-26 19:49:32 +00:00
newAgents ,
2025-09-06 01:39:02 -04:00
} ) ,
[
isThemeDialogOpen ,
themeError ,
isAuthenticating ,
2025-09-08 16:37:36 -07:00
isConfigInitialized ,
2025-09-06 01:39:02 -04:00
authError ,
isAuthDialogOpen ,
editorError ,
isEditorDialogOpen ,
showPrivacyNotice ,
corgiMode ,
debugMessage ,
quittingMessages ,
isSettingsDialogOpen ,
2025-11-25 11:54:09 -07:00
isSessionBrowserOpen ,
2025-09-23 12:50:09 -04:00
isModelDialogOpen ,
2026-01-22 10:30:44 -08:00
isAgentConfigDialogOpen ,
selectedAgentName ,
selectedAgentDisplayName ,
selectedAgentDefinition ,
2025-09-22 11:45:02 -07:00
isPermissionsDialogOpen ,
2025-11-14 14:41:53 -08:00
permissionsDialogProps ,
2025-09-06 01:39:02 -04:00
slashCommands ,
pendingSlashCommandHistoryItems ,
commandContext ,
2026-01-30 09:57:34 -05:00
commandConfirmationRequest ,
authConsentRequest ,
2025-09-29 14:19:19 -07:00
confirmUpdateExtensionRequests ,
2025-09-10 22:20:13 -07:00
loopDetectionConfirmationRequest ,
2025-09-06 01:39:02 -04:00
geminiMdFileCount ,
streamingState ,
initError ,
pendingGeminiHistoryItems ,
thought ,
shellModeActive ,
2025-12-01 06:21:53 +05:30
inputHistory ,
2025-09-06 01:39:02 -04:00
buffer ,
inputWidth ,
suggestionsWidth ,
isInputActive ,
2026-01-26 19:59:20 +03:00
isResuming ,
2025-09-06 01:39:02 -04:00
shouldShowIdePrompt ,
isFolderTrustDialogOpen ,
isTrustedFolder ,
constrainHeight ,
showErrorDetails ,
2025-10-17 21:10:57 -07:00
showFullTodos ,
2025-09-06 01:39:02 -04:00
filteredConsoleMessages ,
ideContextState ,
2025-10-16 11:23:36 -07:00
renderMarkdown ,
2025-10-22 17:39:27 -04:00
ctrlCPressCount ,
ctrlDPressCount ,
2025-09-06 01:39:02 -04:00
showEscapePrompt ,
2026-02-06 11:33:39 -08:00
shortcutsHelpVisible ,
2025-09-06 01:39:02 -04:00
isFocused ,
elapsedTime ,
currentLoadingPhrase ,
historyRemountKey ,
2026-01-06 15:52:12 -05:00
activeHooks ,
2025-09-06 01:39:02 -04:00
messageQueue ,
2025-10-15 22:32:50 +05:30
queueErrorMessage ,
2026-01-21 10:19:47 -05:00
showApprovalModeIndicator ,
2025-09-06 01:39:02 -04:00
userTier ,
2025-09-08 16:19:52 -04:00
proQuotaRequest ,
2026-01-20 16:23:01 -08:00
validationRequest ,
2025-09-06 01:39:02 -04:00
contextFileNames ,
errorCount ,
availableTerminalHeight ,
mainAreaWidth ,
staticAreaMaxItemHeight ,
staticExtraHeight ,
dialogsVisible ,
pendingHistoryItems ,
nightly ,
branchName ,
sessionStats ,
terminalWidth ,
terminalHeight ,
mainControlsRef ,
2025-10-10 13:18:38 -07:00
rootUiRef ,
2025-09-06 01:39:02 -04:00
currentIDE ,
updateInfo ,
showIdeRestartPrompt ,
2025-09-29 13:54:12 -07:00
ideTrustRestartReason ,
2025-09-06 01:39:02 -04:00
isRestarting ,
currentModel ,
2025-09-12 09:20:04 -07:00
extensionsUpdateState ,
2025-09-11 13:27:27 -07:00
activePtyId ,
2026-01-30 09:53:09 -08:00
backgroundShellCount ,
isBackgroundShellVisible ,
2025-09-22 11:45:02 -07:00
historyManager ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2025-10-07 10:28:35 -07:00
showDebugProfiler ,
2025-11-14 19:06:30 -08:00
customDialog ,
2025-10-29 18:58:08 -07:00
apiKeyDefaultValue ,
authState ,
2025-11-03 13:41:58 -08:00
copyModeEnabled ,
2025-11-14 16:02:28 -08:00
warningMessage ,
2025-12-03 09:08:32 -08:00
bannerData ,
2025-11-18 12:01:16 -05:00
bannerVisible ,
2025-12-18 10:36:48 -08:00
config ,
2026-01-05 15:12:51 -08:00
settingsNonce ,
2026-01-30 09:53:09 -08:00
backgroundShellHeight ,
isBackgroundShellListOpen ,
activeBackgroundShellPid ,
backgroundShells ,
2026-01-16 15:24:53 -05:00
adminSettingsChanged ,
2026-01-26 19:49:32 +00:00
newAgents ,
2025-09-06 01:39:02 -04:00
] ,
) ;
2025-10-09 11:07:25 -07:00
const exitPrivacyNotice = useCallback (
( ) = > setShowPrivacyNotice ( false ) ,
[ setShowPrivacyNotice ] ,
) ;
2025-09-06 01:39:02 -04:00
const uiActions : UIActions = useMemo (
( ) = > ( {
handleThemeSelect ,
2025-10-20 10:50:09 -07:00
closeThemeDialog ,
2025-09-06 01:39:02 -04:00
handleThemeHighlight ,
handleAuthSelect ,
setAuthState ,
onAuthError ,
handleEditorSelect ,
exitEditorDialog ,
2025-10-09 11:07:25 -07:00
exitPrivacyNotice ,
2025-09-06 01:39:02 -04:00
closeSettingsDialog ,
2025-09-23 12:50:09 -04:00
closeModelDialog ,
2026-01-22 10:30:44 -08:00
openAgentConfigDialog ,
closeAgentConfigDialog ,
2025-11-14 14:41:53 -08:00
openPermissionsDialog ,
2025-09-22 11:45:02 -07:00
closePermissionsDialog ,
2025-09-06 01:39:02 -04:00
setShellModeActive ,
vimHandleInput ,
handleIdePromptComplete ,
handleFolderTrustSelect ,
setConstrainHeight ,
onEscapePromptChange : handleEscapePromptChange ,
refreshStatic ,
handleFinalSubmit ,
handleClearScreen ,
handleProQuotaChoice ,
2026-01-20 16:23:01 -08:00
handleValidationChoice ,
2025-11-25 11:54:09 -07:00
openSessionBrowser ,
closeSessionBrowser ,
handleResumeSession ,
handleDeleteSession ,
2025-10-15 22:32:50 +05:30
setQueueErrorMessage ,
2025-10-16 17:04:13 -07:00
popAllMessages ,
2025-10-29 18:58:08 -07:00
handleApiKeySubmit ,
handleApiKeyCancel ,
2025-11-18 12:01:16 -05:00
setBannerVisible ,
2026-02-06 11:33:39 -08:00
setShortcutsHelpVisible ,
2026-01-30 09:53:09 -08:00
handleWarning ,
2025-11-19 15:49:39 -08:00
setEmbeddedShellFocused ,
2026-01-30 09:53:09 -08:00
dismissBackgroundShell ,
setActiveBackgroundShellPid ,
setIsBackgroundShellListOpen ,
2026-01-12 15:39:08 -05:00
setAuthContext ,
2026-01-16 15:24:53 -05:00
handleRestart : async ( ) = > {
2026-01-27 12:14:11 -05:00
if ( process . send ) {
const remoteSettings = config . getRemoteAdminSettings ( ) ;
if ( remoteSettings ) {
process . send ( {
type : 'admin-settings-update' ,
settings : remoteSettings ,
} ) ;
}
}
2026-01-16 15:24:53 -05:00
await runExitCleanup ( ) ;
process . exit ( RELAUNCH_EXIT_CODE ) ;
} ,
2026-01-26 19:49:32 +00:00
handleNewAgentsSelect : async ( choice : NewAgentsChoice ) = > {
if ( newAgents && choice === NewAgentsChoice . ACKNOWLEDGE ) {
const registry = config . getAgentRegistry ( ) ;
try {
await Promise . all (
newAgents . map ( ( agent ) = > registry . acknowledgeAgent ( agent ) ) ,
) ;
} catch ( error ) {
debugLogger . error ( 'Failed to acknowledge agents:' , error ) ;
historyManager . addItem (
{
type : MessageType . ERROR ,
text : ` Failed to acknowledge agents: ${ getErrorMessage ( error ) } ` ,
} ,
Date . now ( ) ,
) ;
}
}
setNewAgents ( null ) ;
} ,
2025-09-06 01:39:02 -04:00
} ) ,
[
handleThemeSelect ,
2025-10-20 10:50:09 -07:00
closeThemeDialog ,
2025-09-06 01:39:02 -04:00
handleThemeHighlight ,
handleAuthSelect ,
setAuthState ,
onAuthError ,
handleEditorSelect ,
exitEditorDialog ,
2025-10-09 11:07:25 -07:00
exitPrivacyNotice ,
2025-09-06 01:39:02 -04:00
closeSettingsDialog ,
2025-09-23 12:50:09 -04:00
closeModelDialog ,
2026-01-22 10:30:44 -08:00
openAgentConfigDialog ,
closeAgentConfigDialog ,
2025-11-14 14:41:53 -08:00
openPermissionsDialog ,
2025-09-22 11:45:02 -07:00
closePermissionsDialog ,
2025-09-06 01:39:02 -04:00
setShellModeActive ,
vimHandleInput ,
handleIdePromptComplete ,
handleFolderTrustSelect ,
setConstrainHeight ,
handleEscapePromptChange ,
refreshStatic ,
handleFinalSubmit ,
handleClearScreen ,
handleProQuotaChoice ,
2026-01-20 16:23:01 -08:00
handleValidationChoice ,
2025-11-25 11:54:09 -07:00
openSessionBrowser ,
closeSessionBrowser ,
handleResumeSession ,
handleDeleteSession ,
2025-10-15 22:32:50 +05:30
setQueueErrorMessage ,
2025-10-16 17:04:13 -07:00
popAllMessages ,
2025-10-29 18:58:08 -07:00
handleApiKeySubmit ,
handleApiKeyCancel ,
2025-11-18 12:01:16 -05:00
setBannerVisible ,
2026-02-06 11:33:39 -08:00
setShortcutsHelpVisible ,
2026-01-30 09:53:09 -08:00
handleWarning ,
2025-11-19 15:49:39 -08:00
setEmbeddedShellFocused ,
2026-01-30 09:53:09 -08:00
dismissBackgroundShell ,
setActiveBackgroundShellPid ,
setIsBackgroundShellListOpen ,
2026-01-12 15:39:08 -05:00
setAuthContext ,
2026-01-26 19:49:32 +00:00
newAgents ,
config ,
historyManager ,
2025-09-06 01:39:02 -04:00
] ,
) ;
2026-01-12 15:39:08 -05:00
if ( authState === AuthState . AwaitingGoogleLoginRestart ) {
return (
< LoginWithGoogleRestartDialog
onDismiss = { ( ) = > {
setAuthContext ( { } ) ;
setAuthState ( AuthState . Updating ) ;
} }
2026-01-27 12:14:11 -05:00
config = { config }
2026-01-12 15:39:08 -05:00
/ >
) ;
}
2025-09-06 01:39:02 -04:00
return (
< UIStateContext.Provider value = { uiState } >
< UIActionsContext.Provider value = { uiActions } >
< ConfigContext.Provider value = { config } >
< AppContext.Provider
value = { {
version : props.version ,
startupWarnings : props.startupWarnings || [ ] ,
} }
>
2026-01-21 16:16:30 -05:00
< ToolActionsProvider config = { config } toolCalls = { allToolCalls } >
2026-01-30 13:32:21 -05:00
< ShellFocusContext.Provider value = { isFocused } >
< App / >
< / ShellFocusContext.Provider >
2026-01-21 16:16:30 -05:00
< / ToolActionsProvider >
2025-09-06 01:39:02 -04:00
< / AppContext.Provider >
< / ConfigContext.Provider >
< / UIActionsContext.Provider >
< / UIStateContext.Provider >
) ;
} ;