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.ts 15.5KB / engine-provisioner.ts 14.2KB / manager.ts 12.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(整体架构)
posted @ 2026-07-22 19:18  Ninghg  阅读(8)  评论(0)    收藏  举报