fix: proper SIGINT handler and output in nonInteractive mode

This commit is contained in:
mingholy.lmh
2025-11-12 12:00:52 +08:00
parent d6288b31fd
commit 7abe2a0aed
4 changed files with 25 additions and 27 deletions

View File

@@ -5,7 +5,6 @@ export default {
commands: 'Commands',
configuration: 'Configuration',
'configuration-v1': 'Configuration (v1)',
'structured-output': 'Structured Output',
themes: 'Themes',
tutorials: 'Tutorials',
'keyboard-shortcuts': 'Keyboard Shortcuts',

View File

@@ -220,12 +220,6 @@ export async function main() {
}
const isDebugMode = cliConfig.isDebugMode(argv);
const consolePatcher = new ConsolePatcher({
stderr: true,
debugMode: isDebugMode,
});
consolePatcher.patch();
registerCleanup(consolePatcher.cleanup);
dns.setDefaultResultOrder(
validateDnsResolutionOrder(settings.merged.advanced?.dnsResolutionOrder),
@@ -350,6 +344,15 @@ export async function main() {
process.exit(0);
}
// Setup unified ConsolePatcher based on interactive mode
const isInteractive = config.isInteractive();
const consolePatcher = new ConsolePatcher({
stderr: isInteractive,
debugMode: isDebugMode,
});
consolePatcher.patch();
registerCleanup(consolePatcher.cleanup);
const wasRaw = process.stdin.isRaw;
let kittyProtocolDetectionComplete: Promise<boolean> | undefined;
if (config.isInteractive() && !wasRaw && process.stdin.isTTY) {

View File

@@ -16,7 +16,6 @@
*/
import type { Config } from '@qwen-code/qwen-code-core';
import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
import { StreamJsonInputReader } from './io/StreamJsonInputReader.js';
import { StreamJsonOutputAdapter } from './io/StreamJsonOutputAdapter.js';
import { ControlContext } from './control/ControlContext.js';
@@ -96,7 +95,6 @@ class SessionManager {
private dispatcher: ControlDispatcher | null = null;
private controlService: ControlService | null = null;
private controlSystemEnabled: boolean | null = null;
private consolePatcher: ConsolePatcher;
private debugMode: boolean;
private shutdownHandler: (() => void) | null = null;
private initialPrompt: CLIUserMessage | null = null;
@@ -113,11 +111,6 @@ class SessionManager {
this.abortController = new AbortController();
this.initialPrompt = initialPrompt ?? null;
this.consolePatcher = new ConsolePatcher({
stderr: true,
debugMode: this.debugMode,
});
this.inputReader = new StreamJsonInputReader();
this.outputAdapter = new StreamJsonOutputAdapter(
config,
@@ -232,8 +225,6 @@ class SessionManager {
*/
async run(): Promise<void> {
try {
this.consolePatcher.patch();
if (this.debugMode) {
console.error('[SessionManager] Starting session', this.sessionId);
}
@@ -264,7 +255,6 @@ class SessionManager {
await this.shutdown();
throw error;
} finally {
this.consolePatcher.cleanup();
// Ensure signal handlers are always cleaned up even if shutdown wasn't called
this.cleanupSignalHandlers();
}

View File

@@ -25,7 +25,6 @@ import { StreamJsonOutputAdapter } from './nonInteractive/io/StreamJsonOutputAda
import type { ControlService } from './nonInteractive/control/ControlService.js';
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { handleAtCommand } from './ui/hooks/atCommandProcessor.js';
import {
handleError,
@@ -67,11 +66,6 @@ export async function runNonInteractive(
options: RunNonInteractiveOptions = {},
): Promise<void> {
return promptIdContext.run(prompt_id, async () => {
const consolePatcher = new ConsolePatcher({
stderr: true,
debugMode: config.getDebugMode(),
});
// Create output adapter based on format
let adapter: JsonOutputAdapterInterface | undefined;
const outputFormat = config.getOutputFormat();
@@ -102,12 +96,22 @@ export async function runNonInteractive(
}
};
const geminiClient = config.getGeminiClient();
const abortController = options.abortController ?? new AbortController();
// Setup signal handlers for graceful shutdown
const shutdownHandler = () => {
if (config.getDebugMode()) {
console.error('[runNonInteractive] Shutdown signal received');
}
abortController.abort();
};
try {
consolePatcher.patch();
process.stdout.on('error', stdoutErrorHandler);
const geminiClient = config.getGeminiClient();
const abortController = options.abortController ?? new AbortController();
process.on('SIGINT', shutdownHandler);
process.on('SIGTERM', shutdownHandler);
let initialPartList: PartListUnion | null = extractPartsFromUserMessage(
options.userMessage,
@@ -362,7 +366,9 @@ export async function runNonInteractive(
handleError(error, config);
} finally {
process.stdout.removeListener('error', stdoutErrorHandler);
consolePatcher.cleanup();
// Cleanup signal handlers
process.removeListener('SIGINT', shutdownHandler);
process.removeListener('SIGTERM', shutdownHandler);
if (isTelemetrySdkInitialized()) {
await shutdownTelemetry(config);
}