fix: report AgentExecutionBlocked in non-interactive programmatic modes (#26262)

This commit is contained in:
Coco Sheng
2026-04-30 16:41:35 -04:00
committed by GitHub
parent a03ec92436
commit 2f0c7518ad
11 changed files with 488 additions and 24 deletions
+30 -3
View File
@@ -56,6 +56,13 @@ interface RunNonInteractiveParams {
resumedSessionData?: ResumedSessionData;
}
/**
* Runs the non-interactive CLI loop.
*
* Programmatic output formats (JSON, STREAM_JSON) use lenient sanitization
* by stripping ANSI escape sequences from messages to ensure clean,
* parseable output for downstream consumers.
*/
export async function runNonInteractive(
params: RunNonInteractiveParams,
): Promise<void> {
@@ -296,6 +303,7 @@ export async function runNonInteractive(
let turnCount = 0;
let invalidStreamError: string | undefined;
const warnings: string[] = [];
while (true) {
turnCount++;
if (
@@ -352,23 +360,27 @@ export async function runNonInteractive(
}
toolCallRequests.push(event.value);
} else if (event.type === GeminiEventType.LoopDetected) {
const message = 'Loop detected, stopping execution';
if (streamFormatter) {
streamFormatter.emitEvent({
type: JsonStreamEventType.ERROR,
timestamp: new Date().toISOString(),
severity: 'warning',
message: 'Loop detected, stopping execution',
message,
});
}
warnings.push(message);
} else if (event.type === GeminiEventType.MaxSessionTurns) {
const message = 'Maximum session turns exceeded';
if (streamFormatter) {
streamFormatter.emitEvent({
type: JsonStreamEventType.ERROR,
timestamp: new Date().toISOString(),
severity: 'error',
message: 'Maximum session turns exceeded',
message,
});
}
warnings.push(message);
} else if (event.type === GeminiEventType.Error) {
throw event.value.error;
} else if (event.type === GeminiEventType.AgentExecutionStopped) {
@@ -395,7 +407,15 @@ export async function runNonInteractive(
const blockMessage = `Agent execution blocked: ${event.value.systemMessage?.trim() || event.value.reason}`;
if (config.getOutputFormat() === OutputFormat.TEXT) {
process.stderr.write(`[WARNING] ${blockMessage}\n`);
} else if (streamFormatter) {
streamFormatter.emitEvent({
type: JsonStreamEventType.ERROR,
timestamp: new Date().toISOString(),
severity: 'warning',
message: stripAnsi(blockMessage),
});
}
warnings.push(blockMessage);
} else if (event.type === GeminiEventType.InvalidStream) {
invalidStreamError =
'Invalid stream: The model returned an empty response or malformed tool call.';
@@ -507,7 +527,13 @@ export async function runNonInteractive(
const formatter = new JsonFormatter();
const stats = uiTelemetryService.getMetrics();
textOutput.write(
formatter.format(config.getSessionId(), responseText, stats),
formatter.format(
config.getSessionId(),
responseText,
stats,
undefined,
warnings,
),
);
} else {
textOutput.ensureTrailingNewline(); // Ensure a final newline
@@ -538,6 +564,7 @@ export async function runNonInteractive(
invalidStreamError
? { type: 'INVALID_STREAM', message: invalidStreamError }
: undefined,
warnings,
),
);
} else {