diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index 2ce94c38b..dfd70cdcd 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -55,8 +55,7 @@ Settings are organized into categories. All settings should be placed within the | ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- | ----------- | | `general.preferredEditor` | string | The preferred editor to open files in. | `undefined` | | `general.vimMode` | boolean | Enable Vim keybindings. | `false` | -| `general.disableAutoUpdate` | boolean | Disable automatic updates. | `false` | -| `general.disableUpdateNag` | boolean | Disable update notification prompts. | `false` | +| `general.enableAutoUpdate` | boolean | Enable automatic update checks and installations on startup. | `true` | | `general.gitCoAuthor` | boolean | Automatically add a Co-authored-by trailer to git commit messages when commits are made through Qwen Code. | `true` | | `general.checkpointing.enabled` | boolean | Enable session checkpointing for recovery. | `false` | @@ -68,18 +67,21 @@ Settings are organized into categories. All settings should be placed within the #### ui -| Setting | Type | Description | Default | -| ---------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `ui.theme` | string | The color theme for the UI. See [Themes](../configuration/themes) for available options. | `undefined` | -| `ui.customThemes` | object | Custom theme definitions. | `{}` | -| `ui.hideWindowTitle` | boolean | Hide the window title bar. | `false` | -| `ui.hideTips` | boolean | Hide helpful tips in the UI. | `false` | -| `ui.showLineNumbers` | boolean | Show line numbers in code blocks in the CLI output. | `true` | -| `ui.showCitations` | boolean | Show citations for generated text in the chat. | `true` | -| `enableWelcomeBack` | boolean | Show welcome back dialog when returning to a project with conversation history. When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/summary` command and quit confirmation dialog. | `true` | -| `ui.accessibility.disableLoadingPhrases` | boolean | Disable loading phrases for accessibility. | `false` | -| `ui.accessibility.screenReader` | boolean | Enables screen reader mode, which adjusts the TUI for better compatibility with screen readers. | `false` | -| `ui.customWittyPhrases` | array of strings | A list of custom phrases to display during loading states. When provided, the CLI will cycle through these phrases instead of the default ones. | `[]` | +| Setting | Type | Description | Default | +| --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `ui.theme` | string | The color theme for the UI. See [Themes](../configuration/themes) for available options. | `undefined` | +| `ui.customThemes` | object | Custom theme definitions. | `{}` | +| `ui.hideWindowTitle` | boolean | Hide the window title bar. | `false` | +| `ui.hideTips` | boolean | Hide helpful tips in the UI. | `false` | +| `ui.hideBanner` | boolean | Hide the application banner. | `false` | +| `ui.hideFooter` | boolean | Hide the footer from the UI. | `false` | +| `ui.showMemoryUsage` | boolean | Display memory usage information in the UI. | `false` | +| `ui.showLineNumbers` | boolean | Show line numbers in code blocks in the CLI output. | `true` | +| `ui.showCitations` | boolean | Show citations for generated text in the chat. | `true` | +| `enableWelcomeBack` | boolean | Show welcome back dialog when returning to a project with conversation history. When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/summary` command and quit confirmation dialog. | `true` | +| `ui.accessibility.enableLoadingPhrases` | boolean | Enable loading phrases (disable for accessibility). | `true` | +| `ui.accessibility.screenReader` | boolean | Enables screen reader mode, which adjusts the TUI for better compatibility with screen readers. | `false` | +| `ui.customWittyPhrases` | array of strings | A list of custom phrases to display during loading states. When provided, the CLI will cycle through these phrases instead of the default ones. | `[]` | #### ide @@ -101,7 +103,7 @@ Settings are organized into categories. All settings should be placed within the | `model.name` | string | The Qwen model to use for conversations. | `undefined` | | `model.maxSessionTurns` | number | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` | | `model.summarizeToolOutput` | object | Enables or disables the summarization of tool output. You can specify the token budget for the summarization using the `tokenBudget` setting. Note: Currently only the `run_shell_command` tool is supported. For example `{"run_shell_command": {"tokenBudget": 2000}}` | `undefined` | -| `model.generationConfig` | object | Advanced overrides passed to the underlying content generator. Supports request controls such as `timeout`, `maxRetries`, `disableCacheControl`, `contextWindowSize` (override model's context window size), `customHeaders` (custom HTTP headers for API requests), and `extra_body` (additional body parameters for OpenAI-compatible API requests only), along with fine-tuning knobs under `samplingParams` (for example `temperature`, `top_p`, `max_tokens`). Leave unset to rely on provider defaults. | `undefined` | +| `model.generationConfig` | object | Advanced overrides passed to the underlying content generator. Supports request controls such as `timeout`, `maxRetries`, `enableCacheControl`, `contextWindowSize` (override model's context window size), `customHeaders` (custom HTTP headers for API requests), and `extra_body` (additional body parameters for OpenAI-compatible API requests only), along with fine-tuning knobs under `samplingParams` (for example `temperature`, `top_p`, `max_tokens`). Leave unset to rely on provider defaults. | `undefined` | | `model.chatCompression.contextPercentageThreshold` | number | Sets the threshold for chat history compression as a percentage of the model's total token limit. This is a value between 0 and 1 that applies to both automatic compression and the manual `/compress` command. For example, a value of `0.6` will trigger compression when the chat history exceeds 60% of the token limit. Use `0` to disable compression entirely. | `0.7` | | `model.skipNextSpeakerCheck` | boolean | Skip the next speaker check. | `false` | | `model.skipLoopDetection` | boolean | Disables loop detection checks. Loop detection prevents infinite loops in AI responses but can generate false positives that interrupt legitimate workflows. Enable this option if you experience frequent false positive loop detection interruptions. | `false` | @@ -116,8 +118,8 @@ Settings are organized into categories. All settings should be placed within the "model": { "generationConfig": { "timeout": 60000, - "disableCacheControl": false, "contextWindowSize": 128000, + "enableCacheControl": true, "customHeaders": { "X-Request-ID": "req-123", "X-User-ID": "user-456" @@ -258,14 +260,14 @@ Per-field precedence for `generationConfig`: | `context.fileFiltering.respectGitIgnore` | boolean | Respect .gitignore files when searching. | `true` | | `context.fileFiltering.respectQwenIgnore` | boolean | Respect .qwenignore files when searching. | `true` | | `context.fileFiltering.enableRecursiveFileSearch` | boolean | Whether to enable searching recursively for filenames under the current tree when completing `@` prefixes in the prompt. | `true` | -| `context.fileFiltering.disableFuzzySearch` | boolean | When `true`, disables the fuzzy search capabilities when searching for files, which can improve performance on projects with a large number of files. | `false` | +| `context.fileFiltering.enableFuzzySearch` | boolean | When `true`, enables fuzzy search capabilities when searching for files. Set to `false` to improve performance on projects with a large number of files. | `true` | #### Troubleshooting File Search Performance If you are experiencing performance issues with file searching (e.g., with `@` completions), especially in projects with a very large number of files, here are a few things you can try in order of recommendation: 1. **Use `.qwenignore`:** Create a `.qwenignore` file in your project root to exclude directories that contain a large number of files that you don't need to reference (e.g., build artifacts, logs, `node_modules`). Reducing the total number of files crawled is the most effective way to improve performance. -2. **Disable Fuzzy Search:** If ignoring files is not enough, you can disable fuzzy search by setting `disableFuzzySearch` to `true` in your `settings.json` file. This will use a simpler, non-fuzzy matching algorithm, which can be faster. +2. **Disable Fuzzy Search:** If ignoring files is not enough, you can disable fuzzy search by setting `enableFuzzySearch` to `false` in your `settings.json` file. This will use a simpler, non-fuzzy matching algorithm, which can be faster. 3. **Disable Recursive File Search:** As a last resort, you can disable recursive file search entirely by setting `enableRecursiveFileSearch` to `false`. This will be the fastest option as it avoids a recursive crawl of your project. However, it means you will need to type the full path to files when using `@` completions. #### tools diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index 0f213acf3..ea41c578c 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -56,7 +56,7 @@ export const DEFAULT_EXCLUDED_ENV_VARS = ['DEBUG', 'DEBUG_MODE']; const MIGRATE_V2_OVERWRITE = true; // Settings version to track migration state -export const SETTINGS_VERSION = 2; +export const SETTINGS_VERSION = 3; export const SETTINGS_VERSION_KEY = '$version'; const MIGRATION_MAP: Record = { @@ -73,8 +73,6 @@ const MIGRATION_MAP: Record = { customThemes: 'ui.customThemes', customWittyPhrases: 'ui.customWittyPhrases', debugKeystrokeLogging: 'general.debugKeystrokeLogging', - disableAutoUpdate: 'general.disableAutoUpdate', - disableUpdateNag: 'general.disableUpdateNag', dnsResolutionOrder: 'advanced.dnsResolutionOrder', enforcedAuthType: 'security.auth.enforcedType', excludeTools: 'tools.exclude', @@ -127,6 +125,28 @@ const MIGRATION_MAP: Record = { visionModelPreview: 'experimental.visionModelPreview', }; +// Settings that need boolean inversion during migration (V1 -> V3) +// Old negative naming -> new positive naming with inverted value +const INVERTED_BOOLEAN_MIGRATIONS: Record = { + disableAutoUpdate: 'general.enableAutoUpdate', + disableUpdateNag: 'general.enableAutoUpdate', + disableLoadingPhrases: 'ui.accessibility.enableLoadingPhrases', + disableFuzzySearch: 'context.fileFiltering.enableFuzzySearch', + disableCacheControl: 'model.generationConfig.enableCacheControl', +}; + +// V2 nested paths that need inversion when migrating to V3 +const INVERTED_V2_PATHS: Record = { + 'general.disableAutoUpdate': 'general.enableAutoUpdate', + 'general.disableUpdateNag': 'general.enableAutoUpdate', + 'ui.accessibility.disableLoadingPhrases': + 'ui.accessibility.enableLoadingPhrases', + 'context.fileFiltering.disableFuzzySearch': + 'context.fileFiltering.enableFuzzySearch', + 'model.generationConfig.disableCacheControl': + 'model.generationConfig.enableCacheControl', +}; + export function getSystemSettingsPath(): string { if (process.env['QWEN_CODE_SYSTEM_SETTINGS_PATH']) { return process.env['QWEN_CODE_SYSTEM_SETTINGS_PATH']; @@ -168,7 +188,7 @@ export interface SummarizeToolOutputSettings { } export interface AccessibilitySettings { - disableLoadingPhrases?: boolean; + enableLoadingPhrases?: boolean; screenReader?: boolean; } @@ -272,6 +292,17 @@ function migrateSettingsToV2( } } + // Handle V1 settings that need boolean inversion (disable* -> enable*) + for (const [oldKey, newPath] of Object.entries(INVERTED_BOOLEAN_MIGRATIONS)) { + if (flatKeys.has(oldKey)) { + const oldValue = flatSettings[oldKey]; + if (typeof oldValue === 'boolean') { + setNestedProperty(v2Settings, newPath, !oldValue); + } + flatKeys.delete(oldKey); + } + } + // Preserve mcpServers at the top level if (flatSettings['mcpServers']) { v2Settings['mcpServers'] = flatSettings['mcpServers']; @@ -310,6 +341,56 @@ function migrateSettingsToV2( return v2Settings; } +// Migrate V2 settings to V3 (invert disable* -> enable* booleans) +function migrateV2ToV3( + settings: Record, +): Record | null { + const version = settings[SETTINGS_VERSION_KEY]; + if (typeof version === 'number' && version >= 3) { + return null; + } + + let changed = false; + const result = structuredClone(settings); + + for (const [oldPath, newPath] of Object.entries(INVERTED_V2_PATHS)) { + const oldValue = getNestedProperty(result, oldPath); + if (typeof oldValue === 'boolean') { + // Remove old property + deleteNestedProperty(result, oldPath); + // Set new property with inverted value + setNestedProperty(result, newPath, !oldValue); + changed = true; + } + } + + if (changed) { + result[SETTINGS_VERSION_KEY] = SETTINGS_VERSION; + return result; + } + + return null; +} + +function deleteNestedProperty( + obj: Record, + path: string, +): void { + const keys = path.split('.'); + const lastKey = keys.pop(); + if (!lastKey) return; + + let current: Record = obj; + for (const key of keys) { + const next = current[key]; + if (typeof next !== 'object' || next === null) { + return; + } + current = next as Record; + } + delete current[lastKey]; +} + function getNestedProperty( obj: Record, path: string, @@ -775,6 +856,33 @@ export function loadSettings( } } } + + // V2 to V3 migration (invert disable* -> enable* booleans) + const v3Migrated = migrateV2ToV3(settingsObject); + if (v3Migrated) { + if (MIGRATE_V2_OVERWRITE) { + try { + // Only backup if not already backed up by V1->V2 migration + const backupPath = `${filePath}.orig`; + if (!fs.existsSync(backupPath)) { + fs.renameSync(filePath, backupPath); + } + fs.writeFileSync( + filePath, + JSON.stringify(v3Migrated, null, 2), + 'utf-8', + ); + } catch (e) { + console.error( + `Error migrating settings file to V3: ${getErrorMessage(e)}`, + ); + } + } else { + migratedInMemorScopes.add(scope); + } + settingsObject = v3Migrated; + } + return { settings: settingsObject as Settings, rawJson: content }; } } catch (error: unknown) { diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index 7d97d5465..fc902234f 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -80,7 +80,7 @@ describe('SettingsSchema', () => { ).toBeDefined(); expect( getSettingsSchema().ui?.properties?.accessibility.properties - ?.disableLoadingPhrases.type, + ?.enableLoadingPhrases.type, ).toBe('boolean'); }); @@ -164,7 +164,7 @@ describe('SettingsSchema', () => { true, ); expect( - getSettingsSchema().general.properties.disableAutoUpdate.showInDialog, + getSettingsSchema().general.properties.enableAutoUpdate.showInDialog, ).toBe(true); expect( getSettingsSchema().ui.properties.hideWindowTitle.showInDialog, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 44340b81e..b1a7f1299 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -143,24 +143,16 @@ const SETTINGS_SCHEMA = { description: 'Enable Vim keybindings', showInDialog: true, }, - disableAutoUpdate: { + enableAutoUpdate: { type: 'boolean', - label: 'Disable Auto Update', + label: 'Enable Auto Update', category: 'General', requiresRestart: false, - default: false, - description: 'Disable automatic updates', + default: true, + description: + 'Enable automatic update checks and installations on startup.', showInDialog: true, }, - disableUpdateNag: { - type: 'boolean', - label: 'Disable Update Nag', - category: 'General', - requiresRestart: false, - default: false, - description: 'Disable update notification prompts.', - showInDialog: false, - }, gitCoAuthor: { type: 'boolean', label: 'Attribution: commit', @@ -382,14 +374,14 @@ const SETTINGS_SCHEMA = { description: 'Accessibility settings.', showInDialog: false, properties: { - disableLoadingPhrases: { + enableLoadingPhrases: { type: 'boolean', - label: 'Disable Loading Phrases', + label: 'Enable Loading Phrases', category: 'UI', requiresRestart: true, - default: false, - description: 'Disable loading phrases for accessibility', - showInDialog: false, + default: true, + description: 'Enable loading phrases (disable for accessibility)', + showInDialog: true, }, screenReader: { type: 'boolean', @@ -609,13 +601,13 @@ const SETTINGS_SCHEMA = { parentKey: 'generationConfig', showInDialog: false, }, - disableCacheControl: { + enableCacheControl: { type: 'boolean', - label: 'Disable Cache Control', + label: 'Enable Cache Control', category: 'Generation Configuration', requiresRestart: false, - default: false, - description: 'Disable cache control for DashScope providers.', + default: true, + description: 'Enable cache control for DashScope providers.', parentKey: 'generationConfig', showInDialog: false, }, @@ -733,14 +725,14 @@ const SETTINGS_SCHEMA = { description: 'Enable recursive file search functionality', showInDialog: false, }, - disableFuzzySearch: { + enableFuzzySearch: { type: 'boolean', - label: 'Disable Fuzzy Search', + label: 'Enable Fuzzy Search', category: 'Context', requiresRestart: true, - default: false, - description: 'Disable fuzzy search when searching for files.', - showInDialog: false, + default: true, + description: 'Enable fuzzy search when searching for files.', + showInDialog: true, }, }, }, diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index 7a1647278..981454934 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -643,16 +643,9 @@ describe('startInteractiveUI', () => { expect(checkForUpdates).toHaveBeenCalledTimes(1); }); - it('should not check for updates when enableAutoUpdate is false', async () => { + it('should not call checkForUpdates when enableAutoUpdate is false', async () => { const { checkForUpdates } = await import('./ui/utils/updateCheck.js'); - const mockInitializationResult = { - authError: null, - themeError: null, - shouldOpenAuthDialog: false, - geminiMdFileCount: 0, - }; - const settingsWithAutoUpdateDisabled = { merged: { general: { @@ -664,6 +657,13 @@ describe('startInteractiveUI', () => { }, } as LoadedSettings; + const mockInitializationResult = { + authError: null, + themeError: null, + shouldOpenAuthDialog: false, + geminiMdFileCount: 0, + }; + await startInteractiveUI( mockConfig, settingsWithAutoUpdateDisabled, @@ -673,6 +673,8 @@ describe('startInteractiveUI', () => { ); await new Promise((resolve) => setTimeout(resolve, 0)); + + // checkForUpdates should NOT be called when enableAutoUpdate is false expect(checkForUpdates).not.toHaveBeenCalled(); }); }); diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 16fea6311..da7f29151 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -182,7 +182,9 @@ export async function startInteractiveUI( }, ); - if (!settings.merged.general?.disableUpdateNag) { + // Check for updates only if enableAutoUpdate is not explicitly disabled. + // Using !== false ensures updates are enabled by default when undefined. + if (settings.merged.general?.enableAutoUpdate !== false) { checkForUpdates() .then((info) => { handleAutoUpdate(info, settings, config.getProjectRoot()); diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 50b04a1d2..30608a961 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -56,14 +56,16 @@ export const Composer = () => { {!uiState.embeddedShellFocused && ( { mockSettings = { merged: { general: { - disableAutoUpdate: false, + enableAutoUpdate: true, }, }, } as LoadedSettings; @@ -94,24 +94,29 @@ describe('handleAutoUpdate', () => { expect(mockSpawn).not.toHaveBeenCalled(); }); - it('should emit "update-received" but not update if auto-updates are disabled', () => { - mockSettings.merged.general!.disableAutoUpdate = true; + it('should show manual update message when enableAutoUpdate is false', () => { + // When enableAutoUpdate is false, gemini.tsx won't call checkForUpdates(), + // but if handleAutoUpdate is still called, it should show a manual update message. + mockSettings.merged.general!.enableAutoUpdate = false; mockGetInstallationInfo.mockReturnValue({ updateCommand: 'npm i -g @qwen-code/qwen-code@latest', - updateMessage: 'Please update manually.', + updateMessage: + 'Please run npm i -g @qwen-code/qwen-code@latest to update', isGlobal: true, packageManager: PackageManager.NPM, }); handleAutoUpdate(mockUpdateInfo, mockSettings, '/root', mockSpawn); - expect(mockUpdateEventEmitter.emit).toHaveBeenCalledTimes(1); + // Should still emit update-received with manual update message expect(mockUpdateEventEmitter.emit).toHaveBeenCalledWith( 'update-received', { - message: 'An update is available!\nPlease update manually.', + message: + 'An update is available!\nPlease run npm i -g @qwen-code/qwen-code@latest to update', }, ); + // Should NOT spawn update when enableAutoUpdate is false expect(mockSpawn).not.toHaveBeenCalled(); }); diff --git a/packages/cli/src/utils/handleAutoUpdate.ts b/packages/cli/src/utils/handleAutoUpdate.ts index 676aa981a..e02c0f615 100644 --- a/packages/cli/src/utils/handleAutoUpdate.ts +++ b/packages/cli/src/utils/handleAutoUpdate.ts @@ -24,9 +24,14 @@ export function handleAutoUpdate( return; } + // enableAutoUpdate is checked in gemini.tsx before calling this function, + // so if we get here, auto-update is enabled (or undefined, which defaults to enabled). + const isAutoUpdateEnabled = + settings.merged.general?.enableAutoUpdate !== false; + const installationInfo = getInstallationInfo( projectRoot, - settings.merged.general?.disableAutoUpdate ?? false, + isAutoUpdateEnabled, ); let combinedMessage = info.message; @@ -38,10 +43,8 @@ export function handleAutoUpdate( message: combinedMessage, }); - if ( - !installationInfo.updateCommand || - settings.merged.general?.disableAutoUpdate - ) { + // Don't automatically run the update if auto-update is disabled or no update command + if (!installationInfo.updateCommand || !isAutoUpdateEnabled) { return; } const isNightly = info.update.latest.includes('nightly'); diff --git a/packages/cli/src/utils/installationInfo.ts b/packages/cli/src/utils/installationInfo.ts index 0805ad218..d910c158d 100644 --- a/packages/cli/src/utils/installationInfo.ts +++ b/packages/cli/src/utils/installationInfo.ts @@ -30,7 +30,7 @@ export interface InstallationInfo { export function getInstallationInfo( projectRoot: string, - isAutoUpdateDisabled: boolean, + isAutoUpdateEnabled: boolean, ): InstallationInfo { const cliPath = process.argv[1]; if (!cliPath) { @@ -99,9 +99,9 @@ export function getInstallationInfo( packageManager: PackageManager.PNPM, isGlobal: true, updateCommand, - updateMessage: isAutoUpdateDisabled - ? `Please run ${updateCommand} to update` - : 'Installed with pnpm. Attempting to automatically update now...', + updateMessage: isAutoUpdateEnabled + ? 'Installed with pnpm. Attempting to automatically update now...' + : `Please run ${updateCommand} to update`, }; } @@ -112,9 +112,9 @@ export function getInstallationInfo( packageManager: PackageManager.YARN, isGlobal: true, updateCommand, - updateMessage: isAutoUpdateDisabled - ? `Please run ${updateCommand} to update` - : 'Installed with yarn. Attempting to automatically update now...', + updateMessage: isAutoUpdateEnabled + ? 'Installed with yarn. Attempting to automatically update now...' + : `Please run ${updateCommand} to update`, }; } @@ -132,9 +132,9 @@ export function getInstallationInfo( packageManager: PackageManager.BUN, isGlobal: true, updateCommand, - updateMessage: isAutoUpdateDisabled - ? `Please run ${updateCommand} to update` - : 'Installed with bun. Attempting to automatically update now...', + updateMessage: isAutoUpdateEnabled + ? 'Installed with bun. Attempting to automatically update now...' + : `Please run ${updateCommand} to update`, }; } @@ -165,9 +165,9 @@ export function getInstallationInfo( packageManager: PackageManager.NPM, isGlobal: true, updateCommand, - updateMessage: isAutoUpdateDisabled - ? `Please run ${updateCommand} to update` - : 'Installed with npm. Attempting to automatically update now...', + updateMessage: isAutoUpdateEnabled + ? 'Installed with npm. Attempting to automatically update now...' + : `Please run ${updateCommand} to update`, }; } catch (error) { console.log(error); diff --git a/packages/cli/src/utils/settingsUtils.test.ts b/packages/cli/src/utils/settingsUtils.test.ts index 109197be3..223e83f0b 100644 --- a/packages/cli/src/utils/settingsUtils.test.ts +++ b/packages/cli/src/utils/settingsUtils.test.ts @@ -121,7 +121,7 @@ describe('SettingsUtils', () => { description: 'Accessibility settings.', showInDialog: false, properties: { - disableLoadingPhrases: { + enableLoadingPhrases: { type: 'boolean', label: 'Disable Loading Phrases', category: 'UI', @@ -285,14 +285,14 @@ describe('SettingsUtils', () => { it('should handle nested settings correctly', () => { const settings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); const mergedSettings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: false } }, + ui: { accessibility: { enableLoadingPhrases: false } }, }); const value = getEffectiveValue( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, mergedSettings, ); @@ -316,7 +316,7 @@ describe('SettingsUtils', () => { it('should return all setting keys', () => { const keys = getAllSettingKeys(); expect(keys).toContain('test'); - expect(keys).toContain('ui.accessibility.disableLoadingPhrases'); + expect(keys).toContain('ui.accessibility.enableLoadingPhrases'); }); }); @@ -343,9 +343,9 @@ describe('SettingsUtils', () => { describe('isValidSettingKey', () => { it('should return true for valid setting keys', () => { expect(isValidSettingKey('ui.requiresRestart')).toBe(true); - expect( - isValidSettingKey('ui.accessibility.disableLoadingPhrases'), - ).toBe(true); + expect(isValidSettingKey('ui.accessibility.enableLoadingPhrases')).toBe( + true, + ); }); it('should return false for invalid setting keys', () => { @@ -358,7 +358,7 @@ describe('SettingsUtils', () => { it('should return correct category for valid settings', () => { expect(getSettingCategory('ui.requiresRestart')).toBe('UI'); expect( - getSettingCategory('ui.accessibility.disableLoadingPhrases'), + getSettingCategory('ui.accessibility.enableLoadingPhrases'), ).toBe('UI'); }); @@ -392,7 +392,7 @@ describe('SettingsUtils', () => { const uiSettings = categories['UI']; const uiKeys = uiSettings.map((s) => s.key); expect(uiKeys).toContain('ui.requiresRestart'); - expect(uiKeys).toContain('ui.accessibility.disableLoadingPhrases'); + expect(uiKeys).toContain('ui.accessibility.enableLoadingPhrases'); expect(uiKeys).not.toContain('ui.theme'); // This is now marked false }); @@ -422,7 +422,7 @@ describe('SettingsUtils', () => { const keys = booleanSettings.map((s) => s.key); expect(keys).toContain('ui.requiresRestart'); - expect(keys).toContain('ui.accessibility.disableLoadingPhrases'); + expect(keys).toContain('ui.accessibility.enableLoadingPhrases'); expect(keys).not.toContain('privacy.usageStatisticsEnabled'); expect(keys).not.toContain('security.auth.selectedType'); // Advanced setting expect(keys).not.toContain('security.auth.useExternal'); // Advanced setting @@ -455,7 +455,7 @@ describe('SettingsUtils', () => { expect(dialogKeys).toContain('ui.requiresRestart'); // Should include nested settings marked for dialog - expect(dialogKeys).toContain('ui.accessibility.disableLoadingPhrases'); + expect(dialogKeys).toContain('ui.accessibility.enableLoadingPhrases'); // Should NOT include settings marked as hidden expect(dialogKeys).not.toContain('ui.theme'); // Hidden @@ -602,14 +602,14 @@ describe('SettingsUtils', () => { it('should return true when value differs from default', () => { expect(isSettingModified('ui.requiresRestart', true)).toBe(true); expect( - isSettingModified('ui.accessibility.disableLoadingPhrases', true), + isSettingModified('ui.accessibility.enableLoadingPhrases', true), ).toBe(true); }); it('should return false when value matches default', () => { expect(isSettingModified('ui.requiresRestart', false)).toBe(false); expect( - isSettingModified('ui.accessibility.disableLoadingPhrases', false), + isSettingModified('ui.accessibility.enableLoadingPhrases', false), ).toBe(false); }); }); @@ -629,11 +629,11 @@ describe('SettingsUtils', () => { it('should return true for nested settings that exist', () => { const settings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); expect( settingExistsInScope( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, ), ).toBe(true); @@ -643,7 +643,7 @@ describe('SettingsUtils', () => { const settings = makeMockSettings({}); expect( settingExistsInScope( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, ), ).toBe(false); @@ -653,7 +653,7 @@ describe('SettingsUtils', () => { const settings = makeMockSettings({ ui: { accessibility: {} } }); expect( settingExistsInScope( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, ), ).toBe(false); @@ -675,25 +675,25 @@ describe('SettingsUtils', () => { it('should set nested setting value', () => { const pendingSettings = makeMockSettings({}); const result = setPendingSettingValue( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', true, pendingSettings, ); - expect(result.ui?.accessibility?.disableLoadingPhrases).toBe(true); + expect(result.ui?.accessibility?.enableLoadingPhrases).toBe(true); }); it('should preserve existing nested settings', () => { const pendingSettings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: false } }, + ui: { accessibility: { enableLoadingPhrases: false } }, }); const result = setPendingSettingValue( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', true, pendingSettings, ); - expect(result.ui?.accessibility?.disableLoadingPhrases).toBe(true); + expect(result.ui?.accessibility?.enableLoadingPhrases).toBe(true); }); it('should not mutate original settings', () => { @@ -1030,7 +1030,7 @@ describe('SettingsUtils', () => { const settings = makeMockSettings({}); // nested setting doesn't exist const result = isDefaultValue( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, ); expect(result).toBe(true); @@ -1038,11 +1038,11 @@ describe('SettingsUtils', () => { it('should return false when nested setting exists in scope', () => { const settings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); // nested setting exists const result = isDefaultValue( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, ); expect(result).toBe(false); @@ -1080,14 +1080,14 @@ describe('SettingsUtils', () => { it('should return false for nested settings that exist in scope', () => { const settings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); const mergedSettings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); const result = isValueInherited( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, mergedSettings, ); @@ -1097,11 +1097,11 @@ describe('SettingsUtils', () => { it('should return true for nested settings that do not exist in scope', () => { const settings = makeMockSettings({}); const mergedSettings = makeMockSettings({ - ui: { accessibility: { disableLoadingPhrases: true } }, + ui: { accessibility: { enableLoadingPhrases: true } }, }); const result = isValueInherited( - 'ui.accessibility.disableLoadingPhrases', + 'ui.accessibility.enableLoadingPhrases', settings, mergedSettings, ); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index af2d28555..cb4da3bca 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -168,7 +168,7 @@ export const APPROVAL_MODE_INFO: Record = { }; export interface AccessibilitySettings { - disableLoadingPhrases?: boolean; + enableLoadingPhrases?: boolean; screenReader?: boolean; } @@ -304,7 +304,7 @@ export interface ConfigParameters { respectGitIgnore?: boolean; respectQwenIgnore?: boolean; enableRecursiveFileSearch?: boolean; - disableFuzzySearch?: boolean; + enableFuzzySearch?: boolean; }; checkpointing?: boolean; proxy?: string; @@ -454,7 +454,7 @@ export class Config { respectGitIgnore: boolean; respectQwenIgnore: boolean; enableRecursiveFileSearch: boolean; - disableFuzzySearch: boolean; + enableFuzzySearch: boolean; }; private fileDiscoveryService: FileDiscoveryService | null = null; private gitService: GitService | undefined = undefined; @@ -572,7 +572,7 @@ export class Config { respectQwenIgnore: params.fileFiltering?.respectQwenIgnore ?? true, enableRecursiveFileSearch: params.fileFiltering?.enableRecursiveFileSearch ?? true, - disableFuzzySearch: params.fileFiltering?.disableFuzzySearch ?? false, + enableFuzzySearch: params.fileFiltering?.enableFuzzySearch ?? true, }; this.checkpointing = params.checkpointing ?? false; this.proxy = params.proxy; @@ -1230,8 +1230,8 @@ export class Config { return this.fileFiltering.enableRecursiveFileSearch; } - getFileFilteringDisableFuzzySearch(): boolean { - return this.fileFiltering.disableFuzzySearch; + getFileFilteringEnableFuzzySearch(): boolean { + return this.fileFiltering.enableFuzzySearch; } getFileFilteringRespectGitIgnore(): boolean { diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 6ac6d9c72..f3af06bda 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -71,7 +71,7 @@ export type ContentGeneratorConfig = { openAILoggingDir?: string; timeout?: number; // Timeout configuration in milliseconds maxRetries?: number; // Maximum retries for failed requests - disableCacheControl?: boolean; // Disable cache control for DashScope providers + enableCacheControl?: boolean; // Enable cache control for DashScope providers samplingParams?: { top_p?: number; top_k?: number; diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts index 9587f3688..a57bbacb7 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts @@ -66,7 +66,7 @@ describe('DashScopeOpenAICompatibleProvider', () => { getCliVersion: vi.fn().mockReturnValue('1.0.0'), getSessionId: vi.fn().mockReturnValue('test-session-id'), getContentGeneratorConfig: vi.fn().mockReturnValue({ - disableCacheControl: false, + enableCacheControl: true, }), getProxy: vi.fn().mockReturnValue(undefined), } as unknown as Config; @@ -500,7 +500,7 @@ describe('DashScopeOpenAICompatibleProvider', () => { > ).mockReturnValue({ model: 'qwen-max', - disableCacheControl: true, + enableCacheControl: false, }); const result = provider.buildRequest(baseRequest, 'test-prompt-id'); diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts index 09d2825a9..ccf201e24 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts @@ -106,8 +106,8 @@ export class DashScopeOpenAICompatibleProvider let messages = request.messages; let tools = request.tools; - // Apply DashScope cache control only if not disabled - if (!this.shouldDisableCacheControl()) { + // Apply DashScope cache control if enabled (default is enabled). + if (this.shouldEnableCacheControl()) { const { messages: updatedMessages, tools: updatedTools } = this.addDashScopeCacheControl( request, @@ -339,11 +339,12 @@ export class DashScopeOpenAICompatibleProvider /** * Check if cache control should be disabled based on configuration. * - * @returns true if cache control should be disabled, false otherwise + * @returns true if cache control should be enabled, false otherwise */ - private shouldDisableCacheControl(): boolean { + private shouldEnableCacheControl(): boolean { + // Cache control is enabled by default (when enableCacheControl is undefined or true). return ( - this.cliConfig.getContentGeneratorConfig()?.disableCacheControl === true + this.cliConfig.getContentGeneratorConfig()?.enableCacheControl !== false ); } } diff --git a/packages/core/src/utils/filesearch/fileSearch.test.ts b/packages/core/src/utils/filesearch/fileSearch.test.ts index cd4a09a8c..854944e0c 100644 --- a/packages/core/src/utils/filesearch/fileSearch.test.ts +++ b/packages/core/src/utils/filesearch/fileSearch.test.ts @@ -32,7 +32,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -58,7 +58,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -86,7 +86,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -115,7 +115,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -148,7 +148,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -172,7 +172,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -207,7 +207,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -237,7 +237,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -267,7 +267,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); // Expect no errors to be thrown during initialization @@ -294,7 +294,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -320,7 +320,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -329,7 +329,7 @@ describe('FileSearch', () => { expect(results).toEqual(['src/style.css']); }); - it('should not use fzf for fuzzy matching when disableFuzzySearch is true', async () => { + it('should not use fzf for fuzzy matching when enableFuzzySearch is false', async () => { tmpDir = await createTmpDir({ src: { 'file1.js': '', @@ -346,7 +346,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: true, + enableFuzzySearch: false, }); await fileSearch.initialize(); @@ -355,7 +355,7 @@ describe('FileSearch', () => { expect(results).toEqual(['src/flexible.js']); }); - it('should use fzf for fuzzy matching when disableFuzzySearch is false', async () => { + it('should use fzf for fuzzy matching when enableFuzzySearch is true', async () => { tmpDir = await createTmpDir({ src: { 'file1.js': '', @@ -372,7 +372,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -396,7 +396,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -427,7 +427,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await expect(fileSearch.search('')).rejects.toThrow( @@ -449,7 +449,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -472,7 +472,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -496,7 +496,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -533,7 +533,7 @@ describe('FileSearch', () => { cache: true, // Enable caching for this test cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -573,7 +573,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -617,7 +617,7 @@ describe('FileSearch', () => { cache: true, // Ensure caching is enabled cacheTtl: 10000, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -655,7 +655,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: true, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -685,7 +685,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: false, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -710,7 +710,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: false, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -735,7 +735,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: false, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); @@ -758,7 +758,7 @@ describe('FileSearch', () => { cache: false, cacheTtl: 0, enableRecursiveFileSearch: false, - disableFuzzySearch: false, + enableFuzzySearch: true, }); await fileSearch.initialize(); diff --git a/packages/core/src/utils/filesearch/fileSearch.ts b/packages/core/src/utils/filesearch/fileSearch.ts index 73169a1fe..9a030b436 100644 --- a/packages/core/src/utils/filesearch/fileSearch.ts +++ b/packages/core/src/utils/filesearch/fileSearch.ts @@ -22,7 +22,7 @@ export interface FileSearchOptions { cache: boolean; cacheTtl: number; enableRecursiveFileSearch: boolean; - disableFuzzySearch: boolean; + enableFuzzySearch: boolean; maxDepth?: number; } @@ -116,9 +116,11 @@ class RecursiveFileSearch implements FileSearch { pattern: string, options: SearchOptions = {}, ): Promise { + // Check if engine is properly initialized. + // If fuzzy search is enabled (or undefined, default true), fzf must be initialized. if ( !this.resultCache || - (!this.fzf && !this.options.disableFuzzySearch) || + (!this.fzf && this.options.enableFuzzySearch !== false) || !this.ignore ) { throw new Error('Engine not initialized. Call initialize() first.'); @@ -179,7 +181,8 @@ class RecursiveFileSearch implements FileSearch { private buildResultCache(): void { this.resultCache = new ResultCache(this.allFiles); - if (!this.options.disableFuzzySearch) { + // Initialize fuzzy search if enabled (or undefined, default true). + if (this.options.enableFuzzySearch !== false) { // The v1 algorithm is much faster since it only looks at the first // occurence of the pattern. We use it for search spaces that have >20k // files, because the v2 algorithm is just too slow in those cases.