Skip to content

Architecture pt

Hermes Agent edited this page Oct 1, 2026 · 1 revision

Arquitetura

English | 中文 | 日本語 | 한국어 | Español | Português | Русский

browsa é uma extensão Chrome MV3 com uma UI de chat em painel lateral. O código-fonte da extensão não tem etapa de build — o Chrome carrega cada arquivo JS diretamente; o único build (build/build.mjs) empacota as bibliotecas vendor com esbuild.

A jornada de uma mensagem

page content scripts (MAIN-world interceptors / ISOLATED-world selection toolbar)
        │  chrome.runtime.sendMessage / port
        ▼
background.js (service worker, single message router)
        │  handle() switch — case bodies live in lib/handlers/*
        ▼
sidepanel.js (the UI orchestrator)
        ▲
        ├── browsa-nav      long-lived port: NAVIGATED / XHS pushes / SELECTION_ACTION
        ├── browsa-chat     fresh per turn: main chat stream (chunk protocol)
        └── browsa-subchat  fresh per send: detail-thread stream
  • background.js é o único roteador de chrome.runtime.onMessage. Um switch handle() permanece o único dispatcher exportado que os testes importam; os corpos de case maiores vivem em lib/handlers/ (chat / subchat / session / família attach / mermaid-repair / provider-resolver / approval-relay / agent-stream-session / prompt-assembly / site-cache-store …).
  • O estado de stream (streamPorts / streamState / chatControllers / Maps de aprovação pendente …) vive uma única vez em lib/state.js, que o background.js re-exporta — testes que importam de ../background.js recebem as mesmas instâncias de Map.

Ciclo de vida das portas: persistente vs por turno (deliberadamente diferentes)

  • browsa-nav é de longa duração: conectada uma vez na inicialização do painel, reconectada após reinícios do SW via o padrão connectNavPort() (backoff de 1s; um novo objeto de porta precisa ter todos os seus listeners onMessage reanexados). Ela significa "a aba que o painel estiver observando" — registrar-se de novo sob um novo tabId via NAV_FOLLOW é exatamente a semântica dela.
  • browsa-chat / browsa-subchat abrem novas a cada turno / a cada envio: um turno = uma porta. Uma desconexão causada pelo SW adormecer não precisa de autocura — o próximo envio abre uma nova. Deliberado: o ciclo de vida de um turno nunca vaza para o seguinte.

Envelope de resposta (uma família histórica de bugs — verifique em qual camada você está)

  • A maioria dos handlers: sucesso { ok: true, data } / erro lançado { ok: false, error, code, hint }.
  • Alguns (APPROVAL_RESPOND / CLARIFY_RESPOND …) capturam falhas de relay internamente e retornam um { ok, ... } interno dentro de data.
  • Leia o case real antes de presumir. Ler o envelope errado é uma família inteira de bugs reais encontrados na varredura de repositório completo de 2026-08 (ex.: LOAD_SESSION retorna -1 em ausência — 0 é uma sessão vazia legal; o res.data.ok interno é a flag real de sucesso).

Streams em background entre trocas de sessão (2026-09-24)

Trocar de conversa não cancela mais um turno em andamento. A regra de roteamento em uma linha: enquanto o painel observa o stream, o turno grava no histórico LIVE (bg === false); uma vez em segundo plano, grava no snapshot da sessão de ORIGEM. persistTurnEntry é o único gravador; REASSIGN_STREAM_SESSION (no auto-save ao trocar de conversa) e STREAM_PEEK (reanexar ao voltar) são as únicas duas mensagens que mudam bg. Um turno abortado com texto já transmitido é resgatado como { interrupted: true } na origem (Esc / timeout por inatividade / queda de rede); destruição explícita de histórico usa salvage: false.

Pegadinhas do service worker MV3 (leia antes de escrever código do lado do SW)

  • O SW adormece após ~30s de inatividade. Maps em nível de módulo se resetam a cada reinício — nunca armazene estado durável ali.
  • setTimeout dentro do SW não é confiável depois que o tratamento da mensagem retorna — use chrome.alarms ou chrome.storage.session.
  • Registro de listeners sob demanda é o estilo da casa: os três listeners de chrome.webNavigation se registram apenas enquanto existe uma navPort, tabs.onRemoved se registra sob demanda, o alarme de GC de stream existe apenas enquanto streamState não está vazio — caso contrário, cada navegação/fechamento de aba faz cold-start do SW (parse de módulo de 644KB) para não fazer nada.
  • chrome.storage.session sobrevive a reinícios do SW dentro de uma sessão do navegador (restauração do cache de site, SELECTION_ACTION pendente …).

Troca de abas

chrome.tabs.onActivated não deve tocar no DOM — o Chrome mantém o documento do painel lateral vivo entre trocas de abas. Atualize currentTabId e o texto de page-meta, envie NAV_FOLLOW. Aborts de stream endereçam a PRÓPRIA aba do stream (streamTabIdOf()), nunca a aba atual do painel.

Mapa de módulos

Caminho Responsabilidade
background.js SW; dispatch do handle() + cases pequenos inline (ATTACH_PAGE …)
lib/handlers/* Corpos de case grandes: chat / subchat / session / attach-* / approval-relay / provider-resolver / stream-dispatch / agent-stream-session / site-cache-store / attach-store / attach-modes
lib/state.js Maps de estado de stream + protocolo pushChunk + tombstones terminais
lib/llm-client.js Camada de protocolo de rede: quatro streams sobre um único esqueleto openSseStream()
lib/message-builder.js Formas de requisição por provedor + ageStaleAttachments
lib/agent-turn.js / lib/image-budget.js Camada compartilhada de turnos de agente (orçamento/backfill de texto/imagens)
lib/prompt-assembly.js CAPABILITY_HINTS_ENTRIES — a única fonte de texto do contrato de renderização
lib/storage.js Wrapper do chrome.storage.local; histórico global + chaves de sessão divididas
lib/sidepanel/* 26 módulos do lado da UI (pipeline de renderização, gaveta de sessões, thread de detalhe, linha do tempo …)
lib/content-scripts/* Interceptores de site no MAIN world + toolbar de seleção ISOLATED; conhecimento por site em SITES.md
lib/page-extractor.js … Camada de anexação/extração (cascata reader/dom/full/auto + fast-paths por site + handoffs de PDF/Office/ASR)

Versões autoritativas: Architecture (inglês) / Architecture-zh (chinês) — instantâneo de primeira tradução por IA, sincronizado em 2026-10-01.

Clone this wiki locally