Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
703fc64
feat(packages): add dsh-plugin-browserskill — DeepSeek Harness tools …
BB-fat Aug 13, 2026
2de7c5c
fix(dsh-plugin-browserskill): enforce the session cap before spawning
BB-fat Aug 13, 2026
54869ca
fix(dsh-plugin-browserskill): include the bsk error hint in the throw…
BB-fat Aug 13, 2026
0771292
fix(dsh-plugin-browserskill): keep the terminal result card for multi…
BB-fat Aug 13, 2026
6b96fcc
feat(dsh-plugin-browserskill): add the web client half with a browser…
BB-fat Aug 14, 2026
afdc398
fix(dsh-plugin-browserskill): strict session ownership on a shared da…
BB-fat Aug 14, 2026
3d050e0
feat(dsh-plugin-browserskill): strict ownership boundary — foreign se…
BB-fat Aug 14, 2026
a7bc8ba
feat(dsh-plugin-browserskill): host-side ObservationService for the P…
BB-fat Aug 14, 2026
24faa10
feat(dsh-plugin-browserskill): observation overlay — floating card, i…
BB-fat Aug 14, 2026
ce89e95
feat(dsh-plugin-browserskill): multi-session strip, focus pin, and ed…
BB-fat Aug 14, 2026
2955260
fix(dsh-plugin-browserskill): register observation routes once webSer…
BB-fat Aug 14, 2026
bc5d961
fix(dsh-plugin-browserskill): serialize per-session commands (observa…
BB-fat Aug 14, 2026
461d204
fix(dsh-plugin-browserskill): serve observation thumbnails over the p…
BB-fat Aug 14, 2026
e2d0757
fix(dsh-plugin-browserskill): prefix routes register without a traili…
BB-fat Aug 14, 2026
dde707b
fix(dsh-plugin-browserskill): report interrupt-killed commands as int…
BB-fat Aug 14, 2026
916229d
docs(dsh-plugin-browserskill): README section for the observation ove…
BB-fat Aug 14, 2026
47070fd
feat(dsh-plugin-browserskill): default the overlay to the top-right +…
BB-fat Aug 14, 2026
b07b8fe
fix(dsh-plugin-browserskill): dock below the shell top bar
BB-fat Aug 14, 2026
eb71ddc
docs(dsh-plugin-browserskill): note the top-right docking and shell-n…
BB-fat Aug 14, 2026
d1001e1
feat(dsh-plugin-browserskill): reskin the overlay with BrowserSkill's…
BB-fat Aug 14, 2026
52aa356
test(dsh-plugin-browserskill): build the scoped css before vitest
BB-fat Aug 14, 2026
a4e6685
fix(dsh-plugin-browserskill): address review findings (leaks, trust f…
BB-fat Aug 14, 2026
4d1dcd1
feat(dsh-plugin-browserskill): publish the BSK skill through ctx.skills
BB-fat Aug 14, 2026
ce11325
feat(dsh-plugin-browserskill): reveal tool schemas only after skill i…
BB-fat Aug 14, 2026
43ebfab
fix(dsh-plugin-browserskill): register the lazy suite from any fiber
BB-fat Aug 14, 2026
51df868
chore(dsh-plugin-browserskill): prepare the package for npm publishing
BB-fat Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .stylelintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
**/.output/**
**/target/**
**/node_modules/**
# Generated client CSS (tailwind compile output); built by
# packages/dsh-plugin-browserskill/scripts/build-client-css.mjs.
**/dsh-plugin-browserskill/src/client/bsk-ui.nomodule.css
5 changes: 5 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
"**",
"!**/node_modules/**",
"!**/dist/**",
"!packages/dsh-plugin-browserskill/lib/**",
"!packages/dsh-plugin-browserskill/src/skill-content.generated.ts",
"!packages/dsh-plugin-browserskill/src/client/bsk-ui.nomodule.css",
"!packages/dsh-plugin-browserskill/.bsk-ui.raw.css",
"!packages/dsh-plugin-browserskill/.bsk-ui.input.css",
"!**/target/**",
"!**/.wxt/**",
"!**/.output/**",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"ext:dev": "pnpm --filter @browser-skill/extension dev",
"ext:test": "pnpm --filter @browser-skill/extension test",
"vom:test": "pnpm --filter @browser-skill/vom test",
"lint": "biome check . && stylelint \"**/*.css\"",
"lint": "biome check . && stylelint \"**/*.css\" && pnpm --filter dsh-plugin-browserskill typecheck && pnpm --filter dsh-plugin-browserskill test",
"format": "biome format --write ."
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions packages/dsh-plugin-browserskill/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib/
src/client/bsk-ui.nomodule.css
.bsk-ui.input.css
.bsk-ui.raw.css
src/skill-content.generated.ts
21 changes: 21 additions & 0 deletions packages/dsh-plugin-browserskill/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Tencent

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
174 changes: 174 additions & 0 deletions packages/dsh-plugin-browserskill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# 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. |

## Agent skill (progressive disclosure)

Beyond the tools, the plugin publishes the **`browser-skill` agent skill** through the harness's
official skill seam (`ctx.skills.register`): the catalog entry (name + routing description) is
resident in `<available_skills>`, and the body is loaded only when the model invokes the `skill`
tool. The body is assembled at build time from two parts, so there is exactly one source of
truth: a dsh-specific prelude (`skill/prelude.md` — tool↔CLI map, owned-session semantics,
plugin-only overrides) followed verbatim by the canonical CLI skill (`skill/SKILL.md` at the
repo root — the same file `crates/bsk-cli/build.rs` mirrors for the CLI package; workflows,
stop-when-done rules, refs usage, sandbox rules). Registration and every pre-step catalog
snapshot are pure in-memory reads (no disk/process/daemon); compositions without the skill
seam degrade silently.

## 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
# lazyTools: true # reveal browser_* tools only after the skill is invoked
```

- **`lazyTools` (default `true`)** — the final progressive-disclosure stage: the eleven
`browser_*` tool schemas stay OUT of the system prompt (zero schema tokens) until the
`browser-skill` skill is actually invoked — the skill catalog entry is the only
advertisement. One successful invocation (model tool call, or a `/browser-skill` user
gesture) registers the whole suite for the rest of the process; repeated invocations are
no-ops, and sessions resumed with a past invocation in their durable log reveal the suite
on entry. Set `false` for the legacy always-on registration.

## 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
3 changes: 3 additions & 0 deletions packages/dsh-plugin-browserskill/cordis.patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- insert:
- id: browserskill
name: dsh-plugin-browserskill
111 changes: 111 additions & 0 deletions packages/dsh-plugin-browserskill/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"name": "dsh-plugin-browserskill",
"description": "DeepSeek Harness tool plugin that exposes BrowserSkill (bsk) browser automation to the model",
"version": "0.1.0",
"type": "module",
"main": "lib/index.mjs",
"types": "lib/index.d.mts",
"exports": {
".": {
"types": "./lib/index.d.mts",
"default": "./lib/index.mjs"
},
"./client": {
"default": "./lib/client.cjs"
},
"./package.json": "./package.json"
},
"files": [
"lib",
"cordis.patch.yml",
"LICENSE"
],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
},
"client": {
"platform": "web",
"inject": [
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-tool",
"@deepseek-ai/dsh-client-ui-layout"
]
}
},
"scripts": {
"build": "node scripts/build-client-css.mjs && node scripts/build-skill-content.mjs && tsdown",
"typecheck": "pnpm run build:skill && tsc --noEmit",
"test": "pnpm run build:css && pnpm run build:skill && vitest run",
"build:css": "node scripts/build-client-css.mjs",
"build:skill": "node scripts/build-skill-content.mjs",
"prepack": "pnpm run build",
"lint:publint": "publint"
},
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.1",
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-attachment": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-tool": "^0.1.0-rc.6",
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
"@deepseek-ai/schemastery": "^3.18.1",
"react": "^18.2.0"
},
"peerDependenciesMeta": {
"@deepseek-ai/dsh-attachment": {
"optional": true
},
"@deepseek-ai/dsh-client-runtime": {
"optional": true
},
"@deepseek-ai/dsh-client-ui-attachment": {
"optional": true
},
"@deepseek-ai/dsh-client-ui-primitives": {
"optional": true
},
"@deepseek-ai/dsh-client-ui-tool": {
"optional": true
},
"@deepseek-ai/dsh-llm": {
"optional": true
},
"react": {
"optional": true
}
},
"devDependencies": {
"@deepseek-ai/cordis": "^4.0.1",
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-attachment": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
"@deepseek-ai/dsh-client-ui-tool": "^0.1.0-rc.6",
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
"@deepseek-ai/schemastery": "^3.18.1",
"@tailwindcss/cli": "^4.3.3",
"@testing-library/react": "^16.1.0",
"@types/node": "^22.10.0",
"@types/react": "~18.3.1",
"@types/react-dom": "^18.3.0",
"happy-dom": "^15.11.7",
"lightningcss": "^1.30.0",
"postcss": "^8.5.0",
"postcss-prefix-selector": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^4.3.3",
"tsdown": "^0.22.14",
"typescript": "^5.8.0",
"vitest": "^4.1.6",
"@browser-skill/ui": "workspace:*",
"@remixicon/react": "^4.9.0"
},
"license": "MIT"
}
Loading