Skip to content

多会话配对

多个 Claude Code 会话可以同时驱动同一个浏览器,各自一条连接。这一页解释它怎么运作, 以及为什么是这个形状。

为什么需要配对

MCP server 进程是 WS 服务端,扩展是客户端。 MV3 的 service worker 没有监听端口的能力, 所以方向只能如此。两个后果决定了整个设计:

  • 端口会冲突,因为每个 server 都要绑一个
  • 发现只能由扩展发起,因为服务端没法主动找到浏览器

配对页就是补第二点的 —— server 在自己的端口上提供一个页面,那是它把端口告知扩展的 唯一途径。

流程

会话启动           → 什么都不做,不绑端口
tools/list         → 不碰端口
第一次 browser_*   → 绑一个空闲端口 → 打开 http://127.0.0.1:<port>/pair
                   → 你点「允许」→ 扩展接入 → 重试那次调用

不碰网页的会话全程零副作用,也就不可能和别的会话撞端口。

批准 UI 由扩展注入(closed shadow DOM),不是配对页自带的按钮 —— 让请求方画自己的 「允许」按钮,等于让它自己签发许可。配对页只负责把 {installId, secret, sessionId, label, pid, port} postMessage 出去;扩展的 content script 收到后交给 service worker 判断。

信任

信任是安装级的,存在 ~/.atwebpilot/identity.json(权限 0600):

jsonc
{ "installId": "inst_…", "secret": "…", "lastPort": 51234 }

批准一次,本机之后所有会话免确认。sessionId 和工作目录只用于展示与管理,不参与信任判断

为什么不按目录分身份

按 cwd 分身份的安全收益是假的:secret 就在 home 目录里,任何能读到它的进程都可以声称自己 cwd 是任意值。它真正带来的是 git worktree 和目录改名会被当成不同身份反复询问 —— 用户完全 不理解为什么。

情况行为
已信任,上次的端口还能绑静默重连,不开页不点击
已信任,端口变了开页,识别出是老熟人,自动连、自关
未知来源 / 已撤销开页 + 点「允许」

端口复用是「静默」成立的前提,身份校验是端口复用安全的前提 —— 光记端口会连到抢占该端口 的陌生进程上,靠 secret 不符把它挡掉。

防得住:本地进程静默夺取浏览器控制权。防不住:能读 ~/.atwebpilot/ 的进程 —— 但这样的 进程能干的坏事远不止这个。

设置页

扩展设置 → Coordinator 有两块:

已接入的会话
  ~/code/caiji2   pid 1234  :51234  ● connected   [断开]
  ~/code/wanxin   pid 5678  :51299  ○ dormant     [重连]

已信任
  本机 (inst_…)   首次授权 2026-08-18            [撤销]

断开只结束当前连接(会话会重连);撤销才清除凭据,此后每个会话都要重新确认。

tab 归属

list_tabs 返回的每个 tab 带三个字段:

字段含义
mine这个 tab 是你自己绑定的
busy别的会话绑着
busy_label占用方的工作目录

只警示,不拦截。 看到 busy 通常直接 openTab 开个新页就绕开了,但两个 agent 也可以 故意协作同一个页面。

同一个 tab 对持有者是 mine,对其他人是 busy —— 所以每条连接收到的是为它单独推导的列表。

断线与重连

三层兜底:

  1. 优雅关闭 — server 正常退出发 WS close code 4000,扩展立即摘除该端点,一次都不重试
  2. 失败上限 — 被 kill 或机器休眠时,连续失败 10 次转 dormant,停止重试但保留信任记录
  3. 唤醒 — 设置页手动重连、新配对、或浏览器重启

v0.0.57 之前

重连没有终止条件:退避到 30s 封顶但永不停止,而 15 秒的心跳 alarm 一看到 socket 关闭就直接 重连、不看退避 —— 实际是固定 15 秒重试到永远,指数退避形同虚设。

MIT License