DSH Browser Tabs
English | Simplified Chinese
A DeepSeek Harness plugin for referencing and controlling existing Chrome and
Edge tabs, without opening a separate browser or exporting login cookies.
Current Status
Prototype targeting the plugin APIs shipped with DSH 0.2.0-rc.2.
- Host and client modules build successfully.
- Unit tests and an isolated, headless Chromium extension integration test pass.
- The Remote contribution is mounted and invoked against the real DSH client
Gateway and Typert registry bundles, so
@ and / reach the host service.
- Version
0.1.3 pairs by code instead of by generated files: the shipped
extension folder is loaded as-is (it pins its own id), the extension shows a
4-digit code on its toolbar badge, and the DSH setup page approves the request
carrying the same code. The token then lives in the extension's browser storage.
- Version
0.1.2 fixed four defects. Three made both menus report
服务不可用: Cannot read properties of undefined (reading 'list'):
- the Remote descriptors declared
src-json codecs, which the client Gateway
refuses, so remote.browserTabs was never mounted;
- the client read the namespace as
ctx.remote.browserTabs while injecting only
remote; Cordis rejects that nested access without the nested key injected;
- the generated extension folder briefly held the unpaired
config.js template
on every start, so a service worker that restarted during that window imported
an empty token and reported OFF while DSH was running.
The fourth silently disabled browser control after the first step of a turn:
agent/pre-step claims one input batch per step, and the batch carries tool
results rather than the user message on every later step, so rebuilding the
authorized tab set from each batch revoked the citation. Grants now accumulate
per conversation and are remembered in authorized.json, so a DSH restart
keeps previously cited tabs controllable.
- Connection to a real, logged-in browser profile has not yet been re-verified
end to end in a running DSH session after these fixes; no computer-control
debugging is used. Installing the DSH package alone does not install the browser
extension.
Features
- Existing HTTP/HTTPS tabs appear in both
@ and / menus, alongside built-in sources.
- Website icons come from the browser's local favicon cache, with a globe fallback.
- A
/browser command for the companion extension's connection settings.
- One-click code pairing: load the shipped extension once, compare a 4-digit code, done.
- An offline connection entry instead of silently hiding the browser candidates.
- Automatic background connection and reconnection, without opening the popup.
- Compact accessibility snapshots with element references and explicit pagination.
- Batched browser actions through Playwright, with a maximum of 12 steps per call.
- Local PNG screenshots on demand.
- Conversation-scoped tab selection; stale snapshots are rejected before acting.
Requirements
| Component | Requirement |
|---|
| DSH | 0.2.0-rc.2 plugin APIs. DSH and its packages are published on npm under @deepseek-ai/dsh* (MIT / BSD-3-Clause). |
| Node.js | 22 or newer, plus npm. |
| Browser | Chromium-based Chrome or Edge 125 or newer, with Developer Mode enabled. |
| Platform | Verified on Windows. macOS and Linux are expected to work (paths come from os.homedir() / DSH_HOME) but have not been tested. |
Build And Install
npm ci
npm run build
In DSH, open Plugins > Add Plugin and point it at this built project directory,
wherever you cloned it (for example ~/code/dsh-browser-tabs).
The package declares dsh.bundle and includes cordis.patch.yml, which contributes
the browser-tabs plugin configuration. Do not install an unbuilt source checkout:
the runtime entry points are lib/host.js and lib/client.js.
To create a distributable package containing the compiled modules:
npm pack
prepack runs the build automatically. The resulting dsh-browser-tabs-0.1.3.tgz
contains the runtime modules, extension templates, bundle configuration, and docs.
It can be installed with DSH's CLI:
dsh plugin --profile desktop add ./dsh-browser-tabs-0.1.3.tgz
For directory-based UI installation from a distribution, extract the archive and
select its package directory. Cloning the source from Git requires the build step
above; there is no build-free source install.
Connect The Browser
Setup page: copy buttons for the extension folder and manager URL, a pending pairing request with its code, and the port hint
The companion extension is loaded straight from this package's extension folder
(editions installed through DSH's plugin manager ship it inside the package); it
pins its own manifest key, so its id is the same everywhere and nothing has to be
generated, copied, or edited before loading it. That key is a public key, not
a secret: it only fixes the extension id, which the bridge then requires as the
WebSocket origin. Do not rotate it, or every already-loaded copy becomes a
different extension.
- Select
/browser in DSH to open the local connection settings page. It hands
out the folder path as a copy button and lists any pending pairing request.
- Open
edge://extensions or chrome://extensions, enable Developer Mode, and
choose Load unpacked, pasting the copied path into the folder picker
(macOS: press ⇧⌘G). Browsers forbid a web page from opening the extension
manager, so this one paste is unavoidable; the page offers edge://extensions
and chrome://extensions as copy buttons for the address bar.
- The extension now asks DSH to pair: its toolbar badge shows a 4-digit code.
- On the DSH setup page, click 允许 for the request showing the same code.
That comparison is what authorises this browser profile; nothing is typed or
copied, and the token never leaves the two processes.
- Return to DSH, type
@, select a tab, and describe the task.
The token is then stored in the extension's own browser storage and reused on every
start, so pairing happens once per browser profile. The popup shows the state,
offers 解除配对 (unpair) and a port field: DSH's default port is 43187, and
only if the plugin's port setting was changed does that field need the same value
(the setup page shows the port in use).
This is a normal Manifest V3 browser extension: load it once per browser profile.
Afterward, keep the extension enabled and both DSH and the browser running. No
popup or setup page needs to stay open. If DSH starts later or restarts, the
extension reconnects automatically. A pause selected in the popup is saved and
remains paused across browser restarts.
After updating the package, click Reload on the installed extension in the
browser's extension manager. Do not remove it or clear its storage: that preserves
the pairing and profile identity. An OFF toolbar badge means it is waiting for
DSH. Browser-internal and incognito pages are not listed, and each menu query
returns up to 50 matching tabs.
The extension is currently distributed as an unpacked extension, not a Chrome Web
Store or Edge Add-ons listing. Store publication would require a developer account
and review; it is separate from installation through DSH's plugin manager.
The default setup page is http://127.0.0.1:43187/setup while the plugin is running.
Updating From 0.1.2 Or Earlier
Versions before 0.1.3 generated a per-install copy of the extension with the
token baked into config.js, and that copy was the folder you loaded. The bridge
still accepts such a copy with the old token, so an existing install keeps working;
to move to code pairing, load this package's extension folder instead, approve
the request it raises, and remove the generated copy from the browser's extension
manager. Delete the leftover browser-tabs/extension directory afterwards — the
bridge no longer reads or writes it.
Restart DSH (or disable and re-enable the plugin) after updating so the rebuilt
host and client modules are loaded, then refresh the DSH page in the browser.
Port Already In Use
Version 0.1.0 had an unload cleanup bug: disabling or removing the plugin could
leave its bridge listening on port 43187. Version 0.1.1 uses Cordis-owned
resource cleanup, including failed activation, so subsequent unloads release it.
Activation now fails with an explicit message naming the port instead of a bare
EADDRINUSE, and the extension popup lets you point a browser at another port.
For the first update from 0.1.0, save any ongoing work and fully quit DSH,
including its background/tray process, then start it again. Closing the window or
reinstalling the plugin alone cannot release a listener leaked by the old code.
Install or enable the updated plugin from the same project directory. Browser
tabs, login state, and extension pairing do not need to be reset.
If another application or a second DSH instance owns the configured port, stop
that instance or change the plugin's port setting and set the same value in the
extension popup. The plugin does not kill other processes or silently change the
browser's connection port.
Configuration
Defaults are contributed by cordis.patch.yml; the host also accepts an optional
home setting.
| Setting | Default | Meaning |
|---|
port | 43187 | Local bridge port, bound to 127.0.0.1 only. |
snapshotChars | 2400 | Default character budget for a snapshot response. |
home | DSH_HOME or ~/.dsh | Base directory; plugin data is stored under browser-tabs/. |
Plugin data under browser-tabs/ is pairing.json (the connection token — never
commit or share it), authorized.json (the tab references each conversation cited
with @; identifiers only, no page content), and captures/ (requested
screenshots). Deleting pairing.json rotates the token, which means the extension
must be unpaired and paired again; deleting authorized.json only means previously
cited tabs must be selected with @ again. Version 0.1.3 keeps nothing else:
there is no generated extension folder any more.
If the plugin's port setting is changed, set the same port in the extension popup
(the setup page shows it) and avoid running two plugin instances on one port.
Model Tools
| Tool | Purpose |
|---|
browser_read | Read a compact snapshot; narrow with selector or query, or continue with cursor. |
browser_act | Use the latest snapshot ID to click, fill, press, select, check, hover, scroll, or navigate. |
browser_capture | Save a screenshot and return its local path; no base64 is placed in text output. |
Actions can use snapshot element references such as e1 or Playwright selectors.
A batch stops at the first failure or navigation and reports completed steps.
Read the current state before continuing; do not replay completed actions.
Efficiency And Privacy
Tab discovery returns metadata to the UI rather than sending every open page to
the model. Cached favicon images also stay in the UI, not the model context. When
a direct user message references a tab, the plugin prepares a
bounded initial snapshot and caches that context for the message. Full snapshot
text remains local; omitted content can be retrieved with a cursor. Read limits
are 400 to 8000 characters, with accessibility depth 8 by default.
The bridge binds 127.0.0.1 only, accepts the WebSocket origin of the pinned
extension id, and requires a per-install token. Because any local process can forge
an origin, the first connection is instead confirmed by the person at the keyboard:
the extension displays a 4-digit code on its badge and the DSH setup page approves
the request carrying the same code, so a local impostor cannot collect the token.
The bridge attaches the debugger to referenced tabs on demand and releases it when
the connection stops. Incognito tabs and browser-internal pages are excluded.
Opening DevTools or another debugger may interrupt control.
Page data returned by these tools enters the configured DSH model's context.
Only reference pages whose content you intend to share with that model. Browser
content is untrusted data, not instructions or authorization. Destructive actions,
purchases, and sensitive submissions still require explicit user authorization;
the tool descriptions are not a transaction-level approval mechanism.
Do not commit or distribute pairing.json: it contains the local connection token.
The packaged extension/config.js carries only the default port, so it is safe to
publish and to load as-is.
Development And Tests
npm test
npm run build
CI runs this suite on Linux and Windows plus the browser integration test on Linux; see .github/workflows/ci.yml.
For the browser integration test, install Playwright's matching test browser once:
npx playwright-core install chromium
npm run test:browser
The integration test uses a disposable profile, a local fixture page, and bridge
port 43188. It checks extension inventory, authenticated CDP transport, compact
references, batched filling and clicking, iframe access, stale navigation,
screenshots, and release without closing the controlled tab. It also checks local
favicon images, background connection when DSH starts later, and reconnection
after DSH/browser restarts without opening the popup. It does not interact with
the user's normal browser profile or prove the native DSH menu UI works.
Unit tests pin the DSH contracts and the pairing rules: the Remote descriptors must
carry strict codecs (the client Gateway refuses src-json), the client must resolve
the namespace without declaring the nested key it mounts itself, the bridge must
never write into the folder the user loads, a pairing token is handed only to the
request whose code the user approved (and never to a forged or expired one), and a
tab cited by the user must stay authorized across later steps and activation. The
lifecycle and grant checks run in the same disposable child process as the
agent/pre-step waterfall, and the extension background is executed in a stub
chrome context that asserts the badge code, the stored token, and the port field.
The plugin depends on three DSH interfaces that its own documentation does not
freeze: generated strict Remote codecs, the Typert registry, and the client API
Gateway. To re-check them after a DSH upgrade, point the verification script at the
installed archive (DSH ships as an ASAR bundle inside its application resources):
npm run verify:dsh -- --asar "<path to app.asar>"
It extracts the relevant bundles into .local/dsh-extract (Git-ignored), then
asserts that the real registry accepts this package's host contribution, that the
real Gateway mounts and invokes its client contribution, and that the built
lib/client.js lists host tabs through its @ source. The script needs npm ci
first so the extracted bundles can resolve Cordis.
Host lifecycle tests use the same Cordis 4.0.4 version as DSH 0.2.0-rc.2, in
disposable child processes. They verify repeated enable/disable and cleanup after
failed activation, without touching a running DSH instance.
Toolbar PNG icons are included in the source package. To regenerate them from the
included Lucide artwork with the matching test browser, run npm run icons. The README screenshot is generated from the real setup page by npm run screenshot.
Project Layout
src/ DSH host/client, bridge, grants, and browser engine
src/vendor/ Adapted Playwright CDP tab model
extension/ Companion extension (loadable as-is; pins its own id)
scripts/ Build, integration test, DSH contract verification
test/ Node.js unit tests
cordis.patch.yml DSH bundle configuration
lib/ Generated runtime entry points (Git-ignored)
License
Project code is MIT-licensed. The adapted Playwright tab model is Apache-2.0;
see NOTICE and extension/APACHE-2.0.txt for attribution and license terms.
The toolbar globe artwork is from Lucide under ISC; see extension/icons/LICENSE.