Rowboat Code Mode 怎么把任务扔给 Claude Code 和 Codex:从 15.5KB client.ts 到 60 秒连接复用
一、起因
Rowboat 在 HN 上周从 168 涨到 219p,rowboatlabs/rowboat 16,703 stars + 1,660 forks,Apache-2.0,YC S24。先前写过整体架构(7-08 evening p/21263260),这次把视角收窄到 Code Mode —— 它怎么把 coding 任务委托给 Claude Code 或 Codex,以及那一层叫 ACP(Agent Client Protocol)的胶水代码长什么样。
HN 里 @ramnique 引述 segmenta(Rowboat Labs 联创)的关键澄清:
"There's no app-enforced sandbox today. However, coding tasks get delegated to Claude Code or Codex using ACP. Codex runs under its native OS sandbox (Seatbelt on macOS, bubblewrap on Linux) — so the security boundary moves to the adapter."
也就是:Rowboat 自己不实现 sandbox,沙箱下沉到下游 adapter。这跟"一层通用 VM 包住所有事情"的传统思路正好相反。
二、我做了什么
2.1 定位 Code Mode 的代码
入口 apps/x/packages/core/src/code-mode/acp/,12 个文件:
client.ts 15,556 B # ACP 协议客户端
engine-provisioner.ts 14,216 B # 引擎发现 + provision
manager.ts 12,166 B # 上层生命周期管理
agents.ts 5,040 B
claude-exec.ts 4,442 B
permission-broker.ts 3,785 B # 权限审批中介
shell-env.ts 2,013 B
session-store.ts 1,713 B
permission-registry.ts 1,689 B
types.ts 391 B
总和 ~60KB —— 不像 5MB 空壳,也不像 100MB 那种过度工程化。
2.2 manager.ts:连接复用的核心
const DISPOSE_GRACE_MS = 60_000; // 一个 turn 结束后 adapter 多保留 60s
const CANCEL_GRACE_MS = 2_000; // 用户 stop 后 2s 内 cancel, 否则 force-kill
两个时间常量是 Rowboat 工程取舍的精华:
- 60s grace —— 一个 coding "turn" 结束后 adapter 进程不立即销毁,在 60s 窗口内等下一次复用。
edit → test → fix这种连续工作流后两步直接复用 warm connection,省掉冷启动几秒 - 2s cancel grace —— 用户按 stop 时先发 ACP
session/cancel通知下游 engine 自己收拾;2s 内没响应就 force-kill(否则 hang 的 prompt 会把聊天锁死)
照这个思路抄,在自己 harness 里也得给"连接复用窗口"和"取消 grace"分别留出可调常量,否则用户连按 5 次 stop 都退不出去,或者每次 follow-up 都冷启动 3 秒。
2.3 engine-provisioner:协议握手 + 缓存
engine-provisioner.ts(14.2KB)动态发现下游 engine 能力:
async listModelOptions(agent: CodingAgent): Promise<CodeAgentModelOptions> {
const cached = this.modelOptionsCache.get(agent);
if (cached) return cached;
const broker = new PermissionBroker({ policy: 'yolo', ask: async () => 'reject' });
const client = new AcpClient({ agent, cwd: os.homedir(), broker, onEvent: () => {} });
try {
await client.start();
用一个 'yolo' policy 的 throwaway broker 启动短生命周期 client,只为读 /model picker 的能力列表,然后立刻 teardown。结果缓存到进程级 Map。
不用每次打开 UI 都 cold-start 一次 Claude Code 跑 /model。代价是引擎发布新模型时,需重启 Rowboat 才能 picker 上看到。
2.4 permission-broker.ts:免审批 + fallback
permission-broker.ts(3.8KB)是这次拆解里最值得抄的一段:
const READ_KINDS = new Set(['read', 'search', 'fetch', 'think']);
function memoryKey(ask: PermissionAsk): string {
return ask.kind ? `kind:${ask.kind}` : `title:${ask.title}`;
}
read / search / fetch / think 这 4 类工具调用自动免审批(只读不写)。其余执行类(edit / bash / write)走用户审批,审批结果用 kind:edit 这种结构化 key 缓存到 session 级,同一类操作只问一次。
pickOption 里有个容易踩坑的事 —— adapter 提供的 options 可能是子集(比如 Codex 只给 allow_once + reject_once,没有 allow_always)。fallback 链:
const order: Record<PermissionDecision, PermissionOptionKind[]> = {
allow_always: ['allow_always', 'allow_once'],
allow_once: ['allow_once', 'allow_always'],
reject: ['reject_once', 'reject_always'],
};
policy 想"始终允许"但 adapter 只给"一次性允许"时,自动降级到"一次性允许"。这种"宽松意图 + 严格 capability"的双层 fallback,在写自己的 permission 系统时一定要有,否则用户选了"始终允许"实际只生效一次,会以为是 bug。
三、效果 / 数字
- 协议层:
~60KB(12 文件 TS),client.ts15.5KB /engine-provisioner.ts14.2KB /manager.ts12.2KB - 连接:
DISPOSE_GRACE_MS = 60_000(60s),CANCEL_GRACE_MS = 2_000(2s) - 免审批:
READ_KINDS = ['read', 'search', 'fetch', 'think'](4 类) - Star/fork:16,703/1,660,YC S24
四、还没完全搞清楚的几个点(局限与待验证项)
- 不做 OS sandbox,完全依赖下游 adapter(待验证) ——
@ramnique明确说没有 Rowboat 二级 sandbox 是设计选择。本地 Ollama / LM Studio 时,Code Mode 委托给 Claude Code / Codex 进程没有额外隔离 - 60s grace 在 background agent 下会被中断(待验证) —— 普通 chat 够用,但 background agent(新邮件触发)进入时主进程可能因 idle 过 60s 已销毁 adapter,冷启动延迟 3-5 秒
- READ_KINDS 只 4 类,自定义 MCP tool 需 adapter 配合(不足) ——
kind: 'database-write'如果下游 Codex 不识别,permission broker 默认走用户审批,体验变慢 - peer-to-peer 多人协作还在探索(坑点) ——
@segmenta:"Today the UI and the server run as a single app... That's why we're exploring a peer-to-peer setup for group chats instead." 目前不支持两人共享会话 'Bring your own model'不等于'Bring any model'(还在调研) —— 本地模型有"suppressing background agents during chat"特殊处理,4B Qwen 跑 7 域知识图谱基本不可用,32GB M2 跑 13B+ 勉强- Apache-2.0 + 第三方 SaaS API key 双层依赖(不足) —— 至少需 Google OAuth / Deepgram / ElevenLabs / Exa / Composio 之一,完全离线不可行。
五、适用场景
| 场景 | 适合度 | 理由 |
|---|---|---|
| 个人 M2 Mac 32GB+Ollama 13B+ | ✅ | ACP + 60s grace + READ 免审批 是当下最完整的桌面 AI 集成 |
| 团队 email + 知识图谱 + 背景 agent | ✅ | Brain + Background agents 跟 Claude Desktop 拉开差距 |
| 企业内网 / 完全离线 | ❌ | 必须配至少 1 个第三方 API key |
| 多人协作 + 共享 context | ❌ | UI+server 单进程,P2P 在做但没发布 |
| 2-4GB RAM 老本 | ❌ | Electron + 知识图谱全开,380MB+ 内存 |
六、参考链接
- Repo:
https://github.com/rowboatlabs/rowboat(16,703 stars) - ACP 目录:
apps/x/packages/core/src/code-mode/acp/(12 文件,~60KB) - manager.ts:
https://github.com/rowboatlabs/rowboat/blob/main/apps/x/packages/core/src/code-mode/acp/manager.ts - permission-broker.ts:
https://github.com/rowboatlabs/rowboat/blob/main/apps/x/packages/core/src/code-mode/acp/permission-broker.ts - HN:
https://news.ycombinator.com/item?id=48819808(219p/99c) - 下载:
https://www.rowboatlabs.com/downloads - 上一篇:
https://www.cnblogs.com/ninghg/p/21263260(整体架构)
浙公网安备 33010602011771号