oh-my-pi Browser Relay 实战:让 omp 通过 Chrome 扩展驱动你现有的浏览器标签页
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
本文聚焦 oh-my-pi(omp)项目中的@oh-my-pi/browser-relay组件,讲解它如何通过一个 Chrome MV3 扩展与本地 CDP Relay 服务,让 Eval 的browserAPI 直接驱动你现有的Chrome 标签页(包括已登录会话),而无需重启浏览器或手动暴露调试端口。读完本文,你将掌握 browser relay 的架构原理、安装步骤、两种 opt-in 配置方式(app.relay与browser.relay)、omp browser-relay的常用参数,以及它的安全边界与开发验证方法。
为什么需要 Browser Relay:Chrome 136 之后的新约束
传统上,让自动化工具驱动 Chrome 的做法是携带--remote-debugging-port重启浏览器。但根据 browser-relay 包 README 的说明,Chrome 136+ 会拒绝在默认 profile 上使用该启动参数,这使得"复用已登录、已配置好的日常浏览器"这条路基本被堵死。
@oh-my-pi/browser-relay的解法是绕开启动参数,改用 Chrome 官方支持的扩展机制:
- 浏览器侧:一个Chrome MV3 扩展,通过
chrome.debuggerAPI 附加到标签页并收发 CDP 命令; - 进程侧:一个随 omp CLI 分发的relay 服务器(
omp browser-relay,源码位于 packages/coding-agent/src/tools/browser/relay/),对外伪装成 Chrome 的 CDP discovery endpoint,供 omp 的 browser 工具(基于 puppeteer)以普通browserURL方式连接。
这套组合的首次发布记录在 CHANGELOG.md 的17.2.5(2026-08-03)条目中:MV3 扩展使 omp browser tool 能够通过chrome.debugger附加并驱动现有浏览器标签页,同时引入了自动、健壮的标签页管理能力——将 agent 正在驱动的标签页归入每个窗口专属的 "omp" 标签组,并在断开连接时干净地解散该分组。
整体架构:扩展、中继服务与多路复用
relay 架构由三个角色组成,它们的分工在 server.ts 的模块注释中定义得非常清晰:
| 端点 | 角色 |
|---|---|
GET /json/version | 伪装 Chrome 的 CDP discovery 握手;扩展连接完成后返回 200 及webSocketDebuggerUrl,尚未连接时返回 503(客户端如waitForCdp会持续轮询) |
GET /json、GET /json/list | 可附加的页面目标列表(调试辅助) |
WS /cdp | 下游 CDP 客户端(puppeteer)接入点 |
WS /ext | Chrome 扩展接入点(配置 token 时做门控) |
关键难点在于:Chrome 每个标签页只允许一个chrome.debugger附加,而 omp 的 browser 工具会为每个被驱动的标签页建立两条 puppeteer 连接(一条 supervisor、一条 tab worker)。relay 的 bridge 在 bridge.ts 中解决了这一冲突:它通过扩展在每个标签页上只维护一个chrome.debugger附加,再用派发的会话 ID 把所有下游连接复用到这条通道上。同时,chrome.debugger本身不暴露浏览器级 target 和Target.*层级,bridge 负责合成这部分表面(参考了 puppeteer-core 的cdp/ExtensionTransport.ts)。
从源码结构看,扩展与 relay 之间的线协议定义在 protocol.ts:扩展主动拨号到ws://127.0.0.1:<port>/ext,交换 JSON 消息。relay 通过带编号的 RPC 驱动扩展,扩展则把标签页生命周期事件与chrome.debugger事件实时推送回去。
安装:一条命令 + 开发者模式加载
扩展的安装分两步,均在 README.md 中有明确说明:
- 运行
omp browser-relay install,该命令会把打包好的扩展写入~/.omp/browser-relay/extension; - 打开
chrome://extensions,开启Developer mode,点击Load unpacked选择上述目录加载。
也可以直接从发布资产中获取omp-browser-relay-extension.zip解压加载。扩展的清单文件位于 extension/manifest.json,声明了debugger、tabs、tabGroups、storage、alarms五个权限,并提供了独立设置页options.html(点击工具栏图标即可打开设置)。
两种 Opt-in 方式:单次调用与全局默认
18.0.7(2026-08-26)这一版的 CHANGELOG 专门澄清了两个 opt-in 路径的范围差异,这是配置 relay 最容易混淆的地方:
方式一:按调用启用(per-call)
在 Eval 的browser.open(...)中传入app: { relay: true }。这种方式只对这一次调用生效,不持久化任何状态,其他调用和会话的默认行为保持不变。适合偶尔需要操作真实浏览器、不想改变全局行为的场景。
方式二:设为默认(as the default)
运行omp config set browser.relay true,relay 会成为该 profile 下所有会话、所有项目的默认驱动方式。需要特别注意的是优先级关系:项目级设置、环境变量PI_BROWSER_RELAY、以及显式的app选择(方式一)仍然优先于这个默认值。
一旦启用,任何会话里普通的browser.open(...)调用都会驱动你的真实浏览器——包括你没有正在观看的后台会话。这里有一个值得留意的副作用(README 原话):如果不带app.target,这样的调用会采纳当前可见的标签页;而如果调用携带了url,它会把这个标签页导航离开你正在阅读的内容。因此在开启默认模式前,请确认这对你的日常浏览体验是可接受的。
目标选择与 "omp" 标签组
选择哪个标签页:通过app.target按 URL/title 的子串匹配来指定具体标签页;不提供时,omp 采纳当前可见的标签页且不抢焦点。对应到 relay 协议层,扩展在 background.ts 中通过activateTabRPC 实现焦点与激活切换。
标签页分组管理:这正是 CHANGELOG 17.2.5 提到的"自动、健壮的标签页管理"。omp正在主动驱动的标签页会被收集进每个窗口的"omp" 标签组(青色),当 omp 释放该标签页时分组被解除,断开连接时分组合部解散。其余标签页、固定标签页(pinned)、你自己创建的组、以及你手动拖出的标签页都不会被触碰。可以在omp browser-relay时加--no-group禁用该行为。
分组逻辑的健壮性在源码里有充分体现:
- 扩展端把"查询→分组→设标题"这一非原子序列串行化(
enqueueGroupOp,见 background.ts),避免并发竞态产生重复的 "omp" 组,并会"愈合"历史竞态留下的同标题重复组; - 固定标签页永远不会被分组(Chrome 分组会静默取消固定);
- relay 端通过
groupOptOut机制尊重用户意愿:如果你手动把标签页拖出 omp 组,relay不会再把它组回去,也不会与用户对抗(见 bridge.ts 的#onTabUpsert逻辑)。
自动启动:全局 daemon broker 与租约机制
你不需要手动运行 relay——当 Eval 的 browser API 第一次需要它时,relay 会在 omp 的profile 无关的全局 daemon broker下自动启动。实现细节在 daemon.ts:
- 由于 MV3 扩展只能向外拨号(service worker 无法监听 socket),必须有一个原生进程持有 relay 端口,这个进程就由 broker 拉起;
- 每个 relay consumer 都持有 broker 租约(lease),因此一个项目退出不会中断另一个项目;只有当所有项目的最后一个 consumer 退出后,服务才会停止;
- 如果某个 relay 已经手动占用了端口,consumer 会采纳现成的服务而不会争夺绑定;
- 启动是幂等且可自愈的:先探测
http://<cdpUrl>/json/version,若返回 503(等待扩展)或 2xx 即视为就绪;发现"记录在案但无响应"的僵死 daemon 会替换重启,跨进程启动竞争则通过最多 3 轮"探测→描述→启动"收敛。
扩展连接后,工具栏 badge 会显示on(对应源码中 background.ts 的setBadge:绿色 "on" / 灰色 "off")。
omp browser-relay命令行参数
该命令在 cli-reference.md 中登记为:"运行 Eval 的 browser API 用来驱动你自己的 Chrome 标签页的本地 CDP relay"。日常使用中你通常不需要手动运行它,只在以下三种情况才需要:
| 参数 | 作用 |
|---|---|
--token <secret> | 为/ext端点设置共享密钥,扩展在设置页中填入相同 token 后以?token=形式携带;用于防止本机不可信进程驱动你的已登录浏览器 |
--no-group | 禁用 "omp" 标签组分组行为 |
--port <port> | 指定非默认端口(扩展默认连接 9224,见 background.ts) |
relay 服务器只绑定loopback(127.0.0.1),并在握手层做了两道防线(见 server.ts):/cdp拒绝带Origin头的升级请求(防止网页驱动 relay),/ext只接受chrome-extension://来源并校验 token。
扩展内部实现:一个"哑管道"式的 MV3 Service Worker
扩展的设计哲学在 background.ts 的注释里写得很直白:按设计保持哑管道(dumb pipe),所有 CDP 编排逻辑都放在 relay 服务器侧。service worker 只做三件事:
- 保持一条到 relay 的 WebSocket;
- 执行 relay 下发的 RPC(
attach、detach、send、createTab、removeTab、activateTab、group、ungroup,对应 protocol.ts); - 把
chrome.debugger事件和标签页增删改事件流式推回。
针对 MV3 service worker 的生命周期问题,扩展做了三重保活与恢复设计:
- 连接期间:打开状态的 WebSocket 本身 + 每 20 秒一次的
ping(Chrome 116+ 支持); - 断开被回收后:
chrome.alarms每 0.5 分钟触发一次connect()重新拨号(background.ts); - 重连采用指数退避(1 秒起步、上限 10 秒),并对 relay 主动发起的 detach 与用户取消(如关闭调试 infobar)做了区分,避免把替换 socket 误判为用户操作;
- 分组标题写入
chrome.storage.session,即使 service worker 重启后仍能正确解散 omp 组; - 监听
chrome.storage.local变化:设置页修改端口/token 后立即断开并用新参数重拨。
每次连接时,扩展通过hello消息上报浏览器版本、全量标签页快照和当前已附加的标签页 ID,relay 据此做状态对账,并在 service worker 重启丢失附加后尽力恢复持有会话的标签页附加(见 bridge.ts)。
协议与多路复用细节
对下游 puppeteer 客户端而言,bridge 模拟了这些行为(详见 bridge.ts):
Browser.getVersion、Target.getBrowserContexts等浏览器级命令由 bridge 直接应答;Target.setDiscoverTargets/Target.setAutoAttach/Target.attachToTarget/Target.createTarget/Target.closeTarget/Target.activateTarget等被转译为对扩展的 RPC;Browser.close会被拒绝并忽略——relay 绝不关闭用户的真实浏览器;- 会话 ID 有明确命名空间:
ST<tab>.<conn>.<n>为 minted tab 伪会话(仅用于满足 Target 层级)、SP<tab>.<conn>.<n>为 minted page 伪会话(转发到标签页根 debugger 会话)、OOPIF/worker 等真实子会话则原样透传; - Runtime 域做了状态机管理(
default/enabled/disabled),保证多个下游连接共享一个根Runtime.enable循环,并对新启用者重放已存在的 execution context。
限制与安全边界
以下是官方 README 明确列出的限制,使用前务必知晓:
chrome://、DevTools、Web Store 以及其他扩展页面不可附加,对 agent 完全隐藏(对应 bridge.ts 中的不合法 URL 正则);- 只要有任何标签页被附加,Chrome 就会显示 "is debugging this browser" 的提示条;关闭该提示条会分离标签页,直到该标签页再次导航才会恢复;
- 打开了 DevTools 的标签页无法附加(每个标签页只允许一个 debugger,这正是 relay 要为自身客户端做多路复用的原因);
- 安全上,任何能访问 relay 端口的东西都能驱动你的已登录浏览器,因此 relay 只绑定 loopback;若本机存在不可信进程,请务必使用
--token。
开发与验证:构建与端到端冒烟
对于想参与开发或自行验证的读者,package.json 与 README 的 Development 一节给出了两条命令:
bun run build:将扩展打包到dist/extension/,压缩出用于发布的 zip,并重新生成 omp CLI 内嵌的安装资产(位于 packages/coding-agent/src/tools/browser/relay/extension-assets/),这些资产需要提交;bun scripts/smoke.ts [relay-url] [target-substring]:端到端冒烟测试,完整复刻 omp 的 supervisor + tab worker 双连接模式:先通过browserURL建立 supervisor 连接并发现目标,再通过browserWSEndpoint建立 worker 连接按 ID 采纳目标,随后依次执行evaluate、Page.getFrameTree(额外 CDP 会话)、goto导航、screenshot截图,以及Target.createTarget/closeTarget新建与关闭标签页的路径,最后断开两条连接并打印SMOKE OK。
结合 CHANGELOG.md 的演进记录可以看到,browser relay 从 17.2.5 的"首版 MV3 扩展 + omp 标签组管理",到 18.0.7 的"opt-in 路径范围澄清",其核心价值始终稳定:让 coding agent 安全、可配置地接管你日常使用的真实浏览器,而不是在隔离环境里另起炉灶。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考