ULTRAPLAN
Claude Code 如何将深度多代理规划卸载到远程 CCR 会话 — 从关键字触发到计划批准以及传送回终端。
ULTRAPLAN 是Claude Code 的远程计划模式。 当您输入时 /ultraplan
或嵌入单词 ultraplan 在提示中的任何位置,CLI 都会在云中生成完整的 Claude Code 会话(CCR — 网络上的 Claude Code),在最强大的可用模型 (Opus) 上运行,并有 30 分钟的窗口可通过浏览器与您一起迭代计划。 您的本地终端始终保持免费状态。
commands/ultraplan.tsx →
utils/ultraplan/ccrSession.ts →
utils/ultraplan/keyword.ts →
utils/teleport.tsx →
tasks/RemoteAgentTask/RemoteAgentTask.tsx
在最高级别,ULTRAPLAN 有四个阶段,每个阶段由不同的模块管理:
触发检测
关键字扫描仪(keyword.ts) 在自由格式输入中查找“ultraplan”,或者使用斜杠命令直接路由它。
CCR 会议启动
teleportToRemote() in teleport.tsx 创建远程会话,上传 git 包,并返回会话 ID。
Long-Poll
pollForApprovedExitPlanMode() in ccrSession.ts 每 3 秒轮询一次事件流,持续长达 30 分钟,跟踪阶段转换。
计划交付
获得批准后,该计划将通过以下方式在当地落地: UltraplanChoiceDialog (传送路径)或停留在 CCR(远程执行路径)。
该图跟踪了从用户击键到计划交付的确切代码路径:
大多数 Claude 功能都需要明确的 /command。 ULTRAPLAN 很不寻常:它由自由格式的文本触发。 这个词 ultraplan 提示中的任何位置都会触发启动 - unless 上下文清楚表明它不是指令。
逻辑完全存在于 utils/ultraplan/keyword.ts 在
findKeywordTriggerPositions() 功能。 它通过逐个字符地遍历输入来构建“引用范围”列表,然后根据这些范围以及几个附加的防护来过滤单词边界匹配:
// keyword.ts — what is filtered OUT (never triggers)
// 1. Slash-command input — /ultraplan is routed to the command handler, not here
if (text.startsWith('/')) return []
// 2. Inside paired delimiters: `backticks`, "quotes", <tags>, {braces}, [brackets], (parens)
// e.g. `src/ultraplan/foo.ts` or <ultraplan> in HTML do NOT trigger
// 3. Path / identifier context: preceded or followed by / \ -
// e.g. src/ultraplan/ or --ultraplan-mode do NOT trigger
// 4. Followed by ? — a question about the feature shouldn't invoke it
// e.g. "what is ultraplan?" does NOT trigger
// 5. Followed by . + word char (file extension)
// e.g. ultraplan.tsx does NOT trigger
相同的发动机手柄 ultrareview。 两者都导出类型化的
TriggerPosition[] 因此,PromptInput 组件可以用彩虹效果突出显示匹配的单词,并在用户提交之前显示“将启动 ultraplan”的工具提示。
当检测到关键字并将提示转发到远程会话时,本地提示将被重写,因此单词“ultraplan”变为“plan” - 保持转发的提示符合语法:
// keyword.ts
export function replaceUltraplanKeyword(text: string): string {
const [trigger] = findUltraplanTriggerPositions(text)
if (!trigger) return text
const before = text.slice(0, trigger.start)
const after = text.slice(trigger.end)
if (!(before + after).trim()) return ''
// Preserves user casing: "ultraplan" → "plan", "Ultraplan" → "Plan"
return before + trigger.word.slice('ultra'.length) + after
}
“分离”模式
launchUltraplan() 返回面向用户的消息 immediately,在远程会话存在之前。 所有异步工作——资格检查、俳句标题生成、git 包创建、POST 到 CCR——都在一个 void 异步闭包调用
launchDetached()。 调用者永远不会等待它。
// ultraplan.tsx — returns to the REPL in milliseconds
export async function launchUltraplan(opts): Promise<string> {
// Synchronously set ultraplanLaunching to block duplicate launches
setAppState(prev => prev.ultraplanLaunching ? prev : { ...prev, ultraplanLaunching: true })
void launchDetached({ blurb, seedPlan, getAppState, setAppState, signal, onSessionReady })
return buildLaunchMessage(disconnectedBridge)
// ↑ "◇ ultraplan\nStarting Claude Code on the web…"
}
ultraplanLaunching 标志已设置 synchronously 在分离流开始之前。 这会关闭窗口,在该窗口中,两次快速按键都可以在任一方调用之前通过警卫检查 teleportToRemote()。 一旦会话 URL 到达(或发生任何错误),该标志就会被清除。
提示构建和系统提醒技巧
最初的 CCR 消息有一个经过深思熟虑的分层。 简介和种子计划开始
outside the <system-reminder> 标签,以便 CCR 浏览器将它们呈现给用户可见。 脚手架说明(加载自
utils/ultraplan/prompt.txt 在构建时)进入标签内部,因此远程模型可以看到它们,但浏览器 UI 会隐藏它们。 并且提示文本中故意省略了“ultraplan”一词,以避免远程 CCR CLI 自触发另一个会话:
// ultraplan.tsx
export function buildUltraplanPrompt(blurb: string, seedPlan?: string): string {
const parts: string[] = []
if (seedPlan) {
parts.push('Here is a draft plan to refine:', '', seedPlan, '')
}
parts.push(ULTRAPLAN_INSTRUCTIONS) // from prompt.txt, wrapped in <system-reminder>
if (blurb) {
parts.push('', blurb)
}
return parts.join('\n')
}
资格检查
在花时间进行网络通话之前, checkRemoteAgentEligibility()
验证了几个前提条件。 失败以通知形式出现(不是抛出的错误),因此终端不会崩溃:
// RemoteAgentTask.tsx — formatted error messages
case 'not_logged_in':
return 'Please run /login and sign in with your Claude.ai account (not Console).'
case 'no_remote_environment':
return 'No cloud environment available. Set one up at https://claude.ai/code/onboarding?magic=env-setup'
case 'not_in_git_repo':
return 'Background tasks require a git repository.'
case 'no_git_remote':
return 'Background tasks require a GitHub remote.'
case 'github_app_not_installed':
return 'The Claude GitHub app must be installed on this repository first.'
case 'policy_blocked':
return "Remote sessions are disabled by your organization's policy."
teleportToRemote() — CCR 会话工厂
创建远程会话的繁重工作是通过以下方式完成的 teleportToRemote()
in utils/teleport.tsx。 特别对于 ULTRAPLAN,它被称为
permissionMode: 'plan' and ultraplan: true。 这会导致 CCR 会话在计划模式下创建 — 远程代理只能计划,而不能执行。 该会话还通过 git 包克隆本地 git 存储库:
// ultraplan.tsx — call site
const session = await teleportToRemote({
initialMessage: prompt,
description: blurb || 'Refine local plan',
model: getUltraplanModel(), // opus4.6 from GrowthBook flag
permissionMode: 'plan',
ultraplan: true,
signal,
useDefaultEnvironment: true,
onBundleFail: msg => { bundleFailMsg = msg }
})
Inside teleportToRemote(),克劳德俳句生成一个简短的会话标题,并且
claude/<slug> 描述中的分支名称。 然后它发布到
/v1/sessions 包含 OAuth 标头、git 源和初始消息。 返回的 session.id 是接下来一切的锚点。
会议直播后, startDetachedPoll() calls
pollForApprovedExitPlanMode() — 30 分钟的基于光标的事件调查。 该函数位于 utils/ultraplan/ccrSession.ts 并且故意没有副作用:它是一个纯异步循环,将所有分类委托给
ExitPlanModeScanner.
基于光标的分页
每一次蜱虫呼叫 pollRemoteSessionEvents(sessionId, cursor) 哪个击中
GET /v1/sessions/{id}/events?after_id={cursor},在一次调用中获取最多 50 页的事件。 光标前进至 response.lastEventId。 这意味着轮询器永远不会重新读取事件 - 每个刻度仅获取新活动。
// ccrSession.ts
const POLL_INTERVAL_MS = 3000
const MAX_CONSECUTIVE_FAILURES = 5 // ~600 calls over 30min; tolerate transient 5xx
while (Date.now() < deadline) {
if (shouldStop?.()) throw new UltraplanPollError('poll stopped by caller', 'stopped', ...)
const resp = await pollRemoteSessionEvents(sessionId, cursor)
cursor = resp.lastEventId
const result = scanner.ingest(resp.newEvents)
// ... classify result and update phase ...
await sleep(POLL_INTERVAL_MS)
}
ExitPlanModeScanner — 纯状态分类器
扫描器累积事件批次并对会话的 ExitPlanMode 状态进行分类。 它跟踪三个内部集合: exitPlanCalls (ExitPlanMode 的工具使用 ID), results (由 ID 键入的 tool_result 块),以及
rejectedIds (用户在浏览器中拒绝的 ID)。 每次摄取时,它都会从最新到最旧进行扫描,以查找第一个未拒绝的呼叫及其解决方案:
// ccrSession.ts — ScanResult kinds
// 'approved' → plan in tool_result with is_error=false, marker "## Approved Plan:"
// 'teleport' → is_error=true but contains ULTRAPLAN_TELEPORT_SENTINEL marker
// 'rejected' → is_error=true, no sentinel — user said "revise this"
// 'pending' → tool_use seen, no tool_result yet (browser showing approval dialog)
// 'terminated' → result(non-success) — remote session crashed or hit max turns
// 'unchanged' → no new relevant events
type:'result', subtype:'error_during_execution'),返回批准的计划 - 代码注释明确记录了此优先级:
“已批准 > 已终止 > 已拒绝 > 待定 > 未更改”.
相变和药丸徽章
轮询器通过以下方式向 UI 呈现三个阶段 onPhaseChange():
running
默认。 远程正在执行轮流。 没有特殊徽章。
needs_input
远程问了一个澄清问题并闲着。 徽章:“需要输入”。 用户必须在浏览器中回复。
plan_ready
ExitPlanMode tool_use 存在但没有 tool_result。 徽章:“计划就绪”。 浏览器正在显示批准对话框。
过渡到 needs_input 使用仔细的启发式: sessionStatus
=== 'idle' AND newEvents.length === 0。 第二个条件至关重要 - CCR 在工具轮次之间短暂翻转为“空闲”状态,因此轮询器仅在同一时间周期内没有活动时才信任它:
// ccrSession.ts — quiet-idle heuristic
const quietIdle =
(sessionStatus === 'idle' || sessionStatus === 'requires_action') &&
newEvents.length === 0
const phase: UltraplanPhase = scanner.hasPendingPlan
? 'plan_ready'
: quietIdle
? 'needs_input'
: 'running'
当投票结果确定后, executionTarget 字段决定计划运行的位置。 恰好有两种结果:
路径 A:远程执行(CCR 中“已批准”)
用户在 CCR 浏览器 PlanModal 中单击“执行”。 远程会话已处于编码模式。 本地 CLI 不得存档会话(存档会停止会话),并且不得显示选择对话框。 它只是标记任务已完成并将通知排入队列:
// ultraplan.tsx — remote execution path
if (executionTarget === 'remote') {
updateTaskState(taskId, setAppState, t => ({
...t, status: 'completed', endTime: Date.now()
}))
enqueuePendingNotification({
value: [
`Ultraplan approved — executing in Claude Code on the web. Follow along at: ${url}`,
'',
'Results will land as a pull request when the remote session finishes.',
'There is nothing to do here.'
].join('\n'),
mode: 'task-notification'
})
}
路径 B:传送(“传送回终端”)
用户在 PlanModal 浏览器中单击“传送回终端”。 浏览器发送一个 is_error=true tool_result 与哨兵字符串
__ULTRAPLAN_TELEPORT_LOCAL__ 作为前缀,后跟计划文本。 扫描仪检测到这一点并返回 { kind: 'teleport', plan }。 民意调查结果如下: executionTarget: 'local'.
然后本地 CLI 设置 ultraplanPendingChoice 在 AppState 中,这会导致 REPL 挂载 UltraplanChoiceDialog。 该对话框负责归档远程会话并根据用户选择清除状态:
// ccrSession.ts — teleport plan extraction
export const ULTRAPLAN_TELEPORT_SENTINEL = '__ULTRAPLAN_TELEPORT_LOCAL__'
function extractTeleportPlan(content): string | null {
const text = contentToText(content)
const marker = `${ULTRAPLAN_TELEPORT_SENTINEL}\n`
const idx = text.indexOf(marker)
if (idx === -1) return null // no sentinel → normal rejection
return text.slice(idx + marker.length).trimEnd()
}
// approved path uses a different extractor
function extractApprovedPlan(content): string {
// Checks "## Approved Plan (edited by user):\n" first,
// then "## Approved Plan:\n"
}
停止 ULTRAPLAN 是经过协调的:CLI 存档远程会话(这会停止它,但保持 URL 可见),终止本地任务条目,并清除所有相关的 AppState 字段。 独立民意调查 shouldStop 回调在下一个滴答声中检测到终止状态并抛出 UltraplanPollError 合理
'stopped' — 民意调查的 catch 块通过提前返回来处理(没有额外的通知)。
// ultraplan.tsx
export async function stopUltraplan(taskId, sessionId, setAppState): Promise<void> {
await RemoteAgentTask.kill(taskId, setAppState) // archives session internally
setAppState(prev => ({
...prev,
ultraplanSessionUrl: undefined,
ultraplanPendingChoice: undefined,
ultraplanLaunching: undefined
}))
// Enqueue two notifications: one for the user, one meta-instruction
// for the model so it doesn't try to respond to the stop event
enqueuePendingNotification({ value: `Ultraplan stopped.\n\nSession: ${url}`, ... })
enqueuePendingNotification({
value: 'The user stopped the ultraplan session above. Do not respond...',
mode: 'task-notification',
isMeta: true // ← model-only instruction, not shown to user
})
}
teleportToRemote() 成功了但是
before poll 循环是健康的,catch 块显式地归档会话。 如果没有这个,远程容器将运行 30 分钟,而没有轮询器监视它。 当地的 sessionId 变量被精确地提升到 try 块之上,以便 catch 可以引用它。
ULTRAPLAN 会话注册为 RemoteAgentTaskState 统一任务框架中的条目。 这就是驱动 REPL 状态栏中的药丸的原因。 这
isUltraplan: true 标志将它们与常规远程代理任务区分开来,因此通用轮询器(startRemoteSessionPolling) 知道不要自行声明完成 — ULTRAPLAN 生命周期由 startDetachedPoll:
// RemoteAgentTask.tsx — state shape (relevant fields)
type RemoteAgentTaskState = TaskStateBase & {
type: 'remote_agent'
remoteTaskType: RemoteTaskType // 'ultraplan' | 'ultrareview' | 'remote-agent' | ...
sessionId: string
isUltraplan?: boolean
// Scanner-derived pill badge state
ultraplanPhase?: Exclude<UltraplanPhase, 'running'> // 'needs_input' | 'plan_ready'
log: SDKMessage[] // populated by the generic poller for the detail view
todoList: TodoList
}
该会话还通过以下方式持久保存到磁盘上的 sidecar 文件中
writeRemoteAgentMetadata()。 这意味着如果您关闭终端并重新打开它 claude --resume,ULTRAPLAN 会话恢复:读取 sidecar,获取 CCR 状态,如果会话仍在运行,则重新启动轮询。
远程模型不是硬编码的。 它在调用时从 GrowthBook 功能标志中读取,回退到 Opus 4.6 第一方 ID:
// ultraplan.tsx
function getUltraplanModel(): string {
return getFeatureValue_CACHED_MAY_BE_STALE(
'tengu_ultraplan_model',
ALL_MODEL_CONFIGS.opus46.firstParty // fallback
)
}
请注意来源中的评论: “在调用时读取,而不是模块加载:GrowthBook 缓存在导入时为空,/config Gates 可以在调用之间翻转它。” 这就是该函数不是模块级常量的原因。
还有一个仅限开发的提示覆盖:当构建为 ant (内部)构建和 ULTRAPLAN_PROMPT_FILE env var 指向一个文件,该文件替换捆绑的 prompt.txt。 该路径在编译时从外部构建中消除了死代码。
正在运行的 ULTRAPLAN 会话的三相状态机记录在源注释中,值得明确可视化:
深入探讨:ExitPlanModeScanner ingest() 逻辑一步一步
扫描器分两次处理事件批次。 第一遍:它遍历批次中的每个事件并更新三个内部数据结构:
- For
type:'assistant'messages — 任何具有名称的 tool_use 块exit_plan_mode_v2已将其 ID 推送至exitPlanCalls[] - For
type:'user'messages — tool_result 块存储在results地图由tool_use_id - For
type:'result'具有非成功子类型 — 集terminated带有子类型字符串的标志
第二遍(扫描):迭代 exitPlanCalls 从最新到最旧,跳过被拒绝的 ID。 对于每位候选人:
- 还没有工具结果 →
{ kind: 'pending' } - 工具结果与
is_error=true+ 哨兵 →{ kind: 'teleport' } - 工具结果与
is_error=true,没有哨兵 →{ kind: 'rejected' } - 工具结果与
is_error=false→{ kind: 'approved' }
优先级解析:批准/传送立即返回(无终止检查)。 拒绝的 ID 将添加到集合中,并计划在下一个周期进行重新扫描。 终止优先于拒绝和待定,但不高于批准/传送。
The rescanAfterRejection 标志是性能优化:当没有发生任何事情(没有新事件,没有拒绝最后一个滴答)时,扫描将完全跳过 - 结果不会改变。
本课涵盖的内容
- ULTRAPLAN 将多代理规划卸载到远程 CCR 会话(仅限规划模式、Opus 模型、30 分钟窗口),同时保持本地终端完全空闲。
- 关键字扫描仪在
keyword.ts在触发触发器之前过滤引用的上下文、文件路径、斜杠命令和问号 - 自由格式文本即可工作。 - 中的“分离”模式
launchUltraplan()立即返回一条消息并在 a 中运行所有异步工作void关闭——终端永远不会阻塞。 - The
ultraplanLaunching标志已设置 synchronously 在任何异步调用之前,关闭双启动竞争窗口。 - 轮询循环使用基于游标的分页(每个刻度最多 50 页),最多可容忍 5 次连续网络故障,并驱动三态阶段机(
running → needs_input → plan_ready). - The
ExitPlanModeScanner是一个无副作用的状态分类器:它可以从记录的事件日志中离线重放以进行调试。 - 两种交付路径: remote (用户在浏览器中批准,CCR 执行,结果作为 PR 落地)和 teleport (用户单击“返回终端”,计划通过哨兵字符串嵌入到拒绝 tool_result 中)。
- 停止会清除三个 AppState 字段、存档远程会话并发送
isMeta:true仅模型通知,因此本地模型不会尝试响应停止事件。 - 会话存活
--resume:如果 CCR 会话仍然存在,元数据将保留到 sidecar 文件并在重新启动时恢复。
知识检查
ultraplanLaunching 同步设置 before 分离的异步流程开始了吗?quietIdle 过渡到之前的启发式要求 needs_input?