@machina.ai/cell-cli 1.56.0-rc1 → 1.57.0-rc1

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 (46) hide show
  1. package/dist/package.json +3 -3
  2. package/dist/src/config/config.js +5 -3
  3. package/dist/src/config/config.js.map +1 -1
  4. package/dist/src/config/config.test.js +5 -5
  5. package/dist/src/config/extension.test.js +1 -0
  6. package/dist/src/config/extension.test.js.map +1 -1
  7. package/dist/src/config/extensions/github.js +3 -3
  8. package/dist/src/config/extensions/github.js.map +1 -1
  9. package/dist/src/config/extensions/github.test.js +2 -0
  10. package/dist/src/config/extensions/github.test.js.map +1 -1
  11. package/dist/src/generated/git-commit.d.ts +2 -2
  12. package/dist/src/generated/git-commit.js +2 -2
  13. package/dist/src/nonInteractiveCliAgentSession.test.js +10 -2
  14. package/dist/src/nonInteractiveCliAgentSession.test.js.map +1 -1
  15. package/dist/src/ui/AppContainer.js +4 -1
  16. package/dist/src/ui/AppContainer.js.map +1 -1
  17. package/dist/src/ui/AppContainer.test.js +25 -2
  18. package/dist/src/ui/AppContainer.test.js.map +1 -1
  19. package/dist/src/ui/commands/modelCommand.js +1 -0
  20. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  21. package/dist/src/ui/components/AsciiArt.d.ts +6 -6
  22. package/dist/src/ui/components/AsciiArt.js +6 -6
  23. package/dist/src/ui/components/ModelDialog.js +16 -3
  24. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  25. package/dist/src/ui/components/ModelDialog.test.js +48 -78
  26. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  27. package/dist/src/ui/hooks/useCommandCompletion.js +1 -9
  28. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  29. package/dist/src/ui/hooks/useCommandCompletion.test.js +2 -2
  30. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  31. package/dist/src/ui/hooks/useGeminiStream.js +31 -11
  32. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  33. package/dist/src/ui/hooks/useGeminiStream.test.js +117 -30
  34. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  35. package/dist/src/ui/hooks/useQuotaAndFallback.d.ts +1 -1
  36. package/dist/src/ui/hooks/useQuotaAndFallback.js +5 -2
  37. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  38. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +41 -3
  39. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  40. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js +11 -3
  41. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js.map +1 -1
  42. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js +12 -0
  43. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js.map +1 -1
  44. package/dist/src/utils/handleAutoUpdate.test.js +2 -2
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +3 -3
@@ -31,6 +31,11 @@ const MockedGeminiClientClass = vi.hoisted(() => vi.fn().mockImplementation(func
31
31
  this.startChat = mockStartChat;
32
32
  this.sendMessageStream = mockSendMessageStream;
33
33
  this.addHistory = vi.fn();
34
+ let mockHistory = [];
35
+ this.getHistory = vi.fn().mockImplementation(() => mockHistory);
36
+ this.setHistory = vi.fn().mockImplementation((newHistory) => {
37
+ mockHistory = [...newHistory];
38
+ });
34
39
  this.generateContent = vi.fn().mockResolvedValue({
35
40
  candidates: [
36
41
  { content: { parts: [{ text: 'Got it. Focusing on tests only.' }] } },
@@ -554,6 +559,12 @@ describe('useGeminiStream', () => {
554
559
  0,
555
560
  ];
556
561
  });
562
+ mockSendMessageStream.mockReturnValue((async function* () {
563
+ yield {
564
+ type: ServerGeminiEventType.Content,
565
+ value: 'Visible response text',
566
+ };
567
+ })());
557
568
  await renderHookWithProviders(() => useGeminiStream(new MockedGeminiClientClass(mockConfig), [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24));
558
569
  // Trigger the onComplete callback with completed tools
559
570
  await act(async () => {
@@ -646,7 +657,73 @@ describe('useGeminiStream', () => {
646
657
  input: sentParts,
647
658
  });
648
659
  });
649
- it('should handle all tool calls being cancelled', async () => {
660
+ it('should auto-nudge the model when tool execution succeeds but model stream is empty', async () => {
661
+ const toolCallResponseParts = [{ text: 'tool final response' }];
662
+ const completedToolCalls = [
663
+ {
664
+ request: {
665
+ callId: 'call1',
666
+ name: 'tool1',
667
+ args: {},
668
+ isClientInitiated: false,
669
+ prompt_id: 'prompt-id-ack',
670
+ },
671
+ status: 'success',
672
+ responseSubmittedToGemini: false,
673
+ response: {
674
+ callId: 'call1',
675
+ responseParts: toolCallResponseParts,
676
+ errorType: undefined,
677
+ },
678
+ tool: {
679
+ displayName: 'MockTool',
680
+ },
681
+ invocation: {
682
+ getDescription: () => `Mock description`,
683
+ },
684
+ },
685
+ ];
686
+ let callCount = 0;
687
+ mockSendMessageStream.mockImplementation(() => {
688
+ callCount += 1;
689
+ if (callCount === 1) {
690
+ return (async function* () { })();
691
+ }
692
+ else {
693
+ return (async function* () {
694
+ yield {
695
+ type: ServerGeminiEventType.Content,
696
+ value: 'I have analyzed the empty response. Here is the final answer.',
697
+ };
698
+ })();
699
+ }
700
+ });
701
+ let capturedOnComplete = null;
702
+ mockUseToolScheduler.mockImplementation((onComplete) => {
703
+ capturedOnComplete = onComplete;
704
+ return [
705
+ [],
706
+ mockScheduleToolCalls,
707
+ mockMarkToolsAsSubmitted,
708
+ vi.fn(),
709
+ mockCancelAllToolCalls,
710
+ 0,
711
+ ];
712
+ });
713
+ await renderHookWithProviders(() => useGeminiStream(new MockedGeminiClientClass(mockConfig), [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24, undefined, () => 'focus on tests only'));
714
+ await act(async () => {
715
+ if (capturedOnComplete) {
716
+ await new Promise((resolve) => setTimeout(resolve, 0));
717
+ await capturedOnComplete(completedToolCalls);
718
+ }
719
+ });
720
+ await waitFor(() => {
721
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
722
+ });
723
+ const sentParts = mockSendMessageStream.mock.calls[1][0];
724
+ expect(sentParts[0].text).toContain('[System: You successfully executed a tool but returned an empty response. Please analyze the tool output and explain your progress or final answer.]');
725
+ });
726
+ it('should handle all tool calls being cancelled by rolling back the history', async () => {
650
727
  const cancelledToolCalls = [
651
728
  {
652
729
  request: {
@@ -696,6 +773,7 @@ describe('useGeminiStream', () => {
696
773
  },
697
774
  ];
698
775
  const client = new MockedGeminiClientClass(mockConfig);
776
+ client.setHistory([{ role: 'user', parts: [{ text: 'User prompt' }] }]);
699
777
  // Capture the onComplete callback
700
778
  let capturedOnComplete = null;
701
779
  mockUseToolScheduler.mockImplementation((onComplete) => {
@@ -709,7 +787,20 @@ describe('useGeminiStream', () => {
709
787
  0,
710
788
  ];
711
789
  });
712
- await renderHookWithProviders(() => useGeminiStream(client, [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24));
790
+ const { result } = await renderHookWithProviders(() => useGeminiStream(client, [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24));
791
+ // Call submitQuery to populate the user turn and set historyLengthAfterUserPromptRef
792
+ await act(async () => {
793
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
794
+ result.current.submitQuery('User prompt');
795
+ });
796
+ // Model issues a functionCall request, which appends to history
797
+ client.setHistory([
798
+ { role: 'user', parts: [{ text: 'User prompt' }] },
799
+ {
800
+ role: 'model',
801
+ parts: [{ functionCall: { name: 'testTool', args: {} } }],
802
+ },
803
+ ]);
713
804
  // Trigger the onComplete callback with cancelled tools
714
805
  await act(async () => {
715
806
  if (capturedOnComplete) {
@@ -720,21 +811,12 @@ describe('useGeminiStream', () => {
720
811
  });
721
812
  await waitFor(() => {
722
813
  expect(mockMarkToolsAsSubmitted).toHaveBeenCalledWith(['topic1', '1']);
723
- expect(client.addHistory).toHaveBeenCalledWith({
724
- role: 'user',
725
- parts: [
726
- {
727
- functionResponse: {
728
- name: UPDATE_TOPIC_TOOL_NAME,
729
- id: 'topic1',
730
- response: {},
731
- },
732
- },
733
- { text: CoreToolCallStatus.Cancelled },
734
- ],
735
- });
736
- // Ensure we do NOT call back to the API
737
- expect(mockSendMessageStream).not.toHaveBeenCalled();
814
+ // Should NOT have appended cancellations via addHistory
815
+ expect(client.addHistory).not.toHaveBeenCalled();
816
+ // Should have rolled history back to pre-model length (1)
817
+ expect(client.getHistory().length).toBe(1);
818
+ // Ensure we do NOT call back to the API a second time (only the initial user turn was sent)
819
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
738
820
  });
739
821
  });
740
822
  it('should record tool responses in history when the model was switched due to a quota error', async () => {
@@ -972,7 +1054,7 @@ describe('useGeminiStream', () => {
972
1054
  expect(failureHintIndex).toBe(-1);
973
1055
  expect(noteIndex).toBeLessThan(stopIndex);
974
1056
  });
975
- it('should group multiple cancelled tool call responses into a single history entry', async () => {
1057
+ it('should rollback multiple cancelled tool calls rather than appending them to history', async () => {
976
1058
  const cancelledToolCall1 = {
977
1059
  request: {
978
1060
  callId: 'cancel-1',
@@ -1033,6 +1115,7 @@ describe('useGeminiStream', () => {
1033
1115
  };
1034
1116
  const allCancelledTools = [cancelledToolCall1, cancelledToolCall2];
1035
1117
  const client = new MockedGeminiClientClass(mockConfig);
1118
+ client.setHistory([{ role: 'user', parts: [{ text: 'User prompt' }] }]);
1036
1119
  let capturedOnComplete = null;
1037
1120
  mockUseToolScheduler.mockImplementation((onComplete) => {
1038
1121
  capturedOnComplete = onComplete;
@@ -1045,7 +1128,17 @@ describe('useGeminiStream', () => {
1045
1128
  0,
1046
1129
  ];
1047
1130
  });
1048
- await renderHookWithProviders(() => useGeminiStream(client, [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24));
1131
+ const { result } = await renderHookWithProviders(() => useGeminiStream(client, [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, 80, 24));
1132
+ // Call submitQuery to populate the user turn and set historyLengthAfterUserPromptRef
1133
+ await act(async () => {
1134
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1135
+ result.current.submitQuery('User prompt');
1136
+ });
1137
+ // Model issues model turns, which appends to history
1138
+ client.setHistory([
1139
+ { role: 'user', parts: [{ text: 'User prompt' }] },
1140
+ { role: 'model', parts: [{ functionCall: { name: 'toolA', args: {} } }] },
1141
+ ]);
1049
1142
  // Trigger the onComplete callback with multiple cancelled tools
1050
1143
  await act(async () => {
1051
1144
  if (capturedOnComplete) {
@@ -1060,18 +1153,12 @@ describe('useGeminiStream', () => {
1060
1153
  'cancel-1',
1061
1154
  'cancel-2',
1062
1155
  ]);
1063
- // Crucially, addHistory should be called only ONCE
1064
- expect(client.addHistory).toHaveBeenCalledTimes(1);
1065
- // And that single call should contain BOTH function responses
1066
- expect(client.addHistory).toHaveBeenCalledWith({
1067
- role: 'user',
1068
- parts: [
1069
- ...cancelledToolCall1.response.responseParts,
1070
- ...cancelledToolCall2.response.responseParts,
1071
- ],
1072
- });
1156
+ // Crucially, addHistory should NOT be called
1157
+ expect(client.addHistory).not.toHaveBeenCalled();
1158
+ // Instead, history should be rolled back to pre-model length (1)
1159
+ expect(client.getHistory().length).toBe(1);
1073
1160
  // No message should be sent back to the API for a turn with only cancellations
1074
- expect(mockSendMessageStream).not.toHaveBeenCalled();
1161
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
1075
1162
  });
1076
1163
  });
1077
1164
  it('should not flicker streaming state to Idle between tool completion and submission', async () => {