Skip to content

Architecture es

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

Arquitectura

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

browsa es una extensión de Chrome MV3 con una UI de chat en panel lateral. El código fuente de la extensión no tiene paso de compilación — Chrome carga cada archivo JS directamente; la única compilación (build/build.mjs) empaqueta las librerías vendor con esbuild.

El viaje de un mensaje

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 es el único router de chrome.runtime.onMessage. Un único switch handle() sigue siendo el dispatcher exportado que importan los tests; los cuerpos de los casos más grandes viven en lib/handlers/ (chat / subchat / session / familia attach / mermaid-repair / provider-resolver / approval-relay / agent-stream-session / prompt-assembly / site-cache-store …).
  • El estado de streams (streamPorts / streamState / chatControllers / Maps de aprobaciones pendientes …) vive una sola vez en lib/state.js, que background.js re-exporta — los tests que importan desde ../background.js obtienen las mismas instancias de Map.

Ciclo de vida de los puertos: persistentes vs por turno (deliberadamente distintos)

  • browsa-nav es de larga vida: se conecta una vez en el inicio del panel y se reconecta tras los reinicios del SW mediante el patrón connectNavPort() (backoff de 1s; un nuevo objeto port debe tener todos sus listeners onMessage vueltos a registrar). Significa «la pestaña que el panel esté observando» — volver a registrarse bajo un nuevo tabId vía NAV_FOLLOW es exactamente su semántica.
  • browsa-chat / browsa-subchat se abren nuevos por turno / por envío: un turno = un puerto. Una desconexión causada por la hibernación del SW no necesita auto-reparación — el siguiente envío abre uno nuevo. Es deliberado: el ciclo de vida de un turno nunca se filtra al siguiente.

Sobre de respuesta (una familia histórica de bugs — verifica qué capa se está leyendo)

  • La mayoría de los handlers: éxito { ok: true, data } / error lanzado { ok: false, error, code, hint }.
  • Algunos pocos (APPROVAL_RESPOND / CLARIFY_RESPOND …) capturan internamente los fallos del relevo y devuelven un { ok, ... } interno dentro de data.
  • Leer el caso real antes de asumir. Malinterpretar el sobre es toda una familia de bugs reales hallados en la revisión de todo el repositorio de 2026-08 (p. ej. LOAD_SESSION devuelve -1 si no encuentra — 0 es una sesión vacía legal; el res.data.ok interno es la auténtica señal de éxito).

Streams en background entre cambios de sesión (2026-09-24)

Cambiar de conversación ya no cancela un turno en curso. La regla de enrutado en una línea: mientras el panel está observando el stream, el turno escribe en el historial EN VIVO (bg === false); una vez pasado a segundo plano escribe en la instantánea de la sesión de ORIGEN. persistTurnEntry es el escritor único; REASSIGN_STREAM_SESSION (autoguardado al cambiar de sesión) y STREAM_PEEK (re-adjuntar al volver) son los únicos dos mensajes que cambian bg. Un turno abortado con texto ya emitido se recupera como { interrupted: true } hacia el origen (Esc / timeout por inactividad / caída de red); la destrucción explícita del historial usa salvage: false.

Peculiaridades del service worker en MV3 (leer antes de escribir código del lado del SW)

  • El SW se duerme tras ~30s de inactividad. Los Maps a nivel de módulo se reinician en cada arranque — nunca almacenar allí estado duradero.
  • setTimeout dentro del SW no es fiable una vez que el manejo del mensaje retorna — usar chrome.alarms o chrome.storage.session.
  • El registro de listeners bajo demanda es el estilo de la casa: los tres listeners de chrome.webNavigation se registran solo mientras existe un navPort, tabs.onRemoved se registra bajo demanda, la alarma de GC de streams existe solo mientras streamState no está vacío — de lo contrario cada navegación/cierre de pestaña produce un arranque en frío del SW (parseo de 644KB de módulo) para no hacer nada.
  • chrome.storage.session sobrevive a los reinicios del SW dentro de una sesión de navegador (restauración de cachés de sitio, SELECTION_ACTION pendientes …).

Cambio de pestañas

chrome.tabs.onActivated no debe tocar el DOM — Chrome mantiene vivo el documento del panel lateral entre cambios de pestaña. Actualizar currentTabId y el texto de page-meta, y enviar NAV_FOLLOW. Los abortos de stream apuntan a la pestaña PROPIA del stream (streamTabIdOf()), nunca a la pestaña actual del panel.

Mapa de módulos

Ruta Responsabilidad
background.js SW; dispatch de handle() + casos pequeños inline (ATTACH_PAGE …)
lib/handlers/* Cuerpos de casos 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 streams + protocolo pushChunk + tombstones de eventos terminales
lib/llm-client.js Capa de protocolo de red: cuatro streams sobre un único esqueleto openSseStream()
lib/message-builder.js Formas de petición por proveedor + ageStaleAttachments
lib/agent-turn.js / lib/image-budget.js Capa compartida de turnos de agente (presupuesto/backfill de texto/imágenes)
lib/prompt-assembly.js CAPABILITY_HINTS_ENTRIES — la única fuente de texto del contrato de renderizado
lib/storage.js wrapper de chrome.storage.local; historial global + claves de sesión divididas
lib/sidepanel/* 26 módulos del lado de la UI (pipeline de renderizado, cajón de sesiones, hilo de detalle, línea de tiempo …)
lib/content-scripts/* Interceptores de sitios en el mundo MAIN + barra de selección ISOLATED; conocimiento por sitio en SITES.md
lib/page-extractor.js … Capa de adjunto/extracción (cascada reader/dom/full/auto + fast-paths por sitio + handoffs de PDF/Office/ASR)

Versiones autoritativas: Architecture (inglés) / Architecture-zh (chino) — instantánea de primera traducción por IA, sincronizada el 2026-10-01.

Clone this wiki locally