目录
  1. 1. 一、背景:Plan Mode 的演进
  2. 2. 二、核心参数配置
    1. 2.1. 2.1 执行 Agent 数量
    2. 2.2. 2.2 探索 Agent 数量
  3. 3. 三、5 阶段工作流
  4. 4. 四、Interview Phase 门控
  5. 5. 五、Pewter Ledger 实验(计划文件格式优化)
    1. 5.1. 实验背景
    2. 5.2. 实验目标
    3. 5.3. 三种实验臂
  6. 6. 六、与 Swarm 系统的关系
  7. 7. 七、Feature Flag 体系总结
  8. 8. 八、面试要点
  9. 9. 九、Plan Mode 状态机与主循环集成(深度解析)
    1. 9.1. 9.1 权限模式状态定义
    2. 9.2. 9.2 进入 Plan Mode 的状态写入
    3. 9.3. 9.3 Plan Mode 中的工具禁用机制
    4. 9.4. 9.4 shouldDefer: true 与主循环集成
  10. 10. 十、EnterPlanModeTool 实现详解
    1. 10.1. 10.1 工具元数据
    2. 10.2. 10.2 触发条件(外部用户 vs 内部用户的差异)
    3. 10.3. 10.3 KAIROS 渠道禁用保护
    4. 10.4. 10.4 EnterPlanMode 不能在 Agent 上下文中调用
  11. 11. 十一、用户确认 UI 流程(EnterPlanModePermissionRequest)
    1. 11.1. 11.1 组件触发路径
    2. 11.2. 11.2 Dialog 显示内容
    3. 11.3. 11.3 用户选择处理(handleResponse)
  12. 12. 十二、ExitPlanModeV2Tool 实现详解
    1. 12.1. 12.1 工具输入 Schema
    2. 12.2. 12.2 输出 Schema
    3. 12.3. 12.3 validateInput 前置校验
    4. 12.4. 12.4 Plan 文件读取链路
    5. 12.5. 12.5 退出时的权限恢复
  13. 13. 十三、Plan 文件的存储与生命周期
    1. 13.1. 13.1 文件路径生成
    2. 13.2. 13.2 计划恢复机制(Resume & Fork)
    3. 13.3. 13.3 远程环境文件快照
  14. 14. 十四、Teammate 模式下的 Plan 审批流
  15. 15. 十五、面试深度题(含 V2 vs V1 对比)
【Claude Code源码剖析】26-Plan Mode V2 — 多 Agent 规划系统

源码路径:src/utils/planModeV2.ts(95行)
Plan Mode V2 是 CC 的”先规划、后执行”模式——将复杂任务分解为结构化计划,由多个 Agent 并行探索后再决策。


一、背景:Plan Mode 的演进

版本 特点
Plan Mode V1 单 Agent 串行规划,用户审批后执行
Plan Mode V2 多 Agent 并行探索(Explore Phase),5阶段工作流,订阅分级

二、核心参数配置

2.1 执行 Agent 数量

getPlanModeV2AgentCount(): number {
// 优先级 1:环境变量覆盖(开发调试用)
if (process.env.CLAUDE_CODE_PLAN_V2_AGENT_COUNT) {
const count = parseInt(...) // 有效范围: [1, 10]
if (!isNaN(count) && count > 0 && count <= 10) return count
}

// 优先级 2:订阅等级决定
if (subscriptionType === 'max' && rateLimitTier === 'default_claude_max_20x') return 3
if (subscriptionType === 'enterprise' || subscriptionType === 'team') return 3

return 1 // 默认(免费/Pro 等)
}
订阅类型 执行 Agent 数
Max (20x tier) 3
Enterprise / Team 3
其他(Free/Pro) 1
开发调试(ENV 覆盖) 1-10

2.2 探索 Agent 数量

getPlanModeV2ExploreAgentCount(): number {
// 环境变量: CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT
// 默认值: 3(无订阅限制,所有用户相同)
return 3
}

探索 Agent 独立于执行 Agent:探索阶段并行跑 3 个 Sonnet 实例快速理解代码库,不受订阅限制。


三、5 阶段工作流

完整工作流定义在 messages.ts(getPlanPhase4Section 等函数),planModeV2.ts 只提供配置参数。

Phase 1: Interview(访谈阶段)
├─ 主 Agent 向用户提问,澄清需求
├─ isPlanModeInterviewPhaseEnabled() 控制是否启用
└─ 内部用户(ant)永远开启,外部用 GrowthBook 门控

Phase 2: Explore(探索阶段)
├─ 3 个并行 Explore Agent 读取代码库
├─ 各自探索不同方向(不互通)
└─ 汇总探索结果给主 Agent

Phase 3: Analysis(分析阶段)
└─ 主 Agent 综合 Explore 结果 + 用户需求,生成候选方案

Phase 4: Plan Generation(计划生成)
├─ 生成结构化 Plan 文件
├─ tengu_pewter_ledger 实验控制文件格式
└─ 用户审阅并确认

Phase 5: Execution(执行阶段)
├─ 多个执行 Agent 并行实现各子任务
└─ 执行 Agent 数量由 getPlanModeV2AgentCount() 决定

四、Interview Phase 门控

isPlanModeInterviewPhaseEnabled(): boolean {
// 1. 内部用户无条件开启(dogfooding)
if (process.env.USER_TYPE === 'ant') return true

// 2. 环境变量显式控制(测试用)
if (isEnvTruthy(env)) return true
if (isEnvDefinedFalsy(env)) return false

// 3. GrowthBook Feature Flag: tengu_plan_mode_interview_phase
return getFeatureValue_CACHED_MAY_BE_STALE('tengu_plan_mode_interview_phase', false)
}

Interview Phase 让模型在规划前充分理解需求,减少规划偏差。


五、Pewter Ledger 实验(计划文件格式优化)

// Feature Flag: tengu_pewter_ledger
type PewterLedgerVariant = 'trim' | 'cut' | 'cap' | null

getPewterLedgerVariant(): PewterLedgerVariant {
const raw = getFeatureValue_CACHED_MAY_BE_STALE('tengu_pewter_ledger', null)
if (raw === 'trim' || raw === 'cut' || raw === 'cap') return raw
return null // 控制组
}

实验背景

指标 数值(2026-03-02, N=26.3M)
计划文件 p50 大小 4,906 字符
计划文件 p90 大小 11,617 字符
计划文件平均大小 6,207 字符
模型占比 82% Opus 4.6
拒绝率(<2K chars) 20%
拒绝率(>20K chars) 50%

实验目标

主指标session-level Avg Cost(Opus 输出是输入价格的 5×,成本是输出量的加权代理)

护栏指标

  • feedback-bad rate(计划太薄 → 实现迭代更多 → 更多工具调用)
  • requests/session(反映计划是否导致更多来回)
  • tool error rate

三种实验臂

含义 目的
trim 轻度缩减计划文件大小指导 温和约束
cut 中度缩减 中等约束
cap 严格上限 强约束
null 控制组,无约束 基准

六、与 Swarm 系统的关系

Plan Mode V2 的多 Agent 执行本质上复用了 Swarm 基础设施

PlanModeV2 执行阶段

创建 N 个 SubAgent(同 Swarm TeamFile 协议)

各 SubAgent 认领 Plan 中的子任务

并行执行(inProcessRunner)

主 Agent 汇总结果

差异:Plan Mode 的 SubAgent 是有计划引导的(知道整体方案),Swarm 的 Teammate 更独立。


七、Feature Flag 体系总结

Flag 控制内容 默认值
tengu_plan_mode_interview_phase 是否启用访谈阶段 false(外部用户)
tengu_pewter_ledger 计划文件格式约束级别 null(控制组)
CLAUDE_CODE_PLAN_V2_AGENT_COUNT 执行 Agent 数(ENV 覆盖) 订阅决定
CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT 探索 Agent 数(ENV 覆盖) 3

八、面试要点

Q:Plan Mode V2 与 V1 的核心区别?

V2 引入了并行 Explore Phase(3 个 Agent 并发探索代码库),将”理解问题”与”执行任务”分开。V1 是单 Agent 串行:规划 → 用户批准 → 执行。V2 让规划更深入,执行更并行。

Q:为什么 Explore Agent 数量不受订阅限制,但执行 Agent 受限?

Explore Agent 只读不写,成本可预估且较低(快速读代码)。执行 Agent 涉及写文件、运行命令,并行越多风险和成本越高,因此通过订阅分级控制。

Q:Pewter Ledger 实验为什么以 Avg Cost 而非 planLengthChars 为主指标?

更短的计划文件可能反而增加”写→计数→编辑”循环次数,导致总输出 token 更多。主指标直接量化最终成本,而不是代理指标(计划长度),避免局部优化陷阱。


九、Plan Mode 状态机与主循环集成(深度解析)

9.1 权限模式状态定义

Plan Mode 本质上是 CC 权限系统中的一个模式(mode),由 toolPermissionContext.mode 字段控制。合法值为:

'default' | 'auto' | 'plan' | 'acceptEdits' | 'bypassPermissions'

Plan Mode 对应 'plan',进入时还会保存上一个模式到 prePlanMode,退出时用于恢复。

状态转移路径

default / auto
│ (用户批准 EnterPlanMode)

plan ←── EnterPlanModeTool.call() 写入 setMode('plan')

│ (用户批准 ExitPlanMode)

prePlanMode(恢复到进入前的模式:default 或 auto)

9.2 进入 Plan Mode 的状态写入

EnterPlanModeTool.call() 的核心逻辑:

// EnterPlanModeTool.ts - call()
context.setAppState(prev => ({
...prev,
toolPermissionContext: applyPermissionUpdate(
prepareContextForPlanMode(prev.toolPermissionContext),
{ type: 'setMode', mode: 'plan', destination: 'session' },
),
}))

prepareContextForPlanMode() 负责在切换到 plan 模式前的准备工作(如激活分类器副作用),applyPermissionUpdate 执行实际的 mode 写入并将当前 mode 存入 prePlanMode

9.3 Plan Mode 中的工具禁用机制

Plan Mode 激活时,模型只能使用只读工具(Glob、Grep、Read、LS)加上 AskUserQuestion。写文件/执行命令类工具在权限检查阶段被拒绝。

关键约束来自两个层面:

  1. Prompt 层约束(软约束):mapToolResultToToolResultBlockParam 在进入成功的 tool_result 中明确写入:
// EnterPlanModeTool.ts - mapToolResultToToolResultBlockParam()
`${message}

In plan mode, you should:
1. Thoroughly explore the codebase to understand existing patterns
...
Remember: DO NOT write or edit any files yet. This is a read-only exploration and planning phase.`
  1. 权限层约束(硬约束):mode: 'plan' 状态下,权限系统对写操作返回 deny,模型无论怎样尝试调用 Bash/FileWrite 都会被拒绝,不会弹出用户确认框。

9.4 shouldDefer: true 与主循环集成

EnterPlanModeToolExitPlanModeV2Tool 都设置了 shouldDefer: true,表示这两个工具需要暂停当前 query loop、等待用户交互后再继续:

shouldDefer: true  // 两个工具均设置

主循环(query loop)遇到 deferred tool 时会:

  1. 暂停推进 assistant turn
  2. 把控制权交给 UI permission dialog
  3. 用户选择后,把结果作为 tool_result 注入,继续 loop

这正是 Plan Mode 不需要 model 在 tool_use 里带计划内容的原因——整个”弹窗 → 用户确认 → 模式切换”是同步阻塞流程。


十、EnterPlanModeTool 实现详解

10.1 工具元数据

// constants.ts
export const ENTER_PLAN_MODE_TOOL_NAME = 'EnterPlanMode'

// EnterPlanModeTool.ts
name: ENTER_PLAN_MODE_TOOL_NAME,
searchHint: 'switch to plan mode to design an approach before coding',
maxResultSizeChars: 100_000,
isReadOnly() { return true }, // Plan Mode 进入是只读操作
isConcurrencySafe() { return true },
shouldDefer: true,

输入 schema 为空对象(不需要任何参数):

const inputSchema = lazySchema(() => z.strictObject({}))

10.2 触发条件(外部用户 vs 内部用户的差异)

prompt.ts 根据 USER_TYPE 环境变量分发两套 prompt:

外部用户(getEnterPlanModeToolPromptExternal)——更激进地触发:

  • 任何新功能实现
  • 多文件修改(> 2-3 个文件)
  • 有多个合理方案的任务
  • 需求不清晰时
  • 用户偏好可能影响实现方式时

核心指导语:

“Use this tool proactively when you’re about to start a non-trivial implementation task.”

内部用户(getEnterPlanModeToolPromptAnt)——更保守,避免过度规划:

  • 仅当有显著架构歧义
  • 需求真正不清晰时
  • 高影响重构时
  • 明确排除:用户说”can we work on X”时直接开始,不进规划

核心指导语:

“When in doubt, prefer starting work and using AskUserQuestion for specific questions over entering a full planning phase.”

这个差异反映了 CC 团队的实际经验:内部用户(ant)对代码库更熟悉,过度规划反而是摩擦。

10.3 KAIROS 渠道禁用保护

isEnabled() {
if (
(feature('KAIROS') || feature('KAIROS_CHANNELS')) &&
getAllowedChannels().length > 0
) {
return false
}
return true
}

当 CC 运行在 Telegram/Discord 等渠道模式时(KAIROS),EnterPlanModeExitPlanMode 同时禁用。原因:ExitPlanMode 的审批 dialog 需要 TUI 终端,渠道模式下没有 TUI,进入后无法退出,形成死锁。

10.4 EnterPlanMode 不能在 Agent 上下文中调用

async call(_input, context) {
if (context.agentId) {
throw new Error('EnterPlanMode tool cannot be used in agent contexts')
}
// ...
}

Plan Mode 是顶层 session 级别的模式切换,SubAgent 不能触发它。这防止了 SubAgent 意外改变顶层模式。


十一、用户确认 UI 流程(EnterPlanModePermissionRequest)

11.1 组件触发路径

model 发出 tool_use: EnterPlanMode

主循环检测到 shouldDefer=true

渲染 EnterPlanModePermissionRequest 组件

用户看到 PermissionDialog(planMode 配色)

11.2 Dialog 显示内容

╔════════════════════════════════╗
║ Enter plan mode? ║
║ ║
║ Claude wants to enter plan ║
║ mode to explore and design ║
║ an implementation approach. ║
║ ║
║ In plan mode, Claude will: ║
║ · Explore the codebase ║
║ · Identify existing patterns ║
║ · Design an implementation ║
║ strategy ║
║ · Present a plan for your ║
║ approval ║
║ ║
║ No code changes will be made ║
║ until you approve the plan. ║
║ ║
║ [Yes, enter plan mode] ║
║ [No, start implementing now] ║
╚════════════════════════════════╝

11.3 用户选择处理(handleResponse)

// EnterPlanModePermissionRequest.tsx
function handleResponse(value: 'yes' | 'no'): void {
if (value === 'yes') {
logEvent('tengu_plan_enter', {
interviewPhaseEnabled: isPlanModeInterviewPhaseEnabled(),
entryMethod: 'tool',
})
handlePlanModeTransition(toolPermissionContextMode, 'plan')
onDone()
toolUseConfirm.onAllow({}, [
{ type: 'setMode', mode: 'plan', destination: 'session' },
])
} else {
onDone()
onReject()
toolUseConfirm.onReject()
}
}

Yes 路径

  1. 记录 tengu_plan_enter analytics 事件(含 interviewPhase 是否开启)
  2. 调用 handlePlanModeTransition 处理模式转换副作用
  3. 调用 toolUseConfirm.onAllow 传入 setMode: 'plan' 权限更新
  4. 主循环收到 allow + 权限更新,将 mode 写入 AppState

No 路径

  1. 调用 toolUseConfirm.onReject()
  2. 工具调用被拒绝,model 收到 rejection,可以直接开始编码

十二、ExitPlanModeV2Tool 实现详解

12.1 工具输入 Schema

const inputSchema = lazySchema(() =>
z.strictObject({
allowedPrompts: z.array(z.object({
tool: z.enum(['Bash']),
prompt: z.string().describe(
'Semantic description of the action, e.g. "run tests", "install dependencies"'
),
})).optional(),
}).passthrough()
)

allowedPrompts 是模型在退出规划阶段时请求的语义级 Bash 权限——描述”需要做什么”而非具体命令,权限系统根据此做模式匹配授权。

注意:Plan 内容本身不在输入 schema 里,由 normalizeToolInput 从磁盘读取后注入(详见 12.4)。

12.2 输出 Schema

z.object({
plan: z.string().nullable(), // 呈现给用户的计划内容
isAgent: z.boolean(), // 是否在 SubAgent 上下文
filePath: z.string().optional(), // 计划文件路径
hasTaskTool: z.boolean().optional(), // 是否可用 Agent 工具(并行执行提示)
planWasEdited: z.boolean().optional(), // 用户是否通过 UI 编辑了计划
awaitingLeaderApproval: z.boolean().optional(), // Teammate 等待 Lead 审批
requestId: z.string().optional(), // 审批请求 ID
})

12.3 validateInput 前置校验

async validateInput(_input, { getAppState, options }) {
if (isTeammate()) return { result: true }

const mode = getAppState().toolPermissionContext.mode
if (mode !== 'plan') {
logEvent('tengu_exit_plan_mode_called_outside_plan', { model, mode, ... })
return {
result: false,
message: 'You are not in plan mode. This tool is only for exiting plan mode ...',
errorCode: 1,
}
}
return { result: true }
}

防止非 plan 模式下调用 ExitPlanMode:compact/clear 后 deferred tool 列表里仍有 ExitPlanMode,model 可能在非 plan 状态下错误调用。validateInputcheckPermissions(会弹用户 dialog)之前拦截,避免给用户弹出无意义的审批窗口,同时记录异常事件。

12.4 Plan 文件读取链路

Plan 内容从不在网络调用中传递,而是走文件系统:

model 规划阶段写文件(FileWrite 工具写 plan file)

model 调用 ExitPlanMode(不带 plan 参数)

normalizeToolInput 读磁盘:getPlan(agentId) → readFileSync(planFilePath)

注入到 input.plan(SDK 层可见,内部 schema 不含此字段)

ExitPlanModeV2Tool.call() 读取 input.plan 或回退到 getPlan()

plan 内容嵌入 tool_result 返回给 model("Approved Plan: ...")

CCR Web UI 的特殊路径:用户可以在 Web UI 中编辑计划文本,编辑后的版本通过 permissionResult.updatedInput.plan 传入,call() 优先使用此版本并写回磁盘:

const inputPlan =
'plan' in input && typeof input.plan === 'string' ? input.plan : undefined
const plan = inputPlan ?? getPlan(context.agentId)

if (inputPlan !== undefined && filePath) {
await writeFile(filePath, inputPlan, 'utf-8')
void persistFileSnapshotIfRemote()
}

12.5 退出时的权限恢复

context.setAppState(prev => {
let restoreMode = prev.toolPermissionContext.prePlanMode ?? 'default'

// 电路断路器:如果进入规划前是 auto 模式,但 auto gate 现在关闭了
// 则回退到 default,而不是强制开启 auto(规避安全规则)
if (feature('TRANSCRIPT_CLASSIFIER')) {
if (restoreMode === 'auto' && !isAutoModeGateEnabled()) {
restoreMode = 'default'
}
setAutoModeActive(restoreMode === 'auto')
}

// 如果恢复到非 auto 模式,恢复被剥离的危险权限
if (!restoringToAuto && strippedDangerousRules) {
baseContext = restoreDangerousPermissions(baseContext)
}

return {
...prev,
toolPermissionContext: {
...baseContext,
mode: restoreMode,
prePlanMode: undefined, // 清除,避免下次 exit 错误恢复
},
}
})

退出逻辑处理三种情形:

进入前模式 auto gate 状态 退出后模式
default 任意 default
auto gate 开启 auto(恢复)
auto gate 关闭(断路) default(安全回退)

十三、Plan 文件的存储与生命周期

13.1 文件路径生成

// plans.ts
export function getPlanFilePath(agentId?: AgentId): string {
const planSlug = getPlanSlug(getSessionId())

if (!agentId) {
// 主会话:{plansDir}/{随机词组}.md
return join(getPlansDirectory(), `${planSlug}.md`)
}
// SubAgent:{plansDir}/{随机词组}-agent-{agentId}.md
return join(getPlansDirectory(), `${planSlug}-agent-${agentId}.md`)
}

planSlug 是基于当前 session ID 生成的随机词组(如 violet-mountain),保证在 plans 目录下唯一(最多重试 10 次)。

默认存储位置~/.claude/plans/{slug}.md(可在 settings.json 的 plansDirectory 中自定义,但必须在项目根目录内)。

13.2 计划恢复机制(Resume & Fork)

CC 支持会话恢复(/resume)和会话 fork(Ctrl+G),plan 文件需要跨会话持久化。

Resume 场景copyPlanForResume() 从 log 中读取原始 slug,恢复到同一文件路径。在 CCR(远程)环境中,文件不持久,优先从 file_snapshot 系统消息恢复,其次从消息历史中的三种位置提取:

// plans.ts - recoverPlanFromMessages()
// 三种提取位置(按优先级):
// 1. assistant message 中的 ExitPlanMode tool_use input.plan 字段
// 2. user message 的 planContent 字段
// 3. attachment message 中的 plan_file_reference.planContent

Fork 场景copyPlanForFork() 生成新 slug,将原计划内容复制到新文件,防止原会话和 fork 会话互相覆盖。

13.3 远程环境文件快照

CCR(Claude Code Remote)无本地文件系统,计划文件通过增量快照嵌入 transcript:

// plans.ts - persistFileSnapshotIfRemote()
if (getEnvironmentKind() === null) return // 本地环境不需要

const message: SystemFileSnapshotMessage = {
type: 'system',
subtype: 'file_snapshot',
snapshotFiles: [{ key: 'plan', path: planFilePath, content: planContent }],
// ...
}
await recordTranscript([message])

每次 writeFile 到 plan 文件后都会触发快照,保证远程会话的计划内容不会丢失。


十四、Teammate 模式下的 Plan 审批流

当 ExitPlanMode 在 Teammate(SubAgent)上下文中被调用时,走完全不同的审批路径:

if (isTeammate() && isPlanModeRequired()) {
const approvalRequest = {
type: 'plan_approval_request',
from: agentName,
planFilePath: filePath,
planContent: plan,
requestId: generateRequestId('plan_approval', ...),
timestamp: new Date().toISOString(),
}

// 写入 Team Lead 的邮箱
await writeToMailbox('team-lead', {
from: agentName,
text: JSON.stringify(approvalRequest),
timestamp: new Date().toISOString(),
}, teamName)

return { data: { plan, isAgent: true, awaitingLeaderApproval: true, requestId } }
}

Tool result 告知 Teammate:

Your plan has been submitted to the team lead for approval.
**Important:** Do NOT proceed until you receive approval. Check your inbox for response.

Teammate 进入等待状态,Team Lead 在 UI 中审批后,通过 mailbox 协议通知 Teammate 继续。


十五、面试深度题(含 V2 vs V1 对比)

Q1:Plan Mode V2 相比 V1 最核心的改进是什么?从源码角度说明。

V1 是单 Agent 串行流程:EnterPlanMode → 探索 → ExitPlanMode → 用户批准 → 执行。V2 有三处关键改进:

  1. 并行 Explore PhasegetPlanModeV2ExploreAgentCount() 固定返回 3,所有订阅等级都获得 3 个并行探索 Agent,分别探索代码库的不同部分,汇总后给主 Agent 更丰富的上下文。

  2. 执行并行化getPlanModeV2AgentCount() 根据订阅返回 1-3,Max/Enterprise/Team 用户进入执行阶段时可并行跑多个实现 Agent,将大型计划的各子任务同时推进。

  3. Interview Phase(可选)isPlanModeInterviewPhaseEnabled() 控制,内部用户始终开启——在探索前先通过多轮对话彻底澄清需求,减少”探索方向偏了”的浪费。

Q2:Plan Mode 如何在技术层面防止 model 在规划阶段”偷跑”工具(写文件、执行命令)?

双重防线:

第一层(软约束/Prompt 层):mapToolResultToToolResultBlockParam 在 Enter 成功的 tool_result 中注入明确指令:"DO NOT write or edit any files yet. This is a read-only exploration and planning phase."这段文字直接出现在 context window 里,作为强约束。

第二层(硬约束/权限层):mode: 'plan' 状态下,权限系统对所有写操作统一返回 deny,不弹用户确认。即使 model 无视 prompt 指令尝试调用 Bash 或 FileWrite,也会在 checkPermissions 阶段被拒绝,工具调用失败,不产生任何副作用。两层保护互补:prompt 层减少模型的主动尝试,权限层提供安全网。

Q3:ExitPlanMode 的 validateInput 为什么要在 checkPermissions 之前拦截”非 plan 模式调用”?

checkPermissions 在验证通过后会向用户显示确认 dialog(弹窗),如果 model 在非 plan 模式下(如 compact 后)错误地调用 ExitPlanMode,让用户看到”退出规划模式?”的弹窗会造成困惑。validateInput 先于权限检查运行,直接返回 { result: false, message: '...' },主循环将其转化为工具错误注入 context,model 自行纠正,用户完全无感知。同时记录 tengu_exit_plan_mode_called_outside_plan 事件,供团队监控此类异常频率。

Q4:Plan 文件的内容如何从规划阶段传递到执行阶段,以及 CCR 远程环境如何保证不丢失?

本地环境下,plan 文件写在 ~/.claude/plans/{slug}.md,整个 session 期间文件持久存在,getPlan() 随时可读。normalizeToolInput 在 ExitPlanMode 被调用时从磁盘读取内容注入 input.plan,用户批准后,内容随 tool_result "## Approved Plan:\n{plan}" 进入 context window,实现从规划阶段到执行阶段的信息传递。

CCR(远程)环境无持久文件系统,通过两个机制保证不丢失:

  1. 增量快照:每次写 plan 文件后调用 persistFileSnapshotIfRemote(),将内容序列化为 SystemFileSnapshotMessage 追加到 transcript。
  2. Resume 恢复copyPlanForResume() 在恢复会话时,先找 file_snapshot,再扫描消息历史中 ExitPlanMode tool_use 的 input.plan、user message 的 planContent、attachment 的 plan_file_reference,三路回退确保计划内容可靠恢复。
打赏
  • 微信
  • 支付宝

评论