Skip to content
learnspace
Go back

omp Browser Relay 全解:让 agent 直接驱动你登录着的 Chrome

这是「omp 编程工具使用探讨」专栏的又一篇追加文章。前面几篇讲的都是 agent 的”脑”——Rules、TTSR、Skill、Memory、多 agent、内部 URL;这篇讲”手”:怎么让 omp 真正操作你日常在用的那个浏览器。素材来自我刚做完的一次实测——omp 在 WSL2 里,Chrome 在 Windows 上,最后 agent 成功驱动了真实标签页。

TL;DR:想用你的登录态,共享 user-data-dir 是死路(DPAPI 加密 + profile 锁 + 正常启动的 Chrome 根本不监听 CDP 端口)。正解是 browser.relay:WSL 里跑一个冒充 Chrome CDP 发现端点的本地中继,Windows Chrome 里的 MV3 扩展用 chrome.debugger 主动拨回来,omp 的 puppeteer 连中继就能落到你真实浏览器的标签页上。relay 模式不启动任何浏览器——在 WSL 里开 Chrome 是错的解法。最大的坑是 relay 会过滤 chrome:// 等内部页,没有普通网页时报 No page targets available on the attached browser

目标不是”能上网”,是”用你的身份上网”

先把需求说准。让 agent 上网不难,难的是让它用你已登录的身份上网:后台管理台、内网系统、登录过的 SaaS——agent 自己打开是登录页,用你日常那个浏览器打开就是登录态。

omp 的 browser 工具(Eval 里的 browser facade)有几种浏览器来源(docs/tools/browser.md):

模式触发方式浏览器从哪来有你的登录态吗
headless默认omp 在项目里共享的 Chromium
spawnedapp.path指定可执行文件新起一个❌(独立 profile)
connectedapp.cdp_url连一个已在跑的 CDP 端点取决于那个端点
relayapp.relay: truebrowser.relay你自己那个 Chrome

显式指定时的优先级是 app.cdp_urlapp.pathapp.relay;不显式指定时,先看 relay 设置,再看配置的 CDP 端点,然后 cmux(omp 连接 WKWebView 表面的后端),最后才回落到项目共享的无头 Chromium(tools/browser.tsresolveBrowserKind)。

命名规则:app.* 是单次 browser.open 的调用参数,browser.* 是全局配置项。app.cdp_urlbrowser.cdpUrl 就是同一个东西的两种形态。

为什么”共享用户数据目录”走不通

最初的问题很直接:能不能让 omp 的浏览器和 Windows 上的 Chrome 共享用户数据(把 --user-data-dir 指过去),这样登录信息就直接可用?三条硬伤:

  1. Windows Chrome 的 cookie/密码是 DPAPI 加密的,密钥绑定 Windows 用户账户。WSL 里的 Linux 浏览器进程解不开这些密文,profile 拷过去登录态全部失效。
  2. 同一 profile 目录不能被两个实例同时占用——Chrome 有单例锁,正开着的时候第二个进程进不去。
  3. CDP 直连也走不通(再退一步:不共享目录,直接去连正在运行的那个 Chrome 呢?):正常双击启动的 Chrome 根本不监听调试端口。本次实测里主进程的命令行就是干干净净的 "C:\Program Files\Google\Chrome\Application\chrome.exe",没有任何 flag。omp 的浏览器启动路径遇到”同机已有 Chrome 在跑、却没有可复用 CDP 端点”时,会直接报错(tools/browser/attach.tsfindReusableCdp):
Cannot launch chrome.exe because it is already running without a reusable CDP endpoint.
Close chrome.exe, relaunch it with --remote-debugging-port, or pass app.cdp_url for an existing endpoint.

(注意这条来自”同机启动浏览器”的路径;在 WSL + Windows 的组合里,你更可能先撞上后文那个 No page targets available。)

要让 Chrome 监听调试端口,就得带 --remote-debugging-port 重启;而 Chrome 官方近年来专门发文收紧了这类开关(Changes to remote debugging switches to improve security)。就算你愿意重启,代价也是把当前所有窗口关掉。

于是 relay 的定位就清楚了:不碰你的 profile,不要求你重启浏览器,而是从浏览器内部开一扇门

Relay 架构:一次”倒着拨号”的 CDP 中继

+------------------------------+ +----------------------------------+
| Chrome (Default profile) | | omp session |
| +-- your tabs (logged in) | | +-- prelude (puppeteer) |
| +-- Relay ext (MV3) | | | |
| | chrome.debugger API | | | /cdp |
| | | | v |
| +-- /ext -------------+ | | --> relay @127.0.0.1:9224 |
+------------------------------+ +----------------------------------+
(扩展主动拨出 ws://127.0.0.1:9224/ext)

几个关键点,逐条对应源码:

架构不复杂,动手也就四步。

装起来:四步

Terminal window
# 1. 生成扩展(在 WSL 里执行),默认写到 ~/.omp/browser-relay/extension
omp browser-relay install
# 2. 复制到 Windows 侧(建议,原因见下)
cp -r ~/.omp/browser-relay/extension /mnt/c/Users/<你>/omp-relay-ext
# 3. Chrome → chrome://extensions → 打开开发者模式
# → “加载已解压的扩展程序” → 选第 2 步复制到 Windows 的那个目录
# 4. 打开开关
omp config set browser.relay true

第 4 步之后不需要手动起中继。omp 在真正需要浏览器时才懒启动一个 broker 托管的守护进程(relay/daemon.ts):守护进程名 omp.browser.relay,broker scope browser-relay,是机器全局单例——多个项目共享同一个中继,任何一个项目退出都不会把它拆掉。想手动起(比如加 token 或调试)才用 omp browser-relay

换端口要成对配置:omp browser-relay serve -p 9333 之后,omp 侧 omp config set browser.relayUrl http://127.0.0.1:9333,扩展 options 页里也填同一个端口(配了 token 同理)。

第 2 步为什么建议复制:直接从 \\wsl.localhost\... 的 UNC 路径加载扩展也能用(本次实测就是这么装的,Chrome 接受了 UNC 路径),但很脆弱——WSL 没启动、发行版改名、路径变动,Chrome 启动时扩展就会加载失败。复制到 Windows 本地可以解除对 WSL 启动顺序的依赖。

扩展本身很轻:MV3,权限只有 debuggertabstabGroupsstoragealarmsextension-assets/manifest.json.txt)。它会每 20 秒 ping 一次中继,断线后按 1s→10s 指数退避重连,另外挂了一个 0.5 分钟的 keepalive alarm 兜住 service worker 被回收的情况(background.js)。连通后工具栏图标会显示 on

第 5 步:验证与排错

装完先确认三件事:

  1. 工具栏图标变成 on——扩展已连上中继。
  2. Chrome 里至少开着一个普通网页,否则第一次调用必然报 No page targets available on the attached browser(原因见下文「机制细节 1」)。
  3. 最小冒烟测试:
const tab = await browser.open({ app: { relay: true }, url: "https://example.com" });
await tab.title(); // "Example Domain"

读回 URL/Title 就算通。失败时按这个顺序查:

现象先查什么
图标不是 on扩展是否启用、options 页端口是否与中继一致
图标是 on,但报 No page targets开一个普通网页(chrome:// 不算)
连接被拒 / 中继起不来.wslconfiglocalhostForwarding 是否被关掉
想看清中继在干什么omp ps logs omp.browser.relay --global browser-relay

实测证据链

下面是我这次装完之后的实测结果。

步骤结果
扩展位置Chrome Default profile,路径 \\wsl.localhost\Ubuntu-24.04\home\shanyou\.omp\browser-relay\extensionlocation=4(未打包扩展)
中继连通WSL 内 omp.browser.relay 监听 127.0.0.1:9224/json/versionChrome/152.0.0.0(实测时你 Chrome 的实时版本,由扩展从 UA 提取)、UA Windows NT 10.0
新建标签页经中继发 CDP Target.createTarget → 返回 PAGE848164796;Chrome 里真的开出 example.com,中继日志出现 grouped tabs(收进 “omp” 标签组)
驱动页面browser.open({ app: { relay: true } }) 复用该标签,URL/Title 正确;tab.evaluate 在页面里执行 JS 得到 links=1
自动启动路径停掉手动中继后,browser.relay=true 触发 broker 自动拉起守护进程并重连成功
清理测试标签页用 CDP Target.closeTarget 关闭;用户自己的标签页未受影响

Default profile 这点也有据可查:Secure Preferences 里该扩展的 path/location 与上表一致。也就是说,agent 打开你登录过的站点,拿到的就是已登录状态。顺带一提,relay 模式下 tab.close({ kill: true }) / browser.close({ kill: true }) 只释放 omp 侧的句柄,不会关闭或杀掉你的 Chromedocs/tools/browser.md)。

证据链之外,还有三个机制细节值得单独拎出来——第一个就是最容易踩的坑。

三个必须知道的机制细节

1. chrome:// 会被过滤 —— “No page targets”

这是最容易踩的坑。中继有一条过滤正则(relay/bridge.ts):

/** URLs `chrome.debugger` cannot attach to; hidden from downstream discovery entirely. */
const INELIGIBLE_URL = /^(chrome|devtools|edge|view-source|chrome-extension|chrome-untrusted|chrome-search):/i;

chrome.debugger 无法 attach 这些内部页,所以它们从下游的发现结果里被整体隐藏。本次实测一开始就栽在这里:扩展首次连上中继时只上报了 2 个标签页(hello 是它的握手消息),而 /json/list 返回 []——那 2 个都是 chrome:// 内部页。此时 omp 报:

No page targets available on the attached browser

tools/browser/attach.tspickElectronTarget:可发现的 page target 和 browser.pages() 都为空时抛出。)

结论:relay 需要至少一个普通网页标签。要么你自己开一个,要么让 agent 直接 browser.open({ url: ... }) 新建——新建走的是扩展的 createTabchrome.tabs.create),不受这条过滤影响,实测中它正是这样开出了第一个可用标签。

2. 标签组是”agent 正在控制”的可视信号

relay 默认把 agent 真正驱动的标签收进一个名为 omp 的 Chrome 标签组(DEFAULT_GROUP = { title: "omp", color: "cyan" }relay/server.ts)。两条相关规则:

不想要分组:omp browser-relay serve --no-group

3. 中继只绑回环,且有 token 与 Origin 两道门

relay/server.ts 绑定 127.0.0.1,源码注释把风险写得很直白:“anything that can reach this port can drive the user’s logged-in browser.”

两道防护:

安全边界:它操作的是”你”

这条比任何技术细节都重要。官方文档的措辞值得原样引用(docs/tools/browser.md):

Relay and attached modes operate on real logged-in sessions; sites attribute actions to the user. Name a target or create a dedicated tab. Never navigate the user’s visible tab or take a consequential action without direct authorization.

实践上:

什么时候不要用 relay

场景更合适的选择
抓公开网页、跑无头脚本默认 headless(更快、无副作用)
需要干净环境复现问题app.path 起一个独立 profile
已有稳定的调试端点app.cdp_url(或 browser.cdpUrl 设默认)
只是要读一篇静态文章read <url>(根本不启动浏览器)

总结

一句话:

omp 跑在 WSL、Chrome 跑在 Windows、登录态在 Chrome 里——relay 用一个冒充 CDP 发现端点的本地中继加一个 MV3 扩展,把 chrome.debugger 翻译成 CDP,让 puppeteer 直接落到你真实浏览器的标签页上。

值得带走的四点:

  1. 别共享 profile:DPAPI 加密、profile 单例锁、没有 CDP 端口,三条路都堵死
  2. relay 模式不启动浏览器:WSL 里只跑中继;在 WSL 里开 Chrome 是错的解法,因为那是另一个身份
  3. chrome:// 会被过滤:没有普通网页就是 No page targets available,先开一个页面或让 omp 新建
  4. 它操作的是”你”:站点看到的是你的账号与 IP,不可逆动作要逐次确认

上一篇埋下的 local:// 钩子,下一篇来还:用 write local://plan.md + task 指令引用,把超长上下文传给子代理而不撑爆对话(之前只在内部 URL 那篇带过一笔)。

本文是「omp 编程工具使用探讨」专栏的追加篇。源码引用来自 oh-my-pi 当前版本(相对 packages/coding-agent/src/tools/browser/relay/tools/browser/attach.tscli/browser-relay-cli.tsconfig/settings-schema.tsdocs/tools/browser.md),具体实现可能随版本变化,建议以实际源码为准。


Share this post:

Previous Post
omp 内部 URL 全解:14 个 scheme 让 agent 统一寻址所有资源