-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
English | 中文 | 日本語 | 한국어 | Español | Português | Русский
browsa is a Chrome MV3 extension with a side-panel chat UI. The extension source has no build step — Chrome loads every JS file directly; the only build (build/build.mjs) bundles vendor libraries with esbuild.
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.jsis the onlychrome.runtime.onMessagerouter. Onehandle()switch remains the single exported dispatcher tests import; the biggest case bodies live inlib/handlers/(chat / subchat / session / attach family / mermaid-repair / provider-resolver / approval-relay / agent-stream-session / prompt-assembly / site-cache-store …). - Stream state (
streamPorts/streamState/chatControllers/ pending-approval Maps …) lives once inlib/state.js, whichbackground.jsre-exports — tests importing from../background.jsget the same Map instances.
-
browsa-navis long-lived: connected once at panel init, reconnected after SW restarts via theconnectNavPort()pattern (1s backoff; a new port object must have all its onMessage listeners re-attached). It means "whichever tab the panel is watching" — re-registering under a new tabId viaNAV_FOLLOWis exactly its semantics. -
browsa-chat/browsa-subchatopen fresh per turn / per send: one turn = one port. A disconnect caused by the SW sleeping needs no self-healing — the next send opens a new one. Deliberate: a turn's lifecycle never leaks into the next.
- Most handlers: success
{ ok: true, data }/ thrown error{ ok: false, error, code, hint }. - A few (
APPROVAL_RESPOND/CLARIFY_RESPOND…) catch relay failures internally and return an inner{ ok, ... }insidedata. - Read the actual case before assuming. Mis-reading the envelope is a whole family of real bugs found in the 2026-08 full-repo sweep (e.g.
LOAD_SESSIONreturns -1 on miss — 0 is a legal empty session; the innerres.data.okis the real success flag).
Switching conversations no longer cancels a running turn. The routing rule in one line: while the panel is watching the stream, the turn writes to LIVE history (bg === false); once backgrounded it writes to the ORIGIN session's snapshot. persistTurnEntry is the single writer; REASSIGN_STREAM_SESSION (on switch-away auto-save) and STREAM_PEEK (re-attach on switch-back) are the only two messages that change bg. An aborted turn with streamed text is salvaged as { interrupted: true } into the origin (Esc / idle timeout / network drop); explicit history destruction uses salvage: false.
- The SW sleeps after ~30s idle. Module-level Maps reset on every restart — never store durable state there.
-
setTimeoutinside the SW is unreliable after message handling returns — usechrome.alarmsorchrome.storage.session. -
On-demand listener registration is house style: the three
chrome.webNavigationlisteners register only while a navPort exists,tabs.onRemovedregisters on demand, the stream-GC alarm exists only whilestreamStateis non-empty — otherwise every navigation/tab-close cold-starts the SW (644KB module parse) to no-op. -
chrome.storage.sessionsurvives SW restarts within a browser session (site-cache restore, pending SELECTION_ACTION …).
chrome.tabs.onActivated must not touch the DOM — Chrome keeps the side-panel document alive across tab switches. Update currentTabId and the page-meta text, send NAV_FOLLOW. Stream aborts address the stream's OWN tab (streamTabIdOf()), never the panel's current tab.
| Path | Responsibility |
|---|---|
background.js |
SW; handle() dispatch + inline small cases (ATTACH_PAGE …) |
lib/handlers/* |
Big case bodies: chat / subchat / session / attach-* / approval-relay / provider-resolver / stream-dispatch / agent-stream-session / site-cache-store / attach-store / attach-modes |
lib/state.js |
Stream-state Maps + pushChunk protocol + terminal tombstones |
lib/llm-client.js |
Wire-protocol layer: four streams over one openSseStream() skeleton |
lib/message-builder.js |
Per-provider request shapes + ageStaleAttachments
|
lib/agent-turn.js / lib/image-budget.js
|
Shared agent-turn layer (text/images budget/backfill) |
lib/prompt-assembly.js |
CAPABILITY_HINTS_ENTRIES — the single render-contract text source |
lib/storage.js |
chrome.storage.local wrapper; global history + split session keys |
lib/sidepanel/* |
26 UI-side modules (render pipeline, sessions drawer, detail thread, timeline …) |
lib/content-scripts/* |
MAIN-world site interceptors + ISOLATED selection toolbar; per-site knowledge in SITES.md
|
lib/page-extractor.js … |
Attach/extraction layer (reader/dom/full/auto cascade + site fast-paths + PDF/Office/ASR handoffs) |
Source of truth: AGENTS.md "Message flow", "Port lifecycle", "MV3 service worker gotchas", "Background streams across session switches" sections; CONTEXT.md glossary. Synced 2026-10-01.
English
- Home
- Architecture
- Rendering Pipeline
- Storage Model
- Providers and Agents
- ASR and Video Analysis
- Security Model
- Design Decisions
- Contributing
中文
相关 / Related
日本語
한국어
Español
- Inicio
- Arquitectura
- Pipeline de renderizado
- Modelo de almacenamiento
- Proveedores y agentes
- ASR y análisis de vídeo
- Modelo de seguridad
- Decisiones de diseño
- Contribuir
Português
- Início
- Arquitetura
- Pipeline de renderização
- Modelo de armazenamento
- Provedores e agentes
- ASR e análise de vídeo
- Modelo de segurança
- Decisões de design
- Contribuindo
Русский