Documentação oficial desta página · v0.20.6
Supervisor CDP do Navegador
O supervisor CDP fecha duas lacunas antigas do ferramental de navegador do Hermes:
- Diálogos nativos de JS (
alert/confirm/prompt/beforeunload) bloqueiam a thread de JS da página. Sem supervisão, o agente não tem como saber que um diálogo está aberto — as chamadas de ferramenta seguintes travam ou devolvem erros opacos. - Iframes de origem cruzada (OOPIFs) são invisíveis para o
Runtime.evaluatede nível superior. O agente enxerga os nós de iframe no snapshot do DOM, mas não consegue clicar, digitar nem avaliar código dentro deles sem uma sessão CDP anexada ao alvo filho.
O supervisor resolve os dois casos mantendo um WebSocket persistente com o endpoint
CDP do backend por tarefa de navegador, expondo diálogos pendentes e a estrutura de frames
no browser_snapshot, e disponibilizando a ferramenta browser_dialog para respostas
explícitas.
Suporte por backend
| Backend | Detecta diálogo | Responde diálogo | Árvore de frames | Runtime.evaluate em OOPIF via browser_cdp(frame_id=...) |
|---|---|---|---|---|
Chrome local (--remote-debugging-port) / /browser connect | ✓ | ✓ fluxo completo | ✓ | ✓ |
| Browserbase | ✓ (via ponte) | ✓ fluxo completo (via ponte) | ✓ | ✓ |
| Camofox | ✗ sem CDP (só REST) | ✗ | parcial, via snapshot do DOM | ✗ |
Peculiaridade do Browserbase. O proxy CDP do Browserbase usa Playwright internamente e
descarta diálogos nativos automaticamente em cerca de 10 ms, então o Page.handleJavaScriptDialog
não consegue acompanhar. O supervisor injeta um script de ponte via
Page.addScriptToEvaluateOnNewDocument que substitui
window.alert/confirm/prompt por um XHR síncrono para um host mágico
(hermes-dialog-bridge.invalid). O Fetch.enable intercepta esses XHRs antes
que eles cheguem à rede — o diálogo vira um evento Fetch.requestPaused que o
supervisor captura, e o respond_to_dialog responde via
Fetch.fulfillRequest com um corpo JSON que o script injetado decodifica.
Do ponto de vista da página, o prompt() continua devolvendo a string fornecida pelo
agente. Do ponto de vista do agente, é a mesma API browser_dialog(action=...)
nos dois casos.
O Camofox não tem suporte — não expõe superfície CDP, só REST.
Arquitetura
CDPSupervisor
Uma asyncio.Task rodando em uma thread daemon de segundo plano por task_id do Hermes.
Mantém um WebSocket persistente com o endpoint CDP do backend. Guarda:
- Fila de diálogos —
List[PendingDialog]com{id, type, message, default_prompt, session_id, opened_at} - Árvore de frames —
Dict[frame_id, FrameInfo]com relações de pai, URL, origem e se é sessão filha de origem cruzada - Mapa de sessões —
Dict[session_id, SessionInfo]para que as ferramentas de interação encaminhem à sessão anexada correta nas operações com OOPIF - Erros recentes de console — buffer circular com os últimos 50, para diagnóstico
Assina, ao anexar:
Page.enable—javascriptDialogOpening,frameAttached,frameNavigated,frameDetachedRuntime.enable—executionContextCreated,consoleAPICalled,exceptionThrownTarget.setAutoAttach {autoAttach: true, flatten: true}— expõe alvos filhos de OOPIF; o supervisor habilitaPage+Runtimeem cada um
O acesso ao estado é seguro entre threads por meio de um lock de snapshot; os handlers de ferramenta (síncronos) leem o snapshot congelado sem precisar de await.
Ciclo de vida
- Início:
SupervisorRegistry.get_or_start(task_id, cdp_url)— chamado porbrowser_navigate, pela criação de sessão do Browserbase e pelo/browser connect. É idempotente. - Parada: encerramento da sessão ou
/browser disconnect. Cancela a task do asyncio, fecha o WebSocket e descarta o estado. - Religamento: se a URL do CDP muda (o usuário reconecta a um novo Chrome), o supervisor antigo é parado e um novo é iniciado — o estado nunca é reaproveitado entre endpoints.
Política de diálogos
Configurável no config.yaml, em browser.dialog_policy:
must_respond(padrão) — captura, expõe nobrowser_snapshote espera uma chamada explícita debrowser_dialog(action=...). Depois de um timeout de segurança de 300 s sem resposta, descarta automaticamente e registra em log. Evita que um agente com defeito trave para sempre.auto_dismiss— registra e descarta na hora; o agente vê o ocorrido depois do fato, pelobrowser_statedentro dobrowser_snapshot.auto_accept— registra e aceita (útil parabeforeunload, quando o fluxo quer sair da página sem atrito).
A política vale por tarefa; não há sobrescrita por diálogo.
Superfície para o agente
Ferramenta browser_dialog
browser_dialog(action, prompt_text=None, dialog_id=None)
action="accept"/"dismiss"→ responde ao diálogo pendente indicado ou ao único que houver (obrigatório)prompt_text=...→ texto a fornecer a um diálogo deprompt()dialog_id=...→ desambigua quando há vários diálogos na fila (raro)
A ferramenta só responde. O agente lê os diálogos pendentes na saída do
browser_snapshot antes de chamá-la.
Extensão do browser_snapshot
Acrescenta três campos opcionais à saída de snapshot já existente quando há um supervisor anexado:
{
"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— diálogos que estão bloqueando a thread de JS da página no momento. O agente precisa chamarbrowser_dialog(action=...)para responder. Fica vazio no Browserbase, porque o proxy CDP deles descarta tudo em cerca de 10 ms. -
recent_dialogs— buffer circular com até 20 diálogos fechados recentemente, com uma etiquetaclosed_by:"agent"(nós respondemos),"auto_policy"(auto_dismiss/auto_accept local),"watchdog"(estourou o timeout do must_respond) ou"remote"(o navegador/backend fechou por conta própria, como no Browserbase). É assim que agentes no Browserbase ainda enxergam o que aconteceu. -
frame_tree— estrutura de frames, incluindo os filhos de origem cruzada (OOPIF). Limitada a 30 entradas + profundidade 2 de OOPIF, para conter o tamanho do snapshot em páginas cheias de anúncio. O campotruncated: trueaparece quando os limites foram atingidos; agentes que precisam da árvore completa podem usarbrowser_cdpcomPage.getFrameTree.
Nada disso acrescenta esquema novo de ferramenta — o agente lê o snapshot que já pede.
Condição de disponibilidade
As duas superfícies dependem do _browser_cdp_check (o supervisor só roda quando há um
endpoint CDP acessível). Em sessões com Camofox ou sem backend, a ferramenta de diálogo fica
oculta e o snapshot omite os campos novos — sem inchar o esquema.
Interação com iframes de origem cruzada
O browser_cdp(frame_id=...) encaminha chamadas CDP (em especial o Runtime.evaluate)
pelo WebSocket já conectado do supervisor, usando o sessionId filho do
OOPIF. Os agentes escolhem os frame_ids em
browser_snapshot.frame_tree.children[] onde is_oopif=true e os passam
para o browser_cdp. Para iframes de mesma origem (sem sessão CDP dedicada), o
agente usa contentWindow/contentDocument a partir de um Runtime.evaluate
de nível superior — o supervisor devolve um erro apontando para esse
fallback quando o frame_id pertence a um frame que não é OOPIF.
No Browserbase, esse é o único caminho confiável para interagir com iframes —
conexões CDP sem estado (abertas a cada chamada de browser_cdp) esbarram na expiração
da URL assinada, enquanto a conexão de longa duração do supervisor mantém uma sessão válida.
Organização dos arquivos
tools/browser_supervisor.py—CDPSupervisor,SupervisorRegistry,PendingDialog,FrameInfotools/browser_dialog_tool.py— handler da ferramentabrowser_dialogtools/browser_tool.py— hook de início dobrowser_navigate, merge dobrowser_snapshot, reanexação do/browser connect, encerramento do_cleanup_browser_sessiontoolsets.py— registra obrowser_dialognos conjuntosbrowser,hermes-acp,hermes-api-servere nos conjuntos centrais (condicionado ao CDP estar acessível)hermes_cli/config.py— padrões debrowser.dialog_policyebrowser.dialog_timeout_s
Fora de escopo
- Detecção e interação para o Camofox (lacuna do upstream; acompanhada em separado)
- Transmitir eventos de diálogo/frame ao vivo para o usuário (exigiria hooks de gateway)
- Persistir o histórico de diálogos entre sessões (só em memória)
- Políticas de diálogo por iframe (o agente consegue expressar isso pelo
dialog_id) - Substituir o
browser_cdp— ele continua sendo a válvula de escape para a cauda longa (cookies, viewport, limitação de rede)
Testes
Os testes de unidade (tests/tools/test_browser_supervisor.py) usam um servidor CDP falso em
asyncio que fala o suficiente do protocolo para exercitar todas as transições de estado:
anexar, habilitar, navegar, disparar diálogo, descartar diálogo, anexar/desanexar frame,
anexar alvo filho, encerrar sessão. O E2E com backend real (Browserbase + navegador
da família Chromium local) é manual — exercite pelo /browser connect em um
navegador da família Chromium ao vivo e rode os casos de teste de diálogo/frame descritos
acima.