Files
deepseek-harness/packages/experimental/webworker-runtime
Turtle d1e22a7e24 feat(preset): declare Agent compositions in profile YAML (#4569)
* feat(preset): declare Agent compositions in profile YAML

* fix(preset): share scope binding cleanup and refresh module graph

* fix(preset): use host syntax colors in the YAML editor

* fix(preset): write new profile declarations as block YAML

* fix(preset): align eager activation with browser and worker surfaces

* fix(ui-agent-preset): retain the existing preset card layout

* fix(preset): keep rows waiting for a Host service mounted and re-audit after settlement

A preset row whose Host provider was still activating at declaration time
was reported as waiting for a missing service, and the registry recorded the
definition as broken for the rest of the process. Split the mount audit into
failed and pending rows: failures and root-realm leaks still reject the
mount, while pending rows stay mounted and every read and binding re-audits
them after the Host Loader tree settles.

Remove the accidental self-dependency of @deepseek-ai/dsh-web-app and its
lockfile self-link. Archive the copy-only preset authoring note, which the
declarative-preset note fully supersedes, and update the ralph note's
restore recipe to the profile-patch mechanism.

* chore(lint): re-key and prune the unknown-cast baseline after the master merge

* refactor(web-app): ship each Agent preset as its own bundle patch file

`dsh.bundle.patch` accepts an ordered file list, so the four shipped preset
declarations move out of the 1000-line web patch into
`presets/<id>.patch.yml`, one insert per file, applied after the host patch.
The host patch keeps the preset registry row. Gates and test scaffolds that
read the web layer resolve every patch file through `bundlePatchPaths`
instead of one hardcoded path.

* refactor(preset): drop the save notice injected into live Agents

The notice duplicated the save result the Web editor already shows, and
after the producer-owned message sources it would have needed its own
persisted source kind. Saving now only persists and reports the activation
diagnostic; the persistence catalog is unchanged from master.

* docs: regenerate the module graph after dropping the registry's llm dependency

* test(ui-agent-preset): cover the guide dialog's keyboard handling on the declarative section

* fix(preset): address the remaining review comments

Apply the lowercase id rule only when creating a declaration, so presets
declared with other ids stay editable. Export `isBuiltInPreset` from the
display fold and key the client card groups on it. Remove the dead `error`
and `retry` locale keys, the JSDoc orphaned by the `trust` removal, the
`trust` fields in the plugin-inventory test fixtures, and the stale
`dsh-agent-presets` package name in comments and READMEs.

* refactor(preset): make the Web preset page view-and-select only

Remove the child-plugin YAML editor, the `agent_preset` tool and the
registry's read/save Remote methods. Presets are declared and changed only by
bundle patches; the settings page keeps the roster, the new-task default and
the mode help, and its question icon explains that and offers to start a
Creator-mode task. The `editing-cordis-compositions` skill and the Creator
persona describe the bundle-based path, and the authored skill-load Session
and Web snapshots are refreshed for it.

* docs(skill): make the composition skill self-sufficient for preset authoring

State where the shipped declarations live, list every declaration field,
inline a complete minimal bundle and an override example, and name the
verification calls, so Creator mode no longer reads DSH package sources to
infer the format. Refresh the authored skill-load Session for the new text.

* test(tools): drop the removed agent_preset tool from the catalog expectation
2026-09-21 08:24:18 +00:00
..
2026-09-17 21:11:19 +08:00

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/startWorkerHost mount the base image and any ordered data overlays (storage/), install the module loader (module-system/) and the process shim, boot the tree through the image's own dsh-app-boot, and hand the tunnel its serving seams. Overlays may replace files only under home/ and workspace/; they cannot replace the base manifest, configuration, or modules. The image layout contract (image-layout.ts: virtual root, config/manifest paths, empty directories, the lowered wrapper-contract gate) is shared with the packer. Boot patches select plaintext JSONL session logs. Preset declarations are part of the composed configuration.
  • 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:module supplies createRequire().resolve and .resolve.paths() over the image package root, so unchanged packages can discover manifests without evaluating their modules. The node:util JSON-data comparator compares own enumerable properties, preserving Node admission decisions for recorded fields such as __proto__. The global process shim carries Node detection fields including title, preventing Worker execution from entering DOM-only branches. The pack-time parser reports statically named module requests, including module-scope direct calls of the form createRequire(import.meta.url)('pkg') through a named node:module or module import, to the packer's reachability walk. Stored, CommonJS-obtained, and rebased createRequire calls require image entry seeds. VFS mutations drive node:fs callback, polling, and promise watchers; 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, and FileHandle.chmod() updates the opened file identity; readable-stream supplies the stream state machine used by file streams and unchanged image packages such as Chokidar and readdirp. AsyncLocalStorage carries sync-stack causality across await through 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 callback fs.realpath.native alias resolves within the mounted VFS and supports util.promisify.
  • src/shell/ (the worker's own process layer) — a browser worker cannot fork, so node:child_process is not a stub but an implementation: spawn starts the command in its own Web Worker — this same bundle, told by its first frame to be a shell process — and reports it through the ChildProcess surface the subprocess service consumes. The command runs off the host's thread, SIGKILL terminates 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 to node:child_process; ordinary commands use the package's evaluator and coreutils command table. The grammar is @yarnpkg/parsers' parseShell, while execSync/fork still 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: without preview-fixture it 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/web invokes both and supplies its statically bundled Worker. The opening init frame carries the base and ordered overlay URLs, the boot payload delivers the structured index-injection table, and applyIndexInjections executes it before the shell entry runs. Script preload rows are advisory and skipped because /plugins resources resolve only through the tunnel; loadBundle fetches each combo on first demand, embeds its tunnel-only source map as a Base64 data URL, and executes the script as a Blob. 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 transfer ReadableStream<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 as stream-uplink-item / stream-uplink-end frames into an unbounded TunnelUplink that drops items arriving after end: the page and its worker are one trusted same-origin boundary, so the Host inbox cap does not apply.

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 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. See the stylesheet exemption decision.


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' boot patch): it carries no Zstandard codec, so exported logs are .jsonl, never .jsonl.zstd.
  • node:dns/promises, node:vm, node:net, node:sqlite, node:worker_threads are structural stubs: every call reports its refusal on the console and throws. Rows needing native DNS, a real process, or realm isolation cannot run here.
  • Host package commands are unavailable: execa reports an explicit worker-host refusal; the preview cannot run pnpm or install native dependencies.
  • PTC Node programs are unavailable: the process shim exposes /dsh/bin/node as its executable identity so the provider can activate, but the Worker has neither a Node executable nor stripTypeScriptTypes. 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.
  • Filesystem watchers observe only the mounted VFS: image seeding is silent and the VFS has no symlinks or external writers. persistent, ref(), and unref() 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-only and workspace-write run the unchanged @deepseek-ai/node-addon-system/landlock-run JavaScript and launcher argv, but the process layer implements the logical landlock-run executable and enforces its grants on every shell filesystem request. full therefore 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 own lib/shell.js instead 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, sed accepts only substitution scripts, patterns are JavaScript regular expressions, and the command table holds coreutils only (no git, 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.

Runtime invariant: No companion is published. This package is pre-Cordis platform glue — the tree it boots runs the product packages' own invariants, and the assembly's contracts (image contract gate, tunnel refusals) fail loud at boot rather than drifting at run time.