mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 22:51:00 -07:00
feat(plan): enforce read-only constraints in Plan Mode (#19433)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Jerop Kipruto <jerop@google.com>
This commit is contained in:
@@ -55,3 +55,11 @@ decision = "allow"
|
||||
priority = 70
|
||||
modes = ["plan"]
|
||||
argsPattern = "\"file_path\":\"[^\"]+/\\.gemini/tmp/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/plans/[a-zA-Z0-9_-]+\\.md\""
|
||||
|
||||
# Explicitly Deny other write operations in Plan mode with a clear message.
|
||||
[[rule]]
|
||||
toolName = ["write_file", "edit"]
|
||||
decision = "deny"
|
||||
priority = 65
|
||||
modes = ["plan"]
|
||||
deny_message = "You are in Plan Mode and cannot modify source code. You may ONLY use write_file or replace to save plans to the designated plans directory as .md files."
|
||||
|
||||
@@ -460,7 +460,7 @@ ${options.planModeToolsList}
|
||||
</available_tools>
|
||||
|
||||
## Rules
|
||||
1. **Read-Only:** You cannot modify source code. You may ONLY use read-only tools to explore, and you can only write to \`${options.plansDir}/\`.
|
||||
1. **Read-Only:** You cannot modify source code. You may ONLY use read-only tools to explore, and you can only write to \`${options.plansDir}/\`. If the user asks you to modify source code directly, you MUST explain that you are in Plan Mode and must first create a detailed plan in the plans directory and get approval before any source code changes can be made.
|
||||
2. **Efficiency:** Autonomously combine discovery and drafting phases to minimize conversational turns. If the request is ambiguous, use ${formatToolName(ASK_USER_TOOL_NAME)} to clarify. Otherwise, explore the codebase and write the draft in one fluid motion.
|
||||
3. **Inquiries and Directives:** Distinguish between Inquiries and Directives to minimize unnecessary planning.
|
||||
- **Inquiries:** If the request is an **Inquiry** (e.g., "How does X work?"), use read-only tools to explore and answer directly in your chat response. DO NOT create a plan or call ${formatToolName(
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
} from './tools.js';
|
||||
import { Kind, BaseDeclarativeTool, BaseToolInvocation } from './tools.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { ApprovalMode } from '../policy/types.js';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { StringDecoder } from 'node:string_decoder';
|
||||
import { DiscoveredMCPTool } from './mcp-tool.js';
|
||||
@@ -25,6 +26,9 @@ import {
|
||||
DISCOVERED_TOOL_PREFIX,
|
||||
TOOL_LEGACY_ALIASES,
|
||||
getToolAliases,
|
||||
PLAN_MODE_TOOLS,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
} from './tool-names.js';
|
||||
|
||||
type ToolParams = Record<string, unknown>;
|
||||
@@ -484,6 +488,31 @@ export class ToolRegistry {
|
||||
excludeTools ??=
|
||||
this.expandExcludeToolsWithAliases(this.config.getExcludeTools()) ??
|
||||
new Set([]);
|
||||
|
||||
// Filter tools in Plan Mode to only allow approved read-only tools.
|
||||
const isPlanMode =
|
||||
typeof this.config.getApprovalMode === 'function' &&
|
||||
this.config.getApprovalMode() === ApprovalMode.PLAN;
|
||||
if (isPlanMode) {
|
||||
const allowedToolNames = new Set<string>(PLAN_MODE_TOOLS);
|
||||
// We allow write_file and replace for writing plans specifically.
|
||||
allowedToolNames.add(WRITE_FILE_TOOL_NAME);
|
||||
allowedToolNames.add(EDIT_TOOL_NAME);
|
||||
|
||||
// Discovered MCP tools are allowed if they are read-only.
|
||||
if (
|
||||
tool instanceof DiscoveredMCPTool &&
|
||||
tool.isReadOnly &&
|
||||
!allowedToolNames.has(tool.name)
|
||||
) {
|
||||
allowedToolNames.add(tool.name);
|
||||
}
|
||||
|
||||
if (!allowedToolNames.has(tool.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedClassName = tool.constructor.name.replace(/^_+/, '');
|
||||
const possibleNames = [tool.name, normalizedClassName];
|
||||
if (tool instanceof DiscoveredMCPTool) {
|
||||
@@ -507,9 +536,22 @@ export class ToolRegistry {
|
||||
* @returns An array of FunctionDeclarations.
|
||||
*/
|
||||
getFunctionDeclarations(modelId?: string): FunctionDeclaration[] {
|
||||
const isPlanMode = this.config.getApprovalMode() === ApprovalMode.PLAN;
|
||||
const plansDir = this.config.storage.getPlansDir();
|
||||
|
||||
const declarations: FunctionDeclaration[] = [];
|
||||
this.getActiveTools().forEach((tool) => {
|
||||
declarations.push(tool.getSchema(modelId));
|
||||
let schema = tool.getSchema(modelId);
|
||||
if (
|
||||
isPlanMode &&
|
||||
(tool.name === WRITE_FILE_TOOL_NAME || tool.name === EDIT_TOOL_NAME)
|
||||
) {
|
||||
schema = {
|
||||
...schema,
|
||||
description: `ONLY FOR PLANS: ${schema.description}. You are currently in Plan Mode and may ONLY use this tool to write or update plans (.md files) in the plans directory: ${plansDir}/. You cannot use this tool to modify source code directly.`,
|
||||
};
|
||||
}
|
||||
declarations.push(schema);
|
||||
});
|
||||
return declarations;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user