首页
/ Gemini CLI 规划工具体系:enter_plan_mode 与 exit_plan_mode 如何驱动"先规划、后实施"的工作流

Gemini CLI 规划工具体系:enter_plan_mode 与 exit_plan_mode 如何驱动"先规划、后实施"的工作流

2026-09-06 14:03:27作者:胡唯隽

本文基于 Gemini CLI 仓库中的 planning 工具文档 与对应源码,深入解析规划(Planning)工具的设计与实现:enter_plan_mode 如何让 Agent 安全地切入只读"Plan Mode"进行调研与规划,exit_plan_mode 如何把最终计划提交给用户正式审批并切换回实施模式。读完后,你将掌握这两个工具的参数定义、确认(confirmation)流程、计划文件的路径校验机制、审批模式(Approval Mode)切换逻辑,以及背后的路径安全设计与测试验证方式。

1. 规划工具的定位:为什么需要 Plan Mode

复杂改动往往需要先充分调研、再动手修改。Gemini CLI 的规划工具(Planning tools)让 Gemini 能够:

  1. 切换到一个安全的只读"Plan Mode",在该模式下 Agent 只被允许使用只读类工具,用于安全地探索代码库、梳理改动方案;
  2. 在规划完成后,向用户发出"计划定稿"信号,把最终计划呈现给用户并请求正式批准,批准后才进入实施阶段。

这一机制的核心价值是"读写分离":规划阶段绝不产生副作用,实施阶段则基于用户已审阅的书面计划进行,显著降低了 Agent 在不确定的情况下直接改动代码的风险。规划工具由两个工具组成,源码分别位于 enter-plan-mode.tsexit-plan-mode.ts

2. enter_plan_mode:进入只读规划模式

2.1 工具定义与参数

项目 内容
工具名 enter_plan_mode
显示名 Enter Plan Mode
源码文件 packages/core/src/tools/enter-plan-mode.ts
工具分类 Kind.Plan
参数 reason(string,可选):进入计划模式的简短原因,例如 "Starting a complex feature implementation"
是否需要确认 是。用户会看到确认提示:"This will restrict the agent to read-only tools to allow for safe planning."

该工具通常由 Agent 在用户以自然语言要求"先制定一个计划"时调用。从 tool-names.ts 中的常量 ENTER_PLAN_MODE_TOOL_NAME 可知,工具名在多处(注册表、策略引擎、测试)统一引用,避免硬编码漂移。

注意(来自官方文档):当 CLI 处于 YOLO 模式(全自动执行、不请求确认)时,该工具不可用。这一限制在源码注释中同样有印证——exit-plan-mode.ts 的注释写明 "By default, YOLO mode in interactive environment cannot enter/exit plan mode"。也就是说,Plan Mode 本质上是"需要人工把关"的流程,与 YOLO 的无人值守语义天然冲突。

2.2 执行流程(源码级)

EnterPlanModeInvocation.execute 的实现可以看到完整的执行链路:

  1. 检查确认结果:如果用户在确认对话框中取消了操作,直接返回 llmContent: 'User cancelled entering Plan Mode.',不做任何模式切换;
  2. 切换审批模式:调用 this.config.setApprovalMode(ApprovalMode.PLAN),把 CLI 的审批模式正式切换为 PLAN,后续工具调用即受只读限制约束;
  3. 预创建计划目录:调用 this.config.storage.getPlansDir() 获取项目的计划目录,若不存在则递归创建(fs.mkdirSync(plansDir, { recursive: true }))。源码注释解释了原因:"In sandboxed environments, the plans directory must exist on the host before it can be bound/allowed in the sandbox"——在沙箱环境中,计划目录必须先宿主机上存在,才能被绑定/放行给沙箱。创建失败不会中断流程(仅记录 debug 日志),后续 write_file 时还会再尝试;
  4. 返回 LLM 可见的输出llmContent 固定为 'Switching to Plan mode.',同时给 UI 的 returnDisplay 会带上用户可读的原因(若提供了 reason 参数)。

此外,工具的确认逻辑 shouldConfirmExecute 遵循统一的策略管线:先通过 getMessageBusDecision 向消息总线(confirmation-bus/message-bus.ts)征询策略决策:

  • 决策为 allow(企业策略/用户策略显式放行):跳过确认直接执行;
  • 决策为 deny:抛出 "denied by policy" 错误,执行被拦截;
  • 决策为 ask_user(默认交互场景):弹出 type: 'info' 类型的确认框,标题为 "Enter Plan Mode"。

这种"策略先行、确认兜底"的设计,使 Plan Mode 入口同时受控于策略引擎与用户交互两个层面。

3. exit_plan_mode:计划定稿与正式审批

3.1 工具定义与参数

项目 内容
工具名 exit_plan_mode
显示名 Exit Plan Mode
源码文件 packages/core/src/tools/exit-plan-mode.ts
工具分类 Kind.Plan
参数 计划文件的文件名(string,必填),必须位于项目计划目录内,例如 ~/.gemini/tmp/<project>/plans/ 下的 feature-x.md
是否需要确认 是。向用户展示最终计划并请求正式批准实施

关于参数名需要做一个精确说明:planning.md 文档 中写作 plan_path,而当前仓库源码中的实际参数名是 plan_filename(见 base-declarations.ts 中的常量 EXIT_PLAN_PARAM_PLAN_FILENAME = 'plan_filename',以及 ExitPlanModeParams 接口)。工具描述要求 Agent 只给文件名(如 "feature-x.md","Do not provide an absolute path"),而底层解析逻辑对绝对路径和项目相对路径同样做了兼容处理(见下一节)。

该工具的官方描述(来自 dynamic-declaration-helpers.ts)包含两条强约束,这也是文档中强调的行为要求:

  • 调用前必须先与用户达成非正式共识(informal agreement):Agent 必须先在聊天中就拟议方案与用户讨论达成一致,然后才能调用本工具提交正式审批;
  • 这是退出 Plan Mode 的必经之路:"This tool MUST be used to exit Plan Mode before any source code edits can be performed"——未走审批就不能实施。

3.2 计划路径的三重安全校验

exit_plan_mode 的核心安全点在于:模型提供的是不受信任的路径输入,必须防止路径穿越、访问计划目录之外的文件。这一职责由 planUtils.ts 承担,关键函数是 resolveAndValidatePlanPath,它按优先级尝试三种解析方式:

  1. 绝对路径:若模型直接给了绝对路径,先解析出真实路径(resolveToRealPath 会消除符号链接影响),必须落在 plans 目录之内(isSubpath 判定)才接受;
  2. 项目相对路径:相对项目根(projectRoot)解析后,同样要求落在 plans 目录之内;
  3. 默认情况——相对 plans 目录:把路径当作相对 plans 目录处理,再解析真实路径做边界校验。

三种方式都失败时抛出 PlanErrorMessages.PATH_ACCESS_DENIED,错误文案形如:Access denied: plan path (xxx) must be within the designated plans directory (yyy)validatePlanPath 在此之上补充文件存在性校验FILE_NOT_FOUND),validatePlanContent 则用 isEmpty 校验文件非空FILE_EMPTY)——空文件不允许提交审批。这些标准错误消息由 PlanErrorMessages 集中定义,注释说明它们是"Shared between backend tools and CLI UI for consistency",保证前后端文案一致。

校验失败时,工具并不会抛错,而是把错误信息作为 llmContent 返回给模型(例如 llmContent: this.planValidationErrorreturnDisplay: 'Error: Invalid plan'),使模型能够自我纠正:先创建计划文件、再重新请求审批。参数层面还有前置的 validateToolParamValues,在 schema 校验阶段就拒绝空文件名和越界路径。

3.3 审批流程与模式切换

ExitPlanModeInvocation.shouldConfirmExecute 的执行顺序体现了"先校验、后交互"的原则:

  1. 依次执行路径校验(validatePlanPath)与内容校验(validatePlanContent),任一失败则记录 planValidationError不弹出确认框
  2. 向消息总线征询策略决策:
    • deny:抛错拦截;
    • allow自动批准confirmationOutcome 直接置为 ProceedOnce,并按 getAllowApprovalMode 选择目标审批模式——非交互环境下默认切到 YOLO 以支持自动化执行,交互环境下默认切到 DEFAULT
    • ask_user:弹出 type: 'exit_plan_mode' 的专用审批对话框(标题 "Plan Approval"),向用户展示计划内容并请求正式批准。用户在对话框中选择批准后进入的实施模式(DEFAULTAUTO_EDIT)与是否附带的反馈(feedback),都会通过 payloadToolExitPlanModeConfirmationPayload)回传给工具。

execute 的分支逻辑如下:

  • 验证失败:返回错误信息供模型修正;
  • 用户取消:返回 llmContent: 'User cancelled the plan approval dialog. The plan was not approved and you are still in Plan Mode.',保持在 Plan Mode;
  • 用户批准payload.approved === true):
    • 防御性检查:目标模式若仍是 PLAN 则抛错("Unexpected approval mode");
    • this.config.setApprovalMode(newMode) 切换到用户选择的模式;
    • this.config.setApprovedPlanPath(resolvedPlanPath) 将已批准的计划路径登记到配置中;
    • 通过 logPlanExecution 上报 PlanExecutionEvent 遥测事件;
    • 返回给模型的 llmContent 会明确指示:"Read and follow the plan strictly during implementation",并附上计划文件路径;
  • 用户拒绝
    • 有反馈时返回 Plan rejected. User feedback: <feedback>,要求模型据此修订计划;
    • 无反馈时返回 "No feedback provided",提示模型主动向用户询问具体意见。

两条路径都保持在 Plan Mode,形成"提出计划 → 反馈 → 修订 → 再提交"的闭环。

3.4 策略直通(policy allow)的边界情况

源码中有一段值得注意的注释(exit-plan-mode.ts#L207-L213):当用户策略对 exit_plan_mode 授予 allow 时,调度器会完全跳过确认阶段shouldConfirmExecute 根本不会被调用,approvalPayload 会是 null。因此 execute 中对 approvalPayload 做了兜底:若为空,则按"已批准 + 策略默认审批模式"处理。这一细节保证了自动化场景(CI、headless)下计划流程不会因为缺少 UI 交互而卡死。

4. 与 Plan Mode 的整体关系

enter_plan_mode / exit_plan_mode 只是 Plan Mode 状态机的两个入口点。完整的模式语义、UI 表现与手动切换方式在 plan-mode 文档 中有更广泛的说明;本文聚焦的两个工具承担的是由模型主动发起的模式切换职责:

  • enter_plan_mode 对应状态机的"进入"边:setApprovalMode(ApprovalMode.PLAN) + 预建 plans 目录;
  • exit_plan_mode 对应"退出"边:审批通过后切换到 DEFAULT / AUTO_EDIT(或自动化场景下的 YOLO),并登记批准计划路径;
  • 审批模式常量定义于 policy/types.ts,工具可用性由策略引擎(policy 目录)按模式过滤,从而保证 Plan Mode 下写类工具被禁用。

工具声明层面,coreTools.ts 中的 ENTER_PLAN_MODE_DEFINITIONgetExitPlanModeDefinition 遵循"base 声明 + 按模型覆盖(overrides)"的模式:不同模型族(如 gemini-3 工具集)可以针对特定模型提供差异化的参数 schema,resolveToolDeclaration 在运行时按 modelId 解析最终声明。

5. 测试如何验证这些行为

两个工具均有对应的单元测试,可作为行为契约参考:

  • enter-plan-mode.test.ts:覆盖确认流程、取消分支、模式切换等;
  • exit-plan-mode.test.ts:覆盖路径校验失败(目录外路径、文件不存在、空文件)、批准/拒绝/取消各分支的输出内容,以及策略 allow 直通场景;
  • planUtils 相关校验 的错误消息常量被前后端共享,保证"Access denied / file does not exist / file is empty"等文案在工具层与 UI 层一致。

6. 小结

维度 enter_plan_mode exit_plan_mode
作用 切入只读 Plan Mode,安全探索与规划 提交定稿计划,请求正式批准并退出 Plan Mode
关键参数 reason(可选) plan_filename(必填,须位于计划目录内且非空)
核心副作用 setApprovalMode(PLAN) + 预建 plans 目录 校验通过并批准后切换到 DEFAULT/AUTO_EDIT,登记批准计划路径,上报遥测
确认机制 策略 allow/deny/ask_user 三级决策 + info 确认框 专用 "Plan Approval" 审批框(展示计划、返回反馈)
YOLO 限制 交互 YOLO 模式下不可用 同左;策略 allow 时非交互环境自动切 YOLO 执行
失败行为 用户取消 → 不切换模式 校验失败/拒绝 → 保持 Plan Mode,返回信息供模型修订

从源码结构看,Gemini CLI 的规划工具把"安全"落实到了三个层面:模式层(Plan Mode 下只读限制)、路径层(三重解析 + 真实路径边界校验 + 非空校验)、交互层(策略决策与用户确认双通道)。对希望在自己的 Agent 产品中实现"计划/实施"两阶段流程的开发者而言,这套"策略总线决策 → 用户确认 → 路径白名单校验 → 审批模式状态机"的组合是一个值得参考的完整实现范本。

登录后查看全文
热门项目推荐
相关项目推荐