mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 12:57:12 -07:00
fix: restore nonInteractiveCli.ts and update AgentSession logic
- Reverted packages/cli/src/nonInteractiveCli.ts to the main branch state, restoring the fallback while(true) loop behavior. - Added debugLogger.error call for unknown event types in the AgentSession event loop.
This commit is contained in:
@@ -6,41 +6,33 @@
|
||||
|
||||
import type {
|
||||
Config,
|
||||
ToolCallRequestInfo,
|
||||
ResumedSessionData,
|
||||
UserFeedbackPayload,
|
||||
AgentEvent,
|
||||
ContentPart,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { isSlashCommand } from './ui/utils/commandUtils.js';
|
||||
import type { LoadedSettings } from './config/settings.js';
|
||||
import {
|
||||
convertSessionToClientHistory,
|
||||
FatalError,
|
||||
FatalAuthenticationError,
|
||||
GeminiEventType,
|
||||
FatalInputError,
|
||||
FatalSandboxError,
|
||||
FatalConfigError,
|
||||
FatalTurnLimitedError,
|
||||
FatalToolExecutionError,
|
||||
FatalCancellationError,
|
||||
promptIdContext,
|
||||
OutputFormat,
|
||||
JsonFormatter,
|
||||
StreamJsonFormatter,
|
||||
JsonStreamEventType,
|
||||
uiTelemetryService,
|
||||
debugLogger,
|
||||
coreEvents,
|
||||
CoreEvent,
|
||||
createWorkingStdio,
|
||||
recordToolCallInteractions,
|
||||
ToolErrorType,
|
||||
Scheduler,
|
||||
ROOT_SCHEDULER_ID,
|
||||
LegacyAgentSession,
|
||||
ToolErrorType,
|
||||
geminiPartsToContentParts,
|
||||
debugLogger,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import type { Part } from '@google/genai';
|
||||
import type { Content, Part } from '@google/genai';
|
||||
import readline from 'node:readline';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
@@ -163,6 +155,8 @@ export async function runNonInteractive(
|
||||
}, 200);
|
||||
|
||||
abortController.abort();
|
||||
// Note: Don't exit here - let the abort flow through the system
|
||||
// and trigger handleCancellationError() which will exit with proper code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -193,8 +187,6 @@ export async function runNonInteractive(
|
||||
};
|
||||
|
||||
let errorToHandle: unknown | undefined;
|
||||
let terminalProcessExitHandled = false;
|
||||
let abortSession = () => {};
|
||||
try {
|
||||
consolePatcher.patch();
|
||||
|
||||
@@ -259,6 +251,9 @@ export async function runNonInteractive(
|
||||
config,
|
||||
settings,
|
||||
);
|
||||
// If a slash command is found and returns a prompt, use it.
|
||||
// Otherwise, slashCommandResult falls through to the default prompt
|
||||
// handling.
|
||||
if (slashCommandResult) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
query = slashCommandResult as Part[];
|
||||
@@ -276,6 +271,8 @@ export async function runNonInteractive(
|
||||
escapePastedAtSymbols: false,
|
||||
});
|
||||
if (error || !processedQuery) {
|
||||
// An error occurred during @include processing (e.g., file not found).
|
||||
// The error message is already logged by handleAtCommand.
|
||||
throw new FatalInputError(
|
||||
error || 'Exiting due to an error processing the @ command.',
|
||||
);
|
||||
@@ -294,335 +291,235 @@ export async function runNonInteractive(
|
||||
});
|
||||
}
|
||||
|
||||
// Create LegacyAgentSession — owns the agentic loop
|
||||
const session = new LegacyAgentSession({
|
||||
client: geminiClient,
|
||||
scheduler,
|
||||
config,
|
||||
promptId: prompt_id,
|
||||
});
|
||||
let currentMessages: Content[] = [{ role: 'user', parts: query }];
|
||||
|
||||
// Wire Ctrl+C to session abort
|
||||
abortSession = () => {
|
||||
void session.abort();
|
||||
};
|
||||
abortController.signal.addEventListener('abort', abortSession);
|
||||
if (abortController.signal.aborted) {
|
||||
return handleCancellationError(config);
|
||||
}
|
||||
let turnCount = 0;
|
||||
while (true) {
|
||||
turnCount++;
|
||||
if (
|
||||
config.getMaxSessionTurns() >= 0 &&
|
||||
turnCount > config.getMaxSessionTurns()
|
||||
) {
|
||||
handleMaxTurnsExceededError(config);
|
||||
}
|
||||
const toolCallRequests: ToolCallRequestInfo[] = [];
|
||||
|
||||
// Start the agentic loop (runs in background)
|
||||
const { streamId } = await session.send({
|
||||
message: {
|
||||
content: geminiPartsToContentParts(query),
|
||||
displayContent: input,
|
||||
},
|
||||
});
|
||||
if (streamId === null) {
|
||||
throw new Error(
|
||||
'LegacyAgentSession.send() unexpectedly returned no stream for a message send.',
|
||||
const responseStream = geminiClient.sendMessageStream(
|
||||
currentMessages[0]?.parts || [],
|
||||
abortController.signal,
|
||||
prompt_id,
|
||||
undefined,
|
||||
false,
|
||||
turnCount === 1 ? input : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const getTextContent = (parts?: ContentPart[]): string | undefined => {
|
||||
const text = parts
|
||||
?.map((part) => (part.type === 'text' ? part.text : ''))
|
||||
.join('');
|
||||
return text ? text : undefined;
|
||||
};
|
||||
let responseText = '';
|
||||
for await (const event of responseStream) {
|
||||
if (abortController.signal.aborted) {
|
||||
handleCancellationError(config);
|
||||
}
|
||||
|
||||
const emitFinalSuccessResult = (): void => {
|
||||
if (streamFormatter) {
|
||||
const metrics = uiTelemetryService.getMetrics();
|
||||
const durationMs = Date.now() - startTime;
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'success',
|
||||
stats: streamFormatter.convertToStreamStats(metrics, durationMs),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const stats = uiTelemetryService.getMetrics();
|
||||
textOutput.write(
|
||||
formatter.format(config.getSessionId(), responseText, stats),
|
||||
);
|
||||
} else {
|
||||
textOutput.ensureTrailingNewline();
|
||||
}
|
||||
};
|
||||
|
||||
const reconstructFatalError = (event: AgentEvent<'error'>): Error => {
|
||||
const errorMeta = event._meta;
|
||||
const name =
|
||||
typeof errorMeta?.['errorName'] === 'string'
|
||||
? errorMeta['errorName']
|
||||
: undefined;
|
||||
|
||||
let errToThrow: Error;
|
||||
switch (name) {
|
||||
case 'FatalAuthenticationError':
|
||||
errToThrow = new FatalAuthenticationError(event.message);
|
||||
break;
|
||||
case 'FatalInputError':
|
||||
errToThrow = new FatalInputError(event.message);
|
||||
break;
|
||||
case 'FatalSandboxError':
|
||||
errToThrow = new FatalSandboxError(event.message);
|
||||
break;
|
||||
case 'FatalConfigError':
|
||||
errToThrow = new FatalConfigError(event.message);
|
||||
break;
|
||||
case 'FatalTurnLimitedError':
|
||||
errToThrow = new FatalTurnLimitedError(event.message);
|
||||
break;
|
||||
case 'FatalToolExecutionError':
|
||||
errToThrow = new FatalToolExecutionError(event.message);
|
||||
break;
|
||||
case 'FatalCancellationError':
|
||||
errToThrow = new FatalCancellationError(event.message);
|
||||
break;
|
||||
case 'FatalError':
|
||||
errToThrow = new FatalError(
|
||||
event.message,
|
||||
typeof errorMeta?.['exitCode'] === 'number'
|
||||
? errorMeta['exitCode']
|
||||
: 1,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
errToThrow = new Error(event.message);
|
||||
if (name) {
|
||||
Object.defineProperty(errToThrow, 'name', {
|
||||
value: name,
|
||||
enumerable: true,
|
||||
if (event.type === GeminiEventType.Content) {
|
||||
const isRaw =
|
||||
config.getRawOutput() || config.getAcceptRawOutputRisk();
|
||||
const output = isRaw ? event.value : stripAnsi(event.value);
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.MESSAGE,
|
||||
timestamp: new Date().toISOString(),
|
||||
role: 'assistant',
|
||||
content: output,
|
||||
delta: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (errorMeta?.['exitCode'] !== undefined) {
|
||||
Object.defineProperty(errToThrow, 'exitCode', {
|
||||
value: errorMeta['exitCode'],
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
if (errorMeta?.['code'] !== undefined) {
|
||||
Object.defineProperty(errToThrow, 'code', {
|
||||
value: errorMeta['code'],
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
if (errorMeta?.['status'] !== undefined) {
|
||||
Object.defineProperty(errToThrow, 'status', {
|
||||
value: errorMeta['status'],
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
return errToThrow;
|
||||
};
|
||||
|
||||
const runTerminalExitHandler = (handler: () => never): never => {
|
||||
terminalProcessExitHandled = true;
|
||||
return handler();
|
||||
};
|
||||
|
||||
// Consume AgentEvents for output formatting
|
||||
let responseText = '';
|
||||
let preToolResponseText: string | undefined;
|
||||
let streamEnded = false;
|
||||
for await (const event of session.stream({ streamId })) {
|
||||
if (streamEnded) break;
|
||||
switch (event.type) {
|
||||
case 'message': {
|
||||
if (event.role === 'agent') {
|
||||
for (const part of event.content) {
|
||||
if (part.type === 'text') {
|
||||
const isRaw =
|
||||
config.getRawOutput() || config.getAcceptRawOutputRisk();
|
||||
const output = isRaw ? part.text : stripAnsi(part.text);
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.MESSAGE,
|
||||
timestamp: new Date().toISOString(),
|
||||
role: 'assistant',
|
||||
content: output,
|
||||
delta: true,
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
responseText += output;
|
||||
} else {
|
||||
if (part.text) {
|
||||
textOutput.write(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
responseText += output;
|
||||
} else {
|
||||
if (event.value) {
|
||||
textOutput.write(output);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'tool_request': {
|
||||
if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
// Final JSON output should reflect the last assistant answer after
|
||||
// any tool orchestration, not intermediate pre-tool text.
|
||||
preToolResponseText = responseText || preToolResponseText;
|
||||
responseText = '';
|
||||
}
|
||||
} else if (event.type === GeminiEventType.ToolCallRequest) {
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.TOOL_USE,
|
||||
timestamp: new Date().toISOString(),
|
||||
tool_name: event.name,
|
||||
tool_id: event.requestId,
|
||||
parameters: event.args,
|
||||
tool_name: event.value.name,
|
||||
tool_id: event.value.callId,
|
||||
parameters: event.value.args,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'tool_response': {
|
||||
textOutput.ensureTrailingNewline();
|
||||
if (streamFormatter) {
|
||||
const displayText = getTextContent(event.displayContent);
|
||||
const errorMsg = getTextContent(event.content) ?? 'Tool error';
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.TOOL_RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
tool_id: event.requestId,
|
||||
status: event.isError ? 'error' : 'success',
|
||||
output: displayText,
|
||||
error: event.isError
|
||||
? {
|
||||
type:
|
||||
typeof event.data?.['errorType'] === 'string'
|
||||
? event.data['errorType']
|
||||
: 'TOOL_EXECUTION_ERROR',
|
||||
message: errorMsg,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
if (event.isError) {
|
||||
const displayText = getTextContent(event.displayContent);
|
||||
const errorMsg = getTextContent(event.content) ?? 'Tool error';
|
||||
|
||||
if (event.data?.['errorType'] === ToolErrorType.STOP_EXECUTION) {
|
||||
if (
|
||||
config.getOutputFormat() === OutputFormat.JSON &&
|
||||
!responseText &&
|
||||
preToolResponseText
|
||||
) {
|
||||
responseText = preToolResponseText;
|
||||
}
|
||||
const stopMessage = `Agent execution stopped: ${errorMsg}`;
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
return;
|
||||
}
|
||||
handleToolError(
|
||||
event.name,
|
||||
new Error(errorMsg),
|
||||
config,
|
||||
typeof event.data?.['errorType'] === 'string'
|
||||
? event.data['errorType']
|
||||
: undefined,
|
||||
displayText,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
if (event.fatal) {
|
||||
throw reconstructFatalError(event);
|
||||
}
|
||||
|
||||
const errorCode = event._meta?.['code'];
|
||||
|
||||
if (errorCode === 'AGENT_EXECUTION_BLOCKED') {
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`[WARNING] ${event.message}\n`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const severity =
|
||||
event.status === 'RESOURCE_EXHAUSTED' ? 'error' : 'warning';
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`[WARNING] ${event.message}\n`);
|
||||
}
|
||||
toolCallRequests.push(event.value);
|
||||
} else if (event.type === GeminiEventType.LoopDetected) {
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.ERROR,
|
||||
timestamp: new Date().toISOString(),
|
||||
severity,
|
||||
message: event.message,
|
||||
severity: 'warning',
|
||||
message: 'Loop detected, stopping execution',
|
||||
});
|
||||
}
|
||||
break;
|
||||
} else if (event.type === GeminiEventType.MaxSessionTurns) {
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.ERROR,
|
||||
timestamp: new Date().toISOString(),
|
||||
severity: 'error',
|
||||
message: 'Maximum session turns exceeded',
|
||||
});
|
||||
}
|
||||
} else if (event.type === GeminiEventType.Error) {
|
||||
throw event.value.error;
|
||||
} else if (event.type === GeminiEventType.AgentExecutionStopped) {
|
||||
const stopMessage = `Agent execution stopped: ${event.value.systemMessage?.trim() || event.value.reason}`;
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
}
|
||||
// Emit final result event for streaming JSON if needed
|
||||
if (streamFormatter) {
|
||||
const metrics = uiTelemetryService.getMetrics();
|
||||
const durationMs = Date.now() - startTime;
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'success',
|
||||
stats: streamFormatter.convertToStreamStats(
|
||||
metrics,
|
||||
durationMs,
|
||||
),
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else if (event.type === GeminiEventType.AgentExecutionBlocked) {
|
||||
const blockMessage = `Agent execution blocked: ${event.value.systemMessage?.trim() || event.value.reason}`;
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`[WARNING] ${blockMessage}\n`);
|
||||
}
|
||||
}
|
||||
case 'agent_end': {
|
||||
if (event.reason === 'aborted') {
|
||||
runTerminalExitHandler(() => handleCancellationError(config));
|
||||
} else if (event.reason === 'max_turns') {
|
||||
const isConfiguredTurnLimit =
|
||||
typeof event.data?.['maxTurns'] === 'number' ||
|
||||
typeof event.data?.['turnCount'] === 'number';
|
||||
}
|
||||
|
||||
if (isConfiguredTurnLimit) {
|
||||
runTerminalExitHandler(() =>
|
||||
handleMaxTurnsExceededError(config),
|
||||
);
|
||||
} else if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.ERROR,
|
||||
timestamp: new Date().toISOString(),
|
||||
severity: 'error',
|
||||
message: 'Maximum session turns exceeded',
|
||||
});
|
||||
}
|
||||
if (toolCallRequests.length > 0) {
|
||||
textOutput.ensureTrailingNewline();
|
||||
const completedToolCalls = await scheduler.schedule(
|
||||
toolCallRequests,
|
||||
abortController.signal,
|
||||
);
|
||||
const toolResponseParts: Part[] = [];
|
||||
|
||||
for (const completedToolCall of completedToolCalls) {
|
||||
const toolResponse = completedToolCall.response;
|
||||
const requestInfo = completedToolCall.request;
|
||||
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.TOOL_RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
tool_id: requestInfo.callId,
|
||||
status:
|
||||
completedToolCall.status === 'error' ? 'error' : 'success',
|
||||
output:
|
||||
typeof toolResponse.resultDisplay === 'string'
|
||||
? toolResponse.resultDisplay
|
||||
: undefined,
|
||||
error: toolResponse.error
|
||||
? {
|
||||
type: toolResponse.errorType || 'TOOL_EXECUTION_ERROR',
|
||||
message: toolResponse.error.message,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const stopMessage =
|
||||
typeof event.data?.['message'] === 'string'
|
||||
? event.data['message']
|
||||
: '';
|
||||
if (stopMessage && config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`Agent execution stopped: ${stopMessage}\n`);
|
||||
if (toolResponse.error) {
|
||||
handleToolError(
|
||||
requestInfo.name,
|
||||
toolResponse.error,
|
||||
config,
|
||||
toolResponse.errorType || 'TOOL_EXECUTION_ERROR',
|
||||
typeof toolResponse.resultDisplay === 'string'
|
||||
? toolResponse.resultDisplay
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
emitFinalSuccessResult();
|
||||
streamEnded = true;
|
||||
break;
|
||||
if (toolResponse.responseParts) {
|
||||
toolResponseParts.push(...toolResponse.responseParts);
|
||||
}
|
||||
}
|
||||
case 'initialize':
|
||||
case 'session_update':
|
||||
case 'agent_start':
|
||||
case 'tool_update':
|
||||
case 'elicitation_request':
|
||||
case 'elicitation_response':
|
||||
case 'usage':
|
||||
// TODO: We should think about converting this into the usage event.
|
||||
// fallthrough
|
||||
case 'custom':
|
||||
// Explicitly ignore these non-interactive events
|
||||
break;
|
||||
default:
|
||||
debugLogger.error('Unknown agent event type:', event);
|
||||
event satisfies never;
|
||||
break;
|
||||
|
||||
// Record tool calls with full metadata before sending responses to Gemini
|
||||
try {
|
||||
const currentModel =
|
||||
geminiClient.getCurrentSequenceModel() ?? config.getModel();
|
||||
geminiClient
|
||||
.getChat()
|
||||
.recordCompletedToolCalls(currentModel, completedToolCalls);
|
||||
|
||||
await recordToolCallInteractions(config, completedToolCalls);
|
||||
} catch (error) {
|
||||
debugLogger.error(
|
||||
`Error recording completed tool call information: ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if any tool requested to stop execution immediately
|
||||
const stopExecutionTool = completedToolCalls.find(
|
||||
(tc) => tc.response.errorType === ToolErrorType.STOP_EXECUTION,
|
||||
);
|
||||
|
||||
if (stopExecutionTool && stopExecutionTool.response.error) {
|
||||
const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error.message}`;
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
}
|
||||
|
||||
// Emit final result event for streaming JSON
|
||||
if (streamFormatter) {
|
||||
const metrics = uiTelemetryService.getMetrics();
|
||||
const durationMs = Date.now() - startTime;
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'success',
|
||||
stats: streamFormatter.convertToStreamStats(
|
||||
metrics,
|
||||
durationMs,
|
||||
),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const stats = uiTelemetryService.getMetrics();
|
||||
textOutput.write(
|
||||
formatter.format(config.getSessionId(), responseText, stats),
|
||||
);
|
||||
} else {
|
||||
textOutput.ensureTrailingNewline(); // Ensure a final newline
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
currentMessages = [{ role: 'user', parts: toolResponseParts }];
|
||||
} else {
|
||||
// Emit final result event for streaming JSON
|
||||
if (streamFormatter) {
|
||||
const metrics = uiTelemetryService.getMetrics();
|
||||
const durationMs = Date.now() - startTime;
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'success',
|
||||
stats: streamFormatter.convertToStreamStats(metrics, durationMs),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const stats = uiTelemetryService.getMetrics();
|
||||
textOutput.write(
|
||||
formatter.format(config.getSessionId(), responseText, stats),
|
||||
);
|
||||
} else {
|
||||
textOutput.ensureTrailingNewline(); // Ensure a final newline
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -630,16 +527,12 @@ export async function runNonInteractive(
|
||||
} finally {
|
||||
// Cleanup stdin cancellation before other cleanup
|
||||
cleanupStdinCancellation();
|
||||
abortController.signal.removeEventListener('abort', abortSession);
|
||||
|
||||
consolePatcher.cleanup();
|
||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
}
|
||||
|
||||
if (errorToHandle) {
|
||||
if (terminalProcessExitHandled) {
|
||||
throw errorToHandle;
|
||||
}
|
||||
handleError(errorToHandle, config);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
LegacyAgentSession,
|
||||
ToolErrorType,
|
||||
geminiPartsToContentParts,
|
||||
debugLogger,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import type { Part } from '@google/genai';
|
||||
@@ -599,6 +600,7 @@ export async function runNonInteractive({
|
||||
// Explicitly ignore these non-interactive events
|
||||
break;
|
||||
default:
|
||||
debugLogger.error('Unknown agent event type:', event);
|
||||
event satisfies never;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@ Enter to submit · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
● 1. TypeScript
|
||||
2. JavaScript
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
@@ -22,6 +33,17 @@ Enter to submit · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
● 1. TypeScript
|
||||
2. JavaScript
|
||||
3. Type another language...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = `
|
||||
"Choose an option
|
||||
|
||||
@@ -181,6 +203,19 @@ Enter to submit · Tab/Shift+Tab to edit answers · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > shows warning for unanswered questions on Review tab 2`] = `
|
||||
"← □ License │ □ README │ ≡ Review →
|
||||
|
||||
Which license?
|
||||
|
||||
● 1. MIT
|
||||
Permissive license
|
||||
2. Enter a custom value
|
||||
|
||||
Enter to select · ←/→ to switch questions · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > verifies "All of the above" visual state with snapshot 1`] = `
|
||||
"Which features?
|
||||
(Select all that apply)
|
||||
|
||||
@@ -140,6 +140,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
|
||||
"Overview
|
||||
|
||||
Add user authentication to the CLI application.
|
||||
|
||||
Implementation Steps
|
||||
|
||||
1. Create src/auth/AuthService.ts with login/logout methods
|
||||
2. Add session storage in src/storage/SessionStore.ts
|
||||
3. Update src/commands/index.ts to check auth status
|
||||
4. Add tests in src/auth/__tests__/
|
||||
|
||||
Files to Modify
|
||||
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
|
||||
"Overview
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ exports[`DenseToolMessage > flattens newlines in string results 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`DenseToolMessage > flattens newlines in string results 2`] = `
|
||||
" ✓ test-tool Test description → Line 1 Line 2
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`DenseToolMessage > renders correctly for Edit tool using confirmationDetails 1`] = `
|
||||
" ? Edit styles.scss → Confirming
|
||||
"
|
||||
|
||||
@@ -151,6 +151,15 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should handle diff with only header and no changes 2`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ No changes detected. │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should handle empty diff content 1`] = `
|
||||
"No diff content.
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user