mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-14 07:10:34 -07:00
feat(core): improve shell execution service reliability (#10607)
This commit is contained in:
@@ -219,7 +219,7 @@ describe('useShellCommandProcessor', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should throttle pending UI updates for text streams (non-interactive)', async () => {
|
||||
it('should update UI for text streams (non-interactive)', async () => {
|
||||
const { result } = renderProcessorHook();
|
||||
act(() => {
|
||||
result.current.handleShellCommand(
|
||||
@@ -243,61 +243,43 @@ describe('useShellCommandProcessor', () => {
|
||||
);
|
||||
|
||||
// Wait for the async PID update to happen.
|
||||
// Call 1: Initial, Call 2: PID update
|
||||
await vi.waitFor(() => {
|
||||
// It's called once for initial, and once for the PID update.
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// Simulate rapid output
|
||||
// Get the state after the PID update to feed into the stream updaters
|
||||
const pidUpdateFn = setPendingHistoryItemMock.mock.calls[1][0];
|
||||
const initialState = setPendingHistoryItemMock.mock.calls[0][0];
|
||||
const stateAfterPid = pidUpdateFn(initialState);
|
||||
|
||||
// Simulate first output chunk
|
||||
act(() => {
|
||||
mockShellOutputCallback({
|
||||
type: 'data',
|
||||
chunk: 'hello',
|
||||
});
|
||||
});
|
||||
// The count should still be 2, as throttling is in effect.
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(2);
|
||||
// A UI update should have occurred.
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
// Simulate more rapid output
|
||||
const streamUpdateFn1 = setPendingHistoryItemMock.mock.calls[2][0];
|
||||
const stateAfterStream1 = streamUpdateFn1(stateAfterPid);
|
||||
expect(stateAfterStream1.tools[0].resultDisplay).toBe('hello');
|
||||
|
||||
// Simulate second output chunk
|
||||
act(() => {
|
||||
mockShellOutputCallback({
|
||||
type: 'data',
|
||||
chunk: ' world',
|
||||
});
|
||||
});
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(2);
|
||||
// Another UI update should have occurred.
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(4);
|
||||
|
||||
// Advance time, but the update won't happen until the next event
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(OUTPUT_UPDATE_INTERVAL_MS + 1);
|
||||
});
|
||||
|
||||
// Trigger one more event to cause the throttled update to fire.
|
||||
act(() => {
|
||||
mockShellOutputCallback({
|
||||
type: 'data',
|
||||
chunk: '',
|
||||
});
|
||||
});
|
||||
|
||||
// Now the cumulative update should have occurred.
|
||||
// Call 1: Initial, Call 2: PID update, Call 3: Throttled stream update
|
||||
expect(setPendingHistoryItemMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
const streamUpdateFn = setPendingHistoryItemMock.mock.calls[2][0];
|
||||
if (!streamUpdateFn || typeof streamUpdateFn !== 'function') {
|
||||
throw new Error(
|
||||
'setPendingHistoryItem was not called with a stream updater function',
|
||||
);
|
||||
}
|
||||
|
||||
// Get the state after the PID update to feed into the stream updater
|
||||
const pidUpdateFn = setPendingHistoryItemMock.mock.calls[1][0];
|
||||
const initialState = setPendingHistoryItemMock.mock.calls[0][0];
|
||||
const stateAfterPid = pidUpdateFn(initialState);
|
||||
|
||||
const stateAfterStream = streamUpdateFn(stateAfterPid);
|
||||
expect(stateAfterStream.tools[0].resultDisplay).toBe('hello world');
|
||||
const streamUpdateFn2 = setPendingHistoryItemMock.mock.calls[3][0];
|
||||
const stateAfterStream2 = streamUpdateFn2(stateAfterStream1);
|
||||
expect(stateAfterStream2.tools[0].resultDisplay).toBe('hello world');
|
||||
});
|
||||
|
||||
it('should show binary progress messages correctly', async () => {
|
||||
|
||||
@@ -109,7 +109,6 @@ export const useShellCommandProcessor = (
|
||||
const executeCommand = async (
|
||||
resolve: (value: void | PromiseLike<void>) => void,
|
||||
) => {
|
||||
let lastUpdateTime = Date.now();
|
||||
let cumulativeStdout: string | AnsiOutput = '';
|
||||
let isBinaryStream = false;
|
||||
let binaryBytesReceived = 0;
|
||||
@@ -168,6 +167,7 @@ export const useShellCommandProcessor = (
|
||||
typeof cumulativeStdout === 'string'
|
||||
) {
|
||||
cumulativeStdout += event.chunk;
|
||||
shouldUpdate = true;
|
||||
}
|
||||
break;
|
||||
case 'binary_detected':
|
||||
@@ -178,6 +178,7 @@ export const useShellCommandProcessor = (
|
||||
case 'binary_progress':
|
||||
isBinaryStream = true;
|
||||
binaryBytesReceived = event.bytesReceived;
|
||||
shouldUpdate = true;
|
||||
break;
|
||||
default: {
|
||||
throw new Error('An unhandled ShellOutputEvent was found.');
|
||||
@@ -200,10 +201,7 @@ export const useShellCommandProcessor = (
|
||||
}
|
||||
|
||||
// Throttle pending UI updates, but allow forced updates.
|
||||
if (
|
||||
shouldUpdate ||
|
||||
Date.now() - lastUpdateTime > OUTPUT_UPDATE_INTERVAL_MS
|
||||
) {
|
||||
if (shouldUpdate) {
|
||||
setPendingHistoryItem((prevItem) => {
|
||||
if (prevItem?.type === 'tool_group') {
|
||||
return {
|
||||
@@ -217,7 +215,6 @@ export const useShellCommandProcessor = (
|
||||
}
|
||||
return prevItem;
|
||||
});
|
||||
lastUpdateTime = Date.now();
|
||||
}
|
||||
},
|
||||
abortSignal,
|
||||
|
||||
Reference in New Issue
Block a user