mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
Blocking: - B1: delete screenshot scratch PNGs — observation frames reuse one fixed per-session path and unlink after every read (finally); browser_screenshot unlinks once the bytes are in the attachment store (kept only when the file itself is the model-facing artifact). - B2: replicate dsh's browser-trust fence on /bsk-observation/*: loopback Host only, Origin must match Host, sec-fetch-site: cross-site refused, POST requires application/json; README documents the loopback trust premise and the 0.0.0.0 warning. Major: - M1: KeyedExecutor tail chains previous+task (allSettled) so a task aborted while queued cannot release the next one into the running session. - M2: client tracks the live frame per session — replacing/removing/reset revokes the old blob URL at once; loads settling after replacement never resurrect it. Minor: drop replaced thumbRefs (+on remove); beginAction fires inside the queue (no label overwrite while queued, no idle flash); half-initialized session cleanup stops through the queue with one retry; SSE (re)open refetches /state (+ snapshot flag renamed subscribed); install probe uses --version (no daemon spawn); emulate mobile documents the width+height requirement (the daemon refuses it alone — verified). Nit: single BskRunOptions declaration; tails map entry dropped on drain; SSE cleanup on res close; popOut catches requestWindow rejections; resize handle gains keyboard control (arrows, 16px steps, aria value attrs). Tests: +14 (fence rules, scratch lifecycle, queue race, blob revocation, instrumentation timing, SSE resync). 113/113 green.
153 lines
9.4 KiB
Markdown
153 lines
9.4 KiB
Markdown
# dsh-plugin-browserskill
|
||
|
||
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) tool plugin that exposes
|
||
[BrowserSkill](https://github.com/Tencent/BrowserSkill) (`bsk`) browser automation to the model.
|
||
|
||
Each tool maps to one `bsk <cmd> --json` invocation: the plugin spawns the bsk CLI, parses its
|
||
structured JSON output, and returns a canonical typed value. The bsk daemon, browser, and Chrome
|
||
extension keep owning the actual browser control — this package is a thin, well-typed bridge.
|
||
|
||
## Tools
|
||
|
||
| Tool | bsk command | Purpose |
|
||
| --- | --- | --- |
|
||
| `browser_session_start` | `bsk session start` | Open an Agent Window session; optional initial URL, window size, and device emulation preset. Returns the session id and makes it the current session. |
|
||
| `browser_session_stop` | `bsk session stop` | Stop a session (the current one by default) and close its Agent Window. Only plugin-created sessions can be stopped. |
|
||
| `browser_session_list` | — (registry only) | List the sessions this plugin created, marking the current one. Foreign daemon sessions are never shown. |
|
||
| `browser_navigate` | `bsk navigate` | Navigate the active tab, with `waitUntil` / timeout control. |
|
||
| `browser_snapshot` | `bsk snapshot` | Indented aria-tree snapshot with `@eN` refs for interaction tools. |
|
||
| `browser_observe` | `bsk observe` | Semantic VOM observation (read-only) with `@eN` refs. |
|
||
| `browser_click` | `bsk click` | Click a snapshot ref or CSS selector (button / click-count options). |
|
||
| `browser_fill` | `bsk fill` | Fill an input / textarea / contenteditable (clears first by default). |
|
||
| `browser_press` | `bsk press` | Dispatch a key or combo, optionally focusing a target first. |
|
||
| `browser_screenshot` | `bsk screenshot` | PNG capture of the tab or a ref-cropped element; inlines the image when the deployment supports image input, otherwise returns a file path. |
|
||
| `browser_emulate` | `bsk emulate` | Apply or clear mobile device emulation on the active tab. |
|
||
|
||
## Multi-session model
|
||
|
||
One agent conversation can drive several browser sessions at once:
|
||
|
||
- `browser_session_start` returns the session id and makes it the **current session**.
|
||
- Every operation tool accepts an optional `session` argument. When omitted, the call acts on the
|
||
current session (the one most recently started or used); when given, that session becomes current.
|
||
- Every tool result echoes the session it actually acted on, so the model never has to guess.
|
||
- The number of concurrent sessions started through the plugin is capped (`maxSessions`, default 5).
|
||
- Unloading the plugin stops every session it started and kills in-flight bsk processes.
|
||
|
||
**Ownership boundary**: the bsk daemon may be shared with other agents, terminals, or dsh
|
||
instances. The plugin therefore only ever sees and operates on sessions it created itself —
|
||
an explicit `session` argument naming a foreign or unknown id is rejected, `browser_session_list`
|
||
shows plugin-created sessions only (no daemon-wide view), and stop/unload cleanup can never touch
|
||
a session owned by another program.
|
||
|
||
## Installation
|
||
|
||
The plugin follows the standard dsh bundle layout (`dsh.bundle` manifest + `cordis.patch.yml`):
|
||
|
||
```sh
|
||
dsh plugin --profile <name> add <path-or-spec-of-this-package>
|
||
dsh --profile <name>
|
||
```
|
||
|
||
Prerequisite: the `bsk` CLI must be installed and on `PATH`, and the BrowserSkill Chrome extension
|
||
must be connected — see the [BrowserSkill README](https://github.com/Tencent/BrowserSkill). When bsk
|
||
is missing, tool calls fail with install guidance instead of a bare spawn error.
|
||
|
||
## Configuration
|
||
|
||
All fields are optional and validated through the plugin's Schemastery `Config`:
|
||
|
||
```yaml
|
||
# cordis.patch.yml override example
|
||
- insert:
|
||
- id: browserskill
|
||
name: dsh-plugin-browserskill
|
||
config:
|
||
bskPath: bsk # path to the bsk binary (default: resolve from PATH)
|
||
defaultTimeoutMs: 120000
|
||
maxSessions: 5
|
||
# observationEnabled: true # live PiP/overlay observation (below)
|
||
# thumbnailIntervalMs: 1500 # frame cadence while a session is active
|
||
# idleIntervalMs: 8000 # idle cadence / recent-activity window
|
||
```
|
||
|
||
## Observation overlay (PiP mini-window)
|
||
|
||
When the plugin runs inside the dsh Web UI, an **observation overlay** floats over the app
|
||
(registered into the `shell.overlay` seat): a breathing thumbnail per owned session plus its
|
||
current action and elapsed time. The card docks at the top-right of the content area (clear of
|
||
the composer and the shell's header controls) and wears the **BrowserSkill product family's own
|
||
look**: the overlay reuses `@browser-skill/ui` components (`Button`, `cn`) and its oklch design
|
||
tokens (`--card`, `--primary`, `--destructive`, `--ring`, …), status dots spec'd after the
|
||
extension popup's connection indicator, and Remix icons. The BSK utility sheet is compiled
|
||
scoped under the `.bsk-obs` root class (`scripts/build-client-css.mjs`), so the overlay looks
|
||
like the BrowserSkill extension without leaking a single selector into the host shell — and the
|
||
shell's theme cannot bleed back in.
|
||
|
||
- **Lifecycle**: hidden while the plugin owns no sessions; appears on the first
|
||
`browser_session_start`; disappears when all sessions stop (or the plugin unloads).
|
||
- **Focus view**: status row (green/idle/red dot + session + action + mm:ss), the latest page
|
||
frame (refreshes every ~1.5s while active, ~8s when idle, keeps the last frame on errors),
|
||
and the action row (Interrupt + Pop out).
|
||
- **Interrupt**: one click kills the in-flight bsk command of the focus session (same semantics
|
||
as the chat Stop button — the current action fails, the agent run may continue). Strip items
|
||
carry their own hover interrupt button.
|
||
- **Multi-session strip**: every session gets a tile (mini frame + id + status dot); focus
|
||
auto-follows the most recently active session; clicking a tile pins focus (pin badge, click
|
||
again to release); errored sessions get a red edge without stealing focus; sessions the daemon
|
||
lost are greyed out; prolonged daemon/browser outage shows "browser unavailable" and greys
|
||
the interrupt button until captures recover.
|
||
- **Drag & resize**: drag the header to move the card, drag the corner handle to resize
|
||
(min 240×180, max 80% of the viewport); both are remembered for the page lifetime.
|
||
- **Pop out (PiP)**: upgrades the card into a native Document PiP window (requires a user
|
||
gesture, per browser rules), sized from the current card; closing the PiP falls back to the
|
||
in-page card with state intact. Browsers without Document PiP simply hide the button.
|
||
- **Wire**: the host serves `GET /bsk-observation/state`, `GET /bsk-observation/events` (SSE),
|
||
`POST /bsk-observation/interrupt`, and `GET /bsk-observation/thumbnail/<attachmentId>` over
|
||
the dsh `webServer` route seam (dsh 0.1's Typert Remote pipeline is closed to out-of-tree
|
||
packages). All commands for one session — tool calls and frame captures alike — run through a
|
||
per-session FIFO, because the daemon accepts only one unfinished command per session.
|
||
- **Trust model**: these routes expose live screenshots (and an interrupt write), so they
|
||
replicate the browser-trust fence dsh applies to its own `/api` routes: the request Host
|
||
must be a loopback authority (`localhost`, `127.0.0.0/8`, `[::1]`), a present Origin must
|
||
match the Host, `sec-fetch-site: cross-site` is refused, and POST requires an
|
||
`application/json` body (cross-site simple requests can never satisfy that). The channel is
|
||
therefore built for **loopback-only serving** — binding the dsh web server to `0.0.0.0` and
|
||
reaching it through a LAN address will (deliberately) fail the fence; do not put these
|
||
routes behind a non-loopback reverse proxy without adding your own authentication.
|
||
- Configure with `observationEnabled` / `thumbnailIntervalMs` / `idleIntervalMs`.
|
||
|
||
## Behavior notes
|
||
|
||
- **Cancellation**: aborting a tool call (`exec.signal`) kills the underlying bsk child process,
|
||
matching BrowserSkill's cooperative tool-cancellation model.
|
||
- **UI cards**: calls render as terminal cards (command line as title, output as the completed
|
||
card). Screenshots additionally attach the image itself when the host mounts an attachment store
|
||
and the active model route declares image input; otherwise the PNG path is returned.
|
||
- **Web UI toolview (browser half)**: the package is dual-face. `dsh.client` (platform `web`) ships
|
||
`lib/client.js`, which registers a keyed `tool.call.toolview` view for `browser_screenshot`. The
|
||
custom view keeps the terminal block (command + output) and, when the settled result carries an
|
||
image block, resolves the durable attachment through the client session's authorized
|
||
`readAttachment` RPC and renders it with the shared `MessageImage` thumbnail/lightbox atoms.
|
||
Every other `browser_*` tool keeps the stock terminal card. The bundle follows the dsh client
|
||
contract: a CJS closure factory handed to `window.__ModuleLoader__.load`, platform modules
|
||
(`react`, `dsh-client-ui-*`) external, everything else inlined, CSS Modules compiled by
|
||
lightningcss.
|
||
- **Errors**: non-zero bsk exits surface the CLI's JSON error envelope (`code`, `message`, `hint`)
|
||
so the model gets the daemon's actionable guidance.
|
||
- **Long-running work** (e.g. `bsk record`) is not backgrounded via `ctx.jobs` yet — tracked as a
|
||
follow-up.
|
||
|
||
## Development
|
||
|
||
```sh
|
||
pnpm install
|
||
pnpm --filter dsh-plugin-browserskill typecheck
|
||
pnpm --filter dsh-plugin-browserskill test # unit tests mock bsk; no browser needed
|
||
pnpm --filter dsh-plugin-browserskill build # tsdown -> lib/
|
||
```
|
||
|
||
## License
|
||
|
||
MIT
|