远程控制 / daemon / workflow
前面讲完了 Agent 的核心与本地体验;今天走到最外圈——本 fork 最有特色的两块扩展:手机/web 远程操控终端里的 Claude Code,以及 workflow 多 agent 编排引擎。贴真实代码,看一类系统能扩展到什么程度,也为明天 Day 20 收官做铺垫。
先说清:这些是本 fork 的扩展
src/server/* 有多个文件是明写的 "Auto-generated stub"(未完成占位)。所以今天定位是"了解一类 Agent CLI 能扩展到什么程度",而非官方核心行为。怎么判断"是不是官方功能"(Day 01/17 窍门):看 feature('BRIDGE_MODE') 这种自定义门控、包名 @claude-code-best/*、成片 stub 文件。这几块全中。
远程控制全景
目标:用手机/网页远程操控你本地终端里的 Claude Code 会话。数据流:
组件:src/bridge/(CLI 侧远程客户端)+ packages/remote-control-server/(自托管服务端,Hono+Bun+React Web UI,Docker 部署)+ src/daemon/(后台守护)+ src/ssh/(SSH 远程部署)。
CLAUDE_BRIDGE_BASE_URL + token 后跑 ccb --remote-control。bridge:长轮询 + spawn 子会话(真实代码)
src/bridge/(40 文件)。入口 runBridgeLoop(bridgeMain.ts:140)。核心是长轮询——真实代码(bridgeApi.ts:214):
// bridge 主动 GET 服务器的 work/poll 接口,问"有没有任务"
`${deps.baseUrl}/v1/environments/${environmentId}/work/poll`
// 日志:GET .../work/poll -> 200 (no work, N consecutive empty polls)
// GET .../work/poll -> 200 workId=... type=... sessionId=...
其它机制:
- spawn 子 CLI:拿到任务就起一个子 CLI 进程跑(
--print --sdk-url ... --input-format stream-json),env 里剥离 bridge token、改用会话专属 token(安全)。 - 三层凭证:可信设备 token + work secret/JWT(到期前 5 分钟刷新)+ 二维码配对。
--input-format stream-json 通过标准流通信(像 Day 12 的 stdio MCP)。长轮询(long poll):客户端发一个请求,服务器如果暂时没数据就"挂着"不立刻回,直到有数据或超时——比"每秒问一次"更省资源。daemon:后台守护进程
src/daemon/(feature('DAEMON'))让远程控制能"一直挂着"——你关掉终端,daemon 仍在后台跑、接远程任务。
daemonMain(main.ts:52):start/stop/status 等子命令。runSupervisor(:230):监管 worker,崩溃指数退避重启(退出码 78 视为永久错误不重启)。- 唯一内置 worker =
remoteControl,实体runRemoteControlWorker→runBridgeHeadless(把 daemon 和 bridge 连起来)。
远程权限桥:手机上批准工具(真实代码)
一个精妙问题:远程会话里 Agent 要调 Edit,权限卡该弹在哪?——弹在你的手机/网页上。src/remote/remotePermissionBridge.ts:12 有个关键函数:
export function createSyntheticAssistantMessage(...) {
// 远端执行工具时,本地没有真实的 assistant 消息,
// 就"伪造"一个带 tool_use 的消息,好复用本地那套权限卡 UI
}
workflow 引擎:脚本化多 agent 编排(真实原语)
packages/workflow-engine/——"多 agent 编排"(对应 /ultracode、/workflows)。核心抽象不是图或状态机,而是一段确定性、可续跑的命令式 JS 脚本。引擎注入给脚本的原语——真实类型(packages/workflow-engine/src/engine/script.ts:11):
export type WorkflowHooks = {
agent: (prompt, opts?) => Promise<unknown> // 派一个子 agent 跑,返回结果
parallel: (thunks) => Promise<Array> // 并行跑多个,全完成才返回
pipeline: (items, ...stages) => Promise<Array> // 每个 item 流过所有 stage
phase: (title) => void // 进度分组标签
log: (message) => void
workflow: (nameOrRef, args?) => Promise<unknown> // 调另一个 workflow
}
agent()/parallel()/pipeline()。引擎执行脚本,把每个 agent() 调用派发成一次真实的 Claude Code 子 agent 运行(src/workflow/backends/claudeCodeBackend.ts,委派给 Day 15 的 runAgent)。const files = await agent("列出所有缺测试的文件") → 拿到 3 个文件 → await parallel(files.map(f => () => agent(`给 ${f} 补测试`))) → 引擎同时派 3 个子 agent 各修一个文件,全部完成才返回。若中途挂了,--resume 时已修好的靠 journal 跳过(L07)。const files = await agent("列出所有测试"); await parallel(files.map(f => () => agent(\`修 ${f}\`))) 就能"发现-然后-并行处理"。这就是你在本对话里可能见过的 Workflow 工具背后的引擎。它"零核心层运行时依赖,通过端口适配器和世界对话"——引擎本身不知道 Claude Code 存在,backend 才把它接上。注意:workflow 建立在 Day 15 的"递归 query"(runAgent)之上——又是统一原语的复用。workflow 可续跑:journal 机制
workflow 可能跑很久(几十个 agent)。中途挂了怎么办?靠 journal(日志)做可续跑(resume):
- 每个
agent()调用完成后,把结果记进 journal。 - 续跑时,journal 命中的 agent 调用直接返回记录的结果、跳过重跑——只重跑没完成的。
- 如果脚本源码 hash 变了,整份 journal 失效重跑(防止用旧结果跑新逻辑)。
- 持久化在
.claude/workflow-runs/(原子写 tmp+rename,保留最近 50 个)。
Date.now()/Math.random(),禁止不确定来源)。这样中断后,前面已完成的 agent 调用能安全地用 journal 里的旧结果"快进",只重跑断点之后的。这跟 Day 03 的 checkpointer(断点续跑)是同类思想,但粒度是"整个 agent 调用"。TUI 面板 /workflows 能实时看进度、杀/续跑。今日小结 + 动手
🧠 今天你应该能回答
- 为什么这些是"扩展"而非官方功能?怎么判断?
- 远程控制数据流?bridge 为什么"长轮询主动要活"?
- 远程任务为什么 spawn 独立子 CLI 进程?
- 远程权限桥为什么"伪造 assistant 消息"?(复用本地 UI)
- workflow 引擎的核心抽象是什么(脚本 vs 图)?有哪些原语?
- workflow 怎么靠 journal 做可续跑?为什么要确定性?
✋ 动手:对着真实代码读一遍
# 1. bridge 长轮询(L03)
grep -n "work/poll\|runBridgeLoop" src/bridge/bridgeApi.ts src/bridge/bridgeMain.ts | head
# 2. 远程权限桥"伪造消息"(L05)
sed -n '12,40p' src/remote/remotePermissionBridge.ts
# 3. workflow 原语(L06,本页精华)
sed -n '10,26p' packages/workflow-engine/src/engine/script.ts
# 4. workflow backend 委派 runAgent(接 Day 15)
grep -n "runAgent\|claudeCodeBackend" src/workflow/backends/claudeCodeBackend.ts | head
# 5. daemon 监管
sed -n '52,60p' src/daemon/main.ts