- remote-access 新增 whenAccess / whenResourceAccess,ui-session、ui-workspace 各自提供 whenAccess - schedule、plugin-manager、settings-general、settings-account、directory-picker-native 的入口包进 global whenAccess;消息反馈按钮按会话 useAccess 渲染 - webserver/index-inject 带 Peer,renderIndex / collectIndexInjections 需要 Peer,remote-access 注入 snapshotFor(peer.principal) - client-test-runtime 新增 StaticRemoteAccess、staticAccessPlugin 与 TestClientOptions.access - implemented Agent Note 2026-10-21-when-access 与升级指南 webserver-index-peer Refs #2940
description, kind
| description | kind |
|---|---|
| Browser-worker harness hosting for maintainers building or debugging the experimental Web preview runtime. | package-library |
@deepseek-ai/dsh-experimental-webworker-runtime
English | 中文
Summary
The browser worker host: the whole harness plugin tree runs inside one dedicated Web Worker, for preview deployments and packaging regressions (experimental group). The worker inflates a packed VFS image off its download and mounts it in memory, loads its modules through a CommonJS wrapper loader, and serves the page over a postMessage tunnel that speaks plain HTTP. Use it when a preview must run the packaged harness without a Node host.
Table of Contents
Use this package
The public npm package exposes the runtime library at its root, the self-contained browser Worker at ./worker, and the page connector at ./client. Deployments supply and serve the VFS image separately through the packer; installing this library does not add a profile layer. The root also exports the module-proxy and replacement tables used by the packer.
Three build artifacts and one source-owned process layer:
lib/index.js(assembly library) —createWorkerHost/startWorkerHostmount the base image and any ordered data overlays (storage/), register the image's lazy archives, install the module loader (module-system/) and theprocessshim, boot the tree through the image's owndsh-app-boot, and hand the tunnel its serving seams. Overlays may replace files only underhome/andworkspace/; they cannot replace the base manifest or modules. The image layout contract (image-layout.ts: virtual root, manifest path, lazy archive directory, empty directories) and the manifest (image-manifest.ts: profile, installation, theloweredwrapper-contract gate, lazy archives) are shared with the packer. The Worker boots the manifest's shipped profile as the launcher does: the image's app-boot initializeshome/profiles/<profile>/from the shipped template, resolves bundles from the installation manifest the image carries alone, and composes the bundle layers, the profile patch, and the home patch. The profile context, the launch environment snapshot, and aPluginPackagesservice without a runtime resolution are provided as the launcher provides them; the Worker loader owns module resolution. The profile context carries no command-line overlay; the image's home patch (home/cordis.patch.yml), which the packer writes fromPLATFORM_ENTRY_CONFIG, keeps JSONL Session logs plaintext. ConfigEditor persists overrides in the profile patch, reconciles each write, and Loader commits live values. The Worker commitsappReadyonce the tunnel serves, so HMR loads and applies profile changes live; the Worker's ES module cache stays empty and theprocessshim reports no entry script, so HMR compares and replaces no loaded modules. Plugin management lists and switches the installation's optional bundles, and its package operations fail with theexecarefusal. Configuration edits last for the current Worker lifetime.- Lazy archives — the manifest registers each page chunk the packer moved out of the base image by path and size, with its archive name relative to the image URL. Existence, stats, and listings answer at once; the first asynchronous read (
fs.promises.readFile,fs.promises.open,fs.createReadStream,fs.promises.cp) fetches and inflates that archive once for every waiting reader, checks it against the registration, and mounts its files without publishing a mutation. A failed fetch rejects the read and leaves the files registered, so the next read fetches again. Until then, synchronous reads, writes, appends, truncation, chmod, rename, and link of a registered file fail withERR_DSH_VFS_DEFERREDnaming the archive; unlink and recursive removal drop the registration, and seeding the path replaces it. The archive name derives from its content digest, and the fetch checks only the registered paths and sizes, not the digest. An image that registers lazy archives must be passed as a URL. lib/worker.js(worker bundle) — the assembly plus this package's Node-compatibility layer as one self-contained ES module. The module proxy table (module-proxies.ts) is the only platform fork:node:*builtins over VFS/tunnel/browser primitives, structural stubs that fail loud on the console for what a browser cannot do, and native/binary package replacements.node:modulesuppliescreateRequire().resolveand.resolve.paths()over the image package root, so unchanged packages can discover manifests without evaluating their modules.node-addon-require-builtinanswers the Node v24.18.0 internal modules that the vendored Loader, app-boot, and HMR read without--expose-internals: the cascaded ESM loader'sresolveSyncandimportover the Worker loader, with Node'sERR_MODULE_NOT_FOUNDandERR_PACKAGE_PATH_NOT_EXPORTEDcodes, and an emptyloadCache; and the CommonJS loader, package reader,modulesbinding, and resolve-cache members that HMR replaces, which refuse when called.process.versions.nodereports24.18.0, soModuleLoader.fromInternal()and the Loader'sinternalreach that cascaded loader.node:path.win32.isAbsoluteandnode:util.inheritsfollow Node; otherwin32members throw. Thenode:utilJSON-data comparator compares own enumerable properties, preserving Node admission decisions for recorded fields such as__proto__. The globalprocessshim carries Node detection fields includingtitle, preventing Worker execution from entering DOM-only branches; Node’sglobalalias references the sameglobalThisobject for image-loaded packages. The pack-time parser reports statically named module requests, including module-scope direct calls of the formcreateRequire(import.meta.url)('pkg')through a namednode:moduleormoduleimport, to the packer's reachability walk. Stored, CommonJS-obtained, and rebasedcreateRequirecalls require image entry seeds. VFS mutations drivenode:fscallback, polling, and promise watchers, whose listeners run in the async context where the watcher was created rather than that of the writing code; open descriptors retain file identity and access mode across rename, replacement, and unlink,FileHandle.stat({ bigint: true })reports the same device and inode identity as a path stat while the name still refers to that file, andFileHandle.chmod()updates the opened file identity;readable-streamsuppliesnode:stream/promisescompletion and pipeline helpers and the stream state machine used by file streams and unchanged image packages such as Chokidar and readdirp. The maintainedstring_decoderpackage suppliesnode:string_decoder, retaining incomplete multibyte characters between chunks. AsyncLocalStorage carries sync-stack causality acrossawaitthrough the snapshot/restore faces the pack-time lowering injects. The worker holds no compiler: an image the packer did not lower is refused at mount. The callbackfs.realpath.nativealias resolves within the mounted VFS and supportsutil.promisify.src/shell/(the worker's own process layer) — a browser worker cannot fork, sonode:child_processis not a stub but an implementation:spawnstarts the command in its own Web Worker — this same bundle, told by its first frame to be a shell process — and reports it through theChildProcesssurface the subprocess service consumes. The command runs off the host's thread,SIGKILLterminates it whatever it is doing, and it reaches the VFS only by message (the host serves those frames). Worker platform executables preserve native-package protocols such as Landlock without replacing their JavaScript packages or coupling their implementations tonode:child_process;xdg-opensends one VFS text file to the page's read-only viewer, so desktop-opener actions such as Open configuration file show the file in the page; ordinary commands use the package's evaluator and coreutils command table. The grammar is@yarnpkg/parsers'parseShell, whileexecSync/forkstill refuse because they need a real process.lib/client.js(page half) — startup has two independent stages.chooseWorkerHostSource({ image?, fixtureManifest? })optionally owns the boot barrier and fixture manifest: withoutpreview-fixtureit waits at the source chooser, while a valid query selects directly; either path returns ordered overlays.connectWorkerHost(worker, { image?, overlays? })remains the public base-runtime connector; callers that skip the chooser get an empty overlay list.apps/webinvokes both and supplies its statically bundled Worker. The openinginitframe carries the base and ordered overlay URLs, the boot payload delivers the structured index-injection table, andapplyIndexInjectionsexecutes it before the shell entry runs. Script preload rows are advisory and skipped because/pluginsresources resolve only through the tunnel;loadBundlefetches each combo on first demand, embeds its tunnel-only source map as a Base64 data URL, and executes the script as a Blob. Before the shell entry runs, the page readsGET /__assets__, which the tunnel answers with an uncompressed tar of every package asset in the image, and suppliesresolveAsset, which maps eachplugins/<package>/assets/<path>reference a bundle passes torequire.assetto a Blob URL of those bytes. The worker's unsolicitedview-textframe opens the page's modal text viewer. The tunnel also exposes fetch-shaped transport, the independent file-upload carrier, and the API client. Request frames preserve Blob bodies through structured clone and transferReadableStream<Uint8Array>ownership. The Host Worker streams both forms into the route, so neither browser thread creates a complete byte array for a generic-file upload. A Remote stream's uplink crosses asstream-uplink-item/stream-uplink-endframes into an unboundedTunnelUplinkthat drops items arriving afterend: the page and its worker are one trusted same-origin boundary, so the Host inbox cap does not apply.
Callbacks and timers registered under an explicit root context retain its empty stores, so they cannot inherit an unrelated transaction.
Acceptance lives in apps/web/tests/preview-boot.e2e.ts, which serves the real built pages and drives the pre-boot chooser plus Worker activation in headless Chromium. The empty selection exercises first-run startup. The vfs-example overlay supplies ordinary workspace files and plaintext persistence artifacts for cold Workspace/Session discovery, tool presentation, subagent navigation, and history paging without a model request. The showcase mixes empty, whitespace-only, and non-empty reasoning with a long multiline command and a parent-path file link; these are authored fixtures, not live model output. The packer prepares current-generation successors from committed historical logs in Node; those source logs remain byte-identical in the overlay. The fixture generator owns the acceptance test's current projection cache. The chooser reserves WebFS as a separate user-authorized source; that provider does not read the built-in fixture.
The built-bundle import sweep checks bare Node imports after the library build. Its Dockkit exception accepts Node’s unknown-.css-extension refusal for any stylesheet rather than one pinned path; another extension, error code, or message, and an unexpectedly successful exempt import, remain errors.
Model Experience
None, as this package only hosts the tree in a browser worker and answers its node:* calls; every model-facing registration belongs to the plugins it boots.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- The worker composition writes plaintext session logs (
compression: 'none'in the image's home patch; ConfigEditor rejects edits to that row's config): it carries no Zstandard codec, so exported logs are.jsonl, never.jsonl.zstd. node:dns/promises,node:vm,node:net,node:sqlite,node:worker_threadsare structural stubs: every call reports its refusal on the console and throws with Node'sERR_FEATURE_UNAVAILABLE_ON_PLATFORMcode. Rows needing native DNS, a real process, or realm isolation cannot run here.node:assert/strictsupports truthiness assertions only: for falsy values, the callable default andokthrowERR_ASSERTIONwith the supplied message or generic text, or rethrow a supplied Error unchanged. Other assertion APIs are absent.- Native TLS listeners require a Node Host: the worker's
node:https.createServerrejects activation rather than substituting plaintext. The preview's internal HTTP tunnel does not bind sockets; browser-facing HTTPS belongs to its deployment server. - Desktop product telemetry is unavailable:
got.postreports an explicit worker-host refusal. Got and its Node HTTP dependencies are excluded from the browser image; Desktop reporting remains disabled in the preview composition. - Host package commands are unavailable:
execareports an explicit worker-host refusal; the preview cannot run pnpm, install plugins, or install native dependencies. - Pi agent execution is unavailable: the
@earendil-works/pi-coding-agentreplacement permits metadata imports and rejects SDK calls withERR_FEATURE_UNAVAILABLE_ON_PLATFORM. The image excludes its native CLI and terminal UI dependencies. Native DSH retains the full OAR runtime registry. - pi-ai model requests are unavailable: the replacement exposes a fixed subset of installed provider identifiers with no models, allowing the adapter to register configuration metadata. Its identifiers must stay valid when pi-ai is upgraded; inference entry points report an explicit worker-host refusal.
- PTC Node programs are unavailable: the process shim exposes
/dsh/bin/nodeas its executable identity so the provider can activate, but the Worker has neither a Node executable norstripTypeScriptTypes. Program execution fails before launching a child. - Office conversion requires a Node Host: the LibreOffice kit replacement rejects converter creation with
unavailable. The browser image excludes the kit and its engine dependencies; the Office preview reports that conversion is unavailable. - HMR replaces no modules: the Worker's ES module cache stays empty, so a module file changed in the VFS keeps its loaded code while HMR applies the profile change. The preview image changes no module files: package operations fail with the
execarefusal. - The Node internal modules answer only their HMR-replaced members: the CommonJS loader, package reader,
modulesbinding, andgetOrCreateModuleJobrefuse when called, and the Worker's ownrequiredoes not route through the CommonJS loader, so replacingModule._resolveFilenameorModule._loaddoes not change Worker resolution. - CommonJS
require('stream')returns the module namespace, not Node'sStreamconstructor: code that passes it toutil.inheritsfails at load. - A lazily supplied file has no synchronous read before its archive arrives: a consumer must read it asynchronously first; the Host's on-demand Client chunk route does.
- Filesystem watchers observe only the mounted VFS: image seeding is silent and the VFS has no symlinks or external writers.
persistent,ref(), andunref()preserve the Node API but cannot control a dedicated Worker's lifetime because browsers expose no ref-counted event loop. - Worker confinement is a VFS boundary, not kernel Landlock:
read-onlyandworkspace-writerun the unchanged@deepseek-ai/node-addon-system/landlock-runJavaScript and launcher argv, but the process layer implements the logicallandlock-runexecutable and enforces its grants on every shell filesystem request.fulltherefore covers the Worker command table and mounted VFS only; it does not claim arbitrary native-process execution or Linux kernel isolation. - The worker bundle pins a path inside
@yarnpkg/parsers— the build resolves the package's ownlib/shell.jsinstead of its root, whose barrel also re-exports the Syml parser and so drags js-yaml into a bundle that never parses that format (around 175 kB, plus its module body at worker start). The path is derived from the package manifest, so a layout change fails the build rather than reinstating the barrel; upgrading the dependency means re-checking that the shell parser still lives there. - The shell is not bash: no loops, functions,
case, job control, or process substitution — the grammar stops at pipelines,&&/||, subshells, groups, redirections, and expansion.&runs its command to completion in place,sedaccepts only substitution scripts, patterns are JavaScript regular expressions, and the command table holds coreutils only (nogit, no network tools). - A shell process has no synchronous filesystem: it reads and writes the host's VFS by message, because blocking on a reply would need
SharedArrayBuffer, which requires a cross-origin isolation GitHub Pages cannot grant. Directory-walking commands therefore cost one round trip per entry, and two concurrent commands can interleave their writes. - Transport, worker-host, and page-half coverage needs a browser-grade harness — the per-file coverage gate is unmet for those modules; unit specs cover storage, ALS, the transform, and the stub contracts.
Dev Note
Working context for maintainers — click to expand
None.