mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-23 20:40:41 -07:00
feat(plan): add experimental 'plan' approval mode (#16753)
This commit is contained in:
@@ -1346,6 +1346,10 @@ for that specific session.
|
||||
- `auto_edit`: Automatically approve edit tools (replace, write_file) while
|
||||
prompting for others
|
||||
- `yolo`: Automatically approve all tool calls (equivalent to `--yolo`)
|
||||
- `plan`: Read-only mode for tool calls (requires experimental planning to
|
||||
be enabled).
|
||||
> **Note:** This mode is currently under development and not yet fully
|
||||
> functional.
|
||||
- Cannot be used together with `--yolo`. Use `--approval-mode=yolo` instead of
|
||||
`--yolo` for the new unified approach.
|
||||
- Example: `gemini --approval-mode auto_edit`
|
||||
|
||||
@@ -1011,6 +1011,30 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME);
|
||||
});
|
||||
|
||||
it('should exclude all interactive tools in non-interactive mode with plan approval mode', async () => {
|
||||
process.argv = [
|
||||
'node',
|
||||
'script.js',
|
||||
'--approval-mode',
|
||||
'plan',
|
||||
'-p',
|
||||
'test',
|
||||
];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
|
||||
const excludedTools = config.getExcludeTools();
|
||||
expect(excludedTools).toContain(SHELL_TOOL_NAME);
|
||||
expect(excludedTools).toContain(EDIT_TOOL_NAME);
|
||||
expect(excludedTools).toContain(WRITE_FILE_TOOL_NAME);
|
||||
});
|
||||
|
||||
it('should exclude no interactive tools in non-interactive mode with legacy yolo flag', async () => {
|
||||
process.argv = ['node', 'script.js', '--yolo', '-p', 'test'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
@@ -1099,7 +1123,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
await expect(
|
||||
loadCliConfig(settings, 'test-session', invalidArgv as CliArgs),
|
||||
).rejects.toThrow(
|
||||
'Invalid approval mode: invalid_mode. Valid values are: yolo, auto_edit, default',
|
||||
'Invalid approval mode: invalid_mode. Valid values are: yolo, auto_edit, plan, default',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2052,6 +2076,42 @@ describe('loadCliConfig approval mode', () => {
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO);
|
||||
});
|
||||
|
||||
it('should set Plan approval mode when --approval-mode=plan is used and experimental.plan is enabled', async () => {
|
||||
process.argv = ['node', 'script.js', '--approval-mode', 'plan'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN);
|
||||
});
|
||||
|
||||
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());
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: false,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when --approval-mode=plan is used but experimental.plan setting is missing', async () => {
|
||||
process.argv = ['node', 'script.js', '--approval-mode', 'plan'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({});
|
||||
|
||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled.',
|
||||
);
|
||||
});
|
||||
|
||||
// --- Untrusted Folder Scenarios ---
|
||||
describe('when folder is NOT trusted', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -143,9 +143,9 @@ export async function parseArguments(
|
||||
.option('approval-mode', {
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
choices: ['default', 'auto_edit', 'yolo'],
|
||||
choices: ['default', 'auto_edit', 'yolo', 'plan'],
|
||||
description:
|
||||
'Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools)',
|
||||
'Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools), plan (read-only mode)',
|
||||
})
|
||||
.option('experimental-acp', {
|
||||
type: 'boolean',
|
||||
@@ -492,12 +492,20 @@ export async function loadCliConfig(
|
||||
case 'auto_edit':
|
||||
approvalMode = ApprovalMode.AUTO_EDIT;
|
||||
break;
|
||||
case 'plan':
|
||||
if (!(settings.experimental?.plan ?? false)) {
|
||||
throw new Error(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled.',
|
||||
);
|
||||
}
|
||||
approvalMode = ApprovalMode.PLAN;
|
||||
break;
|
||||
case 'default':
|
||||
approvalMode = ApprovalMode.DEFAULT;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid approval mode: ${argv.approvalMode}. Valid values are: yolo, auto_edit, default`,
|
||||
`Invalid approval mode: ${argv.approvalMode}. Valid values are: yolo, auto_edit, plan, default`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -578,6 +586,11 @@ export async function loadCliConfig(
|
||||
);
|
||||
|
||||
switch (approvalMode) {
|
||||
case ApprovalMode.PLAN:
|
||||
// In plan non-interactive mode, all tools that require approval are excluded.
|
||||
// TODO(#16625): Replace this default exclusion logic with specific rules for plan mode.
|
||||
extraExcludes.push(...defaultExcludes.filter(toolExclusionFilter));
|
||||
break;
|
||||
case ApprovalMode.DEFAULT:
|
||||
// In default non-interactive mode, all tools that require approval are excluded.
|
||||
extraExcludes.push(...defaultExcludes.filter(toolExclusionFilter));
|
||||
|
||||
@@ -46,6 +46,7 @@ export enum ApprovalMode {
|
||||
DEFAULT = 'default',
|
||||
AUTO_EDIT = 'autoEdit',
|
||||
YOLO = 'yolo',
|
||||
PLAN = 'plan',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user