mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 23:42:03 +00:00
Merge pull request #1610 from QwenLM/feat/debug-logging-refactor
Some checks are pending
Qwen Code CI / CodeQL (push) Waiting to run
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Some checks are pending
Qwen Code CI / CodeQL (push) Waiting to run
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
feat: debug mode output refactor — route console calls to logfile-first debugLogger
This commit is contained in:
commit
83fd2bc7f9
250 changed files with 3423 additions and 3734 deletions
|
|
@ -66,7 +66,6 @@ describe('App', () => {
|
|||
);
|
||||
|
||||
expect(lastFrame()).toContain('MainContent');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('Composer');
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +97,6 @@ describe('App', () => {
|
|||
);
|
||||
|
||||
expect(lastFrame()).toContain('MainContent');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('DialogManager');
|
||||
});
|
||||
|
||||
|
|
@ -157,6 +155,6 @@ describe('App', () => {
|
|||
</UIStateContext.Provider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('MainContent\nNotifications\nComposer');
|
||||
expect(lastFrame()).toContain('MainContent\nComposer');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ vi.mock('./hooks/useEditorSettings.js');
|
|||
vi.mock('./hooks/useSettingsCommand.js');
|
||||
vi.mock('./hooks/useModelCommand.js');
|
||||
vi.mock('./hooks/slashCommandProcessor.js');
|
||||
vi.mock('./hooks/useConsoleMessages.js');
|
||||
vi.mock('./hooks/useTerminalSize.js', () => ({
|
||||
useTerminalSize: vi.fn(() => ({ columns: 80, rows: 24 })),
|
||||
}));
|
||||
|
|
@ -85,7 +84,6 @@ vi.mock('./hooks/useLogger.js');
|
|||
// Mock external utilities
|
||||
vi.mock('../utils/events.js');
|
||||
vi.mock('../utils/handleAutoUpdate.js');
|
||||
vi.mock('./utils/ConsolePatcher.js');
|
||||
vi.mock('../utils/cleanup.js');
|
||||
|
||||
import { useHistory } from './hooks/useHistoryManager.js';
|
||||
|
|
@ -95,7 +93,6 @@ import { useEditorSettings } from './hooks/useEditorSettings.js';
|
|||
import { useSettingsCommand } from './hooks/useSettingsCommand.js';
|
||||
import { useModelCommand } from './hooks/useModelCommand.js';
|
||||
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
|
||||
import { useConsoleMessages } from './hooks/useConsoleMessages.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
import { useVim } from './hooks/vim.js';
|
||||
import { useFolderTrust } from './hooks/useFolderTrust.js';
|
||||
|
|
@ -125,7 +122,6 @@ describe('AppContainer State Management', () => {
|
|||
const mockedUseSettingsCommand = useSettingsCommand as Mock;
|
||||
const mockedUseModelCommand = useModelCommand as Mock;
|
||||
const mockedUseSlashCommandProcessor = useSlashCommandProcessor as Mock;
|
||||
const mockedUseConsoleMessages = useConsoleMessages as Mock;
|
||||
const mockedUseGeminiStream = useGeminiStream as Mock;
|
||||
const mockedUseVim = useVim as Mock;
|
||||
const mockedUseFolderTrust = useFolderTrust as Mock;
|
||||
|
|
@ -206,11 +202,6 @@ describe('AppContainer State Management', () => {
|
|||
shellConfirmationRequest: null,
|
||||
confirmationRequest: null,
|
||||
});
|
||||
mockedUseConsoleMessages.mockReturnValue({
|
||||
consoleMessages: [],
|
||||
handleNewMessage: vi.fn(),
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
mockedUseGeminiStream.mockReturnValue({
|
||||
streamingState: 'idle',
|
||||
submitQuery: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
type IdeContext,
|
||||
IdeClient,
|
||||
ideContextStore,
|
||||
createDebugLogger,
|
||||
getErrorMessage,
|
||||
getAllGeminiMdFilenames,
|
||||
ShellExecutionService,
|
||||
|
|
@ -55,7 +56,6 @@ import { useApprovalModeCommand } from './hooks/useApprovalModeCommand.js';
|
|||
import { useResumeCommand } from './hooks/useResumeCommand.js';
|
||||
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
|
||||
import { useVimMode } from './contexts/VimModeContext.js';
|
||||
import { useConsoleMessages } from './hooks/useConsoleMessages.js';
|
||||
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
||||
import { calculatePromptWidths } from './components/InputPrompt.js';
|
||||
import { useStdin, useStdout } from 'ink';
|
||||
|
|
@ -63,6 +63,7 @@ import ansiEscapes from 'ansi-escapes';
|
|||
import * as fs from 'node:fs';
|
||||
import { basename } from 'node:path';
|
||||
import { computeWindowTitle } from '../utils/windowTitle.js';
|
||||
import { clearScreen } from '../utils/stdioHelpers.js';
|
||||
import { useTextBuffer } from './components/shared/text-buffer.js';
|
||||
import { useLogger } from './hooks/useLogger.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
|
|
@ -80,10 +81,8 @@ import { type IdeIntegrationNudgeResult } from './IdeIntegrationNudge.js';
|
|||
import { type CommandMigrationNudgeResult } from './CommandFormatMigrationNudge.js';
|
||||
import { useCommandMigration } from './hooks/useCommandMigration.js';
|
||||
import { migrateTomlCommands } from '../services/command-migration-tool.js';
|
||||
import { appEvents, AppEvent } from '../utils/events.js';
|
||||
import { type UpdateObject } from './utils/updateCheck.js';
|
||||
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
|
||||
import { ConsolePatcher } from './utils/ConsolePatcher.js';
|
||||
import { registerCleanup, runExitCleanup } from '../utils/cleanup.js';
|
||||
import { useMessageQueue } from './hooks/useMessageQueue.js';
|
||||
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
|
||||
|
|
@ -111,6 +110,7 @@ import {
|
|||
} from '../commands/extensions/consent.js';
|
||||
|
||||
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
||||
const debugLogger = createDebugLogger('APP_CONTAINER');
|
||||
|
||||
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
|
||||
return pendingHistoryItems.some((item) => {
|
||||
|
|
@ -313,21 +313,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
return () => clearInterval(interval);
|
||||
}, [config, currentModel, getCurrentModel]);
|
||||
|
||||
const {
|
||||
consoleMessages,
|
||||
handleNewMessage,
|
||||
clearConsoleMessages: clearConsoleMessagesState,
|
||||
} = useConsoleMessages();
|
||||
|
||||
useEffect(() => {
|
||||
const consolePatcher = new ConsolePatcher({
|
||||
onNewMessage: handleNewMessage,
|
||||
debugMode: config.getDebugMode(),
|
||||
});
|
||||
consolePatcher.patch();
|
||||
registerCleanup(consolePatcher.cleanup);
|
||||
}, [handleNewMessage, config]);
|
||||
|
||||
// Derive widths for InputPrompt using shared helper
|
||||
const { inputWidth, suggestionsWidth } = useMemo(() => {
|
||||
const { inputWidth, suggestionsWidth } =
|
||||
|
|
@ -607,10 +592,13 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
[visionSwitchResolver],
|
||||
);
|
||||
|
||||
// onDebugMessage should log to console, not update footer debugMessage
|
||||
const onDebugMessage = useCallback((message: string) => {
|
||||
console.debug(message);
|
||||
}, []);
|
||||
// onDebugMessage should log to debug logfile, not update footer debugMessage
|
||||
const onDebugMessage = useCallback(
|
||||
(message: string) => {
|
||||
config.getDebugLogger().debug(message);
|
||||
},
|
||||
[config],
|
||||
);
|
||||
|
||||
const performMemoryRefresh = useCallback(async () => {
|
||||
historyManager.addItem(
|
||||
|
|
@ -626,7 +614,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
settings.merged.context?.loadFromIncludeDirectories
|
||||
? config.getWorkspaceContext().getDirectories()
|
||||
: [],
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
config.getExtensionContextFilePaths(),
|
||||
config.isTrustedFolder(),
|
||||
|
|
@ -648,14 +635,12 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
},
|
||||
Date.now(),
|
||||
);
|
||||
if (config.getDebugMode()) {
|
||||
console.log(
|
||||
`[DEBUG] Refreshed memory content in config: ${memoryContent.substring(
|
||||
0,
|
||||
200,
|
||||
)}...`,
|
||||
);
|
||||
}
|
||||
debugLogger.debug(
|
||||
`[DEBUG] Refreshed memory content in config: ${memoryContent.substring(
|
||||
0,
|
||||
200,
|
||||
)}...`,
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
historyManager.addItem(
|
||||
|
|
@ -665,7 +650,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
},
|
||||
Date.now(),
|
||||
);
|
||||
console.error('Error refreshing memory:', error);
|
||||
debugLogger.error('Error refreshing memory:', error);
|
||||
}
|
||||
}, [config, historyManager, settings.merged]);
|
||||
|
||||
|
|
@ -769,10 +754,9 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
|
||||
const handleClearScreen = useCallback(() => {
|
||||
historyManager.clearItems();
|
||||
clearConsoleMessagesState();
|
||||
console.clear();
|
||||
clearScreen();
|
||||
refreshStatic();
|
||||
}, [historyManager, clearConsoleMessagesState, refreshStatic]);
|
||||
}, [historyManager, refreshStatic]);
|
||||
|
||||
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
||||
|
||||
|
|
@ -900,7 +884,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
setShowMigrationNudge: setShowCommandMigrationNudge,
|
||||
} = useCommandMigration(settings, config.storage);
|
||||
|
||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
||||
const [showToolDescriptions, setShowToolDescriptions] =
|
||||
useState<boolean>(false);
|
||||
|
||||
|
|
@ -951,28 +934,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const openDebugConsole = () => {
|
||||
setShowErrorDetails(true);
|
||||
setConstrainHeight(false);
|
||||
};
|
||||
appEvents.on(AppEvent.OpenDebugConsole, openDebugConsole);
|
||||
|
||||
const logErrorHandler = (errorMessage: unknown) => {
|
||||
handleNewMessage({
|
||||
type: 'error',
|
||||
content: String(errorMessage),
|
||||
count: 1,
|
||||
});
|
||||
};
|
||||
appEvents.on(AppEvent.LogError, logErrorHandler);
|
||||
|
||||
return () => {
|
||||
appEvents.off(AppEvent.OpenDebugConsole, openDebugConsole);
|
||||
appEvents.off(AppEvent.LogError, logErrorHandler);
|
||||
};
|
||||
}, [handleNewMessage]);
|
||||
|
||||
const handleEscapePromptChange = useCallback((showPrompt: boolean) => {
|
||||
setShowEscapePrompt(showPrompt);
|
||||
}, []);
|
||||
|
|
@ -1180,7 +1141,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
(key: Key) => {
|
||||
// Debug log keystrokes if enabled
|
||||
if (settings.merged.general?.debugKeystrokeLogging) {
|
||||
console.log('[DEBUG] Keystroke:', JSON.stringify(key));
|
||||
debugLogger.debug('[DEBUG] Keystroke:', JSON.stringify(key));
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.QUIT](key)) {
|
||||
|
|
@ -1214,9 +1175,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
setConstrainHeight(true);
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.SHOW_ERROR_DETAILS](key)) {
|
||||
setShowErrorDetails((prev) => !prev);
|
||||
} else if (keyMatchers[Command.TOGGLE_TOOL_DESCRIPTIONS](key)) {
|
||||
if (keyMatchers[Command.TOGGLE_TOOL_DESCRIPTIONS](key)) {
|
||||
const newValue = !showToolDescriptions;
|
||||
setShowToolDescriptions(newValue);
|
||||
|
||||
|
|
@ -1244,7 +1203,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
[
|
||||
constrainHeight,
|
||||
setConstrainHeight,
|
||||
setShowErrorDetails,
|
||||
showToolDescriptions,
|
||||
setShowToolDescriptions,
|
||||
config,
|
||||
|
|
@ -1303,22 +1261,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
stdout,
|
||||
]);
|
||||
|
||||
const filteredConsoleMessages = useMemo(() => {
|
||||
if (config.getDebugMode()) {
|
||||
return consoleMessages;
|
||||
}
|
||||
return consoleMessages.filter((msg) => msg.type !== 'debug');
|
||||
}, [consoleMessages, config]);
|
||||
|
||||
// Computed values
|
||||
const errorCount = useMemo(
|
||||
() =>
|
||||
filteredConsoleMessages
|
||||
.filter((msg) => msg.type === 'error')
|
||||
.reduce((total, msg) => total + msg.count, 0),
|
||||
[filteredConsoleMessages],
|
||||
);
|
||||
|
||||
const nightly = props.version.includes('nightly');
|
||||
|
||||
const dialogsVisible =
|
||||
|
|
@ -1413,8 +1355,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
isFolderTrustDialogOpen: isFolderTrustDialogOpen ?? false,
|
||||
isTrustedFolder,
|
||||
constrainHeight,
|
||||
showErrorDetails,
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
showToolDescriptions,
|
||||
ctrlCPressedOnce,
|
||||
|
|
@ -1428,7 +1368,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
showAutoAcceptIndicator,
|
||||
currentModel,
|
||||
contextFileNames,
|
||||
errorCount,
|
||||
availableTerminalHeight,
|
||||
mainAreaWidth,
|
||||
staticAreaMaxItemHeight,
|
||||
|
|
@ -1506,8 +1445,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
isFolderTrustDialogOpen,
|
||||
isTrustedFolder,
|
||||
constrainHeight,
|
||||
showErrorDetails,
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
showToolDescriptions,
|
||||
ctrlCPressedOnce,
|
||||
|
|
@ -1520,7 +1457,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
messageQueue,
|
||||
showAutoAcceptIndicator,
|
||||
contextFileNames,
|
||||
errorCount,
|
||||
availableTerminalHeight,
|
||||
mainAreaWidth,
|
||||
staticAreaMaxItemHeight,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ describe('copyCommand', () => {
|
|||
getGeminiClient: () => ({
|
||||
getChat: mockGetChat,
|
||||
}),
|
||||
getDebugLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const copyCommand: SlashCommand = {
|
|||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.debug(message);
|
||||
context.services.config?.getDebugLogger().debug(message);
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ export const directoryCommand: SlashCommand = {
|
|||
...config.getWorkspaceContext().getDirectories(),
|
||||
...pathsToAdd,
|
||||
],
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
config.getExtensionContextFilePaths(),
|
||||
config.getFolderTrust(),
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ import {
|
|||
parseInstallSource,
|
||||
type ExtensionUpdateInfo,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import open from 'open';
|
||||
import { extensionToOutputString } from '../../commands/extensions/utils.js';
|
||||
|
||||
const debugLogger = createDebugLogger('EXTENSIONS_COMMAND');
|
||||
const EXTENSION_EXPLORE_URL = {
|
||||
Gemini: 'https://geminicli.com/extensions/',
|
||||
ClaudeCode: 'https://claudemarketplaces.com/',
|
||||
|
|
@ -240,7 +242,7 @@ async function updateAction(context: CommandContext, args: string) {
|
|||
async function installAction(context: CommandContext, args: string) {
|
||||
const extensionManager = context.services.config?.getExtensionManager();
|
||||
if (!(extensionManager instanceof ExtensionManager)) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
);
|
||||
return;
|
||||
|
|
@ -297,7 +299,7 @@ async function installAction(context: CommandContext, args: string) {
|
|||
async function uninstallAction(context: CommandContext, args: string) {
|
||||
const extensionManager = context.services.config?.getExtensionManager();
|
||||
if (!(extensionManager instanceof ExtensionManager)) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
);
|
||||
return;
|
||||
|
|
@ -357,7 +359,7 @@ function getEnableDisableContext(
|
|||
} | null {
|
||||
const extensionManager = context.services.config?.getExtensionManager();
|
||||
if (!(extensionManager instanceof ExtensionManager)) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
);
|
||||
return null;
|
||||
|
|
@ -479,7 +481,7 @@ async function enableAction(context: CommandContext, args: string) {
|
|||
async function detailAction(context: CommandContext, args: string) {
|
||||
const extensionManager = context.services.config?.getExtensionManager();
|
||||
if (!(extensionManager instanceof ExtensionManager)) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ import {
|
|||
resolveOutputLanguage,
|
||||
updateOutputLanguageFile,
|
||||
} from '../../utils/languageUtils.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('LANGUAGE_COMMAND');
|
||||
|
||||
/**
|
||||
* Gets the current LLM output language setting and its resolved value.
|
||||
|
|
@ -100,7 +103,7 @@ async function setUiLanguage(
|
|||
try {
|
||||
services.settings.setValue(SettingScope.User, 'general.language', lang);
|
||||
} catch (error) {
|
||||
console.warn('Failed to save language setting:', error);
|
||||
debugLogger.warn('Failed to save language setting:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +145,7 @@ async function setOutputLanguage(
|
|||
settingValue,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Failed to save output language setting:', error);
|
||||
debugLogger.warn('Failed to save output language setting:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -309,7 +309,6 @@ export const memoryCommand: SlashCommand = {
|
|||
config.shouldLoadMemoryFromIncludeDirectories()
|
||||
? config.getWorkspaceContext().getDirectories()
|
||||
: [],
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
config.getExtensionContextFilePaths(),
|
||||
config.getFolderTrust(),
|
||||
|
|
|
|||
|
|
@ -219,20 +219,13 @@ describe('updateGitignore', () => {
|
|||
});
|
||||
|
||||
it('handles permission errors gracefully', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
|
||||
|
||||
const fsModule = await import('node:fs');
|
||||
const writeFileSpy = vi
|
||||
.spyOn(fsModule.promises, 'writeFile')
|
||||
.mockRejectedValue(new Error('Permission denied'));
|
||||
|
||||
await expect(updateGitignore(scratchDir)).resolves.toBeUndefined();
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Failed to update .gitignore:',
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
writeFileSpy.mockRestore();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
|||
import { CommandKind } from './types.js';
|
||||
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('SETUP_GITHUB');
|
||||
|
||||
export const GITHUB_WORKFLOW_PATHS = [
|
||||
'qwen-dispatch/qwen-dispatch.yml',
|
||||
|
|
@ -85,7 +88,7 @@ export async function updateGitignore(gitRepoRoot: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Failed to update .gitignore:', error);
|
||||
debugLogger.debug('Failed to update .gitignore:', error);
|
||||
// Continue without failing the whole command
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +115,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.',
|
||||
);
|
||||
|
|
@ -128,7 +131,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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ import { MessageType, type HistoryItemSkillsList } from '../types.js';
|
|||
import { t } from '../../i18n/index.js';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
import type { SkillConfig } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('SKILLS_COMMAND');
|
||||
|
||||
export const skillsCommand: SlashCommand = {
|
||||
name: 'skills',
|
||||
|
|
@ -123,7 +126,7 @@ async function getSkillMatches(
|
|||
.map((result) => skillMap.get(result.item))
|
||||
.filter((skill): skill is SkillConfig => !!skill);
|
||||
} catch (error) {
|
||||
console.error('[skillsCommand] Fuzzy match failed:', error);
|
||||
debugLogger.error('[skillsCommand] Fuzzy match failed:', error);
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return skills.filter((skill) =>
|
||||
skill.name.toLowerCase().startsWith(lowerQuery),
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ vi.mock('./ShellModeIndicator.js', () => ({
|
|||
ShellModeIndicator: () => <Text>ShellModeIndicator</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./DetailedMessagesDisplay.js', () => ({
|
||||
DetailedMessagesDisplay: () => <Text>DetailedMessagesDisplay</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./InputPrompt.js', () => ({
|
||||
InputPrompt: () => <Text>InputPrompt</Text>,
|
||||
calculatePromptWidths: vi.fn(() => ({
|
||||
|
|
@ -60,10 +56,6 @@ vi.mock('./Footer.js', () => ({
|
|||
Footer: () => <Text>Footer</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./ShowMoreLines.js', () => ({
|
||||
ShowMoreLines: () => <Text>ShowMoreLines</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./QueuedMessageDisplay.js', () => ({
|
||||
QueuedMessageDisplay: ({ messageQueue }: { messageQueue: string[] }) => {
|
||||
if (messageQueue.length === 0) {
|
||||
|
|
@ -91,7 +83,6 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
|||
contextFileNames: [],
|
||||
showAutoAcceptIndicator: ApprovalMode.DEFAULT,
|
||||
messageQueue: [],
|
||||
showErrorDetails: false,
|
||||
constrainHeight: false,
|
||||
isInputActive: true,
|
||||
buffer: '',
|
||||
|
|
@ -111,7 +102,6 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
|||
ideContextState: null,
|
||||
geminiMdFileCount: 0,
|
||||
showToolDescriptions: false,
|
||||
filteredConsoleMessages: [],
|
||||
sessionStats: {
|
||||
lastPromptTokenCount: 0,
|
||||
sessionTokenCount: 0,
|
||||
|
|
@ -119,7 +109,6 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
|||
},
|
||||
branchName: 'main',
|
||||
debugMessage: '',
|
||||
errorCount: 0,
|
||||
nightly: false,
|
||||
isTrustedFolder: true,
|
||||
...overrides,
|
||||
|
|
@ -354,31 +343,4 @@ describe('Composer', () => {
|
|||
expect(lastFrame()).toContain('Footer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Details Display', () => {
|
||||
it('shows DetailedMessagesDisplay when showErrorDetails is true', () => {
|
||||
const uiState = createMockUIState({
|
||||
showErrorDetails: true,
|
||||
filteredConsoleMessages: [
|
||||
{ level: 'error', message: 'Test error', timestamp: new Date() },
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
] as any,
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toContain('DetailedMessagesDisplay');
|
||||
expect(lastFrame()).toContain('ShowMoreLines');
|
||||
});
|
||||
|
||||
it('does not show error details when showErrorDetails is false', () => {
|
||||
const uiState = createMockUIState({
|
||||
showErrorDetails: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('DetailedMessagesDisplay');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@
|
|||
*/
|
||||
|
||||
import { Box, useIsScreenReaderEnabled } from 'ink';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { LoadingIndicator } from './LoadingIndicator.js';
|
||||
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
||||
import { InputPrompt, calculatePromptWidths } from './InputPrompt.js';
|
||||
import { InputPrompt } from './InputPrompt.js';
|
||||
import { Footer } from './Footer.js';
|
||||
import { ShowMoreLines } from './ShowMoreLines.js';
|
||||
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
|
||||
import { KeyboardShortcuts } from './KeyboardShortcuts.js';
|
||||
import { OverflowProvider } from '../contexts/OverflowContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||
|
|
@ -29,8 +26,6 @@ export const Composer = () => {
|
|||
const uiState = useUIState();
|
||||
const uiActions = useUIActions();
|
||||
const { vimEnabled } = useVimMode();
|
||||
const terminalWidth = process.stdout.columns;
|
||||
const debugConsoleMaxHeight = Math.floor(Math.max(terminalWidth * 0.2, 5));
|
||||
|
||||
const { showAutoAcceptIndicator } = uiState;
|
||||
|
||||
|
|
@ -46,12 +41,6 @@ export const Composer = () => {
|
|||
setShowSuggestions(visible);
|
||||
}, []);
|
||||
|
||||
// Use the container width of InputPrompt for width of DetailedMessagesDisplay
|
||||
const { containerWidth } = useMemo(
|
||||
() => calculatePromptWidths(uiState.terminalWidth),
|
||||
[uiState.terminalWidth],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{!uiState.embeddedShellFocused && (
|
||||
|
|
@ -77,21 +66,6 @@ export const Composer = () => {
|
|||
|
||||
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
|
||||
|
||||
{uiState.showErrorDetails && (
|
||||
<OverflowProvider>
|
||||
<Box flexDirection="column">
|
||||
<DetailedMessagesDisplay
|
||||
messages={uiState.filteredConsoleMessages}
|
||||
maxHeight={
|
||||
uiState.constrainHeight ? debugConsoleMaxHeight : undefined
|
||||
}
|
||||
width={containerWidth}
|
||||
/>
|
||||
<ShowMoreLines constrainHeight={uiState.constrainHeight} />
|
||||
</Box>
|
||||
</OverflowProvider>
|
||||
)}
|
||||
|
||||
{uiState.isFeedbackDialogOpen && <FeedbackDialog />}
|
||||
|
||||
{uiState.isInputActive && (
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
interface ConsoleSummaryDisplayProps {
|
||||
errorCount: number;
|
||||
// logCount is not currently in the plan to be displayed in summary
|
||||
}
|
||||
|
||||
export const ConsoleSummaryDisplay: React.FC<ConsoleSummaryDisplayProps> = ({
|
||||
errorCount,
|
||||
}) => {
|
||||
if (errorCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorIcon = '\u2716'; // Heavy multiplication x (✖)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{errorCount > 0 && (
|
||||
<Text color={theme.status.error}>
|
||||
{errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '}
|
||||
<Text color={theme.text.secondary}>(ctrl+o for details)</Text>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
36
packages/cli/src/ui/components/DebugModeNotification.tsx
Normal file
36
packages/cli/src/ui/components/DebugModeNotification.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Box, Text } from 'ink';
|
||||
import { Storage, isDebugLoggingDegraded } from '@qwen-code/qwen-code-core';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
/**
|
||||
* Displays debug mode status and log file path when debug mode is enabled.
|
||||
*/
|
||||
export const DebugModeNotification = () => {
|
||||
const config = useConfig();
|
||||
|
||||
if (!config.getDebugMode()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const logPath = Storage.getDebugLogPath(config.getSessionId());
|
||||
const isDegraded = isDebugLoggingDegraded();
|
||||
|
||||
return (
|
||||
<Box paddingX={1} marginTop={1} flexDirection="column">
|
||||
<Text color={theme.status.warning}>Debug mode enabled</Text>
|
||||
<Text dimColor>Logging to: {logPath}</Text>
|
||||
{isDegraded && (
|
||||
<Text dimColor>
|
||||
Warning: Debug logging is degraded (write failures occurred)
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
import { MaxSizedBox } from './shared/MaxSizedBox.js';
|
||||
|
||||
interface DetailedMessagesDisplayProps {
|
||||
messages: ConsoleMessageItem[];
|
||||
maxHeight: number | undefined;
|
||||
width: number;
|
||||
// debugMode is not needed here if App.tsx filters debug messages before passing them.
|
||||
// If DetailedMessagesDisplay should handle filtering, add debugMode prop.
|
||||
}
|
||||
|
||||
export const DetailedMessagesDisplay: React.FC<
|
||||
DetailedMessagesDisplayProps
|
||||
> = ({ messages, maxHeight, width }) => {
|
||||
if (messages.length === 0) {
|
||||
return null; // Don't render anything if there are no messages
|
||||
}
|
||||
|
||||
const borderAndPadding = 4;
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
marginTop={1}
|
||||
borderStyle="round"
|
||||
borderColor={theme.border.default}
|
||||
paddingX={1}
|
||||
width={width}
|
||||
>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Debug Console{' '}
|
||||
<Text color={theme.text.secondary}>(ctrl+o to close)</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<MaxSizedBox maxHeight={maxHeight} maxWidth={width - borderAndPadding}>
|
||||
{messages.map((msg, index) => {
|
||||
let textColor = theme.text.primary;
|
||||
let icon = '\u2139'; // Information source (ℹ)
|
||||
|
||||
switch (msg.type) {
|
||||
case 'warn':
|
||||
textColor = theme.status.warning;
|
||||
icon = '\u26A0'; // Warning sign (⚠)
|
||||
break;
|
||||
case 'error':
|
||||
textColor = theme.status.error;
|
||||
icon = '\u2716'; // Heavy multiplication x (✖)
|
||||
break;
|
||||
case 'debug':
|
||||
textColor = theme.text.secondary; // Or theme.text.secondary
|
||||
icon = '\u{1F50D}'; // Left-pointing magnifying glass (🔍)
|
||||
break;
|
||||
case 'log':
|
||||
default:
|
||||
// Default textColor and icon are already set
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={index} flexDirection="row">
|
||||
<Text color={textColor}>{icon} </Text>
|
||||
<Text color={textColor} wrap="wrap">
|
||||
{msg.content}
|
||||
{msg.count && msg.count > 1 && (
|
||||
<Text color={theme.text.secondary}> (x{msg.count})</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</MaxSizedBox>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -18,10 +18,15 @@ import { ScopeSelector } from './shared/ScopeSelector.js';
|
|||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import type { EditorType } from '@qwen-code/qwen-code-core';
|
||||
import { isEditorAvailable } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
createDebugLogger,
|
||||
isEditorAvailable,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
const debugLogger = createDebugLogger('EDITOR_SETTINGS_DIALOG');
|
||||
|
||||
interface EditorDialogProps {
|
||||
onSelect: (editorType: EditorType | undefined, scope: SettingScope) => void;
|
||||
settings: LoadedSettings;
|
||||
|
|
@ -61,7 +66,7 @@ export function EditorSettingsDialog({
|
|||
)
|
||||
: 0;
|
||||
if (editorIndex === -1) {
|
||||
console.error(`Editor is not supported: ${currentPreference}`);
|
||||
debugLogger.error(`Editor is not supported: ${currentPreference}`);
|
||||
editorIndex = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
|
||||
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
|
||||
|
|
@ -25,20 +24,11 @@ export const Footer: React.FC = () => {
|
|||
const config = useConfig();
|
||||
const { vimEnabled, vimMode } = useVimMode();
|
||||
|
||||
const {
|
||||
errorCount,
|
||||
showErrorDetails,
|
||||
promptTokenCount,
|
||||
showAutoAcceptIndicator,
|
||||
} = {
|
||||
errorCount: uiState.errorCount,
|
||||
showErrorDetails: uiState.showErrorDetails,
|
||||
const { promptTokenCount, showAutoAcceptIndicator } = {
|
||||
promptTokenCount: uiState.sessionStats.lastPromptTokenCount,
|
||||
showAutoAcceptIndicator: uiState.showAutoAcceptIndicator,
|
||||
};
|
||||
|
||||
const showErrorIndicator = !showErrorDetails && errorCount > 0;
|
||||
|
||||
const { columns: terminalWidth } = useTerminalSize();
|
||||
const isNarrow = isNarrowWidth(terminalWidth);
|
||||
|
||||
|
|
@ -103,13 +93,6 @@ export const Footer: React.FC = () => {
|
|||
),
|
||||
});
|
||||
}
|
||||
if (showErrorIndicator) {
|
||||
rightItems.push({
|
||||
key: 'errors',
|
||||
node: <ConsoleSummaryDisplay errorCount={errorCount} />,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
justifyContent="space-between"
|
||||
|
|
|
|||
|
|
@ -38,19 +38,13 @@ describe('IdeTrustChangeDialog', () => {
|
|||
expect(frameText).toContain("Press 'r' to restart Gemini");
|
||||
});
|
||||
|
||||
it('renders a generic message and logs an error for NONE reason', () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
it('renders a generic message for NONE reason', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<IdeTrustChangeDialog reason="NONE" />,
|
||||
);
|
||||
|
||||
const frameText = lastFrame();
|
||||
expect(frameText).toContain('Workspace trust has changed.');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'IdeTrustChangeDialog rendered with unexpected reason "NONE"',
|
||||
);
|
||||
});
|
||||
|
||||
it('calls relaunchApp when "r" is pressed', () => {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ import { theme } from '../semantic-colors.js';
|
|||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { type RestartReason } from '../hooks/useIdeTrustListener.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
interface IdeTrustChangeDialogProps {
|
||||
reason: RestartReason;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('IDE_TRUST_DIALOG');
|
||||
|
||||
export const IdeTrustChangeDialog = ({ reason }: IdeTrustChangeDialogProps) => {
|
||||
useKeypress(
|
||||
(key) => {
|
||||
|
|
@ -27,7 +30,7 @@ export const IdeTrustChangeDialog = ({ reason }: IdeTrustChangeDialogProps) => {
|
|||
let message = 'Workspace trust has changed.';
|
||||
if (reason === 'NONE') {
|
||||
// This should not happen, but provides a fallback and a debug log.
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
'IdeTrustChangeDialog rendered with unexpected reason "NONE"',
|
||||
);
|
||||
} else if (reason === 'CONNECTION_CHANGE') {
|
||||
|
|
|
|||
|
|
@ -475,10 +475,7 @@ describe('InputPrompt', () => {
|
|||
unmount();
|
||||
});
|
||||
|
||||
it('should handle errors during clipboard operations', async () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
it('should handle errors during clipboard operations gracefully', async () => {
|
||||
vi.mocked(clipboardUtils.clipboardHasImage).mockRejectedValue(
|
||||
new Error('Clipboard error'),
|
||||
);
|
||||
|
|
@ -491,13 +488,9 @@ describe('InputPrompt', () => {
|
|||
stdin.write('\x16'); // Ctrl+V
|
||||
await wait();
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Error handling clipboard image:',
|
||||
expect.any(Error),
|
||||
);
|
||||
// Should not throw and should not set buffer text on error
|
||||
expect(mockBuffer.setText).not.toHaveBeenCalled();
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
|||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import type { CommandContext, SlashCommand } from '../commands/types.js';
|
||||
import type { Config } from '@qwen-code/qwen-code-core';
|
||||
import { ApprovalMode } from '@qwen-code/qwen-code-core';
|
||||
import { ApprovalMode, createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
parseInputForHighlighting,
|
||||
buildSegmentsForVisualSlice,
|
||||
|
|
@ -39,6 +39,8 @@ import { useShellFocusState } from '../contexts/ShellFocusContext.js';
|
|||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||
import { FEEDBACK_DIALOG_KEYS } from '../FeedbackDialog.js';
|
||||
|
||||
const debugLogger = createDebugLogger('INPUT_PROMPT');
|
||||
export interface InputPromptProps {
|
||||
buffer: TextBuffer;
|
||||
onSubmit: (value: string) => void;
|
||||
|
|
@ -301,7 +303,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling clipboard image:', error);
|
||||
debugLogger.error('Error handling clipboard image:', error);
|
||||
}
|
||||
}, [buffer, config]);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
import { Box, Static } from 'ink';
|
||||
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
|
||||
import { ShowMoreLines } from './ShowMoreLines.js';
|
||||
import { Notifications } from './Notifications.js';
|
||||
import { OverflowProvider } from '../contexts/OverflowContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useAppContext } from '../contexts/AppContext.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
import { DebugModeNotification } from './DebugModeNotification.js';
|
||||
|
||||
// Limit Gemini messages to a very high number of lines to mitigate performance
|
||||
// issues in the worst case if we somehow get an enormous response from Gemini.
|
||||
|
|
@ -35,6 +37,8 @@ export const MainContent = () => {
|
|||
key={uiState.historyRemountKey}
|
||||
items={[
|
||||
<AppHeader key="app-header" version={version} />,
|
||||
<DebugModeNotification key="debug-notification" />,
|
||||
<Notifications key="notifications" />,
|
||||
...uiState.history.map((h) => (
|
||||
<HistoryItemDisplay
|
||||
terminalWidth={terminalWidth}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,6 @@ export const Notifications = () => {
|
|||
const showInitError =
|
||||
initError && streamingState !== StreamingState.Responding;
|
||||
|
||||
if (!showStartupWarnings && !showInitError && !updateInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{updateInfo && <UpdateNotification message={updateInfo.message} />}
|
||||
|
|
|
|||
|
|
@ -394,10 +394,6 @@ describe('QwenOAuthProgress', () => {
|
|||
it('should handle QR code generation errors gracefully', async () => {
|
||||
const qrcode = await import('qrcode-terminal');
|
||||
const mockGenerate = vi.mocked(qrcode.default.generate);
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
mockGenerate.mockImplementation(() => {
|
||||
throw new Error('QR Code generation failed');
|
||||
});
|
||||
|
|
@ -413,12 +409,6 @@ describe('QwenOAuthProgress', () => {
|
|||
// Should not crash and should not show QR code section since QR generation failed
|
||||
const output = lastFrame();
|
||||
expect(output).not.toContain('Or scan the QR code below:');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Failed to generate QR code:',
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not generate QR code when deviceAuth is null', async () => {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import Link from 'ink-link';
|
|||
import qrcode from 'qrcode-terminal';
|
||||
import { Colors } from '../colors.js';
|
||||
import type { DeviceAuthorizationData } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
|
|
@ -29,6 +30,8 @@ interface QwenOAuthProgressProps {
|
|||
authMessage?: string | null;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('QWEN_OAUTH_PROGRESS');
|
||||
|
||||
/**
|
||||
* Static QR Code Display Component
|
||||
* Renders the QR code and URL once and doesn't re-render unless the URL changes
|
||||
|
|
@ -161,7 +164,7 @@ export function QwenOAuthProgress({
|
|||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate QR code:', error);
|
||||
debugLogger.error('Failed to generate QR code:', error);
|
||||
setQrCodeData(null);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
} from '../../utils/settingsUtils.js';
|
||||
import { updateOutputLanguageFile } from '../../utils/languageUtils.js';
|
||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||
import { type Config } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import chalk from 'chalk';
|
||||
import { cpSlice, cpLen, stripUnsafeCharacters } from '../utils/textUtils.js';
|
||||
|
|
@ -46,6 +46,8 @@ interface SettingsDialogProps {
|
|||
config?: Config;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('SETTINGS_DIALOG');
|
||||
|
||||
const maxItemsToShow = 8;
|
||||
|
||||
export function SettingsDialog({
|
||||
|
|
@ -162,7 +164,7 @@ export function SettingsDialog({
|
|||
{} as Settings,
|
||||
);
|
||||
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
`[DEBUG SettingsDialog] Saving ${key} immediately with value:`,
|
||||
newValue,
|
||||
);
|
||||
|
|
@ -177,7 +179,7 @@ export function SettingsDialog({
|
|||
if (key === 'general.vimMode' && newValue !== vimEnabled) {
|
||||
// Call toggleVimEnabled to sync the VimModeContext local state
|
||||
toggleVimEnabled().catch((error) => {
|
||||
console.error('Failed to toggle vim mode:', error);
|
||||
debugLogger.error('Failed to toggle vim mode:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +191,7 @@ export function SettingsDialog({
|
|||
try {
|
||||
config?.setApprovalMode(settings.merged.tools.approvalMode);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
'Failed to apply approval mode to current session:',
|
||||
error,
|
||||
);
|
||||
|
|
@ -663,7 +665,7 @@ export function SettingsDialog({
|
|||
try {
|
||||
config?.setApprovalMode(settings.merged.tools.approvalMode);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
'Failed to apply approval mode to current session:',
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { render, Box, useApp } from 'ink';
|
|||
import { getGitBranch, SessionService } from '@qwen-code/qwen-code-core';
|
||||
import { KeypressProvider } from '../contexts/KeypressContext.js';
|
||||
import { SessionPicker } from './SessionPicker.js';
|
||||
import { writeStdoutLine } from '../../utils/stdioHelpers.js';
|
||||
|
||||
interface StandalonePickerScreenProps {
|
||||
sessionService: SessionService;
|
||||
|
|
@ -70,7 +71,7 @@ export async function showResumeSessionPicker(
|
|||
const sessionService = new SessionService(cwd);
|
||||
const hasSession = await sessionService.loadLastSession();
|
||||
if (!hasSession) {
|
||||
console.log('No sessions found. Start a new session with `qwen`.');
|
||||
writeStdoutLine('No sessions found. Start a new session with `qwen`.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import stringWidth from 'string-width';
|
|||
import { theme } from '../../semantic-colors.js';
|
||||
import { toCodePoints } from '../../utils/textUtils.js';
|
||||
import { useOverflowActions } from '../../contexts/OverflowContext.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
let enableDebugLog = false;
|
||||
const debugLogger = createDebugLogger('MAX_SIZED_BOX');
|
||||
|
||||
/**
|
||||
* Minimum height for the MaxSizedBox component.
|
||||
|
|
@ -28,7 +30,7 @@ function debugReportError(message: string, element: React.ReactNode) {
|
|||
if (!enableDebugLog) return;
|
||||
|
||||
if (!React.isValidElement(element)) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
message,
|
||||
`Invalid element: '${String(element)}' typeof=${typeof element}`,
|
||||
);
|
||||
|
|
@ -44,10 +46,13 @@ function debugReportError(message: string, element: React.ReactNode) {
|
|||
const lineNumber = elementWithSource._source?.lineNumber;
|
||||
sourceMessage = fileName ? `${fileName}:${lineNumber}` : '<Unknown file>';
|
||||
} catch (error) {
|
||||
console.error('Error while trying to get file name:', error);
|
||||
debugLogger.error('Error while trying to get file name:', error);
|
||||
}
|
||||
|
||||
console.error(message, `${String(element.type)}. Source: ${sourceMessage}`);
|
||||
debugLogger.error(
|
||||
message,
|
||||
`${String(element.type)}. Source: ${sourceMessage}`,
|
||||
);
|
||||
}
|
||||
interface MaxSizedBoxProps {
|
||||
children?: React.ReactNode;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|||
import os from 'node:os';
|
||||
import pathMod from 'node:path';
|
||||
import { useState, useCallback, useEffect, useMemo, useReducer } from 'react';
|
||||
import { unescapePath } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger, unescapePath } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
toCodePoints,
|
||||
cpLen,
|
||||
|
|
@ -20,6 +20,8 @@ import {
|
|||
import type { VimAction } from './vim-buffer-actions.js';
|
||||
import { handleVimAction } from './vim-buffer-actions.js';
|
||||
|
||||
const debugLogger = createDebugLogger('TEXT_BUFFER');
|
||||
|
||||
export type Direction =
|
||||
| 'left'
|
||||
| 'right'
|
||||
|
|
@ -1143,7 +1145,7 @@ function textBufferReducerLogic(
|
|||
break;
|
||||
default: {
|
||||
const exhaustiveCheck: never = dir;
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Unknown visual movement direction: ${exhaustiveCheck}`,
|
||||
);
|
||||
return state;
|
||||
|
|
@ -1489,7 +1491,7 @@ function textBufferReducerLogic(
|
|||
|
||||
default: {
|
||||
const exhaustiveCheck: never = action;
|
||||
console.error(`Unknown action encountered: ${exhaustiveCheck}`);
|
||||
debugLogger.error(`Unknown action encountered: ${exhaustiveCheck}`);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -1858,7 +1860,7 @@ export function useTextBuffer({
|
|||
newText = newText.replace(/\r\n?/g, '\n');
|
||||
dispatch({ type: 'set_text', payload: newText, pushToUndo: false });
|
||||
} catch (err) {
|
||||
console.error('[useTextBuffer] external editor error', err);
|
||||
debugLogger.error('[useTextBuffer] external editor error', err);
|
||||
} finally {
|
||||
if (wasRaw) setRawMode?.(true);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ import type {
|
|||
SubagentManager,
|
||||
SubagentConfig,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { theme } from '../../../semantic-colors.js';
|
||||
import { shouldShowColor, getColorForDisplay } from '../utils.js';
|
||||
import { useLaunchEditor } from '../../../hooks/useLaunchEditor.js';
|
||||
import { useKeypress } from '../../../hooks/useKeypress.js';
|
||||
import { t } from '../../../../i18n/index.js';
|
||||
|
||||
const debugLogger = createDebugLogger('SUBAGENT_CREATION_SUMMARY');
|
||||
|
||||
/**
|
||||
* Step 6: Final confirmation and actions.
|
||||
*/
|
||||
|
|
@ -87,7 +90,7 @@ export function CreationSummary({
|
|||
}
|
||||
} catch (error) {
|
||||
// Silently handle errors in warning checks
|
||||
console.warn('Error checking subagent name availability:', error);
|
||||
debugLogger.warn('Error checking subagent name availability:', error);
|
||||
}
|
||||
|
||||
// Check length warnings
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { Box, Text } from 'ink';
|
||||
import { type SubagentConfig } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import type { StepNavigationProps } from '../types.js';
|
||||
import { theme } from '../../../semantic-colors.js';
|
||||
import { useKeypress } from '../../../hooks/useKeypress.js';
|
||||
|
|
@ -16,6 +17,8 @@ interface AgentDeleteStepProps extends StepNavigationProps {
|
|||
onDelete: (agent: SubagentConfig) => Promise<void>;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('AGENT_DELETE_STEP');
|
||||
|
||||
export function AgentDeleteStep({
|
||||
selectedAgent,
|
||||
onDelete,
|
||||
|
|
@ -30,7 +33,7 @@ export function AgentDeleteStep({
|
|||
await onDelete(selectedAgent);
|
||||
// Navigation will be handled by the parent component after successful deletion
|
||||
} catch (error) {
|
||||
console.error('Failed to delete agent:', error);
|
||||
debugLogger.error('Failed to delete agent:', error);
|
||||
}
|
||||
} else if (key.name === 'n') {
|
||||
onNavigateBack();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { MANAGEMENT_STEPS } from '../types.js';
|
|||
import { theme } from '../../../semantic-colors.js';
|
||||
import { getColorForDisplay, shouldShowColor } from '../utils.js';
|
||||
import type { SubagentConfig, Config } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { useKeypress } from '../../../hooks/useKeypress.js';
|
||||
import { t } from '../../../../i18n/index.js';
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ interface AgentsManagerDialogProps {
|
|||
config: Config | null;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('AGENTS_MANAGER_DIALOG');
|
||||
|
||||
/**
|
||||
* Main orchestrator component for the agents management dialog.
|
||||
*/
|
||||
|
|
@ -108,7 +111,7 @@ export function AgentsManagerDialog({
|
|||
setNavigationStack([MANAGEMENT_STEPS.AGENT_SELECTION]);
|
||||
setSelectedAgentIndex(-1);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete agent:', error);
|
||||
debugLogger.error('Failed to delete agent:', error);
|
||||
throw error; // Re-throw to let the component handle the error state
|
||||
}
|
||||
},
|
||||
|
|
@ -253,7 +256,7 @@ export function AgentsManagerDialog({
|
|||
await loadAgents();
|
||||
handleNavigateBack();
|
||||
} catch (error) {
|
||||
console.error('Failed to save agent changes:', error);
|
||||
debugLogger.error('Failed to save agent changes:', error);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
@ -282,7 +285,7 @@ export function AgentsManagerDialog({
|
|||
await loadAgents();
|
||||
handleNavigateBack();
|
||||
} catch (error) {
|
||||
console.error('Failed to save color changes:', error);
|
||||
debugLogger.error('Failed to save color changes:', error);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
import { Box, Text } from 'ink';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { ExtensionUpdateState } from '../../state/extensions.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('EXTENSIONS_LIST');
|
||||
|
||||
export const ExtensionsList = () => {
|
||||
const { extensionsUpdateState, commandContext } = useUIState();
|
||||
|
|
@ -47,7 +50,7 @@ export const ExtensionsList = () => {
|
|||
stateColor = 'green';
|
||||
break;
|
||||
default:
|
||||
console.error(`Unhandled ExtensionUpdateState ${state}`);
|
||||
debugLogger.error(`Unhandled ExtensionUpdateState ${state}`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1190,20 +1190,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
});
|
||||
|
||||
describe('debug keystroke logging', () => {
|
||||
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
||||
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not log keystrokes when debugKeystrokeLogging is false', async () => {
|
||||
it('should handle kitty sequences when debugKeystrokeLogging is false', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -1221,138 +1208,20 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send a kitty sequence
|
||||
// Send a kitty sequence - should work without debug logging
|
||||
act(() => {
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
expect(keyHandler).toHaveBeenCalled();
|
||||
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('[DEBUG] Kitty'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log kitty buffer accumulation when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send a complete kitty sequence for escape
|
||||
act(() => {
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
expect.stringContaining('\x1b[27u'),
|
||||
);
|
||||
const parsedCall = consoleLogSpy.mock.calls.find(
|
||||
(args) =>
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].includes('[DEBUG] Kitty sequence parsed successfully'),
|
||||
);
|
||||
expect(parsedCall).toBeTruthy();
|
||||
expect(parsedCall?.[1]).toEqual(expect.stringContaining('\x1b[27u'));
|
||||
});
|
||||
|
||||
it('should log kitty buffer overflow when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send an invalid long sequence to trigger overflow
|
||||
const longInvalidSequence = '\x1b[' + 'x'.repeat(100);
|
||||
act(() => {
|
||||
stdin.sendKittySequence(longInvalidSequence);
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send incomplete kitty sequence
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: undefined,
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence: '\x1b[1',
|
||||
});
|
||||
});
|
||||
|
||||
// Send Ctrl+C
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: 'c',
|
||||
ctrl: true,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence: '\x03',
|
||||
});
|
||||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||
'\x1b[1',
|
||||
);
|
||||
|
||||
// Verify Ctrl+C was handled
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'c',
|
||||
ctrl: true,
|
||||
name: 'escape',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show char codes when debugKeystrokeLogging is true even without debug mode', async () => {
|
||||
it('should handle kitty sequences when debugKeystrokeLogging is true', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -1370,29 +1239,44 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send incomplete kitty sequence
|
||||
const sequence = '\x1b[12';
|
||||
// Send a complete kitty sequence for escape - should work with debug logging
|
||||
act(() => {
|
||||
stdin.pressKey({
|
||||
name: undefined,
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
sequence,
|
||||
});
|
||||
stdin.sendKittySequence('\x1b[27u');
|
||||
});
|
||||
|
||||
// Verify debug logging for accumulation
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
sequence,
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'escape',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle kitty buffer overflow without crashing', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider
|
||||
kittyProtocolEnabled={true}
|
||||
debugKeystrokeLogging={true}
|
||||
>
|
||||
{children}
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
// Verify warning for char codes
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'Kitty sequence buffer has char codes:',
|
||||
[27, 91, 49, 50],
|
||||
);
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send an invalid long sequence to trigger overflow - should not crash
|
||||
const longInvalidSequence = '\x1b[' + 'x'.repeat(100);
|
||||
expect(() => {
|
||||
act(() => {
|
||||
stdin.sendKittySequence(longInvalidSequence);
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { Config } from '@qwen-code/qwen-code-core';
|
|||
import {
|
||||
KittySequenceOverflowEvent,
|
||||
logKittySequenceOverflow,
|
||||
createDebugLogger,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useStdin } from 'ink';
|
||||
import type React from 'react';
|
||||
|
|
@ -65,6 +66,7 @@ interface KeypressContextValue {
|
|||
const KeypressContext = createContext<KeypressContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const debugLogger = createDebugLogger('KEYPRESS');
|
||||
|
||||
export function useKeypressContext() {
|
||||
const context = useContext(KeypressContext);
|
||||
|
|
@ -486,7 +488,7 @@ export function KeypressProvider({
|
|||
key.sequence === `${ESC}${KITTY_CTRL_C}`
|
||||
) {
|
||||
if (kittySequenceBuffer && debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
@ -520,7 +522,7 @@ export function KeypressProvider({
|
|||
kittySequenceBuffer += key.sequence;
|
||||
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
@ -538,7 +540,7 @@ export function KeypressProvider({
|
|||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
);
|
||||
|
|
@ -554,12 +556,12 @@ export function KeypressProvider({
|
|||
parsed.length,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
);
|
||||
|
|
@ -576,12 +578,12 @@ export function KeypressProvider({
|
|||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
ch.charCodeAt(0),
|
||||
);
|
||||
console.warn('Kitty sequence buffer has char codes:', codes);
|
||||
debugLogger.warn('Kitty sequence buffer has char codes:', codes);
|
||||
}
|
||||
|
||||
if (kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { createContext, useContext } from 'react';
|
|||
import type {
|
||||
HistoryItem,
|
||||
ThoughtSummary,
|
||||
ConsoleMessageItem,
|
||||
ShellConfirmationRequest,
|
||||
ConfirmationRequest,
|
||||
LoopDetectionConfirmationRequest,
|
||||
|
|
@ -81,8 +80,6 @@ export interface UIState {
|
|||
isFolderTrustDialogOpen: boolean;
|
||||
isTrustedFolder: boolean | undefined;
|
||||
constrainHeight: boolean;
|
||||
showErrorDetails: boolean;
|
||||
filteredConsoleMessages: ConsoleMessageItem[];
|
||||
ideContextState: IdeContext | undefined;
|
||||
showToolDescriptions: boolean;
|
||||
ctrlCPressedOnce: boolean;
|
||||
|
|
@ -96,7 +93,6 @@ export interface UIState {
|
|||
// Quota-related state
|
||||
currentModel: string;
|
||||
contextFileNames: string[];
|
||||
errorCount: number;
|
||||
availableTerminalHeight: number | undefined;
|
||||
mainAreaWidth: number;
|
||||
staticAreaMaxItemHeight: number;
|
||||
|
|
|
|||
|
|
@ -234,11 +234,8 @@ export async function handleAtCommand({
|
|||
sawNotFound = true;
|
||||
continue;
|
||||
} else {
|
||||
console.error(
|
||||
`Error stating path ${pathName}: ${getErrorMessage(error)}`,
|
||||
);
|
||||
onDebugMessage(
|
||||
`Error stating path ${pathName}. Path ${pathName} will be skipped.`,
|
||||
`Error stating path ${pathName}: ${getErrorMessage(error)}. Path ${pathName} will be skipped.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -318,7 +315,6 @@ export async function handleAtCommand({
|
|||
}
|
||||
|
||||
const message = `Ignored ${totalIgnored} files:\n${messages.join('\n')}`;
|
||||
console.log(message);
|
||||
onDebugMessage(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ import type {
|
|||
GeminiClient,
|
||||
ShellExecutionResult,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { isBinary, ShellExecutionService } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
createDebugLogger,
|
||||
isBinary,
|
||||
ShellExecutionService,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { type PartListUnion } from '@google/genai';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import { SHELL_COMMAND_NAME } from '../constants.js';
|
||||
|
|
@ -29,6 +33,7 @@ import { themeManager } from '../../ui/themes/theme-manager.js';
|
|||
|
||||
export const OUTPUT_UPDATE_INTERVAL_MS = 1000;
|
||||
const MAX_OUTPUT_LENGTH = 10000;
|
||||
const debugLogger = createDebugLogger('SHELL_COMMAND_PROCESSOR');
|
||||
|
||||
function addShellCommandToGeminiHistory(
|
||||
geminiClient: GeminiClient,
|
||||
|
|
@ -231,7 +236,7 @@ export const useShellCommandProcessor = (
|
|||
shellExecutionConfig,
|
||||
);
|
||||
|
||||
console.log(terminalHeight, terminalWidth);
|
||||
debugLogger.debug(terminalHeight, terminalWidth);
|
||||
|
||||
executionPid = pid;
|
||||
if (pid) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
|||
import {
|
||||
type Logger,
|
||||
type Config,
|
||||
createDebugLogger,
|
||||
GitService,
|
||||
logSlashCommand,
|
||||
makeSlashCommandEvent,
|
||||
|
|
@ -33,12 +34,14 @@ import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
|
|||
import { FileCommandLoader } from '../../services/FileCommandLoader.js';
|
||||
import { McpPromptLoader } from '../../services/McpPromptLoader.js';
|
||||
import { parseSlashCommand } from '../../utils/commands.js';
|
||||
import { clearScreen } from '../../utils/stdioHelpers.js';
|
||||
import {
|
||||
type ExtensionUpdateAction,
|
||||
type ExtensionUpdateStatus,
|
||||
} from '../state/extensions.js';
|
||||
|
||||
type SerializableHistoryItem = Record<string, unknown>;
|
||||
const debugLogger = createDebugLogger('SLASH_COMMAND_PROCESSOR');
|
||||
|
||||
function serializeHistoryItemForRecording(
|
||||
item: Omit<HistoryItem, 'id'>,
|
||||
|
|
@ -200,7 +203,7 @@ export const useSlashCommandProcessor = (
|
|||
addItem,
|
||||
clear: () => {
|
||||
clearItems();
|
||||
console.clear();
|
||||
clearScreen();
|
||||
refreshStatic();
|
||||
},
|
||||
loadHistory,
|
||||
|
|
@ -600,12 +603,10 @@ export const useSlashCommandProcessor = (
|
|||
});
|
||||
}
|
||||
} catch (recordError) {
|
||||
if (config.getDebugMode()) {
|
||||
console.error(
|
||||
'[slashCommand] Failed to record slash command:',
|
||||
recordError,
|
||||
);
|
||||
}
|
||||
debugLogger.error(
|
||||
'[slashCommand] Failed to record slash command:',
|
||||
recordError,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (config && resolvedCommandPath[0] && !hasError) {
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
import { useConsoleMessages } from './useConsoleMessages';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
describe('useConsoleMessages', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
const useTestableConsoleMessages = () => {
|
||||
const { handleNewMessage, ...rest } = useConsoleMessages();
|
||||
const log = useCallback(
|
||||
(content: string) => handleNewMessage({ type: 'log', content, count: 1 }),
|
||||
[handleNewMessage],
|
||||
);
|
||||
const error = useCallback(
|
||||
(content: string) =>
|
||||
handleNewMessage({ type: 'error', content, count: 1 }),
|
||||
[handleNewMessage],
|
||||
);
|
||||
return {
|
||||
...rest,
|
||||
log,
|
||||
error,
|
||||
clearConsoleMessages: rest.clearConsoleMessages,
|
||||
};
|
||||
};
|
||||
|
||||
it('should initialize with an empty array of console messages', () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
expect(result.current.consoleMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should add a new message when log is called', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ type: 'log', content: 'Test message', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should batch and count identical consecutive messages', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ type: 'log', content: 'Test message', count: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not batch different messages', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.log('First message');
|
||||
result.current.error('Second message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ type: 'log', content: 'First message', count: 1 },
|
||||
{ type: 'error', content: 'Second message', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should clear all messages when clearConsoleMessages is called', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toHaveLength(1);
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should clear the pending timeout when clearConsoleMessages is called', () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should clean up the timeout on unmount', () => {
|
||||
const { result, unmount } = renderHook(() => useTestableConsoleMessages());
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
unmount();
|
||||
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useReducer,
|
||||
useRef,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
export interface UseConsoleMessagesReturn {
|
||||
consoleMessages: ConsoleMessageItem[];
|
||||
handleNewMessage: (message: ConsoleMessageItem) => void;
|
||||
clearConsoleMessages: () => void;
|
||||
}
|
||||
|
||||
type Action =
|
||||
| { type: 'ADD_MESSAGES'; payload: ConsoleMessageItem[] }
|
||||
| { type: 'CLEAR' };
|
||||
|
||||
function consoleMessagesReducer(
|
||||
state: ConsoleMessageItem[],
|
||||
action: Action,
|
||||
): ConsoleMessageItem[] {
|
||||
switch (action.type) {
|
||||
case 'ADD_MESSAGES': {
|
||||
const newMessages = [...state];
|
||||
for (const queuedMessage of action.payload) {
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (
|
||||
lastMessage &&
|
||||
lastMessage.type === queuedMessage.type &&
|
||||
lastMessage.content === queuedMessage.content
|
||||
) {
|
||||
// Create a new object for the last message to ensure React detects
|
||||
// the change, preventing mutation of the existing state object.
|
||||
newMessages[newMessages.length - 1] = {
|
||||
...lastMessage,
|
||||
count: lastMessage.count + 1,
|
||||
};
|
||||
} else {
|
||||
newMessages.push({ ...queuedMessage, count: 1 });
|
||||
}
|
||||
}
|
||||
return newMessages;
|
||||
}
|
||||
case 'CLEAR':
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function useConsoleMessages(): UseConsoleMessagesReturn {
|
||||
const [consoleMessages, dispatch] = useReducer(consoleMessagesReducer, []);
|
||||
const messageQueueRef = useRef<ConsoleMessageItem[]>([]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
const processQueue = useCallback(() => {
|
||||
if (messageQueueRef.current.length > 0) {
|
||||
const messagesToProcess = messageQueueRef.current;
|
||||
messageQueueRef.current = [];
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'ADD_MESSAGES', payload: messagesToProcess });
|
||||
});
|
||||
}
|
||||
timeoutRef.current = null;
|
||||
}, []);
|
||||
|
||||
const handleNewMessage = useCallback(
|
||||
(message: ConsoleMessageItem) => {
|
||||
messageQueueRef.current.push(message);
|
||||
if (!timeoutRef.current) {
|
||||
// Batch updates using a timeout. 16ms is a reasonable delay to batch
|
||||
// rapid-fire messages without noticeable lag.
|
||||
timeoutRef.current = setTimeout(processQueue, 16);
|
||||
}
|
||||
},
|
||||
[processQueue],
|
||||
);
|
||||
|
||||
const clearConsoleMessages = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
messageQueueRef.current = [];
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'CLEAR' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { consoleMessages, handleNewMessage, clearConsoleMessages };
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from 'react';
|
|||
import * as fs from 'node:fs';
|
||||
import {
|
||||
type Config,
|
||||
createDebugLogger,
|
||||
logUserFeedback,
|
||||
UserFeedbackEvent,
|
||||
type UserFeedbackRating,
|
||||
|
|
@ -24,6 +25,7 @@ const MIN_USER_MESSAGES = 5; // Minimum user messages to show feedback dialog
|
|||
|
||||
// Fatigue mechanism constants
|
||||
const FEEDBACK_COOLDOWN_HOURS = 24; // Hours to wait before showing feedback dialog again
|
||||
const debugLogger = createDebugLogger('FEEDBACK_DIALOG');
|
||||
|
||||
/**
|
||||
* Check if the last message in the conversation history is an AI response
|
||||
|
|
@ -43,7 +45,7 @@ const getFeedbackLastShownTimestampFromFile = (): number => {
|
|||
}
|
||||
} catch (error) {
|
||||
if (isNodeError(error) && error.code !== 'ENOENT') {
|
||||
console.warn(
|
||||
debugLogger.warn(
|
||||
'Failed to read feedbackLastShownTimestamp from settings file:',
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1601,9 +1601,6 @@ describe('useGeminiStream', () => {
|
|||
});
|
||||
|
||||
it('should handle errors gracefully when auto-approving tool calls', async () => {
|
||||
const consoleSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
const mockOnConfirmSuccess = vi.fn().mockResolvedValue(undefined);
|
||||
const mockOnConfirmError = vi
|
||||
.fn()
|
||||
|
|
@ -1673,14 +1670,6 @@ describe('useGeminiStream', () => {
|
|||
// Both confirmation methods should be called
|
||||
expect(mockOnConfirmSuccess).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnConfirmError).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Error should be logged
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Failed to auto-approve tool call call2:',
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should skip tool calls without confirmationDetails', async () => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import type {
|
|||
} from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
GeminiEventType as ServerGeminiEventType,
|
||||
createDebugLogger,
|
||||
getErrorMessage,
|
||||
isNodeError,
|
||||
MessageSenderType,
|
||||
|
|
@ -65,6 +66,8 @@ import { useSessionStats } from '../contexts/SessionContext.js';
|
|||
import { useKeypress } from './useKeypress.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
const debugLogger = createDebugLogger('GEMINI_STREAM');
|
||||
|
||||
enum StreamProcessingStatus {
|
||||
Completed,
|
||||
UserCancelled,
|
||||
|
|
@ -336,7 +339,7 @@ export const useGeminiStream = (
|
|||
|
||||
if (typeof query === 'string') {
|
||||
const trimmedQuery = query.trim();
|
||||
onDebugMessage(`User query: '${trimmedQuery}'`);
|
||||
onDebugMessage(`Received user query (${trimmedQuery.length} chars)`);
|
||||
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||
|
||||
// Handle UI-only commands first
|
||||
|
|
@ -985,7 +988,7 @@ export const useGeminiStream = (
|
|||
if (processingStatus === StreamProcessingStatus.UserCancelled) {
|
||||
// Restore original model if it was temporarily overridden
|
||||
restoreOriginalModel().catch((error) => {
|
||||
console.error('Failed to restore original model:', error);
|
||||
debugLogger.error('Failed to restore original model:', error);
|
||||
});
|
||||
isSubmittingQueryRef.current = false;
|
||||
return;
|
||||
|
|
@ -1002,12 +1005,12 @@ export const useGeminiStream = (
|
|||
|
||||
// Restore original model if it was temporarily overridden
|
||||
restoreOriginalModel().catch((error) => {
|
||||
console.error('Failed to restore original model:', error);
|
||||
debugLogger.error('Failed to restore original model:', error);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
// Restore original model if it was temporarily overridden
|
||||
restoreOriginalModel().catch((error) => {
|
||||
console.error('Failed to restore original model:', error);
|
||||
debugLogger.error('Failed to restore original model:', error);
|
||||
});
|
||||
|
||||
if (error instanceof UnauthorizedError) {
|
||||
|
|
@ -1077,7 +1080,7 @@ export const useGeminiStream = (
|
|||
ToolConfirmationOutcome.ProceedOnce,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`Failed to auto-approve tool call ${call.request.callId}:`,
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -107,8 +107,6 @@ describe('useInputHistoryStore', () => {
|
|||
.mockRejectedValue(new Error('Logger error')),
|
||||
};
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
|
|
@ -116,12 +114,6 @@ describe('useInputHistoryStore', () => {
|
|||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Failed to initialize input history from logger:',
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should initialize only once', async () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
interface Logger {
|
||||
getPreviousUserMessages(): Promise<string[]>;
|
||||
|
|
@ -16,6 +17,8 @@ export interface UseInputHistoryStoreReturn {
|
|||
initializeFromLogger: (logger: Logger | null) => Promise<void>;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('INPUT_HISTORY_STORE');
|
||||
|
||||
/**
|
||||
* Hook for independently managing input history.
|
||||
* Completely separated from chat history and unaffected by /clear commands.
|
||||
|
|
@ -69,7 +72,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);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import type {
|
|||
Status as CoreStatus,
|
||||
EditorType,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { CoreToolScheduler } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
CoreToolScheduler,
|
||||
createDebugLogger,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import type {
|
||||
HistoryItemToolGroup,
|
||||
|
|
@ -28,6 +31,8 @@ import type {
|
|||
} from '../types.js';
|
||||
import { ToolCallStatus } from '../types.js';
|
||||
|
||||
const debugLogger = createDebugLogger('REACT_TOOL_SCHEDULER');
|
||||
|
||||
export type ScheduleFn = (
|
||||
request: ToolCallRequestInfo | ToolCallRequestInfo[],
|
||||
signal: AbortSignal,
|
||||
|
|
@ -198,7 +203,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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { useReducer, useRef, useEffect } from 'react';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
|
||||
export interface SelectionListItem<T> {
|
||||
|
|
@ -22,6 +23,8 @@ export interface UseSelectionListOptions<T> {
|
|||
showNumbers?: boolean;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('SELECTION_LIST');
|
||||
|
||||
export interface UseSelectionListResult {
|
||||
activeIndex: number;
|
||||
setActiveIndex: (index: number) => void;
|
||||
|
|
@ -203,7 +206,7 @@ function selectionListReducer<T>(
|
|||
|
||||
default: {
|
||||
const exhaustiveCheck: never = action;
|
||||
console.error(`Unknown selection list action: ${exhaustiveCheck}`);
|
||||
debugLogger.error(`Unknown selection list action: ${exhaustiveCheck}`);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ vi.mock('@qwen-code/qwen-code-core', () => {
|
|||
return {
|
||||
isNodeError: (err: unknown): err is NodeJS.ErrnoException =>
|
||||
typeof err === 'object' && err !== null && 'code' in err,
|
||||
createDebugLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
Storage,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,9 +7,14 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { isNodeError, Storage } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
createDebugLogger,
|
||||
isNodeError,
|
||||
Storage,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
|
||||
const MAX_HISTORY_LENGTH = 100;
|
||||
const debugLogger = createDebugLogger('SHELL_HISTORY');
|
||||
|
||||
export interface UseShellHistoryReturn {
|
||||
history: string[];
|
||||
|
|
@ -52,7 +57,7 @@ async function readHistoryFile(filePath: string): Promise<string[]> {
|
|||
return result;
|
||||
} catch (err) {
|
||||
if (isNodeError(err) && err.code === 'ENOENT') return [];
|
||||
console.error('Error reading history:', err);
|
||||
debugLogger.error('Error reading history:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +70,7 @@ async function writeHistoryFile(
|
|||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, history.join('\n'));
|
||||
} catch (error) {
|
||||
console.error('Error writing shell history:', error);
|
||||
debugLogger.error('Error writing shell history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import type { Message } from '../types.js';
|
|||
import { MessageType } from '../types.js';
|
||||
import type { Config } from '@qwen-code/qwen-code-core';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('SHOW_MEMORY');
|
||||
|
||||
export function createShowMemoryAction(
|
||||
config: Config | null,
|
||||
|
|
@ -24,11 +27,7 @@ export function createShowMemoryAction(
|
|||
return;
|
||||
}
|
||||
|
||||
const debugMode = config.getDebugMode();
|
||||
|
||||
if (debugMode) {
|
||||
console.log('[DEBUG] Show Memory command invoked.');
|
||||
}
|
||||
debugLogger.debug('[DEBUG] Show Memory command invoked.');
|
||||
|
||||
const currentMemory = config.getUserMemory();
|
||||
const fileCount = config.getGeminiMdFileCount();
|
||||
|
|
@ -37,12 +36,10 @@ export function createShowMemoryAction(
|
|||
? contextFileName
|
||||
: [contextFileName];
|
||||
|
||||
if (debugMode) {
|
||||
console.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.debug(
|
||||
`[DEBUG] Showing memory. Content from config.getUserMemory() (first 200 chars): ${currentMemory.substring(0, 200)}...`,
|
||||
);
|
||||
debugLogger.debug(`[DEBUG] Number of context files loaded: ${fileCount}`);
|
||||
|
||||
if (fileCount > 0) {
|
||||
const allNamesTheSame = new Set(contextFileNames).size < 2;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
import {
|
||||
CommandKind,
|
||||
|
|
@ -29,13 +30,15 @@ interface FzfCommandCacheEntry {
|
|||
commandMap: Map<string, SlashCommand>;
|
||||
}
|
||||
|
||||
const debugLogger = createDebugLogger('SLASH_COMPLETION');
|
||||
|
||||
// Utility function to safely handle errors without information disclosure
|
||||
function logErrorSafely(error: unknown, context: string): void {
|
||||
if (error instanceof Error) {
|
||||
// Log full error details securely for debugging
|
||||
console.error(`[${context}]`, error);
|
||||
debugLogger.error(`[${context}]`, error);
|
||||
} else {
|
||||
console.error(`[${context}] Non-error thrown:`, error);
|
||||
debugLogger.error(`[${context}] Non-error thrown:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +193,7 @@ function useCommandSuggestions(
|
|||
|
||||
// Safety check: ensure leafCommand and completion exist
|
||||
if (!leafCommand?.completion) {
|
||||
console.warn(
|
||||
debugLogger.warn(
|
||||
'Attempted argument completion without completion function',
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ export function useWelcomeBack(
|
|||
}
|
||||
} catch (error) {
|
||||
// Silently ignore errors - welcome back is not critical
|
||||
console.debug('Welcome back check failed:', error);
|
||||
config.getDebugLogger().debug('Welcome back check failed:', error);
|
||||
}
|
||||
}, [settings.ui?.enableWelcomeBack]);
|
||||
}, [config, settings.ui?.enableWelcomeBack]);
|
||||
|
||||
// Handle welcome back dialog selection
|
||||
const handleWelcomeBackSelection = useCallback(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { useCallback, useReducer, useEffect } from 'react';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import type { Key } from './useKeypress.js';
|
||||
import type { TextBuffer } from '../components/shared/text-buffer.js';
|
||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||
|
|
@ -16,6 +17,8 @@ const DIGIT_MULTIPLIER = 10;
|
|||
const DEFAULT_COUNT = 1;
|
||||
const DIGIT_1_TO_9 = /^[1-9]$/;
|
||||
|
||||
const debugLogger = createDebugLogger('VIM_MODE');
|
||||
|
||||
// Command types
|
||||
const CMD_TYPES = {
|
||||
DELETE_WORD_FORWARD: 'dw',
|
||||
|
|
@ -394,7 +397,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ describe('keyMatchers', () => {
|
|||
[Command.OPEN_EXTERNAL_EDITOR]: (key: Key) =>
|
||||
key.ctrl && (key.name === 'x' || key.sequence === '\x18'),
|
||||
[Command.PASTE_CLIPBOARD_IMAGE]: (key: Key) => key.ctrl && key.name === 'v',
|
||||
[Command.SHOW_ERROR_DETAILS]: (key: Key) => key.ctrl && key.name === 'o',
|
||||
[Command.TOGGLE_TOOL_DESCRIPTIONS]: (key: Key) =>
|
||||
key.ctrl && key.name === 't',
|
||||
[Command.TOGGLE_IDE_CONTEXT_DETAIL]: (key: Key) =>
|
||||
|
|
@ -222,11 +221,6 @@ describe('keyMatchers', () => {
|
|||
},
|
||||
|
||||
// App level bindings
|
||||
{
|
||||
command: Command.SHOW_ERROR_DETAILS,
|
||||
positive: [createKey('o', { ctrl: true })],
|
||||
negative: [createKey('o'), createKey('e', { ctrl: true })],
|
||||
},
|
||||
{
|
||||
command: Command.TOGGLE_TOOL_DESCRIPTIONS,
|
||||
positive: [createKey('t', { ctrl: true })],
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import type React from 'react';
|
||||
import { Box } from 'ink';
|
||||
import { Notifications } from '../components/Notifications.js';
|
||||
import { MainContent } from '../components/MainContent.js';
|
||||
import { DialogManager } from '../components/DialogManager.js';
|
||||
import { Composer } from '../components/Composer.js';
|
||||
|
|
@ -23,8 +22,6 @@ export const DefaultAppLayout: React.FC = () => {
|
|||
<MainContent />
|
||||
|
||||
<Box flexDirection="column" ref={uiState.mainControlsRef}>
|
||||
<Notifications />
|
||||
|
||||
{uiState.dialogsVisible ? (
|
||||
<Box marginX={2} flexDirection="column" width={uiState.mainAreaWidth}>
|
||||
<DialogManager
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-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<Record<string, string>> = {
|
||||
|
|
@ -147,6 +149,8 @@ export const CSS_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
|
|||
yellowgreen: '#9acd32',
|
||||
};
|
||||
|
||||
const debugLogger = createDebugLogger('COLOR_UTILS');
|
||||
|
||||
// Define the set of Ink's named colors for quick lookup
|
||||
export const INK_SUPPORTED_NAMES = new Set([
|
||||
'black',
|
||||
|
|
@ -224,7 +228,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;
|
||||
|
|
|
|||
|
|
@ -160,22 +160,14 @@ describe('ThemeManager', () => {
|
|||
expect(themeManager.getActiveTheme().name).toBe(DEFAULT_THEME.name);
|
||||
});
|
||||
|
||||
it('should not load a theme from an untrusted file path and log a message', () => {
|
||||
it('should not load a theme from an untrusted file path', () => {
|
||||
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(mockTheme));
|
||||
const consoleWarnSpy = vi
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const result = themeManager.setActiveTheme('/untrusted/my-theme.json');
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(themeManager.getActiveTheme().name).toBe(DEFAULT_THEME.name);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('is outside your home directory'),
|
||||
);
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ import { ANSI } from './ansi.js';
|
|||
import { ANSILight } from './ansi-light.js';
|
||||
import { NoColorTheme } from './no-color.js';
|
||||
import process from 'node:process';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('THEME_MANAGER');
|
||||
|
||||
export interface ThemeDisplay {
|
||||
name: string;
|
||||
|
|
@ -79,7 +82,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,
|
||||
|
|
@ -92,10 +95,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
|
||||
|
|
@ -260,7 +263,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.`,
|
||||
);
|
||||
|
|
@ -273,14 +276,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.
|
||||
|
|
@ -300,7 +303,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ import {
|
|||
MINIMUM_MAX_HEIGHT,
|
||||
} from '../components/shared/MaxSizedBox.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
// Configure theming and parsing utilities.
|
||||
const lowlight = createLowlight(common);
|
||||
const debugLogger = createDebugLogger('CODE_COLORIZER');
|
||||
|
||||
function renderHastNode(
|
||||
node: Root | Element | HastText | RootContent,
|
||||
|
|
@ -188,7 +190,7 @@ export function colorizeCode(
|
|||
</MaxSizedBox>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
debugLogger.error(
|
||||
`[colorizeCode] Error highlighting code for language "${language}":`,
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import util from 'node:util';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
interface ConsolePatcherParams {
|
||||
onNewMessage?: (message: Omit<ConsoleMessageItem, 'id'>) => void;
|
||||
debugMode: boolean;
|
||||
stderr?: boolean;
|
||||
}
|
||||
|
||||
export class ConsolePatcher {
|
||||
private originalConsoleLog = console.log;
|
||||
private originalConsoleWarn = console.warn;
|
||||
private originalConsoleError = console.error;
|
||||
private originalConsoleDebug = console.debug;
|
||||
private originalConsoleInfo = console.info;
|
||||
|
||||
private params: ConsolePatcherParams;
|
||||
|
||||
constructor(params: ConsolePatcherParams) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
patch() {
|
||||
console.log = this.patchConsoleMethod('log', this.originalConsoleLog);
|
||||
console.warn = this.patchConsoleMethod('warn', this.originalConsoleWarn);
|
||||
console.error = this.patchConsoleMethod('error', this.originalConsoleError);
|
||||
console.debug = this.patchConsoleMethod('debug', this.originalConsoleDebug);
|
||||
console.info = this.patchConsoleMethod('info', this.originalConsoleInfo);
|
||||
}
|
||||
|
||||
cleanup = () => {
|
||||
console.log = this.originalConsoleLog;
|
||||
console.warn = this.originalConsoleWarn;
|
||||
console.error = this.originalConsoleError;
|
||||
console.debug = this.originalConsoleDebug;
|
||||
console.info = this.originalConsoleInfo;
|
||||
};
|
||||
|
||||
private formatArgs = (args: unknown[]): string => util.format(...args);
|
||||
|
||||
private patchConsoleMethod =
|
||||
(
|
||||
type: 'log' | 'warn' | 'error' | 'debug' | 'info',
|
||||
originalMethod: (...args: unknown[]) => void,
|
||||
) =>
|
||||
(...args: unknown[]) => {
|
||||
if (this.params.stderr) {
|
||||
if (type !== 'debug' || this.params.debugMode) {
|
||||
this.originalConsoleError(this.formatArgs(args));
|
||||
}
|
||||
} else {
|
||||
if (this.params.debugMode) {
|
||||
originalMethod.apply(console, args);
|
||||
}
|
||||
|
||||
if (type !== 'debug' || this.params.debugMode) {
|
||||
this.params.onNewMessage?.({
|
||||
type,
|
||||
content: this.formatArgs(args),
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import React from 'react';
|
|||
import { Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import stringWidth from 'string-width';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
// Constants for Markdown parsing
|
||||
const BOLD_MARKER_LENGTH = 2; // For "**"
|
||||
|
|
@ -17,6 +18,8 @@ const INLINE_CODE_MARKER_LENGTH = 1; // For "`"
|
|||
const UNDERLINE_TAG_START_LENGTH = 3; // For "<u>"
|
||||
const UNDERLINE_TAG_END_LENGTH = 4; // For "</u>"
|
||||
|
||||
const debugLogger = createDebugLogger('INLINE_MARKDOWN');
|
||||
|
||||
interface RenderInlineProps {
|
||||
text: string;
|
||||
textColor?: string;
|
||||
|
|
@ -143,7 +146,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({
|
|||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing inline markdown part:', fullMatch, e);
|
||||
debugLogger.error('Error parsing inline markdown part:', fullMatch, e);
|
||||
renderedNode = null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { execCommand } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger, execCommand } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const MACOS_CLIPBOARD_TIMEOUT_MS = 1500;
|
||||
|
||||
const debugLogger = createDebugLogger('CLIPBOARD_UTILS');
|
||||
|
||||
/**
|
||||
* Checks if the system clipboard contains an image (macOS only for now)
|
||||
* @returns true if clipboard contains an image
|
||||
|
|
@ -115,7 +117,7 @@ export async function saveClipboardImage(
|
|||
// No format worked
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error saving clipboard image:', error);
|
||||
debugLogger.error('Error saving clipboard image:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import type { SpawnOptions } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
/**
|
||||
* Common Windows console code pages (CP) used for encoding conversions.
|
||||
|
|
@ -61,6 +62,8 @@ export const isSlashCommand = (query: string): boolean => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const debugLogger = createDebugLogger('COMMAND_UTILS');
|
||||
|
||||
// Copies a string snippet to the clipboard for different platforms
|
||||
export const copyToClipboard = async (text: string): Promise<void> => {
|
||||
const run = (cmd: string, args: string[], options?: SpawnOptions) =>
|
||||
|
|
@ -162,7 +165,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;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import { promisify } from 'node:util';
|
|||
import { isKittyProtocolEnabled } from './kittyProtocolDetector.js';
|
||||
import { VSCODE_SHIFT_ENTER_SEQUENCE } from './platformConstants.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('TERMINAL_SETUP');
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
|
|
@ -96,7 +99,7 @@ async function detectTerminal(): Promise<SupportedTerminal | null> {
|
|||
return 'trae';
|
||||
} catch (error) {
|
||||
// Continue detection even if process check fails
|
||||
console.debug('Parent process detection failed:', error);
|
||||
debugLogger.debug('Parent process detection failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +114,7 @@ async function backupFile(filePath: string): Promise<void> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import type { UpdateInfo } from 'update-notifier';
|
|||
import updateNotifier from 'update-notifier';
|
||||
import semver from 'semver';
|
||||
import { getPackageJson } from '../../utils/package.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const debugLogger = createDebugLogger('UPDATE_CHECK');
|
||||
|
||||
export const FETCH_TIMEOUT_MS = 2000;
|
||||
|
||||
|
|
@ -95,7 +98,7 @@ export async function checkForUpdates(): Promise<UpdateObject | null> {
|
|||
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.warn('Failed to check for updates: ' + e);
|
||||
debugLogger.warn('Failed to check for updates: ' + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue