@vybestack/llxprt-code 0.1.14 → 0.1.16-hotfix1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +90 -2
  2. package/dist/package.json +4 -5
  3. package/dist/src/config/config.d.ts +4 -1
  4. package/dist/src/config/config.js +151 -54
  5. package/dist/src/config/config.js.map +1 -1
  6. package/dist/src/config/extension.d.ts +1 -0
  7. package/dist/src/config/extension.js +4 -0
  8. package/dist/src/config/extension.js.map +1 -1
  9. package/dist/src/config/settings.d.ts +13 -3
  10. package/dist/src/config/settings.js +84 -23
  11. package/dist/src/config/settings.js.map +1 -1
  12. package/dist/src/gemini.d.ts +3 -0
  13. package/dist/src/gemini.js +212 -36
  14. package/dist/src/gemini.js.map +1 -1
  15. package/dist/src/generated/git-commit.d.ts +1 -1
  16. package/dist/src/generated/git-commit.js +1 -1
  17. package/dist/src/generated/git-commit.js.map +1 -1
  18. package/dist/src/integration-tests/GITHUB_ACTIONS_README.md +41 -0
  19. package/dist/src/integration-tests/test-utils.d.ts +68 -0
  20. package/dist/src/integration-tests/test-utils.js +167 -0
  21. package/dist/src/integration-tests/test-utils.js.map +1 -0
  22. package/dist/src/nonInteractiveCli.js +24 -66
  23. package/dist/src/nonInteractiveCli.js.map +1 -1
  24. package/dist/src/providers/IFileSystem.d.ts +42 -0
  25. package/dist/src/providers/IFileSystem.js +46 -0
  26. package/dist/src/providers/IFileSystem.js.map +1 -0
  27. package/dist/src/providers/providerConfigUtils.d.ts +0 -4
  28. package/dist/src/providers/providerConfigUtils.js +21 -52
  29. package/dist/src/providers/providerConfigUtils.js.map +1 -1
  30. package/dist/src/providers/providerManagerInstance.d.ts +6 -1
  31. package/dist/src/providers/providerManagerInstance.js +117 -82
  32. package/dist/src/providers/providerManagerInstance.js.map +1 -1
  33. package/dist/src/services/BuiltinCommandLoader.js +13 -0
  34. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  35. package/dist/src/services/CommandService.d.ts +8 -4
  36. package/dist/src/services/CommandService.js +24 -8
  37. package/dist/src/services/CommandService.js.map +1 -1
  38. package/dist/src/services/FileCommandLoader.d.ts +15 -3
  39. package/dist/src/services/FileCommandLoader.js +94 -42
  40. package/dist/src/services/FileCommandLoader.js.map +1 -1
  41. package/dist/src/services/McpPromptLoader.d.ts +25 -0
  42. package/dist/src/services/McpPromptLoader.js +192 -0
  43. package/dist/src/services/McpPromptLoader.js.map +1 -0
  44. package/dist/src/services/prompt-processors/shellProcessor.d.ts +32 -0
  45. package/dist/src/services/prompt-processors/shellProcessor.js +77 -0
  46. package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -0
  47. package/dist/src/services/prompt-processors/types.d.ts +4 -0
  48. package/dist/src/services/prompt-processors/types.js +4 -0
  49. package/dist/src/services/prompt-processors/types.js.map +1 -1
  50. package/dist/src/ui/App.js +325 -195
  51. package/dist/src/ui/App.js.map +1 -1
  52. package/dist/src/ui/commands/aboutCommand.js +2 -5
  53. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  54. package/dist/src/ui/commands/baseurlCommand.js +54 -9
  55. package/dist/src/ui/commands/baseurlCommand.js.map +1 -1
  56. package/dist/src/ui/commands/chatCommand.js +39 -1
  57. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  58. package/dist/src/ui/commands/diagnosticsCommand.d.ts +10 -0
  59. package/dist/src/ui/commands/diagnosticsCommand.js +122 -0
  60. package/dist/src/ui/commands/diagnosticsCommand.js.map +1 -0
  61. package/dist/src/ui/commands/directoryCommand.d.ts +8 -0
  62. package/dist/src/ui/commands/directoryCommand.js +116 -0
  63. package/dist/src/ui/commands/directoryCommand.js.map +1 -0
  64. package/dist/src/ui/commands/ideCommand.js +101 -105
  65. package/dist/src/ui/commands/ideCommand.js.map +1 -1
  66. package/dist/src/ui/commands/initCommand.d.ts +7 -0
  67. package/dist/src/ui/commands/initCommand.js +108 -0
  68. package/dist/src/ui/commands/initCommand.js.map +1 -0
  69. package/dist/src/ui/commands/keyCommand.js +75 -11
  70. package/dist/src/ui/commands/keyCommand.js.map +1 -1
  71. package/dist/src/ui/commands/keyfileCommand.js +54 -13
  72. package/dist/src/ui/commands/keyfileCommand.js.map +1 -1
  73. package/dist/src/ui/commands/mcpCommand.js +53 -8
  74. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  75. package/dist/src/ui/commands/memoryCommand.js +2 -1
  76. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  77. package/dist/src/ui/commands/modelCommand.js +2 -5
  78. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  79. package/dist/src/ui/commands/profileCommand.d.ts +10 -0
  80. package/dist/src/ui/commands/profileCommand.js +592 -0
  81. package/dist/src/ui/commands/profileCommand.js.map +1 -0
  82. package/dist/src/ui/commands/providerCommand.js +46 -3
  83. package/dist/src/ui/commands/providerCommand.js.map +1 -1
  84. package/dist/src/ui/commands/setCommand.d.ts +7 -0
  85. package/dist/src/ui/commands/setCommand.js +431 -0
  86. package/dist/src/ui/commands/setCommand.js.map +1 -0
  87. package/dist/src/ui/commands/setupGithubCommand.d.ts +7 -0
  88. package/dist/src/ui/commands/setupGithubCommand.js +49 -0
  89. package/dist/src/ui/commands/setupGithubCommand.js.map +1 -0
  90. package/dist/src/ui/commands/types.d.ts +23 -4
  91. package/dist/src/ui/commands/types.js +1 -0
  92. package/dist/src/ui/commands/types.js.map +1 -1
  93. package/dist/src/ui/commands/vimCommand.js +0 -7
  94. package/dist/src/ui/commands/vimCommand.js.map +1 -1
  95. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +3 -3
  96. package/dist/src/ui/components/ContextSummaryDisplay.js +8 -8
  97. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  98. package/dist/src/ui/components/DebugProfiler.d.ts +6 -0
  99. package/dist/src/ui/components/DebugProfiler.js +26 -0
  100. package/dist/src/ui/components/DebugProfiler.js.map +1 -0
  101. package/dist/src/ui/components/Footer.d.ts +2 -0
  102. package/dist/src/ui/components/Footer.js +5 -4
  103. package/dist/src/ui/components/Footer.js.map +1 -1
  104. package/dist/src/ui/components/Header.js +1 -1
  105. package/dist/src/ui/components/Header.js.map +1 -1
  106. package/dist/src/ui/components/Help.js +2 -2
  107. package/dist/src/ui/components/Help.js.map +1 -1
  108. package/dist/src/ui/components/IDEContextDetailDisplay.d.ts +5 -4
  109. package/dist/src/ui/components/IDEContextDetailDisplay.js +6 -8
  110. package/dist/src/ui/components/IDEContextDetailDisplay.js.map +1 -1
  111. package/dist/src/ui/components/InputPrompt.d.ts +2 -0
  112. package/dist/src/ui/components/InputPrompt.js +128 -13
  113. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  114. package/dist/src/ui/components/LoadProfileDialog.d.ts +13 -0
  115. package/dist/src/ui/components/LoadProfileDialog.js +57 -0
  116. package/dist/src/ui/components/LoadProfileDialog.js.map +1 -0
  117. package/dist/src/ui/components/PrepareLabel.d.ts +15 -0
  118. package/dist/src/ui/components/PrepareLabel.js +16 -0
  119. package/dist/src/ui/components/PrepareLabel.js.map +1 -0
  120. package/dist/src/ui/components/ProviderModelDialog.js +75 -28
  121. package/dist/src/ui/components/ProviderModelDialog.js.map +1 -1
  122. package/dist/src/ui/components/SecureKeyInput.d.ts +15 -0
  123. package/dist/src/ui/components/SecureKeyInput.js +58 -0
  124. package/dist/src/ui/components/SecureKeyInput.js.map +1 -0
  125. package/dist/src/ui/components/ShellConfirmationDialog.d.ts +15 -0
  126. package/dist/src/ui/components/ShellConfirmationDialog.js +45 -0
  127. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -0
  128. package/dist/src/ui/components/SuggestionsDisplay.d.ts +1 -0
  129. package/dist/src/ui/components/SuggestionsDisplay.js +3 -3
  130. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  131. package/dist/src/ui/components/Tips.js +1 -1
  132. package/dist/src/ui/components/Tips.js.map +1 -1
  133. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +15 -4
  134. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  135. package/dist/src/ui/components/messages/UserMessage.js +4 -1
  136. package/dist/src/ui/components/messages/UserMessage.js.map +1 -1
  137. package/dist/src/ui/components/shared/text-buffer.d.ts +270 -2
  138. package/dist/src/ui/components/shared/text-buffer.js +410 -70
  139. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  140. package/dist/src/ui/components/shared/vim-buffer-actions.d.ts +72 -0
  141. package/dist/src/ui/components/shared/vim-buffer-actions.js +565 -0
  142. package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -0
  143. package/dist/src/ui/containers/SessionController.js +14 -15
  144. package/dist/src/ui/containers/SessionController.js.map +1 -1
  145. package/dist/src/ui/contexts/VimModeContext.js +2 -2
  146. package/dist/src/ui/contexts/VimModeContext.js.map +1 -1
  147. package/dist/src/ui/editors/editorSettingsManager.js +2 -0
  148. package/dist/src/ui/editors/editorSettingsManager.js.map +1 -1
  149. package/dist/src/ui/hooks/atCommandProcessor.js +56 -48
  150. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  151. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -0
  152. package/dist/src/ui/hooks/shellCommandProcessor.js +139 -200
  153. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  154. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +7 -3
  155. package/dist/src/ui/hooks/slashCommandProcessor.js +219 -130
  156. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  157. package/dist/src/ui/hooks/useAuthCommand.js +9 -0
  158. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  159. package/dist/src/ui/hooks/useCompletion.d.ts +5 -5
  160. package/dist/src/ui/hooks/useCompletion.js +7 -402
  161. package/dist/src/ui/hooks/useCompletion.js.map +1 -1
  162. package/dist/src/ui/hooks/useConsoleMessages.js +53 -37
  163. package/dist/src/ui/hooks/useConsoleMessages.js.map +1 -1
  164. package/dist/src/ui/hooks/useGeminiStream.js +75 -12
  165. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  166. package/dist/src/ui/hooks/useKeypress.js +5 -2
  167. package/dist/src/ui/hooks/useKeypress.js.map +1 -1
  168. package/dist/src/ui/hooks/useLoadProfileDialog.d.ts +27 -0
  169. package/dist/src/ui/hooks/useLoadProfileDialog.js +138 -0
  170. package/dist/src/ui/hooks/useLoadProfileDialog.js.map +1 -0
  171. package/dist/src/ui/hooks/useReactToolScheduler.js +0 -1
  172. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  173. package/dist/src/ui/hooks/useReverseSearchCompletion.d.ts +19 -0
  174. package/dist/src/ui/hooks/useReverseSearchCompletion.js +54 -0
  175. package/dist/src/ui/hooks/useReverseSearchCompletion.js.map +1 -0
  176. package/dist/src/ui/hooks/useShellHistory.d.ts +1 -0
  177. package/dist/src/ui/hooks/useShellHistory.js +30 -7
  178. package/dist/src/ui/hooks/useShellHistory.js.map +1 -1
  179. package/dist/src/ui/hooks/useSlashCompletion.d.ts +24 -0
  180. package/dist/src/ui/hooks/useSlashCompletion.js +451 -0
  181. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -0
  182. package/dist/src/ui/hooks/vim.d.ts +28 -0
  183. package/dist/src/ui/hooks/vim.js +630 -0
  184. package/dist/src/ui/hooks/vim.js.map +1 -0
  185. package/dist/src/ui/reducers/appReducer.d.ts +3 -2
  186. package/dist/src/ui/reducers/appReducer.js +1 -0
  187. package/dist/src/ui/reducers/appReducer.js.map +1 -1
  188. package/dist/src/ui/themes/theme-manager.js +10 -1
  189. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  190. package/dist/src/ui/themes/theme.d.ts +1 -0
  191. package/dist/src/ui/themes/theme.js +19 -4
  192. package/dist/src/ui/themes/theme.js.map +1 -1
  193. package/dist/src/ui/utils/renderLoopDetector.js +3 -3
  194. package/dist/src/ui/utils/renderLoopDetector.js.map +1 -1
  195. package/dist/src/ui/utils/secureInputHandler.d.ts +46 -0
  196. package/dist/src/ui/utils/secureInputHandler.js +128 -0
  197. package/dist/src/ui/utils/secureInputHandler.js.map +1 -0
  198. package/dist/src/ui/utils/textUtils.d.ts +0 -8
  199. package/dist/src/ui/utils/textUtils.js +0 -22
  200. package/dist/src/ui/utils/textUtils.js.map +1 -1
  201. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  202. package/dist/src/ui/utils/updateCheck.js +59 -25
  203. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  204. package/dist/src/utils/events.d.ts +11 -0
  205. package/dist/src/utils/events.js +13 -0
  206. package/dist/src/utils/events.js.map +1 -0
  207. package/dist/src/utils/gitUtils.d.ts +10 -0
  208. package/dist/src/utils/gitUtils.js +24 -0
  209. package/dist/src/utils/gitUtils.js.map +1 -0
  210. package/dist/src/utils/handleAutoUpdate.d.ts +11 -0
  211. package/dist/src/utils/handleAutoUpdate.js +101 -0
  212. package/dist/src/utils/handleAutoUpdate.js.map +1 -0
  213. package/dist/src/utils/installationInfo.d.ts +23 -0
  214. package/dist/src/utils/installationInfo.js +154 -0
  215. package/dist/src/utils/installationInfo.js.map +1 -0
  216. package/dist/src/utils/sandbox-macos-permissive-closed.sb +6 -0
  217. package/dist/src/utils/sandbox-macos-permissive-open.sb +6 -0
  218. package/dist/src/utils/sandbox-macos-permissive-proxied.sb +6 -0
  219. package/dist/src/utils/sandbox-macos-restrictive-closed.sb +6 -0
  220. package/dist/src/utils/sandbox-macos-restrictive-open.sb +6 -0
  221. package/dist/src/utils/sandbox-macos-restrictive-proxied.sb +6 -0
  222. package/dist/src/utils/sandbox.d.ts +2 -2
  223. package/dist/src/utils/sandbox.js +35 -11
  224. package/dist/src/utils/sandbox.js.map +1 -1
  225. package/dist/src/utils/spawnWrapper.d.ts +7 -0
  226. package/dist/src/utils/spawnWrapper.js +8 -0
  227. package/dist/src/utils/spawnWrapper.js.map +1 -0
  228. package/dist/src/utils/updateEventEmitter.d.ts +11 -0
  229. package/dist/src/utils/updateEventEmitter.js +12 -0
  230. package/dist/src/utils/updateEventEmitter.js.map +1 -0
  231. package/dist/src/validateNonInterActiveAuth.d.ts +2 -1
  232. package/dist/src/validateNonInterActiveAuth.js +31 -5
  233. package/dist/src/validateNonInterActiveAuth.js.map +1 -1
  234. package/dist/tsconfig.tsbuildinfo +1 -1
  235. package/package.json +4 -5
  236. package/dist/src/providers/enhanceConfigWithProviders.d.ts +0 -12
  237. package/dist/src/providers/enhanceConfigWithProviders.js +0 -16
  238. package/dist/src/providers/enhanceConfigWithProviders.js.map +0 -1
@@ -4,18 +4,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * Copyright 2025 Google LLC
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
- import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
8
- import { Box, Static, Text, useStdin, useStdout, useInput, } from 'ink';
9
- import { StreamingState } from './types.js';
7
+ import { useCallback, useEffect, useMemo, useState, useRef, useReducer, } from 'react';
8
+ import { Box, measureElement, Static, Text, useStdin, useStdout, useInput, } from 'ink';
9
+ import { StreamingState, MessageType } from './types.js';
10
+ import { useTerminalSize } from './hooks/useTerminalSize.js';
10
11
  import { useGeminiStream } from './hooks/useGeminiStream.js';
11
12
  import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
12
13
  import { useThemeCommand } from './hooks/useThemeCommand.js';
13
14
  import { useAuthCommand } from './hooks/useAuthCommand.js';
14
15
  import { useEditorSettings } from './hooks/useEditorSettings.js';
15
- import { useProviderModelDialog } from './hooks/useProviderModelDialog.js';
16
- import { useProviderDialog } from './hooks/useProviderDialog.js';
17
- import { ProviderModelDialog } from './components/ProviderModelDialog.js';
18
- import { ProviderDialog } from './components/ProviderDialog.js';
19
16
  import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
20
17
  import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
21
18
  import { useConsoleMessages } from './hooks/useConsoleMessages.js';
@@ -29,8 +26,10 @@ import { ThemeDialog } from './components/ThemeDialog.js';
29
26
  import { AuthDialog } from './components/AuthDialog.js';
30
27
  import { AuthInProgress } from './components/AuthInProgress.js';
31
28
  import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
29
+ import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
32
30
  import { Colors } from './colors.js';
33
31
  import { Help } from './components/Help.js';
32
+ import { loadHierarchicalLlxprtMemory } from '../config/config.js';
34
33
  import { Tips } from './components/Tips.js';
35
34
  import { ConsolePatcher } from './utils/ConsolePatcher.js';
36
35
  import { registerCleanup } from '../utils/cleanup.js';
@@ -38,9 +37,9 @@ import { DetailedMessagesDisplay } from './components/DetailedMessagesDisplay.js
38
37
  import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
39
38
  import { ContextSummaryDisplay } from './components/ContextSummaryDisplay.js';
40
39
  import { IDEContextDetailDisplay } from './components/IDEContextDetailDisplay.js';
41
- // useHistory is now managed by SessionController
40
+ import { useHistory } from './hooks/useHistoryManager.js';
42
41
  import process from 'node:process';
43
- import { getAllLlxprtMdFilenames, ApprovalMode, isEditorAvailable, ideContext, } from '@vybestack/llxprt-code-core';
42
+ import { getErrorMessage, getAllLlxprtMdFilenames, ApprovalMode, isEditorAvailable, FlashFallbackEvent, logFlashFallback, AuthType, ideContext, } from '@vybestack/llxprt-code-core';
44
43
  import { validateAuthMethod } from '../config/auth.js';
45
44
  import { useLogger } from './hooks/useLogger.js';
46
45
  import { StreamingContext } from './contexts/StreamingContext.js';
@@ -49,32 +48,45 @@ import { useGitBranchName } from './hooks/useGitBranchName.js';
49
48
  import { useFocus } from './hooks/useFocus.js';
50
49
  import { useBracketedPaste } from './hooks/useBracketedPaste.js';
51
50
  import { useTextBuffer } from './components/shared/text-buffer.js';
51
+ import { useVimMode, VimModeProvider } from './contexts/VimModeContext.js';
52
+ import { useVim } from './hooks/vim.js';
52
53
  import * as fs from 'fs';
54
+ import { appReducer, initialAppState, } from './reducers/appReducer.js';
55
+ import { AppDispatchProvider } from './contexts/AppDispatchContext.js';
53
56
  import { UpdateNotification } from './components/UpdateNotification.js';
54
- // Quota error functions moved to SessionController
55
- import { checkForUpdates } from './utils/updateCheck.js';
57
+ import { isProQuotaExceededError, isGenericQuotaExceededError, UserTierId, } from '@vybestack/llxprt-code-core';
56
58
  import ansiEscapes from 'ansi-escapes';
57
59
  import { OverflowProvider } from './contexts/OverflowContext.js';
58
60
  import { ShowMoreLines } from './components/ShowMoreLines.js';
59
61
  import { PrivacyNotice } from './privacy/PrivacyNotice.js';
62
+ import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
63
+ import { appEvents, AppEvent } from '../utils/events.js';
60
64
  import { getProviderManager } from '../providers/providerManagerInstance.js';
61
- import { UIStateShell } from './containers/UIStateShell.js';
62
- import { useLayout } from './components/LayoutManager.js';
63
- import { SessionController } from './containers/SessionController.js';
64
- import { useSession } from './hooks/useSession.js';
65
+ import { useProviderModelDialog } from './hooks/useProviderModelDialog.js';
66
+ import { useProviderDialog } from './hooks/useProviderDialog.js';
67
+ import { useLoadProfileDialog } from './hooks/useLoadProfileDialog.js';
68
+ import { ProviderModelDialog } from './components/ProviderModelDialog.js';
69
+ import { ProviderDialog } from './components/ProviderDialog.js';
70
+ import { LoadProfileDialog } from './components/LoadProfileDialog.js';
65
71
  const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
66
- export const AppWrapper = (props) => (_jsx(SessionStatsProvider, { children: _jsx(App, { ...props }) }));
67
- // Inner component that uses layout context
68
- const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthenticating, }) => {
72
+ export const AppWrapper = (props) => (_jsx(SessionStatsProvider, { children: _jsx(VimModeProvider, { settings: props.settings, children: _jsx(AppWithState, { ...props }) }) }));
73
+ // New intermediate component that manages state and provides context
74
+ const AppWithState = (props) => {
75
+ const [appState, appDispatch] = useReducer(appReducer, initialAppState);
76
+ return (_jsx(AppDispatchProvider, { value: appDispatch, children: _jsx(App, { ...props, appState: appState, appDispatch: appDispatch }) }));
77
+ };
78
+ const App = (props) => {
79
+ const { config, settings, startupWarnings = [], version, appState } = props;
69
80
  const isFocused = useFocus();
70
81
  useBracketedPaste();
71
- const [updateMessage, setUpdateMessage] = useState(null);
82
+ const [updateInfo, setUpdateInfo] = useState(null);
72
83
  const { stdout } = useStdout();
73
84
  const nightly = version.includes('nightly');
85
+ const { history, addItem, clearItems, loadHistory } = useHistory();
74
86
  useEffect(() => {
75
- checkForUpdates().then(setUpdateMessage);
76
- }, []);
77
- const { history, addItem, clearItems, loadHistory, sessionState, dispatch: sessionDispatch, appState, appDispatch, checkPaymentModeChange, performMemoryRefresh, } = useSession();
87
+ const cleanup = setUpdateHandler(addItem, setUpdateInfo);
88
+ return cleanup;
89
+ }, [addItem]);
78
90
  const { consoleMessages, handleNewMessage, clearConsoleMessages: clearConsoleMessagesState, } = useConsoleMessages();
79
91
  useEffect(() => {
80
92
  const consolePatcher = new ConsolePatcher({
@@ -85,37 +97,6 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
85
97
  registerCleanup(consolePatcher.cleanup);
86
98
  }, [handleNewMessage, config]);
87
99
  const { stats: sessionStats } = useSessionStats();
88
- // These are now managed by SessionController
89
- const { currentModel, isPaidMode, transientWarnings: sessionTransientWarnings, modelSwitchedFromQuotaError, } = sessionState;
90
- // Add payment mode warning to startup warnings only at startup
91
- const allStartupWarnings = useMemo(() => {
92
- const warnings = [...startupWarnings];
93
- // Only show payment warnings at startup (when history is empty)
94
- if (history.length === 0) {
95
- try {
96
- const providerManager = getProviderManager();
97
- if (providerManager.hasActiveProvider()) {
98
- const provider = providerManager.getActiveProvider();
99
- const isPaidMode = provider.isPaidMode?.();
100
- // Only show paid/free mode warnings for Gemini provider
101
- if (isPaidMode !== undefined && provider.name === 'gemini') {
102
- if (isPaidMode) {
103
- warnings.push(`! PAID MODE: You are using Gemini with API credentials - usage will be charged to your account`);
104
- }
105
- else {
106
- warnings.push(`FREE MODE: You are using Gemini with OAuth authentication - no charges will apply`);
107
- }
108
- }
109
- }
110
- }
111
- catch (_e) {
112
- // Ignore errors when checking payment mode
113
- }
114
- }
115
- return warnings;
116
- }, [startupWarnings, history]);
117
- // Use transient warnings from session state
118
- const transientWarnings = sessionTransientWarnings;
119
100
  const [staticNeedsRefresh, setStaticNeedsRefresh] = useState(false);
120
101
  const [staticKey, setStaticKey] = useState(0);
121
102
  const refreshStatic = useCallback(() => {
@@ -125,6 +106,12 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
125
106
  const [llxprtMdFileCount, setLlxprtMdFileCount] = useState(0);
126
107
  const [debugMessage, setDebugMessage] = useState('');
127
108
  const [showHelp, setShowHelp] = useState(false);
109
+ const [_themeError, _setThemeError] = useState(null);
110
+ const [authError, setAuthError] = useState(null);
111
+ const [_editorError, _setEditorError] = useState(null);
112
+ const [footerHeight, setFooterHeight] = useState(0);
113
+ const [_corgiMode, setCorgiMode] = useState(false);
114
+ const [currentModel, setCurrentModel] = useState(config.getModel());
128
115
  const [shellModeActive, setShellModeActive] = useState(false);
129
116
  const [showErrorDetails, setShowErrorDetails] = useState(false);
130
117
  const [showToolDescriptions, setShowToolDescriptions] = useState(false);
@@ -134,92 +121,233 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
134
121
  const ctrlCTimerRef = useRef(null);
135
122
  const [ctrlDPressedOnce, setCtrlDPressedOnce] = useState(false);
136
123
  const ctrlDTimerRef = useRef(null);
137
- const showPrivacyNotice = appState.openDialogs.privacy;
138
- // modelSwitchedFromQuotaError and userTier are now in sessionState
139
- const [_activeFile, _setActiveFile] = useState();
140
- const [openFiles, setOpenFiles] = useState();
124
+ const [constrainHeight, setConstrainHeight] = useState(true);
125
+ const [showPrivacyNotice, setShowPrivacyNotice] = useState(false);
126
+ const [modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError] = useState(false);
127
+ const [userTier, setUserTier] = useState(undefined);
128
+ const [ideContextState, setIdeContextState] = useState();
129
+ const [isProcessing, setIsProcessing] = useState(false);
130
+ const [providerModels, setProviderModels] = useState([]);
141
131
  useEffect(() => {
142
- const unsubscribe = ideContext.subscribeToOpenFiles(setOpenFiles);
132
+ const unsubscribe = ideContext.subscribeToIdeContext(setIdeContextState);
143
133
  // Set the initial value
144
- setOpenFiles(ideContext.getOpenFilesContext());
134
+ setIdeContextState(ideContext.getIdeContext());
145
135
  return unsubscribe;
146
136
  }, []);
137
+ useEffect(() => {
138
+ const openDebugConsole = () => {
139
+ setShowErrorDetails(true);
140
+ setConstrainHeight(false); // Make sure the user sees the full message.
141
+ };
142
+ appEvents.on(AppEvent.OpenDebugConsole, openDebugConsole);
143
+ const logErrorHandler = (errorMessage) => {
144
+ handleNewMessage({
145
+ type: 'error',
146
+ content: String(errorMessage),
147
+ count: 1,
148
+ });
149
+ };
150
+ appEvents.on(AppEvent.LogError, logErrorHandler);
151
+ return () => {
152
+ appEvents.off(AppEvent.OpenDebugConsole, openDebugConsole);
153
+ appEvents.off(AppEvent.LogError, logErrorHandler);
154
+ };
155
+ }, [handleNewMessage]);
147
156
  const openPrivacyNotice = useCallback(() => {
148
- appDispatch({ type: 'OPEN_DIALOG', payload: 'privacy' });
149
- }, [appDispatch]);
150
- const closePrivacyNotice = useCallback(() => {
151
- appDispatch({ type: 'CLOSE_DIALOG', payload: 'privacy' });
152
- }, [appDispatch]);
157
+ setShowPrivacyNotice(true);
158
+ }, []);
153
159
  const initialPromptSubmitted = useRef(false);
154
- const errorCount = useMemo(() => consoleMessages.filter((msg) => msg.type === 'error').length, [consoleMessages]);
155
- // Create dispatch-based wrapper for addItem
156
- const addItemViaDispatch = useCallback((itemData, baseTimestamp) => {
157
- appDispatch({
158
- type: 'ADD_ITEM',
159
- payload: { itemData, baseTimestamp },
160
- });
161
- }, [appDispatch]);
162
- const { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, } = useThemeCommand(settings, appState, addItemViaDispatch);
163
- const { isAuthDialogOpen, openAuthDialog, handleAuthSelect, isAuthenticating: authIsAuthenticating, cancelAuthentication, } = useAuthCommand(settings, appState, config);
164
- // Sync auth state with parent
165
- useEffect(() => {
166
- setIsAuthenticating(authIsAuthenticating);
167
- }, [authIsAuthenticating, setIsAuthenticating]);
168
- const onAuthTimeout = useCallback(() => {
169
- appDispatch({
170
- type: 'SET_AUTH_ERROR',
171
- payload: 'Authentication timed out. Please try again.',
172
- });
173
- cancelAuthentication();
174
- openAuthDialog();
175
- }, [cancelAuthentication, openAuthDialog, appDispatch]);
160
+ const errorCount = useMemo(() => consoleMessages
161
+ .filter((msg) => msg.type === 'error')
162
+ .reduce((total, msg) => total + msg.count, 0), [consoleMessages]);
163
+ const { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, } = useThemeCommand(settings, appState, addItem);
164
+ const { isAuthDialogOpen, openAuthDialog, handleAuthSelect, isAuthenticating, cancelAuthentication, } = useAuthCommand(settings, appState, config);
176
165
  useEffect(() => {
177
- if (settings.merged.selectedAuthType) {
166
+ if (settings.merged.selectedAuthType && !settings.merged.useExternalAuth) {
178
167
  const error = validateAuthMethod(settings.merged.selectedAuthType);
179
168
  if (error) {
180
- appDispatch({ type: 'SET_AUTH_ERROR', payload: error });
169
+ setAuthError(error);
181
170
  openAuthDialog();
182
171
  }
183
172
  }
184
- }, [settings.merged.selectedAuthType, openAuthDialog, appDispatch]);
185
- // User tier sync is now handled by SessionController
186
- const { isEditorDialogOpen, openEditorDialog, handleEditorSelect, exitEditorDialog, } = useEditorSettings(settings, appState, addItemViaDispatch);
187
- const providerModelDialog = useProviderModelDialog({
188
- addMessage: (m) => addItemViaDispatch({ type: m.type, text: m.content }, m.timestamp.getTime()),
189
- onModelChange: () => {
190
- // Model change detection is handled by SessionController's useEffect
191
- // No need to manually update here
192
- },
173
+ }, [
174
+ settings.merged.selectedAuthType,
175
+ settings.merged.useExternalAuth,
176
+ openAuthDialog,
177
+ setAuthError,
178
+ ]);
179
+ // Sync user tier from config when authentication changes
180
+ useEffect(() => {
181
+ // Only sync when not currently authenticating
182
+ if (!isAuthenticating) {
183
+ setUserTier(config.getGeminiClient()?.getUserTier());
184
+ }
185
+ }, [config, isAuthenticating]);
186
+ const { isEditorDialogOpen, openEditorDialog, handleEditorSelect, exitEditorDialog, } = useEditorSettings(settings, appState, addItem);
187
+ const providerManager = getProviderManager(config);
188
+ const { showDialog: isProviderDialogOpen, openDialog: openProviderDialog, handleSelect: handleProviderSelect, closeDialog: exitProviderDialog, } = useProviderDialog({
189
+ addMessage: (msg) => addItem({ type: msg.type, text: msg.content }, msg.timestamp.getTime()),
193
190
  appState,
191
+ config,
194
192
  });
195
- const handleClearScreen = useCallback(() => {
196
- clearItems();
197
- clearConsoleMessagesState();
198
- console.clear();
199
- refreshStatic();
200
- }, [clearItems, clearConsoleMessagesState, refreshStatic]);
201
- // Provider selection dialog
202
- const providerDialog = useProviderDialog({
203
- addMessage: (m) => addItemViaDispatch({ type: m.type, text: m.content }, m.timestamp.getTime()),
204
- onProviderChange: () => {
205
- // Provider change will be detected by SessionController's useEffect
206
- checkPaymentModeChange?.();
207
- },
193
+ const { showDialog: isProviderModelDialogOpen, openDialog: openProviderModelDialogRaw, handleSelect: handleProviderModelChange, closeDialog: exitProviderModelDialog, } = useProviderModelDialog({
194
+ addMessage: (msg) => addItem({ type: msg.type, text: msg.content }, msg.timestamp.getTime()),
195
+ appState,
196
+ });
197
+ const openProviderModelDialog = useCallback(async () => {
198
+ try {
199
+ const activeProvider = providerManager.getActiveProvider();
200
+ if (activeProvider) {
201
+ const models = await activeProvider.getModels();
202
+ setProviderModels(models);
203
+ }
204
+ }
205
+ catch (e) {
206
+ console.error('Failed to load models:', e);
207
+ setProviderModels([]);
208
+ }
209
+ await openProviderModelDialogRaw();
210
+ }, [providerManager, openProviderModelDialogRaw]);
211
+ // Watch for model changes from config
212
+ useEffect(() => {
213
+ const checkModelChange = () => {
214
+ const configModel = config.getModel();
215
+ const activeProvider = providerManager.getActiveProvider();
216
+ // Get the actual current model from provider
217
+ const providerModel = activeProvider?.getCurrentModel?.() || configModel;
218
+ // Update UI if different from what we're showing
219
+ if (providerModel !== currentModel) {
220
+ console.debug(`[Model Update] Updating footer from ${currentModel} to ${providerModel}`);
221
+ setCurrentModel(providerModel);
222
+ }
223
+ };
224
+ // Check immediately
225
+ checkModelChange();
226
+ // Check periodically (every 500ms)
227
+ const interval = setInterval(checkModelChange, 500);
228
+ return () => clearInterval(interval);
229
+ }, [config, providerManager, currentModel]); // Include currentModel in dependencies
230
+ const toggleCorgiMode = useCallback(() => {
231
+ setCorgiMode((prev) => !prev);
232
+ }, []);
233
+ const { showDialog: isLoadProfileDialogOpen, openDialog: openLoadProfileDialog, handleSelect: handleProfileSelect, closeDialog: exitLoadProfileDialog, profiles, } = useLoadProfileDialog({
234
+ addMessage: (msg) => addItem({ type: msg.type, text: msg.content }, msg.timestamp.getTime()),
208
235
  appState,
209
236
  config,
210
- onClear: handleClearScreen,
237
+ settings,
211
238
  });
212
- // checkPaymentModeChange is now provided by SessionController
213
- // performMemoryRefresh is now provided by SessionController
214
- // Model watching is now handled by SessionController
215
- // Flash fallback handler is now set up by SessionController
216
- const { handleSlashCommand, slashCommands, pendingHistoryItems: pendingSlashCommandHistoryItems, commandContext, } = useSlashCommandProcessor(config, settings, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, setDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, providerDialog.openDialog, providerModelDialog.openDialog, performMemoryRefresh, setQuittingMessages, openPrivacyNotice, checkPaymentModeChange, showToolDescriptions);
217
- // FIX: Initialize as empty array, will be combined with pendingGeminiHistoryItems later
218
- // This prevents mutations during render
219
- let pendingHistoryItems = [...pendingSlashCommandHistoryItems];
220
- const { terminalHeight, terminalWidth, constrainHeight, availableTerminalHeight, setConstrainHeight, footerRef, registerFooterDependency, } = useLayout();
221
- const isInitialMount = useRef(true);
239
+ const performMemoryRefresh = useCallback(async () => {
240
+ addItem({
241
+ type: MessageType.INFO,
242
+ text: 'Refreshing hierarchical memory (LLXPRT.md or other context files)...',
243
+ }, Date.now());
244
+ try {
245
+ const { memoryContent, fileCount } = await loadHierarchicalLlxprtMemory(process.cwd(), config.getDebugMode(), config.getFileService(), settings.merged, config.getExtensionContextFilePaths(), settings.merged.memoryImportFormat || 'tree', // Use setting or default to 'tree'
246
+ config.getFileFilteringOptions());
247
+ config.setUserMemory(memoryContent);
248
+ config.setLlxprtMdFileCount(fileCount);
249
+ setLlxprtMdFileCount(fileCount);
250
+ addItem({
251
+ type: MessageType.INFO,
252
+ text: `Memory refreshed successfully. ${memoryContent.length > 0 ? `Loaded ${memoryContent.length} characters from ${fileCount} file(s).` : 'No memory content found.'}`,
253
+ }, Date.now());
254
+ if (config.getDebugMode()) {
255
+ console.log(`[DEBUG] Refreshed memory content in config: ${memoryContent.substring(0, 200)}...`);
256
+ }
257
+ }
258
+ catch (error) {
259
+ const errorMessage = getErrorMessage(error);
260
+ addItem({
261
+ type: MessageType.ERROR,
262
+ text: `Error refreshing memory: ${errorMessage}`,
263
+ }, Date.now());
264
+ console.error('Error refreshing memory:', error);
265
+ }
266
+ }, [config, addItem, settings.merged]);
267
+ // Removed - consolidated into single useEffect above
268
+ // Set up Flash fallback handler
269
+ useEffect(() => {
270
+ const flashFallbackHandler = async (currentModel, fallbackModel, error) => {
271
+ let message;
272
+ const contentGenConfig = config.getContentGeneratorConfig();
273
+ const authType = contentGenConfig?.authType;
274
+ if (authType === AuthType.LOGIN_WITH_GOOGLE) {
275
+ // Use actual user tier if available; otherwise, default to FREE tier behavior (safe default)
276
+ const isPaidTier = userTier === UserTierId.LEGACY || userTier === UserTierId.STANDARD;
277
+ // Check if this is a Pro quota exceeded error
278
+ if (error && isProQuotaExceededError(error)) {
279
+ if (isPaidTier) {
280
+ message = `⚡ You have reached your daily ${currentModel} quota limit.
281
+ ⚡ To continue using ${currentModel}, you can use /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey
282
+ ⚡ Or you can switch to a different model using the /model command`;
283
+ }
284
+ else {
285
+ message = `⚡ You have reached your daily ${currentModel} quota limit.
286
+ ⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
287
+ ⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
288
+ ⚡ You can switch authentication methods by typing /auth or switch to a different model using /model`;
289
+ }
290
+ }
291
+ else if (error && isGenericQuotaExceededError(error)) {
292
+ if (isPaidTier) {
293
+ message = `⚡ You have reached your daily quota limit.
294
+ ⚡ To continue, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey
295
+ ⚡ Or you can switch to a different model using the /model command`;
296
+ }
297
+ else {
298
+ message = `⚡ You have reached your daily quota limit.
299
+ ⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
300
+ ⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
301
+ ⚡ You can switch authentication methods by typing /auth or switch to a different model using /model`;
302
+ }
303
+ }
304
+ else {
305
+ if (isPaidTier) {
306
+ // Default message for other cases (like consecutive 429s)
307
+ message = `⚡ You are experiencing capacity issues with ${currentModel}.
308
+ ⚡ Possible reasons are consecutive capacity errors or reaching your daily ${currentModel} quota limit.
309
+ ⚡ To continue, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey
310
+ ⚡ Or you can switch to a different model using the /model command`;
311
+ }
312
+ else {
313
+ // Default message for other cases (like consecutive 429s)
314
+ message = `⚡ You are experiencing capacity issues with ${currentModel}.
315
+ ⚡ Possible reasons are consecutive capacity errors or reaching your daily ${currentModel} quota limit.
316
+ ⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
317
+ ⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
318
+ ⚡ You can switch authentication methods by typing /auth or switch to a different model using /model`;
319
+ }
320
+ }
321
+ // Add message to UI history
322
+ if (message) {
323
+ addItem({
324
+ type: MessageType.INFO,
325
+ text: message,
326
+ }, Date.now());
327
+ }
328
+ // Set the flag to prevent tool continuation
329
+ setModelSwitchedFromQuotaError(true);
330
+ // Set global quota error flag to prevent Flash model calls
331
+ config.setQuotaErrorOccurred(true);
332
+ }
333
+ // Don't switch models - let the user decide
334
+ // Don't set fallback mode either
335
+ const contentGenConfigForEvent = config.getContentGeneratorConfig();
336
+ const authTypeForEvent = contentGenConfigForEvent?.authType || AuthType.USE_GEMINI;
337
+ // Still log the event for telemetry
338
+ logFlashFallback(config, new FlashFallbackEvent(authTypeForEvent));
339
+ return false; // Don't continue with current prompt
340
+ };
341
+ config.setFlashFallbackHandler(flashFallbackHandler);
342
+ }, [config, addItem, userTier]);
343
+ // Terminal and UI setup
344
+ const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize();
222
345
  const { stdin, setRawMode } = useStdin();
346
+ const isInitialMount = useRef(true);
347
+ const widthFraction = 0.9;
348
+ const inputWidth = Math.max(20, Math.floor(terminalWidth * widthFraction) - 3);
349
+ const suggestionsWidth = Math.max(60, Math.floor(terminalWidth * 0.8));
350
+ // Utility callbacks
223
351
  const isValidPath = useCallback((filePath) => {
224
352
  try {
225
353
  return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
@@ -228,17 +356,51 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
228
356
  return false;
229
357
  }
230
358
  }, []);
231
- const widthFraction = 0.9;
232
- const inputWidth = Math.max(20, Math.floor(terminalWidth * widthFraction) - 3);
233
- const suggestionsWidth = Math.max(60, Math.floor(terminalWidth * 0.8));
359
+ const getPreferredEditor = useCallback(() => {
360
+ const editorType = settings.merged.preferredEditor;
361
+ const isValidEditor = isEditorAvailable(editorType);
362
+ if (!isValidEditor) {
363
+ openEditorDialog();
364
+ return;
365
+ }
366
+ return editorType;
367
+ }, [settings, openEditorDialog]);
368
+ const onAuthError = useCallback(() => {
369
+ setAuthError('reauth required');
370
+ openAuthDialog();
371
+ }, [openAuthDialog, setAuthError]);
372
+ const handleAuthTimeout = useCallback(() => {
373
+ setAuthError('Authentication timed out. Please try again.');
374
+ cancelAuthentication();
375
+ openAuthDialog();
376
+ }, [setAuthError, cancelAuthentication, openAuthDialog]);
377
+ const handlePrivacyNoticeExit = useCallback(() => {
378
+ setShowPrivacyNotice(false);
379
+ }, []);
380
+ // Core hooks and processors
381
+ const { vimEnabled: vimModeEnabled, vimMode, toggleVimEnabled, } = useVimMode();
382
+ const { handleSlashCommand, slashCommands, pendingHistoryItems: pendingSlashCommandHistoryItems, commandContext, shellConfirmationRequest, } = useSlashCommandProcessor(config, settings, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, setDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, openProviderDialog, openProviderModelDialog, openLoadProfileDialog, toggleCorgiMode, setQuittingMessages, openPrivacyNotice, toggleVimEnabled, setIsProcessing);
383
+ const { streamingState, submitQuery, initError, pendingHistoryItems: pendingGeminiHistoryItems, thought, } = useGeminiStream(config.getGeminiClient(), history, addItem, setShowHelp, config, setDebugMessage, handleSlashCommand, shellModeActive, getPreferredEditor, onAuthError, performMemoryRefresh, modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError);
384
+ // Input handling
385
+ const handleFinalSubmit = useCallback((submittedValue) => {
386
+ const trimmedValue = submittedValue.trim();
387
+ if (trimmedValue.length > 0) {
388
+ submitQuery(trimmedValue);
389
+ }
390
+ }, [submitQuery]);
234
391
  const buffer = useTextBuffer({
235
392
  initialText: '',
236
- viewport: useMemo(() => ({ height: 10, width: inputWidth }), [inputWidth]),
393
+ viewport: { height: 10, width: inputWidth },
237
394
  stdin,
238
395
  setRawMode,
239
396
  isValidPath,
240
397
  shellModeActive,
241
398
  });
399
+ const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
400
+ const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
401
+ pendingHistoryItems.push(...pendingGeminiHistoryItems);
402
+ const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState);
403
+ const showAutoAcceptIndicator = useAutoAcceptIndicator({ config });
242
404
  const handleExit = useCallback((pressedOnce, setPressedOnce, timerRef) => {
243
405
  if (pressedOnce) {
244
406
  if (timerRef.current) {
@@ -276,7 +438,10 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
276
438
  handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
277
439
  }
278
440
  }
279
- else if (key.ctrl && input === 'e' && ideContext) {
441
+ else if (key.ctrl &&
442
+ input === 'e' &&
443
+ config.getIdeMode() &&
444
+ ideContextState) {
280
445
  setShowIDEContextDetail((prev) => !prev);
281
446
  }
282
447
  else if (key.ctrl && (input === 'c' || input === 'C')) {
@@ -298,49 +463,6 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
298
463
  setLlxprtMdFileCount(config.getLlxprtMdFileCount());
299
464
  }
300
465
  }, [config]);
301
- const getPreferredEditor = useCallback(() => {
302
- const editorType = settings.merged.preferredEditor;
303
- const isValidEditor = isEditorAvailable(editorType);
304
- if (!isValidEditor) {
305
- openEditorDialog();
306
- return;
307
- }
308
- return editorType;
309
- }, [settings, openEditorDialog]);
310
- const onAuthError = useCallback(() => {
311
- appDispatch({ type: 'SET_AUTH_ERROR', payload: 'reauth required' });
312
- openAuthDialog();
313
- }, [openAuthDialog, appDispatch]);
314
- const geminiClientForStream = useMemo(() => config.getGeminiClient(), [config]);
315
- const { streamingState, submitQuery, initError, pendingHistoryItems: pendingGeminiHistoryItems, thought, } = useGeminiStream(geminiClientForStream, history, addItem, setShowHelp, config, setDebugMessage, handleSlashCommand, shellModeActive, getPreferredEditor, onAuthError, performMemoryRefresh, modelSwitchedFromQuotaError, useCallback((value) => {
316
- if (typeof value === 'function') {
317
- // Handle function form of setState
318
- const currentValue = modelSwitchedFromQuotaError;
319
- sessionDispatch({
320
- type: 'SET_MODEL_SWITCHED_FROM_QUOTA_ERROR',
321
- payload: value(currentValue),
322
- });
323
- }
324
- else {
325
- sessionDispatch({
326
- type: 'SET_MODEL_SWITCHED_FROM_QUOTA_ERROR',
327
- payload: value,
328
- });
329
- }
330
- }, [modelSwitchedFromQuotaError, sessionDispatch]));
331
- // FIX: Create a new array instead of mutating the existing one
332
- // This ensures React can properly track changes and prevents infinite loops
333
- pendingHistoryItems = [...pendingHistoryItems, ...pendingGeminiHistoryItems];
334
- const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState);
335
- const showAutoAcceptIndicator = useAutoAcceptIndicator({ config });
336
- const handleFinalSubmit = useCallback((submittedValue) => {
337
- const trimmedValue = submittedValue.trim();
338
- if (trimmedValue.length > 0) {
339
- // Clear transient warnings when user submits a message
340
- sessionDispatch({ type: 'CLEAR_TRANSIENT_WARNINGS' });
341
- submitQuery(trimmedValue);
342
- }
343
- }, [submitQuery, sessionDispatch]);
344
466
  const logger = useLogger();
345
467
  const [userMessages, setUserMessages] = useState([]);
346
468
  useEffect(() => {
@@ -372,12 +494,23 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
372
494
  };
373
495
  fetchUserMessages();
374
496
  }, [history, logger]);
375
- const isInputActive = streamingState === StreamingState.Idle && !initError;
497
+ const isInputActive = streamingState === StreamingState.Idle && !initError && !isProcessing;
498
+ const handleClearScreen = useCallback(() => {
499
+ clearItems();
500
+ clearConsoleMessagesState();
501
+ console.clear();
502
+ refreshStatic();
503
+ }, [clearItems, clearConsoleMessagesState, refreshStatic]);
504
+ const mainControlsRef = useRef(null);
376
505
  const pendingHistoryItemRef = useRef(null);
377
- // Register dependencies that affect footer height with LayoutManager
378
506
  useEffect(() => {
379
- registerFooterDependency();
380
- }, [consoleMessages, showErrorDetails, registerFooterDependency]);
507
+ if (mainControlsRef.current) {
508
+ const fullFooterMeasurement = measureElement(mainControlsRef.current);
509
+ setFooterHeight(fullFooterMeasurement.height);
510
+ }
511
+ }, [terminalHeight, consoleMessages, showErrorDetails]);
512
+ const staticExtraHeight = /* margins and padding */ 3;
513
+ const availableTerminalHeight = useMemo(() => terminalHeight - footerHeight - staticExtraHeight, [terminalHeight, footerHeight]);
381
514
  useEffect(() => {
382
515
  // skip refreshing Static during first mount
383
516
  if (isInitialMount.current) {
@@ -414,26 +547,30 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
414
547
  return getAllLlxprtMdFilenames();
415
548
  }, [settings.merged.contextFileName]);
416
549
  const initialPrompt = useMemo(() => config.getQuestion(), [config]);
417
- const geminiClient = useMemo(() => config.getGeminiClient(), [config]);
550
+ const geminiClient = config.getGeminiClient();
418
551
  useEffect(() => {
419
552
  if (initialPrompt &&
420
553
  !initialPromptSubmitted.current &&
421
- !authIsAuthenticating &&
554
+ !isAuthenticating &&
422
555
  !isAuthDialogOpen &&
423
556
  !isThemeDialogOpen &&
424
557
  !isEditorDialogOpen &&
558
+ !isProviderDialogOpen &&
559
+ !isProviderModelDialogOpen &&
425
560
  !showPrivacyNotice &&
426
- geminiClient) {
561
+ geminiClient?.isInitialized?.()) {
427
562
  submitQuery(initialPrompt);
428
563
  initialPromptSubmitted.current = true;
429
564
  }
430
565
  }, [
431
566
  initialPrompt,
432
567
  submitQuery,
433
- authIsAuthenticating,
568
+ isAuthenticating,
434
569
  isAuthDialogOpen,
435
570
  isThemeDialogOpen,
436
571
  isEditorDialogOpen,
572
+ isProviderDialogOpen,
573
+ isProviderModelDialogOpen,
437
574
  showPrivacyNotice,
438
575
  geminiClient,
439
576
  ]);
@@ -445,32 +582,25 @@ const AppInner = ({ config, settings, startupWarnings = [], version, setIsAuthen
445
582
  // Arbitrary threshold to ensure that items in the static area are large
446
583
  // enough but not too large to make the terminal hard to use.
447
584
  const staticAreaMaxItemHeight = Math.max(terminalHeight * 4, 100);
448
- // Show loading state if geminiClient is not initialized
449
- if (!geminiClientForStream) {
450
- console.log('App: geminiClientForStream is not initialized yet');
451
- return _jsx(Text, { children: "Initializing Gemini client..." });
452
- }
453
- return (_jsx(StreamingContext.Provider, { value: streamingState, children: _jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [updateMessage && _jsx(UpdateNotification, { message: updateMessage }), _jsx(Static, { items: [
585
+ const placeholder = vimModeEnabled
586
+ ? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
587
+ : ' Type your message or @path/to/file';
588
+ return (_jsx(StreamingContext.Provider, { value: streamingState, children: _jsxs(Box, { flexDirection: "column", width: "90%", children: [_jsx(Static, { items: [
454
589
  _jsxs(Box, { flexDirection: "column", children: [!settings.merged.hideBanner && (_jsx(Header, { terminalWidth: terminalWidth, version: version, nightly: nightly })), !settings.merged.hideTips && _jsx(Tips, { config: config })] }, "header"),
455
590
  ...history.map((h) => (_jsx(HistoryItemDisplay, { terminalWidth: mainAreaWidth, availableTerminalHeight: staticAreaMaxItemHeight, item: h, isPending: false, config: config }, h.id))),
456
591
  ], children: (item) => item }, staticKey), _jsx(OverflowProvider, { children: _jsxs(Box, { ref: pendingHistoryItemRef, flexDirection: "column", children: [pendingHistoryItems.map((item, i) => (_jsx(HistoryItemDisplay, { availableTerminalHeight: constrainHeight ? availableTerminalHeight : undefined, terminalWidth: mainAreaWidth,
457
592
  // TODO(taehykim): It seems like references to ids aren't necessary in
458
593
  // HistoryItemDisplay. Refactor later. Use a fake id for now.
459
- item: { ...item, id: 0 }, isPending: true, config: config, isFocused: !isEditorDialogOpen }, i))), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) }), showHelp && _jsx(Help, { commands: slashCommands }), _jsxs(Box, { flexDirection: "column", ref: footerRef, children: [(allStartupWarnings.length > 0 || transientWarnings.length > 0) && (_jsxs(Box, { borderStyle: "round", borderColor: Colors.AccentYellow, paddingX: 1, marginY: 1, flexDirection: "column", children: [allStartupWarnings.map((warning, index) => (_jsx(Text, { color: Colors.AccentYellow, children: warning }, index))), transientWarnings.map((warning, index) => (_jsx(Text, { color: Colors.AccentYellow, children: warning }, index)))] })), isThemeDialogOpen ? (_jsxs(Box, { flexDirection: "column", children: [appState.errors.theme && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentRed, children: appState.errors.theme }) })), _jsx(ThemeDialog, { onSelect: handleThemeSelect, onHighlight: handleThemeHighlight, settings: settings, availableTerminalHeight: constrainHeight
460
- ? terminalHeight - 3 // margins and padding
461
- : undefined, terminalWidth: mainAreaWidth })] })) : authIsAuthenticating ? (_jsxs(_Fragment, { children: [_jsx(AuthInProgress, { onTimeout: onAuthTimeout }), showErrorDetails && (_jsx(OverflowProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailedMessagesDisplay, { messages: filteredConsoleMessages, maxHeight: constrainHeight ? debugConsoleMaxHeight : undefined, width: inputWidth }), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) }))] })) : isAuthDialogOpen ? (_jsx(Box, { flexDirection: "column", children: _jsx(AuthDialog, { onSelect: handleAuthSelect, settings: settings, initialErrorMessage: appState.errors.auth }) })) : isEditorDialogOpen ? (_jsxs(Box, { flexDirection: "column", children: [appState.errors.editor && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentRed, children: appState.errors.editor }) })), _jsx(EditorSettingsDialog, { onSelect: handleEditorSelect, settings: settings, onExit: exitEditorDialog })] })) : providerModelDialog.showDialog ? (_jsx(Box, { flexDirection: "column", children: _jsx(ProviderModelDialog, { models: providerModelDialog.models, currentModel: providerModelDialog.currentModel, onSelect: providerModelDialog.handleSelect, onClose: providerModelDialog.closeDialog }) })) : providerDialog.showDialog ? (_jsx(Box, { flexDirection: "column", children: _jsx(ProviderDialog, { providers: providerDialog.providers, currentProvider: providerDialog.currentProvider, onSelect: providerDialog.handleSelect, onClose: providerDialog.closeDialog }) })) : showPrivacyNotice ? (_jsx(PrivacyNotice, { onExit: closePrivacyNotice, config: config })) : (_jsxs(_Fragment, { children: [_jsx(LoadingIndicator, { thought: streamingState === StreamingState.WaitingForConfirmation ||
594
+ item: { ...item, id: 0 }, isPending: true, config: config, isFocused: !isEditorDialogOpen }, i))), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) }), showHelp && _jsx(Help, { commands: slashCommands }), _jsxs(Box, { flexDirection: "column", ref: mainControlsRef, children: [updateInfo && _jsx(UpdateNotification, { message: updateInfo.message }), startupWarnings.length > 0 && (_jsx(Box, { borderStyle: "round", borderColor: Colors.AccentYellow, paddingX: 1, marginY: 1, flexDirection: "column", children: startupWarnings.map((warning, index) => (_jsx(Text, { color: Colors.AccentYellow, children: warning }, index))) })), shellConfirmationRequest ? (_jsx(ShellConfirmationDialog, { request: shellConfirmationRequest })) : isThemeDialogOpen ? (_jsxs(Box, { flexDirection: "column", children: [_themeError && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentRed, children: _themeError }) })), _jsx(ThemeDialog, { onSelect: handleThemeSelect, onHighlight: handleThemeHighlight, settings: settings, availableTerminalHeight: constrainHeight
595
+ ? terminalHeight - staticExtraHeight
596
+ : undefined, terminalWidth: mainAreaWidth })] })) : isAuthenticating ? (_jsxs(_Fragment, { children: [_jsx(AuthInProgress, { onTimeout: handleAuthTimeout }), showErrorDetails && (_jsx(OverflowProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailedMessagesDisplay, { messages: filteredConsoleMessages, maxHeight: constrainHeight ? debugConsoleMaxHeight : undefined, width: inputWidth }), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) }))] })) : isAuthDialogOpen ? (_jsx(Box, { flexDirection: "column", children: _jsx(AuthDialog, { onSelect: handleAuthSelect, settings: settings, initialErrorMessage: authError }) })) : isEditorDialogOpen ? (_jsxs(Box, { flexDirection: "column", children: [_editorError && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentRed, children: _editorError }) })), _jsx(EditorSettingsDialog, { onSelect: handleEditorSelect, settings: settings, onExit: exitEditorDialog })] })) : isProviderDialogOpen ? (_jsx(Box, { flexDirection: "column", children: _jsx(ProviderDialog, { providers: providerManager.listProviders(), currentProvider: providerManager.getActiveProviderName(), onSelect: handleProviderSelect, onClose: exitProviderDialog }) })) : isProviderModelDialogOpen ? (_jsx(Box, { flexDirection: "column", children: _jsx(ProviderModelDialog, { models: providerModels, currentModel: currentModel, onSelect: handleProviderModelChange, onClose: exitProviderModelDialog }) })) : isLoadProfileDialogOpen ? (_jsx(Box, { flexDirection: "column", children: _jsx(LoadProfileDialog, { profiles: profiles, onSelect: handleProfileSelect, onClose: exitLoadProfileDialog }) })) : showPrivacyNotice ? (_jsx(PrivacyNotice, { onExit: handlePrivacyNoticeExit, config: config })) : (_jsxs(_Fragment, { children: [_jsx(LoadingIndicator, { thought: streamingState === StreamingState.WaitingForConfirmation ||
462
597
  config.getAccessibility()?.disableLoadingPhrases
463
598
  ? undefined
464
599
  : thought, currentLoadingPhrase: config.getAccessibility()?.disableLoadingPhrases
465
600
  ? undefined
466
- : currentLoadingPhrase, elapsedTime: elapsedTime }), _jsxs(Box, { marginTop: 1, display: "flex", justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [process.env.GEMINI_SYSTEM_MD && (_jsx(Text, { color: Colors.AccentRed, children: "|\u2310\u25A0_\u25A0| " })), ctrlCPressedOnce ? (_jsx(Text, { color: Colors.AccentYellow, children: "Press Ctrl+C again to exit." })) : ctrlDPressedOnce ? (_jsx(Text, { color: Colors.AccentYellow, children: "Press Ctrl+D again to exit." })) : (_jsx(ContextSummaryDisplay, { activeFile: _activeFile, openFiles: openFiles, llxprtMdFileCount: llxprtMdFileCount, contextFileNames: contextFileNames, mcpServers: config.getMcpServers(), blockedMcpServers: config.getBlockedMcpServers(), showToolDescriptions: showToolDescriptions }))] }), _jsxs(Box, { children: [showAutoAcceptIndicator !== ApprovalMode.DEFAULT &&
467
- !shellModeActive && (_jsx(AutoAcceptIndicator, { approvalMode: showAutoAcceptIndicator })), shellModeActive && _jsx(ShellModeIndicator, {})] })] }), showIDEContextDetail && (_jsx(IDEContextDetailDisplay, { openFiles: openFiles })), showErrorDetails && (_jsx(OverflowProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailedMessagesDisplay, { messages: filteredConsoleMessages, maxHeight: constrainHeight ? debugConsoleMaxHeight : undefined, width: inputWidth }), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) })), isInputActive && (_jsx(InputPrompt, { buffer: buffer, inputWidth: inputWidth, suggestionsWidth: suggestionsWidth, onSubmit: handleFinalSubmit, userMessages: userMessages, onClearScreen: handleClearScreen, config: config, slashCommands: slashCommands, commandContext: commandContext, shellModeActive: shellModeActive, setShellModeActive: setShellModeActive, focus: isFocused }))] })), initError && streamingState !== StreamingState.Responding && (_jsx(Box, { borderStyle: "round", borderColor: Colors.AccentRed, paddingX: 1, marginBottom: 1, children: history.find((item) => item.type === 'error' && item.text?.includes(initError))?.text ? (_jsx(Text, { color: Colors.AccentRed, children: history.find((item) => item.type === 'error' && item.text?.includes(initError))?.text })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { color: Colors.AccentRed, children: ["Initialization Error: ", initError] }), _jsxs(Text, { color: Colors.AccentRed, children: [' ', "Please check API key and configuration."] })] })) })), _jsx(Footer, { model: currentModel, targetDir: config.getTargetDir(), debugMode: config.getDebugMode(), branchName: branchName, debugMessage: debugMessage, errorCount: errorCount, showErrorDetails: showErrorDetails, showMemoryUsage: config.getDebugMode() || config.getShowMemoryUsage(), promptTokenCount: sessionStats.lastPromptTokenCount, isPaidMode: isPaidMode, nightly: nightly })] })] }) }));
468
- };
469
- // Intermediate component to pass isAuthenticating to SessionController
470
- const AppWithAuth = (props) => {
471
- const [isAuthenticating, setIsAuthenticating] = useState(false);
472
- return (_jsx(SessionController, { config: props.config, isAuthenticating: isAuthenticating, children: _jsx(AppInner, { ...props, isAuthenticating: isAuthenticating, setIsAuthenticating: setIsAuthenticating }) }));
601
+ : currentLoadingPhrase, elapsedTime: elapsedTime }), _jsxs(Box, { marginTop: 1, display: "flex", justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [process.env.GEMINI_SYSTEM_MD && (_jsx(Text, { color: Colors.AccentRed, children: "|\u2310\u25A0_\u25A0| " })), ctrlCPressedOnce ? (_jsx(Text, { color: Colors.AccentYellow, children: "Press Ctrl+C again to exit." })) : ctrlDPressedOnce ? (_jsx(Text, { color: Colors.AccentYellow, children: "Press Ctrl+D again to exit." })) : (_jsx(ContextSummaryDisplay, { ideContext: ideContextState, llxprtMdFileCount: llxprtMdFileCount, contextFileNames: contextFileNames, mcpServers: config.getMcpServers(), blockedMcpServers: config.getBlockedMcpServers(), showToolDescriptions: showToolDescriptions }))] }), _jsxs(Box, { children: [showAutoAcceptIndicator !== ApprovalMode.DEFAULT &&
602
+ !shellModeActive && (_jsx(AutoAcceptIndicator, { approvalMode: showAutoAcceptIndicator })), shellModeActive && _jsx(ShellModeIndicator, {})] })] }), showIDEContextDetail && (_jsx(IDEContextDetailDisplay, { ideContext: ideContextState, detectedIdeDisplay: config
603
+ .getIdeClient()
604
+ ?.getDetectedIdeDisplayName() })), showErrorDetails && (_jsx(OverflowProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailedMessagesDisplay, { messages: filteredConsoleMessages, maxHeight: constrainHeight ? debugConsoleMaxHeight : undefined, width: inputWidth }), _jsx(ShowMoreLines, { constrainHeight: constrainHeight })] }) })), isInputActive && (_jsx(InputPrompt, { buffer: buffer, inputWidth: inputWidth, suggestionsWidth: suggestionsWidth, onSubmit: handleFinalSubmit, userMessages: userMessages, onClearScreen: handleClearScreen, config: config, slashCommands: slashCommands, commandContext: commandContext, shellModeActive: shellModeActive, setShellModeActive: setShellModeActive, focus: isFocused, vimHandleInput: vimHandleInput, placeholder: placeholder }))] })), initError && streamingState !== StreamingState.Responding && (_jsx(Box, { borderStyle: "round", borderColor: Colors.AccentRed, paddingX: 1, marginBottom: 1, children: history.find((item) => item.type === 'error' && item.text?.includes(initError))?.text ? (_jsx(Text, { color: Colors.AccentRed, children: history.find((item) => item.type === 'error' && item.text?.includes(initError))?.text })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { color: Colors.AccentRed, children: ["Initialization Error: ", initError] }), _jsxs(Text, { color: Colors.AccentRed, children: [' ', "Please check API key and configuration."] })] })) })), _jsx(Footer, { model: currentModel, targetDir: config.getTargetDir(), debugMode: config.getDebugMode(), branchName: branchName, debugMessage: debugMessage, errorCount: errorCount, showErrorDetails: showErrorDetails, showMemoryUsage: config.getDebugMode() || config.getShowMemoryUsage(), promptTokenCount: sessionStats.lastPromptTokenCount, nightly: nightly, vimMode: vimModeEnabled ? vimMode : undefined, contextLimit: config.getEphemeralSetting('context-limit') })] })] }) }));
473
605
  };
474
- // Main App component that provides the UIStateShell wrapper
475
- const App = (props) => (_jsx(UIStateShell, { children: _jsx(AppWithAuth, { ...props }) }));
476
606
  //# sourceMappingURL=App.js.map