@deepseek-ai/dsh-experimental-webworker-runtime 0.2.1-alpha.3 → 0.2.1-alpha.4

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/lib/worker.js CHANGED
@@ -101,6 +101,202 @@ function createAlsRuntime(causality) {
101
101
  }
102
102
  };
103
103
  }
104
+ //#endregion
105
+ //#region src/node/notImplementedFail.ts
106
+ /**
107
+ * Structural not-implemented stubs: a replaced module must expose every symbol
108
+ * its importers name (a missing CommonJS symbol degrades to `undefined` at call
109
+ * time instead of failing at link time), and every one of those symbols must
110
+ * report exactly what is unavailable when it is finally called. The refusal
111
+ * carries Node's `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` code, so a caller that
112
+ * can run without the feature distinguishes it from other failures.
113
+ */
114
+ /**
115
+ * Build a function that throws naming its module and symbol. The refusal is
116
+ * also written to the console before it propagates: callers routinely swallow
117
+ * these errors far from their cause, and the console line is what places the
118
+ * failure while debugging a worker session.
119
+ *
120
+ * `Face` is the Node declaration this stub stands in for, so the replaced module
121
+ * publishes the type its importers compile against. The value is one throwing
122
+ * function whatever that declaration says: a caller reaches the throw before any
123
+ * declared parameter, return value, or `new` result exists, so the assertion
124
+ * below cannot be observed as a lie. It is a function expression rather than an
125
+ * arrow because a stub standing in for a class must refuse under `new` too, and
126
+ * an arrow has no construct behavior to reach.
127
+ * @param module - module specifier being stubbed.
128
+ * @param symbol - exported symbol name.
129
+ * @returns the throwing stand-in, typed as the member it replaces.
130
+ */
131
+ function notImplementedFail(module, symbol) {
132
+ return (function refuse() {
133
+ throw notAvailableError(module, symbol);
134
+ });
135
+ }
136
+ /** Node's standard code for a feature the running platform cannot provide. */
137
+ const FEATURE_UNAVAILABLE_ON_PLATFORM = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
138
+ /**
139
+ * Build the refusal error and write it to the console first, for stubs that
140
+ * cannot be a plain throwing function (constructors, methods on structural
141
+ * fakes).
142
+ * @param module - module specifier being stubbed.
143
+ * @param symbol - unavailable member, named as the importer sees it.
144
+ * @returns the error to throw, with `code` set to Node's `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM`.
145
+ */
146
+ function notAvailableError(module, symbol) {
147
+ const message = `web-preview: ${module}.${symbol} is not available in the worker host`;
148
+ console.error(message);
149
+ return Object.assign(new Error(message), { code: FEATURE_UNAVAILABLE_ON_PLATFORM });
150
+ }
151
+ //#endregion
152
+ //#region src/node/internal_modules/module-map.ts
153
+ /**
154
+ * `internal/modules/esm/module_map` for the Worker, following Node v24.18.0. The Worker evaluates no ES module
155
+ * jobs, so a {@link LoadCache} stays empty and refuses to store one.
156
+ * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/internal_modules/module-map
157
+ */
158
+ /** Job slot Node uses when an import names no `type` attribute. */
159
+ const IMPLICIT_TYPE_ATTRIBUTE = "javascript";
160
+ /**
161
+ * Node's `validateString` refusal.
162
+ * @param value - Argument to check.
163
+ * @param name - Argument name in the message.
164
+ */
165
+ function validateString(value, name) {
166
+ if (typeof value === "string") return;
167
+ throw Object.assign(/* @__PURE__ */ new TypeError(`The "${name}" argument must be of type string. Received ${typeof value}`), { code: "ERR_INVALID_ARG_TYPE" });
168
+ }
169
+ /** Resolutions keyed by parent URL, then by serialized module request. */
170
+ var ResolveCache = class extends Map {
171
+ /**
172
+ * Serialize a module request: the specifier and its import attributes sorted by key.
173
+ * @param specifier - Requested module.
174
+ * @param importAttributes - Import attributes of the request.
175
+ * @returns The cache key Node uses for this request.
176
+ */
177
+ serializeKey(specifier, importAttributes) {
178
+ const keys = Object.keys(importAttributes);
179
+ if (keys.length === 0) return `${specifier}::`;
180
+ return `${specifier}::${keys.sort().map((key) => JSON.stringify(key) + JSON.stringify(importAttributes[key])).join(",")}`;
181
+ }
182
+ #imports(parentURL) {
183
+ const existing = super.get(parentURL);
184
+ if (existing !== void 0) return existing;
185
+ const created = Object.create(null);
186
+ super.set(parentURL, created);
187
+ return created;
188
+ }
189
+ /**
190
+ * Read one cached resolution.
191
+ * @param serializedKey - Key from {@link serializeKey}.
192
+ * @param parentURL - URL of the importing module.
193
+ * @returns The cached resolution, if any.
194
+ */
195
+ get(serializedKey, parentURL) {
196
+ return this.#imports(parentURL)[serializedKey];
197
+ }
198
+ /**
199
+ * Store one resolution.
200
+ * @param serializedKey - Key from {@link serializeKey}.
201
+ * @param parentURL - URL of the importing module.
202
+ * @param result - Resolution to cache.
203
+ * @returns This cache.
204
+ */
205
+ set(serializedKey, parentURL, result) {
206
+ this.#imports(parentURL)[serializedKey] = result;
207
+ return this;
208
+ }
209
+ /**
210
+ * Check for one cached resolution.
211
+ * @param serializedKey - Key from {@link serializeKey}.
212
+ * @param parentURL - URL of the importing module.
213
+ * @returns Whether the request has a cached resolution.
214
+ */
215
+ has(serializedKey, parentURL) {
216
+ return serializedKey in this.#imports(parentURL);
217
+ }
218
+ };
219
+ /** Module jobs keyed by URL, then by import `type` attribute; always empty in the Worker. */
220
+ var LoadCache = class extends Map {
221
+ /**
222
+ * Read the job for one URL and type.
223
+ * @param url - Module URL.
224
+ * @param type - Import `type` attribute.
225
+ * @returns The cached job, if any.
226
+ */
227
+ get(url, type = IMPLICIT_TYPE_ATTRIBUTE) {
228
+ validateString(url, "url");
229
+ validateString(type, "type");
230
+ return super.get(url)?.[type];
231
+ }
232
+ /**
233
+ * Refuse to store a job: the Worker creates no ES module jobs.
234
+ * @param url - Module URL.
235
+ * @param type - Import `type` attribute.
236
+ * @param _job - Job Node would store.
237
+ * @returns Never; always throws.
238
+ */
239
+ set(url, type = IMPLICIT_TYPE_ATTRIBUTE, _job) {
240
+ validateString(url, "url");
241
+ validateString(type, "type");
242
+ throw notAvailableError("internal/modules/esm/module_map", "LoadCache.set");
243
+ }
244
+ /**
245
+ * Check for the job of one URL and type.
246
+ * @param url - Module URL.
247
+ * @param type - Import `type` attribute.
248
+ * @returns Whether a job is cached.
249
+ */
250
+ has(url, type = IMPLICIT_TYPE_ATTRIBUTE) {
251
+ validateString(url, "url");
252
+ validateString(type, "type");
253
+ return super.get(url)?.[type] !== void 0;
254
+ }
255
+ };
256
+ //#endregion
257
+ //#region src/node/internal_modules/esm-loader.ts
258
+ /**
259
+ * `internal/modules/esm/loader` cascaded loader for the Worker. {@link createCascadedLoader} builds the object with
260
+ * the Node v24.18.0 (v2) loader members this repository calls: `resolveSync` and `import` over one Worker module
261
+ * loader, and an empty `loadCache`. ES module jobs are unavailable on this platform.
262
+ * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/internal_modules/esm-loader
263
+ */
264
+ /**
265
+ * Translate a Worker resolution failure into the code Node's ESM resolver reports.
266
+ * @param error - Failure thrown by the Worker module loader.
267
+ * @returns The same failure with an ESM resolution code.
268
+ */
269
+ function esmResolutionError(error) {
270
+ if (error instanceof Error && error.code === "MODULE_NOT_FOUND") return Object.assign(error, { code: "ERR_MODULE_NOT_FOUND" });
271
+ return error;
272
+ }
273
+ /**
274
+ * Build the cascaded loader for one Worker module loader.
275
+ * @param source - The Worker module loader's resolution and loading operations.
276
+ * @returns A cascaded loader delegating to `source`.
277
+ */
278
+ function createCascadedLoader(source) {
279
+ return {
280
+ loadCache: new LoadCache(),
281
+ resolveSync(parentURL, request) {
282
+ try {
283
+ const { url, format } = source.resolveSync(request.specifier, parentURL);
284
+ return {
285
+ url,
286
+ format
287
+ };
288
+ } catch (error) {
289
+ throw esmResolutionError(error);
290
+ }
291
+ },
292
+ import(specifier, parentURL) {
293
+ return source.import(specifier, parentURL);
294
+ },
295
+ getOrCreateModuleJob() {
296
+ throw notAvailableError("internal/modules/esm/loader", "getOrCreateModuleJob");
297
+ }
298
+ };
299
+ }
104
300
  /**
105
301
  * Collapse `.` and `..` segments.
106
302
  * @param path - Path with any number of separators.
@@ -236,10 +432,11 @@ const WRAPPER_PARAMS = [
236
432
  //#endregion
237
433
  //#region src/module-system/module-loader.ts
238
434
  /**
239
- * CommonJS module loader over the worker VFS. It fills the `loader.internal`
240
- * seam Cordis uses for every entry import, and backs the `node:module`
241
- * `createRequire` proxy that `typert-loader`, `client-modules`, and the plugin
242
- * package inventory resolve package metadata through.
435
+ * CommonJS module loader over the worker VFS. It backs the Node internal ESM
436
+ * loader that `node-addon-require-builtin` answers, which Cordis's Loader uses
437
+ * for every entry import, and the `node:module` `createRequire` proxy that
438
+ * `typert-loader`, `client-modules`, and the plugin package inventory resolve
439
+ * package metadata through.
243
440
  *
244
441
  * Resolution is a narrowed Node `require` algorithm: `exports` walk with a
245
442
  * fixed condition order, extension probing, and one cache keyed by resolved
@@ -275,9 +472,11 @@ var WorkerModuleLoader = class {
275
472
  modules = /* @__PURE__ */ new Map();
276
473
  manifests = /* @__PURE__ */ new Map();
277
474
  stack = [];
475
+ cascaded;
278
476
  /**
279
- * The Cordis module seam. `parentURL` positions relative specifiers;
280
- * import attributes are ignored, as the client implementation does.
477
+ * Resolution and loading over this mount, in Node's v1 argument order. The cascaded ESM loader
478
+ * ({@link cascadedLoader}) and the packer call it; `parentURL` positions relative specifiers and import attributes
479
+ * are ignored.
281
480
  */
282
481
  internal;
283
482
  constructor(options) {
@@ -309,9 +508,22 @@ var WorkerModuleLoader = class {
309
508
  resolveSync: resolveInternal
310
509
  };
311
510
  }
312
- fail(detail) {
511
+ /**
512
+ * The `internal/modules/esm/loader` cascaded loader over this mount, created on first use.
513
+ * @returns This loader's one cascaded loader.
514
+ */
515
+ cascadedLoader() {
516
+ return this.cascaded ??= createCascadedLoader(this.internal);
517
+ }
518
+ /**
519
+ * Throw this loader's diagnostic.
520
+ * @param detail - What failed.
521
+ * @param code - Node error code for a resolution failure Node also reports by code.
522
+ */
523
+ fail(detail, code) {
313
524
  const chain = this.stack.length === 0 ? "" : ` (importer chain: ${this.stack.join(" -> ")})`;
314
- throw new Error(`webworker modules: ${detail}${chain}`);
525
+ const error = /* @__PURE__ */ new Error(`webworker modules: ${detail}${chain}`);
526
+ throw code === void 0 ? error : Object.assign(error, { code });
315
527
  }
316
528
  /** @returns Directory a base path or URL resolves specifiers from. */
317
529
  baseDirectoryOf(base) {
@@ -392,7 +604,7 @@ var WorkerModuleLoader = class {
392
604
  }
393
605
  return this.probe(join$1(path, "index"), specifier);
394
606
  }
395
- return this.fail(`cannot resolve "${specifier}": no file at ${candidates.join(", ")}`);
607
+ return this.fail(`cannot resolve "${specifier}": no file at ${candidates.join(", ")}`, "MODULE_NOT_FOUND");
396
608
  }
397
609
  /** @returns The Worker-provided implementation of a static specifier. */
398
610
  staticModule(specifier) {
@@ -431,12 +643,12 @@ var WorkerModuleLoader = class {
431
643
  const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? specifier;
432
644
  const rest = specifier.slice(packageName.length).replace(/^\//, "");
433
645
  const packageDirectory = join$1(this.root, "node_modules", packageName);
434
- if (!this.vfs.existsSync(join$1(packageDirectory, "package.json"))) return this.fail(`cannot resolve "${specifier}": ${packageDirectory}/package.json is not in the image`);
646
+ if (!this.vfs.existsSync(join$1(packageDirectory, "package.json"))) return this.fail(`cannot resolve "${specifier}": ${packageDirectory}/package.json is not in the image`, "MODULE_NOT_FOUND");
435
647
  const manifest = this.manifestOf(packageDirectory);
436
648
  const subpath = rest === "" ? "." : `./${rest}`;
437
649
  if (manifest.exports !== void 0) {
438
650
  const target = this.selectExport(manifest.exports, subpath, packageName);
439
- if (target === void 0) return this.fail(`"${packageName}" does not export "${subpath}" under conditions [${[...this.conditions].join(", ")}]`);
651
+ if (target === void 0) return this.fail(`"${packageName}" does not export "${subpath}" under conditions [${[...this.conditions].join(", ")}]`, "ERR_PACKAGE_PATH_NOT_EXPORTED");
440
652
  return {
441
653
  kind: "file",
442
654
  path: this.probe(join$1(packageDirectory, target), specifier)
@@ -614,19 +826,21 @@ function signalProcess(pid, signal) {
614
826
  /**
615
827
  * The `process` global the worker needs before any VFS module runs. Cordis
616
828
  * reads `process.env` and `process.versions.node` while the Loader is
617
- * constructed, and `cordis.yml` keeps its `!!js process.*` expressions, so the
618
- * configuration bytes stay identical to the Node deployment. Third-party Node
829
+ * constructed, and bundle patch rows keep their `!!js process.*` expressions, so
830
+ * the configuration bytes stay identical to the Node deployment. Third-party Node
619
831
  * packages use the presence of `process.title` to avoid browser-only globals.
620
832
  * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/globals/process
621
833
  */
622
834
  /** Virtual executable identity; the worker has no Node binary behind it. */
623
835
  const EXEC_PATH = "/dsh/bin/node";
836
+ /** Node release whose internal-module members `node-addon-require-builtin` follows in the Worker. */
837
+ const NODE_VERSION = "24.18.0";
624
838
  /**
625
839
  * Publish `globalThis.process`.
626
840
  *
627
- * `versions.node` is `0.0.0` on purpose: it makes Cordis's
628
- * `ModuleLoader.fromInternal()` return undefined instead of reaching for Node
629
- * internals, which is what lets the worker install its own module seam.
841
+ * `versions.node` names the Node release the Worker's `node-addon-require-builtin` internals follow, so Cordis's
842
+ * `ModuleLoader.fromInternal()` reaches them the way it reaches Node's; `execArgv` stays empty, without
843
+ * `--expose-internals`.
630
844
  * @param options - Root, environment, and argument vector.
631
845
  * @returns The published object, for the module proxy table.
632
846
  */
@@ -645,8 +859,8 @@ function installProcessGlobal(options) {
645
859
  platform: "linux",
646
860
  arch: "x64",
647
861
  pid: 1,
648
- version: "v0.0.0",
649
- versions: { node: "0.0.0" },
862
+ version: `v${NODE_VERSION}`,
863
+ versions: { node: NODE_VERSION },
650
864
  cwd: () => options.cwd,
651
865
  getBuiltinModule: (id) => {
652
866
  let resolution;
@@ -1650,6 +1864,8 @@ function resize(bytes, length) {
1650
1864
  */
1651
1865
  var MemoryVfs = class {
1652
1866
  files = /* @__PURE__ */ new Map();
1867
+ /** Files a lazy archive supplies on their first asynchronous read. */
1868
+ deferred = /* @__PURE__ */ new Map();
1653
1869
  directories = new Set(["/"]);
1654
1870
  /** Directory permission bits; absence means {@link DEFAULT_DIRECTORY_MODE}. */
1655
1871
  directoryModes = /* @__PURE__ */ new Map();
@@ -1698,7 +1914,10 @@ var MemoryVfs = class {
1698
1914
  }
1699
1915
  /** Promise face mirroring `node:fs/promises` for the methods the roster uses. */
1700
1916
  promises = {
1701
- readFile: async (path, options) => this.readFileSync(path, options),
1917
+ readFile: async (path, options) => {
1918
+ await this.whenReadable(path);
1919
+ return this.readFileSync(path, options);
1920
+ },
1702
1921
  writeFile: async (path, data, options) => {
1703
1922
  this.writeFileSync(path, data, options);
1704
1923
  },
@@ -1730,11 +1949,13 @@ var MemoryVfs = class {
1730
1949
  this.chmodSync(path, mode);
1731
1950
  },
1732
1951
  opendir: async (path) => this.opendir(path),
1733
- open: async (path, flags, mode) => this.open(path, flags, mode),
1952
+ open: async (path, flags, mode) => {
1953
+ await this.whenReadable(path);
1954
+ return this.open(path, flags, mode);
1955
+ },
1734
1956
  /** Resolves for any existing path: the VFS grants read and write to everything it holds. */
1735
1957
  access: async (path) => {
1736
- const target = normalize$1(resolve$2(path));
1737
- if (!this.files.has(target) && !this.directories.has(target)) fail("ENOENT", "access", target);
1958
+ if (!this.existsSync(path)) fail("ENOENT", "access", normalize$1(resolve$2(path)));
1738
1959
  }
1739
1960
  };
1740
1961
  /** @returns Absolute path with no trailing separator. */
@@ -1743,6 +1964,101 @@ var MemoryVfs = class {
1743
1964
  return absolute.length > 1 && absolute.endsWith("/") ? absolute.slice(0, -1) : absolute;
1744
1965
  }
1745
1966
  /**
1967
+ * Register files whose bytes arrive with one archive on the first
1968
+ * asynchronous read of any of them. Their parent directories are created now.
1969
+ * @param archive - Archive name, mount root, registered files, and fetch.
1970
+ * @throws When a registered path already holds a file, directory, or another registration.
1971
+ */
1972
+ deferArchive(archive) {
1973
+ const root = this.key(archive.root);
1974
+ const files = /* @__PURE__ */ new Map();
1975
+ for (const file of archive.files) {
1976
+ const target = this.key(join$1(root, file.path));
1977
+ if (this.files.has(target) || this.directories.has(target) || this.deferred.has(target) || files.has(target)) throw new Error(`webworker vfs: ${target} is already registered, so lazy archive ${archive.name} cannot supply it`);
1978
+ files.set(target, file.size);
1979
+ }
1980
+ const state = {
1981
+ name: archive.name,
1982
+ root,
1983
+ load: () => archive.load(),
1984
+ files,
1985
+ fetching: void 0
1986
+ };
1987
+ const mtimeMs = Date.now();
1988
+ for (const [target, size] of files) {
1989
+ this.seedDirectory(dirname$1(target));
1990
+ this.deferred.set(target, {
1991
+ size,
1992
+ mode: DEFAULT_FILE_MODE,
1993
+ mtimeMs,
1994
+ archive: state
1995
+ });
1996
+ this.touchDirectory(dirname$1(target));
1997
+ }
1998
+ }
1999
+ /**
2000
+ * Fetch the lazy archive that supplies a registered path. Concurrent readers
2001
+ * of one archive share one fetch; a failed fetch leaves its files registered,
2002
+ * so the next read fetches again.
2003
+ * @param path - Path about to be read asynchronously.
2004
+ * @returns A promise that settles once the archive arrived, or undefined when the path needs no fetch.
2005
+ */
2006
+ whenReadable(path) {
2007
+ const file = this.deferred.get(this.key(path));
2008
+ if (file === void 0) return void 0;
2009
+ file.archive.fetching ??= this.fetchArchive(file.archive);
2010
+ return file.archive.fetching;
2011
+ }
2012
+ /** Fetch, validate against the registration, and mount one archive's files. */
2013
+ async fetchArchive(state) {
2014
+ try {
2015
+ let archive;
2016
+ try {
2017
+ archive = await state.load();
2018
+ } catch (error) {
2019
+ throw new Error(`webworker vfs: lazy archive ${state.name} could not be fetched`, { cause: error });
2020
+ }
2021
+ const entries = /* @__PURE__ */ new Map();
2022
+ for (const entry of parseTar(archive)) {
2023
+ if (entry.directory) continue;
2024
+ const relativeName = entry.name.startsWith("./") ? entry.name.slice(2) : entry.name;
2025
+ const target = this.key(join$1(state.root, relativeName));
2026
+ const size = state.files.get(target);
2027
+ if (size === void 0) throw new Error(`webworker vfs: lazy archive ${state.name} carries unregistered entry "${relativeName}"`);
2028
+ if (entry.bytes.length !== size) throw new Error(`webworker vfs: lazy archive ${state.name} holds ${String(entry.bytes.length)} bytes for ${target}, registered as ${String(size)}`);
2029
+ entries.set(target, entry.bytes);
2030
+ }
2031
+ for (const target of state.files.keys()) if (!entries.has(target)) throw new Error(`webworker vfs: lazy archive ${state.name} lacks registered file ${target}`);
2032
+ for (const [target, bytes] of entries) {
2033
+ const file = this.deferred.get(target);
2034
+ if (file?.archive !== state) continue;
2035
+ this.deferred.delete(target);
2036
+ const identity = this.identities.get(target);
2037
+ this.identities.delete(target);
2038
+ this.setFile(target, {
2039
+ bytes,
2040
+ mtimeMs: file.mtimeMs,
2041
+ mode: file.mode,
2042
+ paths: void 0,
2043
+ ...identity === void 0 ? {} : { identity }
2044
+ });
2045
+ }
2046
+ } catch (error) {
2047
+ state.fetching = void 0;
2048
+ throw error;
2049
+ }
2050
+ }
2051
+ /** Refuse a synchronous operation on a registered file whose bytes have not arrived. */
2052
+ requireArrived(syscall, target) {
2053
+ const file = this.deferred.get(target);
2054
+ if (file === void 0) return;
2055
+ const error = /* @__PURE__ */ new Error(`ERR_DSH_VFS_DEFERRED: ${syscall} cannot wait for '${target}', which lazy archive ${file.archive.name} supplies on its first asynchronous read`);
2056
+ error.code = "ERR_DSH_VFS_DEFERRED";
2057
+ error.path = target;
2058
+ error.syscall = syscall;
2059
+ throw error;
2060
+ }
2061
+ /**
1746
2062
  * Read a file.
1747
2063
  * @param path - File path.
1748
2064
  * @param options - `'utf8'` or `{encoding}` for text; omitted for bytes.
@@ -1750,6 +2066,7 @@ var MemoryVfs = class {
1750
2066
  */
1751
2067
  readFileSync(path, options) {
1752
2068
  const target = this.key(path);
2069
+ this.requireArrived("open", target);
1753
2070
  const node = this.files.get(target);
1754
2071
  if (node === void 0) {
1755
2072
  if (this.directories.has(target)) fail("EISDIR", "read", target);
@@ -1764,7 +2081,7 @@ var MemoryVfs = class {
1764
2081
  */
1765
2082
  existsSync(path) {
1766
2083
  const target = this.key(path);
1767
- return this.files.has(target) || this.directories.has(target);
2084
+ return this.files.has(target) || this.directories.has(target) || this.deferred.has(target);
1768
2085
  }
1769
2086
  /**
1770
2087
  * Stat a path.
@@ -1775,11 +2092,17 @@ var MemoryVfs = class {
1775
2092
  statSync(path, options) {
1776
2093
  const target = this.key(path);
1777
2094
  const node = this.files.get(target);
2095
+ const deferred = this.deferred.get(target);
1778
2096
  const [size, mtimeMs, directory, mode] = node !== void 0 ? [
1779
2097
  node.bytes.length,
1780
2098
  node.mtimeMs,
1781
2099
  false,
1782
2100
  node.mode
2101
+ ] : deferred !== void 0 ? [
2102
+ deferred.size,
2103
+ deferred.mtimeMs,
2104
+ false,
2105
+ deferred.mode
1783
2106
  ] : this.directories.has(target) ? [
1784
2107
  0,
1785
2108
  this.directoryMtimes.get(target) ?? 0,
@@ -1949,12 +2272,16 @@ var MemoryVfs = class {
1949
2272
  readdirSync(path, options) {
1950
2273
  const target = this.key(path);
1951
2274
  if (!this.directories.has(target)) {
1952
- if (this.files.has(target)) fail("ENOTDIR", "scandir", target);
2275
+ if (this.files.has(target) || this.deferred.has(target)) fail("ENOTDIR", "scandir", target);
1953
2276
  fail("ENOENT", "scandir", target);
1954
2277
  }
1955
2278
  const prefix = target === "/" ? "/" : `${target}/`;
1956
2279
  const names = /* @__PURE__ */ new Set();
1957
- for (const candidate of [...this.files.keys(), ...this.directories]) {
2280
+ for (const candidate of [
2281
+ ...this.files.keys(),
2282
+ ...this.deferred.keys(),
2283
+ ...this.directories
2284
+ ]) {
1958
2285
  if (!candidate.startsWith(prefix) || candidate === target) continue;
1959
2286
  const rest = candidate.slice(prefix.length);
1960
2287
  if (rest === "") continue;
@@ -1994,7 +2321,7 @@ var MemoryVfs = class {
1994
2321
  */
1995
2322
  mkdirSync(path, options) {
1996
2323
  const target = this.key(path);
1997
- if (this.files.has(target)) fail("EEXIST", "mkdir", target);
2324
+ if (this.files.has(target) || this.deferred.has(target)) fail("EEXIST", "mkdir", target);
1998
2325
  if (this.directories.has(target)) {
1999
2326
  if (options?.recursive === true) return void 0;
2000
2327
  fail("EEXIST", "mkdir", target);
@@ -2024,6 +2351,7 @@ var MemoryVfs = class {
2024
2351
  */
2025
2352
  writeFileSync(path, data, options) {
2026
2353
  const target = this.key(path);
2354
+ this.requireArrived("open", target);
2027
2355
  if (this.directories.has(target)) fail("EISDIR", "open", target);
2028
2356
  if (!this.directories.has(dirname$1(target))) fail("ENOENT", "open", target);
2029
2357
  const flag = options?.flag ?? "w";
@@ -2154,6 +2482,7 @@ var MemoryVfs = class {
2154
2482
  */
2155
2483
  openFileSync(path, flags = "r", mode) {
2156
2484
  const target = this.key(path);
2485
+ this.requireArrived("open", target);
2157
2486
  const access = openMode(flags);
2158
2487
  const existing = this.files.get(target);
2159
2488
  if (this.directories.has(target)) fail("EISDIR", "open", target);
@@ -2215,6 +2544,7 @@ var MemoryVfs = class {
2215
2544
  */
2216
2545
  appendFileSync(path, data) {
2217
2546
  const target = this.key(path);
2547
+ this.requireArrived("open", target);
2218
2548
  const existing = this.files.get(target);
2219
2549
  const addition = typeof data === "string" ? encoder$1.encode(data) : data;
2220
2550
  if (existing === void 0) {
@@ -2231,6 +2561,8 @@ var MemoryVfs = class {
2231
2561
  renameSync(from, to) {
2232
2562
  const source = this.key(from);
2233
2563
  const destination = this.key(to);
2564
+ this.requireArrived("rename", source);
2565
+ this.requireArrived("rename", destination);
2234
2566
  if (source === destination) return;
2235
2567
  const node = this.files.get(source);
2236
2568
  if (node !== void 0) {
@@ -2259,13 +2591,14 @@ var MemoryVfs = class {
2259
2591
  if (!this.directories.has(source)) fail("ENOENT", "rename", source);
2260
2592
  if (this.files.has(destination)) fail("ENOTDIR", "rename", destination);
2261
2593
  if (!this.directories.has(dirname$1(destination))) fail("ENOENT", "rename", destination);
2594
+ const prefix = `${source}/`;
2595
+ for (const registered of this.deferred.keys()) if (registered.startsWith(prefix)) this.requireArrived("rename", registered);
2262
2596
  if (this.directories.has(destination)) {
2263
2597
  if (this.readdirSync(destination).length > 0) fail("ENOTEMPTY", "rename", destination);
2264
2598
  this.directories.delete(destination);
2265
2599
  this.directoryModes.delete(destination);
2266
2600
  this.directoryMtimes.delete(destination);
2267
2601
  }
2268
- const prefix = `${source}/`;
2269
2602
  const movedFiles = [];
2270
2603
  for (const [candidate, value] of [...this.files]) {
2271
2604
  if (!candidate.startsWith(prefix)) continue;
@@ -2327,9 +2660,10 @@ var MemoryVfs = class {
2327
2660
  linkSync(existing, next) {
2328
2661
  const source = this.key(existing);
2329
2662
  const target = this.key(next);
2663
+ this.requireArrived("link", source);
2330
2664
  const node = this.files.get(source);
2331
2665
  if (node === void 0) fail("ENOENT", "link", source);
2332
- if (this.files.has(target) || this.directories.has(target)) fail("EEXIST", "link", target);
2666
+ if (this.existsSync(target)) fail("EEXIST", "link", target);
2333
2667
  if (!this.directories.has(dirname$1(target))) fail("ENOENT", "link", target);
2334
2668
  this.setFile(target, node);
2335
2669
  this.touchDirectory(dirname$1(target));
@@ -2348,6 +2682,7 @@ var MemoryVfs = class {
2348
2682
  */
2349
2683
  truncateSync(path, length = 0) {
2350
2684
  const target = this.key(path);
2685
+ this.requireArrived("truncate", target);
2351
2686
  const node = this.files.get(target);
2352
2687
  if (node === void 0) fail("ENOENT", "truncate", target);
2353
2688
  this.truncateFile(node, length);
@@ -2359,6 +2694,7 @@ var MemoryVfs = class {
2359
2694
  */
2360
2695
  chmodSync(path, mode) {
2361
2696
  const target = this.key(path);
2697
+ this.requireArrived("chmod", target);
2362
2698
  const node = this.files.get(target);
2363
2699
  if (node !== void 0) {
2364
2700
  this.chmodFile(node, mode);
@@ -2382,7 +2718,7 @@ var MemoryVfs = class {
2382
2718
  */
2383
2719
  unlinkSync(path) {
2384
2720
  const target = this.key(path);
2385
- if (this.deleteFile(target) === void 0) fail("ENOENT", "unlink", target);
2721
+ if (this.deleteFile(target) === void 0 && !this.deferred.delete(target)) fail("ENOENT", "unlink", target);
2386
2722
  this.forgetIdentity(target);
2387
2723
  this.touchDirectory(dirname$1(target));
2388
2724
  this.publish({
@@ -2397,7 +2733,7 @@ var MemoryVfs = class {
2397
2733
  */
2398
2734
  rmSync(path, options) {
2399
2735
  const target = this.key(path);
2400
- if (this.deleteFile(target) !== void 0) {
2736
+ if (this.deleteFile(target) !== void 0 || this.deferred.delete(target)) {
2401
2737
  this.forgetIdentity(target);
2402
2738
  this.touchDirectory(dirname$1(target));
2403
2739
  this.publish({
@@ -2410,6 +2746,7 @@ var MemoryVfs = class {
2410
2746
  if (options?.recursive !== true) fail("ERR_FS_EISDIR", "rm", target);
2411
2747
  const prefix = `${target}/`;
2412
2748
  for (const candidate of [...this.files.keys()]) if (candidate.startsWith(prefix)) this.deleteFile(candidate);
2749
+ for (const candidate of [...this.deferred.keys()]) if (candidate.startsWith(prefix)) this.deferred.delete(candidate);
2413
2750
  for (const candidate of [...this.directories]) {
2414
2751
  if (!candidate.startsWith(prefix)) continue;
2415
2752
  this.directories.delete(candidate);
@@ -2449,6 +2786,7 @@ var MemoryVfs = class {
2449
2786
  seed(path, data, options = {}) {
2450
2787
  const target = this.key(path);
2451
2788
  this.seedDirectory(dirname$1(target));
2789
+ this.deferred.delete(target);
2452
2790
  this.setFile(target, {
2453
2791
  bytes: typeof data === "string" ? encoder$1.encode(data) : data,
2454
2792
  mtimeMs: options.mtimeMs ?? this.touch(target),
@@ -2467,7 +2805,7 @@ var MemoryVfs = class {
2467
2805
  if (!this.directories.has(target)) {
2468
2806
  const parent = dirname$1(target);
2469
2807
  if (parent !== target) this.seedDirectory(parent);
2470
- if (this.files.has(target)) fail("EEXIST", "mkdir", target);
2808
+ if (this.files.has(target) || this.deferred.has(target)) fail("EEXIST", "mkdir", target);
2471
2809
  this.directories.add(target);
2472
2810
  this.directoryMtimes.set(target, options.mtimeMs ?? Date.now());
2473
2811
  this.touchDirectory(parent);
@@ -2492,7 +2830,7 @@ var MemoryVfs = class {
2492
2830
  /**
2493
2831
  * Mount a tar image produced by the build-time collector.
2494
2832
  *
2495
- * Entry names are relative to `root` (`node_modules/...`, `config/cordis.yml`);
2833
+ * Entry names are relative to `root` (`node_modules/...`, `config/vfs-manifest.json`);
2496
2834
  * an absolute entry name is a collector defect and fails loud. File contents
2497
2835
  * stay views into `image` — nothing is copied at mount time.
2498
2836
  * @param image - The ustar archive, as `inflateImage` produces it from the fetched image.
@@ -2604,6 +2942,72 @@ const XDG_OPEN_EXECUTABLE = {
2604
2942
  return { kind: "asynchronous" };
2605
2943
  }
2606
2944
  };
2945
+ //#endregion
2946
+ //#region src/image-manifest.ts
2947
+ /**
2948
+ * The base image's manifest: what the packer records about one image and what
2949
+ * the worker host requires before it wraps a module or boots the tree. Both
2950
+ * sides use these types; the worker validates the fetched file with
2951
+ * {@link parseImageManifest}.
2952
+ */
2953
+ function recordOf(value) {
2954
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2955
+ }
2956
+ /** A relative path made of non-empty segments without traversal. */
2957
+ function isRelativePath(value) {
2958
+ return typeof value === "string" && value !== "" && !value.startsWith("/") && value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
2959
+ }
2960
+ function isCount(value) {
2961
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
2962
+ }
2963
+ /**
2964
+ * Validate a fetched base-image manifest.
2965
+ * @param value - Parsed manifest JSON.
2966
+ * @param path - Manifest path inside the image, named in refusals.
2967
+ * @returns The detached manifest.
2968
+ * @throws When the manifest is malformed, or its bodies were lowered against another wrapper contract.
2969
+ */
2970
+ function parseImageManifest(value, path) {
2971
+ const record = recordOf(value);
2972
+ if (record === void 0) throw new Error(`webworker host: ${path} does not hold an object`);
2973
+ if (record.lowered !== "dsh-worker-transform/1") throw new Error(`webworker host: image was lowered by ${String(record.lowered)}, this build runs ${LOWERING_VERSION}; rebuild the image`);
2974
+ const { root, profile, installation, javascriptEntries, visitedEntries, rewrittenEntries, lazy } = record;
2975
+ if (typeof root !== "string" || typeof profile !== "string" || profile === "" || installation !== void 0 && (typeof installation !== "string" || installation === "") || !isCount(javascriptEntries) || !isCount(visitedEntries) || !isCount(rewrittenEntries) || !Array.isArray(lazy)) throw new Error(`webworker host: ${path} lacks a valid root, profile, installation, entry count, or lazy archive list`);
2976
+ const archives = [];
2977
+ const archiveNames = /* @__PURE__ */ new Set();
2978
+ const filePaths = /* @__PURE__ */ new Set();
2979
+ for (const entry of lazy) {
2980
+ const archive = recordOf(entry);
2981
+ const name = archive?.archive;
2982
+ const files = archive?.files;
2983
+ if (!isRelativePath(name) || archiveNames.has(name) || !Array.isArray(files) || files.length === 0) throw new Error(`webworker host: ${path} lists an invalid or repeated lazy archive`);
2984
+ archiveNames.add(name);
2985
+ const parsed = [];
2986
+ for (const candidate of files) {
2987
+ const file = recordOf(candidate);
2988
+ if (file === void 0 || !isRelativePath(file.path) || !isCount(file.size) || filePaths.has(file.path)) throw new Error(`webworker host: ${path} lists an invalid or repeated file in lazy archive ${name}`);
2989
+ filePaths.add(file.path);
2990
+ parsed.push({
2991
+ path: file.path,
2992
+ size: file.size
2993
+ });
2994
+ }
2995
+ archives.push({
2996
+ archive: name,
2997
+ files: parsed
2998
+ });
2999
+ }
3000
+ return {
3001
+ root,
3002
+ profile,
3003
+ ...installation === void 0 ? {} : { installation },
3004
+ lowered: LOWERING_VERSION,
3005
+ javascriptEntries,
3006
+ visitedEntries,
3007
+ rewrittenEntries,
3008
+ lazy: archives
3009
+ };
3010
+ }
2607
3011
  function requireGlobalPort(channel) {
2608
3012
  if (channel !== void 0) return channel;
2609
3013
  const post = globalThis.postMessage;
@@ -2626,7 +3030,6 @@ async function readImage(image) {
2626
3030
  */
2627
3031
  function createWorkerHost(options) {
2628
3032
  const root = options.root ?? "/dsh";
2629
- const configPath = options.configPath ?? join$1(root, "config/cordis.yml");
2630
3033
  const port = options.port ?? 3080;
2631
3034
  const tunnel = new TunnelServer({
2632
3035
  port: requireGlobalPort(options.channel),
@@ -2643,7 +3046,7 @@ function createWorkerHost(options) {
2643
3046
  const start = async () => {
2644
3047
  try {
2645
3048
  const home = join$1(root, IMAGE_HOME_DIRECTORY);
2646
- installProcessGlobal({
3049
+ const processShim = installProcessGlobal({
2647
3050
  cwd: root,
2648
3051
  env: {
2649
3052
  DSH_HOME: home,
@@ -2657,7 +3060,8 @@ function createWorkerHost(options) {
2657
3060
  for (const directory of IMAGE_EMPTY_DIRECTORIES) mounted.seedDirectory(join$1(root, directory.replace(/\/$/, "")));
2658
3061
  setActiveVfs(mounted);
2659
3062
  vfs = mounted;
2660
- requireLoweredImage(mounted, options.manifestPath ?? join$1(root, "config/vfs-manifest.json"));
3063
+ const manifest = readImageManifest(mounted, options.manifestPath ?? join$1(root, "config/vfs-manifest.json"));
3064
+ deferLazyArchives(mounted, manifest, root, options.image);
2661
3065
  const staticModules = { ...options.staticModules };
2662
3066
  for (const key of ["node:process", "process"]) staticModules[key] ??= () => globalThis.process;
2663
3067
  const loader = new WorkerModuleLoader({
@@ -2669,16 +3073,18 @@ function createWorkerHost(options) {
2669
3073
  });
2670
3074
  setActiveModuleLoader(loader);
2671
3075
  modules = loader;
2672
- const require = loader.requireFrom(dirname$1(configPath));
3076
+ const require = loader.requireFrom(root);
2673
3077
  const appBoot = require("@deepseek-ai/dsh-app-boot");
3078
+ const { DSH_LAUNCH_ENVIRONMENT_KEY } = require("@deepseek-ai/dsh-launch-environment");
2674
3079
  const cmdline = require("@deepseek-ai/dsh-cmdline");
2675
3080
  const started = Promise.withResolvers();
2676
- const { patches, profile } = bootPatches(loader, mounted, configPath, root);
3081
+ const { patches, profile, environment } = prepareProfile(appBoot, mounted, manifest, root, processShim.env);
2677
3082
  const profileConfig = join$1(profile.dir, "cordis.yml");
2678
- const ctx = await appBoot.boot("dsh-webworker", profileConfig, patches, (hostCtx) => {
3083
+ const ctx = await appBoot.boot(BIN_NAME, profileConfig, patches, async (hostCtx) => {
2679
3084
  hostCtx.provide("profileContext", profile);
2680
- hostCtx.loader.internal = loader.internal;
3085
+ hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, environment);
2681
3086
  installLogSink(hostCtx, require);
3087
+ await hostCtx.plugin(appBoot.PluginPackages, {});
2682
3088
  cmdline.provideCmdline(hostCtx, {
2683
3089
  args: [...options.cmdlineArgs ?? [
2684
3090
  "--host",
@@ -2708,7 +3114,7 @@ function createWorkerHost(options) {
2708
3114
  if (typertGateway === void 0) throw new Error("webworker host: the tree activated without a typertGateway service");
2709
3115
  const handler = connection.createSharedFetchHandler("/api");
2710
3116
  const usage = loader.usage();
2711
- 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})`);
3117
+ console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)}, lazy archives=${String(manifest.lazy.length)}, direct lane=connection.createSharedFetchHandler, als causality=${options.alsCausality === void 0 ? "inert" : "snapshot/restore"}, image lowering=${LOWERING_VERSION})`);
2712
3118
  const peer = connection.browserPeer;
2713
3119
  tunnel.serve({
2714
3120
  directFetch: (request) => handler.fetch(request, peer),
@@ -2768,76 +3174,84 @@ function installLogSink(ctx, require) {
2768
3174
  };
2769
3175
  ctx.logger.exporter(exporter);
2770
3176
  }
3177
+ /** Diagnostic prefix the launcher passes to app-boot; the Worker boots the same profile code. */
3178
+ const BIN_NAME = "dsh";
2771
3179
  /**
2772
- * Require the mounted image to carry bodies this build can wrap.
3180
+ * Read and validate the mounted image's manifest.
2773
3181
  *
2774
3182
  * The manifest the packer writes is the single source of truth: the worker holds
2775
3183
  * no transform, so an image that was never lowered — or was lowered against
2776
3184
  * different wrapper semantics — cannot be recovered at load and must be rebuilt.
2777
3185
  * @param vfs - Mounted filesystem.
2778
3186
  * @param path - Manifest path inside the image.
2779
- * @throws When the manifest is missing, unreadable, or names another contract.
3187
+ * @returns The validated manifest.
3188
+ * @throws When the manifest is missing, malformed, or names another contract.
2780
3189
  */
2781
- function requireLoweredImage(vfs, path) {
3190
+ function readImageManifest(vfs, path) {
2782
3191
  if (!vfs.existsSync(path)) throw new Error(`webworker host: ${path} is missing, so the image records no lowering; rebuild the image`);
2783
- const parsed = JSON.parse(vfs.readFileSync(path, "utf8"));
2784
- if (typeof parsed !== "object" || parsed === null) throw new Error(`webworker host: ${path} does not hold an object`);
2785
- const lowered = parsed.lowered;
2786
- 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`);
3192
+ return parseImageManifest(JSON.parse(vfs.readFileSync(path, "utf8")), path);
2787
3193
  }
2788
3194
  /**
2789
- * Prepare an editable VFS profile from the packed rows and deployment overlays.
2790
- * ConfigEditor writes overrides beside the insertion rows; reconciliation starts
2791
- * at the empty profile root and retains the Worker deployment overlays.
2792
- * @param loader - Module loader, for the image's YAML reader.
2793
- * @param vfs - Filesystem holding the composed configuration.
2794
- * @param configPath - Composed configuration path.
2795
- * @param root - Virtual root.
2796
- * @returns Profile locations and effective boot patches.
2797
- */
2798
- function bootPatches(loader, vfs, configPath, root) {
2799
- const text = vfs.readFileSync(configPath, "utf8");
2800
- const include = loader.load(loader.resolve("@deepseek-ai/cordis-plugin-include", root));
2801
- const yaml = loader.load(loader.resolve("js-yaml", root));
2802
- let rows;
2803
- if (configPath.endsWith(".json")) rows = JSON.parse(text);
2804
- else rows = yaml.load(text, { schema: include.entryListSchema });
2805
- const find = (entries, id) => {
2806
- if (!Array.isArray(entries)) return void 0;
2807
- for (const entry of entries) {
2808
- if (entry.id === id) return entry;
2809
- const nested = find(entry.config, id);
2810
- if (nested !== void 0) return nested;
2811
- }
2812
- };
2813
- const configOf = (row) => typeof row.config === "object" && row.config !== null && !Array.isArray(row.config) ? row.config : {};
2814
- const patches = [];
2815
- const jsonl = find(rows, "session-persistence-jsonl");
2816
- if (jsonl !== void 0) patches.push({
2817
- id: "session-persistence-jsonl",
2818
- config: {
2819
- ...configOf(jsonl),
2820
- compression: "none"
2821
- }
2822
- });
2823
- const dir = join$1(root, IMAGE_HOME_DIRECTORY, "profiles/preview");
3195
+ * Register the manifest's lazy archives; each is fetched beside the image on
3196
+ * the first asynchronous read of a file it carries.
3197
+ * @param vfs - Mounted filesystem.
3198
+ * @param manifest - Validated image manifest.
3199
+ * @param root - Virtual root the archives' entries mount under.
3200
+ * @param image - The image bytes or URL; archive names resolve against a URL.
3201
+ * @throws When the image registers lazy archives but arrived as bytes, leaving them unlocatable.
3202
+ */
3203
+ function deferLazyArchives(vfs, manifest, root, image) {
3204
+ if (manifest.lazy.length === 0) return;
3205
+ if (typeof image !== "string") throw new Error("webworker host: the image registers lazy archives, which only an image fetched from a URL can locate");
3206
+ const base = new URL(image, globalThis.location?.href);
3207
+ for (const archive of manifest.lazy) {
3208
+ const url = new URL(archive.archive, base).href;
3209
+ vfs.deferArchive({
3210
+ name: archive.archive,
3211
+ root,
3212
+ files: archive.files,
3213
+ load: () => readImage(url)
3214
+ });
3215
+ }
3216
+ }
3217
+ /**
3218
+ * Prepare the profile the served launcher would boot: the image's app-boot
3219
+ * initializes the manifest's shipped profile template under the Worker home
3220
+ * and composes its bundle layers, user layer, and home layer. The profile
3221
+ * context carries the launcher's facts with no command-line overlay; the
3222
+ * image's home patch, written by the packer, carries the Worker's required
3223
+ * row config (`PLATFORM_ENTRY_CONFIG` in `image-layout.ts`).
3224
+ * @param appBoot - The image's app-boot.
3225
+ * @param vfs - Filesystem holding the profile.
3226
+ * @param manifest - Validated image manifest naming the profile and installation.
3227
+ * @param root - Virtual root, the process working directory.
3228
+ * @param env - The process shim's environment, after the launch layers applied.
3229
+ * @returns Effective boot patches, the profile context, and the launch environment snapshot.
3230
+ * @throws When the image names no installation manifest to anchor bundle resolution.
3231
+ */
3232
+ function prepareProfile(appBoot, vfs, manifest, root, env) {
3233
+ if (manifest.installation === void 0) throw new Error("webworker host: the image names no installation manifest, so no profile bundle can resolve; rebuild the image");
3234
+ const home = join$1(root, IMAGE_HOME_DIRECTORY);
3235
+ const installAnchor = join$1(root, "node_modules", manifest.installation, "package.json");
3236
+ const environment = appBoot.loadLayeredEnv(BIN_NAME, root);
3237
+ const loaded = appBoot.loadProfile(BIN_NAME, manifest.profile, installAnchor, home);
3238
+ appBoot.reportSkippedBundles(BIN_NAME, loaded);
3239
+ vfs.writeFileSync(join$1(loaded.dir, "cordis.yml"), "[]\n");
2824
3240
  const profile = {
2825
- name: "preview",
2826
- dir,
2827
- patchPath: join$1(dir, "cordis.patch.yml"),
2828
- installAnchor: join$1(dir, "package.json"),
3241
+ name: loaded.name,
3242
+ dir: loaded.dir,
3243
+ patchPath: loaded.patchPath,
3244
+ installAnchor,
2829
3245
  cwd: root,
2830
- home: join$1(root, IMAGE_HOME_DIRECTORY),
2831
- startedBundles: [],
2832
- overlays: patches,
2833
- telemetryDisabledEnv: void 0
2834
- };
2835
- vfs.seed(join$1(dir, "package.json"), "{\"private\":true,\"dsh\":{\"profile\":{\"bundles\":[]}}}\n");
2836
- vfs.seed(join$1(dir, "cordis.yml"), "[]\n");
2837
- if (!vfs.existsSync(profile.patchPath)) vfs.seed(profile.patchPath, yaml.dump([{ insert: rows }], { schema: include.entryListSchema }));
3246
+ home,
3247
+ startedBundles: loaded.layers.map((layer) => layer.packageName),
3248
+ overlays: [],
3249
+ telemetryDisabledEnv: env.DSH_TELEMETRY_DISABLED
3250
+ };
2838
3251
  return {
2839
- patches: loader.load(loader.resolve("@deepseek-ai/dsh-app-boot", root)).readProfilePatches("dsh-webworker", profile),
2840
- profile
3252
+ patches: appBoot.readProfilePatches(BIN_NAME, profile, loaded),
3253
+ profile,
3254
+ environment
2841
3255
  };
2842
3256
  }
2843
3257
  /**
@@ -4331,49 +4745,6 @@ var buffer_default = {
4331
4745
  kMaxLength: import_buffer.kMaxLength
4332
4746
  };
4333
4747
  //#endregion
4334
- //#region src/node/notImplementedFail.ts
4335
- /**
4336
- * Structural not-implemented stubs: a replaced module must expose every symbol
4337
- * its importers name (a missing CommonJS symbol degrades to `undefined` at call
4338
- * time instead of failing at link time), and every one of those symbols must
4339
- * report exactly what is unavailable when it is finally called.
4340
- */
4341
- /**
4342
- * Build a function that throws naming its module and symbol. The refusal is
4343
- * also written to the console before it propagates: callers routinely swallow
4344
- * these errors far from their cause, and the console line is what places the
4345
- * failure while debugging a worker session.
4346
- *
4347
- * `Face` is the Node declaration this stub stands in for, so the replaced module
4348
- * publishes the type its importers compile against. The value is one throwing
4349
- * function whatever that declaration says: a caller reaches the throw before any
4350
- * declared parameter, return value, or `new` result exists, so the assertion
4351
- * below cannot be observed as a lie. It is a function expression rather than an
4352
- * arrow because a stub standing in for a class must refuse under `new` too, and
4353
- * an arrow has no construct behavior to reach.
4354
- * @param module - module specifier being stubbed.
4355
- * @param symbol - exported symbol name.
4356
- * @returns the throwing stand-in, typed as the member it replaces.
4357
- */
4358
- function notImplementedFail(module, symbol) {
4359
- return (function refuse() {
4360
- throw notAvailableError(module, symbol);
4361
- });
4362
- }
4363
- /**
4364
- * Build the refusal error and write it to the console first, for stubs that
4365
- * cannot be a plain throwing function (constructors, methods on structural
4366
- * fakes).
4367
- * @param module - module specifier being stubbed.
4368
- * @param symbol - unavailable member, named as the importer sees it.
4369
- * @returns the error to throw.
4370
- */
4371
- function notAvailableError(module, symbol) {
4372
- const message = `web-preview: ${module}.${symbol} is not available in the worker host`;
4373
- console.error(message);
4374
- return new Error(message);
4375
- }
4376
- //#endregion
4377
4748
  //#region src/node/builtin_modules/implemented/async_hooks.ts
4378
4749
  /**
4379
4750
  * `node:async_hooks` for the worker: `AsyncLocalStorage` over an EXPLICIT-SWITCH
@@ -11281,8 +11652,10 @@ const DSH_TMP = `${DSH_ROOT}/tmp`;
11281
11652
  * Node). `../../../../tests/node/path-diff.spec.ts` pins the port below to
11282
11653
  * Node's answers.
11283
11654
  * A `node:` proxy has to answer what Node answers, since VFS paths were built with
11284
- * Node semantics. `win32` members throw: the worker host reports
11285
- * `process.platform === 'linux'`, so a Windows branch means a bug.
11655
+ * Node semantics. `win32` members throw, except `isAbsolute`, which
11656
+ * cross-platform callers use to reject Windows absolute paths on any platform:
11657
+ * the worker host reports `process.platform === 'linux'`, so any other Windows
11658
+ * branch means a bug.
11286
11659
  */
11287
11660
  var path_exports = /* @__PURE__ */ __exportAll({
11288
11661
  __esModule: () => true,
@@ -11614,11 +11987,31 @@ const posix = {
11614
11987
  const win32Member = (name) => () => {
11615
11988
  throw new Error(`web-preview: node:path.win32.${name} is unreachable — the worker host reports platform "linux"`);
11616
11989
  };
11617
- /** Windows member set: reaching it means a platform branch went the wrong way. */
11990
+ const CHAR_BACKWARD_SLASH = 92;
11991
+ const CHAR_COLON = 58;
11992
+ /** Node's win32 separator test: `/` or `\`. */
11993
+ function isWin32Separator(code) {
11994
+ return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
11995
+ }
11996
+ /** Node's drive-letter test: `A`–`Z` or `a`–`z`. */
11997
+ function isWindowsDeviceRoot(code) {
11998
+ return code >= 65 && code <= 90 || code >= 97 && code <= 122;
11999
+ }
12000
+ /**
12001
+ * Node's `path.win32.isAbsolute`, a string check cross-platform callers make on every platform.
12002
+ * @param path - Path to classify.
12003
+ * @returns Whether a Windows host would treat the path as absolute.
12004
+ */
12005
+ function win32IsAbsolute(path) {
12006
+ if (path.length === 0) return false;
12007
+ const code = path.charCodeAt(0);
12008
+ return isWin32Separator(code) || path.length > 2 && isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON && isWin32Separator(path.charCodeAt(2));
12009
+ }
12010
+ /** Windows member set: reaching a member other than `isAbsolute` means a platform branch went the wrong way. */
11618
12011
  const win32 = {
11619
12012
  resolve: win32Member("resolve"),
11620
12013
  normalize: win32Member("normalize"),
11621
- isAbsolute: win32Member("isAbsolute"),
12014
+ isAbsolute: win32IsAbsolute,
11622
12015
  join: win32Member("join"),
11623
12016
  relative: win32Member("relative"),
11624
12017
  dirname: win32Member("dirname"),
@@ -11692,6 +12085,18 @@ const statOrMissing = (path, bigint) => {
11692
12085
  const statsChanged = (left, right) => left.size !== right.size || left.mtimeMs !== right.mtimeMs || left.mode !== right.mode || left.ino !== right.ino || left.isFile() !== right.isFile() || left.isDirectory() !== right.isDirectory();
11693
12086
  const contains$1 = (parent, child) => parent === "/" || child === parent || child.startsWith(`${parent}/`);
11694
12087
  const overlaps = (left, right) => contains$1(left, right) || contains$1(right, left);
12088
+ /**
12089
+ * Run a watcher callback in the async context where its watcher was created, as Node delivers watch events from the
12090
+ * watch handle: stores captured at creation apply, and every other store reads `undefined` instead of the store of the
12091
+ * code whose write produced the event.
12092
+ * @param context - Context captured when the watcher was created.
12093
+ * @param callback - Event dispatch.
12094
+ */
12095
+ const dispatchInWatcherContext = (context, callback) => {
12096
+ runAtAsyncContextRoot(() => {
12097
+ runWithAsyncContext(context, callback);
12098
+ });
12099
+ };
11695
12100
  /** `fs.FSWatcher` over VFS mutations. */
11696
12101
  var FSWatcher = class extends EventEmitter {
11697
12102
  target;
@@ -11716,7 +12121,7 @@ var FSWatcher = class extends EventEmitter {
11716
12121
  const filename = this.filename(mutation.path);
11717
12122
  queueMicrotask(() => {
11718
12123
  if (this.closed) return;
11719
- runWithAsyncContext(context, () => {
12124
+ dispatchInWatcherContext(context, () => {
11720
12125
  this.emit("change", eventType, filename);
11721
12126
  });
11722
12127
  });
@@ -11823,7 +12228,7 @@ var StatWatcher = class extends EventEmitter {
11823
12228
  const current = statOrMissing(this.path, this.bigint);
11824
12229
  const previous = this.previous;
11825
12230
  this.previous = current;
11826
- if (initialMissing || statsChanged(current, previous)) runWithAsyncContext(this.context, () => {
12231
+ if (initialMissing || statsChanged(current, previous)) dispatchInWatcherContext(this.context, () => {
11827
12232
  this.emit("change", current, previous);
11828
12233
  });
11829
12234
  }, this.interval);
@@ -12468,6 +12873,21 @@ function closeFileStream(stream, callback) {
12468
12873
  });
12469
12874
  stream.destroy();
12470
12875
  }
12876
+ /** Open a read stream's descriptor and announce it, completing `_construct`. */
12877
+ function openReadDescriptor(stream, path, flags, callback) {
12878
+ let fd;
12879
+ try {
12880
+ fd = openSync(path, flags);
12881
+ } catch (error) {
12882
+ callback(error);
12883
+ return;
12884
+ }
12885
+ stream.fd = fd;
12886
+ stream.pending = false;
12887
+ callback();
12888
+ stream.emit("open", fd);
12889
+ stream.emit("ready");
12890
+ }
12471
12891
  /** Read stream over one VFS file. */
12472
12892
  var ReadStream = class extends Readable {
12473
12893
  /** Resolved path opened by this stream. */
@@ -12511,18 +12931,16 @@ var ReadStream = class extends Readable {
12511
12931
  callback(abortError$1(this.signal.reason));
12512
12932
  return;
12513
12933
  }
12514
- let fd;
12515
- try {
12516
- fd = openSync(this.path, this.flags);
12517
- } catch (error) {
12518
- callback(error);
12934
+ const arrival = vfs().whenReadable(this.path);
12935
+ if (arrival === void 0) {
12936
+ openReadDescriptor(this, this.path, this.flags, callback);
12519
12937
  return;
12520
12938
  }
12521
- this.fd = fd;
12522
- this.pending = false;
12523
- callback();
12524
- this.emit("open", fd);
12525
- this.emit("ready");
12939
+ arrival.then(() => {
12940
+ openReadDescriptor(this, this.path, this.flags, callback);
12941
+ }, (error) => {
12942
+ callback(error);
12943
+ });
12526
12944
  }
12527
12945
  _read(size) {
12528
12946
  if (this.fd === null) return;
@@ -12686,7 +13104,10 @@ function opendirSync(path) {
12686
13104
  * check here is that every name is a real `node:fs/promises` export.
12687
13105
  */
12688
13106
  const promises = {
12689
- readFile: async (path, options) => readFileSync(path, options),
13107
+ readFile: async (path, options) => {
13108
+ await vfs().whenReadable(asPath(path));
13109
+ return readFileSync(path, options);
13110
+ },
12690
13111
  writeFile: async (path, data, options) => {
12691
13112
  const flag = typeof options === "object" && options !== null ? options.flag : void 0;
12692
13113
  const mode = typeof options === "object" && options !== null ? options.mode : void 0;
@@ -12734,12 +13155,16 @@ const promises = {
12734
13155
  return;
12735
13156
  }
12736
13157
  mkdirSync(dirname(target), { recursive: true });
13158
+ await vfs().whenReadable(source);
12737
13159
  writeFileSync(target, bytesOf(source));
12738
13160
  },
12739
13161
  link: async (from, to) => {
12740
13162
  linkSync(from, to);
12741
13163
  },
12742
- open: async (path, flags, mode) => openHandleSync(path, flags, mode),
13164
+ open: async (path, flags, mode) => {
13165
+ await vfs().whenReadable(asPath(path));
13166
+ return openHandleSync(path, flags, mode);
13167
+ },
12743
13168
  opendir: async (path) => opendirSync(path),
12744
13169
  truncate: async (path, length = 0) => {
12745
13170
  vfs().truncateSync(asPath(path), length);
@@ -13397,6 +13822,7 @@ var util_exports = /* @__PURE__ */ __exportAll({
13397
13822
  default: () => util_default,
13398
13823
  deprecate: () => deprecate,
13399
13824
  format: () => format,
13825
+ inherits: () => inherits,
13400
13826
  inspect: () => inspect,
13401
13827
  isDeepStrictEqual: () => isDeepStrictEqual,
13402
13828
  parseArgs: () => parseArgs,
@@ -13509,6 +13935,33 @@ function parseArgs() {
13509
13935
  function deprecate(fn) {
13510
13936
  return fn;
13511
13937
  }
13938
+ /**
13939
+ * Node's `ERR_INVALID_ARG_TYPE` refusal for {@link inherits}.
13940
+ * @param name - Argument name.
13941
+ * @param expected - Expected type.
13942
+ * @param actual - Received value.
13943
+ * @returns The error to throw.
13944
+ */
13945
+ function invalidArgType(name, expected, actual) {
13946
+ return Object.assign(/* @__PURE__ */ new TypeError(`The "${name}" argument must be of type ${expected}. Received ${String(actual)}`), { code: "ERR_INVALID_ARG_TYPE" });
13947
+ }
13948
+ /**
13949
+ * Node's `util.inherits`: link `ctor.prototype` to `superCtor.prototype` and record `ctor.super_`.
13950
+ * @param ctor - Constructor that inherits.
13951
+ * @param superCtor - Constructor inherited from.
13952
+ */
13953
+ function inherits(ctor, superCtor) {
13954
+ if (ctor === void 0 || ctor === null) throw invalidArgType("ctor", "Function", ctor);
13955
+ if (superCtor === void 0 || superCtor === null) throw invalidArgType("superCtor", "Function", superCtor);
13956
+ const parent = superCtor.prototype;
13957
+ if (parent === void 0) throw invalidArgType("superCtor.prototype", "Object", parent);
13958
+ Object.defineProperty(ctor, "super_", {
13959
+ value: superCtor,
13960
+ writable: true,
13961
+ configurable: true
13962
+ });
13963
+ Object.setPrototypeOf(ctor.prototype, parent);
13964
+ }
13512
13965
  /** Text decoder class, as `node:util` re-exports it. */
13513
13966
  const TextDecoderClass = globalThis.TextDecoder;
13514
13967
  /** Text encoder class, as `node:util` re-exports it. */
@@ -13523,6 +13976,7 @@ var util_default = {
13523
13976
  types: types$1,
13524
13977
  parseArgs,
13525
13978
  deprecate,
13979
+ inherits,
13526
13980
  TextDecoder: TextDecoderClass,
13527
13981
  TextEncoder: TextEncoderClass
13528
13982
  };
@@ -21708,6 +22162,76 @@ var got_exports = /* @__PURE__ */ __exportAll({
21708
22162
  /** Reject product telemetry HTTP requests without opening a connection. */
21709
22163
  const got = { post: notImplementedFail("got", "post") };
21710
22164
  //#endregion
22165
+ //#region src/node/internal_modules/package-surfaces.ts
22166
+ /**
22167
+ * Node internal members HMR reads and replaces while expiring package configuration:
22168
+ * `internal/modules/cjs/loader`'s `Module` caches and loaders, `internal/modules/package_json_reader`, and the
22169
+ * `modules` binding from `internal/bootstrap/realm`. The Worker's own `require` does not route through them, so
22170
+ * they keep Node's member names for replacement and refuse when called.
22171
+ * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/internal_modules/package-surfaces
22172
+ */
22173
+ const CJS_LOADER = "internal/modules/cjs/loader";
22174
+ /** `Module` with the static members HMR reads and replaces. */
22175
+ const Module = Object.assign(function Module() {
22176
+ throw notAvailableError(CJS_LOADER, "Module");
22177
+ }, {
22178
+ /** Empty: the Worker loader keeps its own resolution cache. */
22179
+ _pathCache: Object.create(null),
22180
+ _resolveFilename: notImplementedFail(CJS_LOADER, "Module._resolveFilename"),
22181
+ _load: notImplementedFail(CJS_LOADER, "Module._load")
22182
+ });
22183
+ /** `internal/modules/package_json_reader` with the member HMR replaces. */
22184
+ const packageJsonReader = { getNearestParentPackageJSON: notImplementedFail("internal/modules/package_json_reader", "getNearestParentPackageJSON") };
22185
+ /** `internalBinding('modules')` with the members HMR replaces. */
22186
+ const modulesBinding = {
22187
+ readPackageJSON: notImplementedFail("internalBinding(\"modules\")", "readPackageJSON"),
22188
+ getPackageScopeConfig: notImplementedFail("internalBinding(\"modules\")", "getPackageScopeConfig"),
22189
+ getPackageType: notImplementedFail("internalBinding(\"modules\")", "getPackageType")
22190
+ };
22191
+ /** `internal/bootstrap/realm`: only the `modules` binding is provided. */
22192
+ const realm = {
22193
+ /**
22194
+ * Answer one internal binding.
22195
+ * @param name - Binding name.
22196
+ * @returns The `modules` binding.
22197
+ * @throws Error with `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` for any other binding.
22198
+ */
22199
+ internalBinding(name) {
22200
+ if (name === "modules") return modulesBinding;
22201
+ throw notAvailableError("internal/bootstrap/realm", `internalBinding(${JSON.stringify(name)})`);
22202
+ } };
22203
+ //#endregion
22204
+ //#region src/node/external_packages/node-addon-require-builtin.ts
22205
+ /**
22206
+ * `node-addon-require-builtin` for the Worker. Without `--expose-internals`, the vendored Loader, app-boot, and HMR
22207
+ * read Node internal modules through this package's `requireBuiltin(id)`; the Worker answers the ids those callers
22208
+ * read with members that follow Node v24.18.0 and refuses every other id with Node's platform code.
22209
+ * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/external_packages/node-addon-require-builtin
22210
+ */
22211
+ var node_addon_require_builtin_exports = /* @__PURE__ */ __exportAll({ requireBuiltin: () => requireBuiltin });
22212
+ /** Mutable module objects: HMR replaces members on them and restores the originals. */
22213
+ const INTERNAL_MODULES = new Map([
22214
+ ["internal/modules/esm/loader", { getOrInitializeCascadedLoader: () => requireActiveModuleLoader().cascadedLoader() }],
22215
+ ["internal/modules/esm/module_map", {
22216
+ LoadCache,
22217
+ ResolveCache
22218
+ }],
22219
+ ["internal/modules/cjs/loader", { Module }],
22220
+ ["internal/modules/package_json_reader", packageJsonReader],
22221
+ ["internal/bootstrap/realm", realm]
22222
+ ]);
22223
+ /**
22224
+ * Answer one Node internal module.
22225
+ * @param moduleId - Node internal module id.
22226
+ * @returns The Worker's module object for that id.
22227
+ * @throws Error with `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` for an id the Worker does not provide.
22228
+ */
22229
+ function requireBuiltin(moduleId) {
22230
+ const module = INTERNAL_MODULES.get(moduleId);
22231
+ if (module === void 0) throw notAvailableError("node-addon-require-builtin", `requireBuiltin(${JSON.stringify(moduleId)})`);
22232
+ return module;
22233
+ }
22234
+ //#endregion
21711
22235
  //#region src/node/external_packages/pi-ai.ts
21712
22236
  /**
21713
22237
  * `@earendil-works/pi-ai` stub, including its `/providers/all` and `/api/*.lazy`
@@ -21945,6 +22469,7 @@ const REPLACED_EXTERNAL_PACKAGES = [
21945
22469
  "node-pty",
21946
22470
  "execa",
21947
22471
  "got",
22472
+ "node-addon-require-builtin",
21948
22473
  "sharp",
21949
22474
  "ws"
21950
22475
  ];
@@ -22013,6 +22538,7 @@ const EXTERNALS = {
22013
22538
  "node-pty": () => node_pty_exports,
22014
22539
  "execa": () => execa_exports,
22015
22540
  "got": () => got_exports,
22541
+ "node-addon-require-builtin": () => node_addon_require_builtin_exports,
22016
22542
  "ws": () => ws_exports,
22017
22543
  "@vscode/ripgrep": () => ripgrep_exports,
22018
22544
  "@earendil-works/pi-ai": () => pi_ai_exports