DeepSeek Harness in a browser tab — the real agent, real Node, no server to run.
DeepSeek Harness (dsh) is an
agent harness where everything is a plugin. dsh web runs a Node host and serves
a browser client to it. webdsh is that, as static files — the host runs inside
the page, and the agent's commands run in WebContainers:
Node itself, in the tab.
- ⚡ Nothing to run. No server, no install, no local Node — the harness boots in the page.
- 🖥️ Real Node, real Python.
npm installandpip installboth work, and the terminal and the agent share one container. - 💾 Or a whole PC. Settings → Machine swaps the container for v86 and offers 128 machines — the whole of v86's catalog, from a 512-byte bootsector game to Windows 2000, 127 of them booting with nothing to set up — emulated x86, on its own screen, with the tool set that machine actually has.
- 🌐 The PC is online. A WISP relay by default, so the guest gets real TCP —
https://, package managers,ssh— and without one the page itself is the router: it answers the guest's DHCP, DNS and pings and carries HTTP as browserfetch, through the same CORS policy the rest of the app uses.wget http://example.comworks on an emulated Buildroot either way; the same URL from the container answersfetch failed. - 👁️ It can see. Attach an image and the model reads it: oriented, capped and re-encoded to the route's budget by the browser's own decoder, with the source's EXIF and colour profile stripped on the way.
- 🧩 Real plugins. Install from npm, a tarball, GitHub, or a path — from the browser.
- 📦 Real dsh. The published
@deepseek-ai/*packages, unmodified: 120 of 135 rows compose exactly asdsh webcomposes them. - 🔒 Yours. Files, sessions and keys live in your browser's storage. Nothing is uploaded.
Nothing here is a fork of dsh. The agent loop, tool registry, model adapters and
the entire web client come from npm at install time; the only modification is a
cordis.patch.yml layer — the mechanism dsh documents for exactly this.
What this repository adds is the platform underneath: a synchronous POSIX
filesystem mirrored to IndexedDB (src/vfs), node:* implemented over it
(src/node), the two runtimes a session can run on — WebContainers and an
emulated x86 PC (src/runtime) — an in-page virtual server for /api, the
CORS policy every outbound request goes through and the network the emulated
machine is given (src/net), and the plugins this build ships (packages/).
Six composition rows are swapped, each because the shipped one names something a
page cannot have — or, in the shell's case, cannot honestly describe. Four more
are reconfigured rather than replaced, including the one that decides whether
this deployment can open a path at all. npx tsx scripts/alignment.ts prints
the whole difference.
Nothing to install — open the page. To run it yourself:
npm ci
npm run build # → dist/
node scripts/serve.mjs 4173Node 22 or newer. dist/ is plain static files with relative URLs, so it works
at a domain root, a project path, or a local directory.
Open the page, choose a workspace, start talking. 42 models across six routes are registered up front, so it answers before it asks you for anything.
Three things live in the sidebar:
- Files — the workspace, as the agent and the terminal see it. Drop files in; take a file, a directory or a tick-box selection back out. Click a path the assistant mentions to open it here.
- Machine —
Ctrl+`. What this session runs on: a terminal for the Node container, the live screen for an emulated PC. Click the screen and the machine gets your keyboard and mouse; Escape gives them back. - Settings — which machine, which models, which CORS proxy, which plugins.
Both panels dock beside the conversation on a wide window and along the bottom on a narrow one, and take width from it rather than covering it.
Machines. Settings → Machine offers 128 — the whole of v86's
catalog — and 127 boot with nothing to set up. The
choice applies on the next load, because it decides which tools the assistant
gets: jsh, Node and Python in the container; sh or dos plus
vm_screenshot, vm_key, vm_type, vm_mouse and friends on a guest, whose
disk shares nothing with your workspace. A guest is offered the tools that
currently work on it and not the ones that would come back empty — no
vm_screen on a desktop with no text, no vm_mouse at a prompt that never
turned a mouse on. The one that is left, Arch, wants a host for its 9p tree —
open one from your computer and it stays in your browser, or point the setting
at a host that serves them. npm run v86:catalog prints the difference against
upstream; npm run v86:boot -- --bundled --as-shipped re-boots all 127. The
disks come from v86's own copy/images and the hosts
src/runtime/v86-mirror.json names, mostly
AndyZijianZhang/webdsh-images,
whose NOTICE.json records where every image came from and under what licence.
The machine's network. An emulated PC gets an ethernet card and this page on
the other end of it: it answers the guest's ARP, DHCP, DNS and pings itself, and
turns the HTTP requests inside the guest's TCP into fetch calls — which go
through the CORS policy above, direct first and proxied only when a host refuses
a browser. So an emulated Buildroot can wget http://example.com; the same URL
from the container answers fetch failed, because its requests leave from
StackBlitz's worker where this page never sees them and no automatic retry is
possible. Ports other than 80 work too, which stock v86 resets.
What a tab cannot carry is TLS: https:// from inside the guest would have to
terminate here, so those connections are refused rather than left hanging, and
served over HTTPS this page sends plain http:// as HTTPS on the wire wherever
the host wants that. It is outbound HTTP and nothing else — no inbound route to
a server running in the guest, no UDP beyond the DHCP and DNS the page fakes,
and no cookies or CORS-hidden response headers, so anything that needs a login
will not work. ping and DNS are answered by the page, not by the host named.
The guest is refused this page's own origin, because the fetch is made by the
tab and a same-origin request needs no CORS at all — it would be a line into the
harness's own /api. Your computer's network is not blocked: localhost, the
LAN and v86's <port>.external names all work.
That is the floor. A WISP relay is configured by default, and with one the
guest has real TCP instead of HTTP-shaped TCP: https:// works end to end,
package managers and ssh become possible, and DNS is real. The cost is that a
third party carries every byte the machine sends, including inside a TLS session
it is only forwarding — Settings → Network says so plainly, one click clears it,
and clearing it leaves the in-page bridge working. A relay that does not answer
when a machine starts is dropped for that bridge rather than left as a network
that silently does nothing. Whether a particular guest has a driver for the card is a fact about its
disk: it is measured per machine — three of them so far — and a machine nobody
has measured is described as exactly that rather than promised a network.
Plugins. /plugin add <package> in the composer, or Settings → Plugins.
Takes an npm name, a tarball URL, owner/repo#ref, or a path. The composition
is fixed at boot, so a change applies on the next reload.
Persistence. Workspace, sessions and transcripts survive a reload.
window.dsh.exportFs() downloads a zip; window.dsh.reset() clears it all.
Worth knowing: the container's shell is jsh, not bash, and ships no git;
python3 is CPython 3.14 compiled to WebAssembly, fetched on first use, so it
has pip but no compiler, no subprocesses and no sockets — and write python3,
because jsh aliases python and loses the quoting on the way.
Issues and PRs welcome at futrime/webdsh.
Commits follow Conventional Commits;
npx tsc --noEmit and npm test should pass first; npm run test:vision,
npm run test:v86 and npm run test:workload cover the image pipeline, the
emulated machines and the agent finishing real jobs on each of them, and
npm run test:machine-network exercises the guest HTTP bridge's parser and its
refusals without booting anything.
Apache-2.0 © Zijian Zhang. The
@deepseek-ai/* packages it composes are published by DeepSeek AI under their
own terms, and the disk images it fetches remain under their own authors'.