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 ,
AuthState ,
} from './types.js' ;
2025-09-07 16:17:20 -04:00
import { MessageType , StreamingState } from './types.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 ,
DEFAULT_GEMINI_FLASH_MODEL ,
IdeClient ,
2025-09-11 11:22:20 -07:00
ideContextStore ,
2025-09-06 01:39:02 -04:00
getErrorMessage ,
getAllGeminiMdFilenames ,
AuthType ,
clearCachedCredentialFile ,
2025-09-11 13:27:27 -07:00
ShellExecutionService ,
2025-09-06 01:39:02 -04:00
} from '@google/gemini-cli-core' ;
import { validateAuthMethod } from '../config/auth.js' ;
import { loadHierarchicalGeminiMemory } from '../config/config.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-10-09 19:27:20 -07:00
import { useStdout , useStdin } from 'ink' ;
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' ;
import { computeWindowTitle } 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' ;
import { useVim } from './hooks/vim.js' ;
import { type LoadedSettings , SettingScope } from '../config/settings.js' ;
import { type InitializationResult } from '../core/initializer.js' ;
import { useFocus } from './hooks/useFocus.js' ;
import { useBracketedPaste } from './hooks/useBracketedPaste.js' ;
import { useKeypress , type Key } from './hooks/useKeypress.js' ;
import { keyMatchers , Command } from './keyMatchers.js' ;
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js' ;
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 { ConsolePatcher } from './utils/ConsolePatcher.js' ;
import { registerCleanup , runExitCleanup } from '../utils/cleanup.js' ;
import { useMessageQueue } from './hooks/useMessageQueue.js' ;
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js' ;
import { useWorkspaceMigration } from './hooks/useWorkspaceMigration.js' ;
import { useSessionStats } from './contexts/SessionContext.js' ;
import { useGitBranchName } from './hooks/useGitBranchName.js' ;
2025-09-18 14:49:47 -07:00
import { useExtensionUpdates } from './hooks/useExtensionUpdates.js' ;
2025-09-20 10:59:37 -07:00
import { ShellFocusContext } from './contexts/ShellFocusContext.js' ;
2025-09-06 01:39:02 -04:00
const CTRL_EXIT_PROMPT_DURATION_MS = 1000 ;
2025-10-15 22:32:50 +05:30
const QUEUE_ERROR_DISPLAY_DURATION_MS = 3000 ;
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 ;
} ) ;
}
interface AppContainerProps {
config : Config ;
settings : LoadedSettings ;
startupWarnings? : string [ ] ;
version : string ;
initializationResult : InitializationResult ;
}
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 ) = > {
const { settings , config , initializationResult } = props ;
const historyManager = useHistory ( ) ;
2025-09-17 14:32:46 -07:00
useMemoryMonitor ( historyManager ) ;
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-09-11 13:27:27 -07:00
2025-09-06 01:39:02 -04:00
const [ geminiMdFileCount , setGeminiMdFileCount ] = useState < number > (
initializationResult . geminiMdFileCount ,
) ;
const [ shellModeActive , setShellModeActive ] = useState ( false ) ;
const [ modelSwitchedFromQuotaError , setModelSwitchedFromQuotaError ] =
useState < boolean > ( false ) ;
const [ historyRemountKey , setHistoryRemountKey ] = useState ( 0 ) ;
const [ updateInfo , setUpdateInfo ] = useState < UpdateObject | null > ( null ) ;
const [ isTrustedFolder , setIsTrustedFolder ] = useState < boolean | undefined > (
config . isTrustedFolder ( ) ,
) ;
2025-09-18 14:49:47 -07:00
2025-10-15 22:32:50 +05:30
const [ queueErrorMessage , setQueueErrorMessage ] = useState < string | null > (
null ,
) ;
2025-09-18 14:49:47 -07:00
const extensions = config . getExtensions ( ) ;
2025-09-29 14:19:19 -07:00
const {
extensionsUpdateState ,
2025-10-01 14:53:15 -07:00
extensionsUpdateStateInternal ,
dispatchExtensionStateUpdate ,
2025-09-29 14:19:19 -07:00
confirmUpdateExtensionRequests ,
addConfirmUpdateExtensionRequest ,
} = useExtensionUpdates (
extensions ,
historyManager . addItem ,
config . getWorkingDir ( ) ,
) ;
2025-09-08 16:19:52 -04:00
2025-09-22 11:45:02 -07:00
const [ isPermissionsDialogOpen , setPermissionsDialogOpen ] = useState ( false ) ;
const openPermissionsDialog = useCallback (
( ) = > setPermissionsDialogOpen ( true ) ,
[ ] ,
) ;
const closePermissionsDialog = useCallback (
( ) = > setPermissionsDialogOpen ( false ) ,
[ ] ,
) ;
2025-10-07 10:28:35 -07:00
const toggleDebugProfiler = useCallback (
( ) = > setShowDebugProfiler ( ( prev ) = > ! prev ) ,
[ ] ,
) ;
2025-09-08 16:19:52 -04:00
// Helper to determine the effective model, considering the fallback state.
const getEffectiveModel = useCallback ( ( ) = > {
if ( config . isInFallbackMode ( ) ) {
return DEFAULT_GEMINI_FLASH_MODEL ;
}
return config . getModel ( ) ;
} , [ config ] ) ;
const [ currentModel , setCurrentModel ] = useState ( getEffectiveModel ( ) ) ;
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 ) ;
const [ userMessages , setUserMessages ] = useState < string [ ] > ( [ ] ) ;
// Terminal and layout hooks
const { columns : terminalWidth , rows : terminalHeight } = useTerminalSize ( ) ;
const { stdin , setRawMode } = useStdin ( ) ;
const { stdout } = useStdout ( ) ;
// 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 originalTitleRef = useRef (
computeWindowTitle ( basename ( config . getTargetDir ( ) ) ) ,
) ;
const lastTitleRef = useRef < string | null > ( null ) ;
2025-09-06 01:39:02 -04:00
const staticExtraHeight = 3 ;
useEffect ( ( ) = > {
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-09-06 01:39:02 -04:00
registerCleanup ( async ( ) = > {
const ideClient = await IdeClient . getInstance ( ) ;
await ideClient . disconnect ( ) ;
} ) ;
} , [ config ] ) ;
2025-09-08 16:37:36 -07:00
useEffect (
( ) = > setUpdateHandler ( historyManager . addItem , setUpdateInfo ) ,
[ historyManager . addItem ] ,
) ;
2025-09-06 01:39:02 -04:00
// Watch for model changes (e.g., from Flash fallback)
useEffect ( ( ) = > {
const checkModelChange = ( ) = > {
2025-09-08 16:19:52 -04:00
const effectiveModel = getEffectiveModel ( ) ;
if ( effectiveModel !== currentModel ) {
setCurrentModel ( effectiveModel ) ;
2025-09-06 01:39:02 -04:00
}
} ;
checkModelChange ( ) ;
const interval = setInterval ( checkModelChange , 1000 ) ; // Check every second
return ( ) = > clearInterval ( interval ) ;
2025-09-08 16:19:52 -04:00
} , [ config , currentModel , getEffectiveModel ] ) ;
2025-09-06 01:39:02 -04:00
const {
consoleMessages ,
handleNewMessage ,
clearConsoleMessages : clearConsoleMessagesState ,
} = useConsoleMessages ( ) ;
useEffect ( ( ) = > {
const consolePatcher = new ConsolePatcher ( {
onNewMessage : handleNewMessage ,
debugMode : config.getDebugMode ( ) ,
} ) ;
consolePatcher . patch ( ) ;
registerCleanup ( consolePatcher . cleanup ) ;
} , [ handleNewMessage , config ] ) ;
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 ;
}
} , [ ] ) ;
const buffer = useTextBuffer ( {
initialText : '' ,
viewport : { height : 10 , width : inputWidth } ,
stdin ,
setRawMode ,
isValidPath ,
shellModeActive ,
} ) ;
useEffect ( ( ) = > {
const fetchUserMessages = async ( ) = > {
const pastMessagesRaw = ( await logger ? . getPreviousUserMessages ( ) ) || [ ] ;
const currentSessionUserMessages = historyManager . history
. filter (
( item ) : item is HistoryItem & { type : 'user' ; text : string } = >
item . type === 'user' &&
typeof item . text === 'string' &&
item . text . trim ( ) !== '' ,
)
. map ( ( item ) = > item . text )
. reverse ( ) ;
const combinedMessages = [
. . . currentSessionUserMessages ,
. . . pastMessagesRaw ,
] ;
const deduplicatedMessages : string [ ] = [ ] ;
if ( combinedMessages . length > 0 ) {
deduplicatedMessages . push ( combinedMessages [ 0 ] ) ;
for ( let i = 1 ; i < combinedMessages . length ; i ++ ) {
if ( combinedMessages [ i ] !== combinedMessages [ i - 1 ] ) {
deduplicatedMessages . push ( combinedMessages [ i ] ) ;
}
}
}
setUserMessages ( deduplicatedMessages . reverse ( ) ) ;
} ;
fetchUserMessages ( ) ;
} , [ historyManager . history , logger ] ) ;
const refreshStatic = useCallback ( ( ) = > {
stdout . write ( ansiEscapes . clearTerminal ) ;
setHistoryRemountKey ( ( prev ) = > prev + 1 ) ;
} , [ setHistoryRemountKey , stdout ] ) ;
const {
isThemeDialogOpen ,
openThemeDialog ,
handleThemeSelect ,
handleThemeHighlight ,
} = useThemeCommand (
settings ,
setThemeError ,
historyManager . addItem ,
initializationResult . themeError ,
) ;
const { authState , setAuthState , authError , onAuthError } = useAuthCommand (
settings ,
config ,
) ;
2025-09-08 16:19:52 -04:00
const { proQuotaRequest , handleProQuotaChoice } = useQuotaAndFallback ( {
config ,
historyManager ,
userTier ,
setAuthState ,
setModelSwitchedFromQuotaError ,
} ) ;
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 ;
// Create handleAuthSelect wrapper for backward compatibility
const handleAuthSelect = useCallback (
async ( authType : AuthType | undefined , scope : SettingScope ) = > {
if ( authType ) {
await clearCachedCredentialFile ( ) ;
settings . setValue ( scope , 'security.auth.selectedType' , authType ) ;
try {
await config . refreshAuth ( authType ) ;
setAuthState ( AuthState . Authenticated ) ;
} catch ( e ) {
onAuthError (
` Failed to authenticate: ${ e instanceof Error ? e.message : String ( e ) } ` ,
) ;
return ;
}
if (
authType === AuthType . LOGIN_WITH_GOOGLE &&
config . isBrowserLaunchSuppressed ( )
) {
await runExitCleanup ( ) ;
console . log ( `
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Logging in with Google . . . Please restart Gemini CLI to continue .
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
` );
process . exit ( 0 ) ;
}
}
setAuthState ( AuthState . Authenticated ) ;
} ,
[ settings , config , setAuthState , onAuthError ] ,
) ;
// 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 (
settings . merged . security ? . auth ? . enforcedType &&
settings . merged . security ? . auth . selectedType &&
settings . merged . security ? . auth . enforcedType !==
settings . merged . security ? . auth . selectedType
) {
onAuthError (
` Authentication is enforced to be ${ settings . merged . security ? . auth . enforcedType } , but you are currently using ${ settings . merged . security ? . auth . selectedType } . ` ,
) ;
} else if (
settings . merged . security ? . auth ? . selectedType &&
! settings . merged . security ? . auth ? . useExternal
) {
const error = validateAuthMethod (
settings . merged . security . auth . selectedType ,
) ;
if ( error ) {
onAuthError ( error ) ;
}
}
} , [
settings . merged . security ? . auth ? . selectedType ,
settings . merged . security ? . auth ? . enforcedType ,
settings . merged . security ? . auth ? . useExternal ,
onAuthError ,
] ) ;
const [ editorError , setEditorError ] = useState < string | null > ( null ) ;
const {
isEditorDialogOpen ,
openEditorDialog ,
handleEditorSelect ,
exitEditorDialog ,
} = useEditorSettings ( settings , setEditorError , historyManager . addItem ) ;
const { isSettingsDialogOpen , openSettingsDialog , closeSettingsDialog } =
useSettingsCommand ( ) ;
2025-09-23 12:50:09 -04:00
const { isModelDialogOpen , openModelDialog , closeModelDialog } =
useModelCommand ( ) ;
2025-09-06 01:39:02 -04:00
const {
showWorkspaceMigrationDialog ,
workspaceExtensions ,
onWorkspaceMigrationDialogOpen ,
onWorkspaceMigrationDialogClose ,
} = useWorkspaceMigration ( settings ) ;
const { toggleVimEnabled } = useVimMode ( ) ;
const slashCommandActions = useMemo (
( ) = > ( {
openAuthDialog : ( ) = > setAuthState ( AuthState . Updating ) ,
openThemeDialog ,
openEditorDialog ,
openPrivacyNotice : ( ) = > setShowPrivacyNotice ( true ) ,
openSettingsDialog ,
2025-09-23 12:50:09 -04:00
openModelDialog ,
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 ,
2025-09-06 01:39:02 -04:00
} ) ,
[
setAuthState ,
openThemeDialog ,
openEditorDialog ,
openSettingsDialog ,
2025-09-23 12:50:09 -04:00
openModelDialog ,
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 ,
2025-09-06 01:39:02 -04:00
] ,
) ;
const {
handleSlashCommand ,
slashCommands ,
pendingHistoryItems : pendingSlashCommandHistoryItems ,
commandContext ,
shellConfirmationRequest ,
confirmationRequest ,
} = useSlashCommandProcessor (
config ,
settings ,
historyManager . addItem ,
historyManager . clearItems ,
historyManager . loadHistory ,
refreshStatic ,
toggleVimEnabled ,
setIsProcessing ,
setGeminiMdFileCount ,
slashCommandActions ,
2025-10-01 14:53:15 -07:00
extensionsUpdateStateInternal ,
2025-09-11 12:59:38 -07:00
isConfigInitialized ,
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-10-02 17:46:54 -04:00
const { memoryContent , fileCount , filePaths } =
await loadHierarchicalGeminiMemory (
process . cwd ( ) ,
settings . merged . context ? . loadMemoryFromIncludeDirectories
? config . getWorkspaceContext ( ) . getDirectories ( )
: [ ] ,
config . getDebugMode ( ) ,
config . getFileService ( ) ,
settings . merged ,
config . getExtensionContextFilePaths ( ) ,
config . isTrustedFolder ( ) ,
settings . merged . context ? . importFormat || 'tree' , // Use setting or default to 'tree'
config . getFileFilteringOptions ( ) ,
) ;
2025-09-06 01:39:02 -04:00
config . setUserMemory ( memoryContent ) ;
config . setGeminiMdFileCount ( fileCount ) ;
2025-10-02 17:46:54 -04:00
config . setGeminiMdFilePaths ( filePaths ) ;
2025-09-06 01:39:02 -04:00
setGeminiMdFileCount ( fileCount ) ;
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 ( ) ) {
console . log (
` [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 ( ) ,
) ;
console . error ( 'Error refreshing memory:' , error ) ;
}
} , [ config , historyManager , settings . merged ] ) ;
const cancelHandlerRef = useRef < ( ) = > void > ( ( ) = > { } ) ;
const {
streamingState ,
submitQuery ,
initError ,
pendingHistoryItems : pendingGeminiHistoryItems ,
thought ,
cancelOngoingRequest ,
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-09-06 01:39:02 -04:00
} = useGeminiStream (
config . getGeminiClient ( ) ,
historyManager . history ,
historyManager . addItem ,
config ,
settings ,
setDebugMessage ,
handleSlashCommand ,
shellModeActive ,
( ) = > settings . merged . general ? . preferredEditor as EditorType ,
onAuthError ,
performMemoryRefresh ,
modelSwitchedFromQuotaError ,
setModelSwitchedFromQuotaError ,
refreshStatic ,
( ) = > cancelHandlerRef . current ( ) ,
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
) ;
2025-09-14 20:20:21 -07:00
// Auto-accept indicator
const showAutoAcceptIndicator = useAutoAcceptIndicator ( {
config ,
addItem : historyManager.addItem ,
onApprovalModeChange : handleApprovalModeChange ,
} ) ;
2025-09-06 01:39:02 -04:00
const { messageQueue , addMessage , clearQueue , getQueuedMessagesText } =
useMessageQueue ( {
2025-09-08 16:37:36 -07:00
isConfigInitialized ,
2025-09-06 01:39:02 -04:00
streamingState ,
submitQuery ,
} ) ;
cancelHandlerRef . current = useCallback ( ( ) = > {
const pendingHistoryItems = [
. . . pendingSlashCommandHistoryItems ,
. . . pendingGeminiHistoryItems ,
] ;
if ( isToolExecuting ( pendingHistoryItems ) ) {
buffer . setText ( '' ) ; // Just clear the prompt
return ;
}
const lastUserMessage = userMessages . at ( - 1 ) ;
let textToSet = lastUserMessage || '' ;
const queuedText = getQueuedMessagesText ( ) ;
if ( queuedText ) {
textToSet = textToSet ? ` ${ textToSet } \ n \ n ${ queuedText } ` : queuedText ;
clearQueue ( ) ;
}
if ( textToSet ) {
buffer . setText ( textToSet ) ;
}
} , [
buffer ,
userMessages ,
getQueuedMessagesText ,
clearQueue ,
pendingSlashCommandHistoryItems ,
pendingGeminiHistoryItems ,
] ) ;
const handleFinalSubmit = useCallback (
( submittedValue : string ) = > {
addMessage ( submittedValue ) ;
} ,
[ addMessage ] ,
) ;
const handleClearScreen = useCallback ( ( ) = > {
historyManager . clearItems ( ) ;
clearConsoleMessagesState ( ) ;
console . clear ( ) ;
refreshStatic ( ) ;
} , [ historyManager , clearConsoleMessagesState , refreshStatic ] ) ;
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 =
! initError &&
! isProcessing &&
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-09-12 22:42:17 -07:00
if ( fullFooterMeasurement . height > 0 ) {
setControlsHeight ( fullFooterMeasurement . height ) ;
}
2025-09-06 01:39:02 -04:00
}
2025-09-12 22:42:17 -07:00
} , [ buffer , terminalWidth , terminalHeight ] ) ;
// Compute available terminal height based on controls measurement
2025-09-18 02:51:46 +09:00
const availableTerminalHeight = Math . max (
0 ,
terminalHeight - controlsHeight - staticExtraHeight - 2 ,
) ;
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 ,
) ,
2025-09-11 13:27:27 -07:00
pager : settings.merged.tools?.shell?.pager ,
showColor : settings.merged.tools?.shell?.showColor ,
} ) ;
2025-09-06 01:39:02 -04:00
const isFocused = useFocus ( ) ;
useBracketedPaste ( ) ;
// Context file names computation
const contextFileNames = useMemo ( ( ) = > {
const fromSettings = settings . merged . context ? . fileName ;
return fromSettings
? Array . isArray ( fromSettings )
? fromSettings
: [ fromSettings ]
: getAllGeminiMdFilenames ( ) ;
} , [ settings . merged . context ? . fileName ] ) ;
// 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 ) {
ShellExecutionService . resizePty (
activePtyId ,
Math . floor ( terminalWidth * SHELL_WIDTH_FRACTION ) ,
2025-09-12 22:42:17 -07:00
Math . max ( Math . floor ( availableTerminalHeight - SHELL_HEIGHT_PADDING ) , 1 ) ,
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 ) ;
} ;
getIde ( ) ;
} , [ ] ) ;
const shouldShowIdePrompt = Boolean (
currentIDE &&
! config . getIdeMode ( ) &&
! settings . merged . ide ? . hasSeenNudge &&
! idePromptAnswered ,
) ;
const [ showErrorDetails , setShowErrorDetails ] = useState < boolean > ( false ) ;
const [ showToolDescriptions , setShowToolDescriptions ] =
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
const [ ctrlCPressedOnce , setCtrlCPressedOnce ] = useState ( false ) ;
const ctrlCTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
const [ ctrlDPressedOnce , setCtrlDPressedOnce ] = useState ( false ) ;
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 ) ;
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 ) ;
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 ) ;
const logErrorHandler = ( errorMessage : unknown ) = > {
handleNewMessage ( {
type : 'error' ,
content : String ( errorMessage ) ,
count : 1 ,
} ) ;
} ;
appEvents . on ( AppEvent . LogError , logErrorHandler ) ;
return ( ) = > {
appEvents . off ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
appEvents . off ( AppEvent . LogError , logErrorHandler ) ;
} ;
} , [ handleNewMessage ] ) ;
const handleEscapePromptChange = useCallback ( ( showPrompt : boolean ) = > {
setShowEscapePrompt ( showPrompt ) ;
} , [ ] ) ;
const handleIdePromptComplete = useCallback (
( result : IdeIntegrationNudgeResult ) = > {
if ( result . userSelection === 'yes' ) {
handleSlashCommand ( '/ide install' ) ;
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
} else if ( result . userSelection === 'dismiss' ) {
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
}
setIdePromptAnswered ( true ) ;
} ,
[ handleSlashCommand , settings ] ,
) ;
2025-09-22 21:00:11 +05:30
const { elapsedTime , currentLoadingPhrase } = useLoadingIndicator (
streamingState ,
settings . merged . ui ? . customWittyPhrases ,
) ;
2025-09-06 01:39:02 -04:00
const handleExit = useCallback (
(
pressedOnce : boolean ,
setPressedOnce : ( value : boolean ) = > void ,
timerRef : React.MutableRefObject < NodeJS.Timeout | null > ,
) = > {
if ( pressedOnce ) {
if ( timerRef . current ) {
clearTimeout ( timerRef . current ) ;
}
handleSlashCommand ( '/quit' ) ;
} else {
setPressedOnce ( true ) ;
timerRef . current = setTimeout ( ( ) = > {
setPressedOnce ( false ) ;
timerRef . current = null ;
} , CTRL_EXIT_PROMPT_DURATION_MS ) ;
}
} ,
[ handleSlashCommand ] ,
) ;
const handleGlobalKeypress = useCallback (
( key : Key ) = > {
// Debug log keystrokes if enabled
if ( settings . merged . general ? . debugKeystrokeLogging ) {
console . log ( '[DEBUG] Keystroke:' , JSON . stringify ( key ) ) ;
}
2025-09-19 14:52:29 +08:00
if ( keyMatchers [ Command . QUIT ] ( key ) ) {
if ( ! ctrlCPressedOnce ) {
cancelOngoingRequest ? . ( ) ;
}
if ( ! ctrlCPressedOnce ) {
setCtrlCPressedOnce ( true ) ;
ctrlCTimerRef . current = setTimeout ( ( ) = > {
setCtrlCPressedOnce ( false ) ;
ctrlCTimerRef . current = null ;
} , CTRL_EXIT_PROMPT_DURATION_MS ) ;
return ;
}
handleExit ( ctrlCPressedOnce , setCtrlCPressedOnce , ctrlCTimerRef ) ;
return ;
} else if ( keyMatchers [ Command . EXIT ] ( key ) ) {
if ( buffer . text . length > 0 ) {
return ;
}
handleExit ( ctrlDPressedOnce , setCtrlDPressedOnce , ctrlDTimerRef ) ;
2025-09-06 01:39:02 -04:00
return ;
}
let enteringConstrainHeightMode = false ;
if ( ! constrainHeight ) {
enteringConstrainHeightMode = true ;
setConstrainHeight ( true ) ;
}
if ( keyMatchers [ Command . SHOW_ERROR_DETAILS ] ( key ) ) {
setShowErrorDetails ( ( prev ) = > ! prev ) ;
} else if ( keyMatchers [ Command . TOGGLE_TOOL_DESCRIPTIONS ] ( key ) ) {
const newValue = ! showToolDescriptions ;
setShowToolDescriptions ( newValue ) ;
const mcpServers = config . getMcpServers ( ) ;
if ( Object . keys ( mcpServers || { } ) . length > 0 ) {
handleSlashCommand ( newValue ? '/mcp desc' : '/mcp nodesc' ) ;
}
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 ;
} ) ;
2025-09-06 01:39:02 -04:00
} else if (
keyMatchers [ Command . TOGGLE_IDE_CONTEXT_DETAIL ] ( key ) &&
config . getIdeMode ( ) &&
ideContextState
) {
handleSlashCommand ( '/ide status' ) ;
} else if (
keyMatchers [ Command . SHOW_MORE_LINES ] ( key ) &&
! enteringConstrainHeightMode
) {
setConstrainHeight ( false ) ;
2025-09-11 13:27:27 -07:00
} else if ( keyMatchers [ Command . TOGGLE_SHELL_INPUT_FOCUS ] ( key ) ) {
2025-09-20 10:59:37 -07:00
if ( activePtyId || embeddedShellFocused ) {
setEmbeddedShellFocused ( ( prev ) = > ! prev ) ;
2025-09-11 13:27:27 -07:00
}
2025-09-06 01:39:02 -04:00
}
} ,
[
constrainHeight ,
setConstrainHeight ,
setShowErrorDetails ,
showToolDescriptions ,
setShowToolDescriptions ,
config ,
ideContextState ,
handleExit ,
ctrlCPressedOnce ,
setCtrlCPressedOnce ,
ctrlCTimerRef ,
buffer . text . length ,
ctrlDPressedOnce ,
setCtrlDPressedOnce ,
ctrlDTimerRef ,
handleSlashCommand ,
cancelOngoingRequest ,
2025-09-11 13:27:27 -07:00
activePtyId ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2025-09-06 01:39:02 -04:00
settings . merged . general ? . debugKeystrokeLogging ,
2025-10-16 11:23:36 -07:00
refreshStatic ,
2025-09-06 01:39:02 -04:00
] ,
) ;
useKeypress ( handleGlobalKeypress , { isActive : true } ) ;
2025-09-28 03:48:24 +08:00
// Update terminal title with Gemini CLI status and thoughts
useEffect ( ( ) = > {
// Respect both showStatusInTitle and hideWindowTitle settings
if (
! settings . merged . ui ? . showStatusInTitle ||
settings . merged . ui ? . hideWindowTitle
)
return ;
let title ;
if ( streamingState === StreamingState . Idle ) {
title = originalTitleRef . current ;
} else {
2025-09-29 13:14:53 -04:00
const statusText = thought ? . subject
? . replace ( /[\r\n]+/g , ' ' )
. substring ( 0 , 80 ) ;
2025-09-28 03:48:24 +08:00
title = statusText || originalTitleRef . current ;
}
// Pad the title to a fixed width to prevent taskbar icon resizing.
const paddedTitle = title . padEnd ( 80 , ' ' ) ;
// Only update the title if it's different from the last value we set
if ( lastTitleRef . current !== paddedTitle ) {
lastTitleRef . current = paddedTitle ;
stdout . write ( ` \ x1b]2; ${ paddedTitle } \ x07 ` ) ;
}
// Note: We don't need to reset the window title on exit because Gemini CLI is already doing that elsewhere
} , [
streamingState ,
thought ,
settings . merged . ui ? . showStatusInTitle ,
settings . merged . ui ? . hideWindowTitle ,
stdout ,
] ) ;
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 =
showWorkspaceMigrationDialog ||
shouldShowIdePrompt ||
isFolderTrustDialogOpen ||
! ! shellConfirmationRequest ||
! ! confirmationRequest ||
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 ||
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-09-10 22:20:13 -07:00
! ! proQuotaRequest ;
2025-09-06 01:39:02 -04:00
const pendingHistoryItems = useMemo (
( ) = > [ . . . pendingSlashCommandHistoryItems , . . . pendingGeminiHistoryItems ] ,
[ pendingSlashCommandHistoryItems , pendingGeminiHistoryItems ] ,
) ;
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 ,
editorError ,
isEditorDialogOpen ,
showPrivacyNotice ,
corgiMode ,
debugMessage ,
quittingMessages ,
isSettingsDialogOpen ,
2025-09-23 12:50:09 -04:00
isModelDialogOpen ,
2025-09-22 11:45:02 -07:00
isPermissionsDialogOpen ,
2025-09-06 01:39:02 -04:00
slashCommands ,
pendingSlashCommandHistoryItems ,
commandContext ,
shellConfirmationRequest ,
confirmationRequest ,
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 ,
userMessages ,
buffer ,
inputWidth ,
suggestionsWidth ,
isInputActive ,
shouldShowIdePrompt ,
isFolderTrustDialogOpen : isFolderTrustDialogOpen ? ? false ,
isTrustedFolder ,
constrainHeight ,
showErrorDetails ,
filteredConsoleMessages ,
ideContextState ,
showToolDescriptions ,
2025-10-16 11:23:36 -07:00
renderMarkdown ,
2025-09-06 01:39:02 -04:00
ctrlCPressedOnce ,
ctrlDPressedOnce ,
showEscapePrompt ,
isFocused ,
elapsedTime ,
currentLoadingPhrase ,
historyRemountKey ,
messageQueue ,
2025-10-15 22:32:50 +05:30
queueErrorMessage ,
2025-09-06 01:39:02 -04:00
showAutoAcceptIndicator ,
showWorkspaceMigrationDialog ,
workspaceExtensions ,
currentModel ,
userTier ,
2025-09-08 16:19:52 -04:00
proQuotaRequest ,
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 ,
2025-09-20 10:59:37 -07:00
embeddedShellFocused ,
2025-10-07 10:28:35 -07:00
showDebugProfiler ,
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-09-23 12:50:09 -04:00
isModelDialogOpen ,
2025-09-22 11:45:02 -07:00
isPermissionsDialogOpen ,
2025-09-06 01:39:02 -04:00
slashCommands ,
pendingSlashCommandHistoryItems ,
commandContext ,
shellConfirmationRequest ,
confirmationRequest ,
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 ,
userMessages ,
buffer ,
inputWidth ,
suggestionsWidth ,
isInputActive ,
shouldShowIdePrompt ,
isFolderTrustDialogOpen ,
isTrustedFolder ,
constrainHeight ,
showErrorDetails ,
filteredConsoleMessages ,
ideContextState ,
showToolDescriptions ,
2025-10-16 11:23:36 -07:00
renderMarkdown ,
2025-09-06 01:39:02 -04:00
ctrlCPressedOnce ,
ctrlDPressedOnce ,
showEscapePrompt ,
isFocused ,
elapsedTime ,
currentLoadingPhrase ,
historyRemountKey ,
messageQueue ,
2025-10-15 22:32:50 +05:30
queueErrorMessage ,
2025-09-06 01:39:02 -04:00
showAutoAcceptIndicator ,
showWorkspaceMigrationDialog ,
workspaceExtensions ,
userTier ,
2025-09-08 16:19:52 -04:00
proQuotaRequest ,
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 ,
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-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 ,
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 ,
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 ,
onWorkspaceMigrationDialogOpen ,
onWorkspaceMigrationDialogClose ,
handleProQuotaChoice ,
2025-10-15 22:32:50 +05:30
setQueueErrorMessage ,
2025-09-06 01:39:02 -04:00
} ) ,
[
handleThemeSelect ,
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 ,
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 ,
onWorkspaceMigrationDialogOpen ,
onWorkspaceMigrationDialogClose ,
handleProQuotaChoice ,
2025-10-15 22:32:50 +05:30
setQueueErrorMessage ,
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 || [ ] ,
} }
>
2025-09-20 10:59:37 -07:00
< ShellFocusContext.Provider value = { isFocused } >
2025-09-17 15:37:13 -07:00
< App / >
2025-09-20 10:59:37 -07:00
< / ShellFocusContext.Provider >
2025-09-06 01:39:02 -04:00
< / AppContext.Provider >
< / ConfigContext.Provider >
< / UIActionsContext.Provider >
< / UIStateContext.Provider >
) ;
} ;