@google/gemini-cli 0.15.0-nightly.20251111.51f952e7 → 0.15.0-preview.1

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 (154) hide show
  1. package/dist/google-gemini-cli-0.15.0-preview.0.tgz +0 -0
  2. package/dist/package.json +2 -2
  3. package/dist/src/config/config.d.ts +3 -0
  4. package/dist/src/config/config.js +31 -0
  5. package/dist/src/config/config.js.map +1 -1
  6. package/dist/src/config/config.test.js +20 -0
  7. package/dist/src/config/config.test.js.map +1 -1
  8. package/dist/src/config/extension-manager.js +13 -1
  9. package/dist/src/config/extension-manager.js.map +1 -1
  10. package/dist/src/config/extension.test.js +65 -0
  11. package/dist/src/config/extension.test.js.map +1 -1
  12. package/dist/src/config/settingsSchema.d.ts +10 -1
  13. package/dist/src/config/settingsSchema.js +10 -1
  14. package/dist/src/config/settingsSchema.js.map +1 -1
  15. package/dist/src/gemini.d.ts +2 -2
  16. package/dist/src/gemini.js +35 -4
  17. package/dist/src/gemini.js.map +1 -1
  18. package/dist/src/gemini.test.js +14 -7
  19. package/dist/src/gemini.test.js.map +1 -1
  20. package/dist/src/generated/git-commit.d.ts +2 -2
  21. package/dist/src/generated/git-commit.js +2 -2
  22. package/dist/src/generated/git-commit.js.map +1 -1
  23. package/dist/src/nonInteractiveCli.d.ts +3 -2
  24. package/dist/src/nonInteractiveCli.js +6 -1
  25. package/dist/src/nonInteractiveCli.js.map +1 -1
  26. package/dist/src/nonInteractiveCli.test.js +1 -0
  27. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  28. package/dist/src/test-utils/render.d.ts +2 -1
  29. package/dist/src/test-utils/render.js +13 -3
  30. package/dist/src/test-utils/render.js.map +1 -1
  31. package/dist/src/ui/App.js +9 -1
  32. package/dist/src/ui/App.js.map +1 -1
  33. package/dist/src/ui/App.test.js +25 -1
  34. package/dist/src/ui/App.test.js.map +1 -1
  35. package/dist/src/ui/AppContainer.d.ts +2 -3
  36. package/dist/src/ui/AppContainer.js +22 -7
  37. package/dist/src/ui/AppContainer.js.map +1 -1
  38. package/dist/src/ui/AppContainer.test.js +349 -46
  39. package/dist/src/ui/AppContainer.test.js.map +1 -1
  40. package/dist/src/ui/commands/toolsCommand.js +4 -1
  41. package/dist/src/ui/commands/toolsCommand.js.map +1 -1
  42. package/dist/src/ui/components/AlternateBufferQuittingDisplay.d.ts +6 -0
  43. package/dist/src/ui/components/AlternateBufferQuittingDisplay.js +24 -0
  44. package/dist/src/ui/components/AlternateBufferQuittingDisplay.js.map +1 -0
  45. package/dist/src/ui/components/AlternateBufferQuittingDisplay.test.d.ts +6 -0
  46. package/dist/src/ui/components/AlternateBufferQuittingDisplay.test.js +101 -0
  47. package/dist/src/ui/components/AlternateBufferQuittingDisplay.test.js.map +1 -0
  48. package/dist/src/ui/components/Composer.js +11 -4
  49. package/dist/src/ui/components/Composer.js.map +1 -1
  50. package/dist/src/ui/components/CopyModeWarning.d.ts +7 -0
  51. package/dist/src/ui/components/CopyModeWarning.js +12 -0
  52. package/dist/src/ui/components/CopyModeWarning.js.map +1 -0
  53. package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
  54. package/dist/src/ui/components/Header.js +11 -1
  55. package/dist/src/ui/components/Header.js.map +1 -1
  56. package/dist/src/ui/components/Header.test.js +69 -14
  57. package/dist/src/ui/components/Header.test.js.map +1 -1
  58. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  59. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  60. package/dist/src/ui/components/HistoryItemDisplay.test.js +43 -41
  61. package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
  62. package/dist/src/ui/components/InputPrompt.d.ts +2 -0
  63. package/dist/src/ui/components/InputPrompt.js +14 -8
  64. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  65. package/dist/src/ui/components/MainContent.js +52 -15
  66. package/dist/src/ui/components/MainContent.js.map +1 -1
  67. package/dist/src/ui/components/ModelDialog.test.js +1 -0
  68. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  69. package/dist/src/ui/components/StickyHeader.d.ts +11 -0
  70. package/dist/src/ui/components/StickyHeader.js +5 -0
  71. package/dist/src/ui/components/StickyHeader.js.map +1 -0
  72. package/dist/src/ui/components/StickyHeader.test.d.ts +6 -0
  73. package/dist/src/ui/components/StickyHeader.test.js +17 -0
  74. package/dist/src/ui/components/StickyHeader.test.js.map +1 -0
  75. package/dist/src/ui/components/ThemeDialog.js +11 -3
  76. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  77. package/dist/src/ui/components/messages/DiffRenderer.js +129 -89
  78. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  79. package/dist/src/ui/components/messages/DiffRenderer.test.js +107 -115
  80. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
  81. package/dist/src/ui/components/messages/GeminiMessage.js +3 -1
  82. package/dist/src/ui/components/messages/GeminiMessage.js.map +1 -1
  83. package/dist/src/ui/components/messages/GeminiMessageContent.js +3 -1
  84. package/dist/src/ui/components/messages/GeminiMessageContent.js.map +1 -1
  85. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +152 -121
  86. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  87. package/dist/src/ui/components/messages/ToolGroupMessage.js +5 -7
  88. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  89. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +1 -5
  90. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  91. package/dist/src/ui/components/messages/ToolMessage.js +39 -16
  92. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  93. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js +33 -4
  94. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js.map +1 -1
  95. package/dist/src/ui/components/messages/UserMessage.d.ts +1 -0
  96. package/dist/src/ui/components/messages/UserMessage.js +2 -2
  97. package/dist/src/ui/components/messages/UserMessage.js.map +1 -1
  98. package/dist/src/ui/components/shared/ScrollableList.test.js +61 -24
  99. package/dist/src/ui/components/shared/ScrollableList.test.js.map +1 -1
  100. package/dist/src/ui/components/shared/VirtualizedList.js +1 -1
  101. package/dist/src/ui/components/shared/VirtualizedList.js.map +1 -1
  102. package/dist/src/ui/constants.d.ts +1 -0
  103. package/dist/src/ui/constants.js +5 -0
  104. package/dist/src/ui/constants.js.map +1 -1
  105. package/dist/src/ui/contexts/UIStateContext.d.ts +1 -0
  106. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  107. package/dist/src/ui/hooks/atCommandProcessor.test.js +1 -1
  108. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  109. package/dist/src/ui/hooks/useAlternateBuffer.d.ts +6 -0
  110. package/dist/src/ui/hooks/useAlternateBuffer.js +11 -0
  111. package/dist/src/ui/hooks/useAlternateBuffer.js.map +1 -0
  112. package/dist/src/ui/hooks/useGeminiStream.test.js +4 -0
  113. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  114. package/dist/src/ui/hooks/usePromptCompletion.js +2 -9
  115. package/dist/src/ui/hooks/usePromptCompletion.js.map +1 -1
  116. package/dist/src/ui/hooks/useSessionBrowser.d.ts +18 -0
  117. package/dist/src/ui/hooks/useSessionBrowser.js +152 -0
  118. package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -0
  119. package/dist/src/ui/hooks/useSessionBrowser.test.d.ts +6 -0
  120. package/dist/src/ui/hooks/useSessionBrowser.test.js +544 -0
  121. package/dist/src/ui/hooks/useSessionBrowser.test.js.map +1 -0
  122. package/dist/src/ui/hooks/useSessionResume.d.ts +30 -0
  123. package/dist/src/ui/hooks/useSessionResume.js +56 -0
  124. package/dist/src/ui/hooks/useSessionResume.js.map +1 -0
  125. package/dist/src/ui/hooks/useSessionResume.test.d.ts +6 -0
  126. package/dist/src/ui/hooks/useSessionResume.test.js +325 -0
  127. package/dist/src/ui/hooks/useSessionResume.test.js.map +1 -0
  128. package/dist/src/ui/hooks/useToolScheduler.test.js +1 -0
  129. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  130. package/dist/src/ui/layouts/DefaultAppLayout.js +9 -3
  131. package/dist/src/ui/layouts/DefaultAppLayout.js.map +1 -1
  132. package/dist/src/ui/utils/CodeColorizer.d.ts +11 -3
  133. package/dist/src/ui/utils/CodeColorizer.js +18 -9
  134. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  135. package/dist/src/ui/utils/MarkdownDisplay.js +32 -6
  136. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  137. package/dist/src/ui/utils/ui-sizing.js +9 -3
  138. package/dist/src/ui/utils/ui-sizing.js.map +1 -1
  139. package/dist/src/utils/cleanup.test.js +1 -1
  140. package/dist/src/utils/cleanup.test.js.map +1 -1
  141. package/dist/src/utils/sessionCleanup.test.js +76 -0
  142. package/dist/src/utils/sessionCleanup.test.js.map +1 -1
  143. package/dist/src/utils/sessionUtils.d.ts +54 -0
  144. package/dist/src/utils/sessionUtils.js +141 -1
  145. package/dist/src/utils/sessionUtils.js.map +1 -1
  146. package/dist/src/utils/sessionUtils.test.d.ts +6 -0
  147. package/dist/src/utils/sessionUtils.test.js +260 -0
  148. package/dist/src/utils/sessionUtils.test.js.map +1 -0
  149. package/dist/src/utils/sessions.d.ts +8 -0
  150. package/dist/src/utils/sessions.js +64 -0
  151. package/dist/src/utils/sessions.js.map +1 -0
  152. package/dist/tsconfig.tsbuildinfo +1 -1
  153. package/package.json +3 -3
  154. package/dist/google-gemini-cli-0.15.0-nightly.20251107.b8eeb553.tgz +0 -0
@@ -9,6 +9,7 @@ import { render } from '../test-utils/render.js';
9
9
  import { cleanup } from 'ink-testing-library';
10
10
  import { act, useContext } from 'react';
11
11
  import { AppContainer } from './AppContainer.js';
12
+ import { SettingsContext } from './contexts/SettingsContext.js';
12
13
  import { makeFakeConfig, CoreEvent, } from '@google/gemini-cli-core';
13
14
  // Mock coreEvents
14
15
  const mockCoreEvents = vi.hoisted(() => ({
@@ -116,6 +117,10 @@ describe('AppContainer State Management', () => {
116
117
  let mockSettings;
117
118
  let mockInitResult;
118
119
  let mockExtensionManager;
120
+ // Helper to generate the AppContainer JSX for render and rerender
121
+ const getAppContainer = ({ settings = mockSettings, config = mockConfig, version = '1.0.0', initResult = mockInitResult, startupWarnings, resumedSessionData, } = {}) => (_jsx(SettingsContext.Provider, { value: settings, children: _jsx(AppContainer, { config: config, version: version, initializationResult: initResult, startupWarnings: startupWarnings, resumedSessionData: resumedSessionData }) }));
122
+ // Helper to render the AppContainer
123
+ const renderAppContainer = (props) => render(getAppContainer(props));
119
124
  // Create typed mocks for all hooks
120
125
  const mockedUseQuotaAndFallback = useQuotaAndFallback;
121
126
  const mockedUseHistory = useHistory;
@@ -270,6 +275,7 @@ describe('AppContainer State Management', () => {
270
275
  showStatusInTitle: false,
271
276
  hideWindowTitle: false,
272
277
  },
278
+ useAlternateBuffer: false,
273
279
  },
274
280
  };
275
281
  // Mock InitializationResult
@@ -285,7 +291,7 @@ describe('AppContainer State Management', () => {
285
291
  });
286
292
  describe('Basic Rendering', () => {
287
293
  it('renders without crashing with minimal props', async () => {
288
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
294
+ const { unmount } = renderAppContainer();
289
295
  await act(async () => {
290
296
  await new Promise((resolve) => setTimeout(resolve, 0));
291
297
  });
@@ -293,7 +299,7 @@ describe('AppContainer State Management', () => {
293
299
  });
294
300
  it('renders with startup warnings', async () => {
295
301
  const startupWarnings = ['Warning 1', 'Warning 2'];
296
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, startupWarnings: startupWarnings, version: "1.0.0", initializationResult: mockInitResult }));
302
+ const { unmount } = renderAppContainer({ startupWarnings });
297
303
  await act(async () => {
298
304
  await new Promise((resolve) => setTimeout(resolve, 0));
299
305
  });
@@ -306,7 +312,9 @@ describe('AppContainer State Management', () => {
306
312
  ...mockInitResult,
307
313
  themeError: 'Failed to load theme',
308
314
  };
309
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: initResultWithError }));
315
+ const { unmount } = renderAppContainer({
316
+ initResult: initResultWithError,
317
+ });
310
318
  await act(async () => {
311
319
  await new Promise((resolve) => setTimeout(resolve, 0));
312
320
  });
@@ -316,13 +324,13 @@ describe('AppContainer State Management', () => {
316
324
  const debugConfig = makeFakeConfig();
317
325
  vi.spyOn(debugConfig, 'getDebugMode').mockReturnValue(true);
318
326
  expect(() => {
319
- render(_jsx(AppContainer, { config: debugConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
327
+ renderAppContainer({ config: debugConfig });
320
328
  }).not.toThrow();
321
329
  });
322
330
  });
323
331
  describe('Context Providers', () => {
324
332
  it('provides AppContext with correct values', async () => {
325
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "2.0.0", initializationResult: mockInitResult }));
333
+ const { unmount } = renderAppContainer({ version: '2.0.0' });
326
334
  await act(async () => {
327
335
  await new Promise((resolve) => setTimeout(resolve, 0));
328
336
  });
@@ -330,21 +338,21 @@ describe('AppContainer State Management', () => {
330
338
  expect(() => unmount()).not.toThrow();
331
339
  });
332
340
  it('provides UIStateContext with state management', async () => {
333
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
341
+ const { unmount } = renderAppContainer();
334
342
  await act(async () => {
335
343
  await new Promise((resolve) => setTimeout(resolve, 0));
336
344
  });
337
345
  unmount();
338
346
  });
339
347
  it('provides UIActionsContext with action handlers', async () => {
340
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
348
+ const { unmount } = renderAppContainer();
341
349
  await act(async () => {
342
350
  await new Promise((resolve) => setTimeout(resolve, 0));
343
351
  });
344
352
  unmount();
345
353
  });
346
354
  it('provides ConfigContext with config object', async () => {
347
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
355
+ const { unmount } = renderAppContainer();
348
356
  await act(async () => {
349
357
  await new Promise((resolve) => setTimeout(resolve, 0));
350
358
  });
@@ -361,7 +369,7 @@ describe('AppContainer State Management', () => {
361
369
  showMemoryUsage: false,
362
370
  },
363
371
  };
364
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: settingsAllHidden, version: "1.0.0", initializationResult: mockInitResult }));
372
+ const { unmount } = renderAppContainer({ settings: settingsAllHidden });
365
373
  await act(async () => {
366
374
  await new Promise((resolve) => setTimeout(resolve, 0));
367
375
  });
@@ -376,7 +384,7 @@ describe('AppContainer State Management', () => {
376
384
  showMemoryUsage: true,
377
385
  },
378
386
  };
379
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: settingsWithMemory, version: "1.0.0", initializationResult: mockInitResult }));
387
+ const { unmount } = renderAppContainer({ settings: settingsWithMemory });
380
388
  await act(async () => {
381
389
  await new Promise((resolve) => setTimeout(resolve, 0));
382
390
  });
@@ -385,7 +393,7 @@ describe('AppContainer State Management', () => {
385
393
  });
386
394
  describe('Version Handling', () => {
387
395
  it.each(['1.0.0', '2.1.3-beta', '3.0.0-nightly'])('handles version format: %s', async (version) => {
388
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: version, initializationResult: mockInitResult }));
396
+ const { unmount } = renderAppContainer({ version });
389
397
  await act(async () => {
390
398
  await new Promise((resolve) => setTimeout(resolve, 0));
391
399
  });
@@ -399,7 +407,7 @@ describe('AppContainer State Management', () => {
399
407
  throw new Error('Config error');
400
408
  });
401
409
  // Should still render without crashing - errors should be handled internally
402
- const { unmount } = render(_jsx(AppContainer, { config: errorConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
410
+ const { unmount } = renderAppContainer({ config: errorConfig });
403
411
  await act(async () => {
404
412
  await new Promise((resolve) => setTimeout(resolve, 0));
405
413
  });
@@ -409,7 +417,7 @@ describe('AppContainer State Management', () => {
409
417
  const undefinedSettings = {
410
418
  merged: {},
411
419
  };
412
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: undefinedSettings, version: "1.0.0", initializationResult: mockInitResult }));
420
+ const { unmount } = renderAppContainer({ settings: undefinedSettings });
413
421
  await act(async () => {
414
422
  await new Promise((resolve) => setTimeout(resolve, 0));
415
423
  });
@@ -420,14 +428,294 @@ describe('AppContainer State Management', () => {
420
428
  it('establishes correct provider nesting order', () => {
421
429
  // This tests that all the context providers are properly nested
422
430
  // and that the component tree can be built without circular dependencies
423
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
431
+ const { unmount } = renderAppContainer();
424
432
  expect(() => unmount()).not.toThrow();
425
433
  });
426
434
  });
435
+ describe('Session Resumption', () => {
436
+ it('handles resumed session data correctly', async () => {
437
+ const mockResumedSessionData = {
438
+ conversation: {
439
+ sessionId: 'test-session-123',
440
+ projectHash: 'test-project-hash',
441
+ startTime: '2024-01-01T00:00:00Z',
442
+ lastUpdated: '2024-01-01T00:00:01Z',
443
+ messages: [
444
+ {
445
+ id: 'msg-1',
446
+ type: 'user',
447
+ content: 'Hello',
448
+ timestamp: '2024-01-01T00:00:00Z',
449
+ },
450
+ {
451
+ id: 'msg-2',
452
+ type: 'gemini',
453
+ content: 'Hi there!',
454
+ role: 'model',
455
+ parts: [{ text: 'Hi there!' }],
456
+ timestamp: '2024-01-01T00:00:01Z',
457
+ },
458
+ ],
459
+ },
460
+ filePath: '/tmp/test-session.json',
461
+ };
462
+ let unmount;
463
+ await act(async () => {
464
+ const result = renderAppContainer({
465
+ config: mockConfig,
466
+ settings: mockSettings,
467
+ version: '1.0.0',
468
+ initResult: mockInitResult,
469
+ resumedSessionData: mockResumedSessionData,
470
+ });
471
+ unmount = result.unmount;
472
+ });
473
+ await act(async () => {
474
+ unmount();
475
+ });
476
+ });
477
+ it('renders without resumed session data', async () => {
478
+ let unmount;
479
+ await act(async () => {
480
+ const result = renderAppContainer({
481
+ config: mockConfig,
482
+ settings: mockSettings,
483
+ version: '1.0.0',
484
+ initResult: mockInitResult,
485
+ resumedSessionData: undefined,
486
+ });
487
+ unmount = result.unmount;
488
+ });
489
+ await act(async () => {
490
+ unmount();
491
+ });
492
+ });
493
+ it('initializes chat recording service when config has it', () => {
494
+ const mockChatRecordingService = {
495
+ initialize: vi.fn(),
496
+ recordMessage: vi.fn(),
497
+ recordMessageTokens: vi.fn(),
498
+ recordToolCalls: vi.fn(),
499
+ };
500
+ const mockGeminiClient = {
501
+ isInitialized: vi.fn(() => true),
502
+ resumeChat: vi.fn(),
503
+ getUserTier: vi.fn(),
504
+ getChatRecordingService: vi.fn(() => mockChatRecordingService),
505
+ };
506
+ const configWithRecording = {
507
+ ...mockConfig,
508
+ getGeminiClient: vi.fn(() => mockGeminiClient),
509
+ };
510
+ expect(() => {
511
+ renderAppContainer({
512
+ config: configWithRecording,
513
+ settings: mockSettings,
514
+ version: '1.0.0',
515
+ initResult: mockInitResult,
516
+ });
517
+ }).not.toThrow();
518
+ });
519
+ });
520
+ describe('Session Recording Integration', () => {
521
+ it('provides chat recording service configuration', () => {
522
+ const mockChatRecordingService = {
523
+ initialize: vi.fn(),
524
+ recordMessage: vi.fn(),
525
+ recordMessageTokens: vi.fn(),
526
+ recordToolCalls: vi.fn(),
527
+ getSessionId: vi.fn(() => 'test-session-123'),
528
+ getCurrentConversation: vi.fn(),
529
+ };
530
+ const mockGeminiClient = {
531
+ isInitialized: vi.fn(() => true),
532
+ resumeChat: vi.fn(),
533
+ getUserTier: vi.fn(),
534
+ getChatRecordingService: vi.fn(() => mockChatRecordingService),
535
+ setHistory: vi.fn(),
536
+ };
537
+ const configWithRecording = {
538
+ ...mockConfig,
539
+ getGeminiClient: vi.fn(() => mockGeminiClient),
540
+ getSessionId: vi.fn(() => 'test-session-123'),
541
+ };
542
+ expect(() => {
543
+ renderAppContainer({
544
+ config: configWithRecording,
545
+ settings: mockSettings,
546
+ version: '1.0.0',
547
+ initResult: mockInitResult,
548
+ });
549
+ }).not.toThrow();
550
+ // Verify the recording service structure is correct
551
+ expect(configWithRecording.getGeminiClient).toBeDefined();
552
+ expect(mockGeminiClient.getChatRecordingService).toBeDefined();
553
+ expect(mockChatRecordingService.initialize).toBeDefined();
554
+ expect(mockChatRecordingService.recordMessage).toBeDefined();
555
+ });
556
+ it('handles session recording when messages are added', () => {
557
+ const mockRecordMessage = vi.fn();
558
+ const mockRecordMessageTokens = vi.fn();
559
+ const mockChatRecordingService = {
560
+ initialize: vi.fn(),
561
+ recordMessage: mockRecordMessage,
562
+ recordMessageTokens: mockRecordMessageTokens,
563
+ recordToolCalls: vi.fn(),
564
+ getSessionId: vi.fn(() => 'test-session-123'),
565
+ };
566
+ const mockGeminiClient = {
567
+ isInitialized: vi.fn(() => true),
568
+ getChatRecordingService: vi.fn(() => mockChatRecordingService),
569
+ getUserTier: vi.fn(),
570
+ };
571
+ const configWithRecording = {
572
+ ...mockConfig,
573
+ getGeminiClient: vi.fn(() => mockGeminiClient),
574
+ };
575
+ renderAppContainer({
576
+ config: configWithRecording,
577
+ settings: mockSettings,
578
+ version: '1.0.0',
579
+ initResult: mockInitResult,
580
+ });
581
+ // The actual recording happens through the useHistory hook
582
+ // which would be triggered by user interactions
583
+ expect(mockChatRecordingService.initialize).toBeDefined();
584
+ expect(mockChatRecordingService.recordMessage).toBeDefined();
585
+ });
586
+ });
587
+ describe('Session Resume Flow', () => {
588
+ it('accepts resumed session data', () => {
589
+ const mockResumeChat = vi.fn();
590
+ const mockGeminiClient = {
591
+ isInitialized: vi.fn(() => true),
592
+ resumeChat: mockResumeChat,
593
+ getUserTier: vi.fn(),
594
+ getChatRecordingService: vi.fn(() => ({
595
+ initialize: vi.fn(),
596
+ recordMessage: vi.fn(),
597
+ recordMessageTokens: vi.fn(),
598
+ recordToolCalls: vi.fn(),
599
+ })),
600
+ };
601
+ const configWithClient = {
602
+ ...mockConfig,
603
+ getGeminiClient: vi.fn(() => mockGeminiClient),
604
+ };
605
+ const resumedData = {
606
+ conversation: {
607
+ sessionId: 'resumed-session-456',
608
+ projectHash: 'project-hash',
609
+ startTime: '2024-01-01T00:00:00Z',
610
+ lastUpdated: '2024-01-01T00:01:00Z',
611
+ messages: [
612
+ {
613
+ id: 'msg-1',
614
+ type: 'user',
615
+ content: 'Previous question',
616
+ timestamp: '2024-01-01T00:00:00Z',
617
+ },
618
+ {
619
+ id: 'msg-2',
620
+ type: 'gemini',
621
+ content: 'Previous answer',
622
+ role: 'model',
623
+ parts: [{ text: 'Previous answer' }],
624
+ timestamp: '2024-01-01T00:00:30Z',
625
+ tokenCount: { input: 10, output: 20 },
626
+ },
627
+ ],
628
+ },
629
+ filePath: '/tmp/resumed-session.json',
630
+ };
631
+ expect(() => {
632
+ renderAppContainer({
633
+ config: configWithClient,
634
+ settings: mockSettings,
635
+ version: '1.0.0',
636
+ initResult: mockInitResult,
637
+ resumedSessionData: resumedData,
638
+ });
639
+ }).not.toThrow();
640
+ // Verify the resume functionality structure is in place
641
+ expect(mockGeminiClient.resumeChat).toBeDefined();
642
+ expect(resumedData.conversation.messages).toHaveLength(2);
643
+ });
644
+ it('does not attempt resume when client is not initialized', () => {
645
+ const mockResumeChat = vi.fn();
646
+ const mockGeminiClient = {
647
+ isInitialized: vi.fn(() => false), // Not initialized
648
+ resumeChat: mockResumeChat,
649
+ getUserTier: vi.fn(),
650
+ getChatRecordingService: vi.fn(),
651
+ };
652
+ const configWithClient = {
653
+ ...mockConfig,
654
+ getGeminiClient: vi.fn(() => mockGeminiClient),
655
+ };
656
+ const resumedData = {
657
+ conversation: {
658
+ sessionId: 'test-session',
659
+ projectHash: 'project-hash',
660
+ startTime: '2024-01-01T00:00:00Z',
661
+ lastUpdated: '2024-01-01T00:01:00Z',
662
+ messages: [],
663
+ },
664
+ filePath: '/tmp/session.json',
665
+ };
666
+ renderAppContainer({
667
+ config: configWithClient,
668
+ settings: mockSettings,
669
+ version: '1.0.0',
670
+ initResult: mockInitResult,
671
+ resumedSessionData: resumedData,
672
+ });
673
+ // Should not call resumeChat when client is not initialized
674
+ expect(mockResumeChat).not.toHaveBeenCalled();
675
+ });
676
+ });
677
+ describe('Token Counting from Session Stats', () => {
678
+ it('tracks token counts from session messages', () => {
679
+ // Session stats are provided through the SessionStatsProvider context
680
+ // in the real app, not through the config directly
681
+ const mockChatRecordingService = {
682
+ initialize: vi.fn(),
683
+ recordMessage: vi.fn(),
684
+ recordMessageTokens: vi.fn(),
685
+ recordToolCalls: vi.fn(),
686
+ getSessionId: vi.fn(() => 'test-session-123'),
687
+ getCurrentConversation: vi.fn(() => ({
688
+ sessionId: 'test-session-123',
689
+ messages: [],
690
+ totalInputTokens: 150,
691
+ totalOutputTokens: 350,
692
+ })),
693
+ };
694
+ const mockGeminiClient = {
695
+ isInitialized: vi.fn(() => true),
696
+ getChatRecordingService: vi.fn(() => mockChatRecordingService),
697
+ getUserTier: vi.fn(),
698
+ };
699
+ const configWithRecording = {
700
+ ...mockConfig,
701
+ getGeminiClient: vi.fn(() => mockGeminiClient),
702
+ };
703
+ renderAppContainer({
704
+ config: configWithRecording,
705
+ settings: mockSettings,
706
+ version: '1.0.0',
707
+ initResult: mockInitResult,
708
+ });
709
+ // In the actual app, these stats would be displayed in components
710
+ // and updated as messages are processed through the recording service
711
+ expect(mockChatRecordingService.recordMessageTokens).toBeDefined();
712
+ expect(mockChatRecordingService.getCurrentConversation).toBeDefined();
713
+ });
714
+ });
427
715
  describe('Quota and Fallback Integration', () => {
428
716
  it('passes a null proQuotaRequest to UIStateContext by default', async () => {
429
717
  // The default mock from beforeEach already sets proQuotaRequest to null
430
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
718
+ const { unmount } = renderAppContainer();
431
719
  await act(async () => {
432
720
  await new Promise((resolve) => setTimeout(resolve, 0));
433
721
  });
@@ -447,7 +735,7 @@ describe('AppContainer State Management', () => {
447
735
  handleProQuotaChoice: vi.fn(),
448
736
  });
449
737
  // Act: Render the container
450
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
738
+ const { unmount } = renderAppContainer();
451
739
  await act(async () => {
452
740
  await new Promise((resolve) => setTimeout(resolve, 0));
453
741
  });
@@ -463,7 +751,7 @@ describe('AppContainer State Management', () => {
463
751
  handleProQuotaChoice: mockHandler,
464
752
  });
465
753
  // Act: Render the container
466
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
754
+ const { unmount } = renderAppContainer();
467
755
  await act(async () => {
468
756
  await new Promise((resolve) => setTimeout(resolve, 0));
469
757
  });
@@ -496,7 +784,9 @@ describe('AppContainer State Management', () => {
496
784
  },
497
785
  };
498
786
  // Act: Render the container
499
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithShowStatusFalse, version: "1.0.0", initializationResult: mockInitResult }));
787
+ const { unmount } = renderAppContainer({
788
+ settings: mockSettingsWithShowStatusFalse,
789
+ });
500
790
  // Assert: Check that no title-related writes occurred
501
791
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
502
792
  expect(titleWrites).toHaveLength(0);
@@ -516,7 +806,9 @@ describe('AppContainer State Management', () => {
516
806
  },
517
807
  };
518
808
  // Act: Render the container
519
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithHideTitleTrue, version: "1.0.0", initializationResult: mockInitResult }));
809
+ const { unmount } = renderAppContainer({
810
+ settings: mockSettingsWithHideTitleTrue,
811
+ });
520
812
  // Assert: Check that no title-related writes occurred
521
813
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
522
814
  expect(titleWrites).toHaveLength(0);
@@ -546,7 +838,9 @@ describe('AppContainer State Management', () => {
546
838
  cancelOngoingRequest: vi.fn(),
547
839
  });
548
840
  // Act: Render the container
549
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
841
+ const { unmount } = renderAppContainer({
842
+ settings: mockSettingsWithTitleEnabled,
843
+ });
550
844
  // Assert: Check that title was updated with thought subject
551
845
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
552
846
  expect(titleWrites).toHaveLength(1);
@@ -576,7 +870,9 @@ describe('AppContainer State Management', () => {
576
870
  cancelOngoingRequest: vi.fn(),
577
871
  });
578
872
  // Act: Render the container
579
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
873
+ const { unmount } = renderAppContainer({
874
+ settings: mockSettingsWithTitleEnabled,
875
+ });
580
876
  // Assert: Check that title was updated with default Idle text
581
877
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
582
878
  expect(titleWrites).toHaveLength(1);
@@ -607,7 +903,9 @@ describe('AppContainer State Management', () => {
607
903
  cancelOngoingRequest: vi.fn(),
608
904
  });
609
905
  // Act: Render the container
610
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
906
+ const { unmount } = renderAppContainer({
907
+ settings: mockSettingsWithTitleEnabled,
908
+ });
611
909
  // Assert: Check that title was updated with confirmation text
612
910
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
613
911
  expect(titleWrites).toHaveLength(1);
@@ -638,7 +936,9 @@ describe('AppContainer State Management', () => {
638
936
  cancelOngoingRequest: vi.fn(),
639
937
  });
640
938
  // Act: Render the container
641
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
939
+ const { unmount } = renderAppContainer({
940
+ settings: mockSettingsWithTitleEnabled,
941
+ });
642
942
  // Assert: Check that title is padded to exactly 80 characters
643
943
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
644
944
  expect(titleWrites).toHaveLength(1);
@@ -674,7 +974,9 @@ describe('AppContainer State Management', () => {
674
974
  cancelOngoingRequest: vi.fn(),
675
975
  });
676
976
  // Act: Render the container
677
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
977
+ const { unmount } = renderAppContainer({
978
+ settings: mockSettingsWithTitleEnabled,
979
+ });
678
980
  // Assert: Check that the correct ANSI escape sequence is used
679
981
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
680
982
  expect(titleWrites).toHaveLength(1);
@@ -707,7 +1009,9 @@ describe('AppContainer State Management', () => {
707
1009
  cancelOngoingRequest: vi.fn(),
708
1010
  });
709
1011
  // Act: Render the container
710
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettingsWithTitleEnabled, version: "1.0.0", initializationResult: mockInitResult }));
1012
+ const { unmount } = renderAppContainer({
1013
+ settings: mockSettingsWithTitleEnabled,
1014
+ });
711
1015
  // Assert: Check that title was updated with CLI_TITLE value
712
1016
  const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
713
1017
  expect(titleWrites).toHaveLength(1);
@@ -723,7 +1027,7 @@ describe('AppContainer State Management', () => {
723
1027
  vi.useRealTimers();
724
1028
  });
725
1029
  it('should set and clear the queue error message after a timeout', async () => {
726
- const { rerender, unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1030
+ const { rerender, unmount } = renderAppContainer();
727
1031
  await act(async () => {
728
1032
  vi.advanceTimersByTime(0);
729
1033
  });
@@ -731,24 +1035,24 @@ describe('AppContainer State Management', () => {
731
1035
  act(() => {
732
1036
  capturedUIActions.setQueueErrorMessage('Test error');
733
1037
  });
734
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1038
+ rerender(getAppContainer());
735
1039
  expect(capturedUIState.queueErrorMessage).toBe('Test error');
736
1040
  act(() => {
737
1041
  vi.advanceTimersByTime(3000);
738
1042
  });
739
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1043
+ rerender(getAppContainer());
740
1044
  expect(capturedUIState.queueErrorMessage).toBeNull();
741
1045
  unmount();
742
1046
  });
743
1047
  it('should reset the timer if a new error message is set', async () => {
744
- const { rerender, unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1048
+ const { rerender, unmount } = renderAppContainer();
745
1049
  await act(async () => {
746
1050
  vi.advanceTimersByTime(0);
747
1051
  });
748
1052
  act(() => {
749
1053
  capturedUIActions.setQueueErrorMessage('First error');
750
1054
  });
751
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1055
+ rerender(getAppContainer());
752
1056
  expect(capturedUIState.queueErrorMessage).toBe('First error');
753
1057
  act(() => {
754
1058
  vi.advanceTimersByTime(1500);
@@ -756,18 +1060,18 @@ describe('AppContainer State Management', () => {
756
1060
  act(() => {
757
1061
  capturedUIActions.setQueueErrorMessage('Second error');
758
1062
  });
759
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1063
+ rerender(getAppContainer());
760
1064
  expect(capturedUIState.queueErrorMessage).toBe('Second error');
761
1065
  act(() => {
762
1066
  vi.advanceTimersByTime(2000);
763
1067
  });
764
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1068
+ rerender(getAppContainer());
765
1069
  expect(capturedUIState.queueErrorMessage).toBe('Second error');
766
1070
  // 5. Advance time past the 3 second timeout from the second message
767
1071
  act(() => {
768
1072
  vi.advanceTimersByTime(1000);
769
1073
  });
770
- rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1074
+ rerender(getAppContainer());
771
1075
  expect(capturedUIState.queueErrorMessage).toBeNull();
772
1076
  unmount();
773
1077
  });
@@ -789,7 +1093,7 @@ describe('AppContainer State Management', () => {
789
1093
  cancelOngoingRequest: vi.fn(),
790
1094
  activePtyId: 'some-id',
791
1095
  });
792
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1096
+ const { unmount } = renderAppContainer();
793
1097
  await act(async () => {
794
1098
  await new Promise((resolve) => setTimeout(resolve, 0));
795
1099
  });
@@ -810,11 +1114,11 @@ describe('AppContainer State Management', () => {
810
1114
  let unmount;
811
1115
  // Helper function to reduce boilerplate in tests
812
1116
  const setupKeypressTest = async () => {
813
- const renderResult = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1117
+ const renderResult = renderAppContainer();
814
1118
  await act(async () => {
815
1119
  vi.advanceTimersByTime(0);
816
1120
  });
817
- rerender = () => renderResult.rerender(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1121
+ rerender = () => renderResult.rerender(getAppContainer());
818
1122
  unmount = renderResult.unmount;
819
1123
  };
820
1124
  const pressKey = (key, times = 1) => {
@@ -949,11 +1253,11 @@ describe('AppContainer State Management', () => {
949
1253
  },
950
1254
  },
951
1255
  };
952
- const renderResult = render(_jsx(AppContainer, { config: mockConfig, settings: testSettings, version: "1.0.0", initializationResult: mockInitResult }));
1256
+ const renderResult = renderAppContainer({ settings: testSettings });
953
1257
  await act(async () => {
954
1258
  vi.advanceTimersByTime(0);
955
1259
  });
956
- rerender = () => renderResult.rerender(_jsx(AppContainer, { config: mockConfig, settings: testSettings, version: "1.0.0", initializationResult: mockInitResult }));
1260
+ rerender = () => renderResult.rerender(getAppContainer({ settings: testSettings }));
957
1261
  unmount = renderResult.unmount;
958
1262
  };
959
1263
  beforeEach(() => {
@@ -1078,7 +1382,7 @@ describe('AppContainer State Management', () => {
1078
1382
  openModelDialog: vi.fn(),
1079
1383
  closeModelDialog: vi.fn(),
1080
1384
  });
1081
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1385
+ const { unmount } = renderAppContainer();
1082
1386
  await act(async () => {
1083
1387
  await new Promise((resolve) => setTimeout(resolve, 0));
1084
1388
  });
@@ -1092,7 +1396,7 @@ describe('AppContainer State Management', () => {
1092
1396
  openModelDialog: vi.fn(),
1093
1397
  closeModelDialog: mockCloseModelDialog,
1094
1398
  });
1095
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1399
+ const { unmount } = renderAppContainer();
1096
1400
  await act(async () => {
1097
1401
  await new Promise((resolve) => setTimeout(resolve, 0));
1098
1402
  });
@@ -1106,7 +1410,7 @@ describe('AppContainer State Management', () => {
1106
1410
  });
1107
1411
  describe('CoreEvents Integration', () => {
1108
1412
  it('subscribes to UserFeedback and drains backlog on mount', async () => {
1109
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1413
+ const { unmount } = renderAppContainer();
1110
1414
  await act(async () => {
1111
1415
  await new Promise((resolve) => setTimeout(resolve, 0));
1112
1416
  });
@@ -1115,7 +1419,7 @@ describe('AppContainer State Management', () => {
1115
1419
  unmount();
1116
1420
  });
1117
1421
  it('unsubscribes from UserFeedback on unmount', async () => {
1118
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1422
+ const { unmount } = renderAppContainer();
1119
1423
  await act(async () => {
1120
1424
  await new Promise((resolve) => setTimeout(resolve, 0));
1121
1425
  });
@@ -1123,7 +1427,7 @@ describe('AppContainer State Management', () => {
1123
1427
  expect(mockCoreEvents.off).toHaveBeenCalledWith(CoreEvent.UserFeedback, expect.any(Function));
1124
1428
  });
1125
1429
  it('adds history item when UserFeedback event is received', async () => {
1126
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1430
+ const { unmount } = renderAppContainer();
1127
1431
  await act(async () => {
1128
1432
  await new Promise((resolve) => setTimeout(resolve, 0));
1129
1433
  });
@@ -1147,8 +1451,7 @@ describe('AppContainer State Management', () => {
1147
1451
  it('updates currentModel when ModelChanged event is received', async () => {
1148
1452
  // Arrange: Mock initial model
1149
1453
  vi.spyOn(mockConfig, 'getModel').mockReturnValue('initial-model');
1150
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1151
- // Verify initial model
1454
+ const { unmount } = renderAppContainer();
1152
1455
  await act(async () => {
1153
1456
  await vi.waitFor(() => {
1154
1457
  expect(capturedUIState?.currentModel).toBe('initial-model');
@@ -1183,7 +1486,7 @@ describe('AppContainer State Management', () => {
1183
1486
  activePtyId: 'some-pty-id', // Make sure activePtyId is set
1184
1487
  });
1185
1488
  // The main assertion is that the render does not throw.
1186
- const { unmount } = render(_jsx(AppContainer, { config: mockConfig, settings: mockSettings, version: "1.0.0", initializationResult: mockInitResult }));
1489
+ const { unmount } = renderAppContainer();
1187
1490
  await act(async () => {
1188
1491
  await new Promise((resolve) => setTimeout(resolve, 0));
1189
1492
  });