字节笔记本
2026年6月21日
hermes教程-浏览器CDP主管
后端支持
| 后端 | 对话框检测 | 对话框响应 | 框架树 | 通过 browser_cdp(frame_id=...) 实现 OOPIF Runtime.evaluate |
|---|---|---|---|---|
本地 Chrome(--remote-debugging-port)/ /browser connect | ✓ | ✓ 完整工作流 | ✓ | ✓ |
| Browserbase | ✓(通过桥接) | ✓ 完整工作流(通过桥接) | ✓ | ✓ |
| Camofox | ✗ 无 CDP(仅 REST) | ✗ | 通过 DOM 快照部分支持 | ✗ |
Browserbase 特性。 Browserbase 的 CDP 代理内部使用 Playwright,并在约 10ms 内自动关闭原生对话框,因此 Page.handleJavaScriptDialog 无法跟上。主管通过 Page.addScriptToEvaluateOnNewDocument 注入一个桥接脚本,该脚本用同步 XHR 覆盖 window.alert/confirm/prompt,目标是一个魔法主机(hermes-dialog-bridge.invalid)。Fetch.enable 在 XHR 触及网络之前拦截它们——对话框变成 Fetch.requestPaused 事件,由主管捕获,respond_to_dialog 通过 Fetch.fulfillRequest 使用注入脚本解码的 JSON 主体来满足。
从页面的角度来看,prompt() 仍然返回代理提供的字符串。从代理的角度来看,无论哪种方式,都是相同的 browser_dialog(action=...) API。
Camofox 不受支持——没有 CDP 接口,仅 REST。
架构
CDPSupervisor
每个 Hermes task_id 在后台守护线程中运行一个 asyncio.Task。持有到后端 CDP 端点的持久 WebSocket。维护:
- 对话框队列 —
List[PendingDialog],包含{id, type, message, default_prompt, session_id, opened_at} - 框架树 —
Dict[frame_id, FrameInfo],包含父级关系、URL、来源、是否为跨源子会话 - 会话映射 —
Dict[session_id, SessionInfo],以便交互工具可以路由到正确的附加会话以进行 OOPIF 操作 - 最近的控制台错误 — 最近 50 个错误的环形缓冲区,用于诊断
附加时订阅:
Page.enable—javascriptDialogOpening、frameAttached、frameNavigated、frameDetachedRuntime.enable—executionContextCreated、consoleAPICalled、exceptionThrownTarget.setAutoAttach {autoAttach: true, flatten: true}— 显示子 OOPIF 目标;主管在每个目标上启用Page+Runtime
通过快照锁实现线程安全的状态访问;工具处理程序(同步)读取冻结的快照而无需等待。
生命周期
- 启动:
SupervisorRegistry.get_or_start(task_id, cdp_url)— 由browser_navigate、Browserbase 会话创建、/browser connect调用。幂等。 - 停止: 会话拆除或
/browser disconnect。取消 asyncio 任务,关闭 WebSocket,丢弃状态。 - 重新绑定: 如果 CDP URL 更改(用户重新连接到新的 Chrome),则停止旧的主管并启动新的主管——状态永远不会跨端点重用。
对话框策略
可通过 config.yaml 在 browser.dialog_policy 下配置:
must_respond(默认)— 捕获,在browser_snapshot中显示,等待显式的browser_dialog(action=...)调用。在 300 秒安全超时后无响应,自动关闭并记录。防止有缺陷的代理无限期停滞。auto_dismiss— 记录并立即关闭;代理事后通过browser_snapshot中的browser_state看到它。auto_accept— 记录并接受(对于beforeunload很有用,工作流希望干净地导航离开)。
策略是每个任务;没有每个对话框的覆盖。
代理接口
browser_dialog 工具
browser_dialog(action, prompt_text=None, dialog_id=None)
action="accept"/"dismiss"→ 响应指定的或唯一的待处理对话框(必需)prompt_text=...→ 提供给prompt()对话框的文本dialog_id=...→ 当多个对话框排队时消除歧义(很少见)
工具仅用于响应。代理在调用之前从 browser_snapshot 输出中读取待处理对话框。
browser_snapshot 扩展
当主管附加时,向现有快照输出添加三个可选字段:
{
"pending_dialogs": [
{"id": "d-1", "type": "alert", "message": "Hello", "opened_at": 1650000000.0}
],
"recent_dialogs": [
{"id": "d-1", "type": "alert", "message": "...", "opened_at": 1650000000.0,
"closed_at": 1650000000.1, "closed_by": "remote"}
],
"frame_tree": {
"top": {"frame_id": "FRAME_A", "url": "https://example.com/", "origin": "https://example.com"},
"children": [
{"frame_id": "FRAME_B", "url": "about:srcdoc", "is_oopif": false},
{"frame_id": "FRAME_C", "url": "https://ads.example.net/", "is_oopif": true, "session_id": "SID_C"}
],
"truncated": false
}
}-
pending_dialogs— 当前阻塞页面 JS 线程的对话框。代理必须调用browser_dialog(action=...)来响应。在 Browserbase 上为空,因为它们的 CDP 代理在约 10ms 内自动关闭。 -
recent_dialogs— 最多 20 个最近关闭的对话框的环形缓冲区,带有closed_by标签:"agent"(我们响应了)、"auto_policy"(本地 auto_dismiss/auto_accept)、"watchdog"(must_respond 超时触发)或"remote"(浏览器/后端为我们关闭了它,例如 Browserbase)。这是 Browserbase 上的代理仍然能够了解发生了什么的方式。 -
frame_tree— 框架结构,包括跨源(OOPIF)子框架。上限为 30 个条目 + OOPIF 深度 2,以限制广告繁重页面上的快照大小。当达到限制时显示truncated: true;需要完整树的代理可以使用带有Page.getFrameTree的browser_cdp。
这些都没有新的工具模式接口——代理读取它已经请求的快照。
可用性门控
两个接口都依赖于 _browser_cdp_check(主管仅在 CDP 端点可访问时才能运行)。在 Camofox / 无后端会话上,对话框工具被隐藏,快照省略新字段——没有模式膨胀。
跨源 iframe 交互
browser_cdp(frame_id=...) 通过主管已连接的 WebSocket 使用 OOPIF 的子 sessionId 路由 CDP 调用(特别是 Runtime.evaluate)。代理从 browser_snapshot.frame_tree.children[] 中选取 is_oopif=true 的 frame_id,并将其传递给 browser_cdp。对于同源 iframe(没有专用的 CDP 会话),代理改为使用顶级 Runtime.evaluate 中的 contentWindow/contentDocument——当 frame_id 属于非 OOPIF 时,主管会显示指向该回退的错误。
在 Browserbase 上,这是 iframe 交互的唯一可靠路径——无状态 CDP 连接(每次 browser_cdp 调用打开)会遇到签名 URL 过期,而主管的长连接保持有效会话。
文件布局
tools/browser_supervisor.py—CDPSupervisor、SupervisorRegistry、PendingDialog、FrameInfotools/browser_dialog_tool.py—browser_dialog工具处理程序tools/browser_tool.py—browser_navigate启动钩子、browser_snapshot合并、/browser connect重新附加、_cleanup_browser_session拆除toolsets.py— 在browser、hermes-acp、hermes-api-server和核心工具集中注册browser_dialog(取决于 CDP 可达性)hermes_cli/config.py—browser.dialog_policy和browser.dialog_timeout_s默认值
非目标
- 检测/交互 Camofox(上游差距,单独跟踪)
- 实时流式传输对话框/框架事件给用户(需要网关钩子)
- 跨会话持久化对话框历史(仅内存)
- 每个 iframe 的对话框策略(代理可以通过
dialog_id表达) - 替换
browser_cdp——它仍然是处理长尾问题(cookies、视口、网络节流)的逃生舱口
测试
单元测试(tests/tools/test_browser_supervisor.py)使用一个 asyncio 模拟 CDP 服务器,该服务器实现了足够的协议来执行所有状态转换:附加、启用、导航、对话框触发、对话框关闭、框架附加/分离、子目标附加、会话拆除。真实后端 E2E(Browserbase + 本地 Chromium 系列浏览器)是手动的——通过 /browser connect 连接到活动的 Chromium 系列浏览器并运行上述对话框/框架测试用例。