diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index ef4ef7fdb..23ae20071 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1584,10 +1584,9 @@ export class Config { if (!toolName) { // Log warning and skip this tool instead of crashing - console.warn( - `[Config] Skipping tool registration: ${className} is missing static Name property. ` + - `Tools must define a static Name property to be registered. ` + - `Location: config.ts:registerCoreTool`, + this.debugLogger.warn( + `Skipping tool registration: ${className} is missing static Name property. ` + + `Tools must define a static Name property to be registered.`, ); return; } @@ -1596,8 +1595,8 @@ export class Config { try { registry.registerTool(new ToolClass(...args)); } catch (error) { - console.error( - `[Config] Failed to register tool ${className} (${toolName}):`, + this.debugLogger.error( + `Failed to register tool ${className} (${toolName}):`, error, ); throw error; // Re-throw after logging context diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts index efaf7fb7a..0d52b3aba 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts @@ -29,6 +29,9 @@ import { RequestTokenEstimator } from '../../utils/request-tokenizer/index.js'; import { safeJsonParse } from '../../utils/safeJsonParse.js'; import { AnthropicContentConverter } from './converter.js'; import { buildRuntimeFetchOptions } from '../../utils/runtimeFetchOptions.js'; +import { createDebugLogger } from '../../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('ANTHROPIC'); type StreamingBlockState = { type: string; @@ -117,7 +120,7 @@ export class AnthropicContentGenerator implements ContentGenerator { totalTokens: result.totalTokens, }; } catch (error) { - console.warn( + debugLogger.warn( 'Failed to calculate tokens with tokenizer, ' + 'falling back to simple method:', error, diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 09821e602..f1183f917 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -15,6 +15,9 @@ import type { // Config import { ApprovalMode, type Config } from '../config/config.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('CLIENT'); // Core modules import type { ContentGenerator } from './contentGenerator.js'; @@ -262,9 +265,7 @@ export class GeminiClient { contextLines.join('\n'), ]; - if (this.config.getDebugMode()) { - console.log(contextParts.join('\n')); - } + debugLogger.debug(contextParts.join('\n')); return { contextParts, newIdeContext: currentIdeContext, @@ -394,9 +395,7 @@ export class GeminiClient { changeLines.join('\n'), ]; - if (this.config.getDebugMode()) { - console.log(contextParts.join('\n')); - } + debugLogger.debug(contextParts.join('\n')); return { contextParts, newIdeContext: currentIdeContext, diff --git a/packages/core/src/core/coreToolScheduler.ts b/packages/core/src/core/coreToolScheduler.ts index c7e2806ac..933ca18ab 100644 --- a/packages/core/src/core/coreToolScheduler.ts +++ b/packages/core/src/core/coreToolScheduler.ts @@ -18,6 +18,9 @@ import type { AnyToolInvocation, ChatRecordingService, } from '../index.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('TOOL_SCHEDULER'); import { ToolConfirmationOutcome, ApprovalMode, @@ -1301,7 +1304,7 @@ export class CoreToolScheduler { this.setStatusInternal(pendingTool.request.callId, 'scheduled'); } } catch (error) { - console.error( + debugLogger.error( `Error checking confirmation for tool ${pendingTool.request.callId}:`, error, ); diff --git a/packages/core/src/core/openaiContentGenerator/errorHandler.ts b/packages/core/src/core/openaiContentGenerator/errorHandler.ts index fe74a87a9..8a7509187 100644 --- a/packages/core/src/core/openaiContentGenerator/errorHandler.ts +++ b/packages/core/src/core/openaiContentGenerator/errorHandler.ts @@ -5,6 +5,9 @@ */ import type { GenerateContentParameters } from '@google/genai'; +import { createDebugLogger } from '../../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('OPENAI_ERROR'); export interface RequestContext { userPromptId: string; @@ -48,7 +51,7 @@ export class EnhancedErrorHandler implements ErrorHandler { const logPrefix = context.isStreaming ? 'OpenAI API Streaming Error:' : 'OpenAI API Error:'; - console.error(logPrefix, errorMessage); + debugLogger.error(logPrefix, errorMessage); } // Provide helpful timeout-specific error message diff --git a/packages/core/src/core/openaiContentGenerator/openaiContentGenerator.ts b/packages/core/src/core/openaiContentGenerator/openaiContentGenerator.ts index 2009b273c..5c8479bc0 100644 --- a/packages/core/src/core/openaiContentGenerator/openaiContentGenerator.ts +++ b/packages/core/src/core/openaiContentGenerator/openaiContentGenerator.ts @@ -15,6 +15,9 @@ import { EnhancedErrorHandler } from './errorHandler.js'; import { RequestTokenEstimator } from '../../utils/request-tokenizer/index.js'; import type { ContentGeneratorConfig } from '../contentGenerator.js'; import { isAbortError } from '../../utils/errors.js'; +import { createDebugLogger } from '../../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('OPENAI'); export class OpenAIContentGenerator implements ContentGenerator { protected pipeline: ContentGenerationPipeline; @@ -88,7 +91,7 @@ export class OpenAIContentGenerator implements ContentGenerator { totalTokens: result.totalTokens, }; } catch (error) { - console.warn( + debugLogger.warn( 'Failed to calculate tokens with new tokenizer, falling back to simple method:', error, ); @@ -152,7 +155,7 @@ export class OpenAIContentGenerator implements ContentGenerator { ], }; } catch (error) { - console.error('OpenAI API Embedding Error:', error); + debugLogger.error('OpenAI API Embedding Error:', error); throw new Error( `OpenAI API error: ${error instanceof Error ? error.message : String(error)}`, ); diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 8d3ff4683..6245bd992 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -12,6 +12,9 @@ import process from 'node:process'; import { isGitRepository } from '../utils/gitUtils.js'; import { QWEN_CONFIG_DIR } from '../tools/memoryTool.js'; import type { GenerateContentConfig } from '@google/genai'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('PROMPTS'); export function resolvePathFromEnv(envVar?: string): { isSwitch: boolean; @@ -46,7 +49,7 @@ export function resolvePathFromEnv(envVar?: string): { } } catch (error) { // If os.homedir() fails, we catch the error instead of crashing. - console.warn( + debugLogger.warn( `Could not resolve home directory for path: ${trimmedEnvVar}`, error, ); @@ -774,7 +777,7 @@ function getToolCallExamples(model?: string): string { case 'general': return generalToolCallExamples; default: - console.warn( + debugLogger.warn( `Unknown QWEN_CODE_TOOL_CALL_STYLE value: ${toolCallStyle}. Using model-based detection.`, ); break; diff --git a/packages/core/src/fallback/handler.ts b/packages/core/src/fallback/handler.ts index 375ce252f..d61a2f063 100644 --- a/packages/core/src/fallback/handler.ts +++ b/packages/core/src/fallback/handler.ts @@ -6,6 +6,9 @@ import type { Config } from '../config/config.js'; import { AuthType } from '../core/contentGenerator.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('FALLBACK'); export async function handleFallback( config: Config, @@ -56,17 +59,17 @@ async function handleQwenOAuthError(error?: unknown): Promise { errorMessage.includes('too many requests'); if (isAuthError) { - console.warn('Qwen OAuth authentication error detected:', errorMessage); + debugLogger.warn('Qwen OAuth authentication error detected:', errorMessage); // The QwenContentGenerator should automatically handle token refresh // If it still fails, it likely means the refresh token is also expired - console.log( + debugLogger.info( 'Note: If this persists, you may need to re-authenticate with Qwen OAuth', ); return null; } if (isRateLimitError) { - console.warn('Qwen API rate limit encountered:', errorMessage); + debugLogger.warn('Qwen API rate limit encountered:', errorMessage); // For rate limiting, we don't need to do anything special // The retry mechanism will handle the backoff return null; diff --git a/packages/core/src/models/modelRegistry.ts b/packages/core/src/models/modelRegistry.ts index cec6ebb94..c5de235cd 100644 --- a/packages/core/src/models/modelRegistry.ts +++ b/packages/core/src/models/modelRegistry.ts @@ -14,6 +14,9 @@ import { } from './types.js'; import { DEFAULT_QWEN_MODEL } from '../config/models.js'; import { QWEN_OAUTH_MODELS } from './constants.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('MODEL_REGISTRY'); export { QWEN_OAUTH_MODELS } from './constants.js'; @@ -62,8 +65,8 @@ export class ModelRegistry { const authType = validateAuthTypeKey(rawKey); if (!authType) { - console.warn( - `[ModelRegistry] Invalid authType key "${rawKey}" in modelProviders config. Expected one of: ${Object.values(AuthType).join(', ')}. Skipping.`, + debugLogger.warn( + `Invalid authType key "${rawKey}" in modelProviders config. Expected one of: ${Object.values(AuthType).join(', ')}. Skipping.`, ); continue; } diff --git a/packages/core/src/prompts/prompt-registry.ts b/packages/core/src/prompts/prompt-registry.ts index aac761ab2..8a0367f77 100644 --- a/packages/core/src/prompts/prompt-registry.ts +++ b/packages/core/src/prompts/prompt-registry.ts @@ -5,6 +5,9 @@ */ import type { DiscoveredMCPPrompt } from '../tools/mcp-client.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('PROMPT_REGISTRY'); export class PromptRegistry { private prompts: Map = new Map(); @@ -16,7 +19,7 @@ export class PromptRegistry { registerPrompt(prompt: DiscoveredMCPPrompt): void { if (this.prompts.has(prompt.name)) { const newName = `${prompt.serverName}_${prompt.name}`; - console.warn( + debugLogger.warn( `Prompt with name "${prompt.name}" is already registered. Renaming to "${newName}".`, ); this.prompts.set(newName, { ...prompt, name: newName }); diff --git a/packages/core/src/services/chatRecordingService.ts b/packages/core/src/services/chatRecordingService.ts index b56302126..b3f2d96cd 100644 --- a/packages/core/src/services/chatRecordingService.ts +++ b/packages/core/src/services/chatRecordingService.ts @@ -17,6 +17,9 @@ import { } from '@google/genai'; import * as jsonl from '../utils/jsonl-utils.js'; import { getGitBranch } from '../utils/gitUtils.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('CHAT_RECORDING'); import type { ChatCompressionInfo, ToolCallResponseInfo, @@ -247,7 +250,7 @@ export class ChatRecordingService { jsonl.writeLineSync(conversationFile, record); this.lastRecordUuid = record.uuid; } catch (error) { - console.error('Error appending record:', error); + debugLogger.error('Error appending record:', error); throw error; } } @@ -266,7 +269,7 @@ export class ChatRecordingService { }; this.appendRecord(record); } catch (error) { - console.error('Error saving user message:', error); + debugLogger.error('Error saving user message:', error); } } @@ -300,7 +303,7 @@ export class ChatRecordingService { this.appendRecord(record); } catch (error) { - console.error('Error saving assistant turn:', error); + debugLogger.error('Error saving assistant turn:', error); } } @@ -344,7 +347,7 @@ export class ChatRecordingService { this.appendRecord(record); } catch (error) { - console.error('Error saving tool result:', error); + debugLogger.error('Error saving tool result:', error); } } @@ -364,7 +367,7 @@ export class ChatRecordingService { this.appendRecord(record); } catch (error) { - console.error('Error saving slash command record:', error); + debugLogger.error('Error saving slash command record:', error); } } @@ -384,7 +387,7 @@ export class ChatRecordingService { this.appendRecord(record); } catch (error) { - console.error('Error saving chat compression record:', error); + debugLogger.error('Error saving chat compression record:', error); } } @@ -402,7 +405,7 @@ export class ChatRecordingService { this.appendRecord(record); } catch (error) { - console.error('Error saving ui telemetry record:', error); + debugLogger.error('Error saving ui telemetry record:', error); } } } diff --git a/packages/core/src/services/loopDetectionService.ts b/packages/core/src/services/loopDetectionService.ts index f8a06b5ef..9117d0120 100644 --- a/packages/core/src/services/loopDetectionService.ts +++ b/packages/core/src/services/loopDetectionService.ts @@ -23,6 +23,9 @@ import { isFunctionResponse, } from '../utils/messageInspectors.js'; import { DEFAULT_QWEN_MODEL } from '../config/models.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('LOOP_DETECTION'); const TOOL_CALL_LOOP_THRESHOLD = 5; const CONTENT_LOOP_THRESHOLD = 10; @@ -438,7 +441,7 @@ export class LoopDetectionService { if (typeof result['confidence'] === 'number') { if (result['confidence'] > 0.9) { if (typeof result['reasoning'] === 'string' && result['reasoning']) { - console.warn(result['reasoning']); + debugLogger.warn(result['reasoning']); } logLoopDetected( this.config, diff --git a/packages/core/src/services/sessionService.ts b/packages/core/src/services/sessionService.ts index ca70dd565..b3630e6ad 100644 --- a/packages/core/src/services/sessionService.ts +++ b/packages/core/src/services/sessionService.ts @@ -17,6 +17,9 @@ import type { UiTelemetryRecordPayload, } from './chatRecordingService.js'; import { uiTelemetryService } from '../telemetry/uiTelemetry.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('SESSION'); /** * Session item for list display. @@ -324,7 +327,7 @@ export class SessionService { return await jsonl.read(filePath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error('Error reading session file:', error); + debugLogger.error('Error reading session file:', error); } return []; } diff --git a/packages/core/src/skills/skill-load.ts b/packages/core/src/skills/skill-load.ts index ed88eb907..2844310cd 100644 --- a/packages/core/src/skills/skill-load.ts +++ b/packages/core/src/skills/skill-load.ts @@ -2,6 +2,9 @@ import type { SkillConfig, SkillValidationResult } from './types.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import { parse as parseYaml } from '../utils/yaml-parser.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('SKILL_LOAD'); const SKILL_MANIFEST_FILE = 'SKILL.md'; @@ -26,7 +29,7 @@ export async function loadSkillsFromDir( const config = parseSkillContent(content, skillManifest); skills.push(config); } catch (error) { - console.warn( + debugLogger.warn( `Failed to parse skill at ${skillDir}: ${error instanceof Error ? error.message : 'Unknown error'}`, ); continue; diff --git a/packages/core/src/skills/skill-manager.ts b/packages/core/src/skills/skill-manager.ts index a5851fb51..9a5dda774 100644 --- a/packages/core/src/skills/skill-manager.ts +++ b/packages/core/src/skills/skill-manager.ts @@ -19,6 +19,9 @@ import type { import { SkillError, SkillErrorCode } from './types.js'; import type { Config } from '../config/config.js'; import { validateConfig } from './skill-load.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('SKILL_MANAGER'); const QWEN_CONFIG_DIR = '.qwen'; const SKILLS_CONFIG_DIR = 'skills'; @@ -57,7 +60,7 @@ export class SkillManager { try { listener(); } catch (error) { - console.warn('Skill change listener threw an error:', error); + debugLogger.warn('Skill change listener threw an error:', error); } } } @@ -214,7 +217,7 @@ export class SkillManager { stopWatching(): void { for (const watcher of this.watchers.values()) { void watcher.close().catch((error) => { - console.warn('Failed to close skills watcher:', error); + debugLogger.warn('Failed to close skills watcher:', error); }); } this.watchers.clear(); @@ -426,7 +429,7 @@ export class SkillManager { // Skip directories without valid SKILL.md if (error instanceof SkillError) { // Parse error was already recorded - console.warn( + debugLogger.warn( `Failed to parse skill at ${skillDir}: ${error.message}`, ); } @@ -486,7 +489,7 @@ export class SkillManager { .get(existingPath) ?.close() .catch((error) => { - console.warn( + debugLogger.warn( `Failed to close skills watcher for ${existingPath}:`, error, ); @@ -508,11 +511,11 @@ export class SkillManager { this.scheduleRefresh(); }) .on('error', (error) => { - console.warn(`Skills watcher error for ${watchPath}:`, error); + debugLogger.warn(`Skills watcher error for ${watchPath}:`, error); }); this.watchers.set(watchPath, watcher); } catch (error) { - console.warn( + debugLogger.warn( `Failed to watch skills directory at ${watchPath}:`, error, ); @@ -536,7 +539,7 @@ export class SkillManager { try { await fs.mkdir(baseDir, { recursive: true }); } catch (error) { - console.warn( + debugLogger.warn( `Failed to create user skills directory at ${baseDir}:`, error, ); diff --git a/packages/core/src/subagents/subagent-manager.ts b/packages/core/src/subagents/subagent-manager.ts index 2e1aa472f..fea33040c 100644 --- a/packages/core/src/subagents/subagent-manager.ts +++ b/packages/core/src/subagents/subagent-manager.ts @@ -28,6 +28,9 @@ import { SubagentError, SubagentErrorCode } from './types.js'; import { SubagentValidator } from './validation.js'; import { SubAgentScope } from './subagent.js'; import type { Config } from '../config/config.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('SUBAGENT_MANAGER'); import { BuiltinAgentRegistry } from './builtin-agents.js'; import { ToolDisplayNamesMigration } from '../tools/tool-names.js'; @@ -59,7 +62,7 @@ export class SubagentManager { try { listener(); } catch (error) { - console.warn('Subagent change listener threw an error:', error); + debugLogger.warn('Subagent change listener threw an error:', error); } } } @@ -699,7 +702,7 @@ export class SubagentManager { // If no match found, preserve the original identifier as-is // This allows for tools that might not be registered yet or custom tools result.push(toolIdentifier); - console.warn( + debugLogger.warn( `Tool "${toolIdentifier}" not found in tool registry, preserving as-is`, ); } diff --git a/packages/core/src/subagents/subagent.ts b/packages/core/src/subagents/subagent.ts index 39e43e54f..8c236b734 100644 --- a/packages/core/src/subagents/subagent.ts +++ b/packages/core/src/subagents/subagent.ts @@ -6,6 +6,9 @@ import { reportError } from '../utils/errorReporting.js'; import type { Config } from '../config/config.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('SUBAGENT'); import { type ToolCallRequestInfo } from '../core/turn.js'; import { CoreToolScheduler, @@ -515,7 +518,7 @@ export class SubAgentScope { } as SubAgentRoundEvent); } } catch (error) { - console.error('Error during subagent execution:', error); + debugLogger.error('Error during subagent execution:', error); this.terminateMode = SubagentTerminateMode.ERROR; this.eventEmitter?.emit(SubAgentEventType.ERROR, { subagentId: this.subagentId, diff --git a/packages/core/src/utils/editor.ts b/packages/core/src/utils/editor.ts index 78d8b37fb..70f574ab4 100644 --- a/packages/core/src/utils/editor.ts +++ b/packages/core/src/utils/editor.ts @@ -5,6 +5,9 @@ */ import { execSync, spawn, spawnSync } from 'node:child_process'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('EDITOR'); export type EditorType = | 'vscode' @@ -174,7 +177,7 @@ export async function openDiff( ): Promise { const diffCommand = getDiffCommand(oldPath, newPath, editor); if (!diffCommand) { - console.error('No diff tool available. Install a supported editor.'); + debugLogger.error('No diff tool available. Install a supported editor.'); return; } @@ -217,7 +220,7 @@ export async function openDiff( }); }); } catch (error) { - console.error(error); + debugLogger.error(error); throw error; } } diff --git a/packages/core/src/utils/environmentContext.ts b/packages/core/src/utils/environmentContext.ts index 2bbe12dd9..8c8f0e538 100644 --- a/packages/core/src/utils/environmentContext.ts +++ b/packages/core/src/utils/environmentContext.ts @@ -7,6 +7,9 @@ import type { Content, Part } from '@google/genai'; import type { Config } from '../config/config.js'; import { getFolderStructure } from './getFolderStructure.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('ENV_CONTEXT'); /** * Generates a string describing the current workspace directories and their structures. @@ -87,18 +90,18 @@ ${directoryContext} text: `\n--- Full File Context ---\n${result.llmContent}`, }); } else { - console.warn( + debugLogger.warn( 'Full context requested, but read_many_files returned no content.', ); } } else { - console.warn( + debugLogger.warn( 'Full context requested, but read_many_files tool not found.', ); } } catch (error) { // Not using reportError here as it's a startup/config phase, not a chat/generation phase error. - console.error('Error reading full file context:', error); + debugLogger.error('Error reading full file context:', error); initialParts.push({ text: '\n--- Error reading full file context ---', }); diff --git a/packages/core/src/utils/fileUtils.ts b/packages/core/src/utils/fileUtils.ts index 940e9794d..430a4ea74 100644 --- a/packages/core/src/utils/fileUtils.ts +++ b/packages/core/src/utils/fileUtils.ts @@ -12,6 +12,9 @@ import mime from 'mime/lite'; import { ToolErrorType } from '../tools/tool-error.js'; import { BINARY_EXTENSIONS } from './ignorePatterns.js'; import type { Config } from '../config/config.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('FILE_UTILS'); // Default values for encoding and separator format export const DEFAULT_ENCODING: BufferEncoding = 'utf-8'; @@ -218,7 +221,7 @@ export async function isBinaryFile(filePath: string): Promise { // If >30% non-printable characters, consider it binary return nonPrintableCount / bytesRead > 0.3; } catch (error) { - console.warn( + debugLogger.warn( `Failed to check if file is binary: ${filePath}`, error instanceof Error ? error.message : String(error), ); @@ -228,7 +231,7 @@ export async function isBinaryFile(filePath: string): Promise { try { await fh.close(); } catch (closeError) { - console.warn( + debugLogger.warn( `Failed to close file handle for: ${filePath}`, closeError instanceof Error ? closeError.message : String(closeError), ); diff --git a/packages/core/src/utils/getFolderStructure.ts b/packages/core/src/utils/getFolderStructure.ts index 20b0ea130..da50426f1 100644 --- a/packages/core/src/utils/getFolderStructure.ts +++ b/packages/core/src/utils/getFolderStructure.ts @@ -11,6 +11,9 @@ import { getErrorMessage, isNodeError } from './errors.js'; import type { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import type { FileFilteringOptions } from '../config/constants.js'; import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('FOLDER_STRUCTURE'); const MAX_ITEMS = 20; const TRUNCATION_INDICATOR = '...'; @@ -104,7 +107,7 @@ async function readFullStructure( isNodeError(error) && (error.code === 'EACCES' || error.code === 'ENOENT') ) { - console.warn( + debugLogger.warn( `Warning: Could not read directory ${currentPath}: ${error.message}`, ); if (currentPath === rootPath && error.code === 'ENOENT') { @@ -342,7 +345,10 @@ export async function getFolderStructure( return `${summary}\n\n${resolvedPath}${path.sep}\n${structureLines.join('\n')}`; } catch (error: unknown) { - console.error(`Error getting folder structure for ${resolvedPath}:`, error); + debugLogger.error( + `Error getting folder structure for ${resolvedPath}:`, + error, + ); return `Error processing directory "${resolvedPath}": ${getErrorMessage(error)}`; } } diff --git a/packages/core/src/utils/installationManager.ts b/packages/core/src/utils/installationManager.ts index 154e3b759..c81355bf4 100644 --- a/packages/core/src/utils/installationManager.ts +++ b/packages/core/src/utils/installationManager.ts @@ -8,6 +8,9 @@ import * as fs from 'node:fs'; import { randomUUID } from 'node:crypto'; import * as path from 'node:path'; import { Storage } from '../config/storage.js'; +import { createDebugLogger } from '../utils/debugLogger.js'; + +const debugLogger = createDebugLogger('INSTALLATION'); export class InstallationManager { private getInstallationIdPath(): string { @@ -48,7 +51,7 @@ export class InstallationManager { return installationId; } catch (error) { - console.error( + debugLogger.error( 'Error accessing installation ID file, generating ephemeral ID:', error, ); diff --git a/packages/core/src/utils/jsonl-utils.ts b/packages/core/src/utils/jsonl-utils.ts index d0771f1b7..7c74282ef 100644 --- a/packages/core/src/utils/jsonl-utils.ts +++ b/packages/core/src/utils/jsonl-utils.ts @@ -25,6 +25,9 @@ import fs from 'node:fs'; import path from 'node:path'; import readline from 'node:readline'; import { Mutex } from 'async-mutex'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('JSONL'); /** * A map of file paths to mutexes for preventing concurrent writes. @@ -68,7 +71,7 @@ export async function readLines( return results; } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error( + debugLogger.error( `Error reading first ${count} lines from ${filePath}:`, error, ); @@ -100,7 +103,7 @@ export async function read(filePath: string): Promise { return results; } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error(`Error reading ${filePath}:`, error); + debugLogger.error(`Error reading ${filePath}:`, error); } return []; } @@ -174,7 +177,7 @@ export async function countLines(filePath: string): Promise { return count; } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error(`Error counting lines in ${filePath}:`, error); + debugLogger.error(`Error counting lines in ${filePath}:`, error); } return 0; } diff --git a/packages/core/src/utils/llm-edit-fixer.ts b/packages/core/src/utils/llm-edit-fixer.ts index bc81c8e62..eee806acf 100644 --- a/packages/core/src/utils/llm-edit-fixer.ts +++ b/packages/core/src/utils/llm-edit-fixer.ts @@ -10,6 +10,9 @@ import { type BaseLlmClient } from '../core/baseLlmClient.js'; import { LruCache } from './LruCache.js'; import { DEFAULT_QWEN_FLASH_MODEL } from '../config/models.js'; import { promptIdContext } from './promptIdContext.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('LLM_EDIT_FIXER'); const MAX_CACHE_SIZE = 50; @@ -112,7 +115,7 @@ export async function FixLLMEditWithInstruction( let promptId = promptIdContext.getStore(); if (!promptId) { promptId = `llm-fixer-fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`; - console.warn( + debugLogger.warn( `Could not find promptId in context. This is unexpected. Using a fallback ID: ${promptId}`, ); } diff --git a/packages/core/src/utils/nextSpeakerChecker.ts b/packages/core/src/utils/nextSpeakerChecker.ts index 9570179c9..b69fe011d 100644 --- a/packages/core/src/utils/nextSpeakerChecker.ts +++ b/packages/core/src/utils/nextSpeakerChecker.ts @@ -9,6 +9,9 @@ import { DEFAULT_QWEN_MODEL } from '../config/models.js'; import type { GeminiChat } from '../core/geminiChat.js'; import { isFunctionResponse } from './messageInspectors.js'; import type { Config } from '../config/config.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('NEXT_SPEAKER'); const CHECK_PROMPT = `Analyze *only* the content and structure of your immediately preceding response (your last turn in the conversation history). Based *strictly* on that response, determine who should logically speak next: the 'user' or the 'model' (you). **Decision Rules (apply in order):** @@ -126,7 +129,7 @@ export async function checkNextSpeaker( } return null; } catch (error) { - console.warn( + debugLogger.warn( 'Failed to talk to Gemini endpoint when seeing if conversation should continue.', error, ); diff --git a/packages/core/src/utils/openaiLogger.ts b/packages/core/src/utils/openaiLogger.ts index a4fc41ec8..c6a56ee0a 100644 --- a/packages/core/src/utils/openaiLogger.ts +++ b/packages/core/src/utils/openaiLogger.ts @@ -8,6 +8,9 @@ import * as path from 'node:path'; import { promises as fs } from 'node:fs'; import { v4 as uuidv4 } from 'uuid'; import * as os from 'os'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('OPENAI_LOGGER'); /** * Logger specifically for OpenAI API requests and responses @@ -47,7 +50,7 @@ export class OpenAILogger { await fs.mkdir(this.logDir, { recursive: true }); this.initialized = true; } catch (error) { - console.error('Failed to initialize OpenAI logger:', error); + debugLogger.error('Failed to initialize OpenAI logger:', error); throw new Error(`Failed to initialize OpenAI logger: ${error}`); } } @@ -95,7 +98,7 @@ export class OpenAILogger { await fs.writeFile(filePath, JSON.stringify(logData, null, 2), 'utf-8'); return filePath; } catch (writeError) { - console.error('Failed to write OpenAI log file:', writeError); + debugLogger.error('Failed to write OpenAI log file:', writeError); throw new Error(`Failed to write OpenAI log file: ${writeError}`); } } @@ -123,7 +126,7 @@ export class OpenAILogger { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return []; } - console.error('Failed to read OpenAI log directory:', error); + debugLogger.error('Failed to read OpenAI log directory:', error); return []; } } @@ -138,7 +141,7 @@ export class OpenAILogger { const content = await fs.readFile(filePath, 'utf-8'); return JSON.parse(content); } catch (error) { - console.error(`Failed to read log file ${filePath}:`, error); + debugLogger.error(`Failed to read log file ${filePath}:`, error); throw new Error(`Failed to read log file: ${error}`); } } diff --git a/packages/core/src/utils/request-tokenizer/imageTokenizer.ts b/packages/core/src/utils/request-tokenizer/imageTokenizer.ts index b55c6b9ec..76933a0c8 100644 --- a/packages/core/src/utils/request-tokenizer/imageTokenizer.ts +++ b/packages/core/src/utils/request-tokenizer/imageTokenizer.ts @@ -6,6 +6,9 @@ import type { ImageMetadata } from './types.js'; import { isSupportedImageMimeType } from './supportedImageFormats.js'; +import { createDebugLogger } from '../debugLogger.js'; + +const debugLogger = createDebugLogger('IMAGE_TOKENIZER'); /** * Image tokenizer for calculating image tokens based on dimensions @@ -44,7 +47,7 @@ export class ImageTokenizer { try { // Check if the MIME type is supported if (!isSupportedImageMimeType(mimeType)) { - console.warn(`Unsupported image format: ${mimeType}`); + debugLogger.warn(`Unsupported image format: ${mimeType}`); // Return default metadata for unsupported formats return { width: 512, @@ -65,7 +68,7 @@ export class ImageTokenizer { dataSize: buffer.length, }; } catch (error) { - console.warn('Failed to extract image metadata:', error); + debugLogger.warn('Failed to extract image metadata:', error); // Return default metadata for fallback return { width: 512, @@ -320,7 +323,7 @@ export class ImageTokenizer { const metadata = await this.extractImageMetadata(data, mimeType); results.push(this.calculateTokens(metadata)); } catch (error) { - console.warn('Error calculating tokens for image:', error); + debugLogger.warn('Error calculating tokens for image:', error); // Return minimum tokens as fallback results.push( ImageTokenizer.MIN_TOKENS_PER_IMAGE + @@ -499,7 +502,7 @@ export class ImageTokenizer { } // Fallback: return default dimensions if we can't parse the structure - console.warn('Could not extract HEIC dimensions, using default'); + debugLogger.warn('Could not extract HEIC dimensions, using default'); return { width: 512, height: 512 }; } } diff --git a/packages/core/src/utils/request-tokenizer/requestTokenizer.ts b/packages/core/src/utils/request-tokenizer/requestTokenizer.ts index ace8d10f6..95ebd9a67 100644 --- a/packages/core/src/utils/request-tokenizer/requestTokenizer.ts +++ b/packages/core/src/utils/request-tokenizer/requestTokenizer.ts @@ -13,6 +13,9 @@ import type { import type { TokenCalculationResult } from './types.js'; import { TextTokenizer } from './textTokenizer.js'; import { ImageTokenizer } from './imageTokenizer.js'; +import { createDebugLogger } from '../debugLogger.js'; + +const debugLogger = createDebugLogger('TOKENIZER'); /** * Simple request token estimator that handles text and image content serially @@ -77,7 +80,7 @@ export class RequestTokenizer { processingTime, }; } catch (error) { - console.error('Error calculating tokens:', error); + debugLogger.error('Error calculating tokens:', error); // Fallback calculation const fallbackTokens = this.calculateFallbackTokens(request); @@ -105,7 +108,7 @@ export class RequestTokenizer { // Avoid per-part rounding inflation by estimating once on the combined text. return await this.textTokenizer.calculateTokens(textContents.join('')); } catch (error) { - console.warn('Error calculating text tokens:', error); + debugLogger.warn('Error calculating text tokens:', error); // Fallback: character-based estimation const totalChars = textContents.join('').length; return Math.ceil(totalChars / 4); @@ -125,7 +128,7 @@ export class RequestTokenizer { await this.imageTokenizer.calculateTokensBatch(imageContents); return tokenCounts.reduce((sum, count) => sum + count, 0); } catch (error) { - console.warn('Error calculating image tokens:', error); + debugLogger.warn('Error calculating image tokens:', error); // Fallback: minimum tokens per image return imageContents.length * 6; // 4 image tokens + 2 special tokens as minimum } @@ -151,7 +154,7 @@ export class RequestTokenizer { // Rough estimate: 1 token per 100 bytes of audio data totalTokens += Math.max(Math.ceil(dataSize / 100), 10); // Minimum 10 tokens per audio } catch (error) { - console.warn('Error calculating audio tokens:', error); + debugLogger.warn('Error calculating audio tokens:', error); totalTokens += 10; // Fallback minimum } } @@ -169,7 +172,7 @@ export class RequestTokenizer { // Treat other content as text, and avoid per-item rounding inflation. return await this.textTokenizer.calculateTokens(otherContents.join('')); } catch (error) { - console.warn('Error calculating other content tokens:', error); + debugLogger.warn('Error calculating other content tokens:', error); // Fallback: character-based estimation const totalChars = otherContents.join('').length; return Math.ceil(totalChars / 4); @@ -184,7 +187,7 @@ export class RequestTokenizer { const content = JSON.stringify(request.contents); return Math.ceil(content.length / 4); // Rough estimate: 1 token ≈ 4 characters } catch (error) { - console.warn('Error in fallback token calculation:', error); + debugLogger.warn('Error in fallback token calculation:', error); return 100; // Conservative fallback } } @@ -321,7 +324,7 @@ export class RequestTokenizer { otherContents.push(serialized); } } catch (error) { - console.warn('Failed to serialize unknown part type:', error); + debugLogger.warn('Failed to serialize unknown part type:', error); } } } diff --git a/packages/core/src/utils/retry.ts b/packages/core/src/utils/retry.ts index 4a0672ae9..a62bf37b3 100644 --- a/packages/core/src/utils/retry.ts +++ b/packages/core/src/utils/retry.ts @@ -156,7 +156,7 @@ export async function retryWithBackoff( if (delayDurationMs > 0) { // Respect Retry-After header if present and parsed - console.warn( + debugLogger.warn( `Attempt ${attempt} failed with status ${delayErrorStatus ?? 'unknown'}. Retrying after explicit delay of ${delayDurationMs}ms...`, error, ); @@ -279,25 +279,25 @@ function logRetryAttempt( } if (errorStatus === 429) { - console.warn(message, error); + debugLogger.warn(message, error); } else if (errorStatus && errorStatus >= 500 && errorStatus < 600) { - console.error(message, error); + debugLogger.error(message, error); } else if (error instanceof Error) { // Fallback for errors that might not have a status but have a message if (error.message.includes('429')) { - console.warn( + debugLogger.warn( `Attempt ${attempt} failed with 429 error (no Retry-After header). Retrying with backoff...`, error, ); } else if (error.message.match(/5\d{2}/)) { - console.error( + debugLogger.error( `Attempt ${attempt} failed with 5xx error. Retrying with backoff...`, error, ); } else { - console.warn(message, error); // Default to warn for other errors + debugLogger.warn(message, error); // Default to warn for other errors } } else { - console.warn(message, error); // Default to warn if error type is unknown + debugLogger.warn(message, error); // Default to warn if error type is unknown } } diff --git a/packages/core/src/utils/ripgrepUtils.ts b/packages/core/src/utils/ripgrepUtils.ts index 1f432541e..7d90c46f6 100644 --- a/packages/core/src/utils/ripgrepUtils.ts +++ b/packages/core/src/utils/ripgrepUtils.ts @@ -9,6 +9,9 @@ import { fileURLToPath } from 'node:url'; import { execFile } from 'node:child_process'; import { fileExists } from './fileUtils.js'; import { execCommand, isCommandAvailable } from './shell-utils.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('RIPGREP'); const RIPGREP_COMMAND = 'rg'; const RIPGREP_BUFFER_LIMIT = 20_000_000; // Keep buffers aligned with the original bundle. @@ -313,7 +316,7 @@ export async function runRipgrep( // Log warnings for abnormal exits (except syntax errors) if (!syntaxError && truncated) { - console.warn( + debugLogger.warn( `ripgrep exited abnormally (signal=${error.signal} code=${error.code}) with stderr:\n${stderr.trim() || '(empty)'}`, ); } diff --git a/packages/core/src/utils/safeJsonParse.ts b/packages/core/src/utils/safeJsonParse.ts index 03dc6a27f..76b796fb8 100644 --- a/packages/core/src/utils/safeJsonParse.ts +++ b/packages/core/src/utils/safeJsonParse.ts @@ -5,6 +5,9 @@ */ import { jsonrepair } from 'jsonrepair'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('JSON_PARSE'); /** * Safely parse JSON string with jsonrepair fallback for malformed JSON. @@ -34,7 +37,7 @@ export function safeJsonParse>( // jsonrepair always returns a string, so we need to parse it return JSON.parse(repairedJson) as T; } catch (repairError) { - console.error('Failed to parse JSON even with jsonrepair:', { + debugLogger.error('Failed to parse JSON even with jsonrepair:', { originalError: error, repairError, jsonString, diff --git a/packages/core/src/utils/summarizer.ts b/packages/core/src/utils/summarizer.ts index c5290cfa2..8c2b391ea 100644 --- a/packages/core/src/utils/summarizer.ts +++ b/packages/core/src/utils/summarizer.ts @@ -13,6 +13,9 @@ import type { import type { GeminiClient } from '../core/client.js'; import { DEFAULT_QWEN_FLASH_MODEL } from '../config/models.js'; import { getResponseText, partToString } from './partUtils.js'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('SUMMARIZER'); /** * A function that summarizes the result of a tool execution. @@ -90,7 +93,7 @@ export async function summarizeToolOutput( )) as unknown as GenerateContentResponse; return getResponseText(parsedResponse) || textToSummarize; } catch (error) { - console.error('Failed to summarize tool output.', error); + debugLogger.error('Failed to summarize tool output.', error); return textToSummarize; } } diff --git a/packages/core/src/utils/systemEncoding.ts b/packages/core/src/utils/systemEncoding.ts index d76bdbabf..4bce69f4c 100644 --- a/packages/core/src/utils/systemEncoding.ts +++ b/packages/core/src/utils/systemEncoding.ts @@ -7,6 +7,9 @@ import { execSync } from 'node:child_process'; import os from 'node:os'; import { detect as chardetDetect } from 'chardet'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('ENCODING'); // Cache for system encoding to avoid repeated detection // Use undefined to indicate "not yet checked" vs null meaning "checked but failed" @@ -75,7 +78,7 @@ export function getSystemEncoding(): string | null { `Unable to parse Windows code page from 'chcp' output "${output.trim()}". `, ); } catch (error) { - console.warn( + debugLogger.warn( `Failed to get Windows code page using 'chcp' command: ${error instanceof Error ? error.message : String(error)}. ` + `Will attempt to detect encoding from command output instead.`, ); @@ -97,7 +100,7 @@ export function getSystemEncoding(): string | null { .toString() .trim(); } catch (_e) { - console.warn('Failed to get locale charmap.'); + debugLogger.warn('Failed to get locale charmap.'); return null; } } @@ -150,7 +153,7 @@ export function windowsCodePageToEncoding(cp: number): string | null { return map[cp]; } - console.warn(`Unable to determine encoding for windows code page ${cp}.`); + debugLogger.warn(`Unable to determine encoding for windows code page ${cp}.`); return null; // Return null if no mapping found } @@ -168,7 +171,7 @@ export function detectEncodingFromBuffer(buffer: Buffer): string | null { return detected.toLowerCase(); } } catch (error) { - console.warn('Failed to detect encoding with chardet:', error); + debugLogger.warn('Failed to detect encoding with chardet:', error); } return null; diff --git a/packages/core/src/utils/workspaceContext.ts b/packages/core/src/utils/workspaceContext.ts index 97db6852c..1fe1a6dfe 100755 --- a/packages/core/src/utils/workspaceContext.ts +++ b/packages/core/src/utils/workspaceContext.ts @@ -8,6 +8,9 @@ import { isNodeError } from '../utils/errors.js'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as process from 'node:process'; +import { createDebugLogger } from './debugLogger.js'; + +const debugLogger = createDebugLogger('WORKSPACE'); export type Unsubscribe = () => void; @@ -53,7 +56,7 @@ export class WorkspaceContext { listener(); } catch (e) { // Don't let one listener break others. - console.error('Error in WorkspaceContext listener:', e); + debugLogger.error('Error in WorkspaceContext listener:', e); } } } @@ -72,8 +75,8 @@ export class WorkspaceContext { this.directories.add(resolved); this.notifyDirectoriesChanged(); } catch (err) { - console.warn( - `[WARN] Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`, + debugLogger.warn( + `Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`, ); } }