# DSH Browser Bridge Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a Chrome side-panel extension and an external DSH plugin that give each prompt structured, turn-scoped access to explicitly attached Chrome tabs. **Architecture:** A WXT Manifest V3 extension embeds DSH Web and owns tab discovery, prompt grants, and `chrome.debugger` sessions. The DSH client plugin contributes `@` tab references and a current-tab shortcut; the host plugin consumes non-secret reference handles at `agent/pre-step`, registers scoped tools for that turn, and relays calls over an authenticated WebSocket. A shared protocol package validates every cross-process frame. **Tech Stack:** Node.js 22.19+, pnpm 11.7, TypeScript 6, Vitest 4, WXT 0.21, React 19 + shadcn/Tailwind 4 for the extension shell, host-provided React for the DSH client bundle, Cordis/DSH public plugin APIs, `ws`, Zod 4, Chrome MV3 `sidePanel` and `debugger`, Playwright 1.62. --- ## Scope and execution posture This is one vertical product, not three independent deliverables: protocol, extension, and DSH plugin are useless without one another. Tasks still keep the units independently testable and integrate them through versioned schemas. The implementation changes only this repository; any discovered DSH limitation requires a reproducer and a separate design decision. The first release supports local DSH Web origins (`localhost` and `127.0.0.1`) and Chrome 118+. Raw CDP passthrough, uploads, downloads, clipboard access, incognito, browser settings, Chrome UI automation, and implicit cross-prompt grants are outside this plan. ## File map ### Workspace - `package.json`: root scripts and pinned toolchain. - `pnpm-workspace.yaml`: `extension` plus `packages/*` workspaces. - `tsconfig.base.json`: strict shared TypeScript options. - `scripts/link-dsh-source.mjs`: creates the local development link consumed by the DSH plugin package. - `.github/workflows/ci.yml`: typecheck, unit tests, build, and Linux headed extension tests under Xvfb. ### Shared protocol - `packages/protocol/src/ids.ts`: branded wire identifiers and random ID factories. - `packages/protocol/src/errors.ts`: stable bridge error codes and payloads. - `packages/protocol/src/grants.ts`: tab descriptors, grant offers, active contexts, and tool request types. - `packages/protocol/src/frames.ts`: Zod frame schemas and parsers. - `packages/protocol/src/markers.ts`: safe non-secret prompt marker encoding and extraction. - `packages/protocol/src/index.ts`: public exports only. ### DSH plugin - `packages/dsh-plugin/src/index.ts`: Cordis assembly, routes, bridge, lifecycle listeners, and config. - `packages/dsh-plugin/src/bridge/pairing-store.ts`: short-lived single-use pairing nonces. - `packages/dsh-plugin/src/bridge/grant-store.ts`: connection/session/turn-bound grant state. - `packages/dsh-plugin/src/bridge/server.ts`: authenticated WebSocket carrier and request correlation. - `packages/dsh-plugin/src/pre-step.ts`: marker consumption, safe message rewrite, scoped tools, and cleanup. - `packages/dsh-plugin/src/tools/definitions.ts`: model-facing structured schemas and renderers. - `packages/dsh-plugin/src/tools/register.ts`: turn-scoped tool registration and alias resolution. - `packages/dsh-plugin/src/client/extension-channel.ts`: exact-origin parent-frame RPC. - `packages/dsh-plugin/src/client/reference-store.ts`: bounded per-session tab-reference state. - `packages/dsh-plugin/src/client/tab-source.ts`: `@` candidates and async reference codec. - `packages/dsh-plugin/src/client/CurrentTabButton.tsx`: one-click current-tab attachment. - `packages/dsh-plugin/src/client/index.tsx`: client plugin registration and slot wiring. ### Chrome extension - `extension/wxt.config.ts`: MV3 manifest, minimum Chrome version, permissions, CSP, and React/Tailwind plugins. - `extension/entrypoints/sidepanel/*`: shadcn settings/connection shell and DSH Web iframe. - `extension/entrypoints/background.ts`: service-worker composition root. - `extension/src/settings.ts`: local-origin validation and storage. - `extension/src/bridge/client.ts`: authenticated host WebSocket and heartbeat. - `extension/src/bridge/router.ts`: side-panel requests, host frames, and cancellation. - `extension/src/tabs/catalog.ts`: eligible/current tab resolution. - `extension/src/grants/vault.ts`: in-memory prompt grants and `storage.session` ownership ledger. - `extension/src/cdp/chrome-debugger.ts`: injected wrapper around `chrome.debugger`. - `extension/src/cdp/session-manager.ts`: lazy attach, domain enablement, navigation generations, buffers, and detach. - `extension/src/cdp/nodes.ts`: generation-bound element reference registry. - `extension/src/cdp/observe.ts`: semantic observation and node reference creation. - `extension/src/cdp/inspect.ts`: attributes, computed styles, geometry, and visibility. - `extension/src/cdp/act.ts`: click/type/select/hover/focus/key/scroll. - `extension/src/cdp/navigate.ts`: URL/history/reload operations and expected-navigation windows. - `extension/src/cdp/wait.ts`: bounded condition polling. - `extension/src/cdp/capture.ts`: screenshots plus bounded console/network projections. ### Integration and documentation - `packages/dsh-plugin/tests/composition.e2e.spec.ts`: real Cordis/DSH services, real HTTP/WebSocket bridge, fake extension peer. - `e2e/fixtures/app.html`: deterministic DOM, style, navigation, console, and failed-request fixture. - `e2e/bridge-harness.ts`: real protocol peer used by the unpacked extension test. - `e2e/extension.spec.ts`: headed Chromium with the unpacked extension. - `INSTALL.md`: development link, plugin install, extension load, update, and uninstall. - `README.md`: product usage and the development feedback loop. ### Task 1: Bootstrap the workspace and portable DSH source link **Files:** - Create: `.gitignore` - Create: `package.json` - Create: `pnpm-workspace.yaml` - Create: `tsconfig.base.json` - Create: `scripts/link-dsh-source.mjs` - Create: `packages/protocol/package.json` - Create: `packages/protocol/tsconfig.json` - Create: `packages/dsh-plugin/package.json` - Create: `packages/dsh-plugin/tsconfig.json` - Create: `extension/package.json` - [ ] **Step 1: Prove the workspace is not bootstrapped** Run: ```bash pnpm install ``` Expected: FAIL because the root has no `package.json`. - [ ] **Step 2: Add the root workspace and strict compiler baseline** Create `package.json` with these scripts and versions: ```json { "name": "dsh-browser-bridge-workspace", "private": true, "type": "module", "packageManager": "pnpm@11.7.0", "engines": { "node": "^22.19.0 || >=24.0.0" }, "scripts": { "link:dsh": "node scripts/link-dsh-source.mjs", "build": "pnpm -r build", "typecheck": "pnpm -r typecheck", "test": "pnpm -r test", "test:e2e": "playwright test", "check": "pnpm typecheck && pnpm test && pnpm build" }, "devDependencies": { "@playwright/test": "^1.62.1", "typescript": "^6.0.3", "vitest": "^4.1.10" } } ``` Create `pnpm-workspace.yaml`: ```yaml packages: - extension - packages/* onlyBuiltDependencies: [] ``` Create `tsconfig.base.json`: ```json { "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noImplicitOverride": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "verbatimModuleSyntax": true, "skipLibCheck": true } } ``` Ignore `.dsh/`, `node_modules/`, `lib/`, `dist/`, `extension/output/`, `test-results/`, and `playwright-report/`. - [ ] **Step 3: Add a fail-closed local DSH linker** Create `scripts/link-dsh-source.mjs` so it accepts exactly one absolute DSH checkout, validates `AGENTS.md`, `packages/client/runtime`, and `vendor/cordis`, creates `.dsh/source/current`, and refuses to overwrite a different existing link: ```js import { lstat, mkdir, readlink, symlink } from 'node:fs/promises' import { isAbsolute, resolve } from 'node:path' const source = process.argv[2] if (source === undefined || !isAbsolute(source)) throw new Error('usage: pnpm link:dsh -- /absolute/path/to/dsh') const root = resolve(import.meta.dirname, '..') for (const required of ['AGENTS.md', 'packages/client/runtime', 'vendor/cordis']) { await lstat(resolve(source, required)) } const link = resolve(root, '.dsh/source/current') await mkdir(resolve(root, '.dsh/source'), { recursive: true }) try { const current = await readlink(link) if (resolve(resolve(link, '..'), current) !== resolve(source)) { throw new Error(`existing DSH link points to ${current}`) } } catch (error) { if (error?.code !== 'ENOENT') throw error await symlink(resolve(source), link, 'dir') } console.log(`${link} -> ${resolve(source)}`) ``` - [ ] **Step 4: Add package manifests and install** Use `@ycp424c/dsh-browser-bridge-protocol`, `@ycp424c/dsh-browser-bridge`, and `@ycp424c/dsh-browser-bridge-extension` as package names. The protocol depends on `zod@^4.4.3`; the host plugin depends on the protocol workspace, `schemastery@^3.18.0`, and `ws@^8.21.0`; its DSH development dependencies use `link:../../.dsh/source/current/...`. The extension depends on the protocol workspace, WXT, React 19, Tailwind 4, and the shadcn runtime packages. Run: ```bash pnpm link:dsh -- /Users/justynchen/Documents/code/dsh/test-ycp424c pnpm install pnpm exec tsc --version ``` Expected: install succeeds and TypeScript reports `Version 6.0.x`. - [ ] **Step 5: Commit the workspace baseline** ```bash git add .gitignore package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.base.json scripts packages/*/package.json packages/*/tsconfig.json extension/package.json git commit -m "build: bootstrap browser bridge workspace" ``` ### Task 2: Define and validate the shared wire protocol **Files:** - Create: `packages/protocol/src/ids.ts` - Create: `packages/protocol/src/errors.ts` - Create: `packages/protocol/src/grants.ts` - Create: `packages/protocol/src/frames.ts` - Create: `packages/protocol/src/markers.ts` - Create: `packages/protocol/src/index.ts` - Create: `packages/protocol/tests/frames.spec.ts` - Create: `packages/protocol/tests/markers.spec.ts` - Create: `packages/protocol/tsdown.config.ts` - [ ] **Step 1: Write failing parser and marker tests** Create tests that require versioned discriminants, reject extra fields, round-trip all stable errors, and extract multiple non-secret handles: ```ts import { describe, expect, it } from 'vitest' import { decodeFrame, encodeMarker, extractMarkers, PROTOCOL_VERSION } from '../src/index.ts' describe('wire frames', () => { it('accepts a valid hello and rejects unknown protocol versions', () => { expect(decodeFrame(JSON.stringify({ v: PROTOCOL_VERSION, type: 'hello', pairingNonce: 'n'.repeat(32) }))).toMatchObject({ type: 'hello' }) expect(() => decodeFrame(JSON.stringify({ v: 99, type: 'pong' }))).toThrow(/protocol frame/) }) }) describe('prompt markers', () => { it('extracts only syntactically valid non-secret handles', () => { const a = 'a'.repeat(32) const b = 'B'.repeat(32) const text = `check ${encodeMarker(a)} and ${encodeMarker(b)}` expect(extractMarkers(text).map(item => item.handle)).toEqual([a, b]) expect(extractMarkers('[[dsh-browser-context: