@machina.ai/cell-cli 1.64.0-rc2 → 1.66.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.
- package/dist/index.js +14 -9
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -4
- package/dist/src/acp/acpSession.js +9 -13
- package/dist/src/acp/acpSession.js.map +1 -1
- package/dist/src/acp/acpSession.test.js +88 -0
- package/dist/src/acp/acpSession.test.js.map +1 -1
- package/dist/src/commands/mcp/add.js +14 -6
- package/dist/src/commands/mcp/add.js.map +1 -1
- package/dist/src/commands/mcp/add.test.js +61 -0
- package/dist/src/commands/mcp/add.test.js.map +1 -1
- package/dist/src/commands/mcp/remove.js +14 -1
- package/dist/src/commands/mcp/remove.js.map +1 -1
- package/dist/src/commands/mcp/remove.test.js +63 -0
- package/dist/src/commands/mcp/remove.test.js.map +1 -1
- package/dist/src/config/config.js +12 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +17 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/envDefaults.d.ts +20 -0
- package/dist/src/config/envDefaults.js +29 -0
- package/dist/src/config/envDefaults.js.map +1 -0
- package/dist/src/config/envDefaults.test.d.ts +6 -0
- package/dist/src/config/envDefaults.test.js +23 -0
- package/dist/src/config/envDefaults.test.js.map +1 -0
- package/dist/src/config/extension-manager.js +13 -4
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +117 -0
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +34 -0
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.js +110 -18
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.js +251 -36
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -1
- package/dist/src/config/settings.d.ts +1 -0
- package/dist/src/config/settings.js +148 -58
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settings.test.js +300 -0
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +1 -1
- package/dist/src/config/settingsSchema.js +6 -1
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/gemini.test.js +69 -0
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/interactiveCli.js +8 -3
- package/dist/src/interactiveCli.js.map +1 -1
- package/dist/src/services/CommandService.js +1 -1
- package/dist/src/services/CommandService.js.map +1 -1
- package/dist/src/services/CommandService.test.js +18 -1
- package/dist/src/services/CommandService.test.js.map +1 -1
- package/dist/src/services/SlashCommandConflictHandler.d.ts +1 -0
- package/dist/src/services/SlashCommandConflictHandler.js +18 -2
- package/dist/src/services/SlashCommandConflictHandler.js.map +1 -1
- package/dist/src/services/SlashCommandConflictHandler.test.js +85 -8
- package/dist/src/services/SlashCommandConflictHandler.test.js.map +1 -1
- package/dist/src/ui/AppContainer.js +254 -12
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +362 -6
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.js +21 -5
- package/dist/src/ui/auth/AuthDialog.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.test.js +83 -0
- package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
- package/dist/src/ui/auth/useAuth.js +7 -1
- package/dist/src/ui/auth/useAuth.js.map +1 -1
- package/dist/src/ui/auth/useAuth.test.js +9 -0
- package/dist/src/ui/auth/useAuth.test.js.map +1 -1
- package/dist/src/ui/commands/marketplaceCommand.js +92 -4
- package/dist/src/ui/commands/marketplaceCommand.js.map +1 -1
- package/dist/src/ui/commands/marketplaceCommand.test.js +98 -1
- package/dist/src/ui/commands/marketplaceCommand.test.js.map +1 -1
- package/dist/src/ui/components/AsciiArt.d.ts +6 -6
- package/dist/src/ui/components/AsciiArt.js +6 -6
- package/dist/src/ui/components/CliSpinner.d.ts +3 -2
- package/dist/src/ui/components/CliSpinner.js +27 -3
- package/dist/src/ui/components/CliSpinner.js.map +1 -1
- package/dist/src/ui/components/CliSpinner.test.js +42 -3
- package/dist/src/ui/components/CliSpinner.test.js.map +1 -1
- package/dist/src/ui/components/Composer.js +3 -3
- package/dist/src/ui/components/Composer.js.map +1 -1
- package/dist/src/ui/components/Composer.test.js +39 -1
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js +5 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.test.js +19 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.test.js.map +1 -1
- package/dist/src/ui/components/Footer.js +1 -1
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/GeminiRespondingSpinner.d.ts +1 -1
- package/dist/src/ui/components/GeminiRespondingSpinner.js +9 -2
- package/dist/src/ui/components/GeminiRespondingSpinner.js.map +1 -1
- package/dist/src/ui/components/GeminiSpinner.d.ts +1 -1
- package/dist/src/ui/components/GeminiSpinner.js +18 -7
- package/dist/src/ui/components/GeminiSpinner.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +5 -2
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.test.js +21 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
- package/dist/src/ui/components/LoadingIndicator.d.ts +1 -1
- package/dist/src/ui/components/LoadingIndicator.js +4 -2
- package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/src/ui/components/MainContent.js +47 -9
- package/dist/src/ui/components/MainContent.js.map +1 -1
- package/dist/src/ui/components/MainContent.test.js +87 -1
- package/dist/src/ui/components/MainContent.test.js.map +1 -1
- package/dist/src/ui/components/MemoryUsageDisplay.js +8 -4
- package/dist/src/ui/components/MemoryUsageDisplay.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.js +30 -16
- package/dist/src/ui/components/ModelDialog.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +32 -24
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/StatusRow.d.ts +7 -6
- package/dist/src/ui/components/StatusRow.js +24 -10
- package/dist/src/ui/components/StatusRow.js.map +1 -1
- package/dist/src/ui/components/ValidationDialog.js +13 -6
- package/dist/src/ui/components/ValidationDialog.js.map +1 -1
- package/dist/src/ui/components/ValidationDialog.test.js +40 -0
- package/dist/src/ui/components/ValidationDialog.test.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.js +17 -12
- package/dist/src/ui/components/messages/DenseToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.test.js +71 -0
- package/dist/src/ui/components/messages/DenseToolMessage.test.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.js +14 -11
- package/dist/src/ui/components/messages/ToolResultDisplay.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.test.js +32 -0
- package/dist/src/ui/components/messages/ToolResultDisplay.test.js.map +1 -1
- package/dist/src/ui/components/shared/MaxSizedBox.js +8 -2
- package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
- package/dist/src/ui/components/shared/MaxSizedBox.test.js +23 -1
- package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
- package/dist/src/ui/contexts/ToolActionsContext.js +7 -1
- package/dist/src/ui/contexts/ToolActionsContext.js.map +1 -1
- package/dist/src/ui/contexts/ToolActionsContext.test.js +54 -1
- package/dist/src/ui/contexts/ToolActionsContext.test.js.map +1 -1
- package/dist/src/ui/hooks/atCommandProcessor.d.ts +2 -1
- package/dist/src/ui/hooks/atCommandProcessor.js +15 -15
- package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useAgentStream.d.ts +9 -2
- package/dist/src/ui/hooks/useAgentStream.js +173 -8
- package/dist/src/ui/hooks/useAgentStream.js.map +1 -1
- package/dist/src/ui/hooks/useAgentStream.test.js +104 -1
- package/dist/src/ui/hooks/useAgentStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +174 -59
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +230 -3
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.d.ts +2 -1
- package/dist/src/ui/hooks/useLoadingIndicator.js +3 -3
- package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +23 -4
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.js +10 -6
- package/dist/src/ui/hooks/useReverseSearchCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +49 -0
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
- package/dist/src/ui/key/keyBindings.js +19 -6
- package/dist/src/ui/key/keyBindings.js.map +1 -1
- package/dist/src/ui/key/keyBindings.test.js +106 -0
- package/dist/src/ui/key/keyBindings.test.js.map +1 -1
- package/dist/src/ui/utils/formatters.d.ts +2 -0
- package/dist/src/ui/utils/formatters.js +13 -5
- package/dist/src/ui/utils/formatters.js.map +1 -1
- package/dist/src/ui/utils/formatters.test.js +18 -0
- package/dist/src/ui/utils/formatters.test.js.map +1 -1
- package/dist/src/utils/activityLogger.js +109 -33
- package/dist/src/utils/activityLogger.js.map +1 -1
- package/dist/src/utils/activityLogger.test.js +441 -0
- package/dist/src/utils/activityLogger.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.d.ts +4 -1
- package/dist/src/utils/handleAutoUpdate.js +45 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.test.js +280 -2
- package/dist/src/utils/handleAutoUpdate.test.js.map +1 -1
- package/dist/src/utils/nodeVersion.d.ts +11 -0
- package/dist/src/utils/nodeVersion.js +42 -0
- package/dist/src/utils/nodeVersion.js.map +1 -0
- package/dist/src/utils/nodeVersion.test.d.ts +6 -0
- package/dist/src/utils/nodeVersion.test.js +35 -0
- package/dist/src/utils/nodeVersion.test.js.map +1 -0
- package/dist/src/utils/processUtils.d.ts +23 -0
- package/dist/src/utils/processUtils.js +72 -0
- package/dist/src/utils/processUtils.js.map +1 -1
- package/dist/src/utils/processUtils.test.js +95 -1
- package/dist/src/utils/processUtils.test.js.map +1 -1
- package/dist/src/utils/relaunch.js +8 -6
- package/dist/src/utils/relaunch.js.map +1 -1
- package/dist/src/utils/relaunch.test.js +107 -1
- package/dist/src/utils/relaunch.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -170,6 +170,7 @@ vi.mock('../utils/events.js');
|
|
|
170
170
|
vi.mock('../utils/handleAutoUpdate.js');
|
|
171
171
|
vi.mock('./utils/ConsolePatcher.js');
|
|
172
172
|
vi.mock('../utils/cleanup.js');
|
|
173
|
+
import { appEvents, AppEvent } from '../utils/events.js';
|
|
173
174
|
import { useHistory } from './hooks/useHistoryManager.js';
|
|
174
175
|
import { useThemeCommand } from './hooks/useThemeCommand.js';
|
|
175
176
|
import { useAuthCommand } from './auth/useAuth.js';
|
|
@@ -196,9 +197,16 @@ import { useInputHistoryStore } from './hooks/useInputHistoryStore.js';
|
|
|
196
197
|
import { useKeypress } from './hooks/useKeypress.js';
|
|
197
198
|
import * as useKeypressModule from './hooks/useKeypress.js';
|
|
198
199
|
import { useSuspend } from './hooks/useSuspend.js';
|
|
200
|
+
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
|
199
201
|
import { writeToStdout, enableMouseEvents, disableMouseEvents, } from '@google/gemini-cli-core';
|
|
200
202
|
import {} from '../config/extension-manager.js';
|
|
201
203
|
import { WARNING_PROMPT_DURATION_MS, EXPAND_HINT_DURATION_MS, } from './constants.js';
|
|
204
|
+
// In the default inline mode, refreshStatic must only clear the visible
|
|
205
|
+
// viewport. It must never emit `\x1b[3J` (Erase Saved Lines), which purges the
|
|
206
|
+
// terminal's native scrollback history.
|
|
207
|
+
const CLEAR_VIEWPORT = ansiEscapes.eraseScreen + ansiEscapes.cursorTo(0, 0);
|
|
208
|
+
const ERASE_SCROLLBACK = '\x1b[3J';
|
|
209
|
+
const getScrollbackPurgingWrites = () => mocks.mockStdout.write.mock.calls.filter((call) => String(call[0]).includes(ERASE_SCROLLBACK));
|
|
202
210
|
describe('AppContainer State Management', () => {
|
|
203
211
|
let mockConfig;
|
|
204
212
|
let mockSettings;
|
|
@@ -238,6 +246,7 @@ describe('AppContainer State Management', () => {
|
|
|
238
246
|
const mockedUseTerminalTheme = useTerminalTheme;
|
|
239
247
|
const mockedUseShellInactivityStatus = useShellInactivityStatus;
|
|
240
248
|
const mockedUseFocusState = useFocus;
|
|
249
|
+
const mockedUseTerminalSize = useTerminalSize;
|
|
241
250
|
const DEFAULT_GEMINI_STREAM_MOCK = {
|
|
242
251
|
streamingState: 'idle',
|
|
243
252
|
submitQuery: vi.fn(),
|
|
@@ -371,6 +380,10 @@ describe('AppContainer State Management', () => {
|
|
|
371
380
|
mockedUseSuspend.mockReturnValue({
|
|
372
381
|
handleSuspend: vi.fn(),
|
|
373
382
|
});
|
|
383
|
+
mockedUseTerminalSize.mockReturnValue({
|
|
384
|
+
columns: 80,
|
|
385
|
+
rows: 24,
|
|
386
|
+
});
|
|
374
387
|
mockedUseHookDisplayState.mockReturnValue([]);
|
|
375
388
|
mockedUseTerminalTheme.mockReturnValue(undefined);
|
|
376
389
|
mockedUseShellInactivityStatus.mockReturnValue({
|
|
@@ -1700,6 +1713,51 @@ describe('AppContainer State Management', () => {
|
|
|
1700
1713
|
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1701
1714
|
unmount();
|
|
1702
1715
|
});
|
|
1716
|
+
it('should await in-flight cancelOngoingRequest promise before quitting on rapid double press', async () => {
|
|
1717
|
+
let resolveCancel;
|
|
1718
|
+
const cancelPromise = new Promise((resolve) => {
|
|
1719
|
+
resolveCancel = resolve;
|
|
1720
|
+
});
|
|
1721
|
+
mockCancelOngoingRequest.mockReturnValue(cancelPromise);
|
|
1722
|
+
await setupKeypressTest();
|
|
1723
|
+
pressKey('\x03', 2); // Rapid double Ctrl+C
|
|
1724
|
+
expect(mockCancelOngoingRequest).toHaveBeenCalledTimes(2);
|
|
1725
|
+
// Should not have called /quit yet while cancelPromise is still pending
|
|
1726
|
+
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
|
1727
|
+
await act(async () => {
|
|
1728
|
+
resolveCancel();
|
|
1729
|
+
await cancelPromise;
|
|
1730
|
+
});
|
|
1731
|
+
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1732
|
+
unmount();
|
|
1733
|
+
});
|
|
1734
|
+
it('should bound waiting on a hanging cancelOngoingRequest to 1s via Promise.race on rapid double press', async () => {
|
|
1735
|
+
mockCancelOngoingRequest.mockReturnValue(new Promise(() => { }));
|
|
1736
|
+
await setupKeypressTest();
|
|
1737
|
+
pressKey('\x03', 2); // Rapid double Ctrl+C
|
|
1738
|
+
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
|
1739
|
+
await act(async () => {
|
|
1740
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
1741
|
+
});
|
|
1742
|
+
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1743
|
+
unmount();
|
|
1744
|
+
});
|
|
1745
|
+
it('should safely handle rejected cancelOngoingRequest promise and still quit on rapid double press', async () => {
|
|
1746
|
+
let rejectCancel;
|
|
1747
|
+
const cancelPromise = new Promise((_, reject) => {
|
|
1748
|
+
rejectCancel = reject;
|
|
1749
|
+
});
|
|
1750
|
+
mockCancelOngoingRequest.mockReturnValue(cancelPromise);
|
|
1751
|
+
await setupKeypressTest();
|
|
1752
|
+
pressKey('\x03', 2); // Rapid double Ctrl+C
|
|
1753
|
+
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
|
1754
|
+
await act(async () => {
|
|
1755
|
+
rejectCancel(new Error('Cancel failed'));
|
|
1756
|
+
await cancelPromise.catch(() => { });
|
|
1757
|
+
});
|
|
1758
|
+
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1759
|
+
unmount();
|
|
1760
|
+
});
|
|
1703
1761
|
it('should reset press count after a timeout', async () => {
|
|
1704
1762
|
await setupKeypressTest();
|
|
1705
1763
|
pressKey('\x03'); // Ctrl+C
|
|
@@ -2472,9 +2530,209 @@ describe('AppContainer State Management', () => {
|
|
|
2472
2530
|
await act(async () => {
|
|
2473
2531
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2474
2532
|
});
|
|
2475
|
-
// Check that
|
|
2476
|
-
const
|
|
2477
|
-
expect(
|
|
2533
|
+
// Check that refreshStatic did NOT clear the viewport
|
|
2534
|
+
const clearViewportCalls = mocks.mockStdout.write.mock.calls.filter((call) => call[0] === CLEAR_VIEWPORT);
|
|
2535
|
+
expect(clearViewportCalls).toHaveLength(0);
|
|
2536
|
+
unmount();
|
|
2537
|
+
});
|
|
2538
|
+
it('keeps constrainHeight true when OpenDebugConsole event is triggered', async () => {
|
|
2539
|
+
const { unmount } = await act(async () => renderAppContainer());
|
|
2540
|
+
expect(capturedUIState).toBeTruthy();
|
|
2541
|
+
expect(capturedUIState.showErrorDetails).toBe(false);
|
|
2542
|
+
expect(capturedUIState.constrainHeight).toBe(true);
|
|
2543
|
+
const openDebugConsoleHandler = vi
|
|
2544
|
+
.mocked(appEvents.on)
|
|
2545
|
+
.mock.calls.find((call) => call[0] === AppEvent.OpenDebugConsole)?.[1];
|
|
2546
|
+
expect(openDebugConsoleHandler).toBeDefined();
|
|
2547
|
+
act(() => {
|
|
2548
|
+
openDebugConsoleHandler();
|
|
2549
|
+
});
|
|
2550
|
+
expect(capturedUIState.showErrorDetails).toBe(true);
|
|
2551
|
+
expect(capturedUIState.constrainHeight).toBe(true);
|
|
2552
|
+
unmount();
|
|
2553
|
+
});
|
|
2554
|
+
it('debounces refreshStatic when terminal width changes in default inline mode, and ignores height-only changes', async () => {
|
|
2555
|
+
vi.useFakeTimers();
|
|
2556
|
+
try {
|
|
2557
|
+
vi.spyOn(mockConfig, 'getUseTerminalBuffer').mockReturnValue(false);
|
|
2558
|
+
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(false);
|
|
2559
|
+
const { rerender, unmount } = await act(async () => renderAppContainer());
|
|
2560
|
+
expect(capturedUIState).toBeTruthy();
|
|
2561
|
+
const initialRemountKey = capturedUIState.historyRemountKey;
|
|
2562
|
+
mocks.mockStdout.write.mockClear();
|
|
2563
|
+
// Initial mount should not trigger refreshStatic after 100ms
|
|
2564
|
+
act(() => {
|
|
2565
|
+
vi.advanceTimersByTime(100);
|
|
2566
|
+
});
|
|
2567
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2568
|
+
expect(capturedUIState.historyRemountKey).toBe(initialRemountKey);
|
|
2569
|
+
// Simulate height-only resize (e.g. mobile virtual keyboard toggle)
|
|
2570
|
+
mockedUseTerminalSize.mockReturnValue({ columns: 80, rows: 16 });
|
|
2571
|
+
await act(async () => {
|
|
2572
|
+
rerender(getAppContainer());
|
|
2573
|
+
});
|
|
2574
|
+
act(() => {
|
|
2575
|
+
vi.advanceTimersByTime(100);
|
|
2576
|
+
});
|
|
2577
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2578
|
+
expect(capturedUIState.historyRemountKey).toBe(initialRemountKey);
|
|
2579
|
+
// Simulate rapid consecutive horizontal resizes (columns: 80 -> 70 -> 60)
|
|
2580
|
+
mockedUseTerminalSize.mockReturnValue({ columns: 70, rows: 16 });
|
|
2581
|
+
await act(async () => {
|
|
2582
|
+
rerender(getAppContainer());
|
|
2583
|
+
});
|
|
2584
|
+
act(() => {
|
|
2585
|
+
vi.advanceTimersByTime(60);
|
|
2586
|
+
});
|
|
2587
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2588
|
+
// Second resize before 100ms elapses should reset the debounce timer
|
|
2589
|
+
mockedUseTerminalSize.mockReturnValue({ columns: 60, rows: 16 });
|
|
2590
|
+
await act(async () => {
|
|
2591
|
+
rerender(getAppContainer());
|
|
2592
|
+
});
|
|
2593
|
+
act(() => {
|
|
2594
|
+
vi.advanceTimersByTime(60);
|
|
2595
|
+
});
|
|
2596
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2597
|
+
expect(capturedUIState.historyRemountKey).toBe(initialRemountKey);
|
|
2598
|
+
// Remaining 40ms completes the 100ms debounce from the last width change
|
|
2599
|
+
act(() => {
|
|
2600
|
+
vi.advanceTimersByTime(40);
|
|
2601
|
+
});
|
|
2602
|
+
expect(mocks.mockStdout.write).toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2603
|
+
expect(capturedUIState.historyRemountKey).toBe(initialRemountKey + 1);
|
|
2604
|
+
unmount();
|
|
2605
|
+
}
|
|
2606
|
+
finally {
|
|
2607
|
+
vi.useRealTimers();
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
it('corks stdout during horizontal resize, discards intermediate buffered writes, and wraps refreshStatic in synchronized output', async () => {
|
|
2611
|
+
vi.useFakeTimers();
|
|
2612
|
+
const mockStdoutWithCork = mocks.mockStdout;
|
|
2613
|
+
let resizeListener;
|
|
2614
|
+
const bufferedCallback = vi.fn();
|
|
2615
|
+
const writableState = {
|
|
2616
|
+
corked: 0,
|
|
2617
|
+
length: 0,
|
|
2618
|
+
writelen: 0,
|
|
2619
|
+
buffered: [],
|
|
2620
|
+
bufferedIndex: 0,
|
|
2621
|
+
};
|
|
2622
|
+
mockStdoutWithCork.columns = 80;
|
|
2623
|
+
mockStdoutWithCork._writableState = writableState;
|
|
2624
|
+
mockStdoutWithCork.cork = vi.fn(() => {
|
|
2625
|
+
writableState.corked += 1;
|
|
2626
|
+
});
|
|
2627
|
+
mockStdoutWithCork.uncork = vi.fn(() => {
|
|
2628
|
+
writableState.corked = Math.max(0, writableState.corked - 1);
|
|
2629
|
+
});
|
|
2630
|
+
mockStdoutWithCork.prependListener = vi.fn((event, cb) => {
|
|
2631
|
+
if (event === 'resize') {
|
|
2632
|
+
resizeListener = cb;
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
mockStdoutWithCork.off = vi.fn();
|
|
2636
|
+
try {
|
|
2637
|
+
vi.spyOn(mockConfig, 'getUseTerminalBuffer').mockReturnValue(false);
|
|
2638
|
+
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(false);
|
|
2639
|
+
const { rerender, unmount } = await act(async () => renderAppContainer());
|
|
2640
|
+
expect(resizeListener).toBeTypeOf('function');
|
|
2641
|
+
mocks.mockStdout.write.mockClear();
|
|
2642
|
+
mockStdoutWithCork.cork.mockClear();
|
|
2643
|
+
mockStdoutWithCork.uncork.mockClear();
|
|
2644
|
+
// Simulate horizontal resize event before Ink.resized runs
|
|
2645
|
+
mockStdoutWithCork.columns = 60;
|
|
2646
|
+
resizeListener();
|
|
2647
|
+
expect(mockStdoutWithCork.cork).toHaveBeenCalledTimes(1);
|
|
2648
|
+
expect(writableState.corked).toBe(1);
|
|
2649
|
+
// Simulate intermediate buffered frame written while corked during resize drag
|
|
2650
|
+
// alongside an active in-flight write at index 0 (bufferedIndex = 1, writelen = 10)
|
|
2651
|
+
const inFlightCallback = vi.fn();
|
|
2652
|
+
const discardedChunk = 'intermediate frame';
|
|
2653
|
+
writableState.buffered.push({
|
|
2654
|
+
chunk: 'in-flight',
|
|
2655
|
+
encoding: 'utf8',
|
|
2656
|
+
callback: inFlightCallback,
|
|
2657
|
+
}, {
|
|
2658
|
+
chunk: discardedChunk,
|
|
2659
|
+
encoding: 'utf8',
|
|
2660
|
+
callback: bufferedCallback,
|
|
2661
|
+
});
|
|
2662
|
+
writableState.bufferedIndex = 1;
|
|
2663
|
+
writableState.writelen = 10;
|
|
2664
|
+
writableState.length = 10 + Buffer.byteLength(discardedChunk, 'utf8');
|
|
2665
|
+
mockedUseTerminalSize.mockReturnValue({ columns: 60, rows: 24 });
|
|
2666
|
+
await act(async () => {
|
|
2667
|
+
rerender(getAppContainer());
|
|
2668
|
+
});
|
|
2669
|
+
// Advance 80ms (timer for 60 cols has 20ms left), then emit a new raw
|
|
2670
|
+
// stdout resize event (columns = 50) BEFORE React commits the new size.
|
|
2671
|
+
act(() => {
|
|
2672
|
+
vi.advanceTimersByTime(80);
|
|
2673
|
+
});
|
|
2674
|
+
mockStdoutWithCork.columns = 50;
|
|
2675
|
+
resizeListener();
|
|
2676
|
+
// Advancing past the original 100ms mark (20ms more) must NOT flush or
|
|
2677
|
+
// uncork stdout for the stale 60-col width.
|
|
2678
|
+
act(() => {
|
|
2679
|
+
vi.advanceTimersByTime(30);
|
|
2680
|
+
});
|
|
2681
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalled();
|
|
2682
|
+
expect(mockStdoutWithCork.uncork).not.toHaveBeenCalled();
|
|
2683
|
+
expect(writableState.corked).toBe(1);
|
|
2684
|
+
// Now React commits the 50-col width and settles for 100ms
|
|
2685
|
+
mockedUseTerminalSize.mockReturnValue({ columns: 50, rows: 24 });
|
|
2686
|
+
await act(async () => {
|
|
2687
|
+
rerender(getAppContainer());
|
|
2688
|
+
});
|
|
2689
|
+
act(() => {
|
|
2690
|
+
vi.advanceTimersByTime(100);
|
|
2691
|
+
});
|
|
2692
|
+
// Intermediate buffered frame should have been discarded before the viewport clear,
|
|
2693
|
+
// truncating buffered to startIndex (1) and preserving in-flight write state
|
|
2694
|
+
expect(inFlightCallback).not.toHaveBeenCalled();
|
|
2695
|
+
expect(bufferedCallback).toHaveBeenCalledWith(null);
|
|
2696
|
+
expect(writableState.buffered).toHaveLength(1);
|
|
2697
|
+
expect(writableState.bufferedIndex).toBe(1);
|
|
2698
|
+
expect(writableState.length).toBe(10);
|
|
2699
|
+
expect(writableState.writelen).toBe(10);
|
|
2700
|
+
// The viewport clear should be wrapped in DEC Synchronized Output (\x1b[?2026h ... \x1b[?2026l) and uncorked in useLayoutEffect
|
|
2701
|
+
expect(mocks.mockStdout.write).toHaveBeenNthCalledWith(1, '\x1b[?2026h');
|
|
2702
|
+
expect(mocks.mockStdout.write).toHaveBeenNthCalledWith(2, CLEAR_VIEWPORT);
|
|
2703
|
+
expect(mocks.mockStdout.write).toHaveBeenNthCalledWith(3, '\x1b[?2026l');
|
|
2704
|
+
expect(getScrollbackPurgingWrites()).toHaveLength(0);
|
|
2705
|
+
expect(mockStdoutWithCork.uncork).toHaveBeenCalledTimes(1);
|
|
2706
|
+
expect(writableState.corked).toBe(0);
|
|
2707
|
+
unmount();
|
|
2708
|
+
}
|
|
2709
|
+
finally {
|
|
2710
|
+
delete mockStdoutWithCork.columns;
|
|
2711
|
+
delete mockStdoutWithCork.cork;
|
|
2712
|
+
delete mockStdoutWithCork.uncork;
|
|
2713
|
+
delete mockStdoutWithCork.prependListener;
|
|
2714
|
+
delete mockStdoutWithCork.off;
|
|
2715
|
+
delete mockStdoutWithCork._writableState;
|
|
2716
|
+
vi.useRealTimers();
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
it('refreshStatic clears only the viewport and preserves terminal scrollback in default inline mode', async () => {
|
|
2720
|
+
vi.spyOn(mockConfig, 'getUseTerminalBuffer').mockReturnValue(false);
|
|
2721
|
+
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(false);
|
|
2722
|
+
const { unmount } = await act(async () => renderAppContainer());
|
|
2723
|
+
expect(capturedUIActions).toBeTruthy();
|
|
2724
|
+
const initialRemountKey = capturedUIState.historyRemountKey;
|
|
2725
|
+
mocks.mockStdout.write.mockClear();
|
|
2726
|
+
act(() => {
|
|
2727
|
+
capturedUIActions.refreshStatic();
|
|
2728
|
+
});
|
|
2729
|
+
// Only the visible viewport is erased so earlier turns remain available
|
|
2730
|
+
// in the terminal's native scrollback.
|
|
2731
|
+
expect(mocks.mockStdout.write).toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2732
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(ansiEscapes.clearTerminal);
|
|
2733
|
+
expect(getScrollbackPurgingWrites()).toHaveLength(0);
|
|
2734
|
+
// Static history is still remounted so it is re-rendered after the clear.
|
|
2735
|
+
expect(capturedUIState.historyRemountKey).toBe(initialRemountKey + 1);
|
|
2478
2736
|
unmount();
|
|
2479
2737
|
});
|
|
2480
2738
|
});
|
|
@@ -2517,8 +2775,8 @@ describe('AppContainer State Management', () => {
|
|
|
2517
2775
|
await act(async () => capturedUIActions.handleFinalSubmit('test prompt'));
|
|
2518
2776
|
// Should be reset
|
|
2519
2777
|
expect(capturedUIState.constrainHeight).toBe(true);
|
|
2520
|
-
// Should NOT refresh static's
|
|
2521
|
-
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(
|
|
2778
|
+
// Should NOT refresh static's viewport clear in alternate buffer
|
|
2779
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2522
2780
|
unmount();
|
|
2523
2781
|
});
|
|
2524
2782
|
});
|
|
@@ -2730,7 +2988,7 @@ describe('AppContainer State Management', () => {
|
|
|
2730
2988
|
});
|
|
2731
2989
|
// Should remain expanded and not clear terminal
|
|
2732
2990
|
expect(capturedUIState.constrainHeight).toBe(false);
|
|
2733
|
-
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(
|
|
2991
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
2734
2992
|
// Simulate Escape key to exit expanded view
|
|
2735
2993
|
act(() => {
|
|
2736
2994
|
stdin.write('\x1b');
|
|
@@ -2743,6 +3001,104 @@ describe('AppContainer State Management', () => {
|
|
|
2743
3001
|
});
|
|
2744
3002
|
unmount();
|
|
2745
3003
|
});
|
|
3004
|
+
it('does not reset the hint timer when an overflowing ID is removed while others remain', async () => {
|
|
3005
|
+
const { unmount } = await act(async () => renderAppContainer());
|
|
3006
|
+
await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
|
|
3007
|
+
act(() => {
|
|
3008
|
+
capturedOverflowActions.addOverflowingId('test-id-1');
|
|
3009
|
+
capturedOverflowActions.addOverflowingId('test-id-2');
|
|
3010
|
+
});
|
|
3011
|
+
act(() => {
|
|
3012
|
+
vi.advanceTimersByTime(1);
|
|
3013
|
+
});
|
|
3014
|
+
await waitFor(() => {
|
|
3015
|
+
expect(capturedUIState.showIsExpandableHint).toBe(true);
|
|
3016
|
+
});
|
|
3017
|
+
// Advance half the duration
|
|
3018
|
+
act(() => {
|
|
3019
|
+
vi.advanceTimersByTime(EXPAND_HINT_DURATION_MS / 2);
|
|
3020
|
+
});
|
|
3021
|
+
expect(capturedUIState.showIsExpandableHint).toBe(true);
|
|
3022
|
+
// Removing one overflowing ID should NOT reset the hint timer
|
|
3023
|
+
act(() => {
|
|
3024
|
+
capturedOverflowActions.removeOverflowingId('test-id-2');
|
|
3025
|
+
});
|
|
3026
|
+
act(() => {
|
|
3027
|
+
vi.advanceTimersByTime(1);
|
|
3028
|
+
});
|
|
3029
|
+
// Advance the remaining half of the original timer
|
|
3030
|
+
act(() => {
|
|
3031
|
+
vi.advanceTimersByTime(EXPAND_HINT_DURATION_MS / 2 - 1);
|
|
3032
|
+
});
|
|
3033
|
+
await waitFor(() => {
|
|
3034
|
+
expect(capturedUIState.showIsExpandableHint).toBe(false);
|
|
3035
|
+
});
|
|
3036
|
+
unmount();
|
|
3037
|
+
});
|
|
3038
|
+
it('does not clear terminal when expanding with Ctrl+O if there are no last-turn tool calls in history', async () => {
|
|
3039
|
+
const { stdin, unmount } = await act(async () => renderAppContainer());
|
|
3040
|
+
mocks.mockStdout.write.mockClear();
|
|
3041
|
+
// Expand with Ctrl+O when history has no last-turn tool calls
|
|
3042
|
+
act(() => {
|
|
3043
|
+
stdin.write('\x0f');
|
|
3044
|
+
});
|
|
3045
|
+
expect(capturedUIState.constrainHeight).toBe(false);
|
|
3046
|
+
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
3047
|
+
// Collapse with Ctrl+O should still refresh static and clear terminal
|
|
3048
|
+
act(() => {
|
|
3049
|
+
stdin.write('\x0f');
|
|
3050
|
+
});
|
|
3051
|
+
expect(capturedUIState.constrainHeight).toBe(true);
|
|
3052
|
+
expect(mocks.mockStdout.write).toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
3053
|
+
unmount();
|
|
3054
|
+
});
|
|
3055
|
+
it('clears terminal when expanding with Ctrl+O if there are last-turn tool calls in history', async () => {
|
|
3056
|
+
useHistory.mockReturnValue({
|
|
3057
|
+
history: [
|
|
3058
|
+
{ id: 1, type: 'user', text: 'run something' },
|
|
3059
|
+
{
|
|
3060
|
+
id: 2,
|
|
3061
|
+
type: 'tool_group',
|
|
3062
|
+
tools: [
|
|
3063
|
+
{
|
|
3064
|
+
callId: 'call-1',
|
|
3065
|
+
name: 'test_tool',
|
|
3066
|
+
description: 'desc',
|
|
3067
|
+
status: CoreToolCallStatus.Success,
|
|
3068
|
+
resultDisplay: 'result',
|
|
3069
|
+
confirmationDetails: undefined,
|
|
3070
|
+
},
|
|
3071
|
+
],
|
|
3072
|
+
},
|
|
3073
|
+
],
|
|
3074
|
+
addItem: vi.fn(),
|
|
3075
|
+
updateItem: vi.fn(),
|
|
3076
|
+
clearItems: vi.fn(),
|
|
3077
|
+
loadHistory: vi.fn(),
|
|
3078
|
+
});
|
|
3079
|
+
const { stdin, unmount } = await act(async () => renderAppContainer());
|
|
3080
|
+
mocks.mockStdout.write.mockClear();
|
|
3081
|
+
act(() => {
|
|
3082
|
+
stdin.write('\x0f');
|
|
3083
|
+
});
|
|
3084
|
+
expect(capturedUIState.constrainHeight).toBe(false);
|
|
3085
|
+
expect(mocks.mockStdout.write).toHaveBeenCalledWith(CLEAR_VIEWPORT);
|
|
3086
|
+
unmount();
|
|
3087
|
+
});
|
|
3088
|
+
it('passes pauseUpdates=true to useLoadingIndicator when expanded in standard mode', async () => {
|
|
3089
|
+
const { stdin, unmount } = await act(async () => renderAppContainer());
|
|
3090
|
+
expect(useLoadingIndicator).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3091
|
+
pauseUpdates: false,
|
|
3092
|
+
}));
|
|
3093
|
+
act(() => {
|
|
3094
|
+
stdin.write('\x0f');
|
|
3095
|
+
});
|
|
3096
|
+
expect(capturedUIState.constrainHeight).toBe(false);
|
|
3097
|
+
expect(useLoadingIndicator).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3098
|
+
pauseUpdates: true,
|
|
3099
|
+
}));
|
|
3100
|
+
unmount();
|
|
3101
|
+
});
|
|
2746
3102
|
});
|
|
2747
3103
|
describe('Permission Handling', () => {
|
|
2748
3104
|
it('shows permission dialog when checkPermissions returns paths', async () => {
|