feat(cli): migrate console calls to debugLogger and stdioHelpers (M3 Phase 7-9)

Route CLI console.* calls to structured logging:
- Debug/internal diagnostics → debugLogger (logfile)
- User-facing output → writeStdoutLine/writeStderrLine/clearScreen (stdioHelpers)
- Add stdioHelpers.ts with writeStdoutLine, writeStderrLine, clearScreen
- Migrate pre-session files (gemini.tsx, sandbox.ts, config.ts) to stdioHelpers
- Migrate extension/MCP commands to stdioHelpers
- Migrate non-interactive session/control to debugLogger
- Migrate UI hooks and components to debugLogger
This commit is contained in:
tanzhenxin 2026-01-26 15:02:37 +08:00
parent 45df0e8b82
commit 7995c65571
82 changed files with 606 additions and 485 deletions

View file

@ -8,6 +8,7 @@ import type {
Config,
ConfigInitializeOptions,
} from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { StreamJsonInputReader } from './io/StreamJsonInputReader.js';
import { StreamJsonOutputAdapter } from './io/StreamJsonOutputAdapter.js';
import { ControlContext } from './control/ControlContext.js';
@ -34,6 +35,8 @@ import { createMinimalSettings } from '../config/settings.js';
import { runNonInteractive } from '../nonInteractiveCli.js';
import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
const debugLogger = createDebugLogger('NON_INTERACTIVE_SESSION');
class Session {
private userMessageQueue: CLIUserMessage[] = [];
private abortController: AbortController;
@ -46,7 +49,6 @@ class Session {
private dispatcher: ControlDispatcher | null = null;
private controlService: ControlService | null = null;
private controlSystemEnabled: boolean | null = null;
private debugMode: boolean;
private shutdownHandler: (() => void) | null = null;
private initialPrompt: CLIUserMessage | null = null;
private processingPromise: Promise<void> | null = null;
@ -62,7 +64,6 @@ class Session {
constructor(config: Config, initialPrompt?: CLIUserMessage) {
this.config = config;
this.sessionId = config.getSessionId();
this.debugMode = config.getDebugMode();
this.abortController = new AbortController();
this.initialPrompt = initialPrompt ?? null;
@ -105,17 +106,13 @@ class Session {
return;
}
if (this.debugMode) {
console.error('[Session] Initializing config');
}
debugLogger.debug('[Session] Initializing config');
try {
await this.config.initialize(options);
this.configInitialized = true;
} catch (error) {
if (this.debugMode) {
console.error('[Session] Failed to initialize config:', error);
}
debugLogger.error('[Session] Failed to initialize config:', error);
throw error;
}
}
@ -125,9 +122,7 @@ class Session {
*/
private completeInitialization(): void {
if (this.initializationResolve) {
if (this.debugMode) {
console.error('[Session] Initialization complete');
}
debugLogger.debug('[Session] Initialization complete');
this.initializationResolve();
this.initializationResolve = null;
this.initializationReject = null;
@ -139,9 +134,7 @@ class Session {
*/
private failInitialization(error: Error): void {
if (this.initializationReject) {
if (this.debugMode) {
console.error('[Session] Initialization failed:', error);
}
debugLogger.error('[Session] Initialization failed:', error);
this.initializationReject(error);
this.initializationResolve = null;
this.initializationReject = null;
@ -213,11 +206,9 @@ class Session {
return;
}
if (this.debugMode) {
console.error(
'[Session] Ignoring non-initialize control request during initialization',
);
}
debugLogger.debug(
'[Session] Ignoring non-initialize control request during initialization',
);
return;
}
@ -254,9 +245,7 @@ class Session {
// Initialization complete!
this.completeInitialization();
} catch (error) {
if (this.debugMode) {
console.error('[Session] SDK mode initialization failed:', error);
}
debugLogger.error('[Session] SDK mode initialization failed:', error);
this.failInitialization(
error instanceof Error ? error : new Error(String(error)),
);
@ -281,9 +270,7 @@ class Session {
// Enqueue the first user message for processing
this.enqueueUserMessage(userMessage);
} catch (error) {
if (this.debugMode) {
console.error('[Session] Direct mode initialization failed:', error);
}
debugLogger.error('[Session] Direct mode initialization failed:', error);
this.failInitialization(
error instanceof Error ? error : new Error(String(error)),
);
@ -297,18 +284,14 @@ class Session {
private handleControlRequestAsync(request: CLIControlRequest): void {
const dispatcher = this.getDispatcher();
if (!dispatcher) {
if (this.debugMode) {
console.error('[Session] Control system not enabled');
}
debugLogger.warn('[Session] Control system not enabled');
return;
}
// Fire-and-forget: dispatch runs concurrently
// The dispatcher's pendingIncomingRequests tracks completion
void dispatcher.dispatch(request).catch((error) => {
if (this.debugMode) {
console.error('[Session] Control request dispatch error:', error);
}
debugLogger.error('[Session] Control request dispatch error:', error);
// Error response is already sent by dispatcher.dispatch()
});
}
@ -338,9 +321,7 @@ class Session {
private async processUserMessage(userMessage: CLIUserMessage): Promise<void> {
const input = extractUserMessageText(userMessage);
if (!input) {
if (this.debugMode) {
console.error('[Session] No text content in user message');
}
debugLogger.debug('[Session] No text content in user message');
return;
}
@ -362,9 +343,7 @@ class Session {
},
);
} catch (error) {
if (this.debugMode) {
console.error('[Session] Query execution error:', error);
}
debugLogger.error('[Session] Query execution error:', error);
}
}
@ -382,9 +361,7 @@ class Session {
try {
await this.processUserMessage(userMessage);
} catch (error) {
if (this.debugMode) {
console.error('[Session] Error processing user message:', error);
}
debugLogger.error('[Session] Error processing user message:', error);
this.emitErrorResult(error);
}
}
@ -430,18 +407,14 @@ class Session {
}
private handleInterrupt(): void {
if (this.debugMode) {
console.error('[Session] Interrupt requested');
}
debugLogger.info('[Session] Interrupt requested');
this.abortController.abort();
this.abortController = new AbortController();
}
private setupSignalHandlers(): void {
this.shutdownHandler = () => {
if (this.debugMode) {
console.error('[Session] Shutdown signal received');
}
debugLogger.info('[Session] Shutdown signal received');
this.isShuttingDown = true;
this.abortController.abort();
};
@ -458,16 +431,17 @@ class Session {
try {
await this.waitForInitialization();
} catch (error) {
if (this.debugMode) {
console.error('[Session] Initialization error during shutdown:', error);
}
debugLogger.error(
'[Session] Initialization error during shutdown:',
error,
);
}
// 2. Wait for all control request handlers using dispatcher's tracking
if (this.dispatcher) {
const pendingCount = this.dispatcher.getPendingIncomingRequestCount();
if (pendingCount > 0 && this.debugMode) {
console.error(
if (pendingCount > 0) {
debugLogger.debug(
`[Session] Waiting for ${pendingCount} pending control request handlers`,
);
}
@ -476,23 +450,17 @@ class Session {
// 3. Wait for user message processing queue
while (this.processingPromise) {
if (this.debugMode) {
console.error('[Session] Waiting for user message processing');
}
debugLogger.debug('[Session] Waiting for user message processing');
try {
await this.processingPromise;
} catch (error) {
if (this.debugMode) {
console.error('[Session] Error in user message processing:', error);
}
debugLogger.error('[Session] Error in user message processing:', error);
}
}
}
private async shutdown(): Promise<void> {
if (this.debugMode) {
console.error('[Session] Shutting down');
}
debugLogger.debug('[Session] Shutting down');
this.isShuttingDown = true;
@ -528,9 +496,7 @@ class Session {
*/
async run(): Promise<void> {
try {
if (this.debugMode) {
console.error('[Session] Starting session', this.sessionId);
}
debugLogger.info('[Session] Starting session', this.sessionId);
// Handle initial prompt if provided (fire-and-forget)
if (this.initialPrompt !== null) {
@ -571,18 +537,16 @@ class Session {
} else if (isCLIUserMessage(message)) {
// User messages are enqueued, processing runs separately
this.enqueueUserMessage(message as CLIUserMessage);
} else if (this.debugMode) {
if (
!isCLIAssistantMessage(message) &&
!isCLISystemMessage(message) &&
!isCLIResultMessage(message) &&
!isCLIPartialAssistantMessage(message)
) {
console.error(
'[Session] Unknown message type:',
JSON.stringify(message, null, 2),
);
}
} else if (
!isCLIAssistantMessage(message) &&
!isCLISystemMessage(message) &&
!isCLIResultMessage(message) &&
!isCLIPartialAssistantMessage(message)
) {
debugLogger.warn(
'[Session] Unknown message type:',
JSON.stringify(message, null, 2),
);
}
if (this.isShuttingDown) {
@ -590,9 +554,7 @@ class Session {
}
}
} catch (streamError) {
if (this.debugMode) {
console.error('[Session] Stream reading error:', streamError);
}
debugLogger.error('[Session] Stream reading error:', streamError);
throw streamError;
}
@ -600,9 +562,7 @@ class Session {
await this.waitForAllPendingWork();
await this.shutdown();
} catch (error) {
if (this.debugMode) {
console.error('[Session] Error:', error);
}
debugLogger.error('[Session] Error:', error);
await this.shutdown();
throw error;
} finally {