多会话配对
多个 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):
{ "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 —— 所以每条连接收到的是为它单独推导的列表。
断线与重连
三层兜底:
- 优雅关闭 — server 正常退出发 WS close code
4000,扩展立即摘除该端点,一次都不重试 - 失败上限 — 被 kill 或机器休眠时,连续失败 10 次转 dormant,停止重试但保留信任记录
- 唤醒 — 设置页手动重连、新配对、或浏览器重启
v0.0.57 之前
重连没有终止条件:退避到 30s 封顶但永不停止,而 15 秒的心跳 alarm 一看到 socket 关闭就直接 重连、不看退避 —— 实际是固定 15 秒重试到永远,指数退避形同虚设。