/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { MCPServerConfig, BugCommandSettings, TelemetrySettings, AuthType, ChatCompressionSettings, ModelProvidersConfig, } from '@qwen-code/qwen-code-core'; import { ApprovalMode, DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES, DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD, } from '@qwen-code/qwen-code-core'; import type { CustomTheme } from '../ui/themes/theme.js'; import { getLanguageSettingsOptions } from '../i18n/languages.js'; export type SettingsType = | 'boolean' | 'string' | 'number' | 'array' | 'object' | 'enum'; export type SettingsValue = | boolean | string | number | string[] | object | undefined; /** * Setting datatypes that "toggle" through a fixed list of options * (e.g. an enum or true/false) rather than allowing for free form input * (like a number or string). */ export const TOGGLE_TYPES: ReadonlySet = new Set([ 'boolean', 'enum', ]); export interface SettingEnumOption { value: string | number; label: string; } export enum MergeStrategy { // Replace the old value with the new value. This is the default. REPLACE = 'replace', // Concatenate arrays. CONCAT = 'concat', // Merge arrays, ensuring unique values. UNION = 'union', // Shallow merge objects. SHALLOW_MERGE = 'shallow_merge', } export interface SettingDefinition { type: SettingsType; label: string; category: string; requiresRestart: boolean; default: SettingsValue; description?: string; parentKey?: string; key?: string; properties?: SettingsSchema; showInDialog?: boolean; mergeStrategy?: MergeStrategy; /** Enum type options */ options?: readonly SettingEnumOption[]; /** Schema for array items when type is 'array' */ items?: SettingItemDefinition; } /** * Schema definition for array item types. * Supports simple types (string, number, boolean) and complex object types. */ export interface SettingItemDefinition { type: 'string' | 'number' | 'boolean' | 'object' | 'array'; properties?: Record< string, SettingItemDefinition & { required?: boolean; enum?: string[]; additionalProperties?: SettingItemDefinition; } >; items?: SettingItemDefinition; required?: boolean; enum?: string[]; description?: string; additionalProperties?: boolean | SettingItemDefinition; } export interface SettingsSchema { [key: string]: SettingDefinition; } /** * Common items schema for hook definitions. * Used by all hook event types in the hooks configuration. */ const HOOK_DEFINITION_ITEMS: SettingItemDefinition = { type: 'object', description: 'A hook definition with an optional matcher and a list of hook configurations.', properties: { matcher: { type: 'string', description: 'An optional matcher pattern to filter when this hook definition applies.', }, sequential: { type: 'boolean', description: 'Whether the hooks should be executed sequentially instead of in parallel.', }, hooks: { type: 'array', description: 'The list of hook configurations to execute.', required: true, items: { type: 'object', description: 'A hook configuration entry that defines a command to execute.', properties: { type: { type: 'string', description: 'The type of hook.', enum: ['command'], required: true, }, command: { type: 'string', description: 'The command to execute when the hook is triggered.', required: true, }, name: { type: 'string', description: 'An optional name for the hook.', }, description: { type: 'string', description: 'An optional description of what the hook does.', }, timeout: { type: 'number', description: 'Timeout in milliseconds for the hook execution.', }, env: { type: 'object', description: 'Environment variables to set when executing the hook command.', additionalProperties: { type: 'string' }, }, }, }, }, }, }; export type MemoryImportFormat = 'tree' | 'flat'; export type DnsResolutionOrder = 'ipv4first' | 'verbatim'; /** * The canonical schema for all settings. * The structure of this object defines the structure of the `Settings` type. * `as const` is crucial for TypeScript to infer the most specific types possible. */ const SETTINGS_SCHEMA = { // Maintained for compatibility/criticality mcpServers: { type: 'object', label: 'MCP Servers', category: 'Advanced', requiresRestart: true, default: {} as Record, description: 'Configuration for MCP servers.', showInDialog: false, mergeStrategy: MergeStrategy.SHALLOW_MERGE, }, // Model providers configuration grouped by authType modelProviders: { type: 'object', label: 'Model Providers', category: 'Model', requiresRestart: false, default: {} as ModelProvidersConfig, description: 'Model providers configuration grouped by authType. Each authType contains an array of model configurations.', showInDialog: false, mergeStrategy: MergeStrategy.REPLACE, }, // Coding Plan configuration codingPlan: { type: 'object', label: 'Coding Plan', category: 'Model', requiresRestart: false, default: {}, description: 'Coding Plan template version tracking and configuration.', showInDialog: false, properties: { version: { type: 'string', label: 'Coding Plan Template Version', category: 'Model', requiresRestart: false, default: undefined as string | undefined, description: 'SHA256 hash of the Coding Plan template. Used to detect template updates.', showInDialog: false, }, }, }, // Environment variables fallback env: { type: 'object', label: 'Environment Variables', category: 'Advanced', requiresRestart: true, default: {} as Record, description: 'Environment variables to set as fallback defaults. These are loaded with the lowest priority: system environment variables > .env files > settings.json env field.', showInDialog: false, mergeStrategy: MergeStrategy.SHALLOW_MERGE, }, general: { type: 'object', label: 'General', category: 'General', requiresRestart: false, default: {}, description: 'General application settings.', showInDialog: false, properties: { preferredEditor: { type: 'string', label: 'Preferred Editor', category: 'General', requiresRestart: false, default: undefined as string | undefined, description: 'The preferred editor to open files in.', showInDialog: true, }, vimMode: { type: 'boolean', label: 'Vim Mode', category: 'General', requiresRestart: false, default: false, description: 'Enable Vim keybindings', showInDialog: true, }, enableAutoUpdate: { type: 'boolean', label: 'Enable Auto Update', category: 'General', requiresRestart: false, default: true, description: 'Enable automatic update checks and installations on startup.', showInDialog: true, }, gitCoAuthor: { type: 'boolean', label: 'Attribution: commit', category: 'General', requiresRestart: false, default: true, description: 'Automatically add a Co-authored-by trailer to git commit messages when commits are made through Qwen Code.', showInDialog: true, }, checkpointing: { type: 'object', label: 'Checkpointing', category: 'General', requiresRestart: true, default: {}, description: 'Session checkpointing settings.', showInDialog: false, properties: { enabled: { type: 'boolean', label: 'Enable Checkpointing', category: 'General', requiresRestart: true, default: false, description: 'Enable session checkpointing for recovery', showInDialog: false, }, }, }, debugKeystrokeLogging: { type: 'boolean', label: 'Debug Keystroke Logging', category: 'General', requiresRestart: false, default: false, description: 'Enable debug logging of keystrokes to the console.', showInDialog: false, }, language: { type: 'enum', label: 'Language: UI', category: 'General', requiresRestart: true, default: 'auto', description: 'The language for the user interface. Use "auto" to detect from system settings. ' + 'You can also use custom language codes (e.g., "es", "fr") by placing JS language files ' + 'in ~/.qwen/locales/ (e.g., ~/.qwen/locales/es.js).', showInDialog: true, options: [] as readonly SettingEnumOption[], }, outputLanguage: { type: 'string', label: 'Language: Model', category: 'General', requiresRestart: true, default: 'auto', description: 'The language for LLM output. Use "auto" to detect from system settings, ' + 'or set a specific language.', showInDialog: true, }, terminalBell: { type: 'boolean', label: 'Terminal Bell Notification', category: 'General', requiresRestart: false, default: true, description: 'Play terminal bell sound when response completes or needs approval.', showInDialog: true, }, chatRecording: { type: 'boolean', label: 'Chat Recording', category: 'General', requiresRestart: true, default: true, description: 'Enable saving chat history to disk. Disabling this will also prevent --continue and --resume from working.', showInDialog: false, }, defaultFileEncoding: { type: 'enum', label: 'Default File Encoding', category: 'General', requiresRestart: false, default: 'utf-8', description: 'Default encoding for new files. Use "utf-8" (default) for UTF-8 without BOM, or "utf-8-bom" for UTF-8 with BOM. Only change this if your project specifically requires BOM.', showInDialog: false, options: [ { value: 'utf-8', label: 'UTF-8 (without BOM)' }, { value: 'utf-8-bom', label: 'UTF-8 with BOM' }, ], }, }, }, output: { type: 'object', label: 'Output', category: 'General', requiresRestart: false, default: {}, description: 'Settings for the CLI output.', showInDialog: false, properties: { format: { type: 'enum', label: 'Output Format', category: 'General', requiresRestart: false, default: 'text', description: 'The format of the CLI output.', showInDialog: false, options: [ { value: 'text', label: 'Text' }, { value: 'json', label: 'JSON' }, ], }, }, }, ui: { type: 'object', label: 'UI', category: 'UI', requiresRestart: false, default: {}, description: 'User interface settings.', showInDialog: false, properties: { theme: { type: 'string', label: 'Theme', category: 'UI', requiresRestart: false, default: 'Qwen Dark' as string, description: 'The color theme for the UI.', showInDialog: true, }, customThemes: { type: 'object', label: 'Custom Themes', category: 'UI', requiresRestart: false, default: {} as Record, description: 'Custom theme definitions.', showInDialog: false, }, hideWindowTitle: { type: 'boolean', label: 'Hide Window Title', category: 'UI', requiresRestart: true, default: false, description: 'Hide the window title bar', showInDialog: false, }, showStatusInTitle: { type: 'boolean', label: 'Show Status in Title', category: 'UI', requiresRestart: false, default: false, description: 'Show Qwen Code status and thoughts in the terminal window title', showInDialog: false, }, hideTips: { type: 'boolean', label: 'Hide Tips', category: 'UI', requiresRestart: false, default: false, description: 'Hide helpful tips in the UI', showInDialog: true, }, showLineNumbers: { type: 'boolean', label: 'Show Line Numbers in Code', category: 'UI', requiresRestart: false, default: true, description: 'Show line numbers in the code output.', showInDialog: true, }, showCitations: { type: 'boolean', label: 'Show Citations', category: 'UI', requiresRestart: false, default: false, description: 'Show citations for generated text in the chat.', showInDialog: false, }, customWittyPhrases: { type: 'array', label: 'Custom Witty Phrases', category: 'UI', requiresRestart: false, default: [] as string[], description: 'Custom witty phrases to display during loading.', showInDialog: false, }, enableWelcomeBack: { type: 'boolean', label: 'Show Welcome Back Dialog', category: 'UI', requiresRestart: false, default: true, description: 'Show welcome back dialog when returning to a project with conversation history.', showInDialog: true, }, enableUserFeedback: { type: 'boolean', label: 'Enable User Feedback', category: 'UI', requiresRestart: false, default: true, description: 'Show optional feedback dialog after conversations to help improve Qwen performance.', showInDialog: true, }, accessibility: { type: 'object', label: 'Accessibility', category: 'UI', requiresRestart: true, default: {}, description: 'Accessibility settings.', showInDialog: false, properties: { enableLoadingPhrases: { type: 'boolean', label: 'Enable Loading Phrases', category: 'UI', requiresRestart: true, default: true, description: 'Enable loading phrases (disable for accessibility)', showInDialog: true, }, screenReader: { type: 'boolean', label: 'Screen Reader Mode', category: 'UI', requiresRestart: true, default: undefined as boolean | undefined, description: 'Render output in plain-text to be more screen reader accessible', showInDialog: false, }, }, }, feedbackLastShownTimestamp: { type: 'number', label: 'Feedback Last Shown Timestamp', category: 'UI', requiresRestart: false, default: 0, description: 'The last time the feedback dialog was shown.', showInDialog: false, }, }, }, ide: { type: 'object', label: 'IDE', category: 'IDE', requiresRestart: true, default: {}, description: 'IDE integration settings.', showInDialog: false, properties: { enabled: { type: 'boolean', label: 'Auto-connect to IDE', category: 'IDE', requiresRestart: true, default: false, description: 'Enable IDE integration mode', showInDialog: true, }, hasSeenNudge: { type: 'boolean', label: 'Has Seen IDE Integration Nudge', category: 'IDE', requiresRestart: false, default: false, description: 'Whether the user has seen the IDE integration nudge.', showInDialog: false, }, }, }, privacy: { type: 'object', label: 'Privacy', category: 'Privacy', requiresRestart: true, default: {}, description: 'Privacy-related settings.', showInDialog: false, properties: { usageStatisticsEnabled: { type: 'boolean', label: 'Enable Usage Statistics', category: 'Privacy', requiresRestart: true, default: true, description: 'Enable collection of usage statistics', showInDialog: true, }, }, }, telemetry: { type: 'object', label: 'Telemetry', category: 'Advanced', requiresRestart: true, default: undefined as TelemetrySettings | undefined, description: 'Telemetry configuration.', showInDialog: false, }, model: { type: 'object', label: 'Model', category: 'Model', requiresRestart: false, default: {}, description: 'Settings related to the generative model.', showInDialog: false, properties: { name: { type: 'string', label: 'Model', category: 'Model', requiresRestart: false, default: undefined as string | undefined, description: 'The model to use for conversations.', showInDialog: false, }, maxSessionTurns: { type: 'number', label: 'Max Session Turns', category: 'Model', requiresRestart: false, default: -1, description: 'Maximum number of user/model/tool turns to keep in a session. -1 means unlimited.', showInDialog: false, }, chatCompression: { type: 'object', label: 'Chat Compression', category: 'Model', requiresRestart: false, default: undefined as ChatCompressionSettings | undefined, description: 'Chat compression settings.', showInDialog: false, }, sessionTokenLimit: { type: 'number', label: 'Session Token Limit', category: 'Model', requiresRestart: false, default: undefined as number | undefined, description: 'The maximum number of tokens allowed in a session.', showInDialog: false, }, skipNextSpeakerCheck: { type: 'boolean', label: 'Skip Next Speaker Check', category: 'Model', requiresRestart: false, default: true, description: 'Skip the next speaker check.', showInDialog: false, }, skipLoopDetection: { type: 'boolean', label: 'Skip Loop Detection', category: 'Model', requiresRestart: false, default: true, description: 'Disable all loop detection checks (streaming and LLM).', showInDialog: false, }, skipStartupContext: { type: 'boolean', label: 'Skip Startup Context', category: 'Model', requiresRestart: true, default: false, description: 'Avoid sending the workspace startup context at the beginning of each session.', showInDialog: false, }, enableOpenAILogging: { type: 'boolean', label: 'Enable OpenAI Logging', category: 'Model', requiresRestart: false, default: false, description: 'Enable OpenAI logging.', showInDialog: false, }, openAILoggingDir: { type: 'string', label: 'OpenAI Logging Directory', category: 'Model', requiresRestart: false, default: undefined as string | undefined, description: 'Custom directory path for OpenAI API logs. If not specified, defaults to logs/openai in the current working directory.', showInDialog: false, }, generationConfig: { type: 'object', label: 'Generation Configuration', category: 'Model', requiresRestart: false, default: undefined as Record | undefined, description: 'Generation configuration settings.', showInDialog: false, properties: { timeout: { type: 'number', label: 'Timeout', category: 'Generation Configuration', requiresRestart: false, default: undefined as number | undefined, description: 'Request timeout in milliseconds.', parentKey: 'generationConfig', showInDialog: false, }, maxRetries: { type: 'number', label: 'Max Retries', category: 'Generation Configuration', requiresRestart: false, default: undefined as number | undefined, description: 'Maximum number of retries for failed requests.', parentKey: 'generationConfig', showInDialog: false, }, enableCacheControl: { type: 'boolean', label: 'Enable Cache Control', category: 'Generation Configuration', requiresRestart: false, default: true, description: 'Enable cache control for DashScope providers.', parentKey: 'generationConfig', showInDialog: false, }, schemaCompliance: { type: 'enum', label: 'Tool Schema Compliance', category: 'Generation Configuration', requiresRestart: false, default: 'auto', description: 'The compliance mode for tool schemas sent to the model. Use "openapi_30" for strict OpenAPI 3.0 compatibility (e.g., for Gemini).', parentKey: 'generationConfig', showInDialog: false, options: [ { value: 'auto', label: 'Auto (Default)' }, { value: 'openapi_30', label: 'OpenAPI 3.0 Strict' }, ], }, contextWindowSize: { type: 'number', label: 'Context Window Size', category: 'Generation Configuration', requiresRestart: false, default: undefined, description: "Overrides the default context window size for the selected model. Use this setting when a provider's effective context limit differs from Qwen Code's default. This value defines the model's assumed maximum context capacity, not a per-request token limit.", parentKey: 'generationConfig', showInDialog: false, }, }, }, }, }, context: { type: 'object', label: 'Context', category: 'Context', requiresRestart: false, default: {}, description: 'Settings for managing context provided to the model.', showInDialog: false, properties: { fileName: { type: 'object', label: 'Context File Name', category: 'Context', requiresRestart: false, default: undefined as string | string[] | undefined, description: 'The name of the context file.', showInDialog: false, }, importFormat: { type: 'string', label: 'Memory Import Format', category: 'Context', requiresRestart: false, default: undefined as MemoryImportFormat | undefined, description: 'The format to use when importing memory.', showInDialog: false, }, includeDirectories: { type: 'array', label: 'Include Directories', category: 'Context', requiresRestart: false, default: [] as string[], description: 'Additional directories to include in the workspace context. Missing directories will be skipped with a warning.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, }, loadFromIncludeDirectories: { type: 'boolean', label: 'Load Memory From Include Directories', category: 'Context', requiresRestart: false, default: false, description: 'Whether to load memory files from include directories.', showInDialog: false, }, fileFiltering: { type: 'object', label: 'File Filtering', category: 'Context', requiresRestart: true, default: {}, description: 'Settings for git-aware file filtering.', showInDialog: false, properties: { respectGitIgnore: { type: 'boolean', label: 'Respect .gitignore', category: 'Context', requiresRestart: true, default: true, description: 'Respect .gitignore files when searching', showInDialog: true, }, respectQwenIgnore: { type: 'boolean', label: 'Respect .qwenignore', category: 'Context', requiresRestart: true, default: true, description: 'Respect .qwenignore files when searching', showInDialog: true, }, enableRecursiveFileSearch: { type: 'boolean', label: 'Enable Recursive File Search', category: 'Context', requiresRestart: true, default: true, description: 'Enable recursive file search functionality', showInDialog: false, }, enableFuzzySearch: { type: 'boolean', label: 'Enable Fuzzy Search', category: 'Context', requiresRestart: true, default: true, description: 'Enable fuzzy search when searching for files.', showInDialog: true, }, }, }, }, }, permissions: { type: 'object', label: 'Permissions', category: 'Tools', requiresRestart: true, default: {}, description: 'Permission rules controlling tool usage. Rules are evaluated in priority order: deny > ask > allow.', showInDialog: false, properties: { allow: { type: 'array', label: 'Allow Rules', category: 'Tools', requiresRestart: true, default: undefined as string[] | undefined, description: 'Tools or commands that are auto-approved without confirmation. ' + 'Examples: "ShellTool", "Bash(git *)", "ReadFileTool".', showInDialog: false, mergeStrategy: MergeStrategy.UNION, }, ask: { type: 'array', label: 'Ask Rules', category: 'Tools', requiresRestart: true, default: undefined as string[] | undefined, description: 'Tools or commands that always require user confirmation. ' + 'Takes precedence over allow rules.', showInDialog: false, mergeStrategy: MergeStrategy.UNION, }, deny: { type: 'array', label: 'Deny Rules', category: 'Tools', requiresRestart: true, default: undefined as string[] | undefined, description: 'Tools or commands that are always blocked. Highest priority rule. ' + 'Examples: "ShellTool", "Bash(rm -rf *)".', showInDialog: false, mergeStrategy: MergeStrategy.UNION, }, }, }, tools: { type: 'object', label: 'Tools', category: 'Tools', requiresRestart: true, default: {}, description: 'Settings for built-in and custom tools.', showInDialog: false, properties: { sandbox: { type: 'object', label: 'Sandbox', category: 'Tools', requiresRestart: true, default: undefined as boolean | string | undefined, description: 'Sandbox execution environment (can be a boolean or a path string).', showInDialog: false, }, shell: { type: 'object', label: 'Shell', category: 'Tools', requiresRestart: false, default: {}, description: 'Settings for shell execution.', showInDialog: false, properties: { enableInteractiveShell: { type: 'boolean', label: 'Interactive Shell (PTY)', category: 'Tools', requiresRestart: true, default: true, description: 'Use node-pty for an interactive shell experience. Falls back to child_process if PTY is unavailable.', showInDialog: true, }, pager: { type: 'string', label: 'Pager', category: 'Tools', requiresRestart: false, default: 'cat' as string | undefined, description: 'The pager command to use for shell output. Defaults to `cat`.', showInDialog: false, }, showColor: { type: 'boolean', label: 'Show Color', category: 'Tools', requiresRestart: false, default: false, description: 'Show color in shell output.', showInDialog: false, }, }, }, // Legacy tool permission fields – kept for backward compatibility. // Use permissions.{allow,ask,deny} instead. core: { type: 'array', label: 'Core Tools (deprecated)', category: 'Tools', requiresRestart: true, default: undefined as string[] | undefined, description: 'Deprecated. Use permissions.allow instead.', showInDialog: false, }, allowed: { type: 'array', label: 'Allowed Tools (deprecated)', category: 'Advanced', requiresRestart: true, default: undefined as string[] | undefined, description: 'Deprecated. Use permissions.allow instead.', showInDialog: false, }, exclude: { type: 'array', label: 'Exclude Tools (deprecated)', category: 'Tools', requiresRestart: true, default: undefined as string[] | undefined, description: 'Deprecated. Use permissions.deny instead.', showInDialog: false, mergeStrategy: MergeStrategy.UNION, }, approvalMode: { type: 'enum', label: 'Tool Approval Mode', category: 'Tools', requiresRestart: false, default: ApprovalMode.DEFAULT, description: 'Approval mode for tool usage. Controls how tools are approved before execution.', showInDialog: true, options: [ { value: ApprovalMode.PLAN, label: 'Plan' }, { value: ApprovalMode.DEFAULT, label: 'Default' }, { value: ApprovalMode.AUTO_EDIT, label: 'Auto Edit' }, { value: ApprovalMode.YOLO, label: 'YOLO' }, ], }, autoAccept: { type: 'boolean', label: 'Auto Accept', category: 'Tools', requiresRestart: false, default: false, description: 'Automatically accept and execute tool calls that are considered safe (e.g., read-only operations) without explicit user confirmation.', showInDialog: false, }, discoveryCommand: { type: 'string', label: 'Tool Discovery Command', category: 'Tools', requiresRestart: true, default: undefined as string | undefined, description: 'Command to run for tool discovery.', showInDialog: false, }, callCommand: { type: 'string', label: 'Tool Call Command', category: 'Tools', requiresRestart: true, default: undefined as string | undefined, description: 'Command to run for tool calls.', showInDialog: false, }, useRipgrep: { type: 'boolean', label: 'Use Ripgrep', category: 'Tools', requiresRestart: false, default: true, description: 'Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.', showInDialog: false, }, useBuiltinRipgrep: { type: 'boolean', label: 'Use Builtin Ripgrep', category: 'Tools', requiresRestart: false, default: true, description: 'Use the bundled ripgrep binary. When set to false, the system-level "rg" command will be used instead. This setting is only effective when useRipgrep is true.', showInDialog: false, }, truncateToolOutputThreshold: { type: 'number', label: 'Tool Output Truncation Threshold', category: 'General', requiresRestart: true, default: DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD, description: 'Truncate tool output if it is larger than this many characters. Set to -1 to disable.', showInDialog: false, }, truncateToolOutputLines: { type: 'number', label: 'Tool Output Truncation Lines', category: 'General', requiresRestart: true, default: DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES, description: 'The number of lines to keep when truncating tool output.', showInDialog: false, }, }, }, mcp: { type: 'object', label: 'MCP', category: 'MCP', requiresRestart: true, default: {}, description: 'Settings for Model Context Protocol (MCP) servers.', showInDialog: false, properties: { serverCommand: { type: 'string', label: 'MCP Server Command', category: 'MCP', requiresRestart: true, default: undefined as string | undefined, description: 'Command to start an MCP server.', showInDialog: false, }, allowed: { type: 'array', label: 'Allow MCP Servers', category: 'MCP', requiresRestart: true, default: undefined as string[] | undefined, description: 'A list of MCP servers to allow.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, }, excluded: { type: 'array', label: 'Exclude MCP Servers', category: 'MCP', requiresRestart: true, default: undefined as string[] | undefined, description: 'A list of MCP servers to exclude.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, }, }, }, security: { type: 'object', label: 'Security', category: 'Security', requiresRestart: true, default: {}, description: 'Security-related settings.', showInDialog: false, properties: { folderTrust: { type: 'object', label: 'Folder Trust', category: 'Security', requiresRestart: false, default: {}, description: 'Settings for folder trust.', showInDialog: false, properties: { enabled: { type: 'boolean', label: 'Folder Trust', category: 'Security', requiresRestart: true, default: false, description: 'Setting to track whether Folder trust is enabled.', showInDialog: false, }, }, }, auth: { type: 'object', label: 'Authentication', category: 'Security', requiresRestart: true, default: {}, description: 'Authentication settings.', showInDialog: false, properties: { selectedType: { type: 'string', label: 'Selected Auth Type', category: 'Security', requiresRestart: true, default: undefined as AuthType | undefined, description: 'The currently selected authentication type.', showInDialog: false, }, enforcedType: { type: 'string', label: 'Enforced Auth Type', category: 'Advanced', requiresRestart: true, default: undefined as AuthType | undefined, description: 'The required auth type. If this does not match the selected auth type, the user will be prompted to re-authenticate.', showInDialog: false, }, useExternal: { type: 'boolean', label: 'Use External Auth', category: 'Security', requiresRestart: true, default: undefined as boolean | undefined, description: 'Whether to use an external authentication flow.', showInDialog: false, }, apiKey: { type: 'string', label: 'API Key', category: 'Security', requiresRestart: true, default: undefined as string | undefined, description: 'API key for OpenAI compatible authentication.', showInDialog: false, }, baseUrl: { type: 'string', label: 'Base URL', category: 'Security', requiresRestart: true, default: undefined as string | undefined, description: 'Base URL for OpenAI compatible API.', showInDialog: false, }, }, }, }, }, advanced: { type: 'object', label: 'Advanced', category: 'Advanced', requiresRestart: true, default: {}, description: 'Advanced settings for power users.', showInDialog: false, properties: { autoConfigureMemory: { type: 'boolean', label: 'Auto Configure Max Old Space Size', category: 'Advanced', requiresRestart: true, default: false, description: 'Automatically configure Node.js memory limits', showInDialog: false, }, dnsResolutionOrder: { type: 'string', label: 'DNS Resolution Order', category: 'Advanced', requiresRestart: true, default: undefined as DnsResolutionOrder | undefined, description: 'The DNS resolution order.', showInDialog: false, }, excludedEnvVars: { type: 'array', label: 'Excluded Project Environment Variables', category: 'Advanced', requiresRestart: false, default: ['DEBUG', 'DEBUG_MODE'] as string[], description: 'Environment variables to exclude from project context.', showInDialog: false, mergeStrategy: MergeStrategy.UNION, }, bugCommand: { type: 'object', label: 'Bug Command', category: 'Advanced', requiresRestart: false, default: undefined as BugCommandSettings | undefined, description: 'Configuration for the bug report command.', showInDialog: false, }, runtimeOutputDir: { type: 'string', label: 'Runtime Output Directory', category: 'Advanced', requiresRestart: true, default: undefined as string | undefined, description: 'Custom directory for runtime output (temp files, debug logs, session data, todos, etc.). ' + 'Config files remain at ~/.qwen. Env var QWEN_RUNTIME_DIR takes priority.', showInDialog: false, }, tavilyApiKey: { type: 'string', label: 'Tavily API Key (Deprecated)', category: 'Advanced', requiresRestart: false, default: undefined as string | undefined, description: '⚠️ DEPRECATED: Please use webSearch.provider configuration instead. Legacy API key for the Tavily API.', showInDialog: false, }, }, }, webSearch: { type: 'object', label: 'Web Search', category: 'Advanced', requiresRestart: true, default: undefined as | { provider: Array<{ type: 'tavily' | 'google' | 'dashscope'; apiKey?: string; searchEngineId?: string; }>; default: string; } | undefined, description: 'Configuration for web search providers.', showInDialog: false, }, agents: { type: 'object', label: 'Agents', category: 'Advanced', requiresRestart: false, default: {}, description: 'Settings for multi-agent collaboration features (Arena, Team, Swarm).', showInDialog: false, properties: { displayMode: { type: 'enum', label: 'Display Mode', category: 'Advanced', requiresRestart: false, default: undefined as string | undefined, description: 'Display mode for multi-agent sessions. Currently only "in-process" is supported.', showInDialog: false, options: [ { value: 'in-process', label: 'In-process' }, // { value: 'tmux', label: 'tmux' }, // { value: 'iterm2', label: 'iTerm2' }, ], }, arena: { type: 'object', label: 'Arena', category: 'Advanced', requiresRestart: false, default: {}, description: 'Settings for Arena (multi-model competitive execution).', showInDialog: false, properties: { worktreeBaseDir: { type: 'string', label: 'Worktree Base Directory', category: 'Advanced', requiresRestart: true, default: undefined as string | undefined, description: 'Custom base directory for Arena worktrees. Defaults to ~/.qwen/arena.', showInDialog: false, }, preserveArtifacts: { type: 'boolean', label: 'Preserve Arena Artifacts', category: 'Advanced', requiresRestart: false, default: false, description: 'When enabled, Arena worktrees and session state files are preserved after the session ends or the main agent exits.', showInDialog: true, }, maxRoundsPerAgent: { type: 'number', label: 'Max Rounds Per Agent', category: 'Advanced', requiresRestart: false, default: undefined as number | undefined, description: 'Maximum number of rounds (turns) each agent can execute. No limit if unset.', showInDialog: false, }, timeoutSeconds: { type: 'number', label: 'Timeout (seconds)', category: 'Advanced', requiresRestart: false, default: undefined as number | undefined, description: 'Total timeout in seconds for the Arena session. No limit if unset.', showInDialog: false, }, }, }, team: { type: 'object', label: 'Team', category: 'Advanced', requiresRestart: false, default: {}, description: 'Settings for Agent Team (role-based collaborative execution). Reserved for future use.', showInDialog: false, }, swarm: { type: 'object', label: 'Swarm', category: 'Advanced', requiresRestart: false, default: {}, description: 'Settings for Agent Swarm (parallel sub-agent execution). Reserved for future use.', showInDialog: false, }, }, }, disableAllHooks: { type: 'boolean', label: 'Disable All Hooks', category: 'Advanced', requiresRestart: true, // Future enhancement: consider supporting mid-session toggle for better UX default: false, description: 'Temporarily disable all hooks without deleting configurations. Default is false (hooks enabled).', showInDialog: false, }, hooks: { type: 'object', label: 'Hooks', category: 'Advanced', requiresRestart: false, default: {}, description: 'Hook event configurations for extending CLI behavior at various lifecycle points.', showInDialog: false, properties: { UserPromptSubmit: { type: 'array', label: 'Before Agent Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute before agent processing. Can modify prompts or inject context.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, Stop: { type: 'array', label: 'After Agent Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute after agent processing. Can post-process responses or log interactions.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, Notification: { type: 'array', label: 'Notification Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when notifications are sent.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, PreToolUse: { type: 'array', label: 'Pre Tool Use Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute before tool execution.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, PostToolUse: { type: 'array', label: 'Post Tool Use Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute after successful tool execution.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, PostToolUseFailure: { type: 'array', label: 'Post Tool Use Failure Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when tool execution fails. ', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, SessionStart: { type: 'array', label: 'Session Start Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when a new session starts or resumes.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, SessionEnd: { type: 'array', label: 'Session End Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when a session ends.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, PreCompact: { type: 'array', label: 'Pre Compact Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute before conversation compaction.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, SubagentStart: { type: 'array', label: 'Subagent Start Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when a subagent (Task tool call) is started.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, SubagentStop: { type: 'array', label: 'Subagent Stop Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute right before a subagent (Task tool call) concludes its response.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, PermissionRequest: { type: 'array', label: 'Permission Request Hooks', category: 'Advanced', requiresRestart: false, default: [], description: 'Hooks that execute when a permission dialog is displayed.', showInDialog: false, mergeStrategy: MergeStrategy.CONCAT, items: HOOK_DEFINITION_ITEMS, }, }, }, experimental: { type: 'object', label: 'Experimental', category: 'Experimental', requiresRestart: true, default: {}, description: 'Setting to enable experimental features', showInDialog: false, properties: {}, }, } as const satisfies SettingsSchema; export type SettingsSchemaType = typeof SETTINGS_SCHEMA; export function getSettingsSchema(): SettingsSchemaType { // Inject dynamic language options const schema = SETTINGS_SCHEMA as unknown as SettingsSchema; if (schema['general']?.properties?.['language']) { ( schema['general'].properties['language'] as { options?: SettingEnumOption[]; } ).options = getLanguageSettingsOptions(); } return SETTINGS_SCHEMA; } type InferSettings = { -readonly [K in keyof T]?: T[K] extends { properties: SettingsSchema } ? InferSettings : T[K]['type'] extends 'enum' ? T[K]['options'] extends readonly SettingEnumOption[] ? T[K]['options'][number]['value'] : T[K]['default'] : T[K]['default'] extends boolean ? boolean : T[K]['default']; }; export type Settings = InferSettings;