# Agent brief — EXT (Firefox extension) **Starts the day PROTO freezes the contract. Develops against `tools/mockd` over the WebSocket transport — you never wait for `veloxd`.** ## You own ``` extension/** ``` Read `contracts/`, `docs/`. Never write in `core/`, `daemon/`, `gui/`, or `nmhost/` (the native host belongs to DAEMON; if you need a change there, file it). ## Read first `docs/05-extension-spec.md` in full, then `docs/06` R1 (snap Firefox) and R3 (AMO). ## Your first task is a spike, not code **Spike S1 (2 days, blocks nothing else):** on a clean Ubuntu 26.04 VM with snap Firefox, determine empirically which of the four native-messaging manifest locations actually work, and whether the launched host can reach `$XDG_RUNTIME_DIR`. Write `docs/adr/0003-native-messaging-under-snap.md`. Then build the WebSocket transport first regardless of the answer — it is the one that is known to work here (`snap connections firefox` shows `network`/`network-bind` connected). ## Build order 1. **`transport/`** — the `Transport` interface, `WebSocketTransport` (port discovery over 52000–52016 + `session.hello` verification), pairing flow with token in `browser.storage.local`, reconnect with backoff, then `NativeTransport`, then `transport/index.ts` picking at runtime with a manual override in Options. 2. **`capture/headers.ts`** — `onBeforeSendHeaders` stash keyed by `requestId`, ring buffer, 5-minute TTL, bounded size (a leak here eats the browser's memory). 3. **`capture/rules.ts`** — `shouldCapture()` exactly as specified in `docs/05` §2. **Table-driven tests before implementation.** This function is where the bugs will be: too eager and you hijack page navigations; too shy and you're not a download manager. 4. **`capture/index.ts`** — the `onHeadersReceived` blocking hook: gather cookies, call `capture.offer` with a **750 ms budget**, `{cancel: true}` only on `take`. **Fail-open is a hard requirement** — daemon down, slow, or erroring means Firefox downloads normally. Write that test before the feature. 5. **`capture/downloads-api.ts`** — the `downloads.onCreated` safety net for what slips past. 6. **`context-menus.ts`**, **`popup/`** (live progress from relayed events, pause/resume, status dot), **`options/`** (transport, pairing/unpair, monitored types synced via `capture.getRules`, min size, exclusions, default category, bypass modifier). 7. **`content/` media detection** — `.m3u8`/`.mpd`/MIME sniffing plus a `MediaSource` observer; in-page "Download this video ▾" panel listing variants from `media.listVariants`. **The extension never parses manifests** — the daemon does. Detect DRM/EME and grey the button out with "Protected content". ## Definition of done (M1) - Intercepts a real download in real Firefox and hands it to `mockd`. - **Fail-open proven by an automated test:** kill the mock mid-flow, the file still downloads through Firefox, no error dialog, no lost download. - Pairing works, unpair revokes, token survives a browser restart, and a wrong token is rejected and rate-limited. - `web-ext lint` clean; no remote code, no `eval`, no CDN script — AMO rejects those. - `shouldCapture` test table covers: attachment, monitored extension, monitored MIME, size threshold, excluded host, HTML navigation, blob URL, bypass modifier held, streaming media, and a range request the page itself issued. - Popup shows live progress at ≤ 4 Hz without pinning a core. - Permission justification written and committed for AMO submission. ## Do not - Do not implement any download logic in the extension. You collect URL + headers + cookies and hand them over. That's the whole job. - Do not add a framework (React/Vue) for a popup and an options page; plain TS keeps the AMO review and the bundle small. - Do not invent protocol fields — file a request with PROTO.