Browser CDP Supervisor
CDP-супервизор закрывает два давних пробела в браузерном инструментарии VibeOS:
- Нативные JS-диалоги (
alert/confirm/prompt/beforeunload) блокируют JS-поток страницы. Без надзора агент не может узнать, что диалог открыт — последующие вызовы инструментов зависают или выдают непонятные ошибки. - Кросс-доменные iframe (OOPIF) невидимы для
Runtime.evaluateверхнего уровня. Агент видит узлы iframe в DOM-снимке, но не может кликать, вводить текст или выполнять eval внутри них без CDP-сессии, прикреплённой к дочернему целевому объекту.
Супервизор решает обе проблемы, удерживая постоянный WebSocket к CDP-точке
бэкенда для каждой задачи браузера, выводя ожидающие диалоги и структуру фреймов
в browser_snapshot, а также предоставляя инструмент browser_dialog для явных
ответов.
Поддержка бэкендов
| Бэкенд | Обнаружение диалогов | Ответ на диалоги | Дерево фреймов | OOPIF Runtime.evaluate через browser_cdp(frame_id=...) |
|---|---|---|---|---|
Локальный Chrome (--remote-debugging-port) / /browser connect | ✓ | ✓ полный workflow | ✓ | ✓ |
| Browserbase | ✓ (через мост) | ✓ полный workflow (через мост) | ✓ | ✓ |
| Camofox | ✗ нет CDP (только REST) | ✗ | частично через DOM-снимок | ✗ |
Особенность Browserbase. Прокси CDP Browserbase использует Playwright внутри и
автоматически закрывает нативные диалоги примерно за 10 мс, поэтому
Page.handleJavaScriptDialog не успевает сработать. Супервизор внедряет скрипт-мост через
Page.addScriptToEvaluateOnNewDocument, который переопределяет
window.alert/confirm/prompt синхронным XHR-запросом к магическому хосту
(vibeos-dialog-bridge.invalid). Fetch.enable перехватывает эти XHR-запросы до того,
как они достигают сети — диалог становится событием Fetch.requestPaused, которое
супервизор захватывает, а respond_to_dialog обрабатывает через
Fetch.fulfillRequest с JSON-телом, которое декодирует внедрённый скрипт.
С точки зрения страницы, prompt() всё ещё возвращает строку, предоставленную агентом.
С точки зрения агента, это тот же API browser_dialog(action=...)
в любом случае.
Camofox не поддерживается — нет CDP-поверхности, только REST.
Архитектура
CDPSupervisor
Одна задача asyncio.Task, работающая в фоновом потоке-демоне для каждого task_id VibeOS.
Удерживает постоянный WebSocket к CDP-точке бэкенда. Поддерживает:
- Очередь диалогов —
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, удаляет состояние. - Перепривязка: если URL CDP изменился (пользователь переподключается к новому Chrome), старый супервизор останавливается и запускается новый — состояние никогда не используется повторно между точками подключения.
Политика диалогов
Настраивается в config.yaml в разделе browser.dialog_policy:
must_respond(по умолчанию) — захватить, отобразить вbrowser_snapshot, ждать явного вызоваbrowser_dialog(action=...). После 300-секундного таймаута безопасности без ответа автоматически закрыть и записать в лог. Предотвращает бесконечное зависание из-за ошибок агента.auto_dismiss— записать и сразу закрыть; агент видит это постфактум черезbrowser_stateвнутриbrowser_snapshot.auto_accept— записать и принять (полезно дляbeforeunload, когда workflow хочет чисто уйти со страницы).
Политика применяется к задаче; переопределений для отдельных диалогов нет.
Поверхность для агента
Инструмент 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-прокси автоматически закрывает диалоги примерно за 10 мс. -
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показывает, когда лимиты были превышены; агентам, которым нужно полное дерево, можно использоватьbrowser_cdpсPage.getFrameTree.
Новая схема инструментов для этих полей не требуется — агент читает снимок, который он уже запрашивает.
Ограничение доступности
Обе поверхности ограничены проверкой _browser_cdp_check (супервизор может работать только когда CDP-точка
доступна). На Camofox / сессиях без бэкенда инструмент диалогов
скрыт, а снимок опускает новые поля — без раздувания схемы.
Взаимодействие с кросс-доменными iframe
browser_cdp(frame_id=...) направляет CDP-вызовы (в частности Runtime.evaluate)
через уже подключённый WebSocket супервизора, используя дочерний sessionId OOPIF.
Агенты выбирают frame_id из
browser_snapshot.frame_tree.children[], где is_oopif=true, и передают их
в browser_cdp. Для iframe одного источника (без выделенной CDP-сессии)
агент использует contentWindow/contentDocument из Runtime.evaluate
верхнего уровня — супервизор выводит ошибку, указывающую на этот
запасной вариант, когда frame_id принадлежит не-OOPIF.
На Browserbase это единственный надёжный путь для взаимодействия с iframe —
stateless CDP-соединения (открываемые при каждом вызове browser_cdp) сталкиваются с
истечением подписанных URL, в то время как долгоживущее соединение супервизора сохраняет
действительную сессию.
Структура файлов
tools/browser_supervisor.py—CDPSupervisor,SupervisorRegistry,PendingDialog,FrameInfotools/browser_dialog_tool.py— обработчик инструментаbrowser_dialogtools/browser_tool.py— хук запускаbrowser_navigate, слияниеbrowser_snapshot, переподключение/browser connect, завершение_cleanup_browser_sessiontoolsets.py— регистрируетbrowser_dialogвbrowser,vibeos-acp,vibeos-api-serverи основных наборах инструментов (с проверкой доступности CDP)vibeos_cli/config.py— значения по умолчанию дляbrowser.dialog_policyиbrowser.dialog_timeout_s
Нецелевые задачи
- Обнаружение/взаимодействие для Camofox (пробел на стороне вышестоящего проекта; отслеживается отдельно)
- Потоковая передача событий диалогов/фреймов в реальном времени пользователю (требовало бы хуков шлюза)
- Сохранение истории диалогов между сессиями (только в памяти)
- Политики диалогов для отдельных iframe (агент может выразить это через
dialog_id) - Замена
browser_cdp— он остаётся запасным выходом для редких случаев (cookies, viewport, сетевое регулирование)
Тестирование
Модульные тесты (tests/tools/test_browser_supervisor.py) используют асинхронный mock-сервер CDP,
который реализует достаточно протокола для проверки всех переходов состояний:
подключение, включение, навигация, срабатывание диалога, закрытие диалога, подключение/отключение фрейма,
подключение дочернего целевого объекта, завершение сессии. Сквозное тестирование с реальным бэкендом (Browserbase + локальный
браузер на основе Chromium) выполняется вручную — через /browser connect к
живому браузеру на основе Chromium и запуск описанных выше тестовых случаев диалогов/фреймов.