Browser CDP Supervisor — Design

Status: Shipped (PR 14540) Last updated: 2026-04-23 Author: @teknium1

Problem

Native JS dialogs (alert/confirm/prompt/beforeunload) and iframes are the two biggest gaps in our browser tooling:

  1. Dialogs block the JS thread. Any operation on the page stalls until the dialog is handled. Before this work, the agent had no way to know a dialog was open — subsequent tool calls would hang or throw opaque errors.
  2. Iframes are invisible. The agent could see iframe nodes in the DOM snapshot but could not click, type, or eval inside them — especially cross-origin (OOPIF) iframes that live in separate Chromium processes.

PR #12550 proposed a stateless browser_dialog wrapper. That doesn't solve detection — it's a cleaner CDP call for when the agent already knows (via symptoms) that a dialog is open. Closed as superseded.

Backend capability matrix (verified live 2026-04-23)

Using throwaway probe scripts against a data-URL page that fires alerts in the main frame and in a same-origin srcdoc iframe, plus a cross-origin https://example.com iframe:

| Backend | Dialog detect | Dialog respond | Frame tree | OOPIF Runtime.evaluate via browser_cdp(frame_id=...) | || lang: ru


|---|---|---| | Локальный Chrome (--remote-debugging-port) / /browser connect | ✓ | ✓ полный рабочий процесс | ✓ | ✓ | | База браузеров | ✓ (через мост) | ✓ полный рабочий процесс (через мост) | ✓ | ✓ (document.title = "Example Domain" проверено на реальном iframe с перекрестным происхождением) | | Камофокс | ✗ нет CDP (только REST) ​​| ✗ | частично через снимок DOM | ✗ |

Как работает ответ Browserbase. Прокси-сервер CDP Browserbase использует Playwright. внутренне и автоматически закрывает собственные диалоги в течение ~ 10 мс, поэтому Page.handleJavaScriptDialog не успеваю. Чтобы обойти эту проблему, супервизор внедряет сценарий моста через Page.addScriptToEvaluateOnNewDocument, который переопределяет window.alert/confirm/prompt с синхронным XHR на магический хост (hermes-dialog-bridge.invalid). Fetch.enable перехватывает эти XHR. прежде чем они коснутся сети — диалоговое окно становится Fetch.requestPaused событие, которое супервизор фиксирует, а respond_to_dialog выполняет через Fetch.fulfillRequest с телом JSON, которое декодирует внедренный скрипт.

Конечный результат: с точки зрения страницы prompt() по-прежнему возвращает строка, предоставленная агентом. С точки зрения агента, это то же самое. browser_dialog(action=...) API в любом случае. Сквозное тестирование против реальные сеансы с базой браузеров — 4/4 (предупреждение/запрос/подтверждение-принятие/подтверждение-отклонение) pass, включая возврат значений обратно на страницу JS.

Camofox остается без поддержки этого PR; последующий выпуск восходящего потока запланирован на jo-inc/camofox-browser запрашивает конечную точку опроса диалога.

Архитектура

CDPSupervisor

Один asyncio.Task работает в фоновом потоке демона согласно Hermes task_id. Удерживает постоянный WebSocket для конечной точки CDP серверной части. Поддерживает:

Подписка во вложении: - Page.enablejavascriptDialogOpening, frameAttached, frameNavigated, frameDetached - Runtime.enableexecutionContextCreated, consoleAPICalled, exceptionThrown - Target.setAutoAttach {autoAttach: true, flatten: true} — выводит дочерние цели OOPIF; супервизор включает Page+Runtime на каждом

Потокобезопасный доступ к состоянию через блокировку моментального снимка; обработчики инструментов (синхронизация) прочтите замороженный снимок без ожидания.

Жизненный цикл

Политика диалога

Настраивается через config.yaml в browser.dialog_policy:

Политика индивидуальна для каждой задачи; в v1 нет переопределений для каждого диалога.

Поверхность агента (PR 1)

Один новый инструмент

browser_dialog(action, prompt_text=None, dialog_id=None)

Инструмент предназначен только для ответов. Агент читает ожидающие диалоги от 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
  }
}

Ни для одного из них не появляется новая схема инструмента — агент считывает снимок. оно уже просит.

Доступность

Обе поверхности включают _browser_cdp_check (супервизор может работать только тогда, когда CDP конечная точка достижима). В сеансах Camofox/без бэкэнда диалоговый инструмент «Скрытый» и «Снимок» не включают новые поля — никакого раздувания схемы.

Взаимодействие iframe между источниками

Расширяя работу по обнаружению диалогов, browser_cdp(frame_id=...) маршрутизирует CDP звонки (особенно Runtime.evaluate) через уже подключенный супервизор WebSocket с использованием дочернего объекта OOPIF sessionId. Агенты выбирают идентификаторы кадров из browser_snapshot.frame_tree.children[] где is_oopif=true и передайте их на browser_cdp. Для iframe того же происхождения (без выделенного сеанса CDP) агент использует contentWindow/contentDocument с верхнего уровня Вместо этого Runtime.evaluate — супервизор выдает ошибку, указывающую на это. резервный вариант, когда frame_id принадлежит не-OOPIF.

В Browserbase это ЕДИНСТВЕННЫЙ надежный путь взаимодействия iframe — Соединения CDP без сохранения состояния (открытые для вызова browser_cdp) достигают подписанного URL-адреса истечения срока действия, в то время как долговременное соединение супервизора сохраняет действительный сеанс.

Камофокс (продолжение)

Выпуск запланирован против jo-inc/camofox-browser добавления: - Драматург page.on('dialog', handler) за сеанс - GET /tabs/:tabId/dialogs конечная точка опроса - POST /tabs/:tabId/dialogs/:id, чтобы принять/отклонить - Конечная точка самоанализа дерева фреймов

Файлы затронуты (PR 1)

Новый

Изменено

Нецели

Тестирование

Модульные тесты используют асинхронный макет CDP-сервера, который достаточно хорошо знает протокол. для выполнения всех переходов между состояниями: присоединения, включения, навигации, запуска диалога, закрытие диалога, присоединение/отсоединение фрейма, присоединение дочерней цели, завершение сеанса. Реальный бэкэнд E2E (база браузера + локальный Chrome) выполняется вручную; проверять сценарии из расследование от 23 апреля 2026 г. хранится в репозитории под scripts/browser_supervisor_e2e.py, чтобы каждый мог пройти повторную верификацию на новом сервере. версии.