dsh-browser-install
Automatically installs Playwright Chromium (npx playwright install chromium --with-deps) into the Docker container running DeepSeek Harness, and bridges the installed binary onto PATH so the dsh-ego-browser side-bar plugin finds it — no manual "find the binary / symlink it / set EGO_LINUX_* env vars / wrap it with --no-sandbox" steps for agents ever again.
Why this plugin exists
dsh-ego-browser discovers its browser the following way (see lib/index.js → resolveEgoEnv and runtime/ego-linux/src/chrome.mjs):
- It scans
PATH+ fixed locations forgoogle-chrome,google-chrome-stable,chromium,chromium-browser, … and - on root hosts (docker default), it hands the found binary to a bundled
--no-sandboxwrapper that only looks at fixed paths (/usr/bin/*,/opt/google/...,/usr/local/bin/google-chrome-stable,/snap/bin/chromium).
Playwright installs Chromium into ~/.cache/ms-playwright/chromium-<rev>/chrome-linux{,64}/chrome — a location invisible to both scans. That is why agents kept having to hunt the binary down, symlink/wrap it, and set EGO_LINUX_CHROME / EGO_LINUX_EXTRA_ARGS by hand.
This plugin does exactly that bridging once, automatically:
- on load it checks the Playwright cache (idempotent, no-op when the browser is already there),
- if the browser is missing it runs
npx --yes playwright install chromium --with-depsin the background (profile startup is never blocked for minutes), and - it then symlinks the installed binary at
/usr/local/bin/google-chrome-stableand/usr/local/bin/chromium— the exact names ego-browser's PATH scan and its bundled--no-sandboxwrapper look for. On this root container, the chain then resolves to: root detected → bundled--no-sandboxwrapper →--no-sandboxlaunch; noDISPLAY→ automatic headless. No env vars, no manual wrapper, no agent steps.
Nothing is ever clobbered: a pre-existing system Chrome or a user wrapper keeps winning (the plugin only creates links where nothing of its own/an external binary exists).
Install (from git — recommended)
Install from your forge (the command below is the full-URL form for any host; on GitHub you can use the shorthand github:<you>/dsh-browser-install#main):
dsh plugin --profile web add https://git.notexpectedyet.com/Not-Expected-Yet/dsh-browser-install.git#main
-
The
#mainbranch ref is what makes updates work:pnpm updatere-resolves the branch tip (verified against pnpm 12.8.1), so after you push new commits simply rundsh plugin --profile web update # all deps dsh plugin --profile web update dsh-browser-install # just this one -
Bundle changes only become active after a profile restart:
dsh --profile web. -
No build step: the bundle ships plain ESM and declares no
preparescript, so pnpm's default block on git-dependency build scripts never applies — nothing toallowBuildsinpnpm-workspace.yaml. -
pnpm installs git deps over HTTPS, so keep the repo public (or set up credentials on the host).
Alternative: install from a local checkout
dsh plugin --profile web add /root/workspaces/Plugin\ Development/dsh-browser-install
A directory install is a symlinked file: dependency — handy while iterating. For persistent installs and updates, prefer the git flow above.
Restart and verify
Restart the profile, then check the plugin loaded and did its job:
dsh --profile web
dsh --profile web --dump-config | grep -A3 browser-install
ls -la /usr/local/bin/google-chrome-stable # -> .../ms-playwright/chromium-*/chrome-linux64/chrome
Then from any agent session, ego_doctor should report a browser binary, and ego_space_open/ego_navigate work with no environment prep.
Non-root hosts: set
symlinkDirto a directory on yourPATH(e.g.~/.local/bin). On root only/usr/local/binis needed — it also satisfies the bundled--no-sandboxwrapper's absolute-path list.
Config
All fields are optional. Override them in your profile's cordis.patch.yml (or in the bundle row's config):
- id: browser-install
name: "dsh-browser-install"
config:
withDeps: true # run `--with-deps` (apt system deps); needs root/apt
target: chromium # chromium | chromium-headless-shell | chrome
package: playwright # npx package spec, e.g. "playwright@1.54.1"
forceInstall: false # always re-run the install on load
installOnStartup: true # install in background when the browser is missing
cacheDir: "" # empty = $PLAYWRIGHT_BROWSERS_PATH | ~/.cache/ms-playwright
symlinkDir: /usr/local/bin
symlinkNames: [google-chrome-stable, chromium]
installTimeoutMs: 600000
| Field | Default | Meaning |
|---|---|---|
installOnStartup |
true |
Run the Playwright install when the plugin loads and no matching browser is found. The install runs in the background so profile startup is not blocked. |
forceInstall |
false |
Always run the install on load, even when a browser is already present (e.g. after a Playwright upgrade). |
package |
playwright |
Package spec passed to npx --yes <package> install .... Pin with e.g. playwright@1.54.1. |
target |
chromium |
Which Playwright browser to ensure: chromium (full build), chromium-headless-shell, or chrome (Google Chrome). Default matches what ego-browser expects. |
withDeps |
true |
Pass --with-deps so Playwright also installs the browser's system dependencies via apt (requires root; the DSH image runs as root). |
cacheDir |
auto | Playwright browsers cache root. Empty = $PLAYWRIGHT_BROWSERS_PATH, else ~/.cache/ms-playwright. When set here, the installer child also gets PLAYWRIGHT_BROWSERS_PATH. |
symlinkDir |
/usr/local/bin |
Directory for the PATH links; must be on the DSH process PATH. |
symlinkNames |
[google-chrome-stable, chromium] |
Link names to create. google-chrome-stable is the one ego-browser's PATH scan and its --no-sandbox wrapper both check. |
installTimeoutMs |
600000 |
Max time for the background install before it is killed. |
Behaviour notes
- No-clobber links: a link whose target lives inside the Playwright cache is treated as ours and refreshed to the newest installed revision; any other existing file/symlink is left untouched (and wins).
- Lifecycle: the background installer child is killed if the plugin is unloaded mid-install (
ctx.effect). The PATH links are intentionally not removed on unload — a running browser may be using them and they are harmless, idempotent shared state. - Error visibility: install failures, symlink errors, and a
symlinkDirmissing fromPATHare all logged loudly under thedsh-browser-installlogger scope, so an agent can read the reason instead of guessing. - ego-browser settings untouched: the plugin does not modify dsh-ego-browser's config or any
EGO_LINUX_*env vars — it only fills the gap (install + visible symlink) that makes ego-browser's existing auto-adaptation work (root →--no-sandboxwrapper, no DISPLAY → headless).
Uninstall
dsh plugin --profile web remove dsh-browser-install
The symlinks are left in place (harmless; the browser keeps working for ego-browser). Remove them manually if you truly want them gone:
rm -f /usr/local/bin/google-chrome-stable /usr/local/bin/chromium
Development
Pure helpers (resolveCacheDir, resolveBrowserBinary, planSymlinks, applySymlinkPlan, buildInstallCmd) are exported for testing; the plugin itself needs no build step (plain ESM, no TypeScript compile).
- Node ≥ 22 (the harness image ships v24).
- Requires DSH ≥ 0.1.2-rc.1 (same baseline as dsh-ego-browser).
- Runtime dependency:
@deepseek-ai/schemastery(already present in the profile).