@google/gemini-cli 0.25.0-nightly.20260112.15891721a → 0.25.0-preview.0

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 (255) hide show
  1. package/dist/package.json +2 -2
  2. package/dist/src/commands/extensions/examples/mcp-server/example.d.ts +6 -0
  3. package/dist/src/commands/extensions/examples/mcp-server/example.js +22 -36
  4. package/dist/src/commands/extensions/examples/mcp-server/example.js.map +1 -0
  5. package/dist/src/commands/extensions/examples/mcp-server/example.test.d.ts +6 -0
  6. package/dist/src/commands/extensions/examples/mcp-server/example.test.js +111 -0
  7. package/dist/src/commands/extensions/examples/mcp-server/example.test.js.map +1 -0
  8. package/dist/src/commands/extensions/examples/mcp-server/example.test.ts +135 -0
  9. package/dist/src/commands/extensions/examples/mcp-server/example.ts +60 -0
  10. package/dist/src/commands/extensions/examples/mcp-server/gemini-extension.json +1 -1
  11. package/dist/src/commands/extensions/examples/mcp-server/package.json +7 -0
  12. package/dist/src/commands/extensions/examples/mcp-server/tsconfig.json +13 -0
  13. package/dist/src/commands/extensions/utils.d.ts +1 -1
  14. package/dist/src/commands/skills/disable.js +1 -1
  15. package/dist/src/commands/skills/disable.js.map +1 -1
  16. package/dist/src/commands/skills/disable.test.js +1 -1
  17. package/dist/src/commands/skills/disable.test.js.map +1 -1
  18. package/dist/src/commands/skills/install.d.ts +14 -0
  19. package/dist/src/commands/skills/install.js +61 -0
  20. package/dist/src/commands/skills/install.js.map +1 -0
  21. package/dist/src/commands/skills/install.test.d.ts +6 -0
  22. package/dist/src/commands/skills/install.test.js +57 -0
  23. package/dist/src/commands/skills/install.test.js.map +1 -0
  24. package/dist/src/commands/skills/list.js +1 -1
  25. package/dist/src/commands/skills/list.js.map +1 -1
  26. package/dist/src/commands/skills/list.test.js +1 -1
  27. package/dist/src/commands/skills/list.test.js.map +1 -1
  28. package/dist/src/commands/skills/uninstall.d.ts +13 -0
  29. package/dist/src/commands/skills/uninstall.js +56 -0
  30. package/dist/src/commands/skills/uninstall.js.map +1 -0
  31. package/dist/src/commands/skills/uninstall.test.d.ts +6 -0
  32. package/dist/src/commands/skills/uninstall.test.js +61 -0
  33. package/dist/src/commands/skills/uninstall.test.js.map +1 -0
  34. package/dist/src/commands/skills.js +4 -0
  35. package/dist/src/commands/skills.js.map +1 -1
  36. package/dist/src/config/config.js +5 -0
  37. package/dist/src/config/config.js.map +1 -1
  38. package/dist/src/config/extension-manager-agents.test.d.ts +6 -0
  39. package/dist/src/config/extension-manager-agents.test.js +115 -0
  40. package/dist/src/config/extension-manager-agents.test.js.map +1 -0
  41. package/dist/src/config/extension-manager-scope.test.js +11 -0
  42. package/dist/src/config/extension-manager-scope.test.js.map +1 -1
  43. package/dist/src/config/extension-manager-skills.test.js +4 -0
  44. package/dist/src/config/extension-manager-skills.test.js.map +1 -1
  45. package/dist/src/config/extension-manager.js +7 -1
  46. package/dist/src/config/extension-manager.js.map +1 -1
  47. package/dist/src/config/extension.test.js +10 -1
  48. package/dist/src/config/extension.test.js.map +1 -1
  49. package/dist/src/config/keyBindings.d.ts +14 -2
  50. package/dist/src/config/keyBindings.js +85 -10
  51. package/dist/src/config/keyBindings.js.map +1 -1
  52. package/dist/src/config/settings.js +8 -9
  53. package/dist/src/config/settings.js.map +1 -1
  54. package/dist/src/config/settingsSchema.d.ts +66 -2
  55. package/dist/src/config/settingsSchema.js +99 -2
  56. package/dist/src/config/settingsSchema.js.map +1 -1
  57. package/dist/src/gemini.js +12 -6
  58. package/dist/src/gemini.js.map +1 -1
  59. package/dist/src/generated/git-commit.d.ts +2 -2
  60. package/dist/src/generated/git-commit.js +2 -2
  61. package/dist/src/generated/git-commit.js.map +1 -1
  62. package/dist/src/services/BuiltinCommandLoader.js +18 -1
  63. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  64. package/dist/src/test-utils/render.js +1 -0
  65. package/dist/src/test-utils/render.js.map +1 -1
  66. package/dist/src/ui/AppContainer.js +103 -40
  67. package/dist/src/ui/AppContainer.js.map +1 -1
  68. package/dist/src/ui/AppContainer.test.js +214 -41
  69. package/dist/src/ui/AppContainer.test.js.map +1 -1
  70. package/dist/src/ui/auth/AuthDialog.d.ts +4 -1
  71. package/dist/src/ui/auth/AuthDialog.js +8 -2
  72. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  73. package/dist/src/ui/auth/AuthDialog.test.js +17 -0
  74. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  75. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.d.ts +10 -0
  76. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.js +27 -0
  77. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.js.map +1 -0
  78. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.test.d.ts +6 -0
  79. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.test.js +68 -0
  80. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.test.js.map +1 -0
  81. package/dist/src/ui/commands/aboutCommand.js +1 -1
  82. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  83. package/dist/src/ui/commands/aboutCommand.test.js +4 -4
  84. package/dist/src/ui/commands/aboutCommand.test.js.map +1 -1
  85. package/dist/src/ui/commands/agentsCommand.js +5 -1
  86. package/dist/src/ui/commands/agentsCommand.js.map +1 -1
  87. package/dist/src/ui/commands/agentsCommand.test.js +1 -1
  88. package/dist/src/ui/commands/agentsCommand.test.js.map +1 -1
  89. package/dist/src/ui/commands/chatCommand.js +1 -1
  90. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  91. package/dist/src/ui/commands/chatCommand.test.js +1 -1
  92. package/dist/src/ui/commands/chatCommand.test.js.map +1 -1
  93. package/dist/src/ui/commands/directoryCommand.js +9 -9
  94. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  95. package/dist/src/ui/commands/directoryCommand.test.js +9 -9
  96. package/dist/src/ui/commands/directoryCommand.test.js.map +1 -1
  97. package/dist/src/ui/commands/extensionsCommand.js +36 -36
  98. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  99. package/dist/src/ui/commands/extensionsCommand.test.js +36 -36
  100. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  101. package/dist/src/ui/commands/helpCommand.js +1 -1
  102. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  103. package/dist/src/ui/commands/helpCommand.test.js +1 -1
  104. package/dist/src/ui/commands/helpCommand.test.js.map +1 -1
  105. package/dist/src/ui/commands/hooksCommand.js +1 -1
  106. package/dist/src/ui/commands/hooksCommand.js.map +1 -1
  107. package/dist/src/ui/commands/hooksCommand.test.js +4 -4
  108. package/dist/src/ui/commands/hooksCommand.test.js.map +1 -1
  109. package/dist/src/ui/commands/mcpCommand.js +6 -6
  110. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  111. package/dist/src/ui/commands/mcpCommand.test.js +3 -3
  112. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
  113. package/dist/src/ui/commands/memoryCommand.js +18 -43
  114. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  115. package/dist/src/ui/commands/memoryCommand.test.js +85 -15
  116. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  117. package/dist/src/ui/commands/skillsCommand.js +23 -8
  118. package/dist/src/ui/commands/skillsCommand.js.map +1 -1
  119. package/dist/src/ui/commands/skillsCommand.test.js +33 -12
  120. package/dist/src/ui/commands/skillsCommand.test.js.map +1 -1
  121. package/dist/src/ui/commands/statsCommand.js +4 -4
  122. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  123. package/dist/src/ui/commands/statsCommand.test.js +4 -4
  124. package/dist/src/ui/commands/statsCommand.test.js.map +1 -1
  125. package/dist/src/ui/commands/toolsCommand.js +2 -2
  126. package/dist/src/ui/commands/toolsCommand.js.map +1 -1
  127. package/dist/src/ui/commands/toolsCommand.test.js +2 -2
  128. package/dist/src/ui/commands/toolsCommand.test.js.map +1 -1
  129. package/dist/src/ui/components/DialogManager.js +1 -1
  130. package/dist/src/ui/components/DialogManager.js.map +1 -1
  131. package/dist/src/ui/components/Help.js +1 -1
  132. package/dist/src/ui/components/Help.js.map +1 -1
  133. package/dist/src/ui/components/InputPrompt.js +18 -7
  134. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  135. package/dist/src/ui/components/InputPrompt.test.js +64 -1
  136. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  137. package/dist/src/ui/components/MainContent.js +6 -9
  138. package/dist/src/ui/components/MainContent.js.map +1 -1
  139. package/dist/src/ui/components/MainContent.test.js +19 -9
  140. package/dist/src/ui/components/MainContent.test.js.map +1 -1
  141. package/dist/src/ui/components/ModelDialog.js +1 -1
  142. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  143. package/dist/src/ui/components/MultiFolderTrustDialog.d.ts +2 -2
  144. package/dist/src/ui/components/MultiFolderTrustDialog.js +1 -1
  145. package/dist/src/ui/components/MultiFolderTrustDialog.js.map +1 -1
  146. package/dist/src/ui/components/MultiFolderTrustDialog.test.js +2 -2
  147. package/dist/src/ui/components/MultiFolderTrustDialog.test.js.map +1 -1
  148. package/dist/src/ui/components/SettingsDialog.test.js +5 -2
  149. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  150. package/dist/src/ui/components/StickyHeader.d.ts +2 -0
  151. package/dist/src/ui/components/StickyHeader.js +1 -1
  152. package/dist/src/ui/components/StickyHeader.js.map +1 -1
  153. package/dist/src/ui/components/messages/DiffRenderer.js +5 -13
  154. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  155. package/dist/src/ui/components/messages/DiffRenderer.test.js +26 -17
  156. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
  157. package/dist/src/ui/components/messages/ShellToolMessage.js +8 -5
  158. package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -1
  159. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +1 -5
  160. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  161. package/dist/src/ui/components/messages/ToolMessage.js +6 -2
  162. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  163. package/dist/src/ui/components/messages/ToolResultDisplay.js +22 -16
  164. package/dist/src/ui/components/messages/ToolResultDisplay.js.map +1 -1
  165. package/dist/src/ui/components/messages/ToolResultDisplay.test.js +36 -18
  166. package/dist/src/ui/components/messages/ToolResultDisplay.test.js.map +1 -1
  167. package/dist/src/ui/components/messages/ToolStickyHeaderRegression.test.d.ts +6 -0
  168. package/dist/src/ui/components/messages/ToolStickyHeaderRegression.test.js +134 -0
  169. package/dist/src/ui/components/messages/ToolStickyHeaderRegression.test.js.map +1 -0
  170. package/dist/src/ui/components/shared/MaxSizedBox.d.ts +2 -38
  171. package/dist/src/ui/components/shared/MaxSizedBox.js +34 -419
  172. package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
  173. package/dist/src/ui/components/shared/MaxSizedBox.test.js +48 -133
  174. package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
  175. package/dist/src/ui/components/shared/text-buffer.d.ts +4 -4
  176. package/dist/src/ui/components/shared/text-buffer.js +35 -38
  177. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  178. package/dist/src/ui/components/views/AgentsStatus.js +3 -3
  179. package/dist/src/ui/components/views/AgentsStatus.js.map +1 -1
  180. package/dist/src/ui/constants/tips.js +1 -1
  181. package/dist/src/ui/constants/tips.js.map +1 -1
  182. package/dist/src/ui/constants.d.ts +1 -0
  183. package/dist/src/ui/constants.js +1 -0
  184. package/dist/src/ui/constants.js.map +1 -1
  185. package/dist/src/ui/contexts/KeypressContext.js +28 -0
  186. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  187. package/dist/src/ui/contexts/KeypressContext.test.js +31 -1
  188. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  189. package/dist/src/ui/contexts/UIActionsContext.d.ts +3 -0
  190. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  191. package/dist/src/ui/hooks/useAutoAcceptIndicator.d.ts +2 -1
  192. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +5 -4
  193. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  194. package/dist/src/ui/hooks/useCommandCompletion.d.ts +1 -0
  195. package/dist/src/ui/hooks/useCommandCompletion.js +1 -0
  196. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  197. package/dist/src/ui/hooks/useCommandCompletion.test.js +7 -2
  198. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  199. package/dist/src/ui/hooks/useExtensionUpdates.test.js +11 -2
  200. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  201. package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -1
  202. package/dist/src/ui/hooks/useGeminiStream.js +34 -14
  203. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  204. package/dist/src/ui/hooks/useGeminiStream.test.js +87 -10
  205. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  206. package/dist/src/ui/hooks/useHistoryManager.d.ts +1 -1
  207. package/dist/src/ui/hooks/useHistoryManager.js +1 -1
  208. package/dist/src/ui/hooks/useHistoryManager.js.map +1 -1
  209. package/dist/src/ui/hooks/useHistoryManager.test.js +16 -0
  210. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  211. package/dist/src/ui/hooks/useIncludeDirsTrust.js +2 -2
  212. package/dist/src/ui/hooks/useIncludeDirsTrust.js.map +1 -1
  213. package/dist/src/ui/hooks/useIncludeDirsTrust.test.js +2 -2
  214. package/dist/src/ui/hooks/useIncludeDirsTrust.test.js.map +1 -1
  215. package/dist/src/ui/hooks/useLoadingIndicator.d.ts +2 -1
  216. package/dist/src/ui/hooks/useLoadingIndicator.js +7 -3
  217. package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
  218. package/dist/src/ui/hooks/useLoadingIndicator.test.js +14 -4
  219. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  220. package/dist/src/ui/hooks/usePhraseCycler.d.ts +1 -1
  221. package/dist/src/ui/hooks/usePhraseCycler.js +2 -2
  222. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  223. package/dist/src/ui/keyMatchers.test.js +79 -53
  224. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  225. package/dist/src/ui/types.d.ts +2 -1
  226. package/dist/src/ui/types.js +2 -0
  227. package/dist/src/ui/types.js.map +1 -1
  228. package/dist/src/ui/utils/CodeColorizer.js +2 -2
  229. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  230. package/dist/src/ui/utils/commandUtils.js +1 -3
  231. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  232. package/dist/src/ui/utils/commandUtils.test.js +15 -2
  233. package/dist/src/ui/utils/commandUtils.test.js.map +1 -1
  234. package/dist/src/utils/sessionCleanup.js +12 -0
  235. package/dist/src/utils/sessionCleanup.js.map +1 -1
  236. package/dist/src/utils/skillUtils.d.ts +13 -0
  237. package/dist/src/utils/skillUtils.js +93 -0
  238. package/dist/src/utils/skillUtils.js.map +1 -1
  239. package/dist/src/utils/skillUtils.test.d.ts +6 -0
  240. package/dist/src/utils/skillUtils.test.js +57 -0
  241. package/dist/src/utils/skillUtils.test.js.map +1 -0
  242. package/dist/src/utils/windowTitle.d.ts +13 -4
  243. package/dist/src/utils/windowTitle.js +65 -7
  244. package/dist/src/utils/windowTitle.js.map +1 -1
  245. package/dist/src/utils/windowTitle.test.js +183 -40
  246. package/dist/src/utils/windowTitle.test.js.map +1 -1
  247. package/dist/tsconfig.tsbuildinfo +1 -1
  248. package/package.json +3 -3
  249. package/dist/google-gemini-cli-0.25.0-nightly.20260107.59a18e710.tgz +0 -0
  250. package/dist/src/commands/extensions/examples/hooks/gemini-extension.json +0 -4
  251. package/dist/src/commands/extensions/examples/hooks/hooks/hooks.json +0 -14
  252. package/dist/src/commands/extensions/examples/hooks/scripts/on-start.js +0 -8
  253. package/dist/src/commands/extensions/examples/mcp-server/README.md +0 -35
  254. package/dist/src/commands/extensions/examples/skills/gemini-extension.json +0 -4
  255. package/dist/src/commands/extensions/examples/skills/skills/greeter/SKILL.md +0 -7
@@ -174,16 +174,10 @@ describe('AppContainer State Management', () => {
174
174
  const mockedUseHookDisplayState = useHookDisplayState;
175
175
  beforeEach(() => {
176
176
  vi.clearAllMocks();
177
+ mockIdeClient.getInstance.mockReturnValue(new Promise(() => { }));
177
178
  // Initialize mock stdout for terminal title tests
178
179
  mocks.mockStdout.write.mockClear();
179
- // Mock computeWindowTitle function to centralize title logic testing
180
- vi.mock('../utils/windowTitle.js', async () => ({
181
- computeWindowTitle: vi.fn((folderName) =>
182
- // Default behavior: return "Gemini - {folderName}" unless CLI_TITLE is set
183
- process.env['CLI_TITLE'] || `Gemini - ${folderName}`),
184
- }));
185
180
  capturedUIState = null;
186
- capturedUIActions = null;
187
181
  // **Provide a default return value for EVERY mocked hook.**
188
182
  mockedUseQuotaAndFallback.mockReturnValue({
189
183
  proQuotaRequest: null,
@@ -324,6 +318,7 @@ describe('AppContainer State Management', () => {
324
318
  });
325
319
  afterEach(() => {
326
320
  cleanup();
321
+ vi.restoreAllMocks();
327
322
  });
328
323
  describe('Basic Rendering', () => {
329
324
  it('renders without crashing with minimal props', async () => {
@@ -839,7 +834,7 @@ describe('AppContainer State Management', () => {
839
834
  const { stdout } = useStdout();
840
835
  expect(stdout).toBe(mocks.mockStdout);
841
836
  });
842
- it('should not update terminal title when showStatusInTitle is false', () => {
837
+ it('should update terminal title with Working… when showStatusInTitle is false', () => {
843
838
  // Arrange: Set up mock settings with showStatusInTitle disabled
844
839
  const mockSettingsWithShowStatusFalse = {
845
840
  ...mockSettings,
@@ -852,13 +847,55 @@ describe('AppContainer State Management', () => {
852
847
  },
853
848
  },
854
849
  };
850
+ // Mock the streaming state as Active
851
+ mockedUseGeminiStream.mockReturnValue({
852
+ streamingState: 'responding',
853
+ submitQuery: vi.fn(),
854
+ initError: null,
855
+ pendingHistoryItems: [],
856
+ thought: { subject: 'Some thought' },
857
+ cancelOngoingRequest: vi.fn(),
858
+ });
855
859
  // Act: Render the container
856
860
  const { unmount } = renderAppContainer({
857
861
  settings: mockSettingsWithShowStatusFalse,
858
862
  });
859
- // Assert: Check that no title-related writes occurred
860
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
861
- expect(titleWrites).toHaveLength(0);
863
+ // Assert: Check that title was updated with "Working…"
864
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
865
+ expect(titleWrites).toHaveLength(1);
866
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${'✦ Working… (workspace)'.padEnd(80, ' ')}\x07`);
867
+ unmount();
868
+ });
869
+ it('should use legacy terminal title when dynamicWindowTitle is false', () => {
870
+ // Arrange: Set up mock settings with dynamicWindowTitle disabled
871
+ const mockSettingsWithDynamicTitleFalse = {
872
+ ...mockSettings,
873
+ merged: {
874
+ ...mockSettings.merged,
875
+ ui: {
876
+ ...mockSettings.merged.ui,
877
+ dynamicWindowTitle: false,
878
+ hideWindowTitle: false,
879
+ },
880
+ },
881
+ };
882
+ // Mock the streaming state
883
+ mockedUseGeminiStream.mockReturnValue({
884
+ streamingState: 'responding',
885
+ submitQuery: vi.fn(),
886
+ initError: null,
887
+ pendingHistoryItems: [],
888
+ thought: { subject: 'Some thought' },
889
+ cancelOngoingRequest: vi.fn(),
890
+ });
891
+ // Act: Render the container
892
+ const { unmount } = renderAppContainer({
893
+ settings: mockSettingsWithDynamicTitleFalse,
894
+ });
895
+ // Assert: Check that legacy title was used
896
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
897
+ expect(titleWrites).toHaveLength(1);
898
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${'Gemini CLI (workspace)'.padEnd(80, ' ')}\x07`);
862
899
  unmount();
863
900
  });
864
901
  it('should not update terminal title when hideWindowTitle is true', () => {
@@ -879,7 +916,7 @@ describe('AppContainer State Management', () => {
879
916
  settings: mockSettingsWithHideTitleTrue,
880
917
  });
881
918
  // Assert: Check that no title-related writes occurred
882
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
919
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
883
920
  expect(titleWrites).toHaveLength(0);
884
921
  unmount();
885
922
  });
@@ -910,10 +947,10 @@ describe('AppContainer State Management', () => {
910
947
  const { unmount } = renderAppContainer({
911
948
  settings: mockSettingsWithTitleEnabled,
912
949
  });
913
- // Assert: Check that title was updated with thought subject
914
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
950
+ // Assert: Check that title was updated with thought subject and suffix
951
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
915
952
  expect(titleWrites).toHaveLength(1);
916
- expect(titleWrites[0][0]).toBe(`\x1b]2;${thoughtSubject.padEnd(80, ' ')}\x07`);
953
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${`✦ ${thoughtSubject} (workspace)`.padEnd(80, ' ')}\x07`);
917
954
  unmount();
918
955
  });
919
956
  it('should update terminal title with default text when in Idle state and no thought subject', () => {
@@ -943,12 +980,12 @@ describe('AppContainer State Management', () => {
943
980
  settings: mockSettingsWithTitleEnabled,
944
981
  });
945
982
  // Assert: Check that title was updated with default Idle text
946
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
983
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
947
984
  expect(titleWrites).toHaveLength(1);
948
- expect(titleWrites[0][0]).toBe(`\x1b]2;${'Gemini - workspace'.padEnd(80, ' ')}\x07`);
985
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${'◇ Ready (workspace)'.padEnd(80, ' ')}\x07`);
949
986
  unmount();
950
987
  });
951
- it('should update terminal title when in WaitingForConfirmation state with thought subject', () => {
988
+ it('should update terminal title when in WaitingForConfirmation state with thought subject', async () => {
952
989
  // Arrange: Set up mock settings with showStatusInTitle enabled
953
990
  const mockSettingsWithTitleEnabled = {
954
991
  ...mockSettings,
@@ -964,7 +1001,7 @@ describe('AppContainer State Management', () => {
964
1001
  // Mock the streaming state and thought
965
1002
  const thoughtSubject = 'Confirm tool execution';
966
1003
  mockedUseGeminiStream.mockReturnValue({
967
- streamingState: 'waitingForConfirmation',
1004
+ streamingState: 'waiting_for_confirmation',
968
1005
  submitQuery: vi.fn(),
969
1006
  initError: null,
970
1007
  pendingHistoryItems: [],
@@ -972,15 +1009,146 @@ describe('AppContainer State Management', () => {
972
1009
  cancelOngoingRequest: vi.fn(),
973
1010
  });
974
1011
  // Act: Render the container
975
- const { unmount } = renderAppContainer({
976
- settings: mockSettingsWithTitleEnabled,
1012
+ let unmount;
1013
+ await act(async () => {
1014
+ const result = renderAppContainer({
1015
+ settings: mockSettingsWithTitleEnabled,
1016
+ });
1017
+ unmount = result.unmount;
977
1018
  });
978
1019
  // Assert: Check that title was updated with confirmation text
979
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
1020
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
980
1021
  expect(titleWrites).toHaveLength(1);
981
- expect(titleWrites[0][0]).toBe(`\x1b]2;${thoughtSubject.padEnd(80, ' ')}\x07`);
1022
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${'✋ Action Required (workspace)'.padEnd(80, ' ')}\x07`);
982
1023
  unmount();
983
1024
  });
1025
+ describe('Shell Focus Action Required', () => {
1026
+ beforeEach(() => {
1027
+ vi.useFakeTimers();
1028
+ });
1029
+ afterEach(() => {
1030
+ vi.useRealTimers();
1031
+ });
1032
+ it('should show Action Required in title after a delay when shell is awaiting focus', async () => {
1033
+ const startTime = 1000000;
1034
+ vi.setSystemTime(startTime);
1035
+ // Arrange: Set up mock settings with showStatusInTitle enabled
1036
+ const mockSettingsWithTitleEnabled = {
1037
+ ...mockSettings,
1038
+ merged: {
1039
+ ...mockSettings.merged,
1040
+ ui: {
1041
+ ...mockSettings.merged.ui,
1042
+ showStatusInTitle: true,
1043
+ hideWindowTitle: false,
1044
+ },
1045
+ },
1046
+ };
1047
+ // Mock an active shell pty but not focused
1048
+ mockedUseGeminiStream.mockReturnValue({
1049
+ streamingState: 'responding',
1050
+ submitQuery: vi.fn(),
1051
+ initError: null,
1052
+ pendingHistoryItems: [],
1053
+ thought: { subject: 'Executing shell command' },
1054
+ cancelOngoingRequest: vi.fn(),
1055
+ activePtyId: 'pty-1',
1056
+ lastOutputTime: 0,
1057
+ });
1058
+ vi.spyOn(mockConfig, 'isInteractive').mockReturnValue(true);
1059
+ vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
1060
+ // Act: Render the container (embeddedShellFocused is false by default in state)
1061
+ const { unmount } = renderAppContainer({
1062
+ settings: mockSettingsWithTitleEnabled,
1063
+ });
1064
+ // Initially it should show the working status
1065
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1066
+ expect(titleWrites[titleWrites.length - 1][0]).toContain('✦ Executing shell command');
1067
+ // Fast-forward time by 40 seconds
1068
+ await act(async () => {
1069
+ await vi.advanceTimersByTimeAsync(40000);
1070
+ });
1071
+ // Now it should show Action Required
1072
+ const titleWritesDelayed = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1073
+ const lastTitle = titleWritesDelayed[titleWritesDelayed.length - 1][0];
1074
+ expect(lastTitle).toContain('✋ Action Required');
1075
+ unmount();
1076
+ });
1077
+ it('should NOT show Action Required in title if shell is streaming output', async () => {
1078
+ const startTime = 1000000;
1079
+ vi.setSystemTime(startTime);
1080
+ // Arrange: Set up mock settings with showStatusInTitle enabled
1081
+ const mockSettingsWithTitleEnabled = {
1082
+ ...mockSettings,
1083
+ merged: {
1084
+ ...mockSettings.merged,
1085
+ ui: {
1086
+ ...mockSettings.merged.ui,
1087
+ showStatusInTitle: true,
1088
+ hideWindowTitle: false,
1089
+ },
1090
+ },
1091
+ };
1092
+ // Mock an active shell pty but not focused
1093
+ let lastOutputTime = 1000;
1094
+ mockedUseGeminiStream.mockImplementation(() => ({
1095
+ streamingState: 'responding',
1096
+ submitQuery: vi.fn(),
1097
+ initError: null,
1098
+ pendingHistoryItems: [],
1099
+ thought: { subject: 'Executing shell command' },
1100
+ cancelOngoingRequest: vi.fn(),
1101
+ activePtyId: 'pty-1',
1102
+ lastOutputTime,
1103
+ }));
1104
+ vi.spyOn(mockConfig, 'isInteractive').mockReturnValue(true);
1105
+ vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
1106
+ // Act: Render the container
1107
+ const { unmount, rerender } = renderAppContainer({
1108
+ settings: mockSettingsWithTitleEnabled,
1109
+ });
1110
+ // Fast-forward time by 20 seconds
1111
+ await act(async () => {
1112
+ await vi.advanceTimersByTimeAsync(20000);
1113
+ });
1114
+ // Update lastOutputTime to simulate new output
1115
+ lastOutputTime = 21000;
1116
+ mockedUseGeminiStream.mockImplementation(() => ({
1117
+ streamingState: 'responding',
1118
+ submitQuery: vi.fn(),
1119
+ initError: null,
1120
+ pendingHistoryItems: [],
1121
+ thought: { subject: 'Executing shell command' },
1122
+ cancelOngoingRequest: vi.fn(),
1123
+ activePtyId: 'pty-1',
1124
+ lastOutputTime,
1125
+ }));
1126
+ // Rerender to propagate the new lastOutputTime
1127
+ await act(async () => {
1128
+ rerender(getAppContainer({ settings: mockSettingsWithTitleEnabled }));
1129
+ });
1130
+ // Fast-forward time by another 20 seconds
1131
+ // Total time elapsed: 40s.
1132
+ // Time since last output: 20s.
1133
+ // It should NOT show Action Required yet.
1134
+ await act(async () => {
1135
+ await vi.advanceTimersByTimeAsync(20000);
1136
+ });
1137
+ const titleWritesAfterOutput = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1138
+ const lastTitle = titleWritesAfterOutput[titleWritesAfterOutput.length - 1][0];
1139
+ expect(lastTitle).not.toContain('✋ Action Required');
1140
+ expect(lastTitle).toContain('✦ Executing shell command');
1141
+ // Fast-forward another 40 seconds (Total 60s since last output)
1142
+ await act(async () => {
1143
+ await vi.advanceTimersByTimeAsync(40000);
1144
+ });
1145
+ // Now it SHOULD show Action Required
1146
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1147
+ const lastTitleFinal = titleWrites[titleWrites.length - 1][0];
1148
+ expect(lastTitleFinal).toContain('✋ Action Required');
1149
+ unmount();
1150
+ });
1151
+ });
984
1152
  it('should pad title to exactly 80 characters', () => {
985
1153
  // Arrange: Set up mock settings with showStatusInTitle enabled
986
1154
  const mockSettingsWithTitleEnabled = {
@@ -1009,14 +1177,12 @@ describe('AppContainer State Management', () => {
1009
1177
  settings: mockSettingsWithTitleEnabled,
1010
1178
  });
1011
1179
  // Assert: Check that title is padded to exactly 80 characters
1012
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
1180
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1013
1181
  expect(titleWrites).toHaveLength(1);
1014
1182
  const calledWith = titleWrites[0][0];
1015
- const expectedTitle = shortTitle.padEnd(80, ' ');
1016
- expect(calledWith).toContain(shortTitle);
1017
- expect(calledWith).toContain('\x1b]2;');
1018
- expect(calledWith).toContain('\x07');
1019
- expect(calledWith).toBe('\x1b]2;' + expectedTitle + '\x07');
1183
+ const expectedTitle = `✦ ${shortTitle} (workspace)`.padEnd(80, ' ');
1184
+ const expectedEscapeSequence = `\x1b]0;${expectedTitle}\x07`;
1185
+ expect(calledWith).toBe(expectedEscapeSequence);
1020
1186
  unmount();
1021
1187
  });
1022
1188
  it('should use correct ANSI escape code format', () => {
@@ -1047,30 +1213,30 @@ describe('AppContainer State Management', () => {
1047
1213
  settings: mockSettingsWithTitleEnabled,
1048
1214
  });
1049
1215
  // Assert: Check that the correct ANSI escape sequence is used
1050
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
1216
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1051
1217
  expect(titleWrites).toHaveLength(1);
1052
- const expectedEscapeSequence = `\x1b]2;${title.padEnd(80, ' ')}\x07`;
1218
+ const expectedEscapeSequence = `\x1b]0;${`✦ ${title} (workspace)`.padEnd(80, ' ')}\x07`;
1053
1219
  expect(titleWrites[0][0]).toBe(expectedEscapeSequence);
1054
1220
  unmount();
1055
1221
  });
1056
1222
  it('should use CLI_TITLE environment variable when set', () => {
1057
- // Arrange: Set up mock settings with showStatusInTitle enabled
1058
- const mockSettingsWithTitleEnabled = {
1223
+ // Arrange: Set up mock settings with showStatusInTitle disabled (so it shows suffix)
1224
+ const mockSettingsWithTitleDisabled = {
1059
1225
  ...mockSettings,
1060
1226
  merged: {
1061
1227
  ...mockSettings.merged,
1062
1228
  ui: {
1063
1229
  ...mockSettings.merged.ui,
1064
- showStatusInTitle: true,
1230
+ showStatusInTitle: false,
1065
1231
  hideWindowTitle: false,
1066
1232
  },
1067
1233
  },
1068
1234
  };
1069
1235
  // Mock CLI_TITLE environment variable
1070
1236
  vi.stubEnv('CLI_TITLE', 'Custom Gemini Title');
1071
- // Mock the streaming state as Idle with no thought
1237
+ // Mock the streaming state
1072
1238
  mockedUseGeminiStream.mockReturnValue({
1073
- streamingState: 'idle',
1239
+ streamingState: 'responding',
1074
1240
  submitQuery: vi.fn(),
1075
1241
  initError: null,
1076
1242
  pendingHistoryItems: [],
@@ -1079,12 +1245,12 @@ describe('AppContainer State Management', () => {
1079
1245
  });
1080
1246
  // Act: Render the container
1081
1247
  const { unmount } = renderAppContainer({
1082
- settings: mockSettingsWithTitleEnabled,
1248
+ settings: mockSettingsWithTitleDisabled,
1083
1249
  });
1084
1250
  // Assert: Check that title was updated with CLI_TITLE value
1085
- const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
1251
+ const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]0;'));
1086
1252
  expect(titleWrites).toHaveLength(1);
1087
- expect(titleWrites[0][0]).toBe(`\x1b]2;${'Custom Gemini Title'.padEnd(80, ' ')}\x07`);
1253
+ expect(titleWrites[0][0]).toBe(`\x1b]0;${'✦ Working… (Custom Gemini Title)'.padEnd(80, ' ')}\x07`);
1088
1254
  unmount();
1089
1255
  });
1090
1256
  });
@@ -1094,6 +1260,7 @@ describe('AppContainer State Management', () => {
1094
1260
  });
1095
1261
  afterEach(() => {
1096
1262
  vi.useRealTimers();
1263
+ vi.restoreAllMocks();
1097
1264
  });
1098
1265
  it('should set and clear the queue error message after a timeout', async () => {
1099
1266
  const { rerender, unmount } = renderAppContainer();
@@ -1237,6 +1404,7 @@ describe('AppContainer State Management', () => {
1237
1404
  });
1238
1405
  afterEach(() => {
1239
1406
  vi.useRealTimers();
1407
+ vi.restoreAllMocks();
1240
1408
  });
1241
1409
  describe('CTRL+C', () => {
1242
1410
  it('should cancel ongoing request on first press', async () => {
@@ -1337,6 +1505,7 @@ describe('AppContainer State Management', () => {
1337
1505
  });
1338
1506
  afterEach(() => {
1339
1507
  vi.useRealTimers();
1508
+ vi.restoreAllMocks();
1340
1509
  });
1341
1510
  describe.each([
1342
1511
  {
@@ -1547,7 +1716,9 @@ describe('AppContainer State Management', () => {
1547
1716
  handler({ model: 'new-model' });
1548
1717
  });
1549
1718
  // Assert: Verify model is updated
1550
- expect(capturedUIState.currentModel).toBe('new-model');
1719
+ await waitFor(() => {
1720
+ expect(capturedUIState.currentModel).toBe('new-model');
1721
+ });
1551
1722
  unmount();
1552
1723
  });
1553
1724
  it('provides activeHooks from useHookDisplayState', async () => {
@@ -1652,7 +1823,9 @@ describe('AppContainer State Management', () => {
1652
1823
  await act(async () => {
1653
1824
  onCancelSubmit(true);
1654
1825
  });
1655
- expect(mockSetText).toHaveBeenCalledWith('previous message');
1826
+ await waitFor(() => {
1827
+ expect(mockSetText).toHaveBeenCalledWith('previous message');
1828
+ });
1656
1829
  unmount();
1657
1830
  });
1658
1831
  it('input history is independent from conversation history (survives /clear)', async () => {