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', commands: 'Commands',
configuration: 'Configuration', configuration: 'Configuration',
'configuration-v1': 'Configuration (v1)', 'configuration-v1': 'Configuration (v1)',
'structured-output': 'Structured Output',
themes: 'Themes', themes: 'Themes',
tutorials: 'Tutorials', tutorials: 'Tutorials',
'keyboard-shortcuts': 'Keyboard Shortcuts', 'keyboard-shortcuts': 'Keyboard Shortcuts',

View File

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

View File

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

View File

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