@machina.ai/cell-cli 1.51.0-rc2 → 1.54.4-rc2

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 (50) hide show
  1. package/dist/package.json +3 -3
  2. package/dist/src/acp/acpSession.js +6 -1
  3. package/dist/src/acp/acpSession.js.map +1 -1
  4. package/dist/src/acp/acpSession.test.js +22 -0
  5. package/dist/src/acp/acpSession.test.js.map +1 -1
  6. package/dist/src/config/config.js +9 -3
  7. package/dist/src/config/config.js.map +1 -1
  8. package/dist/src/config/config.test.js +15 -2
  9. package/dist/src/config/config.test.js.map +1 -1
  10. package/dist/src/generated/git-commit.d.ts +2 -2
  11. package/dist/src/generated/git-commit.js +2 -2
  12. package/dist/src/nonInteractiveCli.js +27 -3
  13. package/dist/src/nonInteractiveCli.js.map +1 -1
  14. package/dist/src/nonInteractiveCli.test.js +48 -8
  15. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  16. package/dist/src/nonInteractiveCliAgentSession.js +48 -5
  17. package/dist/src/nonInteractiveCliAgentSession.js.map +1 -1
  18. package/dist/src/nonInteractiveCliAgentSession.test.js +93 -1
  19. package/dist/src/nonInteractiveCliAgentSession.test.js.map +1 -1
  20. package/dist/src/ui/components/AsciiArt.d.ts +6 -6
  21. package/dist/src/ui/components/AsciiArt.js +6 -6
  22. package/dist/src/ui/components/ModelDialog.js +52 -11
  23. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  24. package/dist/src/ui/components/ModelDialog.test.js +122 -66
  25. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  26. package/dist/src/ui/contexts/SessionContext.js +12 -0
  27. package/dist/src/ui/contexts/SessionContext.js.map +1 -1
  28. package/dist/src/ui/hooks/useGeminiStream.js +81 -17
  29. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  30. package/dist/src/ui/hooks/useGeminiStream.test.js +218 -3
  31. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  32. package/dist/src/ui/hooks/usePrivacySettings.d.ts +7 -0
  33. package/dist/src/ui/hooks/usePrivacySettings.js +46 -3
  34. package/dist/src/ui/hooks/usePrivacySettings.js.map +1 -1
  35. package/dist/src/ui/hooks/usePrivacySettings.test.js +49 -4
  36. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  37. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +1 -1
  38. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  39. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js +6 -1
  40. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js.map +1 -1
  41. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js +10 -0
  42. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js.map +1 -1
  43. package/dist/src/utils/handleAutoUpdate.test.js +2 -2
  44. package/dist/src/utils/sandbox-macos-permissive-open.sb +113 -4
  45. package/dist/src/utils/sandbox-macos-permissive-proxied.sb +111 -9
  46. package/dist/src/utils/sandbox-macos-profiles.test.d.ts +6 -0
  47. package/dist/src/utils/sandbox-macos-profiles.test.js +65 -0
  48. package/dist/src/utils/sandbox-macos-profiles.test.js.map +1 -0
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +3 -3
@@ -12,7 +12,7 @@ import { useGeminiStream } from './useGeminiStream.js';
12
12
  import { useKeypress } from './useKeypress.js';
13
13
  import * as atCommandProcessor from './atCommandProcessor.js';
14
14
  import { useToolScheduler, } from './useToolScheduler.js';
15
- import { CoreToolCallStatus, ApprovalMode, AuthType, GeminiEventType as ServerGeminiEventType, ToolErrorType, ToolConfirmationOutcome, MessageBusType, tokenLimit, debugLogger, coreEvents, CoreEvent, SHELL_TOOL_NAME, MCPDiscoveryState, GeminiCliOperation, getPlanModeExitMessage, UPDATE_TOPIC_TOOL_NAME, } from '@google/gemini-cli-core';
15
+ import { CoreToolCallStatus, ApprovalMode, DEFAULT_GEMINI_FLASH_MODEL, AuthType, GeminiEventType as ServerGeminiEventType, ToolErrorType, ToolConfirmationOutcome, MessageBusType, tokenLimit, debugLogger, coreEvents, CoreEvent, SHELL_TOOL_NAME, MCPDiscoveryState, GeminiCliOperation, getPlanModeExitMessage, UPDATE_TOPIC_TOOL_NAME, TRUE_EMPTY_RESPONSE_MESSAGE, } from '@google/gemini-cli-core';
16
16
  import { MessageType, StreamingState } from '../types.js';
17
17
  import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
18
18
  import { theme } from '../semantic-colors.js';
@@ -737,6 +737,77 @@ describe('useGeminiStream', () => {
737
737
  expect(mockSendMessageStream).not.toHaveBeenCalled();
738
738
  });
739
739
  });
740
+ it('should record tool responses in history when the model was switched due to a quota error', async () => {
741
+ // Regression test: returning early on a quota-triggered model switch
742
+ // without recording the responses leaves the already-recorded
743
+ // functionCall unpaired, which corrupts all subsequent requests.
744
+ const responseParts = [
745
+ {
746
+ functionResponse: {
747
+ name: 'testTool',
748
+ id: 'call1',
749
+ response: { output: 'tool result' },
750
+ },
751
+ },
752
+ ];
753
+ const completedToolCalls = [
754
+ {
755
+ request: {
756
+ callId: 'call1',
757
+ name: 'testTool',
758
+ args: {},
759
+ isClientInitiated: false,
760
+ prompt_id: 'prompt-id-quota',
761
+ },
762
+ status: CoreToolCallStatus.Success,
763
+ responseSubmittedToGemini: false,
764
+ response: {
765
+ callId: 'call1',
766
+ responseParts,
767
+ errorType: undefined,
768
+ },
769
+ tool: { displayName: 'MockTool' },
770
+ invocation: {
771
+ getDescription: () => `Mock description`,
772
+ },
773
+ },
774
+ ];
775
+ const client = new MockedGeminiClientClass(mockConfig);
776
+ const mockConsumeUserHint = vi.fn(() => 'switch to the nprd database');
777
+ let capturedOnComplete = null;
778
+ mockUseToolScheduler.mockImplementation((onComplete) => {
779
+ capturedOnComplete = onComplete;
780
+ return [
781
+ [],
782
+ mockScheduleToolCalls,
783
+ mockMarkToolsAsSubmitted,
784
+ vi.fn(),
785
+ mockCancelAllToolCalls,
786
+ 0,
787
+ ];
788
+ });
789
+ await renderHookWithProviders(() => useGeminiStream(client, [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), true, // modelSwitchedFromQuotaError
790
+ () => { }, () => { }, () => { }, 80, 24, false, mockConsumeUserHint));
791
+ await act(async () => {
792
+ if (capturedOnComplete) {
793
+ await new Promise((resolve) => setTimeout(resolve, 0));
794
+ await capturedOnComplete(completedToolCalls);
795
+ }
796
+ });
797
+ await waitFor(() => {
798
+ expect(mockMarkToolsAsSubmitted).toHaveBeenCalledWith(['call1']);
799
+ // The tool response must be paired with its functionCall in history,
800
+ // with no steering-hint text ahead of it...
801
+ expect(client.addHistory).toHaveBeenCalledWith({
802
+ role: 'user',
803
+ parts: responseParts,
804
+ });
805
+ // ...the turn must NOT auto-continue on the fallback model...
806
+ expect(mockSendMessageStream).not.toHaveBeenCalled();
807
+ // ...and the pending hint is left for the next real submit.
808
+ expect(mockConsumeUserHint).not.toHaveBeenCalled();
809
+ });
810
+ });
740
811
  it('should NOT stop responding when only update_topic is called', async () => {
741
812
  const topicToolCalls = [
742
813
  {
@@ -1248,6 +1319,106 @@ describe('useGeminiStream', () => {
1248
1319
  // The cancel function should be called
1249
1320
  expect(mockCancelAllToolCalls).toHaveBeenCalled();
1250
1321
  });
1322
+ it('should transition to Idle state when cancelled while a tool call is in progress and completes', async () => {
1323
+ const toolCalls = [
1324
+ {
1325
+ request: { callId: 'call1', name: 'tool1', args: {} },
1326
+ status: CoreToolCallStatus.Executing,
1327
+ responseSubmittedToGemini: false,
1328
+ tool: {
1329
+ name: 'tool1',
1330
+ description: 'desc1',
1331
+ build: vi.fn().mockImplementation((_) => ({
1332
+ getDescription: () => `Mock description`,
1333
+ })),
1334
+ },
1335
+ invocation: {
1336
+ getDescription: () => `Mock description`,
1337
+ },
1338
+ startTime: Date.now(),
1339
+ liveOutput: '...',
1340
+ },
1341
+ ];
1342
+ const { result } = await renderTestHook(toolCalls);
1343
+ // State is `Responding` because a tool is running
1344
+ expect(result.current.streamingState).toBe(StreamingState.Responding);
1345
+ // Try to cancel
1346
+ simulateEscapeKeyPress();
1347
+ // Trigger the onComplete callback with the cancelled tool call
1348
+ await act(async () => {
1349
+ if (capturedOnComplete) {
1350
+ await capturedOnComplete([
1351
+ {
1352
+ ...toolCalls[0],
1353
+ status: CoreToolCallStatus.Cancelled,
1354
+ response: {
1355
+ callId: 'call1',
1356
+ responseParts: [],
1357
+ },
1358
+ },
1359
+ ]);
1360
+ }
1361
+ });
1362
+ // The final state should be idle because the cancelled tool call was marked as submitted
1363
+ expect(result.current.streamingState).toBe(StreamingState.Idle);
1364
+ });
1365
+ it('should append cancelled tool responses to history when cancelled while a tool call is in progress and completes with response parts', async () => {
1366
+ const toolCalls = [
1367
+ {
1368
+ request: { callId: 'call1', name: 'tool1', args: {} },
1369
+ status: CoreToolCallStatus.Executing,
1370
+ responseSubmittedToGemini: false,
1371
+ tool: {
1372
+ name: 'tool1',
1373
+ description: 'desc1',
1374
+ build: vi.fn().mockImplementation((_) => ({
1375
+ getDescription: () => `Mock description`,
1376
+ })),
1377
+ },
1378
+ invocation: {
1379
+ getDescription: () => `Mock description`,
1380
+ },
1381
+ startTime: Date.now(),
1382
+ liveOutput: '...',
1383
+ },
1384
+ ];
1385
+ const { result, client } = await renderTestHook(toolCalls);
1386
+ // State is `Responding` because a tool is running
1387
+ expect(result.current.streamingState).toBe(StreamingState.Responding);
1388
+ // Try to cancel
1389
+ simulateEscapeKeyPress();
1390
+ const expectedResponseParts = [
1391
+ {
1392
+ functionResponse: {
1393
+ name: 'tool1',
1394
+ id: 'call1',
1395
+ response: { error: 'cancelled' },
1396
+ },
1397
+ },
1398
+ ];
1399
+ // Trigger the onComplete callback with the cancelled tool call having non-empty response parts
1400
+ await act(async () => {
1401
+ if (capturedOnComplete) {
1402
+ await capturedOnComplete([
1403
+ {
1404
+ ...toolCalls[0],
1405
+ status: CoreToolCallStatus.Cancelled,
1406
+ response: {
1407
+ callId: 'call1',
1408
+ responseParts: expectedResponseParts,
1409
+ },
1410
+ },
1411
+ ]);
1412
+ }
1413
+ });
1414
+ // Assert that addHistory was called with the combined response parts
1415
+ expect(client.addHistory).toHaveBeenCalledWith({
1416
+ role: 'user',
1417
+ parts: expectedResponseParts,
1418
+ });
1419
+ // The final state should be idle because the cancelled tool call was marked as submitted
1420
+ expect(result.current.streamingState).toBe(StreamingState.Idle);
1421
+ });
1251
1422
  it('should cancel a request when a tool is awaiting confirmation', async () => {
1252
1423
  const mockOnConfirm = vi.fn().mockResolvedValue(undefined);
1253
1424
  const toolCalls = [
@@ -1616,7 +1787,51 @@ describe('useGeminiStream', () => {
1616
1787
  });
1617
1788
  // 3. Assertion
1618
1789
  await waitFor(() => {
1619
- expect(mockParseAndFormatApiError).toHaveBeenCalledWith('Rate limit exceeded', mockAuthType, undefined, 'gemini-2.5-pro', 'gemini-2.5-flash');
1790
+ expect(mockParseAndFormatApiError).toHaveBeenCalledWith('Rate limit exceeded', mockAuthType, undefined, 'gemini-2.5-pro', DEFAULT_GEMINI_FLASH_MODEL);
1791
+ });
1792
+ });
1793
+ it('should use TRUE_EMPTY_RESPONSE_MESSAGE when receiving an invalid stream event of type NO_RESPONSE_TEXT', async () => {
1794
+ mockSendMessageStream.mockClear();
1795
+ mockSendMessageStream.mockReturnValue((async function* () {
1796
+ yield {
1797
+ type: ServerGeminiEventType.InvalidStream,
1798
+ value: {
1799
+ type: 'NO_RESPONSE_TEXT',
1800
+ message: 'empty response text',
1801
+ },
1802
+ };
1803
+ })());
1804
+ const { result } = await renderTestHook();
1805
+ await act(async () => {
1806
+ await result.current.submitQuery('test query');
1807
+ });
1808
+ await waitFor(() => {
1809
+ expect(mockAddItem).toHaveBeenCalledWith(expect.objectContaining({
1810
+ type: MessageType.ERROR,
1811
+ text: TRUE_EMPTY_RESPONSE_MESSAGE,
1812
+ }), expect.any(Number));
1813
+ });
1814
+ });
1815
+ it('should use the event message when receiving a non-NO_RESPONSE_TEXT invalid stream event', async () => {
1816
+ mockSendMessageStream.mockClear();
1817
+ mockSendMessageStream.mockReturnValue((async function* () {
1818
+ yield {
1819
+ type: ServerGeminiEventType.InvalidStream,
1820
+ value: {
1821
+ type: 'MALFORMED_FUNCTION_CALL',
1822
+ message: 'Custom malformed function call message',
1823
+ },
1824
+ };
1825
+ })());
1826
+ const { result } = await renderTestHook();
1827
+ await act(async () => {
1828
+ await result.current.submitQuery('test query');
1829
+ });
1830
+ await waitFor(() => {
1831
+ expect(mockAddItem).toHaveBeenCalledWith(expect.objectContaining({
1832
+ type: MessageType.ERROR,
1833
+ text: 'Custom malformed function call message',
1834
+ }), expect.any(Number));
1620
1835
  });
1621
1836
  });
1622
1837
  });
@@ -2369,7 +2584,7 @@ describe('useGeminiStream', () => {
2369
2584
  }), expect.any(Number));
2370
2585
  });
2371
2586
  // Verify parseAndFormatApiError was called
2372
- expect(mockParseAndFormatApiError).toHaveBeenCalledWith({ message: 'Test error' }, expect.any(String), undefined, 'gemini-2.5-pro', 'gemini-2.5-flash');
2587
+ expect(mockParseAndFormatApiError).toHaveBeenCalledWith({ message: 'Test error' }, expect.any(String), undefined, 'gemini-2.5-pro', DEFAULT_GEMINI_FLASH_MODEL);
2373
2588
  });
2374
2589
  it('should update lastOutputTime on Gemini thought and content events', async () => {
2375
2590
  vi.useFakeTimers();