fix(coding-agent): retry RPC stdout backpressure

closes #4897
This commit is contained in:
Armin Ronacher
2026-05-24 22:59:35 +02:00
parent 9600ded922
commit ce0e801d8e
4 changed files with 76 additions and 22 deletions

View File

@@ -19,7 +19,12 @@ import type {
ExtensionWidgetOptions,
WorkingIndicatorOptions,
} from "../../core/extensions/index.ts";
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.ts";
import {
flushRawStdout,
takeOverStdout,
waitForRawStdoutBackpressure,
writeRawStdout,
} from "../../core/output-guard.ts";
import { killTrackedDetachedChildren } from "../../utils/shell.ts";
import { type Theme, theme } from "../interactive/theme/theme.ts";
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.ts";
@@ -49,6 +54,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
takeOverStdout();
let session = runtimeHost.session;
let unsubscribe: (() => void) | undefined;
let unsubscribeBackpressure: (() => void) | undefined;
const output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {
writeRawStdout(serializeJsonLine(obj));
@@ -343,9 +349,13 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
});
unsubscribe?.();
unsubscribeBackpressure?.();
unsubscribe = session.subscribe((event) => {
output(event);
});
unsubscribeBackpressure = session.agent.subscribe(async () => {
await waitForRawStdoutBackpressure();
});
};
const registerSignalHandlers = (): void => {
@@ -357,7 +367,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
for (const signal of signals) {
const handler = () => {
killTrackedDetachedChildren();
void shutdown(signal === "SIGHUP" ? 129 : 143);
void shutdown(signal === "SIGHUP" ? 129 : 143, signal);
};
process.on(signal, handler);
signalCleanupHandlers.push(() => process.off(signal, handler));
@@ -665,7 +675,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
*/
let detachInput = () => {};
async function shutdown(exitCode = 0): Promise<never> {
async function shutdown(exitCode = 0, signal?: NodeJS.Signals): Promise<never> {
if (shuttingDown) {
process.exit(exitCode);
}
@@ -674,9 +684,13 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
cleanup();
}
unsubscribe?.();
unsubscribeBackpressure?.();
await runtimeHost.dispose();
detachInput();
process.stdin.pause();
if (signal !== "SIGTERM") {
await flushRawStdout();
}
process.exit(exitCode);
}
@@ -697,6 +711,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
`Failed to parse command: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
),
);
await waitForRawStdoutBackpressure();
return;
}
@@ -721,6 +736,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
const response = await handleCommand(command);
if (response) {
output(response);
await waitForRawStdoutBackpressure();
}
await checkShutdownRequested();
} catch (commandError: unknown) {
@@ -731,6 +747,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
commandError instanceof Error ? commandError.message : String(commandError),
),
);
await waitForRawStdoutBackpressure();
}
};