@deepseek-ai/dsh-experimental-webworker-runtime 0.1.6-alpha.1 → 0.1.7-alpha.1
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 +2 -2
- package/README.md +6 -4
- package/README.zh.md +6 -4
- package/lib/client.js +78 -45
- package/lib/index.js +126 -37
- package/lib/types/client/apply-injections.d.ts +2 -14
- package/lib/types/client/client.d.ts +18 -2
- package/lib/types/index.d.ts +1 -1
- package/lib/types/node/builtin_modules/implemented/stream/promises.d.ts +11 -0
- package/lib/types/node/builtin_modules/implemented/util.d.ts +1 -1
- package/lib/types/node/external_packages/execa.d.ts +3 -0
- package/lib/types/node/external_packages/libreoffice-kit.d.ts +6 -0
- package/lib/types/storage/paths.d.ts +1 -1
- package/lib/types/transport/frames.d.ts +12 -1
- package/lib/types/transport/tunnel.d.ts +6 -2
- package/lib/types/worker-host.d.ts +2 -2
- package/lib/worker.js +170 -50
- package/package.json +25 -23
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/experimental/webworker-runtime/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 3a5d702c2163c19daa3230ffd14270b4f236f2d9
|
|
6
|
+
README.zh.md: a987fed1db5f607aee4e95e2faf96575c1d0c904
|
package/README.md
CHANGED
|
@@ -27,12 +27,12 @@ The public npm package exposes the runtime library at its root, the self-contain
|
|
|
27
27
|
|
|
28
28
|
Three build artifacts and one source-owned process layer:
|
|
29
29
|
|
|
30
|
-
- **`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.
|
|
31
|
-
- **`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 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`.
|
|
30
|
+
- **`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. The Worker supplies `profileContext` for an editable profile at `home/profiles/preview/`: the packed rows seed its insertion patch, and ConfigEditor persists overrides in that VFS patch. ConfigEditor reconciles each write and Loader commits live values; package management and Node module HMR stay disabled. Deployment overlays keep JSONL session logs plaintext and preset roots at the image's `config/agent-presets`. Configuration edits last for the current Worker lifetime.
|
|
31
|
+
- **`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 `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
32
|
- **`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.
|
|
33
|
-
- **`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.
|
|
33
|
+
- **`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.
|
|
34
34
|
|
|
35
|
-
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
|
|
35
|
+
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](../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.
|
|
36
36
|
|
|
37
37
|
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. See the [stylesheet exemption decision](../../../.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md).
|
|
38
38
|
|
|
@@ -53,7 +53,9 @@ None; this package neither assembles nor sends a provider request.
|
|
|
53
53
|
|
|
54
54
|
- **The worker composition writes plaintext session logs** (`compression: 'none'` boot patch): it carries no Zstandard codec, so exported logs are `.jsonl`, never `.jsonl.zstd`.
|
|
55
55
|
- **`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.
|
|
56
|
+
- **Host package commands are unavailable**: `execa` reports an explicit worker-host refusal; the preview cannot run pnpm or install native dependencies.
|
|
56
57
|
- **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.
|
|
58
|
+
- **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.
|
|
57
59
|
- **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.
|
|
58
60
|
- **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.
|
|
59
61
|
- **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.
|
package/README.zh.md
CHANGED
|
@@ -27,12 +27,12 @@ kind: "package-library"
|
|
|
27
27
|
|
|
28
28
|
一条 tsdown 管线产出三个构建产物;另有一层由源码维护的进程实现:
|
|
29
29
|
|
|
30
|
-
- **`lib/index.js`(装配库)**——`createWorkerHost`/`startWorkerHost` 挂载基础镜像和按序排列的数据 overlays(`storage/`)、安装模块加载器(`module-system/`)与 `process` shim、经镜像自带的 `dsh-app-boot` 启动插件树,并把服务 seam 交给隧道。Overlay 只能替换 `home/` 与 `workspace/` 下的文件,不能替换基础 manifest、配置或模块。镜像布局契约(`image-layout.ts`:虚拟根、config/manifest 路径、空目录、`lowered` 包装契约门)与 packer 共享。
|
|
31
|
-
- **`lib/worker.js`(worker 束)**——装配库加本包的 Node 兼容层,合成一个自含 ES module。模块代理表(`module-proxies.ts`)是唯一平台叉口:`node:*` 内建走 VFS、隧道和浏览器原语,浏览器做不到的走结构化 stub(调用即在 console 报错并抛出),native/binary 包则替换执行后端。`node:module` 在镜像 package 根之上提供 `createRequire().resolve` 与 `.resolve.paths()`,使未修改的包无需执行目标模块即可发现 manifest
|
|
30
|
+
- **`lib/index.js`(装配库)**——`createWorkerHost`/`startWorkerHost` 挂载基础镜像和按序排列的数据 overlays(`storage/`)、安装模块加载器(`module-system/`)与 `process` shim、经镜像自带的 `dsh-app-boot` 启动插件树,并把服务 seam 交给隧道。Overlay 只能替换 `home/` 与 `workspace/` 下的文件,不能替换基础 manifest、配置或模块。镜像布局契约(`image-layout.ts`:虚拟根、config/manifest 路径、空目录、`lowered` 包装契约门)与 packer 共享。Worker 为 `home/profiles/preview/` 下可编辑的 profile 提供 `profileContext`:打包行初始化其插入 patch,ConfigEditor 将覆盖值持久化到该 VFS patch。ConfigEditor 协调每次写入,Loader 提交实时值;包管理和 Node 模块 HMR 保持禁用。部署 overlay 保持 JSONL 会话日志为明文,preset 根指向镜像内 `config/agent-presets`。配置修改在当前 Worker 生命周期内有效。预设声明包含在组合后的配置中。
|
|
31
|
+
- **`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:util` 的 JSON 数据比较器检查可枚举的自有属性,使 `__proto__` 等记录字段的准入判定与 Node 一致。全局 `process` shim 带有包括 `title` 在内的 Node 环境识别字段,避免 Worker 执行误入仅适用于 DOM 的分支。pack 期解析器会把名称静态可知的模块请求报告给 packer 的可达性遍历,其中包括通过 `node:module` 或 `module` 具名导入在模块作用域直接发起的 `createRequire(import.meta.url)('pkg')` 调用。保存、经 CommonJS 获取或另设基准的 `createRequire` 调用需要镜像入口种子。VFS mutation 驱动 `node:fs` 的 callback、polling 和 promise watcher;打开的 descriptor 在 rename、replacement 和 unlink 后仍保留文件身份与访问模式,只要文件名仍指向该文件,`FileHandle.stat({ bigint: true })` 报告的 device 与 inode 身份就与路径 stat 相同,`FileHandle.chmod()` 则更新打开文件身份的权限;`readable-stream` 提供 `node:stream/promises` 的完成等待与管道方法,以及文件流以及 Chokidar、readdirp 等未修改镜像包所用的流状态机。AsyncLocalStorage 经 pack 时降低注入的 snapshot/restore 面在 `await` 间携带同步栈因果。worker 不带编译器:packer 未降低的镜像在挂载时被拒。回调式 `fs.realpath.native` 别名在已挂载的 VFS 内解析路径,并支持 `util.promisify`。
|
|
32
32
|
- **`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 包协议;普通命令使用本包的求值器与 coreutils 命令表。语法来自 `@yarnpkg/parsers` 的 `parseShell`,而 `execSync`/`fork` 依然拒绝,因为它们需要真进程。
|
|
33
|
-
- **`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 执行脚本。Tunnel 还暴露 fetch 形式的传输、独立文件上传载体和 API 客户端。请求帧通过结构化克隆保留 Blob 请求体,并转移 `ReadableStream<Uint8Array>` 的所有权。Host Worker 将两种请求体都逐块送入路由,因此通用文件上传不会在任何浏览器线程创建完整字节数组。
|
|
33
|
+
- **`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 执行脚本。Tunnel 还暴露 fetch 形式的传输、独立文件上传载体和 API 客户端。请求帧通过结构化克隆保留 Blob 请求体,并转移 `ReadableStream<Uint8Array>` 的所有权。Host Worker 将两种请求体都逐块送入路由,因此通用文件上传不会在任何浏览器线程创建完整字节数组。Remote 流的上行以 `stream-uplink-item` / `stream-uplink-end` 帧进入没有上限的 `TunnelUplink`,`end` 之后到达的项被丢弃:页面与其 worker 是同一个可信的同源边界,Host 的 inbox 上限在此不适用。
|
|
34
34
|
|
|
35
|
-
验收在 `apps/web/tests/preview-boot.e2e.ts`:静态服务真实构建页面,在 headless Chromium 里驱动 pre-boot 选择面板与 Worker 激活。空白选择验证首次启动;`vfs-example` overlay 提供普通 workspace 文件与明文 persistence 产物,无需模型请求即可验证 Workspace/Session 冷发现、工具呈现、subagent 导航和历史分页。fixture
|
|
35
|
+
验收在 `apps/web/tests/preview-boot.e2e.ts`:静态服务真实构建页面,在 headless Chromium 里驱动 pre-boot 选择面板与 Worker 激活。空白选择验证首次启动;`vfs-example` overlay 提供普通 workspace 文件与明文 persistence 产物,无需模型请求即可验证 Workspace/Session 冷发现、工具呈现、subagent 导航和历史分页。[打包器](../webworker-packer/README.zh.md)在 Node 中根据已提交的历史日志准备当前代后继文件;这些源日志在 overlay 中逐字节保持不变。fixture 生成器负责验收测试的当前投影缓存。选择面板为 WebFS 保留独立的用户授权来源;该 provider 不读取内置 fixture。
|
|
36
36
|
|
|
37
37
|
[已构建 bundle 导入检查](tests/compile/transform-corpus-check.ts)在库构建后检查裸 Node 导入。Dockkit 例外接受 Node 针对任意样式表报告的未知 `.css` 扩展名错误,而不限定某一固定路径;其他扩展名、错误码或消息,以及意外成功的豁免导入,仍然报错。参见 [样式表豁免决策](../../../.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md)。
|
|
38
38
|
|
|
@@ -53,7 +53,9 @@ kind: "package-library"
|
|
|
53
53
|
|
|
54
54
|
- **worker 组合写明文会话日志**(`compression: 'none'` boot patch):不带 Zstandard 编解码器,导出日志是 `.jsonl`,不会是 `.jsonl.zstd`。
|
|
55
55
|
- **`node:dns/promises`、`node:vm`、`node:net`、`node:sqlite`、`node:worker_threads` 是结构化 stub**:每次调用在 console 报告拒绝并抛出。需要原生 DNS、真进程或真 realm 隔离的行在此无法运行。
|
|
56
|
+
- **宿主包管理命令不可用**:`execa` 明确报告 worker host 不支持该调用;预览无法运行 pnpm 或安装原生依赖。
|
|
56
57
|
- **PTC Node 程序不可用**:process shim 用 `/dsh/bin/node` 表示可执行文件身份,使 provider 能够激活,但 Worker 既没有 Node 可执行文件,也没有 `stripTypeScriptTypes`。程序执行会在启动子进程前失败。
|
|
58
|
+
- **Office 转换需要 Node Host**:LibreOffice kit 的替代模块以 `unavailable` 拒绝创建转换器。浏览器镜像排除 kit 及其引擎依赖;Office 预览提示转换不可用。
|
|
57
59
|
- **文件 watcher 只能观察已挂载的 VFS**:镜像 seed 不产生事件,VFS 也没有符号链接或外部写入方。`persistent`、`ref()` 和 `unref()` 保留 Node API,但浏览器没有引用计数事件循环,因此这些接口不能控制 dedicated Worker 的生存期。
|
|
58
60
|
- **Worker confinement 是 VFS 边界,不是内核 Landlock**:`read-only` 和 `workspace-write` 运行未经修改的 `@deepseek-ai/node-addon-system/landlock-run` JavaScript 与 launcher argv,进程层则实现逻辑 `landlock-run` 可执行文件,并在 shell 的每次文件系统请求上执行其授权。`full` 仅覆盖 Worker 命令表和已挂载 VFS,不表示能够执行任意 native 进程,也不表示 Linux 内核隔离。
|
|
59
61
|
- **worker 束钉住了 `@yarnpkg/parsers` 的包内路径**——构建解析到该包自己的 `lib/shell.js` 而非包根,因为包根 barrel 还 re-export 了 Syml 解析器,会把 js-yaml 拖进一个从不解析该格式的束(约 175 kB,外加 worker 启动时的模块体求值)。该路径由包 manifest 派生,包内布局一变即构建期失败、不会静默退回 barrel;升级这个依赖时须复核 shell 解析器是否仍在那里。
|
package/lib/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyIndexInjections } from "@deepseek-ai/dsh-client-web/injections";
|
|
1
2
|
//#region src/image-layout.ts
|
|
2
3
|
/**
|
|
3
4
|
* Leaf name of the packed base image: one gzip member holding the ustar archive.
|
|
@@ -178,7 +179,7 @@ var WorkerTunnel = class {
|
|
|
178
179
|
kind: "carrier",
|
|
179
180
|
message: `web-preview tunnel: worker failed: ${event.message}`
|
|
180
181
|
}, { cause: reason });
|
|
181
|
-
for (const inbox of this.logicalStreams.values()) inbox.fail(failure);
|
|
182
|
+
for (const { inbox } of this.logicalStreams.values()) inbox.fail(failure);
|
|
182
183
|
this.logicalStreams.clear();
|
|
183
184
|
for (const release of this.releases.values()) release();
|
|
184
185
|
this.releases.clear();
|
|
@@ -234,19 +235,24 @@ var WorkerTunnel = class {
|
|
|
234
235
|
* @param endpoint - canonical Gateway Remote endpoint.
|
|
235
236
|
* @param payload - decoded endpoint payload.
|
|
236
237
|
* @param signal - logical-stream cancellation.
|
|
238
|
+
* @param uplink - the stream's uplink, posted as `stream-uplink-item` frames and closed with `stream-uplink-end`.
|
|
237
239
|
* @returns decoded stream values from the worker Host.
|
|
238
240
|
*/
|
|
239
|
-
async *open(endpoint, payload, signal) {
|
|
241
|
+
async *open(endpoint, payload, signal, uplink) {
|
|
240
242
|
signal.throwIfAborted();
|
|
241
243
|
const id = this.nextId++;
|
|
242
244
|
const inbox = new LogicalStreamInbox();
|
|
245
|
+
const stream = {
|
|
246
|
+
inbox,
|
|
247
|
+
pump: void 0
|
|
248
|
+
};
|
|
243
249
|
let opened = false;
|
|
244
250
|
let terminal = false;
|
|
245
251
|
const onAbort = () => {
|
|
246
252
|
inbox.fail(signal.reason);
|
|
247
253
|
};
|
|
248
254
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
249
|
-
this.logicalStreams.set(id,
|
|
255
|
+
this.logicalStreams.set(id, stream);
|
|
250
256
|
this.inFlight.set(id, `STREAM ${endpoint}`);
|
|
251
257
|
try {
|
|
252
258
|
const frame = {
|
|
@@ -264,6 +270,7 @@ var WorkerTunnel = class {
|
|
|
264
270
|
message: `web-preview tunnel: failed to open Remote stream ${endpoint}`
|
|
265
271
|
}, { cause });
|
|
266
272
|
}
|
|
273
|
+
if (uplink !== void 0) stream.pump = this.pumpUplink(id, uplink, signal, inbox);
|
|
267
274
|
while (true) {
|
|
268
275
|
const response = await inbox.next();
|
|
269
276
|
signal.throwIfAborted();
|
|
@@ -279,8 +286,67 @@ var WorkerTunnel = class {
|
|
|
279
286
|
signal.removeEventListener("abort", onAbort);
|
|
280
287
|
this.logicalStreams.delete(id);
|
|
281
288
|
this.inFlight.delete(id);
|
|
289
|
+
stream.pump?.stop();
|
|
282
290
|
if (opened && !terminal) this.abortWorkerOperation(id);
|
|
291
|
+
if (stream.pump !== void 0) await stream.pump.done;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Post the caller's uplink items for one logical stream. A failing uplink
|
|
296
|
+
* fails the downlink, and the enclosing `open` then aborts the worker side.
|
|
297
|
+
* `stop()` interrupts a pump blocked on `uplink.next()` and returns the
|
|
298
|
+
* caller's iterator at once, so a handle's queue closes and `send()` throws
|
|
299
|
+
* from then on; a generator blocked in `next()` completes that return only
|
|
300
|
+
* once it yields, so it is not awaited.
|
|
301
|
+
*/
|
|
302
|
+
pumpUplink(id, uplink, signal, inbox) {
|
|
303
|
+
const interrupt = Promise.withResolvers();
|
|
304
|
+
const iterator = uplink[Symbol.asyncIterator]();
|
|
305
|
+
const state = {
|
|
306
|
+
active: true,
|
|
307
|
+
released: false
|
|
308
|
+
};
|
|
309
|
+
const release = () => {
|
|
310
|
+
if (state.released) return;
|
|
311
|
+
state.released = true;
|
|
312
|
+
Promise.resolve(iterator.return?.()).catch(() => void 0);
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
done: this.forwardUplink(id, iterator, interrupt.promise, () => state.active && !signal.aborted).then((exhausted) => {
|
|
316
|
+
state.released ||= exhausted;
|
|
317
|
+
}, (error) => {
|
|
318
|
+
inbox.fail(error);
|
|
319
|
+
}).then(release),
|
|
320
|
+
stop: () => {
|
|
321
|
+
state.active = false;
|
|
322
|
+
interrupt.resolve({
|
|
323
|
+
value: void 0,
|
|
324
|
+
done: true
|
|
325
|
+
});
|
|
326
|
+
release();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Post items until the caller's iterator ends, the pump is stopped, or the page aborts the stream.
|
|
332
|
+
* @returns whether the caller's iterator was exhausted and the uplink end posted.
|
|
333
|
+
*/
|
|
334
|
+
async forwardUplink(id, iterator, interrupt, isActive) {
|
|
335
|
+
for (;;) {
|
|
336
|
+
const next = await Promise.race([iterator.next(), interrupt]);
|
|
337
|
+
if (!isActive()) return false;
|
|
338
|
+
if (next.done === true) break;
|
|
339
|
+
this.worker.postMessage({
|
|
340
|
+
t: "stream-uplink-item",
|
|
341
|
+
id,
|
|
342
|
+
value: next.value
|
|
343
|
+
});
|
|
283
344
|
}
|
|
345
|
+
this.worker.postMessage({
|
|
346
|
+
t: "stream-uplink-end",
|
|
347
|
+
id
|
|
348
|
+
});
|
|
349
|
+
return true;
|
|
284
350
|
}
|
|
285
351
|
/**
|
|
286
352
|
* Read the pre-cordis boot payload (the injection table).
|
|
@@ -297,7 +363,8 @@ var WorkerTunnel = class {
|
|
|
297
363
|
* The image packs each bundle with a trailing `sourceURL` naming its image
|
|
298
364
|
* path, so the blob shows under that name in the debugger instead of as an
|
|
299
365
|
* anonymous blob entry.
|
|
300
|
-
* @param url - Graph combo
|
|
366
|
+
* @param url - Graph combo reference (`plugins/??<id>/client.js&rev=...`), which
|
|
367
|
+
* this tunnel resolves against the page origin it maps from.
|
|
301
368
|
*/
|
|
302
369
|
async loadBundle(url) {
|
|
303
370
|
const response = await this.fetch(url);
|
|
@@ -473,52 +540,18 @@ var WorkerTunnel = class {
|
|
|
473
540
|
}
|
|
474
541
|
case "stream-item":
|
|
475
542
|
case "stream-end":
|
|
476
|
-
case "stream-error":
|
|
477
|
-
this.logicalStreams.get(frame.id)
|
|
543
|
+
case "stream-error": {
|
|
544
|
+
const stream = this.logicalStreams.get(frame.id);
|
|
545
|
+
if (stream === void 0) return;
|
|
546
|
+
stream.inbox.push(frame);
|
|
547
|
+
if (frame.t !== "stream-item") stream.pump?.stop();
|
|
478
548
|
return;
|
|
549
|
+
}
|
|
479
550
|
default: throw new Error(`web-preview tunnel: unknown frame ${JSON.stringify(frame)}`);
|
|
480
551
|
}
|
|
481
552
|
}
|
|
482
553
|
};
|
|
483
554
|
//#endregion
|
|
484
|
-
//#region src/client/apply-injections.ts
|
|
485
|
-
function assertNever(row) {
|
|
486
|
-
throw new Error(`webworker-runtime: unknown index injection row ${JSON.stringify(row)}`);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Execute every row in table order.
|
|
490
|
-
* @param rows - Injection table from the boot payload.
|
|
491
|
-
* @param loadScript - Executes one script-src row; the tunnel's `loadBundle`,
|
|
492
|
-
* because the row URLs (`/plugins/...`) resolve only through the worker.
|
|
493
|
-
*/
|
|
494
|
-
async function applyIndexInjections(rows, loadScript) {
|
|
495
|
-
for (const row of rows) switch (row.kind) {
|
|
496
|
-
case "global":
|
|
497
|
-
globalThis[row.name] = row.value;
|
|
498
|
-
break;
|
|
499
|
-
case "script": {
|
|
500
|
-
const el = document.createElement("script");
|
|
501
|
-
el.textContent = row.text;
|
|
502
|
-
(row.placement === "head" ? document.head : document.body).append(el);
|
|
503
|
-
break;
|
|
504
|
-
}
|
|
505
|
-
case "script-src":
|
|
506
|
-
await loadScript(row.src);
|
|
507
|
-
break;
|
|
508
|
-
case "script-preload": break;
|
|
509
|
-
case "style": {
|
|
510
|
-
const el = document.createElement("style");
|
|
511
|
-
el.textContent = row.text;
|
|
512
|
-
document.head.append(el);
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
case "html":
|
|
516
|
-
(row.placement === "head" ? document.head : document.body).insertAdjacentHTML("beforeend", row.html);
|
|
517
|
-
break;
|
|
518
|
-
default: assertNever(row);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
//#endregion
|
|
522
555
|
//#region src/client/source-chooser.ts
|
|
523
556
|
/** Pre-boot filesystem-source chooser for static WebWorker previews. */
|
|
524
557
|
const EMPTY_SOURCE = "none";
|
|
@@ -833,7 +866,7 @@ async function connectWorkerHost(worker, options) {
|
|
|
833
866
|
const payload = await tunnel.bootPayload();
|
|
834
867
|
globalThis.__DSH_TRANSPORT__ = {
|
|
835
868
|
fetch: (input, init) => tunnel.fetch(input, init),
|
|
836
|
-
openStream: (endpoint, payload, signal) => tunnel.open(endpoint, payload, signal),
|
|
869
|
+
openStream: (endpoint, payload, signal, uplink) => tunnel.open(endpoint, payload, signal, uplink),
|
|
837
870
|
loadBundle: (url) => tunnel.loadBundle(url),
|
|
838
871
|
ownsHost: true
|
|
839
872
|
};
|
package/lib/index.js
CHANGED
|
@@ -107,6 +107,15 @@ function parseInboundFrame(data) {
|
|
|
107
107
|
t: "abort",
|
|
108
108
|
id
|
|
109
109
|
};
|
|
110
|
+
if (frame.t === "stream-uplink-end") return {
|
|
111
|
+
t: "stream-uplink-end",
|
|
112
|
+
id
|
|
113
|
+
};
|
|
114
|
+
if (frame.t === "stream-uplink-item") return {
|
|
115
|
+
t: "stream-uplink-item",
|
|
116
|
+
id,
|
|
117
|
+
value: frame.value
|
|
118
|
+
};
|
|
110
119
|
if (frame.t === "stream-open") {
|
|
111
120
|
if (typeof frame.endpoint !== "string" || frame.endpoint.length === 0) throw new Error(`webworker tunnel: stream ${String(id)} needs a non-empty endpoint`);
|
|
112
121
|
return {
|
|
@@ -725,13 +734,17 @@ const MODULE_PROXIES = {
|
|
|
725
734
|
"dns/promises": "./node/builtin_modules/mock/dns/promises.ts",
|
|
726
735
|
"node:net": "./node/builtin_modules/mock/net.ts",
|
|
727
736
|
"node:stream": "./node/builtin_modules/implemented/stream.ts",
|
|
737
|
+
"node:stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
|
|
738
|
+
"stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
|
|
728
739
|
"node:vm": "./node/builtin_modules/mock/vm.ts",
|
|
729
740
|
"node:worker_threads": "./node/builtin_modules/mock/worker_threads.ts",
|
|
730
741
|
"node:sqlite": "./node/builtin_modules/mock/sqlite.ts",
|
|
742
|
+
"@deepseek-ai/libreoffice-kit": "./node/external_packages/libreoffice-kit.ts",
|
|
731
743
|
"@deepseek-ai/node-addon-system/flock": "./node/external_packages/node-addon-system-flock.ts",
|
|
732
744
|
"koffi": "./node/external_packages/koffi.ts",
|
|
733
745
|
"sharp": "./node/external_packages/sharp.ts",
|
|
734
746
|
"node-pty": "./node/external_packages/node-pty.ts",
|
|
747
|
+
"execa": "./node/external_packages/execa.ts",
|
|
735
748
|
"@vscode/ripgrep": "./node/external_packages/ripgrep.ts",
|
|
736
749
|
"@earendil-works/pi-ai": "./node/external_packages/pi-ai.ts",
|
|
737
750
|
"ws": "./node/external_packages/ws.ts"
|
|
@@ -750,9 +763,11 @@ const MODULE_PROXY_PREFIXES = { "@earendil-works/pi-ai/": "./node/external_packa
|
|
|
750
763
|
const REPLACED_EXTERNAL_PACKAGES = [
|
|
751
764
|
"@earendil-works/pi-ai",
|
|
752
765
|
"@vscode/ripgrep",
|
|
766
|
+
"@deepseek-ai/libreoffice-kit",
|
|
753
767
|
"@deepseek-ai/node-addon-system/flock",
|
|
754
768
|
"koffi",
|
|
755
769
|
"node-pty",
|
|
770
|
+
"execa",
|
|
756
771
|
"sharp",
|
|
757
772
|
"ws"
|
|
758
773
|
];
|
|
@@ -1515,6 +1530,61 @@ var BufferedSink = class {
|
|
|
1515
1530
|
for (const call of this.calls.splice(0)) call();
|
|
1516
1531
|
}
|
|
1517
1532
|
};
|
|
1533
|
+
/** Uplink items of one worker-local logical stream, read once by the Host method as its `uplink`. */
|
|
1534
|
+
var TunnelUplink = class {
|
|
1535
|
+
items = [];
|
|
1536
|
+
ended = false;
|
|
1537
|
+
closed = false;
|
|
1538
|
+
wake;
|
|
1539
|
+
push(value) {
|
|
1540
|
+
if (this.ended || this.closed) return;
|
|
1541
|
+
this.items.push(value);
|
|
1542
|
+
this.signal();
|
|
1543
|
+
}
|
|
1544
|
+
/** Page half-close or stream end; buffered items still drain. */
|
|
1545
|
+
end() {
|
|
1546
|
+
this.ended = true;
|
|
1547
|
+
this.signal();
|
|
1548
|
+
}
|
|
1549
|
+
[Symbol.asyncIterator]() {
|
|
1550
|
+
return this;
|
|
1551
|
+
}
|
|
1552
|
+
async next() {
|
|
1553
|
+
while (true) {
|
|
1554
|
+
if (this.closed) return {
|
|
1555
|
+
value: void 0,
|
|
1556
|
+
done: true
|
|
1557
|
+
};
|
|
1558
|
+
if (this.items.length > 0) return {
|
|
1559
|
+
value: this.items.shift(),
|
|
1560
|
+
done: false
|
|
1561
|
+
};
|
|
1562
|
+
if (this.ended) return {
|
|
1563
|
+
value: void 0,
|
|
1564
|
+
done: true
|
|
1565
|
+
};
|
|
1566
|
+
if (this.wake !== void 0) throw new Error("webworker tunnel: stream uplink has one pending read");
|
|
1567
|
+
await new Promise((resolve) => {
|
|
1568
|
+
this.wake = resolve;
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/** The Host stopped reading: buffered and later items are dropped. */
|
|
1573
|
+
return() {
|
|
1574
|
+
this.closed = true;
|
|
1575
|
+
this.items.length = 0;
|
|
1576
|
+
this.signal();
|
|
1577
|
+
return Promise.resolve({
|
|
1578
|
+
value: void 0,
|
|
1579
|
+
done: true
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
signal() {
|
|
1583
|
+
const wake = this.wake;
|
|
1584
|
+
this.wake = void 0;
|
|
1585
|
+
wake?.();
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1518
1588
|
/** One tunnel per worker; wire {@link TunnelServer.handleMessage} to `onmessage` first. */
|
|
1519
1589
|
var TunnelServer = class {
|
|
1520
1590
|
port;
|
|
@@ -1523,6 +1593,8 @@ var TunnelServer = class {
|
|
|
1523
1593
|
unaryApiLane;
|
|
1524
1594
|
queue = [];
|
|
1525
1595
|
inFlight = /* @__PURE__ */ new Map();
|
|
1596
|
+
/** Uplinks of accepted `stream-open` frames, buffering items that arrive before or while the stream serves. */
|
|
1597
|
+
uplinks = /* @__PURE__ */ new Map();
|
|
1526
1598
|
seams;
|
|
1527
1599
|
failure;
|
|
1528
1600
|
listener;
|
|
@@ -1539,9 +1611,18 @@ var TunnelServer = class {
|
|
|
1539
1611
|
handleMessage(data) {
|
|
1540
1612
|
const frame = parseInboundFrame(data);
|
|
1541
1613
|
if (frame.t === "init") throw new Error("webworker tunnel: duplicate init frame; the tunnel is already open");
|
|
1614
|
+
if (frame.t === "stream-uplink-item") {
|
|
1615
|
+
this.uplinks.get(frame.id)?.push(frame.value);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
if (frame.t === "stream-uplink-end") {
|
|
1619
|
+
this.uplinks.get(frame.id)?.end();
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1542
1622
|
if (frame.t === "abort") {
|
|
1543
1623
|
this.inFlight.get(frame.id)?.abort();
|
|
1544
1624
|
this.inFlight.delete(frame.id);
|
|
1625
|
+
this.dropUplink(frame.id);
|
|
1545
1626
|
const queued = this.queue.findIndex((request) => request.id === frame.id);
|
|
1546
1627
|
if (queued !== -1) this.queue.splice(queued, 1);
|
|
1547
1628
|
return;
|
|
@@ -1550,6 +1631,7 @@ var TunnelServer = class {
|
|
|
1550
1631
|
this.refuse(frame, this.failure);
|
|
1551
1632
|
return;
|
|
1552
1633
|
}
|
|
1634
|
+
if (frame.t === "stream-open") this.uplinks.set(frame.id, new TunnelUplink());
|
|
1553
1635
|
if (this.seams === void 0) {
|
|
1554
1636
|
this.queue.push(frame);
|
|
1555
1637
|
return;
|
|
@@ -1580,6 +1662,7 @@ var TunnelServer = class {
|
|
|
1580
1662
|
}
|
|
1581
1663
|
refuse(frame, message) {
|
|
1582
1664
|
if (frame.t === "stream-open") {
|
|
1665
|
+
this.dropUplink(frame.id);
|
|
1583
1666
|
this.send({
|
|
1584
1667
|
t: "stream-error",
|
|
1585
1668
|
id: frame.id,
|
|
@@ -1610,12 +1693,13 @@ var TunnelServer = class {
|
|
|
1610
1693
|
return;
|
|
1611
1694
|
}
|
|
1612
1695
|
const seams = this.seams;
|
|
1696
|
+
const uplink = this.uplinks.get(frame.id) ?? new TunnelUplink();
|
|
1613
1697
|
const controller = new AbortController();
|
|
1614
1698
|
this.inFlight.set(frame.id, { abort: () => {
|
|
1615
1699
|
controller.abort();
|
|
1616
1700
|
} });
|
|
1617
1701
|
try {
|
|
1618
|
-
const source = await seams.openStream(frame.endpoint, frame.payload, controller.signal);
|
|
1702
|
+
const source = await seams.openStream(frame.endpoint, frame.payload, uplink, controller.signal);
|
|
1619
1703
|
for await (const value of source) {
|
|
1620
1704
|
if (controller.signal.aborted) return;
|
|
1621
1705
|
this.send({
|
|
@@ -1642,8 +1726,15 @@ var TunnelServer = class {
|
|
|
1642
1726
|
}
|
|
1643
1727
|
} finally {
|
|
1644
1728
|
this.inFlight.delete(frame.id);
|
|
1729
|
+
this.dropUplink(frame.id);
|
|
1645
1730
|
}
|
|
1646
1731
|
}
|
|
1732
|
+
/** The stream is over: settle any Host read still waiting on its uplink and stop buffering. */
|
|
1733
|
+
dropUplink(id) {
|
|
1734
|
+
const uplink = this.uplinks.get(id);
|
|
1735
|
+
this.uplinks.delete(id);
|
|
1736
|
+
uplink?.end();
|
|
1737
|
+
}
|
|
1647
1738
|
sinkFor(id) {
|
|
1648
1739
|
const send = this.send.bind(this);
|
|
1649
1740
|
const inFlight = this.inFlight;
|
|
@@ -3282,8 +3373,10 @@ function createWorkerHost(options) {
|
|
|
3282
3373
|
const require = loader.requireFrom(dirname(configPath));
|
|
3283
3374
|
const appBoot = require("@deepseek-ai/dsh-app-boot");
|
|
3284
3375
|
const cmdline = require("@deepseek-ai/dsh-cmdline");
|
|
3285
|
-
const { patches,
|
|
3286
|
-
const
|
|
3376
|
+
const { patches, profile } = bootPatches(loader, mounted, configPath, root);
|
|
3377
|
+
const profileConfig = join(profile.dir, "cordis.yml");
|
|
3378
|
+
const ctx = await appBoot.boot("dsh-webworker", profileConfig, patches, (hostCtx) => {
|
|
3379
|
+
hostCtx.provide("profileContext", profile);
|
|
3287
3380
|
hostCtx.loader.internal = loader.internal;
|
|
3288
3381
|
installLogSink(hostCtx, require);
|
|
3289
3382
|
cmdline.provideCmdline(hostCtx, {
|
|
@@ -3306,11 +3399,11 @@ function createWorkerHost(options) {
|
|
|
3306
3399
|
if (typertGateway === void 0) throw new Error("webworker host: the tree activated without a typertGateway service");
|
|
3307
3400
|
const handler = connection.createSharedFetchHandler("/api");
|
|
3308
3401
|
const usage = loader.usage();
|
|
3309
|
-
console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)},
|
|
3402
|
+
console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)}, direct lane=connection.createSharedFetchHandler, als causality=${options.alsCausality === void 0 ? "inert" : "snapshot/restore"}, image lowering=${LOWERING_VERSION})`);
|
|
3310
3403
|
tunnel.serve({
|
|
3311
3404
|
directFetch: (request) => handler.fetch(request),
|
|
3312
3405
|
bootPayload: () => readBootPayload(ctx),
|
|
3313
|
-
openStream: typertGateway.wireStream.open,
|
|
3406
|
+
openStream: (endpoint, payload, uplink, signal) => typertGateway.wireStream.open(endpoint, payload, uplink, void 0, signal),
|
|
3314
3407
|
streamFailure: typertGateway.wireStream.failure
|
|
3315
3408
|
});
|
|
3316
3409
|
} catch (reason) {
|
|
@@ -3382,30 +3475,22 @@ function requireLoweredImage(vfs, path) {
|
|
|
3382
3475
|
if (lowered !== "dsh-worker-transform/1") throw new Error(`webworker host: image was lowered by ${String(lowered)}, this build runs ${LOWERING_VERSION}; rebuild the image`);
|
|
3383
3476
|
}
|
|
3384
3477
|
/**
|
|
3385
|
-
*
|
|
3386
|
-
*
|
|
3387
|
-
*
|
|
3388
|
-
* A launcher appends this root itself rather than writing it into the roster —
|
|
3389
|
-
* `apps/cli` does it in `composeProfile` (`profile-boot.ts:159-166`) because only
|
|
3390
|
-
* the application knows where its own presets sit. The worker's presets travel
|
|
3391
|
-
* in the image, so the same overlay names their virtual path. Patching replaces
|
|
3392
|
-
* a row's whole `config`, so the current one is read and spread, and a roster
|
|
3393
|
-
* that already names roots keeps them.
|
|
3478
|
+
* Prepare an editable VFS profile from the packed rows and deployment overlays.
|
|
3479
|
+
* ConfigEditor writes overrides beside the insertion rows; reconciliation starts
|
|
3480
|
+
* at the empty profile root and retains the Worker deployment overlays.
|
|
3394
3481
|
* @param loader - Module loader, for the image's YAML reader.
|
|
3395
3482
|
* @param vfs - Filesystem holding the composed configuration.
|
|
3396
3483
|
* @param configPath - Composed configuration path.
|
|
3397
3484
|
* @param root - Virtual root.
|
|
3398
|
-
* @returns
|
|
3399
|
-
* whether the preset overlay was applied.
|
|
3485
|
+
* @returns Profile locations and effective boot patches.
|
|
3400
3486
|
*/
|
|
3401
3487
|
function bootPatches(loader, vfs, configPath, root) {
|
|
3402
3488
|
const text = vfs.readFileSync(configPath, "utf8");
|
|
3489
|
+
const include = loader.load(loader.resolve("@deepseek-ai/cordis-plugin-include", root));
|
|
3490
|
+
const yaml = loader.load(loader.resolve("js-yaml", root));
|
|
3403
3491
|
let rows;
|
|
3404
3492
|
if (configPath.endsWith(".json")) rows = JSON.parse(text);
|
|
3405
|
-
else {
|
|
3406
|
-
const include = loader.load(loader.resolve("@deepseek-ai/cordis-plugin-include", root));
|
|
3407
|
-
rows = loader.load(loader.resolve("js-yaml", root)).load(text, { schema: include.entryListSchema });
|
|
3408
|
-
}
|
|
3493
|
+
else rows = yaml.load(text, { schema: include.entryListSchema });
|
|
3409
3494
|
const find = (entries, id) => {
|
|
3410
3495
|
if (!Array.isArray(entries)) return void 0;
|
|
3411
3496
|
for (const entry of entries) {
|
|
@@ -3416,21 +3501,10 @@ function bootPatches(loader, vfs, configPath, root) {
|
|
|
3416
3501
|
};
|
|
3417
3502
|
const configOf = (row) => typeof row.config === "object" && row.config !== null && !Array.isArray(row.config) ? row.config : {};
|
|
3418
3503
|
const patches = [];
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
patches.push({
|
|
3424
|
-
id: "agent-presets",
|
|
3425
|
-
config: {
|
|
3426
|
-
...configOf(presets),
|
|
3427
|
-
roots: [{
|
|
3428
|
-
path: join(root, "config/agent-presets"),
|
|
3429
|
-
trust: "system"
|
|
3430
|
-
}]
|
|
3431
|
-
}
|
|
3432
|
-
});
|
|
3433
|
-
}
|
|
3504
|
+
for (const id of ["hmr", "plugin-manager"]) if (find(rows, id) !== void 0) patches.push({
|
|
3505
|
+
id,
|
|
3506
|
+
disabled: true
|
|
3507
|
+
});
|
|
3434
3508
|
const jsonl = find(rows, "session-persistence-jsonl");
|
|
3435
3509
|
if (jsonl !== void 0) patches.push({
|
|
3436
3510
|
id: "session-persistence-jsonl",
|
|
@@ -3439,9 +3513,24 @@ function bootPatches(loader, vfs, configPath, root) {
|
|
|
3439
3513
|
compression: "none"
|
|
3440
3514
|
}
|
|
3441
3515
|
});
|
|
3516
|
+
const dir = join(root, IMAGE_HOME_DIRECTORY, "profiles/preview");
|
|
3517
|
+
const profile = {
|
|
3518
|
+
name: "preview",
|
|
3519
|
+
dir,
|
|
3520
|
+
patchPath: join(dir, "cordis.patch.yml"),
|
|
3521
|
+
installAnchor: join(dir, "package.json"),
|
|
3522
|
+
cwd: root,
|
|
3523
|
+
home: join(root, IMAGE_HOME_DIRECTORY),
|
|
3524
|
+
startedBundles: [],
|
|
3525
|
+
overlays: patches,
|
|
3526
|
+
telemetryDisabledEnv: void 0
|
|
3527
|
+
};
|
|
3528
|
+
vfs.seed(join(dir, "package.json"), "{\"private\":true,\"dsh\":{\"profile\":{\"bundles\":[]}}}\n");
|
|
3529
|
+
vfs.seed(join(dir, "cordis.yml"), "[]\n");
|
|
3530
|
+
if (!vfs.existsSync(profile.patchPath)) vfs.seed(profile.patchPath, yaml.dump([{ insert: rows }], { schema: include.entryListSchema }));
|
|
3442
3531
|
return {
|
|
3443
|
-
patches,
|
|
3444
|
-
|
|
3532
|
+
patches: loader.load(loader.resolve("@deepseek-ai/dsh-app-boot", root)).readProfilePatches("dsh-webworker", profile),
|
|
3533
|
+
profile
|
|
3445
3534
|
};
|
|
3446
3535
|
}
|
|
3447
3536
|
/**
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* form renders the same rows into index.html text; a static worker page has
|
|
4
|
-
* no served HTML, so it executes the table directly. Rows execute strictly in
|
|
5
|
-
* table order, so a global row lands before the scripts that read it.
|
|
6
|
-
*/
|
|
7
|
-
import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver';
|
|
8
|
-
/**
|
|
9
|
-
* Execute every row in table order.
|
|
10
|
-
* @param rows - Injection table from the boot payload.
|
|
11
|
-
* @param loadScript - Executes one script-src row; the tunnel's `loadBundle`,
|
|
12
|
-
* because the row URLs (`/plugins/...`) resolve only through the worker.
|
|
13
|
-
*/
|
|
14
|
-
export declare function applyIndexInjections(rows: readonly IndexInjection[], loadScript: (src: string) => Promise<void>): Promise<void>;
|
|
1
|
+
/** Shared page-side interpreter for asynchronous Host startup. */
|
|
2
|
+
export { applyIndexInjections } from '@deepseek-ai/dsh-client-web/injections';
|
|
15
3
|
//# sourceMappingURL=apply-injections.d.ts.map
|
|
@@ -48,9 +48,24 @@ export declare class WorkerTunnel {
|
|
|
48
48
|
* @param endpoint - canonical Gateway Remote endpoint.
|
|
49
49
|
* @param payload - decoded endpoint payload.
|
|
50
50
|
* @param signal - logical-stream cancellation.
|
|
51
|
+
* @param uplink - the stream's uplink, posted as `stream-uplink-item` frames and closed with `stream-uplink-end`.
|
|
51
52
|
* @returns decoded stream values from the worker Host.
|
|
52
53
|
*/
|
|
53
|
-
open(endpoint: string, payload: unknown, signal: AbortSignal): AsyncGenerator;
|
|
54
|
+
open(endpoint: string, payload: unknown, signal: AbortSignal, uplink?: AsyncIterable<unknown>): AsyncGenerator;
|
|
55
|
+
/**
|
|
56
|
+
* Post the caller's uplink items for one logical stream. A failing uplink
|
|
57
|
+
* fails the downlink, and the enclosing `open` then aborts the worker side.
|
|
58
|
+
* `stop()` interrupts a pump blocked on `uplink.next()` and returns the
|
|
59
|
+
* caller's iterator at once, so a handle's queue closes and `send()` throws
|
|
60
|
+
* from then on; a generator blocked in `next()` completes that return only
|
|
61
|
+
* once it yields, so it is not awaited.
|
|
62
|
+
*/
|
|
63
|
+
private pumpUplink;
|
|
64
|
+
/**
|
|
65
|
+
* Post items until the caller's iterator ends, the pump is stopped, or the page aborts the stream.
|
|
66
|
+
* @returns whether the caller's iterator was exhausted and the uplink end posted.
|
|
67
|
+
*/
|
|
68
|
+
private forwardUplink;
|
|
54
69
|
/**
|
|
55
70
|
* Read the pre-cordis boot payload (the injection table).
|
|
56
71
|
* @returns The payload the page applies before the client tree loads.
|
|
@@ -62,7 +77,8 @@ export declare class WorkerTunnel {
|
|
|
62
77
|
* The image packs each bundle with a trailing `sourceURL` naming its image
|
|
63
78
|
* path, so the blob shows under that name in the debugger instead of as an
|
|
64
79
|
* anonymous blob entry.
|
|
65
|
-
* @param url - Graph combo
|
|
80
|
+
* @param url - Graph combo reference (`plugins/??<id>/client.js&rev=...`), which
|
|
81
|
+
* this tunnel resolves against the page origin it maps from.
|
|
66
82
|
*/
|
|
67
83
|
loadBundle(url: string): Promise<void>;
|
|
68
84
|
private rejectOnAbort;
|