Overview
dsh-browser-playwright-codex
README / EN
Package documentation
Registry summary
dsh.pub verifies the pinned bundle contract, runtime facts, and distribution semantics. The complete README remains in the source repository.
Read the full README on GitHubLIMITATIONS
Known limitations
- **Persistent mode is one shared window** — all calling sessions drive the same profile-backed window and share its login state and tabs. Ideal for a personal assistant; if you need hard per-session isolation (separate logins per conversation), set `launch.persistent: false` and manage per-owner contexts, or run separate DSH profiles. - **Headless servers must opt in** — the bundle defaults to `headless: false`; on a headless/remote host set `launch.headless: true`. - **Selector-free, not vision-free** — interaction is a11y-tree based; purely visual widgets (canvas, WebGL, custom-drawn controls) may expose no refs. `browser_screenshot` + an image-capable model is the fallback. - **Cross-origin iframes** appear as leaf `(frame)` nodes without refs; same-origin frames are walked up to two levels deep. - **Evaluate gate is config, not approval** — enabling `allowEvaluate` trusts the model with arbitrary page JavaScript; compose it with the harness approval/permission policy for stricter control. The gate is enforced in **two** places: `tool.ts` refuses before a browser is even launched, and the session itself refuses too (`EVALUATE_DISABLED` / `CDP_DISABLED`), so a sibling plugin or a script holding the browser service cannot go around it. A gate that was never published counts as **off**. `browser_cdp` additionally passes the CDP allow-list at the session layer, so `Runtime.evaluate` is rejected even with the switch on. - **The package ships both power gates OFF, and that is deliberate** — `cordis.patch.yml` is part of the package, so anything it enables is enabled for *everyone who installs this*. It therefore sets `allowEvaluate: false` and `allowCdp: false` (the code defaults), and each deployment opts in through its **own profile patch** (`~/.dsh/profiles/<name>/cordis.patch.yml`). A package that shipped them on would be handing every installer a backdoor they never asked for. Snippet: ```yaml # dsh replaces an entry's config object wholesale — list every key you rely on. - id: browser config: service: { provider: playwright } playwright: { launch: { headless: false, persistent: true }, idleTimeoutMs: 0, maxSessions: 8, snapshot: { diff: false } } tool: toolPrefix: 'browser_' allowEvaluate: true # registers browser_evaluate allowCdp: true # registers browser_cdp (needs a full DSH restart) registerDisabledTools: false maxWaitMs: 60000 ``` - **`launch.viewport` only applies headless** — the visible window is a real window: it tracks the desktop size (`browser-lifecycle.ts` passes `viewport: null` in headful), so setting 1280×800 there changes nothing. Pin the size by running `headless: true`, or resize the window itself. - **Chromium family only** — Firefox/WebKit channels are not probed; providers are swappable if another engine is needed. - **Extract needs a dedicated model route** — it does not reuse the main request's route; misconfiguration fails loudly at call time. - **`allowedDomains` is enforced on the wire, not just at the entry points** — the host list is installed on the browser context itself (`navigation-guard.ts`), so a navigation nobody vetted is refused by the browser: `location.href = …`, `meta refresh`, a `<form>` posting to another host and a page-opened popup all stop at the request. Data requests (`fetch` / `xhr` / `EventSource`) to a host outside the list are refused too, so page script cannot move data off-site; rendering assets (script / image / CSS / font / media) are left alone, because blocking them only makes a page look broken. `browser_navigate`, `browser_open_tab`, every navigating click and `browser_switch_tab` still check first — that is what produces the friendly error before anything happens. One case needs a second look, because Chromium follows a redirect inside its own network stack where request interception never sees the hop: after every action the page's final URL is checked, and a landing outside the li
