# Architecture

## Layer Stack Table

| Layer                       | Directory                              | Responsibility                                                                  | Key File                                   | Test File (in `packages/*/tests/`)                                                                    |
| --------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| Shims                       | `packages/webapp/src/shims/`           | Node.js polyfills for browser bundle                                            | `empty.ts`, `buffer-polyfill.ts`           | N/A                                                                                                   |
| Virtual Filesystem          | `packages/webapp/src/fs/`              | POSIX-like FS (LightningFS/IndexedDB)                                           | `virtual-fs.ts`                            | `virtual-fs.test.ts`                                                                                  |
| Shell                       | `packages/webapp/src/shell/`           | just-bash WASM + xterm terminal                                                 | `wasm-shell.ts`                            | `wasm-shell.test.ts`                                                                                  |
| Git                         | `packages/webapp/src/git/`             | isomorphic-git wrapper                                                          | `git-commands.ts`                          | N/A                                                                                                   |
| Skills                      | `packages/webapp/src/skills/`          | Skill package manager                                                           | `apply.ts`                                 | N/A                                                                                                   |
| CDP                         | `packages/webapp/src/cdp/`             | Chrome DevTools Protocol                                                        | `browser-api.ts`                           | `browser-api.test.ts`                                                                                 |
| Tools                       | `packages/webapp/src/tools/`           | Tool factories; active scoop surface is file + bash                             | `bash-tool.ts`                             | `bash-tool.test.ts`                                                                                   |
| Core Agent                  | `packages/webapp/src/core/`            | pi-mono agent loop + streaming                                                  | `index.ts`                                 | `agent.test.ts`                                                                                       |
| Scoops Orchestrator         | `packages/webapp/src/scoops/`          | Multi-agent system (cone + scoops)                                              | `orchestrator.ts`                          | N/A                                                                                                   |
| UI                          | `packages/webapp/src/ui/`              | Chat, Terminal, Files, Memory panels                                            | `main.ts`                                  | `types.test.ts`                                                                                       |
| CLI / Electron Node Runtime | `packages/node-server/src/`            | Express server, Chrome launcher, Electron float entrypoint                      | `index.ts`                                 | `electron-runtime.test.ts`                                                                            |
| Extension                   | `packages/chrome-extension/src/`       | Chrome Manifest V3 entry point                                                  | `service-worker.ts`                        | N/A                                                                                                   |
| Cloud Tray Hub              | `packages/cloudflare-worker/src/`      | Cloudflare Worker + Durable Object control-plane skeleton + deployed smoke test | `index.ts`                                 | `packages/cloudflare-worker/tests/index.test.ts`, `packages/cloudflare-worker/tests/deployed.test.ts` |
| Providers                   | `packages/webapp/src/providers/`       | Provider types, OAuth service, auto-discovery, build-time filtering             | `types.ts`, `oauth-service.ts`, `index.ts` | `index.test.ts`, `oauth-service.test.ts`                                                              |
| Sprinkles                   | `packages/webapp/src/ui/sprinkle-*.ts` | Composable `.shtml` panels: discovery, rendering, bridge API, picker UI         | `sprinkle-manager.ts`                      | `sprinkle-manager.test.ts`                                                                            |
| Defaults                    | `packages/vfs-root/`                   | Bundled VFS content: agent instructions, skills, sprinkles                      | N/A                                        | N/A                                                                                                   |
| Types                       | `packages/webapp/src/types/`           | Type declarations for external submodules                                       | `pi-coding-agent-compaction.d.ts`          | N/A                                                                                                   |

## Source File Tree

### packages/webapp/src/cdp/ — Chrome DevTools Protocol

| File                              | Purpose                                                                                                                                                                                                                     |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `browser-api.ts`                  | High-level Playwright-inspired API (listPages, navigate, screenshot, evaluate, click, type, waitForSelector, getAccessibilityTree); used by the `playwright-cli` shell command path and related browser automation commands |
| `cdp-client.ts`                   | WebSocket-based CDP client (CLI mode, connects to `ws://localhost:5710/cdp`)                                                                                                                                                |
| `debugger-client.ts`              | Chrome debugger API client (extension mode, uses `chrome.debugger`); adds agent-created tabs to "slicc" tab group                                                                                                           |
| `har-recorder.ts`                 | HAR 1.2 recorder for network traffic; saves snapshots to VFS on navigation                                                                                                                                                  |
| `transport.ts`                    | CDPTransport interface (abstracts CDP/debugger implementations)                                                                                                                                                             |
| `normalize-accessibility-text.ts` | Accessibility tree text normalization utilities                                                                                                                                                                             |
| `index.ts`                        | Re-exports + auto-selects transport based on extension detection                                                                                                                                                            |
| `types.ts`                        | TargetInfo, PageInfo, EvaluateOptions, AccessibilityNode, etc.                                                                                                                                                              |
| `offscreen-cdp-proxy.ts`          | CDPTransport over chrome.runtime messages (offscreen → service worker → chrome.debugger)                                                                                                                                    |
| `panel-cdp-proxy.ts`              | CDPTransport for side panel terminal (panel → offscreen → service worker → chrome.debugger)                                                                                                                                 |

### packages/webapp/src/kernel/ — Kernel Host (worker-resident agent engine)

The kernel host is the off-main-thread home for the agent engine. In standalone, it runs in a `DedicatedWorker`; in the extension, the same factory wires the offscreen document. The "kernel" name is by analogy: the panel ↔ host boundary is the user/kernel split.

| File                           | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `host.ts`                      | `createKernelHost(config)` factory. Single boot sequence shared by the offscreen document, the standalone DedicatedWorker, and tests: orchestrator + lick-manager + agent-bridge + tray subs + cone bootstrap + BshWatchdog + `mountInternal('/proc', …)`. Returns `{ orchestrator, browser, bridge, lickManager, sharedFs, processManager, dispose }`.                                                                                                                   |
| `kernel-worker.ts`             | DedicatedWorker entry. Receives `{ kernelPort, cdpPort, localStorageSeed }` over `MessagePort` transfer; constructs `OffscreenBridge` + `WorkerCdpProxy` + `BrowserAPI`; calls `createKernelHost`; stands up `TerminalSessionHost`. Posts `kernel-worker-ready` once boot completes. Worker-safety guard via `tsconfig.webapp-worker.json`.                                                                                                                               |
| `spawn.ts`                     | `spawnKernelWorker(opts)` — production wrapper around `new Worker(new URL('./kernel-worker.ts', import.meta.url), { type: 'module' })`. `bootstrapKernelWorker(opts)` is the testable inner loop that takes a `WorkerLike`. Returns `{ client, ready, dispose }`.                                                                                                                                                                                                         |
| `transport.ts`                 | `KernelTransport<S, R>` — typed message interface. `OffscreenBridge` and `OffscreenClient` both implement against this; the chrome.runtime adapter and `MessageChannel` adapter are interchangeable.                                                                                                                                                                                                                                                                      |
| `transport-message-channel.ts` | `MessagePort`-backed transport with implicit `port.start()` on first subscribe. `createPanelMessageChannelTransport` / `createBridgeMessageChannelTransport` add the source-tagged envelope wrapper.                                                                                                                                                                                                                                                                      |
| `transport-chrome-runtime.ts`  | chrome.runtime.sendMessage / onMessage adapter. Used by the extension panel ↔ offscreen path.                                                                                                                                                                                                                                                                                                                                                                             |
| `cdp-bridge.ts`                | `CdpTransportBridge` shared base. State (in-flight commands, listener counts, retain refcounts) is identical across the two existing proxies; wire shape and inbound filter pluggable via `CdpBridgeOptions`. `cdp-worker-proxy.ts` extends it.                                                                                                                                                                                                                           |
| `cdp-worker-proxy.ts`          | `WorkerCdpProxy` — kernel-side CDP transport over a MessagePort. Pre-subscribe protocol (`cdp-subscribe` / `cdp-unsubscribe`) so the page-side `startPageCdpForwarder` only allocates `chrome.debugger` listeners while the worker is interested.                                                                                                                                                                                                                         |
| `process-manager.ts`           | `ProcessManager` — single source of truth for every running async unit. `Process` record carries pid (uint32, monotonic from 1024+), ppid, kind (`scoop-turn` / `tool` / `shell` / `jsh` / `py` / `net`), argv, cwd, env, owner, AbortController, `Gate` (pause/resume), status, exitCode, terminatedBy, finishedAt. `signal(pid, sig)` for SIGINT/TERM/KILL/STOP/CONT; `onSignal` for kill-handler subscriptions.                                                        |
| `proc-mount.ts`                | `ProcMountBackend` — read-only `procfs`-shaped view of the manager. Mounted at `/proc` via `vfs.mountInternal` (scoop-invisible). `/proc/<pid>/{status,cmdline,cwd,stat}` plus a synthesized `pid 1` kernel-host anchor.                                                                                                                                                                                                                                                  |
| `realm/`                       | Generalized hard-killable runner for `node` / `.jsh` / `python`. `runInRealm({ kind: 'js' \| 'py', … })` spawns a per-task `DedicatedWorker` (standalone JS + both-mode Python) or sandbox iframe (extension JS); SIGKILL → `worker.terminate()` / `iframe.remove()` (uncatchable, exit 137). Kernel-side `realm-host` proxies `vfs` / `exec` / `fetch` RPC over the realm's port so realm code stays sandboxed and `ctx.fetch`'s SecureFetch substitution still applies. |
| `terminal-session-host.ts`     | Worker-side endpoint for the terminal RPC. Per-session `WasmShellHeadless`. `terminal-exec` registers `kind:'shell'` process and runs the command; `terminal-signal` routes through `pm.signal`. Output gated by `proc.gate.wait()`.                                                                                                                                                                                                                                      |
| `terminal-session-client.ts`   | Page-side counterpart. Per-call `execId` matching against `terminal-exit` events; subscription stays alive across `close()` so the closed-status event surfaces.                                                                                                                                                                                                                                                                                                          |
| `remote-terminal-view.ts`      | `RemoteTerminalView` — page-side xterm view. Minimal line editor (typing, ←/→ ↑/↓ Home/End, Backspace/Delete, Enter, Ctrl+C → SIGINT). Pre-intercepts `mount /<path>` typed lines: runs `showDirectoryPicker` on the keystroke gesture, stashes the handle in IDB, lets the worker's `mountLocal` adopt it.                                                                                                                                                               |
| `page-storage-sync.ts`         | `installPageStorageSync` — page side hook. Monkey-patches `window.localStorage.setItem/removeItem/clear` per-instance (not Storage.prototype, to avoid clobbering sessionStorage); forwards writes over the kernel transport so the worker's localStorage shim stays in sync.                                                                                                                                                                                             |
| `local-vfs-client.ts`          | `LocalVfsClient` — read-only structural facade for the file-browser, memory-panel, and preview-VFS responder on the page side. `VirtualFS` satisfies the interface naturally; the narrowing prevents accidental writes from the page realm (writes flow through the kernel transport).                                                                                                                                                                                    |
| `types.ts`                     | `KernelFacade` / `KernelClientFacade` typed interfaces. `OffscreenBridge` implements `KernelFacade`; `OffscreenClient` implements `KernelClientFacade`. Decouples the wire shape from the implementation.                                                                                                                                                                                                                                                                 |

### packages/node-server/src/ — CLI + Electron Runtimes

| File                     | Purpose                                                                                                                                                                                                |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `index.ts`               | Main CLI entrypoint: launches Chrome by default, or in `--electron` mode launches/relaunches a target Electron app, serves UI, proxies WebSocket CDP traffic, and provides `/api/fetch-proxy` for CORS |
| `runtime-flags.ts`       | Shared CLI/runtime flag parsing for `--dev`, `--serve-only`, `--cdp-port`, `--electron`, `--electron-app`, `--profile`, `--lead`, `--join`, `--log-level`, `--log-dir`, and `--kill`                   |
| `chrome-launch.ts`       | Chrome/Chrome-for-Testing discovery, QA profile resolution, launch-arg construction, and `.qa/chrome/*` scaffold seeding                                                                               |
| `qa-setup.ts`            | CLI helper for `npm run qa:setup`; validates Chrome + `dist/extension` and scaffolds the dedicated QA Chrome profiles                                                                                  |
| `electron-main.ts`       | Electron process entry point: spawns CLI server in `--serve-only` mode, creates BrowserWindow, injects overlay, strips host-page CSP                                                                   |
| `electron-runtime.ts`    | Pure Electron helpers for target app path resolution, overlay URLs/bootstrap scripts, dist paths, and injectable-target filtering                                                                      |
| `electron-controller.ts` | Electron app lifecycle management: detect running app processes, enforce `--kill`, launch with remote debugging, and inject/reinject the overlay across navigations                                    |

### packages/webapp/src/core/ — Agent Core

| File                    | Purpose                                                                                                                                                                                                                                 |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index.ts`              | Re-exports from pi-mono (Agent, AgentTool, AgentEvent, streaming, model utilities)                                                                                                                                                      |
| `types.ts`              | Legacy ToolDefinition, ToolResult, AgentConfig, SessionData                                                                                                                                                                             |
| `tool-adapter.ts`       | Wraps legacy ToolDefinition as pi-compatible AgentTool                                                                                                                                                                                  |
| `tool-registry.ts`      | Registry of active tools with lookup by name                                                                                                                                                                                            |
| `context-compaction.ts` | LLM-summarized context compaction (pi-mono aligned) with naive-drop fallback                                                                                                                                                            |
| `image-processor.ts`    | Image validation and preprocessing; checks base64 size (5MB), dimensions (8000px max, 1568px optimal), and format before agent processing. Parses PNG/GIF/JPEG headers for dimensions without full decode. Resizes via ImageMagick WASM |
| `logger.ts`             | createLogger factory with level filtering (DEBUG dev, ERROR prod)                                                                                                                                                                       |
| `session.ts`            | IndexedDB session storage (`agent-sessions` DB)                                                                                                                                                                                         |
| `mime-types.ts`         | MIME type mappings (html, css, js, json, image, etc.)                                                                                                                                                                                   |

### packages/chrome-extension/src/ — Chrome Extension

| File                    | Purpose                                                                                                                                 |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `service-worker.ts`     | Manifest V3 service worker; message relay between panel and offscreen + CDP proxy via chrome.debugger + tab grouping                    |
| `offscreen.ts`          | Agent engine bootstrap in offscreen document (Orchestrator, VFS, Shell, tools)                                                          |
| `offscreen-bridge.ts`   | Orchestrator ↔ chrome.runtime message bridge; persists chat to `browser-coding-agent` IndexedDB                                         |
| `lick-manager-proxy.ts` | BroadcastChannel proxy enabling side panel terminal to manage cron tasks via LickManager running in offscreen                           |
| `messages.ts`           | Typed message envelopes: PanelToOffscreen, OffscreenToPanel, CdpProxy                                                                   |
| `tab-group.ts`          | Shared tab grouping helper; adds agent-created tabs to a persistent "slicc" Chrome tab group (used by service worker + debugger client) |
| `chrome.d.ts`           | Typed declarations for chrome.debugger, chrome.tabs, chrome.tabGroups, chrome.sidePanel, chrome.offscreen, etc.                         |
| `sprinkle-proxy.ts`     | Lightweight proxy relaying sprinkle operations from offscreen document to side panel UI via chrome.runtime messaging                    |

### packages/webapp/src/fs/ — Virtual Filesystem

| File                   | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- |   |
| `virtual-fs.ts`        | POSIX-like FS facade wrapping LightningFS (IndexedDB); all paths absolute/normalized                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `restricted-fs.ts`     | RestrictedFS wrapper with path ACL (enforces scoop sandboxes: `/scoops/{name}/` + `/shared/`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `types.ts`             | FsError (POSIX codes), DirEntry, Stats, read/write/mkdir options                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `path-utils.ts`        | normalizePath, splitPath, relativePath utilities                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `mount-commands.ts`    | `mount` dispatcher (parses `--source`/`--profile`/`--no-probe`/`--max-body-mb` flags, routes to local/S3/DA backend factories by URL scheme)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `mount-table-store.ts` | IDB persistence layer; `MountTableEntry` + `BackendDescriptor` (discriminated union: local/s3/da) survives session restart                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `mount-recovery.ts`    | On session restore, reconstructs backends per `descriptor.kind`; surfaces unrestorable mounts as a `session-reload` lick                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `mount-index.ts`       | Fast directory walk index for local mounts (remote backends use `RemoteMountCache` instead)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `mount/`               | Backend abstraction. `backend.ts` (`MountBackend` interface), `backend-local.ts` (FS Access), `backend-s3.ts` and `backend-da.ts` are **signing-naive** — they hand logical requests (`{bucket, key, method, ...}` for S3; `{path, method, ...}` for DA) to an injected `SignedFetch*` transport. `signed-fetch.ts` builds the production transport (`makeSignedFetchS3` / `makeSignedFetchDa`) which routes to `/api/s3-sign-and-forward` (CLI) or a `chrome.runtime` message to the SW (extension). `sign-and-forward-shared.ts` holds the shared orchestrator consumed by the SW handler (and node-server has its own mirrored copy). `remote-cache.ts` (TTL + ETag, IDB-backed), `signing-s3.ts` (pure SigV4 v4 via Web Crypto — used server-side / SW-side, NOT in the browser bundle's hot path), `fetch-with-budget.ts` (timeout + retry + abort), `profile.ts` (legacy resolver still used by tests; `getDefaultImsClient()` reads the Adobe LLM provider's IMS token for DA mounts), `mount-id.ts` (UUID generator) |
| `index.ts`             | Re-exports                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

### packages/webapp/src/git/ — Git Integration

| File              | Purpose                                                                                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `git-commands.ts` | CLI-like interface for isomorphic-git (init, clone, add, commit, status, log, branch, checkout, diff, remote, fetch, pull, push, config, rev-parse)     |
| `git-http.ts`     | CORS proxy integration for git HTTP operations; routes through `createProxiedFetch()` (CLI: `/api/fetch-proxy`, extension: `fetch-proxy.fetch` SW Port) |
| `diff.ts`         | Unified diff + stat formatting utilities                                                                                                                |
| `index.ts`        | GitCommands factory                                                                                                                                     |

### packages/webapp/src/providers/ — API Providers

| File                           | Purpose                                                                                                                                                                                                                                |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `types.ts`                     | `ProviderConfig` interface (id, name, isOAuth, onOAuthLogin, onOAuthLogout, getModelIds, modelOverrides), `ModelMetadata` interface (api, context_window, max_tokens, reasoning, input — snake_case wire format), `OAuthLauncher` type |
| `index.ts`                     | Provider auto-discovery: pi-ai providers filtered by `packages/dev-tools/providers.build.json`, built-in extensions via glob, external `/packages/webapp/providers/*.ts` always included                                               |
| `oauth-service.ts`             | Generic `OAuthLauncher` factory: CLI mode (popup → `/auth/callback` → postMessage) and extension mode (service worker → `chrome.identity.launchWebAuthFlow`)                                                                           |
| `built-in/bedrock-camp.ts`     | AWS Bedrock CAMP provider — custom stream function via `register()` (only built-in that needs a file; pure-config providers use pi-ai auto-discovery)                                                                                  |
| `built-in/azure-ai-foundry.ts` | Azure AI Foundry provider configuration (Claude on Azure)                                                                                                                                                                              |

### packages/webapp/src/shell/ — Shell & Terminal

| File                       | Purpose                                                                                                                               |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `wasm-shell.ts`            | WasmShell class; just-bash interpreter + xterm.js terminal + command registration (VfsAdapter bridges to VirtualFS)                   |
| `index.ts`                 | Re-exports                                                                                                                            |
| `vfs-adapter.ts`           | Implements just-bash IFileSystem interface, bridges just-bash ↔ VirtualFS                                                             |
| `binary-cache.ts`          | Caches binary responses (Uint8Array) to preserve byte fidelity through VFS writes                                                     |
| `script-catalog.ts`        | Shared `.jsh`/`.bsh` discovery cache; invalidated by `FsWatcher`, bypasses cache for mounted roots where external edits are invisible |
| `jsh-discovery.ts`         | Scans VFS for `*.jsh` files; returns `Map<name, path>` with priority roots (`/workspace/skills/`) scanned first                       |
| `bsh-discovery.ts`         | Scans `/workspace` and `/shared` for `*.bsh` browser helpers and parses hostname / `@match` metadata                                  |
| `jsh-executor.ts`          | Executes `.jsh` files with Node-like globals (process, console, fs bridge); dual-mode (AsyncFunction CLI, sandbox iframe extension)   |
| `parse-shell-args.ts`      | Shell-like argument parser (double/single quotes, backslash escapes)                                                                  |
| `supplemental-commands.ts` | Re-exports all supplemental command factories                                                                                         |

### packages/webapp/src/shell/supplemental-commands/ — Custom Shell Commands

| File                     | Purpose                                                                                                                                                                     |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index.ts`               | Factory for all supplemental commands                                                                                                                                       |
| `help-command.ts`        | `commands` — list all available commands                                                                                                                                    |
| `convert-command.ts`     | `convert` — ImageMagick-style image processing (resize, rotate, crop, quality) via magick-wasm                                                                              |
| `crontask-command.ts`    | `crontask` — schedule cron jobs (dispatches licks to scoops); backed by node-cron                                                                                           |
| `imgcat-command.ts`      | `imgcat` — display images inline in terminal                                                                                                                                |
| `node-command.ts`        | `node -e` — execute JavaScript (CLI: AsyncFunction, extension: sandbox iframe)                                                                                              |
| `open-command.ts`        | `open <path\|url>` — serve VFS files via preview SW or open URLs in browser tab; `--download` / `-d` forces download; `--view` / `-v` returns image inline for agent vision |
| `pdftk-command.ts`       | `pdftk` — PDF manipulation (concat, split, rotate, burst, etc.)                                                                                                             |
| `python-command.ts`      | `python3/python -c` — execute Python via Pyodide (~13MB bundled, loaded from `chrome.runtime.getURL('pyodide/')`)                                                           |
| `shared.ts`              | Shared utilities: `toPreviewUrl()` (dual-mode preview SW URL), `isLikelyUrl()`, `basename()`, `dirname()`, NodeExitError, nodeRuntimeState, formatConsoleArg                |
| `sqlite-command.ts`      | `sqlite3` — SQLite database operations (in-memory or VFS-backed)                                                                                                            |
| `unzip-command.ts`       | `unzip` — extract archives                                                                                                                                                  |
| `upskill-command.ts`     | `upskill` — install skills from GitHub, the Tessl registry, or browse.sh; `upskill tabs` suggests skills for open browser tabs                                              |
| `uname-command.ts`       | `uname` — print the current browser user agent                                                                                                                              |
| `webhook-command.ts`     | `webhook` — manage webhooks for event-driven automation                                                                                                                     |
| `which-command.ts`       | `which` — resolve command to path (built-ins: `/usr/bin/<name>`, `.jsh` scripts: actual VFS path)                                                                           |
| `zip-command.ts`         | `zip` — create archives                                                                                                                                                     |
| `serve-command.ts`       | `serve` — open a VFS app directory in a browser tab via preview service worker with optional `--entry` override                                                             |
| `oauth-token-command.ts` | `oauth-token` — retrieve OAuth access tokens for configured providers with auto-login                                                                                       |
| `playwright-command.ts`  | `playwright-cli` / `playwright` / `puppeteer` — browser automation shell commands (navigate, snapshot, click, screenshot, cookies, HAR recording)                           |
| `sprinkle-command.ts`    | `sprinkle` — list, open, close, and refresh `.shtml` sprinkle panels from the agent                                                                                         |
| `magick-wasm.ts`         | Shared ImageMagick WASM initialization module for dual-mode (CLI/browser CDN vs extension bundled) image processing                                                         |

### packages/webapp/src/skills/ — Skill Discovery

| File                   | Purpose                                                                                                                                                                                                                          |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `discover.ts`          | discoverSkills: scans native `/workspace/skills/` plus accessible `.agents/skills/*/SKILL.md` and `.claude/skills/*/SKILL.md` roots in the reachable VFS; getSkillInfo/readSkillInstructions expose the winning discovered skill |
| `catalog.ts`           | discoverSkillCandidates: low-level walker shared by discovery, `which`, etc.; resolveSkillNameCollisions: precedence + shadowed-path bookkeeping                                                                                 |
| `install-from-drop.ts` | installSkillFromDrop: validates and unpacks dropped `.skill` ZIP archives into `/workspace/skills/{name}` (must contain a SKILL.md)                                                                                              |
| `constants.ts`         | SKILLS_DIR, SKILL_FILE, archive size limits, etc.                                                                                                                                                                                |
| `types.ts`             | DiscoveredSkill, SkillDiscoverySource interfaces                                                                                                                                                                                 |
| `index.ts`             | Re-exports                                                                                                                                                                                                                       |

All skills (native and compatibility) are read-only — the slicc-specific `manifest.yaml` install/uninstall machinery has been removed. `upskill` writes new skills into `/workspace/skills/` directly; users edit or delete those directories with regular VFS commands.

### packages/webapp/src/scoops/ — Multi-Agent Orchestration

| File                        | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| --------------------------- |   |
| `orchestrator.ts`           | Manages scoop contexts, routes messages, handles responses, owns shared VirtualFS                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `scoop-context.ts`          | Per-scoop agent instance (RestrictedFS, WasmShell, Agent, skills, scoop-management tools); wires file tools + `bash` + `grep`/`find`, with browser automation via `playwright-cli` shell commands. Overflow recovery preserves ToolCall blocks in assistant messages to maintain API-required tool_use ↔ toolResult pairing                                                                                                                                                                                                                                                                         |
| `scoop-management-tools.ts` | Scoop tools: `send_message`; cone-only tools: `list_scoops`, `scoop_scoop`, `feed_scoop`, `drop_scoop`, `update_global_memory`                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `db.ts`                     | IndexedDB (`slicc-groups` DB v3): scoops, messages, sessions, tasks, state, webhooks, crontasks stores                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `lick-manager.ts`           | Browser-side lick management (webhooks + crontasks); all state in IndexedDB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `scheduler.ts`              | TaskScheduler for internal task scheduling (used by orchestrator)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `heartbeat.ts`              | Heartbeat monitoring (detects when scoop contexts are idle)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `skills.ts`                 | loadSkills, formatSkillsForPrompt: load SKILL.md files into agent system prompt; createDefaultSkills: bundled defaults                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `tray-sync-protocol.ts`     | Typed `LeaderToFollowerMessage` / `FollowerToLeaderMessage` unions + chunked snapshot/CDP/sprinkle helpers. Canonical wire format — the iOS follower's `SyncProtocol.swift` mirrors a **subset**: iOS receives leader-initiated `cdp.request` / `tab.open` and sends `cdp.response` / `cdp.event` / `tab.opened` back. What's TS-only: federated `fs.*` in both directions, follower-originated `cdp.request` / `tab.open` (iOS never originates), the leader→follower reply path for those, and `tab.open.error` send-side. See `Multi-Browser Sync (Tray) Architecture` below for the full matrix |
| `tray-leader-sync.ts`       | Leader-side `LeaderSyncManager`: broadcasts agent events, snapshots, scoop list, sprinkle list + content + updates to all connected followers; routes inbound `sprinkle.fetch`/`sprinkle.lick`/`scoops.select`/`request_snapshot` from followers, plus federated CDP and FS                                                                                                                                                                                                                                                                                                                         |
| `tray-follower-sync.ts`     | Follower-side `FollowerSyncManager` (TS — used by browser standalone follower and extension offscreen follower). Implements `AgentHandle`, receives agent events / snapshots / sprinkle messages from leader, advertises local browser targets, forwards CDP+FS for federated targets. `scoops.list` arrives but currently hits the default log-and-drop branch (no scoop switcher on the TS side yet — iOS consumes it for swipe navigation)                                                                                                                                                       |
| `tray-leader.ts`            | `LeaderTrayManager`: WebSocket liaison with the cloudflare-worker tray hub; control-message dispatch                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `tray-follower.ts`          | `FollowerTrayManager` + `startFollowerWithAutoReconnect`: WebRTC signaling client, peer-connection setup, auto-reconnect with backoff                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `tray-webrtc.ts`            | `TrayDataChannelLike` interface and shared WebRTC helpers used by both sides                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `tray-fs-handler.ts`        | Handles inbound `fs.request` messages by running them against the local VFS and chunking large file responses                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `tray-target-registry.ts`   | Leader-side merged registry of CDP targets across all connected runtimes (leader + followers)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `tray-runtime-config.ts`    | `resolveTrayRuntimeConfig`: derives `{ joinUrl, workerBaseUrl }` from URL + storage; determines leader vs follower role                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `tray-follower-status.ts`   | Observable status surface for the follower runtime (`connecting`/`connected`/`error`); used by UI                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `types.ts`                  | RegisteredScoop, ChannelMessage, ScoopTabState, ScheduledTask, WebhookEntry, CronTaskEntry                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `index.ts`                  | Re-exports                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

### packages/webapp/src/tools/ — Agent Tools

| File              | Purpose                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------------------- |
| `bash-tool.ts`    | `bash` tool: execute shell commands via WasmShell                                                             |
| `file-tools.ts`   | `read_file`, `write_file`, `edit_file` tools for VirtualFS operations                                         |
| `search-tools.ts` | `grep` and `find` tool factories for recursive VirtualFS search (not part of the active ScoopContext surface) |
| `index.ts`        | Tool factory functions (createBashTool, createFileTools, createSearchTools)                                   |

### packages/webapp/src/ui/ — User Interface

| File                        | Purpose                                                                                                                                                                                              |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `main.ts`                   | Entry point: `main()` for CLI/Electron embedded app, `mainExtension()` for extension (uses OffscreenClient). Handles layout, API key, orchestrator, skill drag/drop                                  |
| `offscreen-client.ts`       | Extension-only: side panel's interface to offscreen engine. Provides AgentHandle + Orchestrator-compatible facade via chrome.runtime messages                                                        |
| `layout.ts`                 | Unified split-pane layout. `Layout(root, isExtension)` toggles density (scoops rail, switcher, avatar). Detached popout mode passes `isExtension=false` for full standalone UX.                      |
| `tabbed-ui.ts`              | Shared Chat/Terminal/Files/Memory tab definitions + normalization helpers reused by the extension layout and injected overlay shell                                                                  |
| `overlay-shell-state.ts`    | Pure state transitions for the injected Electron overlay shell (open/close + active tab)                                                                                                             |
| `electron-overlay.ts`       | Browser-side custom elements for the injected Electron overlay shell: launcher button, sidebar, persistent iframe host, and parent→iframe tab sync                                                   |
| `electron-overlay-entry.ts` | Standalone injected bundle entry that exposes `window.__SLICC_ELECTRON_OVERLAY__.inject()` / `remove()` for Electron reinjection                                                                     |
| `chat-panel.ts`             | Message list + input with streaming support; voice input (Web Speech API); connects to AgentHandle                                                                                                   |
| `terminal-panel.ts`         | xterm.js terminal UI; exposes WasmShell output                                                                                                                                                       |
| `file-browser-panel.ts`     | File tree browser; download files/ZIP folders; navigate filesystem                                                                                                                                   |
| `memory-panel.ts`           | Global memory editor (IndexedDB-backed; shared across all scoops)                                                                                                                                    |
| `scoops-panel.ts`           | Scoop list (CLI mode left sidebar); create/delete/view scoops                                                                                                                                        |
| `scoop-switcher.ts`         | Dropdown menu for scoop selection (extension mode)                                                                                                                                                   |
| `message-renderer.ts`       | Renders user messages, assistant messages, tool calls, tool results as HTML                                                                                                                          |
| `voice-input.ts`            | Voice mode toggle; auto-sends on 2.5s silence; falls back to popup in extension mode                                                                                                                 |
| `skill-drop.ts`             | Pure helpers for detecting supported dropped `.skill` files                                                                                                                                          |
| `preview-sw.ts`             | Service Worker that intercepts `/preview/*` and serves VFS content (enables in-browser app previews)                                                                                                 |
| `session-store.ts`          | IndexedDB session storage (`browser-coding-agent` DB): conversation history per session                                                                                                              |
| `provider-settings.ts`      | API provider + model selection; stores settings in localStorage                                                                                                                                      |
| `api-key-dialog.ts`         | Dialog for entering API keys                                                                                                                                                                         |
| `theme.ts`                  | Theme toggle (System/Light/Dark)                                                                                                                                                                     |
| `types.ts`                  | AgentHandle, AgentEvent, ChatMessage, ToolCall, UIMessage interfaces                                                                                                                                 |
| `panel-registry.ts`         | Registry of all panels (built-in + SHTML sprinkles) with zone placement and lookup/management methods                                                                                                |
| `panel-types.ts`            | Shared type definitions: ZoneId, PanelDescriptor, PanelRegistryEntry for the panel system                                                                                                            |
| `runtime-mode.ts`           | Runtime mode detection (standalone/extension/electron-overlay) and Electron overlay messaging utilities                                                                                              |
| `tab-zone.ts`               | Generic reusable tab bar + content area manager for a single zone                                                                                                                                    |
| `sprinkle-manager.ts`       | Registry of available and open `.shtml` sprinkle panels with placement and lifecycle management                                                                                                      |
| `sprinkle-discovery.ts`     | Scans VirtualFS for `.shtml` sprinkle files and builds a map of names to metadata (path, title)                                                                                                      |
| `sprinkle-renderer.ts`      | Loads `.shtml` content from VFS and renders into DOM. CLI: direct DOM injection (fragments) or srcdoc iframe (full docs). Extension: ALL content routes through `sprinkle-sandbox.html` (CSP-exempt) |
| `dip.ts`                    | Hydrates ` ```shtml ` code blocks in chat into sandboxed iframes. CLI: direct srcdoc. Extension: routes through `sprinkle-sandbox.html`                                                              |
| `sprinkle-bridge.ts`        | API available to `.shtml` sprinkle scripts for communicating with the agent via lick events and state persistence                                                                                    |
| `sprinkle-picker.ts`        | Popup menu listing closed panels and unopened sprinkles for opening in a zone                                                                                                                        |
| `index.ts`                  | Re-exports                                                                                                                                                                                           |

### packages/webapp/src/shims/ — Node.js Polyfills

| File                                           | Purpose                                                          |
| ---------------------------------------------- | ---------------------------------------------------------------- |
| `empty.ts`                                     | Stubs out node:zlib and node:module (just-bash references these) |
| `buffer-polyfill.ts`                           | Polyfills Buffer for browser (isomorphic-git requirement)        |
| `http.ts`, `http2.ts`, `https.ts`, `stream.ts` | Node module stubs (imported by dependencies, no-op in browser)   |

### packages/webapp/src/types/ — Type Declarations

| File                              | Purpose                                                                                                     |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `pi-coding-agent-compaction.d.ts` | Type declarations for pi-coding-agent compaction submodule (estimateTokens, shouldCompact, generateSummary) |

### packages/vfs-root/ — Bundled VFS Content

Default files bundled into the VFS at startup via `import.meta.glob`:

| Path                | VFS Target           | Purpose                                                        |
| ------------------- | -------------------- | -------------------------------------------------------------- |
| `shared/CLAUDE.md`  | `/shared/CLAUDE.md`  | Agent system-level instructions (loaded into sliccy's context) |
| `workspace/skills/` | `/workspace/skills/` | Default skill packages (playwright-cli, sprinkles, etc.)       |
| `shared/sprinkles/` | `/shared/sprinkles/` | Default sprinkle panels (welcome)                              |

### packages/webapp/src/ — Root

| File           | Purpose                                |
| -------------- | -------------------------------------- |
| `globals.d.ts` | TypeScript globals (\_\_DEV\_\_, etc.) |

## Build Targets Table

| Target                     | tsconfig                                           | Input                              | Output                        | Module Resolution |
| -------------------------- | -------------------------------------------------- | ---------------------------------- | ----------------------------- | ----------------- |
| Browser bundle             | `packages/webapp/vite.config.ts` + `tsconfig.json` | `packages/webapp/`                 | `dist/ui/` (via Vite)         | bundler           |
| CLI + Electron Node target | `tsconfig.cli.json`                                | `packages/node-server/src/`        | `dist/node-server/` (via TSC) | NodeNext          |
| Extension                  | `packages/chrome-extension/vite.config.ts`         | Browser bundle + extension entries | `dist/extension/`             | bundler           |

### Special Build Artifacts

- **preview-sw.ts**: Built as standalone IIFE via esbuild (not rollup) from `packages/webapp/vite.config.ts` during the production webapp build.
- **electron-overlay-entry.ts**: Built as standalone IIFE alongside `dist/ui/electron-overlay-entry.js` from `packages/webapp/vite.config.ts` for Electron reinjection.
- **Extension assets**: Pyodide (~13MB), ImageMagick WASM, `sandbox.html`, `voice-popup.html`, `offscreen.html` copied to `dist/extension/` by `packages/chrome-extension/vite.config.ts`. The `offscreen.html` entry point runs the agent orchestrator in an unrestricted context separate from the side panel.
- **Node shims**: `packages/webapp/src/shims/` provide no-op implementations for Node modules (just-bash references them).

## Extension Three-Layer Architecture

The Chrome extension uses a three-layer design to keep the agent engine alive across side panel close/reopen cycles:

```
┌──────────────────────────────────────────────────────────────┐
│ Side Panel (UI)                                               │
│  offscreen-client.ts — Chat, Terminal, Files, Memory          │
│  Sends: PanelToOffscreenMessage (user input, commands)        │
│  Receives: OffscreenToPanelMessage (agent events, state)      │
└─────────────────────────┬────────────────────────────────────┘
                          │ chrome.runtime messages
┌─────────────────────────▼────────────────────────────────────┐
│ Service Worker Relay (service-worker.ts)                      │
│  Routes Panel ↔ Offscreen messages                            │
│  Proxies CDP: CdpProxyMessage ↔ chrome.debugger               │
└─────────────────────────┬────────────────────────────────────┘
                          │ chrome.runtime messages
┌─────────────────────────▼────────────────────────────────────┐
│ Offscreen Document (offscreen.ts, offscreen-bridge.ts)        │
│  Agent Engine — Orchestrator, VFS, Shell, Tools               │
│  Persists chat to: browser-coding-agent IndexedDB             │
│  Dispatches CDP via service worker proxy                      │
└──────────────────────────────────────────────────────────────┘
```

**Detached popout:** the side panel can also be popped out into a full-page tab (`chrome-extension://<id>/index.html?detached=1`). The detached tab is a second valid UI client surface — it talks to the same offscreen agent via the same `chrome.runtime` messages as the side panel. The service worker enforces global mutual exclusion (at most one detached tab; side panel disabled while one exists). See `packages/chrome-extension/CLAUDE.md` "Detached Popout" and `docs/superpowers/specs/2026-05-13-extension-detached-popout-design.md`.

**Message Flow:**

- **PanelToOffscreenMessage**: User input flows from panel → service worker → offscreen
- **OffscreenToPanelMessage**: Agent responses flow from offscreen → service worker → panel
- **CdpProxyMessage**: Browser automation (screenshot, click, evaluate) flows from offscreen → service worker → chrome.debugger

**IndexedDB Persistence:**

- `browser-coding-agent` DB: Chat display messages (single source of truth, written by offscreen bridge, read by side panel on reconnect)
- `agent-sessions` DB: Agent LLM conversation history (restored by ScoopContext on restart)
- `slicc-groups` DB: Orchestrator routing data (scoops, tasks, webhooks, crontasks)

**CDP Proxy:** Offscreen documents can't call `chrome.debugger` directly. Instead, offscreen sends `CdpProxyMessage` through the service worker, which translates to `chrome.debugger` commands and routes results back.

**Dual Shell Context:** Both the side panel and offscreen document run their own WasmShell instance. The panel shell powers the Terminal tab; the offscreen shell executes agent bash tool calls. They share VFS via IndexedDB but NOT window globals or DOM. Shell commands that need to affect the panel UI from the offscreen agent must use the dual-context pattern: try a `window.__slicc_*` hook first (panel), fall back to `chrome.runtime.sendMessage` relay (offscreen → panel). See `docs/pitfalls.md` "Extension Dual-Shell Context".

## Multi-Browser Sync (Tray) Architecture

SLICC instances can connect to one another over WebRTC data channels to form a **tray** — one **leader** owning the agent runtime and any number of **followers** mirroring its state. The cloudflare-worker is the signaling hub; once the data channel is open, all sync traffic is peer-to-peer.

### Roles

- **Leader**: owns the orchestrator, agent loop, scoops, sprinkles, and lick router. Broadcasts state changes; responds to follower requests (snapshot, sprinkle.fetch, sprinkle.lick, scoops.select, federated CDP/FS).
- **Follower**: presents the leader's chat + sprinkles to a local user, forwards user input back to the leader. Owns no agent runtime — its `ChatPanel` is wired to the follower sync as its `AgentHandle`.

### Implementations

| Float                       | Code                                                                                                | Notes                                                                                                                                                                                       |
| --------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Standalone browser leader   | `packages/webapp/src/ui/page-leader-tray.ts` + `packages/webapp/src/scoops/tray-leader-sync.ts`     | Wired in `main.ts` when no join URL is present and a worker base URL is configured                                                                                                          |
| Standalone browser follower | `packages/webapp/src/ui/page-follower-tray.ts` + `packages/webapp/src/scoops/tray-follower-sync.ts` | Wired in `main.ts` when a join URL is stored. Drives the page's chat panel via the follower sync's `AgentHandle`                                                                            |
| Extension leader            | `packages/chrome-extension/src/offscreen.ts` (`workerBaseUrl` branch in `syncTrayRuntime`)          | Uses `ServiceWorkerLeaderTraySocket` to keep the WebSocket alive in the service worker                                                                                                      |
| Extension follower          | `packages/chrome-extension/src/offscreen.ts` (`joinUrl` branch in `syncTrayRuntime`)                | Constructs a `FollowerSyncManager` against the leader; results relayed to the side panel via `offscreen-bridge` (`bridge.applyFollowerSnapshot`, etc.)                                      |
| iOS follower (native)       | `packages/ios-app/SliccFollower/`                                                                   | Standalone Swift implementation. `Models/SyncProtocol.swift` mirrors a **subset** of the TS wire — the table below marks which messages each side handles. See `packages/ios-app/CLAUDE.md` |

### Protocol

`packages/webapp/src/scoops/tray-sync-protocol.ts` is the canonical wire format. The iOS follower's `packages/ios-app/SliccFollower/Models/SyncProtocol.swift` mirrors a **subset**:

- iOS implements: chat (snapshot/snapshot_chunk/agent_event/user_message_echo), control (status/error/ping/pong), scoops list, full sprinkle round-trip, leader-initiated federated CDP/tabs (receives `cdp.request` + `tab.open` from leader and replies with `cdp.response` / `cdp.event` / `tab.opened`), `targets.advertise`.
- TS-only: federated `fs.request` / `fs.response` in both directions; follower-initiated `cdp.request` and `tab.open` against another runtime (iOS only _responds_ to leader-initiated requests, never originates them); the reply path BACK on the leader→follower side for follower-originated requests (`cdp.response` / `cdp.event` / `tab.opened` / `tab.open.error` flowing leader→follower-the-requester); `tab.open.error` send-side (iOS always sends `.tabOpened` and embeds CDP errors in `cdp.response.error` instead).
- iOS `LeaderToFollowerMessage.init(from:)` has an `.unknown` fallback so the app silently drops messages it doesn't recognize. iOS `FollowerToLeaderMessage.init(from:)` throws on unknown types. The doc-comment headers above each enum's `// MARK: -` boundary in `SyncProtocol.swift` declare this asymmetry explicitly.

The "Followers" column below answers which followers handle each message today. When adding a new message, decide which followers must learn it: a follower-side feature that only matters to the browser (federated FS) can stay TS-only; a chat/sprinkle/scoop feature should land in both.

| Direction       | Message                                                     | Followers       | Used for                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------- | ----------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Leader→Follower | `snapshot`, `snapshot_chunk`                                | TS + iOS        | Full chat replacement (chunked above 64 KB)                                                                                                                                                                                                                                                                                                                                                            |
| Leader→Follower | `agent_event`                                               | TS + iOS        | Streaming agent events (`content_delta`, `tool_use_start`, etc.)                                                                                                                                                                                                                                                                                                                                       |
| Leader→Follower | `user_message_echo`                                         | TS + iOS\*      | Mirrors any user message (local or from any follower). \* iOS drops `attachments` (Codable subset)                                                                                                                                                                                                                                                                                                     |
| Leader→Follower | `status`, `error`, `ping`, `pong`                           | TS + iOS        | Liveness + scoop processing state                                                                                                                                                                                                                                                                                                                                                                      |
| Leader→Follower | `scoops.list`                                               | iOS, TS planned | Multi-scoop awareness — list + active scoop JID. (TS follower controller doesn't yet expose a scoop switcher.)                                                                                                                                                                                                                                                                                         |
| Leader→Follower | `sprinkles.list`, `sprinkle.content`, `sprinkle.update`     | TS + iOS        | Sprinkle list + on-demand .shtml content (chunked) + agent → sprinkle updates                                                                                                                                                                                                                                                                                                                          |
| Leader→Follower | `targets.registry`, `cdp.request`, `tab.open`               | TS + iOS        | Federated CDP / tab.open from leader to a follower that hosts the target                                                                                                                                                                                                                                                                                                                               |
| Leader→Follower | `cdp.response`, `cdp.event`, `tab.opened`, `tab.open.error` | TS only         | Reply path back to a follower that _originated_ a federated request; iOS never originates these so it never has to consume the reply                                                                                                                                                                                                                                                                   |
| Leader→Follower | `fs.request`, `fs.response`                                 | TS only         | Federated FS (read/write/stat/walk against a follower's local VFS)                                                                                                                                                                                                                                                                                                                                     |
| Follower→Leader | `user_message`, `abort`                                     | TS + iOS        | Forward chat input + cancellation                                                                                                                                                                                                                                                                                                                                                                      |
| Follower→Leader | `ping`, `pong`                                              | TS + iOS        | Liveness — symmetric with leader→follower                                                                                                                                                                                                                                                                                                                                                              |
| Follower→Leader | `request_snapshot`                                          | TS + iOS        | Request a fresh snapshot (optionally for a specific scoop via `request_snapshot.scoopJid` — the path iOS actually uses today)                                                                                                                                                                                                                                                                          |
| Follower→Leader | `scoops.select`                                             | declared only   | Protocol type exists and the leader handles it (`tray-leader-sync.ts:scoops.select`), but no follower currently emits it — scoop selection rides on `request_snapshot.scoopJid` instead. Kept reserved                                                                                                                                                                                                 |
| Follower→Leader | `sprinkles.refresh`, `sprinkle.fetch`, `sprinkle.lick`      | TS + iOS        | Re-request sprinkle list; fetch .shtml content by name; forward a lick from a follower-rendered sprinkle to the leader's lick router                                                                                                                                                                                                                                                                   |
| Follower→Leader | `targets.advertise`                                         | TS + iOS        | Advertise a follower's locally hosted CDP targets to the leader                                                                                                                                                                                                                                                                                                                                        |
| Follower→Leader | `cdp.response`, `cdp.event`, `tab.opened`                   | TS + iOS        | Reply path back to leader after the follower fulfills a leader-initiated `cdp.request` / `tab.open`                                                                                                                                                                                                                                                                                                    |
| Follower→Leader | `cdp.request`, `tab.open`                                   | TS only         | Follower-initiated reach into another runtime via the leader (iOS does not originate)                                                                                                                                                                                                                                                                                                                  |
| Follower→Leader | `tab.open.error`                                            | TS only         | Declared in the union for symmetry with the leader→follower error reply. iOS's `CDPBridge.handleTabOpen` issues `.tabOpened` synchronously after kicking off the async navigation, so navigation failures never surface here. The TS follower's `executeLocalTabOpen` sends this in three cases: missing browser transport, `Target.createTarget` returning no usable targetId, or transport rejection |
| Follower→Leader | `fs.request`, `fs.response`                                 | TS only         | Federated FS                                                                                                                                                                                                                                                                                                                                                                                           |

### Sprinkle Sync Specifics

Sprinkle .shtml files live in the **leader's** VFS only — followers see them via three messages:

- The leader sends `sprinkles.list` on follower attach and on every refresh broadcast (default 5 s; the interval is configurable via the `_refreshIntervalMs` hook in `page-leader-tray.ts`).
- The follower opens a sprinkle by sending `sprinkle.fetch` with a request ID; the leader replies with one or more `sprinkle.content` messages (chunked above 64 KB) containing the raw .shtml.
- The follower renders the content and forwards any `sprinkle.lick` events back to the leader. The leader routes them through its lick manager (where `getSprinkleRoute(name)` may target a non-cone scoop).
- When the leader's `SprinkleManager.sendToSprinkle(name, data)` runs, `LeaderSyncManager.broadcastSprinkleUpdate(name, data)` mirrors the payload to every follower as `sprinkle.update`.

The follower's rendered sprinkle has **no VFS access** to the leader. Both follower implementations stub the file-IO bridge methods — `readFile`, `writeFile`, `readDir`, `stat`, `mkdir`, `rm`, and `screenshot` reject with descriptive errors; `exists` returns `Promise.resolve(false)` so feature-detection paths don't throw. See `SprinkleFollowerController.createBridge` (TS) and `SprinkleWebView.bridgeJS` (iOS). Sprinkles that rely on file IO should feature-detect via `exists` and degrade.

## Data Flow Diagrams

### User Message Flow (standalone — kernel-worker mode, default)

```
[Page]                                        [DedicatedWorker]
ChatPanel.sendMessage()
  → OffscreenClient.sendUserMessage()
    → KernelTransport (MessagePort) ─────────→ OffscreenBridge.handleMessage()
                                                 → Orchestrator.handleMessage()
                                                   → routeToScoop()
                                                   → processScoopQueue()
                                                     → ScoopContext.prompt()
                                                       → pm.spawn({ kind:'scoop-turn', … })
                                                       → pi-agent-core loop
                                                         → LLM API call (worker fetch
                                                           with x-bypass-llm-proxy)
                                                         → AgentEvent stream
                                                       → Tool calls (kind:'tool', kind:'shell',
                                                         kind:'jsh', kind:'py')
                                                         → RestrictedFS / WasmShellHeadless /
                                                           BrowserAPI (CDP via WorkerCdpProxy
                                                           ↔ startPageCdpForwarder)
                                                       → pm.exit on completion
                                                     → callbacks fire wire-side events
                                            ←────── KernelTransport stream of
agent-events (text_delta, tool_use_start,
                                                   tool_result, scoop-status, …)
ChatPanel DOM update (streaming)
```

The kernel-worker runs on its own thread; a runaway bash loop or LLM stream can't freeze the page. Page main thread keeps `setInterval(…, 100)` ticking with sub-millisecond jitter while the worker is busy.

### User Message Flow (extension)

```
[Side panel]                          [Service worker]               [Offscreen document]
ChatPanel.sendMessage()
  → OffscreenClient.sendUserMessage()
    → chrome.runtime.sendMessage ────→ relay ────────────────────→ OffscreenBridge.handleMessage()
                                                                     → (same Orchestrator path
                                                                        as worker mode above, but
                                                                        on the offscreen realm)
                                                              ←──── chrome.runtime emit
ChatPanel DOM update
```

Both deployment modes share `createKernelHost(...)` from `kernel/host.ts` — the SAME boot sequence wires the orchestrator, lick manager, agent bridge, process manager, and `/proc` mount. The only differences are the transport (`MessagePort` vs `chrome.runtime`) and the CDP proxy.

### Scoop Delegation

```
Cone executes feed_scoop tool
  → Orchestrator.delegateToScoop()
    → ScoopContext.prompt() [receives full context from cone]
      → pi-agent-core loop
        → Tool calls
        → Scoop processes independently
    → Scoop completes
      → Orchestrator writes full output to /shared/scoop-notifications/
      → Orchestrator notification (path + preview + line count)
        → Cone's message queue
        → Cone decides whether to read the file or act on the preview
```

### Lick (Event) Flow

```
External webhook POST / scheduled cron task / fswatch change fires
  → LickManager receives event in IndexedDB
    → dispatch() routes to target scoop
      → ScoopContext processes lick
        → Agent reacts to event
        → No human in the loop
```

### Agent Session Persistence

Agent conversation history is persisted per scoop, enabling agents to resume where they left off across page reloads or extension close-reopen cycles.

```
ScoopContext init (page load / scoop creation)
  → SessionStore.load(scoop.jid) [retrieves AgentMessage[] from agent-sessions DB]
    → Agent initialized with restored messages
      → agent loop resumes with full context

Agent responds (streaming)
  → agent_end event
    → SessionStore.save({ id, messages, config, createdAt, updatedAt }) [fire-and-forget]
      → Persists SessionData to agent-sessions DB

Scoop removal / app clear
  → Orchestrator calls SessionStore.delete(jid) or SessionStore.clearAll()
    → Clears persisted session data
```

**Session Storage:**

- Database: `agent-sessions` (IndexedDB)
- Key: scoop JID (e.g., `cone`, `analysis-scoop`)
- Value: `SessionData` (`AgentMessage[]` + config + timestamps)
- Lifecycle: Loaded on scoop init, saved on agent_end (error-tolerant), deleted on scoop removal
- Design: Messages are model-agnostic and work with any LLM. `createCompactContext()` provides LLM-summarized compaction at prompt time, so large sessions don't cause token bloat.

## IndexedDB Databases

| Database               | Version | Stores                                                        | Purpose                                                                                                                                                        |
| ---------------------- | ------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `slicc-fs`             | 1       | (VirtualFS data)                                              | POSIX filesystem backing store (LightningFS)                                                                                                                   |
| `browser-coding-agent` | 1       | sessions, settings                                            | UI-level session history + localStorage mirror                                                                                                                 |
| `slicc-groups`         | 3       | scoops, messages, sessions, tasks, state, webhooks, crontasks | Orchestrator data (scoops, messages, tasks)                                                                                                                    |
| `agent-sessions`       | 1       | sessions                                                      | Core agent session history: persisted `SessionData` (`AgentMessage[]` + config + timestamps) per scoop, keyed by JID; loaded on scoop init, saved on agent_end |
| `slicc-fs-global`      | 1       | config                                                        | Git global config storage                                                                                                                                      |

## Secrets & Secret Injection

SLICC prevents the agent from seeing or exfiltrating real secret values (API keys, tokens, credentials). Secrets are stored server-side and injected at the fetch-proxy boundary, scoped to authorized domains only.

### Masking Engine

Each secret is masked using `HMAC-SHA256(session_id + secret_name, secret_value)`. The result is format-preserving — known prefixes like `ghp_`, `sk-`, `AKIA` are kept, and the hash portion matches the original value's length and character set. Masks are deterministic within a session (the agent can compare values) but differ across sessions (no lookup-table attacks).

### Scrubbing Pipeline

Real secret values are scrubbed at every output boundary before reaching the agent:

1. **Shell environment** — env vars contain masked values, not real ones
2. **Tool output** — all `bash`, `read_file`, and other tool results are scanned for real values and replaced with masks
3. **Fetch proxy (outbound)** — masked values in request headers are unmasked if the domain matches; 403 if not. Masked values in request bodies are unmasked for matching domains and passed through unchanged otherwise
4. **Fetch proxy (inbound)** — response bodies and headers are scanned for real values and replaced with masks
5. **Chat messages** — user-typed real values are scrubbed before entering the agent conversation

### Storage Backends

| Backend        | Runtime                     | Storage                                                        | Encryption                           |
| -------------- | --------------------------- | -------------------------------------------------------------- | ------------------------------------ |
| macOS Keychain | swift-server                | `SecItemAdd`/`SecItemCopyMatching`, service `ai.sliccy.slicc`  | Login keychain (encrypted at rest)   |
| `.env` file    | node-server (all platforms) | `~/.slicc/secrets.env`, `KEY=VALUE` + `KEY_DOMAINS=...` format | Filesystem permissions (`chmod 600`) |

Both implement the same `SecretStore` interface: `get(name)`, `set(name, value, config)`, `delete(name)`, `list()`.

File path resolution: `--env-file <path>` CLI flag → `SLICC_SECRETS_FILE` env var → `~/.slicc/secrets.env`.

### Key Source Files

| File                                                                      | Purpose                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `packages/shared-ts/src/secret-masking.ts`                                | Platform-agnostic masking primitives (HMAC-SHA256, domain matching, scrubbing) — moved from `packages/webapp/src/core/` and consumed by webapp, node-server, and chrome-extension SW                                                                                                        |
| `packages/shared-ts/src/secrets-pipeline.ts`                              | Stateful unmask/scrub class; Basic-auth-aware, URL-credential-aware, byte-safe body unmask                                                                                                                                                                                                  |
| `packages/node-server/src/secrets/`                                       | Node `.env` SecretStore, `OauthSecretStore`, sessionId persistence, mount sign-and-forward (`signing-s3.ts` + `sign-and-forward.ts` for `/api/s3-sign-and-forward` and `/api/da-sign-and-forward` endpoints), `POST /api/secrets/oauth-update`, `DELETE /api/secrets/oauth/:providerId`     |
| `packages/swift-server/Sources/`                                          | Swift Keychain SecretStore, `OAuthSecretStore.swift`, `SecretsPipeline.swift` (mirrors TS implementation)                                                                                                                                                                                   |
| `packages/chrome-extension/src/service-worker.ts`                         | SW handlers: `fetch-proxy.fetch` Port (sync onMessage attach + async pipeline via `handleFetchProxyConnectionAsync`, 32MB cap), `secrets.list-masked-entries`, `secrets.mask-oauth-token`, `secrets.list`/`secrets.set`/`secrets.delete` (proxy for offscreen which lacks `chrome.storage`) |
| `packages/node-server/src/index.ts`                                       | Fetch proxy secret injection (node-server). `SecretProxyManager` wires BOTH `EnvSecretStore` (`.env` file) AND `OauthSecretStore` so env-file secrets reach the masking pipeline alongside OAuth tokens                                                                                     |
| `packages/webapp/src/shell/supplemental-commands/secret-command.ts`       | `secret` shell command — CLI mode hits `/api/secrets`, extension mode routes through SW (`secrets.list/set/delete`)                                                                                                                                                                         |
| `packages/webapp/src/shell/supplemental-commands/oauth-domain-command.ts` | `oauth-domain` shell command — per-provider extra allowed domains for OAuth tokens, stored in `localStorage` (`slicc_oauth_extra_domains`), merged with provider defaults on every `saveOAuthAccount` push                                                                                  |
| `packages/webapp/src/ui/oauth-bootstrap.ts`                               | Awaited at page load: silently renews any expiring/expired OAuth token via `provider.onSilentRenew`, then re-pushes the merged-domains replica. Soft 10s timeout so a hung IMS popup doesn't deadlock the UI                                                                                |
| `packages/webapp/src/scoops/scoop-context.ts`                             | Shell env population with masked values (filtered to POSIX-valid identifiers — dotted names like `s3.*` / `oauth.*` are NOT exposed in `$ENV`), tool output scrubbing                                                                                                                       |

See [docs/secrets.md](secrets.md) for user-facing setup instructions.

## File-Finding Guide

### Virtual Filesystem Changes

| I need to...                          | Modify                                    |
| ------------------------------------- | ----------------------------------------- |
| Add a POSIX filesystem method         | `packages/webapp/src/fs/virtual-fs.ts`    |
| Change path normalization logic       | `packages/webapp/src/fs/path-utils.ts`    |
| Restrict file access by path (scoops) | `packages/webapp/src/fs/restricted-fs.ts` |
| Change file types/interfaces          | `packages/webapp/src/fs/types.ts`         |

### Shell & Terminal Changes

| I need to...                      | Modify                                                                                       |
| --------------------------------- | -------------------------------------------------------------------------------------------- |
| Add a bash command                | `packages/webapp/src/shell/supplemental-commands/<name>-command.ts` + register in `index.ts` |
| Change terminal behavior (xterm)  | `packages/webapp/src/shell/wasm-shell.ts`                                                    |
| Change binary handling            | `packages/webapp/src/shell/binary-cache.ts`                                                  |
| Support new `.jsh` script globals | `packages/webapp/src/shell/jsh-executor.ts`                                                  |
| Change shell argument parsing     | `packages/webapp/src/shell/parse-shell-args.ts`                                              |

### Git Integration

| I need to...               | Modify                                    |
| -------------------------- | ----------------------------------------- |
| Add a git command          | `packages/webapp/src/git/git-commands.ts` |
| Change CORS proxy handling | `packages/webapp/src/git/git-http.ts`     |
| Add diff formatting        | `packages/webapp/src/git/diff.ts`         |

### Browser Automation (CDP)

| I need to...                                   | Modify                                                                        |
| ---------------------------------------------- | ----------------------------------------------------------------------------- |
| Add a browser action (screenshot, click, etc.) | `packages/webapp/src/cdp/browser-api.ts`                                      |
| Change CDP transport (CLI vs extension)        | `packages/webapp/src/cdp/transport.ts`, `cdp-client.ts`, `debugger-client.ts` |
| Add HAR recording features                     | `packages/webapp/src/cdp/har-recorder.ts`                                     |
| Change target/page types                       | `packages/webapp/src/cdp/types.ts`                                            |

### Agent Tools

| I need to...                             | Modify                                                                                                                                         |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Add a new agent tool                     | `packages/webapp/src/tools/<name>-tool.ts` + register in `index.ts`                                                                            |
| Change bash tool behavior                | `packages/webapp/src/tools/bash-tool.ts`                                                                                                       |
| Change file tool behavior                | `packages/webapp/src/tools/file-tools.ts`                                                                                                      |
| Change browser automation shell behavior | `packages/webapp/src/shell/supplemental-commands/playwright-command.ts` and `packages/webapp/src/shell/supplemental-commands/serve-command.ts` |
| Change grep/find tool behavior           | `packages/webapp/src/tools/search-tools.ts`                                                                                                    |
| Change tool input/output format          | `packages/webapp/src/core/types.ts` (ToolDefinition, ToolResult)                                                                               |
| Adapt tools to pi-agent-core             | `packages/webapp/src/core/tool-adapter.ts`                                                                                                     |

### Core Agent & Streaming

| I need to...                                     | Modify                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Change token limit / context compaction strategy | `packages/webapp/src/core/context-compaction.ts`                                                                                                                                                                                                                                                         |
| Change logging format/level                      | `packages/webapp/src/core/logger.ts`                                                                                                                                                                                                                                                                     |
| Change agent conversation history persistence    | `packages/webapp/src/core/session.ts` (SessionStore: load/save/delete/clearAll per-scoop `AgentMessage[]` in `agent-sessions` DB) + `packages/webapp/src/scoops/scoop-context.ts` (restore on init, save on agent_end) + `packages/webapp/src/scoops/orchestrator.ts` (create/pass/cleanup SessionStore) |
| Change MIME type detection                       | `packages/webapp/src/core/mime-types.ts`                                                                                                                                                                                                                                                                 |
| Register new tools                               | `packages/webapp/src/core/tool-registry.ts`                                                                                                                                                                                                                                                              |

### Multi-Agent System

| I need to...                                             | Modify                                                                                                                     |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Manage scoops (create/delete/list)                       | `packages/webapp/src/scoops/orchestrator.ts`                                                                               |
| Persist/restore scoop conversation history               | `packages/webapp/src/scoops/orchestrator.ts` (creates SessionStore, passes to ScoopContext, cleans up on unregister/clear) |
| Change scoop isolation/filesystem                        | `packages/webapp/src/scoops/scoop-context.ts`                                                                              |
| Add scoop-management tools (messaging, scoop management) | `packages/webapp/src/scoops/scoop-management-tools.ts`                                                                     |
| Change scoop database schema                             | `packages/webapp/src/scoops/db.ts`                                                                                         |
| Manage webhooks/crontasks                                | `packages/webapp/src/scoops/lick-manager.ts`                                                                               |
| Change skill loading                                     | `packages/webapp/src/scoops/skills.ts`                                                                                     |
| Change types (RegisteredScoop, etc.)                     | `packages/webapp/src/scoops/types.ts`                                                                                      |

### Multi-Browser Sync (Tray)

| I need to...                                 | Modify                                                                                                                                                 |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Add or change a sync message (wire format)   | `packages/webapp/src/scoops/tray-sync-protocol.ts` **and** `packages/ios-app/SliccFollower/Models/SyncProtocol.swift` (mirror)                         |
| Broadcast new state from the leader          | `packages/webapp/src/scoops/tray-leader-sync.ts` (new `broadcast*` method); wire periodic re-broadcast in `packages/webapp/src/ui/page-leader-tray.ts` |
| Handle a new message in the browser follower | `packages/webapp/src/scoops/tray-follower-sync.ts` (extend `handleLeaderMessage` switch + options callbacks)                                           |
| Handle a new message in the iOS follower     | `packages/ios-app/SliccFollower/App/AppState.swift` (extend `handleDataChannelMessage` switch); add UI binding in the relevant `Views/*.swift`         |
| Add follower-side rendering (browser)        | `packages/webapp/src/ui/page-follower-tray.ts` + create a follower-specific controller; wire layout callbacks in `packages/webapp/src/ui/main.ts`      |
| Change tray signaling / reconnect behavior   | `packages/webapp/src/scoops/tray-leader.ts`, `packages/webapp/src/scoops/tray-follower.ts`, `packages/webapp/src/scoops/tray-webrtc.ts`                |

### UI & Layout

| I need to...                                             | Modify                                                                           |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Add a new UI panel                                       | `packages/webapp/src/ui/<panel>-panel.ts` + integrate in `layout.ts` + `main.ts` |
| Change layout density (`Layout(root, isExtension)` flag) | `packages/webapp/src/ui/layout.ts`                                               |
| Change message rendering (HTML format)                   | `packages/webapp/src/ui/message-renderer.ts`                                     |
| Add voice input features                                 | `packages/webapp/src/ui/voice-input.ts`                                          |
| Change preview service worker                            | `packages/webapp/src/ui/preview-sw.ts`                                           |
| Change provider/model selection                          | `packages/webapp/src/ui/provider-settings.ts`                                    |
| Change theme handling                                    | `packages/webapp/src/ui/theme.ts`                                                |
| Change session storage                                   | `packages/webapp/src/ui/session-store.ts`                                        |

### CLI Server

| I need to...                    | Modify                              |
| ------------------------------- | ----------------------------------- |
| Add an API endpoint             | `packages/node-server/src/index.ts` |
| Change Chrome launch options    | `packages/node-server/src/index.ts` |
| Change WebSocket proxy behavior | `packages/node-server/src/index.ts` |
| Change request logging          | `packages/node-server/src/index.ts` |

### Extension Manifest

| I need to...              | Modify                                            |
| ------------------------- | ------------------------------------------------- |
| Change extension behavior | `packages/chrome-extension/src/service-worker.ts` |
| Add Chrome API types      | `packages/chrome-extension/src/chrome.d.ts`       |
| Build extension           | `packages/chrome-extension/vite.config.ts`        |

### Skills & Package Management

| I need to...                              | Modify                                                               |
| ----------------------------------------- | -------------------------------------------------------------------- |
| Change skill discovery                    | `packages/webapp/src/skills/discover.ts`                             |
| Change `.skill` drop archive handling     | `packages/webapp/src/skills/install-from-drop.ts`                    |
| Change install via GitHub/Tessl/browse.sh | `packages/webapp/src/shell/supplemental-commands/upskill-command.ts` |

### Sprinkles System

| I need to...                              | Modify                                                                                                  |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Add/change sprinkle discovery             | `packages/webapp/src/ui/sprinkle-discovery.ts`                                                          |
| Change sprinkle rendering or CSP handling | `packages/webapp/src/ui/sprinkle-renderer.ts`, `packages/webapp/src/ui/dip.ts`, `sprinkle-sandbox.html` |
| Change the sprinkle↔agent bridge API      | `packages/webapp/src/ui/sprinkle-bridge.ts`                                                             |
| Change sprinkle lifecycle/placement       | `packages/webapp/src/ui/sprinkle-manager.ts`                                                            |
| Add sprinkle picker UI features           | `packages/webapp/src/ui/sprinkle-picker.ts`                                                             |
| Change extension sprinkle message proxy   | `packages/chrome-extension/src/sprinkle-proxy.ts`                                                       |
| Change `sprinkle` shell command           | `packages/webapp/src/shell/supplemental-commands/sprinkle-command.ts`                                   |
| Add a default sprinkle                    | `packages/vfs-root/shared/sprinkles/`                                                                   |

### Providers

| I need to...                                                         | Modify                                                                                                                                                                   |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Add an API-key provider (built-in, with custom stream)               | `packages/webapp/src/providers/built-in/<provider>.ts` (exports `config: ProviderConfig` + `register()`; pure-config providers need no file — pi-ai auto-discovers them) |
| Add an external/custom provider                                      | `packages/webapp/providers/<provider>.ts` (gitignored in the webapp package, auto-discovered)                                                                            |
| Add an OAuth provider                                                | Same as above, but set `isOAuth: true` + `onOAuthLogin`/`onOAuthLogout` on the config                                                                                    |
| Change the OAuth transport (popup, chrome.identity)                  | `packages/webapp/src/providers/oauth-service.ts`                                                                                                                         |
| Override model capabilities (context window, max tokens)             | `modelOverrides` on `ProviderConfig` (static) or return metadata fields from `getModelIds()` (dynamic). Three-layer merge: pi-ai → modelOverrides → getModelIds          |
| Add OpenAI-compatible model support                                  | Return `api: 'openai'` in `getModelIds()` metadata — stream routing switches to `streamOpenAICompletions` automatically                                                  |
| Change provider types (ProviderConfig, OAuthLauncher, ModelMetadata) | `packages/webapp/src/providers/types.ts`                                                                                                                                 |
| Change OAuth callback page (CLI mode)                                | `packages/node-server/src/index.ts` (`/auth/callback` route)                                                                                                             |
| Change provider settings UI / model resolution                       | `packages/webapp/src/ui/provider-settings.ts`                                                                                                                            |
