中文 · English
# dsh-browser-slotpool — Concurrent Browser Slot Pool Plugin
> DSH bundle: 并发浏览器会话槽位池包装(slot-pool wrapper for concurrent browser sessions)
Gives DSH's `mcp-client` a **concurrent, idempotent, non-destructive, self-healing** browser MCP. It is just a **patch-layer bundle**: it adds one `@deepseek-ai/dsh-mcp-client` instance to the profile whose `command`/`args` point at this package's own `bin/browser-slotpool.mjs` (the launcher that manages the slot pool). Browser tools surface as `mcp__browser__`.
End-to-end local verification (local server + pooled Chrome real render, not a mock):
```
[browser-slotpool] Claimed slot port 9222 (pid 35196)
[browser-slotpool] Rule: each slot is an independent session — never proxy or steal another slot.
[browser-slotpool] Starting Chrome for slot 9222: C:/Program Files/Google/Chrome/Application/chrome.exe
[browser-slotpool] Chrome ready on 9222
[browser-slotpool] Launching Playwright MCP -> http://127.0.0.1:9222
== initialize: Playwright
== tools: 31 | browser_close, browser_resize, browser_console_messages, browser_handle_dialog, browser_evaluate, browser_file_upload ...
== navigate: OK
== screenshot bytes: 14140
== snapshot contains marker: true
== SMOKE PASS (rendered real page in pooled chrome) ==
```
## What it solves
When multiple sessions/profiles share a browser, each launching its own instance causes port conflicts, memory blow-ups and cross-interference; sharing one causes contention. This bundle makes the instance cap explicit with a **slot pool**:
- **Slot pool**: a fixed set of CDP ports (default `9222,9223`), first-come first-served, rejects when full.
- **Atomic locking**: `fs.openSync(lock, "wx")` (O_EXCL exclusive create) for race-free locking.
- **PID liveness + stale-lock self-heal**: reclaims a slot when its owner dies — never stuck "all slots busy".
- **Idempotent readiness probe**: leaves an already-up CDP port alone, only spawns Chrome when not up; **never kills another slot's browser**.
- **Slot ownership isolation**: each mcp-client session owns exactly one port; never proxies or steals another session's slot.
- stdio is fully delegated to `@playwright/mcp`; diagnostics go only to stderr (keeps the MCP protocol channel clean).
## Install
Recommended: install from GitHub (no local build needed):
```sh
# Run from your DeepSeek Harness checkout (the same profile that hosts the GUI)
pnpm dsh plugin --profile web add github:bluechips-zhao/dsh-browser-slotpool
```
> If you're not comfortable with the CLI, just send this repository link
> `https://github.com/bluechips-zhao/dsh-browser-slotpool` to your AI assistant
> (DeepSeek Harness or another), and have it run the `dsh plugin` install command
> for you following this README.
`dsh plugin add` only installs the bundle into the profile's deps and writes `profile.bundles`; it does not auto-start a browser. **Restart the target profile** after installing.
### Configuration (one required step)
`!!js` is evaluated by DSH with `new Function`, so it has **no `import.meta`/module scope**. The launcher path therefore comes from the `DSH_BROWSER_LAUNCHER` env var (read by the bundle's `cordis.patch.yml` `args`). After install the launcher lives at:
```
/profiles//node_modules/@deepseek-ai/dsh-browser-slotpool/bin/browser-slotpool.mjs
```
Set that absolute path (via `setx` or the profile startup script):
```powershell
# Temporary (current shell)
$env:DSH_BROWSER_LAUNCHER = "/profiles//node_modules/@deepseek-ai/dsh-browser-slotpool/bin/browser-slotpool.mjs"
# Or edit the profile's cordis.patch.yml to pass an absolute path in args
```
Other optional env vars (forwarded to the launcher via `mcp-client`'s `env`):
| Variable | Default | Description |
|---|---|---|
| `DSH_BROWSER_PORTS` | `9222,9223` | Slot (CDP port) list, comma-separated |
| `DSH_BROWSER_BASE_DIR` | `mcp-shared-browsers` under LOCALAPPDATA / tmpdir | Shared browser data / lock root dir |
| `DSH_PLAYWRIGHT_MCP_ENTRY` | (empty → falls back to npx) | Absolute path to `@playwright/mcp`'s `cli.js`; if set, runs node directly, **no npx/network needed** |
| `DSH_PLAYWRIGHT_MCP_CMD` | `npx` | The command used to invoke `@playwright/mcp` (only when entry is unset) |
| `CHROME_PATH` | common install paths probed | Browser executable |
### Discover more plugins
This plugin is public under GitHub's [`dsh-plugin`](https://github.com/topics/dsh-plugin) tag; browse official and community plugin repos on that page. For a visual, app-store-like experience you can also visit the community-run [DSH-Plugin Hub](https://dsh-plugin.org) (third-party, not operated by DeepSeek).
> This plugin is a **patch-layer bundle + pure `.mjs` launcher**, with **no TypeScript source and no build step**. `exports` point directly at the committed `bin/browser-slotpool.mjs` and `cordis.patch.yml`, so users install a ready-to-use artifact. If you rename or move this repo to another namespace, update the `github:bluechips-zhao/dsh-browser-slotpool` segment above.
## Dependencies / Runtime requirements
- **DSH provides** `@deepseek-ai/dsh-mcp-client` (this bundle only adds one line, no extra package).
- **`@playwright/mcp`**: the launcher calls it via `npx @playwright/mcp@latest` (first run, online).
- **Chrome/Chromium**: must be at `CHROME_PATH` or a common install path; otherwise the launcher reports "Chrome not found".
## Slot semantics cheat-sheet
- Each mcp-client instance (one bundle line / one profile) owns one slot; adding another mcp-client line (different `serverName`, e.g. `browser2`) uses another slot.
- Both slots busy → launcher `exit(2)` and prints each slot's holder; `mcp-client` reconnects/errors.
- Crashes/kills leave no stale lock (PID liveness reclaim).
## Verification
```sh
# Minimal MCP stdio smoke test (local-server version): definitively proves the
# "slot pool + Chrome + @playwright/mcp + render + screenshot" chain
node test/mcp-client-smoke.mjs
```
> ⚠️ **External access is environment-restricted**: Chrome on this machine gets
> `ERR_CONNECTION_RESET` for external sites (`example.com`, `registry.npmjs.org`
> time out, while PowerShell can reach some domains). That's a **deployment
> network policy**, not a bug in the wrapper — the wrapper stack is fully
> functional. On a machine with normal internet, `dsh --profile
> "open https://example.com and screenshot"` shows a real external page.
> The smoke test `test/mcp-client-smoke.mjs` needs `DSH_PLAYWRIGHT_MCP_ENTRY` set to
> the local `@playwright/mcp` `cli.js` (e.g. `%APPDATA%\npm\node_modules\@playwright\mcp\cli.js`).