diff --git a/packages/a2a-server/src/config/config.ts b/packages/a2a-server/src/config/config.ts index 2a56f4cd52..1205c20351 100644 --- a/packages/a2a-server/src/config/config.ts +++ b/packages/a2a-server/src/config/config.ts @@ -21,6 +21,7 @@ import { DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_GEMINI_MODEL, type GeminiCLIExtension, + debugLogger, } from '@google/gemini-cli-core'; import { logger } from '../utils/logger.js'; @@ -125,7 +126,7 @@ export function mergeMcpServers( for (const extension of extensions) { Object.entries(extension.mcpServers || {}).forEach(([key, server]) => { if (mcpServers[key]) { - console.warn( + debugLogger.warn( `Skipping extension MCP config for server with key "${key}" as it already exists.`, ); return; diff --git a/packages/a2a-server/src/http/endpoints.test.ts b/packages/a2a-server/src/http/endpoints.test.ts index 076aaec1ea..ccde442f06 100644 --- a/packages/a2a-server/src/http/endpoints.test.ts +++ b/packages/a2a-server/src/http/endpoints.test.ts @@ -16,7 +16,7 @@ import type { AddressInfo } from 'node:net'; import { createApp, updateCoderAgentCardUrl } from './app.js'; import type { TaskMetadata } from '../types.js'; import { createMockConfig } from '../utils/testing_utils.js'; -import type { Config } from '@google/gemini-cli-core'; +import { debugLogger, type Config } from '@google/gemini-cli-core'; // Mock the logger to avoid polluting test output // Comment out to help debug @@ -115,7 +115,7 @@ describe('Agent Server Endpoints', () => { try { fs.rmSync(testWorkspace, { recursive: true, force: true }); } catch (e) { - console.warn(`Could not remove temp dir '${testWorkspace}':`, e); + debugLogger.warn(`Could not remove temp dir '${testWorkspace}':`, e); } } }); diff --git a/packages/cli/src/commands/extensions/enable.ts b/packages/cli/src/commands/extensions/enable.ts index 8337fbe468..f5245088d7 100644 --- a/packages/cli/src/commands/extensions/enable.ts +++ b/packages/cli/src/commands/extensions/enable.ts @@ -5,7 +5,11 @@ */ import { type CommandModule } from 'yargs'; -import { FatalConfigError, getErrorMessage } from '@google/gemini-cli-core'; +import { + debugLogger, + FatalConfigError, + getErrorMessage, +} from '@google/gemini-cli-core'; import { enableExtension } from '../../config/extension.js'; import { SettingScope } from '../../config/settings.js'; import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js'; @@ -28,11 +32,11 @@ export function handleEnable(args: EnableArgs) { enableExtension(args.name, SettingScope.User, extensionEnablementManager); } if (args.scope) { - console.log( + debugLogger.log( `Extension "${args.name}" successfully enabled for scope "${args.scope}".`, ); } else { - console.log( + debugLogger.log( `Extension "${args.name}" successfully enabled in all scopes.`, ); } diff --git a/packages/cli/src/commands/mcp/list.test.ts b/packages/cli/src/commands/mcp/list.test.ts index 6bb6f2eacd..4c20d82c3f 100644 --- a/packages/cli/src/commands/mcp/list.test.ts +++ b/packages/cli/src/commands/mcp/list.test.ts @@ -4,20 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - vi, - describe, - it, - expect, - beforeEach, - afterEach, - type Mock, - type MockInstance, -} from 'vitest'; +import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest'; import { listMcpServers } from './list.js'; import { loadSettings } from '../../config/settings.js'; import { ExtensionStorage, loadExtensions } from '../../config/extension.js'; -import { createTransport } from '@google/gemini-cli-core'; +import { createTransport, debugLogger } from '@google/gemini-cli-core'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; vi.mock('../../config/settings.js', () => ({ @@ -43,6 +34,12 @@ vi.mock('@google/gemini-cli-core', () => ({ })), GEMINI_DIR: '.gemini', getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)), + debugLogger: { + log: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }, })); vi.mock('@modelcontextprotocol/sdk/client/index.js'); @@ -64,15 +61,12 @@ interface MockTransport { } describe('mcp list command', () => { - let consoleSpy: MockInstance; let mockClient: MockClient; let mockTransport: MockTransport; beforeEach(() => { vi.resetAllMocks(); - consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - mockTransport = { close: vi.fn() }; mockClient = { connect: vi.fn(), @@ -86,16 +80,12 @@ describe('mcp list command', () => { mockedGetUserExtensionsDir.mockReturnValue('/mocked/extensions/dir'); }); - afterEach(() => { - consoleSpy.mockRestore(); - }); - it('should display message when no servers configured', async () => { mockedLoadSettings.mockReturnValue({ merged: { mcpServers: {} } }); await listMcpServers(); - expect(consoleSpy).toHaveBeenCalledWith('No MCP servers configured.'); + expect(debugLogger.log).toHaveBeenCalledWith('No MCP servers configured.'); }); it('should display different server types with connected status', async () => { @@ -114,18 +104,18 @@ describe('mcp list command', () => { await listMcpServers(); - expect(consoleSpy).toHaveBeenCalledWith('Configured MCP servers:\n'); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith('Configured MCP servers:\n'); + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'stdio-server: /path/to/server arg1 (stdio) - Connected', ), ); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'sse-server: https://example.com/sse (sse) - Connected', ), ); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'http-server: https://example.com/http (http) - Connected', ), @@ -145,7 +135,7 @@ describe('mcp list command', () => { await listMcpServers(); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'test-server: /test/server (stdio) - Disconnected', ), @@ -171,12 +161,12 @@ describe('mcp list command', () => { await listMcpServers(); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'config-server: /config/server (stdio) - Connected', ), ); - expect(consoleSpy).toHaveBeenCalledWith( + expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( 'extension-server (from test-extension): /ext/server (stdio) - Connected', ), diff --git a/packages/cli/src/commands/mcp/list.ts b/packages/cli/src/commands/mcp/list.ts index fbfae2d913..733f0071f8 100644 --- a/packages/cli/src/commands/mcp/list.ts +++ b/packages/cli/src/commands/mcp/list.ts @@ -8,7 +8,11 @@ import type { CommandModule } from 'yargs'; import { loadSettings } from '../../config/settings.js'; import type { MCPServerConfig } from '@google/gemini-cli-core'; -import { MCPServerStatus, createTransport } from '@google/gemini-cli-core'; +import { + MCPServerStatus, + createTransport, + debugLogger, +} from '@google/gemini-cli-core'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { loadExtensions } from '../../config/extension.js'; import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js'; @@ -84,11 +88,11 @@ export async function listMcpServers(): Promise { const serverNames = Object.keys(mcpServers); if (serverNames.length === 0) { - console.log('No MCP servers configured.'); + debugLogger.log('No MCP servers configured.'); return; } - console.log('Configured MCP servers:\n'); + debugLogger.log('Configured MCP servers:\n'); for (const serverName of serverNames) { const server = mcpServers[serverName]; @@ -125,7 +129,7 @@ export async function listMcpServers(): Promise { serverInfo += `${server.command} ${server.args?.join(' ') || ''} (stdio)`; } - console.log(`${statusIndicator} ${serverInfo} - ${statusText}`); + debugLogger.log(`${statusIndicator} ${serverInfo} - ${statusText}`); } } diff --git a/packages/cli/src/commands/mcp/remove.ts b/packages/cli/src/commands/mcp/remove.ts index bcaa5ad4bb..dda8e2d5a1 100644 --- a/packages/cli/src/commands/mcp/remove.ts +++ b/packages/cli/src/commands/mcp/remove.ts @@ -7,6 +7,7 @@ // File for 'gemini mcp remove' command import type { CommandModule } from 'yargs'; import { loadSettings, SettingScope } from '../../config/settings.js'; +import { debugLogger } from '@google/gemini-cli-core'; async function removeMcpServer( name: string, @@ -23,7 +24,7 @@ async function removeMcpServer( const mcpServers = existingSettings.mcpServers || {}; if (!mcpServers[name]) { - console.log(`Server "${name}" not found in ${scope} settings.`); + debugLogger.log(`Server "${name}" not found in ${scope} settings.`); return; } @@ -31,7 +32,7 @@ async function removeMcpServer( settings.setValue(settingsScope, 'mcpServers', mcpServers); - console.log(`Server "${name}" removed from ${scope} settings.`); + debugLogger.log(`Server "${name}" removed from ${scope} settings.`); } export const removeCommand: CommandModule = { diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 5f0f07cfe4..672f42bd5b 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -1073,8 +1073,6 @@ describe('Settings Loading and Merging', () => { ); const settings = loadSettings(MOCK_WORKSPACE_DIR); - const e = settings.user.settings.model?.chatCompression; - console.log(e); expect(settings.user.settings.model?.chatCompression).toEqual({ contextPercentageThreshold: 0.5, diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index 5311a370bc..0835cdd178 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -10,6 +10,7 @@ import { homedir, platform } from 'node:os'; import * as dotenv from 'dotenv'; import process from 'node:process'; import { + debugLogger, FatalConfigError, GEMINI_DIR, getErrorMessage, @@ -753,7 +754,7 @@ export function migrateDeprecatedSettings( const processScope = (scope: SettingScope) => { const settings = loadedSettings.forScope(scope).settings; if (settings.extensions?.disabled) { - console.log( + debugLogger.log( `Migrating deprecated extensions.disabled settings from ${scope} settings...`, ); const extensionEnablementManager = new ExtensionEnablementManager(); diff --git a/packages/cli/src/services/CommandService.ts b/packages/cli/src/services/CommandService.ts index 5f1e09d50d..0e29a81d00 100644 --- a/packages/cli/src/services/CommandService.ts +++ b/packages/cli/src/services/CommandService.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; import type { SlashCommand } from '../ui/commands/types.js'; import type { ICommandLoader } from './types.js'; @@ -57,7 +58,7 @@ export class CommandService { if (result.status === 'fulfilled') { allCommands.push(...result.value); } else { - console.debug('A command loader failed:', result.reason); + debugLogger.debug('A command loader failed:', result.reason); } } diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 8216e2e925..c8a878129d 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -42,6 +42,7 @@ import { AuthType, clearCachedCredentialFile, ShellExecutionService, + debugLogger, } from '@google/gemini-cli-core'; import { validateAuthMethod } from '../config/auth.js'; import { loadHierarchicalGeminiMemory } from '../config/config.js'; @@ -389,7 +390,7 @@ export const AppContainer = (props: AppContainerProps) => { config.isBrowserLaunchSuppressed() ) { await runExitCleanup(); - console.log(` + debugLogger.log(` ---------------------------------------------------------------- Logging in with Google... Please restart Gemini CLI to continue. ---------------------------------------------------------------- @@ -558,7 +559,7 @@ Logging in with Google... Please restart Gemini CLI to continue. Date.now(), ); if (config.getDebugMode()) { - console.log( + debugLogger.log( `[DEBUG] Refreshed memory content in config: ${memoryContent.substring( 0, 200, @@ -933,7 +934,7 @@ Logging in with Google... Please restart Gemini CLI to continue. (key: Key) => { // Debug log keystrokes if enabled if (settings.merged.general?.debugKeystrokeLogging) { - console.log('[DEBUG] Keystroke:', JSON.stringify(key)); + debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key)); } if (keyMatchers[Command.QUIT](key)) { diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx index d705de508b..4c1c5b330b 100644 --- a/packages/cli/src/ui/auth/AuthDialog.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.tsx @@ -14,6 +14,7 @@ import { SettingScope } from '../../config/settings.js'; import { AuthType, clearCachedCredentialFile, + debugLogger, type Config, } from '@google/gemini-cli-core'; import { useKeypress } from '../hooks/useKeypress.js'; @@ -108,7 +109,7 @@ export function AuthDialog({ config.isBrowserLaunchSuppressed() ) { runExitCleanup(); - console.log( + debugLogger.log( ` ---------------------------------------------------------------- Logging in with Google... Please restart Gemini CLI to continue. diff --git a/packages/cli/src/ui/auth/useAuth.ts b/packages/cli/src/ui/auth/useAuth.ts index 014b74a26c..8ea0520610 100644 --- a/packages/cli/src/ui/auth/useAuth.ts +++ b/packages/cli/src/ui/auth/useAuth.ts @@ -6,7 +6,7 @@ import { useState, useEffect, useCallback } from 'react'; import type { LoadedSettings } from '../../config/settings.js'; -import { AuthType, type Config } from '@google/gemini-cli-core'; +import { AuthType, debugLogger, type Config } from '@google/gemini-cli-core'; import { getErrorMessage } from '@google/gemini-cli-core'; import { AuthState } from '../types.js'; import { validateAuthMethod } from '../../config/auth.js'; @@ -80,7 +80,7 @@ export const useAuthCommand = (settings: LoadedSettings, config: Config) => { try { await config.refreshAuth(authType); - console.log(`Authenticated via "${authType}".`); + debugLogger.log(`Authenticated via "${authType}".`); setAuthError(null); setAuthState(AuthState.Authenticated); } catch (e) { diff --git a/packages/cli/src/ui/commands/copyCommand.ts b/packages/cli/src/ui/commands/copyCommand.ts index 99115491c5..b9ed967ad9 100644 --- a/packages/cli/src/ui/commands/copyCommand.ts +++ b/packages/cli/src/ui/commands/copyCommand.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; import { copyToClipboard } from '../utils/commandUtils.js'; import type { SlashCommand, SlashCommandActionReturn } from './types.js'; import { CommandKind } from './types.js'; @@ -45,7 +46,7 @@ export const copyCommand: SlashCommand = { }; } catch (error) { const message = error instanceof Error ? error.message : String(error); - console.debug(message); + debugLogger.debug(message); return { type: 'message', diff --git a/packages/cli/src/ui/commands/setupGithubCommand.ts b/packages/cli/src/ui/commands/setupGithubCommand.ts index 46b46cbaa8..d790a01424 100644 --- a/packages/cli/src/ui/commands/setupGithubCommand.ts +++ b/packages/cli/src/ui/commands/setupGithubCommand.ts @@ -20,6 +20,7 @@ import { import type { SlashCommand, SlashCommandActionReturn } from './types.js'; import { CommandKind } from './types.js'; import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js'; +import { debugLogger } from '@google/gemini-cli-core'; export const GITHUB_WORKFLOW_PATHS = [ 'gemini-dispatch/gemini-dispatch.yml', @@ -84,7 +85,7 @@ export async function updateGitignore(gitRepoRoot: string): Promise { } } } catch (error) { - console.debug('Failed to update .gitignore:', error); + debugLogger.debug('Failed to update .gitignore:', error); // Continue without failing the whole command } } @@ -109,7 +110,7 @@ export const setupGithubCommand: SlashCommand = { try { gitRepoRoot = getGitRepoRoot(); } catch (_error) { - console.debug(`Failed to get git repo root:`, _error); + debugLogger.debug(`Failed to get git repo root:`, _error); throw new Error( 'Unable to determine the GitHub repository. /setup-github must be run from a git repository.', ); @@ -125,7 +126,7 @@ export const setupGithubCommand: SlashCommand = { try { await fs.promises.mkdir(githubWorkflowsDir, { recursive: true }); } catch (_error) { - console.debug( + debugLogger.debug( `Failed to create ${githubWorkflowsDir} directory:`, _error, ); diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index 6e186e2686..b0e25b40be 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -37,6 +37,7 @@ import { type SettingsValue, TOGGLE_TYPES, } from '../../config/settingsSchema.js'; +import { debugLogger } from '@google/gemini-cli-core'; interface SettingsDialogProps { settings: LoadedSettings; @@ -162,7 +163,7 @@ export function SettingsDialog({ newValue, currentScopeSettings, ); - console.log( + debugLogger.log( `[DEBUG SettingsDialog] Saving ${key} immediately with value:`, newValue, ); @@ -207,7 +208,7 @@ export function SettingsDialog({ setModifiedSettings((prev) => { const updated = new Set(prev).add(key); const needsRestart = hasRestartRequiredSettings(updated); - console.log( + debugLogger.log( `[DEBUG SettingsDialog] Modified settings:`, Array.from(updated), 'Needs restart:', diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts index 6583be55c2..01d5907324 100644 --- a/packages/cli/src/ui/hooks/atCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts @@ -9,6 +9,7 @@ import * as path from 'node:path'; import type { PartListUnion, PartUnion } from '@google/genai'; import type { AnyToolInvocation, Config } from '@google/gemini-cli-core'; import { + debugLogger, getErrorMessage, isNodeError, unescapePath, @@ -372,7 +373,7 @@ export async function handleAtCommand({ } const message = `Ignored ${totalIgnored} files:\n${messages.join('\n')}`; - console.log(message); + debugLogger.log(message); onDebugMessage(message); } diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts index c17501889a..abd66b829c 100644 --- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts @@ -222,8 +222,6 @@ export const useShellCommandProcessor = ( shellExecutionConfig, ); - console.log(terminalHeight, terminalWidth); - executionPid = pid; if (pid) { setActiveShellPtyId(pid); diff --git a/packages/cli/src/ui/hooks/useInputHistoryStore.ts b/packages/cli/src/ui/hooks/useInputHistoryStore.ts index 86e7cd3960..ff53f2ba95 100644 --- a/packages/cli/src/ui/hooks/useInputHistoryStore.ts +++ b/packages/cli/src/ui/hooks/useInputHistoryStore.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; import { useState, useCallback } from 'react'; interface Logger { @@ -69,7 +70,10 @@ export function useInputHistoryStore(): UseInputHistoryStoreReturn { setIsInitialized(true); } catch (error) { // Start with empty history even if logger initialization fails - console.warn('Failed to initialize input history from logger:', error); + debugLogger.warn( + 'Failed to initialize input history from logger:', + error, + ); setPastSessionMessages([]); recalculateHistory([], []); setIsInitialized(true); diff --git a/packages/cli/src/ui/hooks/useReactToolScheduler.ts b/packages/cli/src/ui/hooks/useReactToolScheduler.ts index 370208ed53..d78b66e142 100644 --- a/packages/cli/src/ui/hooks/useReactToolScheduler.ts +++ b/packages/cli/src/ui/hooks/useReactToolScheduler.ts @@ -20,7 +20,7 @@ import type { Status as CoreStatus, EditorType, } from '@google/gemini-cli-core'; -import { CoreToolScheduler } from '@google/gemini-cli-core'; +import { CoreToolScheduler, debugLogger } from '@google/gemini-cli-core'; import { useCallback, useState, useMemo } from 'react'; import type { HistoryItemToolGroup, @@ -198,7 +198,7 @@ function mapCoreStatusToDisplayStatus(coreStatus: CoreStatus): ToolCallStatus { return ToolCallStatus.Pending; default: { const exhaustiveCheck: never = coreStatus; - console.warn(`Unknown core status encountered: ${exhaustiveCheck}`); + debugLogger.warn(`Unknown core status encountered: ${exhaustiveCheck}`); return ToolCallStatus.Error; } } diff --git a/packages/cli/src/ui/hooks/useShowMemoryCommand.ts b/packages/cli/src/ui/hooks/useShowMemoryCommand.ts index bcbeb95903..40b2c6ba76 100644 --- a/packages/cli/src/ui/hooks/useShowMemoryCommand.ts +++ b/packages/cli/src/ui/hooks/useShowMemoryCommand.ts @@ -6,7 +6,7 @@ import type { Message } from '../types.js'; import { MessageType } from '../types.js'; -import type { Config } from '@google/gemini-cli-core'; +import { debugLogger, type Config } from '@google/gemini-cli-core'; import type { LoadedSettings } from '../../config/settings.js'; export function createShowMemoryAction( @@ -27,7 +27,7 @@ export function createShowMemoryAction( const debugMode = config.getDebugMode(); if (debugMode) { - console.log('[DEBUG] Show Memory command invoked.'); + debugLogger.log('[DEBUG] Show Memory command invoked.'); } const currentMemory = config.getUserMemory(); @@ -38,10 +38,10 @@ export function createShowMemoryAction( : [contextFileName]; if (debugMode) { - console.log( + debugLogger.log( `[DEBUG] Showing memory. Content from config.getUserMemory() (first 200 chars): ${currentMemory.substring(0, 200)}...`, ); - console.log(`[DEBUG] Number of context files loaded: ${fileCount}`); + debugLogger.log(`[DEBUG] Number of context files loaded: ${fileCount}`); } if (fileCount > 0) { diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.ts b/packages/cli/src/ui/hooks/useSlashCompletion.ts index ba639d01bd..b121069792 100644 --- a/packages/cli/src/ui/hooks/useSlashCompletion.ts +++ b/packages/cli/src/ui/hooks/useSlashCompletion.ts @@ -12,6 +12,7 @@ import { type CommandContext, type SlashCommand, } from '../commands/types.js'; +import { debugLogger } from '@google/gemini-cli-core'; // Type alias for improved type safety based on actual fzf result structure type FzfCommandResult = { @@ -189,7 +190,7 @@ function useCommandSuggestions( // Safety check: ensure leafCommand and completion exist if (!leafCommand?.completion) { - console.warn( + debugLogger.warn( 'Attempted argument completion without completion function', ); return; diff --git a/packages/cli/src/ui/hooks/vim.ts b/packages/cli/src/ui/hooks/vim.ts index 97b73121dc..d8d5ab38d2 100644 --- a/packages/cli/src/ui/hooks/vim.ts +++ b/packages/cli/src/ui/hooks/vim.ts @@ -8,6 +8,7 @@ import { useCallback, useReducer, useEffect } from 'react'; import type { Key } from './useKeypress.js'; import type { TextBuffer } from '../components/shared/text-buffer.js'; import { useVimMode } from '../contexts/VimModeContext.js'; +import { debugLogger } from '@google/gemini-cli-core'; export type VimMode = 'NORMAL' | 'INSERT'; @@ -394,7 +395,7 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) { normalizedKey = normalizeKey(key); } catch (error) { // Handle malformed key inputs gracefully - console.warn('Malformed key input in vim mode:', key, error); + debugLogger.warn('Malformed key input in vim mode:', key, error); return false; } diff --git a/packages/cli/src/ui/themes/color-utils.ts b/packages/cli/src/ui/themes/color-utils.ts index a861ee3215..703752fd9e 100644 --- a/packages/cli/src/ui/themes/color-utils.ts +++ b/packages/cli/src/ui/themes/color-utils.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; + // Mapping from common CSS color names (lowercase) to hex codes (lowercase) // Excludes names directly supported by Ink export const CSS_NAME_TO_HEX_MAP: Readonly> = { @@ -224,7 +226,7 @@ export function resolveColor(colorValue: string): string | undefined { } // 4. Could not resolve - console.warn( + debugLogger.warn( `[ColorUtils] Could not resolve color "${colorValue}" to an Ink-compatible format.`, ); return undefined; diff --git a/packages/cli/src/ui/themes/theme-manager.ts b/packages/cli/src/ui/themes/theme-manager.ts index e6ad4745b8..081224d9eb 100644 --- a/packages/cli/src/ui/themes/theme-manager.ts +++ b/packages/cli/src/ui/themes/theme-manager.ts @@ -25,6 +25,7 @@ import { ANSI } from './ansi.js'; import { ANSILight } from './ansi-light.js'; import { NoColorTheme } from './no-color.js'; import process from 'node:process'; +import { debugLogger } from '@google/gemini-cli-core'; export interface ThemeDisplay { name: string; @@ -75,7 +76,7 @@ class ThemeManager { const validation = validateCustomTheme(customThemeConfig); if (validation.isValid) { if (validation.warning) { - console.warn(`Theme "${name}": ${validation.warning}`); + debugLogger.warn(`Theme "${name}": ${validation.warning}`); } const themeWithDefaults: CustomTheme = { ...DEFAULT_THEME.colors, @@ -88,10 +89,10 @@ class ThemeManager { const theme = createCustomTheme(themeWithDefaults); this.customThemes.set(name, theme); } catch (error) { - console.warn(`Failed to load custom theme "${name}":`, error); + debugLogger.warn(`Failed to load custom theme "${name}":`, error); } } else { - console.warn(`Invalid custom theme "${name}": ${validation.error}`); + debugLogger.warn(`Invalid custom theme "${name}": ${validation.error}`); } } // If the current active theme is a custom theme, keep it if still valid @@ -246,7 +247,7 @@ class ThemeManager { // 2. Perform security check. const homeDir = path.resolve(os.homedir()); if (!canonicalPath.startsWith(homeDir)) { - console.warn( + debugLogger.warn( `Theme file at "${themePath}" is outside your home directory. ` + `Only load themes from trusted sources.`, ); @@ -259,14 +260,14 @@ class ThemeManager { const validation = validateCustomTheme(customThemeConfig); if (!validation.isValid) { - console.warn( + debugLogger.warn( `Invalid custom theme from file "${themePath}": ${validation.error}`, ); return undefined; } if (validation.warning) { - console.warn(`Theme from "${themePath}": ${validation.warning}`); + debugLogger.warn(`Theme from "${themePath}": ${validation.warning}`); } // 4. Create and cache the theme. @@ -286,7 +287,10 @@ class ThemeManager { if ( !(error instanceof Error && 'code' in error && error.code === 'ENOENT') ) { - console.warn(`Could not load theme from file "${themePath}":`, error); + debugLogger.warn( + `Could not load theme from file "${themePath}":`, + error, + ); } return undefined; } diff --git a/packages/cli/src/ui/utils/commandUtils.ts b/packages/cli/src/ui/utils/commandUtils.ts index a3333c448b..3b3a0437cd 100644 --- a/packages/cli/src/ui/utils/commandUtils.ts +++ b/packages/cli/src/ui/utils/commandUtils.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; import type { SpawnOptions } from 'node:child_process'; import { spawn } from 'node:child_process'; @@ -165,7 +166,7 @@ export const getUrlOpenCommand = (): string => { default: // Default to xdg-open, which appears to be supported for the less popular operating systems. openCmd = 'xdg-open'; - console.warn( + debugLogger.warn( `Unknown platform: ${process.platform}. Attempting to open URLs with: ${openCmd}.`, ); break; diff --git a/packages/cli/src/ui/utils/terminalSetup.ts b/packages/cli/src/ui/utils/terminalSetup.ts index a7b00d86fe..864750c9fd 100644 --- a/packages/cli/src/ui/utils/terminalSetup.ts +++ b/packages/cli/src/ui/utils/terminalSetup.ts @@ -30,6 +30,7 @@ import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import { isKittyProtocolEnabled } from './kittyProtocolDetector.js'; import { VSCODE_SHIFT_ENTER_SEQUENCE } from './platformConstants.js'; +import { debugLogger } from '@google/gemini-cli-core'; const execAsync = promisify(exec); @@ -88,7 +89,7 @@ async function detectTerminal(): Promise { return 'vscode'; } catch (error) { // Continue detection even if process check fails - console.debug('Parent process detection failed:', error); + debugLogger.debug('Parent process detection failed:', error); } } @@ -103,7 +104,7 @@ async function backupFile(filePath: string): Promise { await fs.copyFile(filePath, backupPath); } catch (error) { // Log backup errors but continue with operation - console.warn(`Failed to create backup of ${filePath}:`, error); + debugLogger.warn(`Failed to create backup of ${filePath}:`, error); } } diff --git a/packages/cli/src/ui/utils/updateCheck.ts b/packages/cli/src/ui/utils/updateCheck.ts index c45bf98771..f924964370 100644 --- a/packages/cli/src/ui/utils/updateCheck.ts +++ b/packages/cli/src/ui/utils/updateCheck.ts @@ -9,6 +9,7 @@ import updateNotifier from 'update-notifier'; import semver from 'semver'; import { getPackageJson } from '../../utils/package.js'; import type { LoadedSettings } from '../../config/settings.js'; +import { debugLogger } from '@google/gemini-cli-core'; export const FETCH_TIMEOUT_MS = 2000; @@ -101,7 +102,7 @@ export async function checkForUpdates( return null; } catch (e) { - console.warn('Failed to check for updates: ' + e); + debugLogger.warn('Failed to check for updates: ' + e); return null; } } diff --git a/packages/cli/src/utils/gitUtils.ts b/packages/cli/src/utils/gitUtils.ts index 11cf729b68..c604d7cbc7 100644 --- a/packages/cli/src/utils/gitUtils.ts +++ b/packages/cli/src/utils/gitUtils.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; import { execSync } from 'node:child_process'; import { ProxyAgent } from 'undici'; @@ -24,7 +25,7 @@ export const isGitHubRepository = (): boolean => { return pattern.test(remotes); } catch (_error) { // If any filesystem error occurs, assume not a git repo - console.debug(`Failed to get git remote:`, _error); + debugLogger.debug(`Failed to get git remote:`, _error); return false; } }; @@ -83,7 +84,10 @@ export const getLatestGitHubRelease = async ( } return releaseTag; } catch (_error) { - console.debug(`Failed to determine latest run-gemini-cli release:`, _error); + debugLogger.debug( + `Failed to determine latest run-gemini-cli release:`, + _error, + ); throw new Error( `Unable to determine the latest run-gemini-cli release on GitHub.`, ); diff --git a/packages/cli/src/utils/installationInfo.test.ts b/packages/cli/src/utils/installationInfo.test.ts index 9e72a70a9f..fefe2fea9f 100644 --- a/packages/cli/src/utils/installationInfo.test.ts +++ b/packages/cli/src/utils/installationInfo.test.ts @@ -9,10 +9,16 @@ import { getInstallationInfo, PackageManager } from './installationInfo.js'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as childProcess from 'node:child_process'; -import { isGitRepository } from '@google/gemini-cli-core'; +import { isGitRepository, debugLogger } from '@google/gemini-cli-core'; vi.mock('@google/gemini-cli-core', () => ({ isGitRepository: vi.fn(), + debugLogger: { + log: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }, })); vi.mock('fs', async (importOriginal) => { @@ -59,7 +65,6 @@ describe('getInstallationInfo', () => { }); it('should return UNKNOWN and log error if realpathSync fails', () => { - const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); process.argv[1] = '/path/to/cli'; const error = new Error('realpath failed'); mockedRealPathSync.mockImplementation(() => { @@ -69,8 +74,7 @@ describe('getInstallationInfo', () => { const info = getInstallationInfo(projectRoot, false); expect(info.packageManager).toBe(PackageManager.UNKNOWN); - expect(consoleSpy).toHaveBeenCalledWith(error); - consoleSpy.mockRestore(); + expect(debugLogger.log).toHaveBeenCalledWith(error); }); it('should detect running from a local git clone', () => { diff --git a/packages/cli/src/utils/installationInfo.ts b/packages/cli/src/utils/installationInfo.ts index e6befb5aeb..4aa1fac9c9 100644 --- a/packages/cli/src/utils/installationInfo.ts +++ b/packages/cli/src/utils/installationInfo.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { isGitRepository } from '@google/gemini-cli-core'; +import { debugLogger, isGitRepository } from '@google/gemini-cli-core'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as childProcess from 'node:child_process'; @@ -174,7 +174,7 @@ export function getInstallationInfo( : 'Installed with npm. Attempting to automatically update now...', }; } catch (error) { - console.log(error); + debugLogger.log(error); return { packageManager: PackageManager.UNKNOWN, isGlobal: false }; } } diff --git a/packages/cli/src/utils/readStdin.ts b/packages/cli/src/utils/readStdin.ts index 3ccdaee750..cc0f5da9ac 100644 --- a/packages/cli/src/utils/readStdin.ts +++ b/packages/cli/src/utils/readStdin.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { debugLogger } from '@google/gemini-cli-core'; + export async function readStdin(): Promise { const MAX_STDIN_SIZE = 8 * 1024 * 1024; // 8MB return new Promise((resolve, reject) => { @@ -30,7 +32,7 @@ export async function readStdin(): Promise { if (totalSize + chunk.length > MAX_STDIN_SIZE) { const remainingSize = MAX_STDIN_SIZE - totalSize; data += chunk.slice(0, remainingSize); - console.warn( + debugLogger.warn( `Warning: stdin input truncated to ${MAX_STDIN_SIZE} bytes.`, ); process.stdin.destroy(); // Stop reading further diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index df70ca1218..a08003732a 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -14,7 +14,11 @@ import { quote, parse } from 'shell-quote'; import { USER_SETTINGS_DIR } from '../config/settings.js'; import { promisify } from 'node:util'; import type { Config, SandboxConfig } from '@google/gemini-cli-core'; -import { FatalSandboxError, GEMINI_DIR } from '@google/gemini-cli-core'; +import { + debugLogger, + FatalSandboxError, + GEMINI_DIR, +} from '@google/gemini-cli-core'; import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js'; import { randomBytes } from 'node:crypto'; @@ -92,7 +96,7 @@ async function shouldUseCurrentUserInSandbox(): Promise { } catch (_err) { // Silently ignore if /etc/os-release is not found or unreadable. // The default (false) will be applied in this case. - console.warn( + debugLogger.warn( 'Warning: Could not read /etc/os-release to auto-detect Debian/Ubuntu for UID/GID default.', ); } @@ -301,7 +305,7 @@ export async function start_sandbox( }); // install handlers to stop proxy on exit/signal const stopProxy = () => { - console.log('stopping proxy ...'); + debugLogger.log('stopping proxy ...'); if (proxyProcess?.pid) { process.kill(-proxyProcess.pid, 'SIGTERM'); } @@ -325,7 +329,7 @@ export async function start_sandbox( `Proxy command '${proxyCommand}' exited with code ${code}, signal ${signal}`, ); }); - console.log('waiting for proxy to start ...'); + debugLogger.log('waiting for proxy to start ...'); await execAsync( `until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`, ); @@ -559,7 +563,7 @@ export async function start_sandbox( containerName = `gemini-cli-integration-test-${randomBytes(4).toString( 'hex', )}`; - console.log(`ContainerName: ${containerName}`); + debugLogger.log(`ContainerName: ${containerName}`); } else { let index = 0; const containerNameCheck = execSync( @@ -571,7 +575,7 @@ export async function start_sandbox( index++; } containerName = `${imageName}-${index}`; - console.log(`ContainerName (regular): ${containerName}`); + debugLogger.log(`ContainerName (regular): ${containerName}`); } args.push('--name', containerName, '--hostname', containerName); @@ -773,7 +777,7 @@ export async function start_sandbox( }); // install handlers to stop proxy on exit/signal const stopProxy = () => { - console.log('stopping proxy container ...'); + debugLogger.log('stopping proxy container ...'); execSync(`${config.command} rm -f ${SANDBOX_PROXY_NAME}`); }; process.on('exit', stopProxy); @@ -795,7 +799,7 @@ export async function start_sandbox( `Proxy container command '${proxyContainerCommand}' exited with code ${code}, signal ${signal}`, ); }); - console.log('waiting for proxy to start ...'); + debugLogger.log('waiting for proxy to start ...'); await execAsync( `until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`, ); @@ -821,7 +825,7 @@ export async function start_sandbox( sandboxProcess?.on('close', (code, signal) => { process.stdin.resume(); if (code !== 0 && code !== null) { - console.log( + debugLogger.log( `Sandbox process exited with code: ${code}, signal: ${signal}`, ); } @@ -847,7 +851,7 @@ async function imageExists(sandbox: string, image: string): Promise { } checkProcess.on('error', (err) => { - console.warn( + debugLogger.warn( `Failed to start '${sandbox}' command for image check: ${err.message}`, ); resolve(false); @@ -882,7 +886,7 @@ async function pullImage(sandbox: string, image: string): Promise { }; const onError = (err: Error) => { - console.warn( + debugLogger.warn( `Failed to start '${sandbox} pull ${image}' command: ${err.message}`, ); cleanup(); @@ -895,7 +899,7 @@ async function pullImage(sandbox: string, image: string): Promise { cleanup(); resolve(true); } else { - console.warn( + debugLogger.warn( `Failed to pull image ${image}. '${sandbox} pull ${image}' exited with code ${code}.`, ); if (stderrData.trim()) { @@ -953,7 +957,7 @@ async function ensureSandboxImageIsPresent( console.info(`Sandbox image ${image} is now available after pulling.`); return true; } else { - console.warn( + debugLogger.warn( `Sandbox image ${image} still not found after a pull attempt. This might indicate an issue with the image name or registry, or the pull command reported success but failed to make the image available.`, ); return false; diff --git a/packages/cli/src/utils/sessionCleanup.ts b/packages/cli/src/utils/sessionCleanup.ts index 456d499574..bb5e64e5f3 100644 --- a/packages/cli/src/utils/sessionCleanup.ts +++ b/packages/cli/src/utils/sessionCleanup.ts @@ -6,7 +6,7 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { type Config } from '@google/gemini-cli-core'; +import { debugLogger, type Config } from '@google/gemini-cli-core'; import type { Settings, SessionRetentionSettings } from '../config/settings.js'; import { getAllSessionFiles, type SessionFileEntry } from './sessionUtils.js'; @@ -88,11 +88,11 @@ export async function cleanupExpiredSessions( if (config.getDebugMode()) { if (sessionToDelete.sessionInfo === null) { - console.debug( + debugLogger.debug( `Deleted corrupted session file: ${sessionToDelete.fileName}`, ); } else { - console.debug( + debugLogger.debug( `Deleted expired session: ${sessionToDelete.sessionInfo.id} (${sessionToDelete.sessionInfo.lastUpdated})`, ); } @@ -125,7 +125,7 @@ export async function cleanupExpiredSessions( result.skipped = result.scanned - result.deleted - result.failed; if (config.getDebugMode() && result.deleted > 0) { - console.debug( + debugLogger.debug( `Session cleanup: deleted ${result.deleted}, skipped ${result.skipped}, failed ${result.failed}`, ); } diff --git a/packages/cli/src/validateNonInterActiveAuth.test.ts b/packages/cli/src/validateNonInterActiveAuth.test.ts index d2a7e8e767..475e079bdf 100644 --- a/packages/cli/src/validateNonInterActiveAuth.test.ts +++ b/packages/cli/src/validateNonInterActiveAuth.test.ts @@ -53,10 +53,7 @@ describe('validateNonInterActiveAuth', () => { throw new Error(`process.exit(${code}) called`); }); vi.spyOn(auth, 'validateAuthMethod').mockReturnValue(null); - refreshAuthMock = vi.fn().mockImplementation(async () => { - console.log('DEBUG: refreshAuthMock called'); - return 'refreshed'; - }); + refreshAuthMock = vi.fn().mockImplementation(async () => 'refreshed'); mockSettings = { system: { path: '', settings: {} }, systemDefaults: { path: '', settings: {} }, diff --git a/packages/cli/src/zed-integration/zedIntegration.ts b/packages/cli/src/zed-integration/zedIntegration.ts index 33d1d596ba..d56b805044 100644 --- a/packages/cli/src/zed-integration/zedIntegration.ts +++ b/packages/cli/src/zed-integration/zedIntegration.ts @@ -30,6 +30,7 @@ import { DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_FLASH_MODEL, + debugLogger, } from '@google/gemini-cli-core'; import * as acp from './acp.js'; import { AcpFileSystemService } from './fileSystemService.js'; @@ -592,7 +593,7 @@ class Session { const reason = respectGitIgnore ? 'git-ignored and will be skipped' : 'ignored by custom patterns'; - console.warn(`Path ${pathName} is ${reason}.`); + debugLogger.warn(`Path ${pathName} is ${reason}.`); continue; } let currentPathSpec = pathName; @@ -739,7 +740,7 @@ class Session { if (pathSpecsToRead.length === 0 && embeddedContext.length === 0) { // Fallback for lone "@" or completely invalid @-commands resulting in empty initialQueryText - console.warn('No valid file paths found in @ commands to read.'); + debugLogger.warn('No valid file paths found in @ commands to read.'); return [{ text: initialQueryText }]; } @@ -802,7 +803,7 @@ class Session { } } } else { - console.warn( + debugLogger.warn( 'read_many_files tool returned no content or empty content.', ); } @@ -855,7 +856,7 @@ class Session { debug(msg: string) { if (this.config.getDebugMode()) { - console.warn(msg); + debugLogger.warn(msg); } } } diff --git a/packages/core/src/agents/executor.ts b/packages/core/src/agents/executor.ts index 6dab56973d..554a7d2f27 100644 --- a/packages/core/src/agents/executor.ts +++ b/packages/core/src/agents/executor.ts @@ -43,6 +43,7 @@ import { templateString } from './utils.js'; import { parseThought } from '../utils/thoughtUtils.js'; import { type z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; +import { debugLogger } from '../utils/debugLogger.js'; /** A callback function to report on agent activity. */ export type ActivityCallback = (activity: SubagentActivityEvent) => void; @@ -506,7 +507,7 @@ export class AgentExecutor { if (!allowedToolNames.has(functionCall.name as string)) { const error = `Unauthorized tool call: '${functionCall.name}' is not available to this agent.`; - console.warn(`[AgentExecutor] Blocked call: ${error}`); + debugLogger.warn(`[AgentExecutor] Blocked call: ${error}`); syncResponseParts.push({ functionResponse: { diff --git a/packages/core/src/agents/registry.ts b/packages/core/src/agents/registry.ts index c675179a5b..ad68a596b1 100644 --- a/packages/core/src/agents/registry.ts +++ b/packages/core/src/agents/registry.ts @@ -8,6 +8,7 @@ import type { Config } from '../config/config.js'; import type { AgentDefinition } from './types.js'; import { CodebaseInvestigatorAgent } from './codebase-investigator.js'; import { type z } from 'zod'; +import { debugLogger } from '../utils/debugLogger.js'; /** * Manages the discovery, loading, validation, and registration of @@ -26,7 +27,7 @@ export class AgentRegistry { this.loadBuiltInAgents(); if (this.config.getDebugMode()) { - console.log( + debugLogger.log( `[AgentRegistry] Initialized with ${this.agents.size} agents.`, ); } @@ -72,14 +73,14 @@ export class AgentRegistry { ): void { // Basic validation if (!definition.name || !definition.description) { - console.warn( + debugLogger.warn( `[AgentRegistry] Skipping invalid agent definition. Missing name or description.`, ); return; } if (this.agents.has(definition.name) && this.config.getDebugMode()) { - console.log(`[AgentRegistry] Overriding agent '${definition.name}'`); + debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`); } this.agents.set(definition.name, definition); diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index b88877058b..fac45172e9 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -25,6 +25,7 @@ import readline from 'node:readline'; import { Storage } from '../config/storage.js'; import { OAuthCredentialStorage } from './oauth-credential-storage.js'; import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js'; +import { debugLogger } from '../utils/debugLogger.js'; const userAccountManager = new UserAccountManager(); @@ -110,10 +111,10 @@ async function initOauthClient( await fetchAndCacheUserInfo(client); } catch (error) { // Non-fatal, continue with existing auth. - console.warn('Failed to fetch user info:', getErrorMessage(error)); + debugLogger.warn('Failed to fetch user info:', getErrorMessage(error)); } } - console.log('Loaded cached credentials.'); + debugLogger.log('Loaded cached credentials.'); return client; } @@ -122,13 +123,13 @@ async function initOauthClient( // the identity of the user logged into Cloud Shell. if (authType === AuthType.CLOUD_SHELL) { try { - console.log("Attempting to authenticate via Cloud Shell VM's ADC."); + debugLogger.log("Attempting to authenticate via Cloud Shell VM's ADC."); const computeClient = new Compute({ // We can leave this empty, since the metadata server will provide // the service account email. }); await computeClient.getAccessToken(); - console.log('Authentication successful.'); + debugLogger.log('Authentication successful.'); // Do not cache creds in this case; note that Compute client will handle its own refresh return computeClient; @@ -161,7 +162,7 @@ async function initOauthClient( } else { const webLogin = await authWithWeb(client); - console.log( + debugLogger.log( `\n\nCode Assist login required.\n` + `Attempting to open authentication page in your browser.\n` + `Otherwise navigate to:\n\n${webLogin.authUrl}\n\n`, @@ -193,7 +194,7 @@ async function initOauthClient( `Failed to open browser: ${getErrorMessage(err)}`, ); } - console.log('Waiting for authentication...'); + debugLogger.log('Waiting for authentication...'); // Add timeout to prevent infinite waiting when browser tab gets stuck const authTimeout = 5 * 60 * 1000; // 5 minutes timeout @@ -236,10 +237,12 @@ async function authWithUserCode(client: OAuth2Client): Promise { code_challenge: codeVerifier.codeChallenge, state, }); - console.log('Please visit the following URL to authorize the application:'); - console.log(''); - console.log(authUrl); - console.log(''); + debugLogger.log( + 'Please visit the following URL to authorize the application:', + ); + debugLogger.log(''); + debugLogger.log(authUrl); + debugLogger.log(''); const code = await new Promise((resolve) => { const rl = readline.createInterface({ @@ -337,7 +340,7 @@ async function authWithWeb(client: OAuth2Client): Promise { try { await fetchAndCacheUserInfo(client); } catch (error) { - console.warn( + debugLogger.warn( 'Failed to retrieve Google Account ID during authentication:', getErrorMessage(error), ); @@ -462,7 +465,7 @@ async function loadCachedCredentials(client: OAuth2Client): Promise { return true; } catch (error) { // Log specific error for debugging, but continue trying other paths - console.debug( + debugLogger.debug( `Failed to load credentials from ${keyFile}:`, getErrorMessage(error), ); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 93b4b0214f..b615c048e5 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -157,6 +157,7 @@ import { DEFAULT_FILE_FILTERING_OPTIONS, DEFAULT_MEMORY_FILE_FILTERING_OPTIONS, } from './constants.js'; +import { debugLogger } from '../utils/debugLogger.js'; export type { FileFilteringOptions }; export { @@ -1123,7 +1124,7 @@ export class Config { // the tool registry. const messageBusEnabled = this.getEnableMessageBusIntegration(); if (this.debugMode && messageBusEnabled) { - console.log( + debugLogger.log( `[DEBUG] enableMessageBusIntegration setting: ${messageBusEnabled}`, ); } @@ -1131,7 +1132,7 @@ export class Config { ? [...args, this.getMessageBus()] : args; if (this.debugMode && messageBusEnabled) { - console.log( + debugLogger.log( `[DEBUG] Registering ${className} with messageBus: ${messageBusEnabled ? 'YES' : 'NO'}`, ); } @@ -1206,7 +1207,7 @@ export class Config { ); } } else if (this.getDebugMode()) { - console.log( + debugLogger.log( `[Config] Skipping registration of agent '${definition.name}' due to allow/exclude configuration.`, ); } diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 57aa2ef7d1..b70266d138 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -1385,34 +1385,11 @@ ${JSON.stringify( // Assert expect(finalResult).toBeInstanceOf(Turn); - // Debug: Check how many times checkNextSpeaker was called - const callCount = mockCheckNextSpeaker.mock.calls.length; - // If infinite loop protection is working, checkNextSpeaker should be called many times // but stop at MAX_TURNS (100). Since each recursive call should trigger checkNextSpeaker, // we expect it to be called multiple times before hitting the limit expect(mockCheckNextSpeaker).toHaveBeenCalled(); - // The test should demonstrate that the infinite loop protection works: - // - If checkNextSpeaker is called many times (close to MAX_TURNS), it shows the loop was happening - // - If it's only called once, the recursive behavior might not be triggered - if (callCount === 0) { - throw new Error( - 'checkNextSpeaker was never called - the recursive condition was not met', - ); - } else if (callCount === 1) { - // This might be expected behavior if the turn has pending tool calls or other conditions prevent recursion - console.log( - 'checkNextSpeaker called only once - no infinite loop occurred', - ); - } else { - console.log( - `checkNextSpeaker called ${callCount} times - infinite loop protection worked`, - ); - // If called multiple times, we expect it to be stopped before MAX_TURNS - expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS - } - // The stream should produce events and eventually terminate expect(eventCount).toBeGreaterThanOrEqual(1); expect(eventCount).toBeLessThan(200); // Should not exceed our safety limit @@ -1537,11 +1514,6 @@ ${JSON.stringify( // the loop should stop at MAX_TURNS (100) expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS expect(eventCount).toBeLessThanOrEqual(200); // Should have reasonable number of events - - console.log( - `Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` + - `${eventCount} events generated (properly bounded by MAX_TURNS)`, - ); }); it('should yield ContextWindowWillOverflow when the context window is about to overflow', async () => { diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 74635e207f..484602e636 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -52,6 +52,7 @@ import type { IdeContext, File } from '../ide/types.js'; import { handleFallback } from '../fallback/handler.js'; import type { RoutingContext } from '../routing/routingStrategy.js'; import { uiTelemetryService } from '../telemetry/uiTelemetry.js'; +import { debugLogger } from '../utils/debugLogger.js'; export function isThinkingSupported(model: string) { return model.startsWith('gemini-2.5') || model === DEFAULT_GEMINI_MODEL_AUTO; @@ -341,7 +342,7 @@ My setup is complete. I will provide my first command in the next turn. ]; if (this.config.getDebugMode()) { - console.log(contextParts.join('\n')); + debugLogger.log(contextParts.join('\n')); } return { contextParts, @@ -451,7 +452,7 @@ My setup is complete. I will provide my first command in the next turn. ]; if (this.config.getDebugMode()) { - console.log(contextParts.join('\n')); + debugLogger.log(contextParts.join('\n')); } return { contextParts, diff --git a/packages/core/src/core/logger.ts b/packages/core/src/core/logger.ts index 1f4e6700f1..793b2666a6 100644 --- a/packages/core/src/core/logger.ts +++ b/packages/core/src/core/logger.ts @@ -8,6 +8,7 @@ import path from 'node:path'; import { promises as fs } from 'node:fs'; import type { Content } from '@google/genai'; import type { Storage } from '../config/storage.js'; +import { debugLogger } from '../utils/debugLogger.js'; const LOG_FILE_NAME = 'logs.json'; @@ -82,7 +83,7 @@ export class Logger { const fileContent = await fs.readFile(this.logFilePath, 'utf-8'); const parsedLogs = JSON.parse(fileContent); if (!Array.isArray(parsedLogs)) { - console.debug( + debugLogger.debug( `Log file at ${this.logFilePath} is not a valid JSON array. Starting with empty logs.`, ); await this._backupCorruptedLogFile('malformed_array'); @@ -102,14 +103,14 @@ export class Logger { return []; } if (error instanceof SyntaxError) { - console.debug( + debugLogger.debug( `Invalid JSON in log file ${this.logFilePath}. Backing up and starting fresh.`, error, ); await this._backupCorruptedLogFile('invalid_json'); return []; } - console.debug( + debugLogger.debug( `Failed to read or parse log file ${this.logFilePath}:`, error, ); @@ -122,7 +123,7 @@ export class Logger { const backupPath = `${this.logFilePath}.${reason}.${Date.now()}.bak`; try { await fs.rename(this.logFilePath, backupPath); - console.debug(`Backed up corrupted log file to ${backupPath}`); + debugLogger.debug(`Backed up corrupted log file to ${backupPath}`); } catch (_backupError) { // If rename fails (e.g. file doesn't exist), no need to log an error here as the primary error (e.g. invalid JSON) is already handled. } @@ -166,7 +167,7 @@ export class Logger { entryToAppend: LogEntry, ): Promise { if (!this.logFilePath) { - console.debug('Log file path not set. Cannot persist log entry.'); + debugLogger.debug('Log file path not set. Cannot persist log entry.'); throw new Error('Log file path not set during update attempt.'); } @@ -174,7 +175,7 @@ export class Logger { try { currentLogsOnDisk = await this._readLogFile(); } catch (readError) { - console.debug( + debugLogger.debug( 'Critical error reading log file before append:', readError, ); @@ -205,7 +206,7 @@ export class Logger { ); if (entryExists) { - console.debug( + debugLogger.debug( `Duplicate log entry detected and skipped: session ${entryToAppend.sessionId}, messageId ${entryToAppend.messageId}`, ); this.logs = currentLogsOnDisk; // Ensure in-memory is synced with disk @@ -223,7 +224,7 @@ export class Logger { this.logs = currentLogsOnDisk; return entryToAppend; // Return the successfully appended entry } catch (error) { - console.debug('Error writing to log file:', error); + debugLogger.debug('Error writing to log file:', error); throw error; } } @@ -242,7 +243,7 @@ export class Logger { async logMessage(type: MessageSenderType, message: string): Promise { if (!this.initialized || this.sessionId === undefined) { - console.debug( + debugLogger.debug( 'Logger not initialized or session ID missing. Cannot log message.', ); return; @@ -341,7 +342,7 @@ export class Logger { const fileContent = await fs.readFile(path, 'utf-8'); const parsedContent = JSON.parse(fileContent); if (!Array.isArray(parsedContent)) { - console.warn( + debugLogger.warn( `Checkpoint file at ${path} is not a valid JSON array. Returning empty checkpoint.`, ); return []; diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 727593456b..e0005059b1 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -22,6 +22,7 @@ import { isGitRepository } from '../utils/gitUtils.js'; import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js'; import type { Config } from '../config/config.js'; import { GEMINI_DIR } from '../utils/paths.js'; +import { debugLogger } from '../utils/debugLogger.js'; export function resolvePathFromEnv(envVar?: string): { isSwitch: boolean; @@ -56,7 +57,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, ); diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts index 46ed52876d..c42f61a7a6 100644 --- a/packages/core/src/ide/ide-client.ts +++ b/packages/core/src/ide/ide-client.ts @@ -24,10 +24,11 @@ import * as path from 'node:path'; import { EnvHttpProxyAgent } from 'undici'; import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; import { IDE_REQUEST_TIMEOUT_MS } from './constants.js'; +import { debugLogger } from '../utils/debugLogger.js'; const logger = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - debug: (...args: any[]) => console.debug('[DEBUG] [IDEClient]', ...args), + debug: (...args: any[]) => debugLogger.debug('[DEBUG] [IDEClient]', ...args), // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (...args: any[]) => console.error('[ERROR] [IDEClient]', ...args), }; diff --git a/packages/core/src/mcp/oauth-provider.ts b/packages/core/src/mcp/oauth-provider.ts index bf40c8ebff..f7051cd4f8 100644 --- a/packages/core/src/mcp/oauth-provider.ts +++ b/packages/core/src/mcp/oauth-provider.ts @@ -13,6 +13,7 @@ import type { OAuthToken } from './token-storage/types.js'; import { MCPOAuthTokenStorage } from './oauth-token-storage.js'; import { getErrorMessage } from '../utils/errors.js'; import { OAuthUtils } from './oauth-utils.js'; +import { debugLogger } from '../utils/debugLogger.js'; export const OAUTH_DISPLAY_MESSAGE_EVENT = 'oauth-display-message' as const; @@ -258,7 +259,9 @@ export class MCPOAuthProvider { server.on('error', reject); server.listen(REDIRECT_PORT, () => { - console.log(`OAuth callback server listening on port ${REDIRECT_PORT}`); + debugLogger.log( + `OAuth callback server listening on port ${REDIRECT_PORT}`, + ); }); // Timeout after 5 minutes @@ -314,7 +317,7 @@ export class MCPOAuthProvider { OAuthUtils.buildResourceParameter(mcpServerUrl), ); } catch (error) { - console.warn( + debugLogger.warn( `Could not add resource parameter: ${getErrorMessage(error)}`, ); } @@ -371,7 +374,7 @@ export class MCPOAuthProvider { OAuthUtils.buildResourceParameter(resourceUrl), ); } catch (error) { - console.warn( + debugLogger.warn( `Could not add resource parameter: ${getErrorMessage(error)}`, ); } @@ -413,7 +416,7 @@ export class MCPOAuthProvider { !contentType.includes('application/json') && !contentType.includes('application/x-www-form-urlencoded') ) { - console.warn( + debugLogger.warn( `Token endpoint returned unexpected content-type: ${contentType}. ` + `Expected application/json or application/x-www-form-urlencoded. ` + `Will attempt to parse response.`, @@ -493,7 +496,7 @@ export class MCPOAuthProvider { OAuthUtils.buildResourceParameter(mcpServerUrl), ); } catch (error) { - console.warn( + debugLogger.warn( `Could not add resource parameter: ${getErrorMessage(error)}`, ); } @@ -535,7 +538,7 @@ export class MCPOAuthProvider { !contentType.includes('application/json') && !contentType.includes('application/x-www-form-urlencoded') ) { - console.warn( + debugLogger.warn( `Token refresh endpoint returned unexpected content-type: ${contentType}. ` + `Expected application/json or application/x-www-form-urlencoded. ` + `Will attempt to parse response.`, @@ -593,13 +596,13 @@ export class MCPOAuthProvider { if (events) { events.emit(OAUTH_DISPLAY_MESSAGE_EVENT, message); } else { - console.log(message); + debugLogger.log(message); } }; // If no authorization URL is provided, try to discover OAuth configuration if (!config.authorizationUrl && mcpServerUrl) { - console.debug(`Starting OAuth for MCP server "${serverName}"… + debugLogger.debug(`Starting OAuth for MCP server "${serverName}"… ✓ No authorization URL; using OAuth discovery`); // First check if the server requires authentication via WWW-Authenticate header @@ -636,7 +639,7 @@ export class MCPOAuthProvider { } } } catch (error) { - console.debug( + debugLogger.debug( `Failed to check endpoint for authentication requirements: ${getErrorMessage(error)}`, ); } @@ -681,7 +684,7 @@ export class MCPOAuthProvider { const authUrl = new URL(config.authorizationUrl); const serverUrl = `${authUrl.protocol}//${authUrl.host}`; - console.debug('→ Attempting dynamic client registration...'); + debugLogger.debug('→ Attempting dynamic client registration...'); // Get the authorization server metadata for registration const authServerMetadata = @@ -707,7 +710,7 @@ export class MCPOAuthProvider { config.clientSecret = clientRegistration.client_secret; } - console.debug('✓ Dynamic client registration successful'); + debugLogger.debug('✓ Dynamic client registration successful'); } else { throw new Error( 'No client ID provided and dynamic registration not supported', @@ -747,7 +750,7 @@ ${authUrl} try { await openBrowserSecurely(authUrl); } catch (error) { - console.warn( + debugLogger.warn( 'Failed to open browser automatically:', getErrorMessage(error), ); @@ -756,7 +759,9 @@ ${authUrl} // Wait for callback const { code } = await callbackPromise; - console.debug('✓ Authorization code received, exchanging for tokens...'); + debugLogger.debug( + '✓ Authorization code received, exchanging for tokens...', + ); // Exchange code for tokens const tokenResponse = await this.exchangeCodeForToken( @@ -791,7 +796,7 @@ ${authUrl} config.tokenUrl, mcpServerUrl, ); - console.debug('✓ Authentication successful! Token saved.'); + debugLogger.debug('✓ Authentication successful! Token saved.'); // Verify token was saved const savedToken = await this.tokenStorage.getCredentials(serverName); @@ -802,7 +807,7 @@ ${authUrl} .update(savedToken.token.accessToken) .digest('hex') .slice(0, 8); - console.debug( + debugLogger.debug( `✓ Token verification successful (fingerprint: ${tokenFingerprint})`, ); } else { @@ -829,29 +834,31 @@ ${authUrl} serverName: string, config: MCPOAuthConfig, ): Promise { - console.debug(`Getting valid token for server: ${serverName}`); + debugLogger.debug(`Getting valid token for server: ${serverName}`); const credentials = await this.tokenStorage.getCredentials(serverName); if (!credentials) { - console.debug(`No credentials found for server: ${serverName}`); + debugLogger.debug(`No credentials found for server: ${serverName}`); return null; } const { token } = credentials; - console.debug( + debugLogger.debug( `Found token for server: ${serverName}, expired: ${this.tokenStorage.isTokenExpired(token)}`, ); // Check if token is expired if (!this.tokenStorage.isTokenExpired(token)) { - console.debug(`Returning valid token for server: ${serverName}`); + debugLogger.debug(`Returning valid token for server: ${serverName}`); return token.accessToken; } // Try to refresh if we have a refresh token if (token.refreshToken && config.clientId && credentials.tokenUrl) { try { - console.log(`Refreshing expired token for MCP server: ${serverName}`); + debugLogger.log( + `Refreshing expired token for MCP server: ${serverName}`, + ); const newTokenResponse = await this.refreshAccessToken( config, diff --git a/packages/core/src/mcp/oauth-utils.ts b/packages/core/src/mcp/oauth-utils.ts index 47ef1d3666..4787422f52 100644 --- a/packages/core/src/mcp/oauth-utils.ts +++ b/packages/core/src/mcp/oauth-utils.ts @@ -6,6 +6,7 @@ import type { MCPOAuthConfig } from './oauth-provider.js'; import { getErrorMessage } from '../utils/errors.js'; +import { debugLogger } from '../utils/debugLogger.js'; /** * OAuth authorization server metadata as per RFC 8414. @@ -94,7 +95,7 @@ export class OAuthUtils { } return (await response.json()) as OAuthProtectedResourceMetadata; } catch (error) { - console.debug( + debugLogger.debug( `Failed to fetch protected resource metadata from ${resourceMetadataUrl}: ${getErrorMessage(error)}`, ); return null; @@ -117,7 +118,7 @@ export class OAuthUtils { } return (await response.json()) as OAuthAuthorizationServerMetadata; } catch (error) { - console.debug( + debugLogger.debug( `Failed to fetch authorization server metadata from ${authServerMetadataUrl}: ${getErrorMessage(error)}`, ); return null; @@ -205,7 +206,7 @@ export class OAuthUtils { } } - console.debug( + debugLogger.debug( `Metadata discovery failed for authorization server ${authServerUrl}`, ); return null; @@ -249,7 +250,7 @@ export class OAuthUtils { if (authServerMetadata) { const config = this.metadataToOAuthConfig(authServerMetadata); if (authServerMetadata.registration_endpoint) { - console.log( + debugLogger.log( 'Dynamic client registration is supported at:', authServerMetadata.registration_endpoint, ); @@ -259,14 +260,14 @@ export class OAuthUtils { } // Fallback: try well-known endpoints at the base URL - console.debug(`Trying OAuth discovery fallback at ${serverUrl}`); + debugLogger.debug(`Trying OAuth discovery fallback at ${serverUrl}`); const authServerMetadata = await this.discoverAuthorizationServerMetadata(serverUrl); if (authServerMetadata) { const config = this.metadataToOAuthConfig(authServerMetadata); if (authServerMetadata.registration_endpoint) { - console.log( + debugLogger.log( 'Dynamic client registration is supported at:', authServerMetadata.registration_endpoint, ); @@ -276,7 +277,7 @@ export class OAuthUtils { return null; } catch (error) { - console.debug( + debugLogger.debug( `Failed to discover OAuth configuration: ${getErrorMessage(error)}`, ); return null; diff --git a/packages/core/src/prompts/prompt-registry.ts b/packages/core/src/prompts/prompt-registry.ts index aac761ab20..921d8bc415 100644 --- a/packages/core/src/prompts/prompt-registry.ts +++ b/packages/core/src/prompts/prompt-registry.ts @@ -5,6 +5,7 @@ */ import type { DiscoveredMCPPrompt } from '../tools/mcp-client.js'; +import { debugLogger } from '../utils/debugLogger.js'; export class PromptRegistry { private prompts: Map = new Map(); @@ -16,7 +17,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/routing/strategies/classifierStrategy.ts b/packages/core/src/routing/strategies/classifierStrategy.ts index f3af80ecb6..c3400f34a3 100644 --- a/packages/core/src/routing/strategies/classifierStrategy.ts +++ b/packages/core/src/routing/strategies/classifierStrategy.ts @@ -27,6 +27,7 @@ import { isFunctionCall, isFunctionResponse, } from '../../utils/messageInspectors.js'; +import { debugLogger } from '../../utils/debugLogger.js'; const CLASSIFIER_GENERATION_CONFIG: GenerateContentConfig = { temperature: 0, @@ -153,7 +154,7 @@ export class ClassifierStrategy implements RoutingStrategy { promptId = `classifier-router-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}`, ); } @@ -206,7 +207,7 @@ export class ClassifierStrategy implements RoutingStrategy { } catch (error) { // If the classifier fails for any reason (API error, parsing error, etc.), // we log it and return null to allow the composite strategy to proceed. - console.warn(`[Routing] ClassifierStrategy failed:`, error); + debugLogger.warn(`[Routing] ClassifierStrategy failed:`, error); return null; } } diff --git a/packages/core/src/services/loopDetectionService.ts b/packages/core/src/services/loopDetectionService.ts index 312cf0ac7b..f8e9216398 100644 --- a/packages/core/src/services/loopDetectionService.ts +++ b/packages/core/src/services/loopDetectionService.ts @@ -23,6 +23,7 @@ import { isFunctionCall, isFunctionResponse, } from '../utils/messageInspectors.js'; +import { debugLogger } from '../utils/debugLogger.js'; const TOOL_CALL_LOOP_THRESHOLD = 5; const CONTENT_LOOP_THRESHOLD = 10; @@ -431,14 +432,14 @@ export class LoopDetectionService { }); } catch (e) { // Do nothing, treat it as a non-loop. - this.config.getDebugMode() ? console.error(e) : console.debug(e); + this.config.getDebugMode() ? console.error(e) : debugLogger.debug(e); return false; } 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/telemetry/activity-monitor.ts b/packages/core/src/telemetry/activity-monitor.ts index 85db30a416..2c9393bdb4 100644 --- a/packages/core/src/telemetry/activity-monitor.ts +++ b/packages/core/src/telemetry/activity-monitor.ts @@ -8,6 +8,7 @@ import type { Config } from '../config/config.js'; import { isPerformanceMonitoringActive } from './metrics.js'; import { getMemoryMonitor } from './memory-monitor.js'; import { ActivityType } from './activity-types.js'; +import { debugLogger } from '../utils/debugLogger.js'; /** * Activity event data structure @@ -152,7 +153,7 @@ export class ActivityMonitor { listener(event); } catch (error) { // Silently catch listener errors to avoid disrupting the application - console.debug('ActivityMonitor listener error:', error); + debugLogger.debug('ActivityMonitor listener error:', error); } }); } diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 2325191613..5bce4b8edf 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -53,6 +53,7 @@ import { detectIdeFromEnv, isCloudShell, } from '../../ide/detect-ide.js'; +import { debugLogger } from '../../utils/debugLogger.js'; export enum EventNames { START_SESSION = 'start_session', @@ -238,7 +239,7 @@ export class ClearcutLogger { ]); if (wasAtCapacity && this.config?.getDebugMode()) { - console.debug( + debugLogger.debug( `ClearcutLogger: Dropped old event to prevent memory leak (queue size: ${this.events.size})`, ); } @@ -282,14 +283,14 @@ export class ClearcutLogger { } this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } async flushToClearcut(): Promise { if (this.flushing) { if (this.config?.getDebugMode()) { - console.debug( + debugLogger.debug( 'ClearcutLogger: Flush already in progress, marking pending flush.', ); } @@ -299,7 +300,7 @@ export class ClearcutLogger { this.flushing = true; if (this.config?.getDebugMode()) { - console.log('Flushing log events to Clearcut.'); + debugLogger.log('Flushing log events to Clearcut.'); } const eventsToSend = this.events.toArray() as LogEventEntry[][]; this.events.clear(); @@ -359,7 +360,7 @@ export class ClearcutLogger { // Fire and forget the pending flush this.flushToClearcut().catch((error) => { if (this.config?.getDebugMode()) { - console.debug('Error in pending flush to Clearcut:', error); + debugLogger.debug('Error in pending flush to Clearcut:', error); } }); } @@ -451,7 +452,7 @@ export class ClearcutLogger { // Flush start event immediately this.enqueueLogEvent(this.createLogEvent(EventNames.START_SESSION, data)); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -668,14 +669,14 @@ export class ClearcutLogger { logFlashFallbackEvent(): void { this.enqueueLogEvent(this.createLogEvent(EventNames.FLASH_FALLBACK, [])); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } logRipgrepFallbackEvent(): void { this.enqueueLogEvent(this.createLogEvent(EventNames.RIPGREP_FALLBACK, [])); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -811,7 +812,7 @@ export class ClearcutLogger { // Flush immediately on session end. this.enqueueLogEvent(this.createLogEvent(EventNames.END_SESSION, [])); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -910,7 +911,7 @@ export class ClearcutLogger { this.createLogEvent(EventNames.EXTENSION_INSTALL, data), ); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -930,7 +931,7 @@ export class ClearcutLogger { this.createLogEvent(EventNames.EXTENSION_UNINSTALL, data), ); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -962,7 +963,7 @@ export class ClearcutLogger { this.createLogEvent(EventNames.EXTENSION_UPDATE, data), ); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -1047,7 +1048,7 @@ export class ClearcutLogger { this.createLogEvent(EventNames.EXTENSION_ENABLE, data), ); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -1082,7 +1083,7 @@ export class ClearcutLogger { this.createLogEvent(EventNames.EXTENSION_DISABLE, data), ); this.flushToClearcut().catch((error) => { - console.debug('Error flushing to Clearcut:', error); + debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -1240,7 +1241,7 @@ export class ClearcutLogger { getConfigJson() { const configJson = safeJsonStringifyBooleanValuesOnly(this.config); - console.debug(configJson); + debugLogger.debug(configJson); return safeJsonStringifyBooleanValuesOnly(this.config); } @@ -1254,7 +1255,7 @@ export class ClearcutLogger { // Log a warning if we're dropping events if (eventsToSend.length > MAX_RETRY_EVENTS && this.config?.getDebugMode()) { - console.warn( + debugLogger.warn( `ClearcutLogger: Dropping ${ eventsToSend.length - MAX_RETRY_EVENTS } events due to retry queue limit. Total events: ${ @@ -1269,7 +1270,7 @@ export class ClearcutLogger { if (numEventsToRequeue === 0) { if (this.config?.getDebugMode()) { - console.debug( + debugLogger.debug( `ClearcutLogger: No events re-queued (queue size: ${this.events.size})`, ); } @@ -1292,7 +1293,7 @@ export class ClearcutLogger { } if (this.config?.getDebugMode()) { - console.debug( + debugLogger.debug( `ClearcutLogger: Re-queued ${numEventsToRequeue} events for retry (queue size: ${this.events.size})`, ); } diff --git a/packages/core/src/telemetry/sdk.ts b/packages/core/src/telemetry/sdk.ts index acc76692d0..d4d66b112f 100644 --- a/packages/core/src/telemetry/sdk.ts +++ b/packages/core/src/telemetry/sdk.ts @@ -43,6 +43,7 @@ import { GcpLogExporter, } from './gcp-exporters.js'; import { TelemetryTarget } from './index.js'; +import { debugLogger } from '../utils/debugLogger.js'; // For troubleshooting, set the log level to DiagLogLevel.DEBUG diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); @@ -184,7 +185,7 @@ export function initializeTelemetry(config: Config): void { try { sdk.start(); if (config.getDebugMode()) { - console.log('OpenTelemetry SDK started successfully.'); + debugLogger.log('OpenTelemetry SDK started successfully.'); } telemetryInitialized = true; initializeMetrics(config); @@ -211,7 +212,7 @@ export async function shutdownTelemetry(config: Config): Promise { ClearcutLogger.getInstance()?.shutdown(); await sdk.shutdown(); if (config.getDebugMode()) { - console.log('OpenTelemetry SDK shut down successfully.'); + debugLogger.log('OpenTelemetry SDK shut down successfully.'); } } catch (error) { console.error('Error shutting down SDK:', error); diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts index 73e5e5a927..40b58145f1 100644 --- a/packages/core/src/tools/edit.ts +++ b/packages/core/src/tools/edit.ts @@ -34,6 +34,7 @@ import type { import { IdeClient } from '../ide/ide-client.js'; import { safeLiteralReplace } from '../utils/textUtils.js'; import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js'; +import { debugLogger } from '../utils/debugLogger.js'; export function applyReplacement( currentContent: string | null, @@ -255,12 +256,12 @@ class EditToolInvocation implements ToolInvocation { throw error; } const errorMsg = error instanceof Error ? error.message : String(error); - console.log(`Error preparing edit: ${errorMsg}`); + debugLogger.log(`Error preparing edit: ${errorMsg}`); return false; } if (editData.error) { - console.log(`Error: ${editData.error.display}`); + debugLogger.log(`Error: ${editData.error.display}`); return false; } diff --git a/packages/core/src/tools/grep.ts b/packages/core/src/tools/grep.ts index cee8dd24f7..e2637accb8 100644 --- a/packages/core/src/tools/grep.ts +++ b/packages/core/src/tools/grep.ts @@ -388,7 +388,7 @@ class GrepToolInvocation extends BaseToolInvocation< }); return this.parseGrepOutput(output, absolutePath); } catch (gitError: unknown) { - console.debug( + debugLogger.debug( `GrepLogic: git grep failed: ${getErrorMessage( gitError, )}. Falling back...`, @@ -397,7 +397,7 @@ class GrepToolInvocation extends BaseToolInvocation< } // --- Strategy 2: System grep --- - console.debug( + debugLogger.debug( 'GrepLogic: System grep is being considered as fallback strategy.', ); @@ -494,7 +494,7 @@ class GrepToolInvocation extends BaseToolInvocation< }); return this.parseGrepOutput(output, absolutePath); } catch (grepError: unknown) { - console.debug( + debugLogger.debug( `GrepLogic: System grep failed: ${getErrorMessage( grepError, )}. Falling back...`, @@ -503,7 +503,7 @@ class GrepToolInvocation extends BaseToolInvocation< } // --- Strategy 3: Pure JavaScript Fallback --- - console.debug( + debugLogger.debug( 'GrepLogic: Falling back to JavaScript grep implementation.', ); strategyUsed = 'javascript fallback'; @@ -541,7 +541,7 @@ class GrepToolInvocation extends BaseToolInvocation< } catch (readError: unknown) { // Ignore errors like permission denied or file gone during read if (!isNodeError(readError) || readError.code !== 'ENOENT') { - console.debug( + debugLogger.debug( `GrepLogic: Could not read/process ${fileAbsolutePath}: ${getErrorMessage( readError, )}`, diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 1b5e5edd6f..552d190fa0 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -41,6 +41,7 @@ import type { WorkspaceContext, } from '../utils/workspaceContext.js'; import type { ToolRegistry } from './tool-registry.js'; +import { debugLogger } from '../utils/debugLogger.js'; export const MCP_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000; // default to 10 minutes @@ -321,7 +322,7 @@ async function handleAutomaticOAuth( wwwAuthenticate: string, ): Promise { try { - console.log(`🔐 '${mcpServerName}' requires OAuth authentication`); + debugLogger.log(`🔐 '${mcpServerName}' requires OAuth authentication`); // Always try to parse the resource metadata URI from the www-authenticate header let oauthConfig; @@ -358,13 +359,13 @@ async function handleAutomaticOAuth( // Perform OAuth authentication // Pass the server URL for proper discovery const serverUrl = mcpServerConfig.httpUrl || mcpServerConfig.url; - console.log( + debugLogger.log( `Starting OAuth authentication for server '${mcpServerName}'...`, ); const authProvider = new MCPOAuthProvider(new MCPOAuthTokenStorage()); await authProvider.authenticate(mcpServerName, oauthAuthConfig, serverUrl); - console.log( + debugLogger.log( `OAuth authentication successful for server '${mcpServerName}'`, ); return true; @@ -840,12 +841,12 @@ export async function connectToMcpServer( }, ); if (hasStoredTokens) { - console.log( + debugLogger.log( `Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` + `Please re-authenticate using: /mcp auth ${mcpServerName}`, ); } else { - console.log( + debugLogger.log( `401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` + `Please authenticate using: /mcp auth ${mcpServerName}`, ); @@ -862,7 +863,7 @@ export async function connectToMcpServer( // If we didn't get the header from the error string, try to get it from the server if (!wwwAuthenticate && hasNetworkTransport(mcpServerConfig)) { - console.log( + debugLogger.log( `No www-authenticate header in error, trying to fetch it from server...`, ); try { @@ -880,13 +881,13 @@ export async function connectToMcpServer( if (response.status === 401) { wwwAuthenticate = response.headers.get('www-authenticate'); if (wwwAuthenticate) { - console.log( + debugLogger.log( `Found www-authenticate header from server: ${wwwAuthenticate}`, ); } } } catch (fetchError) { - console.debug( + debugLogger.debug( `Failed to fetch www-authenticate header: ${getErrorMessage( fetchError, )}`, @@ -895,7 +896,7 @@ export async function connectToMcpServer( } if (wwwAuthenticate) { - console.log( + debugLogger.log( `Received 401 with www-authenticate header: ${wwwAuthenticate}`, ); @@ -907,7 +908,7 @@ export async function connectToMcpServer( ); if (oauthSuccess) { // Retry connection with OAuth token - console.log( + debugLogger.log( `Retrying connection to '${mcpServerName}' with OAuth token...`, ); @@ -1000,12 +1001,12 @@ export async function connectToMcpServer( }, ); if (hasStoredTokens) { - console.log( + debugLogger.log( `Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` + `Please re-authenticate using: /mcp auth ${mcpServerName}`, ); } else { - console.log( + debugLogger.log( `401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` + `Please authenticate using: /mcp auth ${mcpServerName}`, ); @@ -1018,7 +1019,9 @@ export async function connectToMcpServer( } // For SSE/HTTP servers, try to discover OAuth configuration from the base URL - console.log(`🔍 Attempting OAuth discovery for '${mcpServerName}'...`); + debugLogger.log( + `🔍 Attempting OAuth discovery for '${mcpServerName}'...`, + ); if (hasNetworkTransport(mcpServerConfig)) { const serverUrl = new URL( @@ -1030,7 +1033,7 @@ export async function connectToMcpServer( // Try to discover OAuth configuration from the base URL const oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl); if (oauthConfig) { - console.log( + debugLogger.log( `Discovered OAuth configuration from base URL for server '${mcpServerName}'`, ); @@ -1046,7 +1049,7 @@ export async function connectToMcpServer( // Pass the server URL for proper discovery const authServerUrl = mcpServerConfig.httpUrl || mcpServerConfig.url; - console.log( + debugLogger.log( `Starting OAuth authentication for server '${mcpServerName}'...`, ); const authProvider = new MCPOAuthProvider( @@ -1257,7 +1260,9 @@ export async function createTransport( if (accessToken) { hasOAuthConfig = true; - console.log(`Found stored OAuth token for server '${mcpServerName}'`); + debugLogger.log( + `Found stored OAuth token for server '${mcpServerName}'`, + ); } } } @@ -1322,7 +1327,10 @@ export async function createTransport( if (debugMode) { transport.stderr!.on('data', (data) => { const stderrStr = data.toString().trim(); - console.debug(`[DEBUG] [MCP STDERR (${mcpServerName})]: `, stderrStr); + debugLogger.debug( + `[DEBUG] [MCP STDERR (${mcpServerName})]: `, + stderrStr, + ); }); } return transport; @@ -1340,7 +1348,7 @@ export function isEnabled( mcpServerConfig: MCPServerConfig, ): boolean { if (!funcDecl.name) { - console.warn( + debugLogger.warn( `Discovered a function declaration without a name from MCP server '${mcpServerName}'. Skipping.`, ); return false; diff --git a/packages/core/src/tools/read-many-files.test.ts b/packages/core/src/tools/read-many-files.test.ts index f0711820dc..3f7ea710e6 100644 --- a/packages/core/src/tools/read-many-files.test.ts +++ b/packages/core/src/tools/read-many-files.test.ts @@ -733,8 +733,6 @@ Content of file[1] const invocation = tool.build({ paths: files }); await invocation.execute(new AbortController().signal); - console.log('Execution order:', executionOrder); - // Verify concurrent execution pattern // In parallel execution: all "start:" events should come before all "end:" events // In sequential execution: "start:file1", "end:file1", "start:file2", "end:file2", etc. diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 054f01b558..f8f9f69776 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -19,6 +19,7 @@ import type { Config } from '../config/config.js'; import { fileExists } from '../utils/fileUtils.js'; import { Storage } from '../config/storage.js'; import { GREP_TOOL_NAME } from './tool-names.js'; +import { debugLogger } from '../utils/debugLogger.js'; const DEFAULT_TOTAL_MAX_MATCHES = 20000; @@ -179,7 +180,7 @@ class GrepToolInvocation extends BaseToolInvocation< const totalMaxMatches = DEFAULT_TOTAL_MAX_MATCHES; if (this.config.getDebugMode()) { - console.log(`[GrepTool] Total result limit: ${totalMaxMatches}`); + debugLogger.log(`[GrepTool] Total result limit: ${totalMaxMatches}`); } for (const searchDir of searchDirectories) { diff --git a/packages/core/src/tools/smart-edit.ts b/packages/core/src/tools/smart-edit.ts index 16c770a9f6..113263ac0f 100644 --- a/packages/core/src/tools/smart-edit.ts +++ b/packages/core/src/tools/smart-edit.ts @@ -39,6 +39,7 @@ import { logSmartEditCorrectionEvent } from '../telemetry/loggers.js'; import { correctPath } from '../utils/pathCorrector.js'; import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js'; +import { debugLogger } from '../utils/debugLogger.js'; interface ReplacementContext { params: EditToolParams; currentContent: string; @@ -616,12 +617,12 @@ class EditToolInvocation implements ToolInvocation { throw error; } const errorMsg = error instanceof Error ? error.message : String(error); - console.log(`Error preparing edit: ${errorMsg}`); + debugLogger.log(`Error preparing edit: ${errorMsg}`); return false; } if (editData.error) { - console.log(`Error: ${editData.error.display}`); + debugLogger.log(`Error: ${editData.error.display}`); return false; } diff --git a/packages/core/src/tools/tool-registry.ts b/packages/core/src/tools/tool-registry.ts index 213efc4b4b..efd647c2bf 100644 --- a/packages/core/src/tools/tool-registry.ts +++ b/packages/core/src/tools/tool-registry.ts @@ -21,6 +21,7 @@ import { parse } from 'shell-quote'; import { ToolErrorType } from './tool-error.js'; import { safeJsonStringify } from '../utils/safeJsonStringify.js'; import type { EventEmitter } from 'node:events'; +import { debugLogger } from '../utils/debugLogger.js'; type ToolParams = Record; @@ -187,7 +188,7 @@ export class ToolRegistry { tool = tool.asFullyQualifiedTool(); } else { // Decide on behavior: throw error, log warning, or allow overwrite - console.warn( + debugLogger.warn( `Tool with name "${tool.name}" is already registered. Overwriting.`, ); } @@ -379,7 +380,7 @@ export class ToolRegistry { // register each function as a tool for (const func of functions) { if (!func.name) { - console.warn('Discovered a tool with no name. Skipping.'); + debugLogger.warn('Discovered a tool with no name. Skipping.'); continue; } const parameters = diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts index 2cc2575472..3e6c529f95 100644 --- a/packages/core/src/tools/web-fetch.ts +++ b/packages/core/src/tools/web-fetch.ts @@ -32,6 +32,7 @@ import { WebFetchFallbackAttemptEvent, } from '../telemetry/index.js'; import { WEB_FETCH_TOOL_NAME } from './tool-names.js'; +import { debugLogger } from '../utils/debugLogger.js'; const URL_FETCH_TIMEOUT_MS = 10000; const MAX_CONTENT_LENGTH = 100000; @@ -274,7 +275,7 @@ ${textContent} DEFAULT_GEMINI_FLASH_MODEL, ); - console.debug( + debugLogger.debug( `[WebFetchTool] Full response for prompt "${userPrompt.substring( 0, 50, @@ -367,7 +368,7 @@ ${sourceListFormatted.join('\n')}`; const llmContent = responseText; - console.debug( + debugLogger.debug( `[WebFetchTool] Formatted tool response for prompt "${userPrompt}:\n\n":`, llmContent, ); diff --git a/packages/core/src/utils/bfsFileSearch.ts b/packages/core/src/utils/bfsFileSearch.ts index cdec2333e9..36006335af 100644 --- a/packages/core/src/utils/bfsFileSearch.ts +++ b/packages/core/src/utils/bfsFileSearch.ts @@ -8,11 +8,13 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import type { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import type { FileFilteringOptions } from '../config/constants.js'; +import { debugLogger } from './debugLogger.js'; // Simple console logger for now. // TODO: Integrate with a more robust server-side logger. const logger = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - debug: (...args: any[]) => console.debug('[DEBUG] [BfsFileSearch]', ...args), + debug: (...args: any[]) => + debugLogger.debug('[DEBUG] [BfsFileSearch]', ...args), }; interface BfsFileSearchOptions { @@ -84,7 +86,7 @@ export async function bfsFileSearch( } catch (error) { // Warn user that a directory could not be read, as this affects search results. const message = (error as Error)?.message ?? 'Unknown error'; - console.warn( + debugLogger.warn( `[WARN] Skipping unreadable directory: ${currentDir} (${message})`, ); if (debug) { diff --git a/packages/core/src/utils/fileUtils.ts b/packages/core/src/utils/fileUtils.ts index 63c6cca48c..321162f192 100644 --- a/packages/core/src/utils/fileUtils.ts +++ b/packages/core/src/utils/fileUtils.ts @@ -14,6 +14,7 @@ import type { FileSystemService } from '../services/fileSystemService.js'; import { ToolErrorType } from '../tools/tool-error.js'; import { BINARY_EXTENSIONS } from './ignorePatterns.js'; import { createRequire as createModuleRequire } from 'node:module'; +import { debugLogger } from './debugLogger.js'; const requireModule = createModuleRequire(import.meta.url); @@ -260,7 +261,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), ); @@ -270,7 +271,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 734b9acbb2..0b9c54cb90 100644 --- a/packages/core/src/utils/getFolderStructure.ts +++ b/packages/core/src/utils/getFolderStructure.ts @@ -11,6 +11,7 @@ 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 { debugLogger } from './debugLogger.js'; const MAX_ITEMS = 200; const TRUNCATION_INDICATOR = '...'; @@ -104,7 +105,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') { diff --git a/packages/core/src/utils/llm-edit-fixer.ts b/packages/core/src/utils/llm-edit-fixer.ts index 40c66fa5e9..93c66a21c5 100644 --- a/packages/core/src/utils/llm-edit-fixer.ts +++ b/packages/core/src/utils/llm-edit-fixer.ts @@ -10,6 +10,7 @@ import { type BaseLlmClient } from '../core/baseLlmClient.js'; import { LruCache } from './LruCache.js'; import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js'; import { promptIdContext } from './promptIdContext.js'; +import { debugLogger } from './debugLogger.js'; const MAX_CACHE_SIZE = 50; @@ -112,7 +113,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/memoryDiscovery.ts b/packages/core/src/utils/memoryDiscovery.ts index a1f8bd2dfe..8d77f0bad4 100644 --- a/packages/core/src/utils/memoryDiscovery.ts +++ b/packages/core/src/utils/memoryDiscovery.ts @@ -16,15 +16,17 @@ import type { FileFilteringOptions } from '../config/constants.js'; import { DEFAULT_MEMORY_FILE_FILTERING_OPTIONS } from '../config/constants.js'; import { GEMINI_DIR } from './paths.js'; import type { GeminiCLIExtension } from '../config/config.js'; +import { debugLogger } from './debugLogger.js'; // Simple console logger, similar to the one previously in CLI's config.ts // TODO: Integrate with a more robust server-side logger if available/appropriate. const logger = { // eslint-disable-next-line @typescript-eslint/no-explicit-any debug: (...args: any[]) => - console.debug('[DEBUG] [MemoryDiscovery]', ...args), + debugLogger.debug('[DEBUG] [MemoryDiscovery]', ...args), // eslint-disable-next-line @typescript-eslint/no-explicit-any - warn: (...args: any[]) => console.warn('[WARN] [MemoryDiscovery]', ...args), + warn: (...args: any[]) => + debugLogger.warn('[WARN] [MemoryDiscovery]', ...args), // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (...args: any[]) => console.error('[ERROR] [MemoryDiscovery]', ...args), diff --git a/packages/core/src/utils/memoryImportProcessor.ts b/packages/core/src/utils/memoryImportProcessor.ts index 4af71cdfd8..eafee3340c 100644 --- a/packages/core/src/utils/memoryImportProcessor.ts +++ b/packages/core/src/utils/memoryImportProcessor.ts @@ -8,14 +8,16 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { isSubpath } from './paths.js'; import { marked, type Token } from 'marked'; +import { debugLogger } from './debugLogger.js'; // Simple console logger for import processing const logger = { // eslint-disable-next-line @typescript-eslint/no-explicit-any debug: (...args: any[]) => - console.debug('[DEBUG] [ImportProcessor]', ...args), + debugLogger.debug('[DEBUG] [ImportProcessor]', ...args), // eslint-disable-next-line @typescript-eslint/no-explicit-any - warn: (...args: any[]) => console.warn('[WARN] [ImportProcessor]', ...args), + warn: (...args: any[]) => + debugLogger.warn('[WARN] [ImportProcessor]', ...args), // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (...args: any[]) => console.error('[ERROR] [ImportProcessor]', ...args), diff --git a/packages/core/src/utils/nextSpeakerChecker.ts b/packages/core/src/utils/nextSpeakerChecker.ts index a7435e2575..00aee87e4a 100644 --- a/packages/core/src/utils/nextSpeakerChecker.ts +++ b/packages/core/src/utils/nextSpeakerChecker.ts @@ -9,6 +9,7 @@ import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js'; import type { BaseLlmClient } from '../core/baseLlmClient.js'; import type { GeminiChat } from '../core/geminiChat.js'; import { isFunctionResponse } from './messageInspectors.js'; +import { debugLogger } from './debugLogger.js'; 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 +127,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/retry.test.ts b/packages/core/src/utils/retry.test.ts index 6a05223771..13af50b475 100644 --- a/packages/core/src/utils/retry.test.ts +++ b/packages/core/src/utils/retry.test.ts @@ -10,6 +10,7 @@ import { ApiError } from '@google/genai'; import type { HttpError } from './retry.js'; import { retryWithBackoff } from './retry.js'; import { setSimulate429 } from './testUtils.js'; +import { debugLogger } from './debugLogger.js'; // Helper to create a mock function that fails a certain number of times const createFailingFunction = ( @@ -43,7 +44,7 @@ describe('retryWithBackoff', () => { // Disable 429 simulation for tests setSimulate429(false); // Suppress unhandled promise rejection warnings for tests that expect errors - console.warn = vi.fn(); + debugLogger.warn = vi.fn(); }); afterEach(() => { diff --git a/packages/core/src/utils/retry.ts b/packages/core/src/utils/retry.ts index 0f42003d1a..70afe42f5d 100644 --- a/packages/core/src/utils/retry.ts +++ b/packages/core/src/utils/retry.ts @@ -12,6 +12,7 @@ import { isGenericQuotaExceededError, } from './quotaErrorDetection.js'; import { delay, createAbortError } from './delay.js'; +import { debugLogger } from './debugLogger.js'; const FETCH_FAILED_MESSAGE = 'exception TypeError: fetch failed sending request'; @@ -168,7 +169,7 @@ export async function retryWithBackoff( } } catch (fallbackError) { // If fallback fails, continue with original error - console.warn('Fallback to Flash model failed:', fallbackError); + debugLogger.warn('Fallback to Flash model failed:', fallbackError); } } @@ -195,7 +196,7 @@ export async function retryWithBackoff( } } catch (fallbackError) { // If fallback fails, continue with original error - console.warn('Fallback to Flash model failed:', fallbackError); + debugLogger.warn('Fallback to Flash model failed:', fallbackError); } } @@ -227,7 +228,7 @@ export async function retryWithBackoff( } } catch (fallbackError) { // If fallback fails, continue with original error - console.warn('Fallback to Flash model failed:', fallbackError); + debugLogger.warn('Fallback to Flash model failed:', fallbackError); } } @@ -244,7 +245,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, ); @@ -367,13 +368,13 @@ function logRetryAttempt( } if (errorStatus === 429) { - console.warn(message, error); + debugLogger.warn(message, error); } else if (errorStatus && errorStatus >= 500 && errorStatus < 600) { console.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, ); @@ -383,9 +384,9 @@ function logRetryAttempt( 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/systemEncoding.ts b/packages/core/src/utils/systemEncoding.ts index 4f43b24ab1..298eed05a7 100644 --- a/packages/core/src/utils/systemEncoding.ts +++ b/packages/core/src/utils/systemEncoding.ts @@ -7,6 +7,7 @@ import { execSync } from 'node:child_process'; import os from 'node:os'; import { detect as chardetDetect } from 'chardet'; +import { debugLogger } from './debugLogger.js'; // Cache for system encoding to avoid repeated detection // Use undefined to indicate "not yet checked" vs null meaning "checked but failed" @@ -66,7 +67,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.`, ); @@ -88,7 +89,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; } } @@ -141,7 +142,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 } @@ -159,7 +160,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/userAccountManager.ts b/packages/core/src/utils/userAccountManager.ts index 28d3cef965..83d27d947b 100644 --- a/packages/core/src/utils/userAccountManager.ts +++ b/packages/core/src/utils/userAccountManager.ts @@ -7,6 +7,7 @@ import path from 'node:path'; import { promises as fsp, readFileSync } from 'node:fs'; import { Storage } from '../config/storage.js'; +import { debugLogger } from './debugLogger.js'; interface UserAccounts { active: string | null; @@ -33,7 +34,7 @@ export class UserAccountManager { // Inlined validation logic if (typeof parsed !== 'object' || parsed === null) { - console.log('Invalid accounts file schema, starting fresh.'); + debugLogger.log('Invalid accounts file schema, starting fresh.'); return defaultState; } const { active, old } = parsed as Partial; @@ -43,7 +44,7 @@ export class UserAccountManager { (Array.isArray(old) && old.every((i) => typeof i === 'string'))); if (!isValid) { - console.log('Invalid accounts file schema, starting fresh.'); + debugLogger.log('Invalid accounts file schema, starting fresh.'); return defaultState; } @@ -66,7 +67,10 @@ export class UserAccountManager { ) { return defaultState; } - console.log('Error during sync read of accounts, starting fresh.', error); + debugLogger.log( + 'Error during sync read of accounts, starting fresh.', + error, + ); return defaultState; } } @@ -84,7 +88,7 @@ export class UserAccountManager { ) { return defaultState; } - console.log('Could not parse accounts file, starting fresh.', error); + debugLogger.log('Could not parse accounts file, starting fresh.', error); return defaultState; } } diff --git a/packages/core/src/utils/workspaceContext.ts b/packages/core/src/utils/workspaceContext.ts index 97db6852cb..007fed6b54 100755 --- a/packages/core/src/utils/workspaceContext.ts +++ b/packages/core/src/utils/workspaceContext.ts @@ -8,6 +8,7 @@ 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 { debugLogger } from './debugLogger.js'; export type Unsubscribe = () => void; @@ -72,7 +73,7 @@ export class WorkspaceContext { this.directories.add(resolved); this.notifyDirectoriesChanged(); } catch (err) { - console.warn( + debugLogger.warn( `[WARN] Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`, ); }