diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 7a545fb351..ea7d5c9f8d 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -90,16 +90,17 @@ they appear in the UI. ### Tools -| UI Label | Setting | Description | Default | -| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` | -| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` | -| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` | -| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` | -| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` | -| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `4000000` | -| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `1000` | -| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` | +| UI Label | Setting | Description | Default | +| -------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` | +| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` | +| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` | +| Approval Mode | `tools.approvalMode` | The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. 'yolo' is not supported yet. | `"default"` | +| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` | +| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` | +| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `4000000` | +| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `1000` | +| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` | ### Security diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 726292160e..9dc13a10d2 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -650,6 +650,13 @@ their corresponding top-level category object in your `settings.json` file. considered safe (e.g., read-only operations). - **Default:** `false` +- **`tools.approvalMode`** (enum): + - **Description:** The default approval mode for tool execution. 'default' + prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is + read-only mode. 'yolo' is not supported yet. + - **Default:** `"default"` + - **Values:** `"default"`, `"auto_edit"`, `"plan"` + - **`tools.core`** (array): - **Description:** Restrict the set of built-in tools with an allowlist. Match semantics mirror tools.allowed; see the built-in tools documentation for diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 193914ef88..b93496262c 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -2233,6 +2233,19 @@ describe('loadCliConfig approval mode', () => { expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); }); + it('should ignore "yolo" in settings.tools.approvalMode and fall back to DEFAULT', async () => { + process.argv = ['node', 'script.js']; + const settings = createTestMergedSettings({ + tools: { + // @ts-expect-error: testing invalid value + approvalMode: 'yolo', + }, + }); + const argv = await parseArguments(settings); + const config = await loadCliConfig(settings, 'test-session', argv); + expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + }); + it('should throw error when --approval-mode=plan is used but experimental.plan is disabled', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'plan']; const argv = await parseArguments(createTestMergedSettings()); @@ -2310,6 +2323,67 @@ describe('loadCliConfig approval mode', () => { expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); }); + + describe('Persistent approvalMode setting', () => { + it('should use approvalMode from settings when no CLI flags are set', async () => { + process.argv = ['node', 'script.js']; + const settings = createTestMergedSettings({ + tools: { approvalMode: 'auto_edit' }, + }); + const argv = await parseArguments(settings); + const config = await loadCliConfig(settings, 'test-session', argv); + expect(config.getApprovalMode()).toBe( + ServerConfig.ApprovalMode.AUTO_EDIT, + ); + }); + + it('should prioritize --approval-mode flag over settings', async () => { + process.argv = ['node', 'script.js', '--approval-mode', 'auto_edit']; + const settings = createTestMergedSettings({ + tools: { approvalMode: 'default' }, + }); + const argv = await parseArguments(settings); + const config = await loadCliConfig(settings, 'test-session', argv); + expect(config.getApprovalMode()).toBe( + ServerConfig.ApprovalMode.AUTO_EDIT, + ); + }); + + it('should prioritize --yolo flag over settings', async () => { + process.argv = ['node', 'script.js', '--yolo']; + const settings = createTestMergedSettings({ + tools: { approvalMode: 'auto_edit' }, + }); + const argv = await parseArguments(settings); + const config = await loadCliConfig(settings, 'test-session', argv); + expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + }); + + it('should respect plan mode from settings when experimental.plan is enabled', async () => { + process.argv = ['node', 'script.js']; + const settings = createTestMergedSettings({ + tools: { approvalMode: 'plan' }, + experimental: { plan: true }, + }); + const argv = await parseArguments(settings); + const config = await loadCliConfig(settings, 'test-session', argv); + expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); + }); + + it('should throw error if plan mode is in settings but experimental.plan is disabled', async () => { + process.argv = ['node', 'script.js']; + const settings = createTestMergedSettings({ + tools: { approvalMode: 'plan' }, + experimental: { plan: false }, + }); + const argv = await parseArguments(settings); + await expect( + loadCliConfig(settings, 'test-session', argv), + ).rejects.toThrow( + 'Approval mode "plan" is only available when experimental.plan is enabled.', + ); + }); + }); }); describe('loadCliConfig fileFiltering', () => { diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 59147c210f..9b00f0ea33 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -502,9 +502,15 @@ export async function loadCliConfig( // Determine approval mode with backward compatibility let approvalMode: ApprovalMode; - if (argv.approvalMode) { - // New --approval-mode flag takes precedence - switch (argv.approvalMode) { + const rawApprovalMode = + argv.approvalMode || + (argv.yolo ? 'yolo' : undefined) || + ((settings.tools?.approvalMode as string) !== 'yolo' + ? settings.tools.approvalMode + : undefined); + + if (rawApprovalMode) { + switch (rawApprovalMode) { case 'yolo': approvalMode = ApprovalMode.YOLO; break; @@ -524,13 +530,11 @@ export async function loadCliConfig( break; default: throw new Error( - `Invalid approval mode: ${argv.approvalMode}. Valid values are: yolo, auto_edit, plan, default`, + `Invalid approval mode: ${rawApprovalMode}. Valid values are: yolo, auto_edit, plan, default`, ); } } else { - // Fallback to legacy --yolo flag behavior - approvalMode = - argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT; + approvalMode = ApprovalMode.DEFAULT; } // Override approval mode if disableYoloMode is set. diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 89e75f32f9..fbd72cec36 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1023,6 +1023,24 @@ const SETTINGS_SCHEMA = { `, showInDialog: true, }, + approvalMode: { + type: 'enum', + label: 'Approval Mode', + category: 'Tools', + requiresRestart: false, + default: 'default', + description: oneLine` + The default approval mode for tool execution. + 'default' prompts for approval, 'auto_edit' auto-approves edit tools, + and 'plan' is read-only mode. 'yolo' is not supported yet. + `, + showInDialog: true, + options: [ + { value: 'default', label: 'Default' }, + { value: 'auto_edit', label: 'Auto Edit' }, + { value: 'plan', label: 'Plan' }, + ], + }, core: { type: 'array', label: 'Core Tools', diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index c14ac0a19e..e0dedc461b 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -1095,6 +1095,14 @@ "default": false, "type": "boolean" }, + "approvalMode": { + "title": "Approval Mode", + "description": "The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. 'yolo' is not supported yet.", + "markdownDescription": "The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. 'yolo' is not supported yet.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `default`", + "default": "default", + "type": "string", + "enum": ["default", "auto_edit", "plan"] + }, "core": { "title": "Core Tools", "description": "Restrict the set of built-in tools with an allowlist. Match semantics mirror tools.allowed; see the built-in tools documentation for available names.",