fix(cli): fix tests and remove unused imports in non-interactive agent session

This commit is contained in:
Adam Weidman
2026-04-01 14:36:46 -07:00
parent ab2f9bd9a7
commit 05baea6627
2 changed files with 17 additions and 55 deletions
@@ -686,7 +686,7 @@ describe('runNonInteractive', () => {
input: 'Trigger loop',
prompt_id: 'prompt-id-6',
}),
).rejects.toThrow('process.exit(53) called');
).rejects.toThrow('Reached max session turns for this session');
});
it('should preprocess @include commands before sending to the model', async () => {
@@ -1194,14 +1194,7 @@ describe('runNonInteractive', () => {
() => process.stdin,
);
// Spy on handleCancellationError to verify it's called
const errors = await import('./utils/errors.js');
const cancellationSentinel = new Error('Cancelled');
const handleCancellationErrorSpy = vi
.spyOn(errors, 'handleCancellationError')
.mockImplementation(() => {
throw cancellationSentinel;
});
// Cancellation will throw FatalCancellationError directly
const events: ServerGeminiStreamEvent[] = [
{ type: GeminiEventType.Content, value: 'Thinking...' },
@@ -1249,10 +1242,7 @@ describe('runNonInteractive', () => {
keypressHandler('\u0003', { ctrl: true, name: 'c' });
}
// The Ctrl+C path should route through handleCancellationError rather than
// surfacing the raw stream abort.
await expect(runPromise).rejects.toBe(cancellationSentinel);
expect(handleCancellationErrorSpy).toHaveBeenCalledTimes(1);
await expect(runPromise).rejects.toThrow('Operation cancelled.');
expect(
processStderrSpy.mock.calls.some(
@@ -1261,8 +1251,6 @@ describe('runNonInteractive', () => {
),
).toBe(true);
handleCancellationErrorSpy.mockRestore();
// Restore original values
Object.defineProperty(process.stdin, 'isTTY', {
value: originalIsTTY,
@@ -1311,13 +1299,7 @@ describe('runNonInteractive', () => {
() => process.stdin,
);
const errors = await import('./utils/errors.js');
const cancellationSentinel = new Error('Cancelled before send');
const handleCancellationErrorSpy = vi
.spyOn(errors, 'handleCancellationError')
.mockImplementation(() => {
throw cancellationSentinel;
});
// Cancellation will throw FatalCancellationError directly
const { LegacyAgentSession } = await import('@google/gemini-cli-core');
const sendSpy = vi.spyOn(LegacyAgentSession.prototype, 'send');
@@ -1329,13 +1311,10 @@ describe('runNonInteractive', () => {
input: 'Cancelled query',
prompt_id: 'prompt-id-pre-send-cancel',
}),
).rejects.toBe(cancellationSentinel);
).rejects.toThrow('Operation cancelled.');
expect(handleCancellationErrorSpy).toHaveBeenCalledTimes(1);
expect(sendSpy).not.toHaveBeenCalled();
expect(stdinOnSpy).toHaveBeenCalled();
handleCancellationErrorSpy.mockRestore();
sendSpy.mockRestore();
Object.defineProperty(process.stdin, 'isTTY', {
@@ -46,12 +46,7 @@ import stripAnsi from 'strip-ansi';
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { handleAtCommand } from './ui/hooks/atCommandProcessor.js';
import {
handleError,
handleToolError,
handleCancellationError,
handleMaxTurnsExceededError,
} from './utils/errors.js';
import { handleError, handleToolError } from './utils/errors.js';
import { TextOutput } from './ui/utils/textOutput.js';
interface RunNonInteractiveParams {
@@ -188,7 +183,6 @@ export async function runNonInteractive({
};
let errorToHandle: unknown | undefined;
let terminalProcessExitHandled = false;
let abortSession = () => {};
try {
consolePatcher.patch();
@@ -213,6 +207,8 @@ export async function runNonInteractive({
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EPIPE') {
// Exit gracefully if the pipe is closed.
cleanupStdinCancellation();
consolePatcher.cleanup();
process.exit(0);
}
});
@@ -303,7 +299,7 @@ export async function runNonInteractive({
};
abortController.signal.addEventListener('abort', abortSession);
if (abortController.signal.aborted) {
return handleCancellationError(config);
throw new FatalCancellationError('Operation cancelled.');
}
// Start the agentic loop (runs in background)
@@ -417,11 +413,6 @@ export async function runNonInteractive({
return errToThrow;
};
const runTerminalExitHandler = (handler: () => never): never => {
terminalProcessExitHandled = true;
return handler();
};
// Consume AgentEvents for output formatting
let responseText = '';
let preToolResponseText: string | undefined;
@@ -515,17 +506,12 @@ export async function runNonInteractive({
}
if (event.data?.['errorType'] === ToolErrorType.NO_SPACE_LEFT) {
terminalProcessExitHandled = true;
handleToolError(
event.name,
new Error(errorMsg),
config,
typeof event.data?.['errorType'] === 'string'
? event.data['errorType']
: undefined,
displayText,
throw new FatalToolExecutionError(
'Error executing tool ' +
event.name +
': ' +
(displayText || errorMsg),
);
return;
}
handleToolError(
event.name,
@@ -570,15 +556,15 @@ export async function runNonInteractive({
}
case 'agent_end': {
if (event.reason === 'aborted') {
runTerminalExitHandler(() => handleCancellationError(config));
throw new FatalCancellationError('Operation cancelled.');
} else if (event.reason === 'max_turns') {
const isConfiguredTurnLimit =
typeof event.data?.['maxTurns'] === 'number' ||
typeof event.data?.['turnCount'] === 'number';
if (isConfiguredTurnLimit) {
runTerminalExitHandler(() =>
handleMaxTurnsExceededError(config),
throw new FatalTurnLimitedError(
'Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.',
);
} else if (streamFormatter) {
streamFormatter.emitEvent({
@@ -629,9 +615,6 @@ export async function runNonInteractive({
}
if (errorToHandle) {
if (terminalProcessExitHandled) {
throw errorToHandle;
}
handleError(errorToHandle, config);
}
});