@deepseek-ai/dsh-experimental-webworker-runtime 0.2.1-rc.3 → 0.2.2-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +4 -4
- package/README.md +7 -4
- package/README.zh.md +7 -4
- package/lib/client.js +104 -9
- package/lib/index.js +79 -4
- package/lib/types/client/client.d.ts +5 -0
- package/lib/types/node/builtin_modules/implemented/async_hooks.d.ts +3 -3
- package/lib/types/node/builtin_modules/implemented/buffer.d.ts +7 -0
- package/lib/types/node/builtin_modules/implemented/string_decoder.d.ts +12 -0
- package/lib/types/node/builtin_modules/mock/repl.d.ts +10 -0
- package/lib/types/node/external_packages/pi-coding-agent.d.ts +64 -0
- package/lib/types/node/globals/process.d.ts +1 -1
- package/lib/types/package-assets.d.ts +17 -0
- package/lib/types/string-decoder-types.d.ts +5 -0
- package/lib/types/transport/tunnel.d.ts +5 -0
- package/lib/worker.js +363 -87
- package/package.json +25 -24
package/README.i18n.yaml
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
en: 4e53164159b5ce41
|
|
16
16
|
zh: 89582dc0294d0776
|
|
17
17
|
/deepseek-ai-dsh-experimental-webworker-runtime/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: 24f1bb4adc4a9bb6
|
|
19
|
+
zh: 4a2caa4bfe2d8911
|
|
20
20
|
/deepseek-ai-dsh-experimental-webworker-runtime/model-experience:
|
|
21
21
|
en: 61fce9b4c7dc96e6
|
|
22
22
|
zh: dc2d931063a38327
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
en: ca75c51c89c2c9b0
|
|
25
25
|
zh: 64715dc7e6d02f71
|
|
26
26
|
/deepseek-ai-dsh-experimental-webworker-runtime/known-limitations-and-deferred-work:
|
|
27
|
-
en:
|
|
28
|
-
zh:
|
|
27
|
+
en: f7092d1e901219c1
|
|
28
|
+
zh: 0fc1938302ee428a
|
|
29
29
|
/deepseek-ai-dsh-experimental-webworker-runtime/known-limitations-and-deferred-work/dev-note:
|
|
30
30
|
en: e67f633dc6fe7773
|
|
31
31
|
zh: e88aeb617ffb74ea
|
package/README.md
CHANGED
|
@@ -29,11 +29,13 @@ Three build artifacts and one source-owned process layer:
|
|
|
29
29
|
|
|
30
30
|
- **`lib/index.js` (assembly library)** — `createWorkerHost`/`startWorkerHost` mount the base image and any ordered data overlays (`storage/`), register the image's lazy archives, 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 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, the `lowered` wrapper-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 initializes `home/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 a `PluginPackages` service 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 from `PLATFORM_ENTRY_CONFIG`, keeps JSONL Session logs plaintext. ConfigEditor persists overrides in the profile patch, reconciles each write, and Loader commits live values. The Worker commits `appReady` once the tunnel serves, so HMR loads and applies profile changes live; the Worker's ES module cache stays empty and the `process` shim 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 the `execa` refusal. Configuration edits last for the current Worker lifetime.
|
|
31
31
|
- **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 with `ERR_DSH_VFS_DEFERRED` naming 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.
|
|
32
|
-
- **`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. `node-addon-require-builtin` answers the Node v24.18.0 internal modules that the vendored Loader, app-boot, and HMR read without `--expose-internals`: the cascaded ESM loader's `resolveSync` and `import` over the Worker loader, with Node's `ERR_MODULE_NOT_FOUND` and `ERR_PACKAGE_PATH_NOT_EXPORTED` codes, and an empty `loadCache`; and the CommonJS loader, package reader, `modules` binding, and resolve-cache members that HMR replaces, which refuse when called. `process.versions.node` reports `24.18.0`, so `ModuleLoader.fromInternal()` and the Loader's `internal` reach that cascaded loader. `node:path.win32.isAbsolute` and `node:util.inherits` follow Node; other `win32` members throw. 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, 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, and `FileHandle.chmod()` updates the opened file identity; `readable-stream` supplies `node:stream/promises` completion and pipeline helpers and 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`.
|
|
32
|
+
- **`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. `node-addon-require-builtin` answers the Node v24.18.0 internal modules that the vendored Loader, app-boot, and HMR read without `--expose-internals`: the cascaded ESM loader's `resolveSync` and `import` over the Worker loader, with Node's `ERR_MODULE_NOT_FOUND` and `ERR_PACKAGE_PATH_NOT_EXPORTED` codes, and an empty `loadCache`; and the CommonJS loader, package reader, `modules` binding, and resolve-cache members that HMR replaces, which refuse when called. `process.versions.node` reports `24.18.0`, so `ModuleLoader.fromInternal()` and the Loader's `internal` reach that cascaded loader. `node:path.win32.isAbsolute` and `node:util.inherits` follow Node; other `win32` members throw. 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; Node’s `global` alias references the same `globalThis` object for image-loaded packages. 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, 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, and `FileHandle.chmod()` updates the opened file identity; `readable-stream` supplies `node:stream/promises` completion and pipeline helpers and the stream state machine used by file streams and unchanged image packages such as Chokidar and readdirp. The maintained `string_decoder` package supplies `node:string_decoder`, retaining incomplete multibyte characters between chunks. 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`.
|
|
33
33
|
- **`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`; `xdg-open` sends 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`, while `execSync`/`fork` still refuse because they need a real process.
|
|
34
|
-
- **`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 worker's unsolicited `view-text` frame 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 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.
|
|
34
|
+
- **`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. Before the shell entry runs, the page reads `GET /__assets__`, which the tunnel answers with an uncompressed tar of every package asset in the image, and supplies `resolveAsset`, which maps each `plugins/<package>/assets/<path>` reference a bundle passes to `require.asset` to a Blob URL of those bytes. The worker's unsolicited `view-text` frame 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 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.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Callbacks and timers registered under an explicit root context retain its empty stores, so they cannot inherit an unrelated transaction.
|
|
37
|
+
|
|
38
|
+
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. A second root Session, `preview-turn-spacing`, carries three multi-step Turns whose bodies compare the tightened process-row spacing with the reply clearance. The [packer](../webworker-packer/README.md) 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.
|
|
37
39
|
|
|
38
40
|
The [built-bundle import sweep](tests/compile/transform-corpus-check.ts) 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.
|
|
39
41
|
|
|
@@ -53,11 +55,12 @@ None; this package neither assembles nor sends a provider request.
|
|
|
53
55
|
<a id="known-limitations-and-deferred-work"></a>
|
|
54
56
|
|
|
55
57
|
- **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`.
|
|
56
|
-
- **`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 with Node's `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` code. Rows needing native DNS, a real process, or realm isolation cannot run here.
|
|
58
|
+
- **`node:dns/promises`, `node:vm`, `node:repl`, `node:net`, `node:sqlite`, `node:worker_threads` are structural stubs**: imports remain available for image packaging; every exposed call reports its refusal on the console and throws with Node's `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` code. Rows needing native DNS, a real process, a Node REPL, or realm isolation cannot run here.
|
|
57
59
|
- **`node:assert/strict` supports truthiness assertions only**: for falsy values, the callable default and `ok` throw `ERR_ASSERTION` with the supplied message or generic text, or rethrow a supplied Error unchanged. Other assertion APIs are absent.
|
|
58
60
|
- **Native TLS listeners require a Node Host**: the worker's `node:https.createServer` rejects activation rather than substituting plaintext. The preview's internal HTTP tunnel does not bind sockets; browser-facing HTTPS belongs to its deployment server.
|
|
59
61
|
- **Desktop product telemetry is unavailable**: `got.post` reports 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.
|
|
60
62
|
- **Host package commands are unavailable**: `execa` reports an explicit worker-host refusal; the preview cannot run pnpm, install plugins, or install native dependencies.
|
|
63
|
+
- **Pi agent execution is unavailable**: the `@earendil-works/pi-coding-agent` replacement permits metadata imports and rejects SDK calls with `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM`. The image excludes its native CLI and terminal UI dependencies. Native DSH retains the full OAR runtime registry.
|
|
61
64
|
- **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.
|
|
62
65
|
- **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.
|
|
63
66
|
- **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.
|
package/README.zh.md
CHANGED
|
@@ -29,11 +29,13 @@ kind: "package-library"
|
|
|
29
29
|
|
|
30
30
|
- **`lib/index.js`(装配库)**——`createWorkerHost`/`startWorkerHost` 挂载基础镜像和按序排列的数据 overlays(`storage/`)、登记镜像的懒加载归档、安装模块加载器(`module-system/`)与 `process` shim、经镜像自带的 `dsh-app-boot` 启动插件树,并把服务 seam 交给隧道。Overlay 只能替换 `home/` 与 `workspace/` 下的文件,不能替换基础 manifest 或模块。镜像布局契约(`image-layout.ts`:虚拟根、manifest 路径、懒加载归档目录、空目录)与 manifest(`image-manifest.ts`:profile、安装清单、`lowered` 包装契约门、懒加载归档)与 packer 共享。Worker 按启动器的方式启动 manifest 指定的随附 profile:镜像里的 app-boot 用随附模板初始化 `home/profiles/<profile>/`,从镜像单独携带的安装 manifest 解析 bundle,并合成各 bundle 层、profile patch 与 home patch。profile 上下文、启动环境快照,以及不带运行时 resolution 的 `PluginPackages` 服务都按启动器的方式提供;模块解析由 Worker 加载器负责。profile 上下文不带命令行 overlay;packer 按 `PLATFORM_ENTRY_CONFIG` 写入镜像的 home patch(`home/cordis.patch.yml`),使 JSONL Session 日志保持明文。ConfigEditor 将覆盖值持久化到 profile patch,协调每次写入,Loader 提交实时值。Worker 在隧道开始服务后提交 `appReady`,因此 HMR 可以加载 profile 变更并实时应用;Worker 的 ES module 缓存始终为空,`process` shim 也不报告入口脚本,因此 HMR 不比较、也不替换已加载的模块。插件管理可以列出并开关安装提供的 optional bundle,其包操作会因 `execa` 拒绝而失败。配置修改在当前 Worker 生命周期内有效。
|
|
31
31
|
- **懒加载归档**——manifest 按路径和大小登记 packer 移出基础镜像的每个页面 chunk,并给出相对镜像 URL 的归档名。存在性、stat 和目录列表立即作答;第一次异步读取(`fs.promises.readFile`、`fs.promises.open`、`fs.createReadStream`、`fs.promises.cp`)为所有等待中的读取方只拉取并解压一次该归档,按登记内容校验后挂载其文件,不发布 mutation。拉取失败时本次读取被拒绝,文件保持登记状态,下次读取会重新拉取。在此之前,对已登记文件的同步读取、写入、追加、截断、chmod、rename 和 link 会以 `ERR_DSH_VFS_DEFERRED` 失败,错误中写明归档名;unlink 和递归删除会移除登记,再次 seed 该路径会替换登记。归档名由内容摘要派生,拉取时只校验登记的路径和大小,不校验摘要。登记了懒加载归档的镜像必须以 URL 传入。
|
|
32
|
-
- **`lib/worker.js`(worker 束)**——装配库加本包的 Node 兼容层,合成一个自含 ES module。模块代理表(`module-proxies.ts`)是唯一平台叉口:`node:*` 内建走 VFS、隧道和浏览器原语,浏览器做不到的走结构化 stub(调用即在 console 报错并抛出),native/binary 包则替换执行后端。`node:module` 在镜像 package 根之上提供 `createRequire().resolve` 与 `.resolve.paths()`,使未修改的包无需执行目标模块即可发现 manifest。`node-addon-require-builtin` 应答 vendor Loader、app-boot 和 HMR 在没有 `--expose-internals` 时读取的 Node v24.18.0 internal 模块:cascaded ESM loader 的 `resolveSync` 和 `import` 经 Worker 加载器完成,失败时带 Node 的 `ERR_MODULE_NOT_FOUND` 与 `ERR_PACKAGE_PATH_NOT_EXPORTED` 错误码,`loadCache` 为空;HMR 会替换的 CommonJS loader、package reader、`modules` binding 与 resolve cache 成员被调用时拒绝。`process.versions.node` 报告 `24.18.0`,因此 `ModuleLoader.fromInternal()` 和 Loader 的 `internal` 拿到的是这个 cascaded loader。`node:path.win32.isAbsolute` 与 `node:util.inherits` 与 Node 一致,其余 `win32` 成员抛出。`node:util` 的 JSON 数据比较器检查可枚举的自有属性,使 `__proto__` 等记录字段的准入判定与 Node 一致。全局 `process` shim 带有包括 `title` 在内的 Node 环境识别字段,避免 Worker 执行误入仅适用于 DOM
|
|
32
|
+
- **`lib/worker.js`(worker 束)**——装配库加本包的 Node 兼容层,合成一个自含 ES module。模块代理表(`module-proxies.ts`)是唯一平台叉口:`node:*` 内建走 VFS、隧道和浏览器原语,浏览器做不到的走结构化 stub(调用即在 console 报错并抛出),native/binary 包则替换执行后端。`node:module` 在镜像 package 根之上提供 `createRequire().resolve` 与 `.resolve.paths()`,使未修改的包无需执行目标模块即可发现 manifest。`node-addon-require-builtin` 应答 vendor Loader、app-boot 和 HMR 在没有 `--expose-internals` 时读取的 Node v24.18.0 internal 模块:cascaded ESM loader 的 `resolveSync` 和 `import` 经 Worker 加载器完成,失败时带 Node 的 `ERR_MODULE_NOT_FOUND` 与 `ERR_PACKAGE_PATH_NOT_EXPORTED` 错误码,`loadCache` 为空;HMR 会替换的 CommonJS loader、package reader、`modules` binding 与 resolve cache 成员被调用时拒绝。`process.versions.node` 报告 `24.18.0`,因此 `ModuleLoader.fromInternal()` 和 Loader 的 `internal` 拿到的是这个 cascaded loader。`node:path.win32.isAbsolute` 与 `node:util.inherits` 与 Node 一致,其余 `win32` 成员抛出。`node:util` 的 JSON 数据比较器检查可枚举的自有属性,使 `__proto__` 等记录字段的准入判定与 Node 一致。全局 `process` shim 带有包括 `title` 在内的 Node 环境识别字段,避免 Worker 执行误入仅适用于 DOM 的分支;镜像中加载的包通过 Node 的 `global` 别名访问同一个 `globalThis` 对象。pack 期解析器会把名称静态可知的模块请求报告给 packer 的可达性遍历,其中包括通过 `node:module` 或 `module` 具名导入在模块作用域直接发起的 `createRequire(import.meta.url)('pkg')` 调用。保存、经 CommonJS 获取或另设基准的 `createRequire` 调用需要镜像入口种子。VFS mutation 驱动 `node:fs` 的 callback、polling 和 promise watcher,监听回调运行在创建 watcher 时的异步上下文中,而不是写入方代码的上下文中;打开的 descriptor 在 rename、replacement 和 unlink 后仍保留文件身份与访问模式,只要文件名仍指向该文件,`FileHandle.stat({ bigint: true })` 报告的 device 与 inode 身份就与路径 stat 相同,`FileHandle.chmod()` 则更新打开文件身份的权限;`readable-stream` 提供 `node:stream/promises` 的完成等待与管道方法,以及文件流以及 Chokidar、readdirp 等未修改镜像包所用的流状态机。维护中的 `string_decoder` 包提供 `node:string_decoder`,在数据块之间保留不完整的多字节字符。AsyncLocalStorage 经 pack 时降低注入的 snapshot/restore 面在 `await` 间携带同步栈因果。worker 不带编译器:packer 未降低的镜像在挂载时被拒。回调式 `fs.realpath.native` 别名在已挂载的 VFS 内解析路径,并支持 `util.promisify`。
|
|
33
33
|
- **`src/shell/`(worker 自己的进程层)**——浏览器 worker 无法 fork,所以 `node:child_process` 不是 stub 而是实现:`spawn` 把命令放进它自己的 Web Worker——就是这同一个束,由首帧告诉它「你是 shell 进程」——并以 subprocess 服务消费的 `ChildProcess` 面报告结果。命令不占宿主线程,`SIGKILL` 不管它在干什么都能终止它,而它只能靠消息触达 VFS(由宿主应答这些帧)。Worker 平台 executable 在不替换 JavaScript 包、也不把具体实现耦合进 `node:child_process` 的情况下保持 Landlock 等 native 包协议;`xdg-open` 把一个 VFS 文本文件交给页面的只读查看器,因此 **打开配置文件** 等桌面打开操作会在页面内显示该文件;普通命令使用本包的求值器与 coreutils 命令表。语法来自 `@yarnpkg/parsers` 的 `parseShell`,而 `execSync`/`fork` 依然拒绝,因为它们需要真进程。
|
|
34
|
-
- **`lib/client.js`(页面半)**——启动分为相互独立的两段。`chooseWorkerHostSource({ image?, fixtureManifest? })` 可选地拥有 boot barrier 与 fixture manifest:没有 `preview-fixture` 时停在来源选择面板,合法 query 则直接选择;两条路径都返回按序排列的 overlays。`connectWorkerHost(worker, { image?, overlays? })` 仍是公开的基础运行态连接器;调用方跳过选择器时 overlay 列表为空。`apps/web` 调用这两段并提供静态打包的 Worker。开局 `init` 帧携带基础镜像与按序排列的 overlay URL,boot 载荷送达结构化 index 注入表,`applyIndexInjections` 在壳入口运行前逐行执行。脚本 preload 行只是提示,因此会被跳过:`/plugins` 资源只能经 tunnel 解析,`loadBundle` 会在首次需要时获取 combo、把仅 tunnel 可达的 sourcemap 内嵌为 Base64 data URL,再以 Blob
|
|
34
|
+
- **`lib/client.js`(页面半)**——启动分为相互独立的两段。`chooseWorkerHostSource({ image?, fixtureManifest? })` 可选地拥有 boot barrier 与 fixture manifest:没有 `preview-fixture` 时停在来源选择面板,合法 query 则直接选择;两条路径都返回按序排列的 overlays。`connectWorkerHost(worker, { image?, overlays? })` 仍是公开的基础运行态连接器;调用方跳过选择器时 overlay 列表为空。`apps/web` 调用这两段并提供静态打包的 Worker。开局 `init` 帧携带基础镜像与按序排列的 overlay URL,boot 载荷送达结构化 index 注入表,`applyIndexInjections` 在壳入口运行前逐行执行。脚本 preload 行只是提示,因此会被跳过:`/plugins` 资源只能经 tunnel 解析,`loadBundle` 会在首次需要时获取 combo、把仅 tunnel 可达的 sourcemap 内嵌为 Base64 data URL,再以 Blob 执行脚本。在 shell 入口运行之前,页面读取 `GET /__assets__`(隧道以镜像中所有包资源组成的未压缩 tar 回答),并提供 `resolveAsset`,把 bundle 传给 `require.asset` 的每个 `plugins/<package>/assets/<path>` 引用映射为这些字节的 Blob URL。worker 主动发出的 `view-text` 帧会打开页面的模态文本查看器。Tunnel 还暴露 fetch 形式的传输、独立文件上传载体和 API 客户端。请求帧通过结构化克隆保留 Blob 请求体,并转移 `ReadableStream<Uint8Array>` 的所有权。Host Worker 将两种请求体都逐块送入路由,因此通用文件上传不会在任何浏览器线程创建完整字节数组。Remote 流的上行以 `stream-uplink-item` / `stream-uplink-end` 帧进入没有上限的 `TunnelUplink`,`end` 之后到达的项被丢弃:页面与其 worker 是同一个可信的同源边界,Host 的 inbox 上限在此不适用。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
在显式根上下文中登记的回调和定时器保留其空 store,避免继承无关事务。
|
|
37
|
+
|
|
38
|
+
验收在 `apps/web/tests/preview-boot.e2e.ts`:静态服务真实构建页面,在 headless Chromium 里驱动 pre-boot 选择面板与 Worker 激活。空白选择验证首次启动;`vfs-example` overlay 提供普通 workspace 文件与明文 persistence 产物,无需模型请求即可验证 Workspace/Session 冷发现、工具呈现、subagent 导航和历史分页。showcase 混排空字符串、纯空白与非空推理块,并包含多行长命令和父路径文件链接;这些是人工构造的 fixture,不是真实模型输出。另一个根 Session `preview-turn-spacing` 含三个多步骤轮次,其正文用于对照收紧后的过程行间距与回复间距。[打包器](../webworker-packer/README.zh.md)在 Node 中根据已提交的历史日志准备当前代后继文件;这些源日志在 overlay 中逐字节保持不变。fixture 生成器负责验收测试的当前投影缓存。选择面板为 WebFS 保留独立的用户授权来源;该 provider 不读取内置 fixture。
|
|
37
39
|
|
|
38
40
|
[已构建 bundle 导入检查](tests/compile/transform-corpus-check.ts)在库构建后检查裸 Node 导入。Dockkit 例外接受 Node 针对任意样式表报告的未知 `.css` 扩展名错误,而不限定某一固定路径;其他扩展名、错误码或消息,以及意外成功的豁免导入,仍然报错。
|
|
39
41
|
|
|
@@ -53,11 +55,12 @@ kind: "package-library"
|
|
|
53
55
|
<a id="known-limitations-and-deferred-work"></a>
|
|
54
56
|
|
|
55
57
|
- **worker 组合写明文会话日志**(镜像 home patch 中的 `compression: 'none'`;ConfigEditor 会拒绝修改这一行的配置):不带 Zstandard 编解码器,导出日志是 `.jsonl`,不会是 `.jsonl.zstd`。
|
|
56
|
-
- **`node:dns/promises`、`node:vm`、`node:net`、`node:sqlite`、`node:worker_threads` 是结构化 stub
|
|
58
|
+
- **`node:dns/promises`、`node:vm`、`node:repl`、`node:net`、`node:sqlite`、`node:worker_threads` 是结构化 stub**:镜像打包可以解析这些导入;每个已暴露的调用都在 console 报告拒绝,并抛出带 Node `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` 错误码的错误。需要原生 DNS、真进程、Node REPL 或真 realm 隔离的行在此无法运行。
|
|
57
59
|
- **`node:assert/strict` 仅支持真值断言**:对于假值,可调用的默认导出和 `ok` 使用传入的消息或通用文本抛出 `ERR_ASSERTION`,或原样抛出传入的 Error。其他断言 API 未提供。
|
|
58
60
|
- **原生 TLS 监听器需要 Node Host**:worker 的 `node:https.createServer` 会拒绝激活,不会替换成明文。预览内部的 HTTP 隧道不绑定套接字;面向浏览器的 HTTPS 由部署服务器提供。
|
|
59
61
|
- **桌面产品埋点不可用**:`got.post` 明确报告 worker host 不支持该调用。浏览器镜像不包含 Got 及其 Node HTTP 依赖;预览组合不启用桌面上报。
|
|
60
62
|
- **宿主包管理命令不可用**:`execa` 明确报告 worker host 不支持该调用;预览无法运行 pnpm、安装插件或安装原生依赖。
|
|
63
|
+
- **Pi Agent 执行不可用**:`@earendil-works/pi-coding-agent` 替代模块允许元数据导入,并以 `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` 拒绝 SDK 调用。镜像不包含其原生 CLI 与终端 UI 依赖。原生 DSH 仍保留完整的 OAR 运行时注册表。
|
|
61
64
|
- **pi-ai 模型请求不可用**:替代实现暴露已安装提供方标识的固定子集,不包含模型,使适配器能够注册配置元数据。升级 pi-ai 时必须保持这些标识有效;推理入口明确报告 worker host 不支持该调用。
|
|
62
65
|
- **PTC Node 程序不可用**:process shim 用 `/dsh/bin/node` 表示可执行文件身份,使 provider 能够激活,但 Worker 既没有 Node 可执行文件,也没有 `stripTypeScriptTypes`。程序执行会在启动子进程前失败。
|
|
63
66
|
- **Office 转换需要 Node Host**:LibreOffice kit 的替代模块以 `unavailable` 拒绝创建转换器。浏览器镜像排除 kit 及其引擎依赖;Office 预览提示转换不可用。
|
package/lib/client.js
CHANGED
|
@@ -51,6 +51,76 @@ function parsePreviewFixtureManifest(value) {
|
|
|
51
51
|
fixtures
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
new TextEncoder();
|
|
55
|
+
const decoder = new TextDecoder();
|
|
56
|
+
const BLOCK = 512;
|
|
57
|
+
/** @returns The NUL-terminated string in one header field. */
|
|
58
|
+
function readField(header, offset, length) {
|
|
59
|
+
let end = offset;
|
|
60
|
+
while (end < offset + length && header[end] !== 0) end += 1;
|
|
61
|
+
return decoder.decode(header.subarray(offset, end));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse an uncompressed ustar archive.
|
|
65
|
+
*
|
|
66
|
+
* File bytes are subarray views into `archive`, not copies; callers own the
|
|
67
|
+
* aliasing. Entry kinds outside the written subset (links, PAX extensions)
|
|
68
|
+
* fail loud instead of being skipped.
|
|
69
|
+
* @param archive - Archive bytes.
|
|
70
|
+
* @returns Entries in archive order.
|
|
71
|
+
*/
|
|
72
|
+
function parseTar(archive) {
|
|
73
|
+
const entries = [];
|
|
74
|
+
let offset = 0;
|
|
75
|
+
while (offset + BLOCK <= archive.length) {
|
|
76
|
+
const header = archive.subarray(offset, offset + BLOCK);
|
|
77
|
+
if (header.every((byte) => byte === 0)) break;
|
|
78
|
+
const short = readField(header, 0, 100);
|
|
79
|
+
const prefix = readField(header, 345, 155);
|
|
80
|
+
const name = prefix === "" ? short : `${prefix}/${short}`;
|
|
81
|
+
const size = Number.parseInt(readField(header, 124, 12).trim() || "0", 8);
|
|
82
|
+
const mode = Number.parseInt(readField(header, 100, 8).trim() || "0", 8) & 511;
|
|
83
|
+
const typeflag = header[156];
|
|
84
|
+
const directory = typeflag === 53 || name.endsWith("/");
|
|
85
|
+
if (typeflag !== 48 && typeflag !== 0 && typeflag !== 53) throw new Error(`vfs tar: unsupported entry type ${String.fromCharCode(typeflag ?? 0)} for "${name}"`);
|
|
86
|
+
const dataStart = offset + BLOCK;
|
|
87
|
+
entries.push({
|
|
88
|
+
name,
|
|
89
|
+
bytes: archive.subarray(dataStart, dataStart + size),
|
|
90
|
+
directory,
|
|
91
|
+
mode
|
|
92
|
+
});
|
|
93
|
+
offset = dataStart + Math.ceil(size / BLOCK) * BLOCK;
|
|
94
|
+
}
|
|
95
|
+
return entries;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/package-assets.ts
|
|
99
|
+
/** Media type of each asset extension the Client build emits. */
|
|
100
|
+
const ASSET_MEDIA_TYPES = new Map([
|
|
101
|
+
[".svg", "image/svg+xml"],
|
|
102
|
+
[".png", "image/png"],
|
|
103
|
+
[".webp", "image/webp"],
|
|
104
|
+
[".jpg", "image/jpeg"],
|
|
105
|
+
[".jpeg", "image/jpeg"]
|
|
106
|
+
]);
|
|
107
|
+
/**
|
|
108
|
+
* Map each archived package asset to a Blob URL kept for the page's lifetime.
|
|
109
|
+
* @param archive - Archive from {@link packPackageAssets}.
|
|
110
|
+
* @returns The synchronous resolver behind `require.asset`: the Blob URL of a
|
|
111
|
+
* reference the image holds, otherwise the reference itself, which then fails as an unserved image.
|
|
112
|
+
*/
|
|
113
|
+
function packageAssetResolver(archive) {
|
|
114
|
+
const urls = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const entry of parseTar(archive)) {
|
|
116
|
+
const [, name, path] = /^plugins\/((?:@[^/]+\/)?[^/]+)\/assets\/(.+)$/u.exec(entry.name) ?? [];
|
|
117
|
+
if (name === void 0 || path === void 0) continue;
|
|
118
|
+
const reference = `plugins/${name}/assets/${path.split("/").map((segment) => encodeURIComponent(segment).replace(/[!'()*]/gu, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)).join("/")}`;
|
|
119
|
+
const extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
120
|
+
urls.set(reference, URL.createObjectURL(new Blob([new Uint8Array(entry.bytes)], { type: ASSET_MEDIA_TYPES.get(extension) ?? "" })));
|
|
121
|
+
}
|
|
122
|
+
return (reference) => urls.get(reference) ?? reference;
|
|
123
|
+
}
|
|
54
124
|
//#endregion
|
|
55
125
|
//#region src/client/text-viewer.ts
|
|
56
126
|
/** Read-only text viewer the page shows when the worker's `xdg-open` names a VFS file. */
|
|
@@ -143,6 +213,13 @@ function showTextViewer(path, text) {
|
|
|
143
213
|
}
|
|
144
214
|
//#endregion
|
|
145
215
|
//#region src/client/client.ts
|
|
216
|
+
/**
|
|
217
|
+
* Page half of the postMessage tunnel. It
|
|
218
|
+
* turns fetch-shaped calls into `req` frames and rebuilds Responses from the
|
|
219
|
+
* worker's `res` / `res-head`+`res-chunk`+`res-end` frames, so every consumer
|
|
220
|
+
* (boot payload, bundle transport, ApiClient, Typert RPC) speaks plain HTTP.
|
|
221
|
+
* The worker's unsolicited `view-text` frame opens the page's text viewer.
|
|
222
|
+
*/
|
|
146
223
|
/** Error carrying stream semantics across independently bundled Client code. */
|
|
147
224
|
var TunnelLogicalStreamError = class extends Error {
|
|
148
225
|
dshRemoteStreamFailure;
|
|
@@ -192,13 +269,21 @@ var LogicalStreamInbox = class {
|
|
|
192
269
|
const REFUSAL_STATUS = 500;
|
|
193
270
|
const encoder = new TextEncoder();
|
|
194
271
|
const SOURCE_MAP_TRAILER = /\/\/# sourceMappingURL=([^\r\n]+)\s*$/;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
272
|
+
/**
|
|
273
|
+
* Encode source-map bytes as a Base64 data URL with the browser's native encoder.
|
|
274
|
+
* DevTools cannot load a `blob:` source map, so the map must be inline.
|
|
275
|
+
*/
|
|
276
|
+
function sourceMapDataUrl(bytes) {
|
|
277
|
+
const reader = new FileReader();
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
reader.addEventListener("load", () => {
|
|
280
|
+
resolve(reader.result);
|
|
281
|
+
}, { once: true });
|
|
282
|
+
reader.addEventListener("error", () => {
|
|
283
|
+
reject(reader.error ?? /* @__PURE__ */ new Error("source map encoding failed"));
|
|
284
|
+
}, { once: true });
|
|
285
|
+
reader.readAsDataURL(new Blob([bytes], { type: "application/json;charset=utf-8" }));
|
|
286
|
+
});
|
|
202
287
|
}
|
|
203
288
|
/** Replace a tunnel-only map reference with a self-contained Base64 data URL. */
|
|
204
289
|
async function localizeSourceMap(source, bundleUrl, fetch) {
|
|
@@ -207,7 +292,7 @@ async function localizeSourceMap(source, bundleUrl, fetch) {
|
|
|
207
292
|
try {
|
|
208
293
|
const response = await fetch(new URL(match[1], new URL(bundleUrl, globalThis.location.origin)));
|
|
209
294
|
if (!response.ok) return source.replace(SOURCE_MAP_TRAILER, "");
|
|
210
|
-
const dataUrl =
|
|
295
|
+
const dataUrl = await sourceMapDataUrl(await response.arrayBuffer());
|
|
211
296
|
return source.replace(SOURCE_MAP_TRAILER, `//# sourceMappingURL=${dataUrl}`);
|
|
212
297
|
} catch {
|
|
213
298
|
return source.replace(SOURCE_MAP_TRAILER, "");
|
|
@@ -448,6 +533,15 @@ var WorkerTunnel = class {
|
|
|
448
533
|
return await response.json();
|
|
449
534
|
}
|
|
450
535
|
/**
|
|
536
|
+
* Read every package asset of the image and map each reference to a Blob URL.
|
|
537
|
+
* @returns The synchronous resolver the module system calls for `require.asset`.
|
|
538
|
+
*/
|
|
539
|
+
async packageAssets() {
|
|
540
|
+
const response = await this.fetch("/__assets__");
|
|
541
|
+
if (!response.ok) throw new Error(`web-preview tunnel: package assets failed with HTTP ${String(response.status)}: ${await response.text()}`);
|
|
542
|
+
return packageAssetResolver(new Uint8Array(await response.arrayBuffer()));
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
451
545
|
* `loadBundle` seam: take one client bundle through the tunnel and execute it
|
|
452
546
|
* as a classic script, exactly like the shell's same-origin `<script src>`.
|
|
453
547
|
* The image packs each bundle with a trailing `sourceURL` naming its image
|
|
@@ -956,11 +1050,12 @@ async function connectWorkerHost(worker, options) {
|
|
|
956
1050
|
try {
|
|
957
1051
|
const tunnel = new WorkerTunnel(worker);
|
|
958
1052
|
tunnel.init(new URL(options?.image ?? "vfs-image.tar.gz", document.baseURI).href, (options?.overlays ?? []).map((overlay) => new URL(overlay, document.baseURI).href));
|
|
959
|
-
const payload = await tunnel.bootPayload();
|
|
1053
|
+
const [payload, resolveAsset] = await Promise.all([tunnel.bootPayload(), tunnel.packageAssets()]);
|
|
960
1054
|
globalThis.__DSH_TRANSPORT__ = {
|
|
961
1055
|
fetch: (input, init) => tunnel.fetch(input, init),
|
|
962
1056
|
openStream: (endpoint, payload, signal, uplink) => tunnel.open(endpoint, payload, signal, uplink),
|
|
963
1057
|
loadBundle: (url) => tunnel.loadBundle(url),
|
|
1058
|
+
resolveAsset,
|
|
964
1059
|
ownsHost: true
|
|
965
1060
|
};
|
|
966
1061
|
globalThis.__DSH_FILE_UPLOAD__ = { fetch: (input, init) => tunnel.fetch(input, init) };
|
package/lib/index.js
CHANGED
|
@@ -878,9 +878,11 @@ const MODULE_PROXIES = {
|
|
|
878
878
|
"dns/promises": "./node/builtin_modules/mock/dns/promises.ts",
|
|
879
879
|
"node:net": "./node/builtin_modules/mock/net.ts",
|
|
880
880
|
"node:stream": "./node/builtin_modules/implemented/stream.ts",
|
|
881
|
+
"node:string_decoder": "./node/builtin_modules/implemented/string_decoder.ts",
|
|
881
882
|
"node:stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
|
|
882
883
|
"stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
|
|
883
884
|
"node:vm": "./node/builtin_modules/mock/vm.ts",
|
|
885
|
+
"node:repl": "./node/builtin_modules/mock/repl.ts",
|
|
884
886
|
"node:worker_threads": "./node/builtin_modules/mock/worker_threads.ts",
|
|
885
887
|
"node:sqlite": "./node/builtin_modules/mock/sqlite.ts",
|
|
886
888
|
"@deepseek-ai/libreoffice-kit": "./node/external_packages/libreoffice-kit.ts",
|
|
@@ -892,6 +894,7 @@ const MODULE_PROXIES = {
|
|
|
892
894
|
"got": "./node/external_packages/got.ts",
|
|
893
895
|
"@vscode/ripgrep": "./node/external_packages/ripgrep.ts",
|
|
894
896
|
"@earendil-works/pi-ai": "./node/external_packages/pi-ai.ts",
|
|
897
|
+
"@earendil-works/pi-coding-agent": "./node/external_packages/pi-coding-agent.ts",
|
|
895
898
|
"node-addon-require-builtin": "./node/external_packages/node-addon-require-builtin.ts",
|
|
896
899
|
"ws": "./node/external_packages/ws.ts"
|
|
897
900
|
};
|
|
@@ -908,6 +911,7 @@ const MODULE_PROXY_PREFIXES = { "@earendil-works/pi-ai/": "./node/external_packa
|
|
|
908
911
|
/** Package or subpath specifiers served from the worker bundle instead of the VFS. */
|
|
909
912
|
const REPLACED_EXTERNAL_PACKAGES = [
|
|
910
913
|
"@earendil-works/pi-ai",
|
|
914
|
+
"@earendil-works/pi-coding-agent",
|
|
911
915
|
"@vscode/ripgrep",
|
|
912
916
|
"@deepseek-ai/libreoffice-kit",
|
|
913
917
|
"@deepseek-ai/node-addon-system/flock",
|
|
@@ -1564,6 +1568,8 @@ function lowerModuleSource(options) {
|
|
|
1564
1568
|
*
|
|
1565
1569
|
* - `GET /__boot__` answers from tunnel glue, never from the host API surface,
|
|
1566
1570
|
* because the page needs the boot payload before its Cordis tree exists.
|
|
1571
|
+
* `GET /__assets__` answers the same way with every package asset of the
|
|
1572
|
+
* image, which the page maps before any Client bundle runs.
|
|
1567
1573
|
* - Privileged `/api` methods take that same direct entry. The method set is not
|
|
1568
1574
|
* restated here: a 401 or 403 from the route lane is retried on the direct
|
|
1569
1575
|
* lane because the page owns the worker and needs no network authentication.
|
|
@@ -1965,6 +1971,10 @@ var TunnelServer = class {
|
|
|
1965
1971
|
this.serveBoot(frame, sink);
|
|
1966
1972
|
return;
|
|
1967
1973
|
}
|
|
1974
|
+
if (path === "/__assets__") {
|
|
1975
|
+
this.servePackageAssets(frame, sink);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1968
1978
|
if (path.startsWith(`/api/`)) {
|
|
1969
1979
|
await this.serveApi(frame, routed, path, sink);
|
|
1970
1980
|
return;
|
|
@@ -2030,6 +2040,24 @@ var TunnelServer = class {
|
|
|
2030
2040
|
}
|
|
2031
2041
|
buffered.flushTo(sink);
|
|
2032
2042
|
}
|
|
2043
|
+
servePackageAssets(frame, sink) {
|
|
2044
|
+
if (this.seams === void 0) throw new Error("webworker tunnel: package assets requested before the host tree is serving");
|
|
2045
|
+
if (frame.method !== "GET") {
|
|
2046
|
+
sink.end({
|
|
2047
|
+
status: 405,
|
|
2048
|
+
headers: { allow: "GET" }
|
|
2049
|
+
});
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
sink.end({
|
|
2053
|
+
status: 200,
|
|
2054
|
+
headers: {
|
|
2055
|
+
"content-type": "application/x-tar",
|
|
2056
|
+
"cache-control": "no-store"
|
|
2057
|
+
},
|
|
2058
|
+
body: this.seams.packageAssets()
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2033
2061
|
serveBoot(frame, sink) {
|
|
2034
2062
|
if (this.seams === void 0) throw new Error("webworker tunnel: boot payload requested before the host tree is serving");
|
|
2035
2063
|
if (frame.method !== "GET") {
|
|
@@ -2144,7 +2172,7 @@ const EXEC_PATH = "/dsh/bin/node";
|
|
|
2144
2172
|
/** Node release whose internal-module members `node-addon-require-builtin` follows in the Worker. */
|
|
2145
2173
|
const NODE_VERSION = "24.18.0";
|
|
2146
2174
|
/**
|
|
2147
|
-
* Publish `globalThis.process
|
|
2175
|
+
* Publish `globalThis.process` and Node's `global` alias for the worker lifetime.
|
|
2148
2176
|
*
|
|
2149
2177
|
* `versions.node` names the Node release the Worker's `node-addon-require-builtin` internals follow, so Cordis's
|
|
2150
2178
|
* `ModuleLoader.fromInternal()` reaches them the way it reaches Node's; `execArgv` stays empty, without
|
|
@@ -2213,6 +2241,7 @@ function installProcessGlobal(options) {
|
|
|
2213
2241
|
console.warn(`webworker process: exit(${String(code ?? 0)}) requested; the worker keeps running`);
|
|
2214
2242
|
}
|
|
2215
2243
|
};
|
|
2244
|
+
globalThis.global = globalThis;
|
|
2216
2245
|
globalThis.process = shim;
|
|
2217
2246
|
return shim;
|
|
2218
2247
|
}
|
|
@@ -3597,6 +3626,50 @@ function requireActiveVfs() {
|
|
|
3597
3626
|
return active;
|
|
3598
3627
|
}
|
|
3599
3628
|
//#endregion
|
|
3629
|
+
//#region src/package-assets.ts
|
|
3630
|
+
/**
|
|
3631
|
+
* Package assets for the preview page. A Client bundle names each image as a
|
|
3632
|
+
* document-relative reference, `plugins/<package>/assets/<path>`, and resolves
|
|
3633
|
+
* it through the module loader's synchronous `require.asset`. The page of the
|
|
3634
|
+
* Worker preview cannot request such a URL itself, so the Worker hands it every
|
|
3635
|
+
* asset the image holds in one archive and the page maps each reference to a
|
|
3636
|
+
* Blob URL before any bundle runs.
|
|
3637
|
+
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/package-assets
|
|
3638
|
+
*/
|
|
3639
|
+
/** Entries of one directory. */
|
|
3640
|
+
function entriesOf(vfs, directory) {
|
|
3641
|
+
return vfs.readdirSync(directory, { withFileTypes: true });
|
|
3642
|
+
}
|
|
3643
|
+
/** Files under one directory, as `/`-separated paths relative to it. */
|
|
3644
|
+
function filesBelow(vfs, directory, prefix = "") {
|
|
3645
|
+
return entriesOf(vfs, directory).flatMap((entry) => entry.isDirectory() ? filesBelow(vfs, join(directory, entry.name), `${prefix}${entry.name}/`) : entry.isFile() ? [`${prefix}${entry.name}`] : []);
|
|
3646
|
+
}
|
|
3647
|
+
/** Installed package directories under `node_modules`, scoped ones included. */
|
|
3648
|
+
function packageDirectories(vfs, modules) {
|
|
3649
|
+
if (!vfs.existsSync(modules)) return [];
|
|
3650
|
+
return entriesOf(vfs, modules).filter((entry) => entry.isDirectory()).flatMap((entry) => entry.name.startsWith("@") ? entriesOf(vfs, join(modules, entry.name)).filter((scoped) => scoped.isDirectory()).map((scoped) => join(modules, entry.name, scoped.name)) : [join(modules, entry.name)]);
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Archive every file a package of the image publishes through `"./assets/*": "./lib/assets/*"`.
|
|
3654
|
+
* @param vfs - Mounted image filesystem.
|
|
3655
|
+
* @param root - Image root holding `node_modules`.
|
|
3656
|
+
* @returns An uncompressed tar whose entries are named `plugins/<package>/assets/<path>` with the path unencoded,
|
|
3657
|
+
* so every name fits the ustar limits the base image already met.
|
|
3658
|
+
*/
|
|
3659
|
+
function packPackageAssets(vfs, root) {
|
|
3660
|
+
const files = {};
|
|
3661
|
+
for (const directory of packageDirectories(vfs, join(root, "node_modules"))) {
|
|
3662
|
+
const manifestPath = join(directory, "package.json");
|
|
3663
|
+
if (!vfs.existsSync(manifestPath)) continue;
|
|
3664
|
+
const manifest = JSON.parse(vfs.readFileSync(manifestPath, "utf8"));
|
|
3665
|
+
if (typeof manifest.name !== "string" || manifest.exports?.["./assets/*"] !== "./lib/assets/*") continue;
|
|
3666
|
+
const assets = join(directory, "lib", "assets");
|
|
3667
|
+
if (!vfs.existsSync(assets)) continue;
|
|
3668
|
+
for (const path of filesBelow(vfs, assets)) files[`plugins/${manifest.name}/assets/${path}`] = vfs.readFileSync(join(assets, path));
|
|
3669
|
+
}
|
|
3670
|
+
return packTar(files);
|
|
3671
|
+
}
|
|
3672
|
+
//#endregion
|
|
3600
3673
|
//#region src/image-manifest.ts
|
|
3601
3674
|
/**
|
|
3602
3675
|
* The base image's manifest: what the packer records about one image and what
|
|
@@ -3769,7 +3842,8 @@ function createWorkerHost(options) {
|
|
|
3769
3842
|
const peer = connection.browserPeer;
|
|
3770
3843
|
tunnel.serve({
|
|
3771
3844
|
directFetch: (request) => handler.fetch(request, peer),
|
|
3772
|
-
bootPayload: () => readBootPayload(ctx),
|
|
3845
|
+
bootPayload: () => readBootPayload(ctx, peer),
|
|
3846
|
+
packageAssets: () => packPackageAssets(mounted, root),
|
|
3773
3847
|
openStream: (endpoint, payload, uplink, signal) => typertGateway.wireStream.open(endpoint, payload, uplink, peer, signal),
|
|
3774
3848
|
streamFailure: typertGateway.wireStream.failure
|
|
3775
3849
|
});
|
|
@@ -3911,12 +3985,13 @@ function prepareProfile(appBoot, vfs, manifest, root, env) {
|
|
|
3911
3985
|
* from the in-process webserver service, never from the API surface, because
|
|
3912
3986
|
* the page has no Cordis tree yet.
|
|
3913
3987
|
* @param ctx - Booted host context.
|
|
3988
|
+
* @param peer - the browser Peer the page speaks for.
|
|
3914
3989
|
* @returns Boot payload for `GET /__boot__`.
|
|
3915
3990
|
*/
|
|
3916
|
-
function readBootPayload(ctx) {
|
|
3991
|
+
function readBootPayload(ctx, peer) {
|
|
3917
3992
|
const webServer = ctx.get("webServer");
|
|
3918
3993
|
if (webServer === void 0) throw new Error("webworker host: no webServer service, so the page cannot receive its boot injections");
|
|
3919
|
-
return { injections: webServer.collectIndexInjections() };
|
|
3994
|
+
return { injections: webServer.collectIndexInjections(peer) };
|
|
3920
3995
|
}
|
|
3921
3996
|
//#endregion
|
|
3922
3997
|
//#region src/fixture-manifest.ts
|
|
@@ -72,6 +72,11 @@ export declare class WorkerTunnel {
|
|
|
72
72
|
* @returns The payload the page applies before the client tree loads.
|
|
73
73
|
*/
|
|
74
74
|
bootPayload(): Promise<BootPayload>;
|
|
75
|
+
/**
|
|
76
|
+
* Read every package asset of the image and map each reference to a Blob URL.
|
|
77
|
+
* @returns The synchronous resolver the module system calls for `require.asset`.
|
|
78
|
+
*/
|
|
79
|
+
packageAssets(): Promise<(reference: string) => string>;
|
|
75
80
|
/**
|
|
76
81
|
* `loadBundle` seam: take one client bundle through the tunnel and execute it
|
|
77
82
|
* as a classic script, exactly like the shell's same-origin `<script src>`.
|
|
@@ -51,7 +51,7 @@ export declare class AsyncLocalStorage<T> {
|
|
|
51
51
|
/**
|
|
52
52
|
* Copy every live instance's current store. Not part of the Node face: this is
|
|
53
53
|
* the shim's own mechanism, kept in the class so the overlay stays private.
|
|
54
|
-
* @returns the snapshot, or undefined when no instance has a store.
|
|
54
|
+
* @returns the snapshot, or undefined when no instance has a store or explicit context.
|
|
55
55
|
*/
|
|
56
56
|
static captureContext(): AsyncContextSnapshot | undefined;
|
|
57
57
|
/**
|
|
@@ -90,8 +90,8 @@ export type AsyncContextSnapshot = readonly CapturedStore[];
|
|
|
90
90
|
export type AmbientSnapshot = readonly CapturedStore[];
|
|
91
91
|
/**
|
|
92
92
|
* Copy every live instance's current store.
|
|
93
|
-
* @returns the snapshot, or undefined when no instance has a store
|
|
94
|
-
* layer then wraps nothing and callbacks inherit the stack top).
|
|
93
|
+
* @returns the snapshot, or undefined when no instance has a store or explicit
|
|
94
|
+
* context (the hook layer then wraps nothing and callbacks inherit the stack top).
|
|
95
95
|
*/
|
|
96
96
|
export declare function captureAsyncContext(): AsyncContextSnapshot | undefined;
|
|
97
97
|
/**
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* `node:buffer` for the worker, backed by the `buffer` npm package (feross), and
|
|
3
3
|
* the matching `globalThis.Buffer` install. Node code treats Buffer as ambient,
|
|
4
4
|
* so the global must exist before any host module evaluates.
|
|
5
|
+
*
|
|
6
|
+
* Loading this module replaces `Buffer.from`, `Buffer.prototype.toString`, and
|
|
7
|
+
* `toLocaleString` on the package's class, so every Buffer user in the Worker,
|
|
8
|
+
* including bundled third-party code, gets the same conversions. Whole-buffer
|
|
9
|
+
* UTF-8 conversions use `TextEncoder` and `TextDecoder` with Node's U+FFFD
|
|
10
|
+
* replacement and BOM retention; other encodings and ranged `toString` calls
|
|
11
|
+
* keep the package's implementation.
|
|
5
12
|
*/
|
|
6
13
|
import { Buffer, kMaxLength } from 'buffer';
|
|
7
14
|
export { Buffer, kMaxLength };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Node string decoding backed by the maintained npm implementation used by readable-stream. */
|
|
2
|
+
import '../../../string-decoder-types.ts';
|
|
3
|
+
import { StringDecoder } from 'string_decoder/';
|
|
4
|
+
export { StringDecoder };
|
|
5
|
+
/** CommonJS interop marker consumed by the worker module loader. */
|
|
6
|
+
export declare const __esModule = true;
|
|
7
|
+
/** CommonJS-compatible namespace for default imports. */
|
|
8
|
+
declare const _default: {
|
|
9
|
+
StringDecoder: typeof StringDecoder;
|
|
10
|
+
};
|
|
11
|
+
export default _default;
|
|
12
|
+
//# sourceMappingURL=string_decoder.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** REPL construction is unavailable in the browser worker. */
|
|
2
|
+
export declare const start: typeof import('node:repl').start;
|
|
3
|
+
/** CommonJS interop marker consumed by the Worker module loader. */
|
|
4
|
+
export declare const __esModule = true;
|
|
5
|
+
/** CommonJS export for the REPL member used by the Node PTC evaluator. */
|
|
6
|
+
declare const _default: {
|
|
7
|
+
start: typeof import("repl").start;
|
|
8
|
+
};
|
|
9
|
+
export default _default;
|
|
10
|
+
//# sourceMappingURL=repl.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Agent directory discovery (unavailable). */
|
|
2
|
+
export declare const getAgentDir: (...args: never[]) => never;
|
|
3
|
+
/** Theme initialization (unavailable). */
|
|
4
|
+
export declare const initTheme: (...args: never[]) => never;
|
|
5
|
+
/** Agent session service creation (unavailable). */
|
|
6
|
+
export declare const createAgentSessionServices: (...args: never[]) => never;
|
|
7
|
+
/** Agent session creation from services (unavailable). */
|
|
8
|
+
export declare const createAgentSessionFromServices: (...args: never[]) => never;
|
|
9
|
+
/** Bash tool definition creation (unavailable). */
|
|
10
|
+
export declare const createBashToolDefinition: (...args: never[]) => never;
|
|
11
|
+
/** MCP extension creation (unavailable). */
|
|
12
|
+
export declare const createMcpExtension: (...args: never[]) => never;
|
|
13
|
+
/** Tool definition (unavailable). */
|
|
14
|
+
export declare const defineTool: (...args: never[]) => never;
|
|
15
|
+
/** CLI model selection (unavailable). */
|
|
16
|
+
export declare const resolveCliModel: (...args: never[]) => never;
|
|
17
|
+
/** Model registry construction (unavailable). */
|
|
18
|
+
export declare const ModelRegistry: (...args: never[]) => never;
|
|
19
|
+
/** Project trust store construction (unavailable). */
|
|
20
|
+
export declare const ProjectTrustStore: (...args: never[]) => never;
|
|
21
|
+
/** Model runtime construction and creation (unavailable). */
|
|
22
|
+
export declare const ModelRuntime: ((...args: never[]) => never) & {
|
|
23
|
+
create: (...args: never[]) => never;
|
|
24
|
+
};
|
|
25
|
+
/** Settings manager construction and creation (unavailable). */
|
|
26
|
+
export declare const SettingsManager: ((...args: never[]) => never) & {
|
|
27
|
+
create: (...args: never[]) => never;
|
|
28
|
+
};
|
|
29
|
+
/** Session manager construction and storage operations (unavailable). */
|
|
30
|
+
export declare const SessionManager: ((...args: never[]) => never) & {
|
|
31
|
+
create: (...args: never[]) => never;
|
|
32
|
+
open: (...args: never[]) => never;
|
|
33
|
+
inMemory: (...args: never[]) => never;
|
|
34
|
+
list: (...args: never[]) => never;
|
|
35
|
+
};
|
|
36
|
+
/** CommonJS interop marker: the worker loader hands `default` to default imports. */
|
|
37
|
+
export declare const __esModule = true;
|
|
38
|
+
/** CommonJS default export: the members `require()` hands a caller of this module. */
|
|
39
|
+
declare const _default: {
|
|
40
|
+
getAgentDir: (...args: never[]) => never;
|
|
41
|
+
initTheme: (...args: never[]) => never;
|
|
42
|
+
createAgentSessionServices: (...args: never[]) => never;
|
|
43
|
+
createAgentSessionFromServices: (...args: never[]) => never;
|
|
44
|
+
createBashToolDefinition: (...args: never[]) => never;
|
|
45
|
+
createMcpExtension: (...args: never[]) => never;
|
|
46
|
+
defineTool: (...args: never[]) => never;
|
|
47
|
+
resolveCliModel: (...args: never[]) => never;
|
|
48
|
+
ModelRegistry: (...args: never[]) => never;
|
|
49
|
+
ProjectTrustStore: (...args: never[]) => never;
|
|
50
|
+
ModelRuntime: ((...args: never[]) => never) & {
|
|
51
|
+
create: (...args: never[]) => never;
|
|
52
|
+
};
|
|
53
|
+
SettingsManager: ((...args: never[]) => never) & {
|
|
54
|
+
create: (...args: never[]) => never;
|
|
55
|
+
};
|
|
56
|
+
SessionManager: ((...args: never[]) => never) & {
|
|
57
|
+
create: (...args: never[]) => never;
|
|
58
|
+
open: (...args: never[]) => never;
|
|
59
|
+
inMemory: (...args: never[]) => never;
|
|
60
|
+
list: (...args: never[]) => never;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export default _default;
|
|
64
|
+
//# sourceMappingURL=pi-coding-agent.d.ts.map
|
|
@@ -66,7 +66,7 @@ export interface ProcessShim {
|
|
|
66
66
|
exit(code?: number): void;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Publish `globalThis.process
|
|
69
|
+
* Publish `globalThis.process` and Node's `global` alias for the worker lifetime.
|
|
70
70
|
*
|
|
71
71
|
* `versions.node` names the Node release the Worker's `node-addon-require-builtin` internals follow, so Cordis's
|
|
72
72
|
* `ModuleLoader.fromInternal()` reaches them the way it reaches Node's; `execArgv` stays empty, without
|