feat(core): refactor shell execution to use node-pty (#6088)

This commit is contained in:
Gal Zahavi
2025-08-14 13:40:12 -07:00
committed by GitHub
parent 48af0456c1
commit 980091cbc2
16 changed files with 453 additions and 409 deletions

View File

@@ -104,8 +104,6 @@ describe('useShellCommandProcessor', () => {
): ShellExecutionResult => ({
rawOutput: Buffer.from(overrides.output || ''),
output: 'Success',
stdout: 'Success',
stderr: '',
exitCode: 0,
signal: null,
error: null,
@@ -223,7 +221,6 @@ describe('useShellCommandProcessor', () => {
act(() => {
mockShellOutputCallback({
type: 'data',
stream: 'stdout',
chunk: 'hello',
});
});
@@ -234,12 +231,9 @@ describe('useShellCommandProcessor', () => {
// Advance time and send another event to trigger the throttled update
await act(async () => {
await vi.advanceTimersByTimeAsync(OUTPUT_UPDATE_INTERVAL_MS + 1);
});
act(() => {
mockShellOutputCallback({
type: 'data',
stream: 'stdout',
chunk: ' world',
chunk: 'hello world',
});
});

View File

@@ -104,7 +104,6 @@ export const useShellCommandProcessor = (
const execPromise = new Promise<void>((resolve) => {
let lastUpdateTime = Date.now();
let cumulativeStdout = '';
let cumulativeStderr = '';
let isBinaryStream = false;
let binaryBytesReceived = 0;
@@ -142,11 +141,7 @@ export const useShellCommandProcessor = (
case 'data':
// Do not process text data if we've already switched to binary mode.
if (isBinaryStream) break;
if (event.stream === 'stdout') {
cumulativeStdout += event.chunk;
} else {
cumulativeStderr += event.chunk;
}
cumulativeStdout = event.chunk;
break;
case 'binary_detected':
isBinaryStream = true;
@@ -172,9 +167,7 @@ export const useShellCommandProcessor = (
'[Binary output detected. Halting stream...]';
}
} else {
currentDisplayOutput =
cumulativeStdout +
(cumulativeStderr ? `\n${cumulativeStderr}` : '');
currentDisplayOutput = cumulativeStdout;
}
// Throttle pending UI updates to avoid excessive re-renders.

View File

@@ -62,6 +62,8 @@ export type TrackedToolCall =
| TrackedCompletedToolCall
| TrackedCancelledToolCall;
import { useTerminalSize } from './useTerminalSize.js';
export function useReactToolScheduler(
onComplete: (tools: CompletedToolCall[]) => Promise<void>,
config: Config,
@@ -71,6 +73,7 @@ export function useReactToolScheduler(
getPreferredEditor: () => EditorType | undefined,
onEditorClose: () => void,
): [TrackedToolCall[], ScheduleFn, MarkToolsAsSubmittedFn] {
const terminalSize = useTerminalSize();
const [toolCallsForDisplay, setToolCallsForDisplay] = useState<
TrackedToolCall[]
>([]);
@@ -140,6 +143,7 @@ export function useReactToolScheduler(
onToolCallsUpdate: toolCallsUpdateHandler,
getPreferredEditor,
config,
getTerminalSize: () => terminalSize,
onEditorClose,
}),
[
@@ -148,6 +152,7 @@ export function useReactToolScheduler(
allToolCallsCompleteHandler,
toolCallsUpdateHandler,
getPreferredEditor,
terminalSize,
onEditorClose,
],
);

View File

@@ -34,6 +34,13 @@ import {
HistoryItemToolGroup,
} from '../types.js';
vi.mock('./useTerminalSize', () => ({
useTerminalSize: () => ({
columns: 80,
rows: 24,
}),
}));
// Mocks
vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual('@google/gemini-cli-core');
@@ -179,6 +186,8 @@ describe('useReactToolScheduler in YOLO Mode', () => {
request.args,
expect.any(AbortSignal),
undefined,
80,
24,
);
// Check that onComplete was called with success
@@ -326,6 +335,8 @@ describe('useReactToolScheduler', () => {
request.args,
expect.any(AbortSignal),
undefined,
80,
24,
);
expect(onComplete).toHaveBeenCalledWith([
expect.objectContaining({