futrime/webdsh--packages-dsh-web-network ↗★ 33

@dsh-web/network

The Network settings page: the CORS proxy this page falls back to, as an ordinary dsh plugin 适合需要配置CORS备用代理以解决浏览器端网络请求限制的用户。

パッケージ
@dsh-web/network
互換性
未検証
バージョン
0.1.0
ライセンス
Apache-2.0
最終更新
2026/08/29

インストール

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:futrime/webdsh#18906346882dad3a0e80203383fe871ec261ce53&path:packages/dsh-web-network

ドキュメント

README 全文を読む ↗

Usage

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, the tab strip and address bar for the browser. Click the screen and the machine gets your keyboard and mouse; Escape gives them back. The browser's tabs are the same tabs the assistant is driving, not a second copy of them — what it clicks, you watch.
  • 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 three kinds: the Node container, a browser, and 128 emulated PCs — the whole of v86's catalog, 127 of which 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; browser_navigate, browser_snapshot, browser_click, browser_screenshot, browser_eval and the rest on the browser; 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.

What the catalog cannot hold is an operating system v86 cannot execute. OpenHarmony is the one people ask for: every target it has is ARM, RISC-V, Xtensa, C-SKY or x86-64, and v86 is 32-bit only, so there is no image to offer and no row for it. docs/openharmony-on-v86.md has the evidence, and npm run openharmony:check re-reads it from upstream so the answer cannot go stale unnoticed.

The browser machine. Tabs, in a frame this page cannot be reached from. Each one is sandboxed without allow-same-origin, which gives it an opaque origin — so the browser itself refuses it this page's DOM, its localStorage, its IndexedDB and the keys in them. That choice costs everything else: an opaque-origin document is not controlled by a service worker, cannot fetch anything cross-origin, and gets SecurityError from localStorage, sessionStorage, document.cookie and indexedDB alike. All of it was measured before any of it was written.

So the page is the browser's network and storage process, and the frame is only its renderer. The page fetches through the same CORS policy everything else here uses, rewrites the document to be self-contained, and hands cookies and per-site storage across a message channel into shims. Three measurements shaped it: a service worker never controls an opaque origin, so the usual same-origin-proxy trick was out; data: URLs do not taint a canvas and blob: URLs do, so every subresource is inlined as data: or browser_screenshot breaks on any page with an image on it; and window.location is the one global defineProperty refuses, which is why script text is parsed with acorn and the expressions naming location, top and parent are rewritten to read a virtual one — a site that routes on its own URL works, and the frame-buster every large site ships quietly does nothing.

Two limits, stated plainly here and in the tool descriptions the model reads. No cookie travels on a request — Cookie is a header a browser forbids a page to set, and set-cookie is not exposed cross-origin — so cookies work inside a page and persist between visits, and nothing behind a login is reachable. Most hosts need the CORS proxy, because most of the web does not permit cross-origin reads; without one, only hosts that send access-control-allow-origin can be browsed. npm run test:browser drives all of it against a local fixture site, including the isolation.

Screenshots. A model driving a machine it can see photographs it to look at it, and both screen tools — vm_screenshot on an emulated PC, browser_screenshot on a tab — hand the picture straight back the way read_image hands back a file: one call, nothing to open. The picture is not also written into the workspace. Watching a boot is a screenshot every few seconds and checking a page is another one every edit, and the Files panel filling up with them is not what anyone asked for. What still writes a file is the assistant naming a path for a view worth keeping, a model that cannot be shown a picture at all (there the file is the whole answer), and Settings → Machine → Screenshots, which keeps every one of them for anybody who wants the record.

Models you add. Settings → Models takes any OpenAI-compatible route: a base URL, a key, and Fetch models. What that listing says about modalities is read along with the rest of it, so a model the endpoint describes as accepting images is registered accepting images — GLM-5.3-Flash on a gateway is the case this was measured against. Nothing is guessed: a listing that describes no modalities leaves its models where the route's default puts them, which is text, and an entry you have declared yourself is never rewritten. If your route says nothing and its model does take pictures, input: [text, image] on that model's entry in the settings document is the whole of what this fills in for you. The roster the page ships is described the same way, from each service's own catalog — except OVHcloud, which publishes no modalities at all, so its vision model was asked a question about a picture instead and answered it. (That route then refuses the tool definitions every agent turn carries, which is its own wall and the provider's to move; what changed here is that the page no longer refuses to send a picture it could have sent.)

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 .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.

The container's network. A WebContainer's requests leave from StackBlitz's own worker, where neither the page's patched fetch nor public/sw.js can see them — so the CORS retry that the rest of the app applies could never reach it, and the shell tool's advice was to prefix a proxy by hand. It is carried in instead: src/container/net-shim.ts is written beside the shell at boot and preloaded into every Node process through NODE_OPTIONS, so a refused request is retried once through the configured proxy, automatically, wherever it came from. Measured: fetch('http://example.com') answers fetch failed without it and returns the page with it.

With a relay configured, globalThis.dshConnect(host, port) gives the container a real TCP socket as well — the emulated machine's relay, serving both runtimes from one setting. It is offered rather than installed over net.connect, because Node's own fetch is built on that function and taking it over hangs every request in the process. TLS on top of that socket does not work here: this runtime's tls reaches for internals a plain stream does not have, so https stays fetch's job.

Plugins. /plugin add 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.