ByteNoteByteNote

字节笔记本

2026年6月21日

hermes教程-浏览器CDP主管

API中转
¥120

后端支持

后端对话框检测对话框响应框架树通过 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.enablejavascriptDialogOpeningframeAttachedframeNavigatedframeDetached
  • Runtime.enableexecutionContextCreatedconsoleAPICalledexceptionThrown
  • Target.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.yamlbrowser.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 扩展

当主管附加时,向现有快照输出添加三个可选字段:

json
{
  "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.getFrameTreebrowser_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.pyCDPSupervisorSupervisorRegistryPendingDialogFrameInfo
  • tools/browser_dialog_tool.pybrowser_dialog 工具处理程序
  • tools/browser_tool.pybrowser_navigate 启动钩子、browser_snapshot 合并、/browser connect 重新附加、_cleanup_browser_session 拆除
  • toolsets.py — 在 browserhermes-acphermes-api-server 和核心工具集中注册 browser_dialog(取决于 CDP 可达性)
  • hermes_cli/config.pybrowser.dialog_policybrowser.dialog_timeout_s 默认值

非目标

  • 检测/交互 Camofox(上游差距,单独跟踪)
  • 实时流式传输对话框/框架事件给用户(需要网关钩子)
  • 跨会话持久化对话框历史(仅内存)
  • 每个 iframe 的对话框策略(代理可以通过 dialog_id 表达)
  • 替换 browser_cdp——它仍然是处理长尾问题(cookies、视口、网络节流)的逃生舱口

测试

单元测试(tests/tools/test_browser_supervisor.py)使用一个 asyncio 模拟 CDP 服务器,该服务器实现了足够的协议来执行所有状态转换:附加、启用、导航、对话框触发、对话框关闭、框架附加/分离、子目标附加、会话拆除。真实后端 E2E(Browserbase + 本地 Chromium 系列浏览器)是手动的——通过 /browser connect 连接到活动的 Chromium 系列浏览器并运行上述对话框/框架测试用例。



分享: