@deepseek-ai/dsh-experimental-webworker-runtime 0.2.1-rc.5 → 0.2.2-alpha.3

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/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { parse } from "acorn";
2
+ import { analyze } from "eslint-scope";
3
+ import { KEYS } from "eslint-visitor-keys";
2
4
  //#region \0rolldown/runtime.js
3
5
  var __defProp = Object.defineProperty;
4
6
  var __exportAll = (all, no_symbols) => {
@@ -878,9 +880,11 @@ const MODULE_PROXIES = {
878
880
  "dns/promises": "./node/builtin_modules/mock/dns/promises.ts",
879
881
  "node:net": "./node/builtin_modules/mock/net.ts",
880
882
  "node:stream": "./node/builtin_modules/implemented/stream.ts",
883
+ "node:string_decoder": "./node/builtin_modules/implemented/string_decoder.ts",
881
884
  "node:stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
882
885
  "stream/promises": "./node/builtin_modules/implemented/stream/promises.ts",
883
886
  "node:vm": "./node/builtin_modules/mock/vm.ts",
887
+ "node:repl": "./node/builtin_modules/mock/repl.ts",
884
888
  "node:worker_threads": "./node/builtin_modules/mock/worker_threads.ts",
885
889
  "node:sqlite": "./node/builtin_modules/mock/sqlite.ts",
886
890
  "@deepseek-ai/libreoffice-kit": "./node/external_packages/libreoffice-kit.ts",
@@ -892,6 +896,7 @@ const MODULE_PROXIES = {
892
896
  "got": "./node/external_packages/got.ts",
893
897
  "@vscode/ripgrep": "./node/external_packages/ripgrep.ts",
894
898
  "@earendil-works/pi-ai": "./node/external_packages/pi-ai.ts",
899
+ "@earendil-works/pi-coding-agent": "./node/external_packages/pi-coding-agent.ts",
895
900
  "node-addon-require-builtin": "./node/external_packages/node-addon-require-builtin.ts",
896
901
  "ws": "./node/external_packages/ws.ts"
897
902
  };
@@ -908,6 +913,7 @@ const MODULE_PROXY_PREFIXES = { "@earendil-works/pi-ai/": "./node/external_packa
908
913
  /** Package or subpath specifiers served from the worker bundle instead of the VFS. */
909
914
  const REPLACED_EXTERNAL_PACKAGES = [
910
915
  "@earendil-works/pi-ai",
916
+ "@earendil-works/pi-coding-agent",
911
917
  "@vscode/ripgrep",
912
918
  "@deepseek-ai/libreoffice-kit",
913
919
  "@deepseek-ai/node-addon-system/flock",
@@ -1052,6 +1058,14 @@ function createSyntheticExchange(frame, sink) {
1052
1058
  * so **line numbers survive**: a stack frame in a transformed module points at
1053
1059
  * the same line as the built artifact it came from.
1054
1060
  *
1061
+ * Named and default imports stay live: every reference reads the required
1062
+ * module object when it runs (`__dsh$m1["x"]`), never a copy taken at the
1063
+ * import statement, so a module in an import cycle sees a binding once the
1064
+ * exporting module has initialized it, as ES modules do. Scope analysis tells
1065
+ * those references apart from same-named locals that shadow them; it runs only
1066
+ * for a module that declares or assigns an import's name somewhere, because
1067
+ * elsewhere every read of the name is the import.
1068
+ *
1055
1069
  * The image packer is this transform's only caller: it lowers every JavaScript
1056
1070
  * entry it packs and records `LOWERING_VERSION` in the image manifest, so the
1057
1071
  * worker wraps those bodies without carrying a compiler of its own.
@@ -1059,7 +1073,7 @@ function createSyntheticExchange(frame, sink) {
1059
1073
  */
1060
1074
  const HELPER_SOURCE = {
1061
1075
  def: "const __dsh$def=(t,k,get)=>Object.defineProperty(t,k,{enumerable:true,configurable:true,get});",
1062
- default: "const __dsh$default=(m)=>(m&&m.__esModule?m.default:m);",
1076
+ default: "const __dsh$default=(m)=>(m&&m.__esModule&&Object.prototype.hasOwnProperty.call(m,\"default\")?m.default:m);",
1063
1077
  ns: "const __dsh$ns=(m)=>(m&&m.__esModule?m:Object.assign({},m,{default:m}));",
1064
1078
  exportAll: "const __dsh$exportAll=(t,m)=>{for(const k of Object.keys(m))if(k!==\"default\"&&!(k in t))__dsh$def(t,k,()=>m[k]);};",
1065
1079
  dynImport: "const __dsh$dynImport=(s)=>Promise.resolve().then(()=>__dsh$ns(require(s)));"
@@ -1076,6 +1090,8 @@ function countNewlines(text) {
1076
1090
  for (let index = text.indexOf("\n"); index >= 0; index = text.indexOf("\n", index + 1)) count += 1;
1077
1091
  return count;
1078
1092
  }
1093
+ /** Prefix of the identifiers this transform emits; a module declaring one would capture them. */
1094
+ const RESERVED_PREFIX = "__dsh$";
1079
1095
  var Transformer = class {
1080
1096
  path;
1081
1097
  edits = [];
@@ -1088,6 +1104,18 @@ var Transformer = class {
1088
1104
  moduleRequests = /* @__PURE__ */ new Set();
1089
1105
  metaResolveRequests = /* @__PURE__ */ new Set();
1090
1106
  createRequireBindings = /* @__PURE__ */ new Set();
1107
+ /** Module temporary per import declaration with specifiers, assigned before traversal. */
1108
+ importModules = /* @__PURE__ */ new Map();
1109
+ /** Named and default imports by local name. */
1110
+ importedBindings = /* @__PURE__ */ new Map();
1111
+ /** Read-position identifiers spelling an import's name, rewritten after traversal. */
1112
+ importCandidates = [];
1113
+ /**
1114
+ * Set when an import name (or the reserved prefix) is declared or assigned.
1115
+ * Only then can a candidate be a shadowing local, so only then does scope
1116
+ * analysis decide which candidates read the import.
1117
+ */
1118
+ scopeAnalysisNeeded = false;
1091
1119
  constructor(source, path) {
1092
1120
  this.path = path;
1093
1121
  this.source = source.startsWith("#!") ? `//${source.slice(2)}` : source;
@@ -1160,29 +1188,69 @@ var Transformer = class {
1160
1188
  if (Array.isArray(node.attributes) && node.attributes.length > 0) this.fail("import attributes are not supported", node.start);
1161
1189
  const source = node.source;
1162
1190
  const request = `require(${this.literal(source)})`;
1163
- const specifiers = node.specifiers;
1164
- if (specifiers.length === 0) {
1191
+ const held = this.importModules.get(node);
1192
+ if (held === void 0) {
1165
1193
  this.replace(node.start, node.end, `${request};`);
1166
1194
  return;
1167
1195
  }
1168
- const held = this.moduleTemp();
1169
1196
  const lines = [`const ${held}=${request};`];
1170
- for (const specifier of specifiers) {
1171
- const local = specifier.local.name;
1172
- if (specifier.type === "ImportDefaultSpecifier") {
1173
- lines.push(`const ${local}=${this.helper("default")}(${held});`);
1174
- continue;
1175
- }
1176
- if (specifier.type === "ImportNamespaceSpecifier") {
1177
- lines.push(`const ${local}=${this.helper("ns")}(${held});`);
1178
- continue;
1179
- }
1180
- const imported = specifier.imported;
1181
- const name = imported.type === "Identifier" ? imported.name : imported.value;
1182
- lines.push(`const ${local}=${held}[${JSON.stringify(name)}];`);
1183
- }
1197
+ for (const specifier of node.specifiers) if (specifier.type === "ImportNamespaceSpecifier") lines.push(`const ${nameOf(specifier.local)}=${this.helper("ns")}(${held});`);
1184
1198
  this.replace(node.start, node.end, lines.join(""));
1185
1199
  }
1200
+ /**
1201
+ * The expression that reads an import when the reference runs. The default
1202
+ * form is parenthesized so it stays one operand in any position, including
1203
+ * `new` callees and member objects.
1204
+ */
1205
+ importAccess(binding) {
1206
+ return binding.kind === "default" ? `(${this.helper("default")}(${binding.held}))` : `${binding.held}[${JSON.stringify(binding.imported)}]`;
1207
+ }
1208
+ /** @returns How a local export reads its binding: imports through their module object. */
1209
+ bindingAccess(local) {
1210
+ const imported = this.importedBindings.get(local);
1211
+ return imported === void 0 ? local : this.importAccess(imported);
1212
+ }
1213
+ /**
1214
+ * Record an identifier that spells an import's name in a read position, and
1215
+ * flag one in a declaration or assignment position (or one using the
1216
+ * reserved prefix there), which makes scope analysis decide the candidates.
1217
+ * @param node - The identifier.
1218
+ * @param parent - Its parent node.
1219
+ * @param key - The parent property holding it.
1220
+ * @param statementStart - Start of the enclosing expression statement when that statement sits in a statement list.
1221
+ */
1222
+ identifier(node, parent, key, statementStart) {
1223
+ if (parent === void 0 || this.importedBindings.size === 0) return;
1224
+ const name = node.name;
1225
+ const binding = this.importedBindings.get(name);
1226
+ if (binding === void 0 && !name.startsWith(RESERVED_PREFIX)) return;
1227
+ if (isBindingPosition(parent, key)) {
1228
+ this.scopeAnalysisNeeded = true;
1229
+ return;
1230
+ }
1231
+ if (binding !== void 0 && isReadPosition(parent, key)) this.importCandidates.push({
1232
+ node,
1233
+ parent,
1234
+ key,
1235
+ statementStart,
1236
+ binding
1237
+ });
1238
+ }
1239
+ /** Flag a declared or assigned name that could shadow an import or capture an emitted temporary. */
1240
+ bindingOccurrence(name) {
1241
+ if (this.importedBindings.size === 0) return;
1242
+ if (this.importedBindings.has(name) || name.startsWith(RESERVED_PREFIX)) this.scopeAnalysisNeeded = true;
1243
+ }
1244
+ /** Rewrite one identifier that reads an import. */
1245
+ importReference({ node, parent, key, statementStart, binding }) {
1246
+ const access = this.importAccess(binding);
1247
+ if (parent.type === "Property" && parent.shorthand === true) {
1248
+ this.replace(node.start, node.end, `${nameOf(node)}:${access}`);
1249
+ return;
1250
+ }
1251
+ const text = (parent.type === "CallExpression" && key === "callee" || parent.type === "TaggedTemplateExpression" && key === "tag") && binding.kind === "named" ? `(0,${access})` : access;
1252
+ this.replace(node.start, node.end, text.startsWith("(") && node.start === statementStart ? `;${text}` : text);
1253
+ }
1186
1254
  exportNamed(node) {
1187
1255
  this.moduleSyntax = true;
1188
1256
  const declaration = node.declaration;
@@ -1295,16 +1363,25 @@ var Transformer = class {
1295
1363
  `finally{if(!${exhausted})${ALS}.resume(await ${ALS}.pause(${ALS}.close(${iterator})))}}`
1296
1364
  ].join(""));
1297
1365
  }
1298
- visit(node, context) {
1366
+ visit(node, context, parent, key) {
1299
1367
  if (node === null || typeof node !== "object") return;
1300
1368
  if (Array.isArray(node)) {
1301
- for (const child of node) this.visit(child, context);
1369
+ for (const child of node) this.visit(child, context, parent, key);
1302
1370
  return;
1303
1371
  }
1304
1372
  const record = node;
1305
1373
  if (typeof record.type !== "string") return;
1306
1374
  let next = context;
1307
1375
  switch (record.type) {
1376
+ case "Identifier":
1377
+ this.identifier(record, parent, key, context.statementStart);
1378
+ break;
1379
+ case "ObjectPattern":
1380
+ for (const property of record.properties) {
1381
+ const value = property.value;
1382
+ if (value?.type === "Identifier") this.bindingOccurrence(value.name);
1383
+ }
1384
+ break;
1308
1385
  case "ImportDeclaration":
1309
1386
  this.importDeclaration(record);
1310
1387
  break;
@@ -1328,6 +1405,7 @@ var Transformer = class {
1328
1405
  case "CallExpression": {
1329
1406
  const callee = record.callee;
1330
1407
  const callArguments = record.arguments;
1408
+ if (callee.type === "Identifier" && callee.name === "eval" && record.optional !== true && this.importedBindings.size > 0) this.fail("direct eval cannot see imports once they are read through their module", record.start);
1331
1409
  if (this.isRequireCall(callee, context.moduleScope) && callArguments.length === 1 && typeof callArguments[0]?.value === "string") this.moduleRequests.add(callArguments[0].value);
1332
1410
  if (callee.type === "MemberExpression") {
1333
1411
  const object = callee.object;
@@ -1388,11 +1466,12 @@ var Transformer = class {
1388
1466
  }
1389
1467
  if (record.type === "ExpressionStatement") next = {
1390
1468
  ...next,
1391
- statement: record
1469
+ statement: record,
1470
+ statementStart: inStatementList(parent, key) ? record.start : void 0
1392
1471
  };
1393
- for (const [key, value] of Object.entries(record)) {
1394
- if (key === "type" || key === "start" || key === "end") continue;
1395
- this.visit(value, next);
1472
+ for (const [childKey, value] of Object.entries(record)) {
1473
+ if (childKey === "type" || childKey === "start" || childKey === "end") continue;
1474
+ this.visit(value, next, record, childKey);
1396
1475
  }
1397
1476
  }
1398
1477
  isCreateRequireCall(node) {
@@ -1415,10 +1494,73 @@ var Transformer = class {
1415
1494
  const source = statement.source;
1416
1495
  if (source.value !== "node:module" && source.value !== "module") continue;
1417
1496
  for (const specifier of statement.specifiers) {
1418
- if (specifier.type !== "ImportSpecifier" || nameOf(specifier.imported) !== "createRequire") continue;
1419
- this.createRequireBindings.add(nameOf(specifier.local));
1497
+ if (specifier.type !== "ImportSpecifier" || specifierName(specifier.imported) !== "createRequire") continue;
1498
+ this.createRequireBindings.add(specifier.local.name);
1499
+ }
1500
+ }
1501
+ }
1502
+ /**
1503
+ * Give every import declaration its module temporary and record its named and
1504
+ * default bindings before traversal, because references may precede the
1505
+ * declaration in source order.
1506
+ */
1507
+ indexImports(program) {
1508
+ for (const statement of program.body) {
1509
+ if (statement.type !== "ImportDeclaration" || statement.specifiers.length === 0) continue;
1510
+ const held = this.moduleTemp();
1511
+ this.importModules.set(statement, held);
1512
+ for (const specifier of statement.specifiers) if (specifier.type === "ImportDefaultSpecifier") this.importedBindings.set(specifier.local.name, {
1513
+ held,
1514
+ kind: "default",
1515
+ imported: "default"
1516
+ });
1517
+ else if (specifier.type === "ImportSpecifier") this.importedBindings.set(specifier.local.name, {
1518
+ held,
1519
+ kind: "named",
1520
+ imported: specifierName(specifier.imported)
1521
+ });
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Decide with scope analysis which candidates read an import.
1526
+ *
1527
+ * Refuses an assignment to an import, which live reads cannot express, and a
1528
+ * declaration using the reserved prefix, which would capture an emitted
1529
+ * module temporary. Direct `eval` is refused during traversal.
1530
+ * @param program - The parsed module, which gains node ranges here.
1531
+ * @returns The identifier nodes that read a named or default import.
1532
+ */
1533
+ resolveImportReads(program) {
1534
+ assignRanges(program);
1535
+ const manager = analyze(program, {
1536
+ ecmaVersion: 2022,
1537
+ sourceType: "module",
1538
+ childVisitorKeys: KEYS
1539
+ });
1540
+ for (const scope of manager.scopes) for (const variable of scope.variables) {
1541
+ const definition = variable.defs[0];
1542
+ if (definition === void 0 || !variable.name.startsWith(RESERVED_PREFIX)) continue;
1543
+ this.fail(`${variable.name} uses the transform's reserved ${RESERVED_PREFIX} prefix`, this.rangeStart(definition.name));
1544
+ }
1545
+ const reads = /* @__PURE__ */ new Set();
1546
+ const moduleScope = manager.scopes.find((scope) => scope.type === "module");
1547
+ for (const variable of moduleScope?.variables ?? []) {
1548
+ if (!this.importedBindings.has(variable.name) || variable.defs[0]?.type !== "ImportBinding") continue;
1549
+ for (const reference of variable.references) {
1550
+ if (reference.isWrite()) this.fail(`the import ${variable.name} cannot be assigned`, this.rangeStart(reference.identifier));
1551
+ reads.add(reference.identifier);
1420
1552
  }
1421
1553
  }
1554
+ return reads;
1555
+ }
1556
+ /**
1557
+ * @param node - A node scope analysis reports.
1558
+ * @returns Its source offset; `assignRanges` gave every node a range.
1559
+ */
1560
+ rangeStart(node) {
1561
+ const start = node.range?.[0];
1562
+ if (start === void 0) this.fail("scope analysis reported a node without a source range", 0);
1563
+ return start;
1422
1564
  }
1423
1565
  run() {
1424
1566
  if (this.source.includes(`${ALS}.pause(`) || this.source.includes("__als$")) this.fail("the module is already lowered; check the image manifest wiring", 0);
@@ -1433,17 +1575,21 @@ var Transformer = class {
1433
1575
  this.fail(`parse failed: ${reason.message}`, 0);
1434
1576
  }
1435
1577
  this.indexCreateRequireImports(program);
1578
+ this.indexImports(program);
1436
1579
  this.visit(program, {
1437
1580
  asyncGenerator: false,
1438
1581
  functionDepth: 0,
1439
1582
  moduleScope: true
1440
1583
  });
1584
+ const reads = this.scopeAnalysisNeeded ? this.resolveImportReads(program) : void 0;
1585
+ for (const candidate of this.importCandidates) if (reads === void 0 || reads.has(candidate.node)) this.importReference(candidate);
1441
1586
  if (this.edits.length === 0 && !this.moduleSyntax) return this.source;
1442
1587
  const prologue = [];
1443
1588
  if (this.moduleSyntax) prologue.push("\"use strict\";Object.defineProperty(exports,\"__esModule\",{value:true});");
1589
+ const getters = this.bindings.map(({ exported, local }) => `__dsh$def(exports,${JSON.stringify(exported)},()=>${this.bindingAccess(local)});`);
1444
1590
  if (this.bindings.length > 0) this.helper("def");
1445
1591
  for (const [name, source] of Object.entries(HELPER_SOURCE)) if (this.helpers.has(name)) prologue.push(source);
1446
- for (const { exported, local } of this.bindings) prologue.push(`__dsh$def(exports,${JSON.stringify(exported)},()=>${local});`);
1592
+ prologue.push(...getters);
1447
1593
  const sorted = [...this.edits].sort((left, right) => left.start - right.start || left.end - right.end);
1448
1594
  const render = (from, to) => {
1449
1595
  let cursor = from;
@@ -1472,6 +1618,96 @@ var Transformer = class {
1472
1618
  function nameOf(node) {
1473
1619
  return node.type === "Identifier" ? node.name : String(node.value);
1474
1620
  }
1621
+ /** @returns The name an import or export specifier spells, as an identifier or a string literal. */
1622
+ function specifierName(node) {
1623
+ return node.type === "Identifier" ? node.name : String(node.value);
1624
+ }
1625
+ /**
1626
+ * Give every node the `range` that eslint-scope reads. The parse leaves ranges
1627
+ * out because only modules that need scope analysis use them.
1628
+ * @param node - A node or node array of the parsed module.
1629
+ */
1630
+ function assignRanges(node) {
1631
+ if (node === null || typeof node !== "object") return;
1632
+ if (Array.isArray(node)) {
1633
+ for (const child of node) assignRanges(child);
1634
+ return;
1635
+ }
1636
+ const record = node;
1637
+ if (typeof record.type !== "string") return;
1638
+ record.range = [record.start, record.end];
1639
+ for (const value of Object.values(record)) assignRanges(value);
1640
+ }
1641
+ /**
1642
+ * ESTree positions where an identifier names a declared or assigned binding.
1643
+ * Property values inside an object pattern are checked at the pattern.
1644
+ *
1645
+ * Together with the ObjectPattern check, this must recognize every ESTree
1646
+ * declaration and assignment form: a missed form lets a shadowing local skip
1647
+ * scope analysis and be rewritten as an import read. A parser upgrade that adds
1648
+ * a binding form must extend this list and the shadowing cases in
1649
+ * `transform-imports.spec.ts`.
1650
+ * @param parent - Parent of the identifier.
1651
+ * @param key - The parent property holding it.
1652
+ * @returns Whether the identifier declares or assigns a binding.
1653
+ */
1654
+ function isBindingPosition(parent, key) {
1655
+ switch (parent.type) {
1656
+ case "VariableDeclarator": return key === "id";
1657
+ case "FunctionDeclaration":
1658
+ case "FunctionExpression":
1659
+ case "ArrowFunctionExpression": return key === "id" || key === "params";
1660
+ case "ClassDeclaration":
1661
+ case "ClassExpression": return key === "id";
1662
+ case "CatchClause": return key === "param";
1663
+ case "ArrayPattern":
1664
+ case "RestElement":
1665
+ case "UpdateExpression": return true;
1666
+ case "AssignmentPattern":
1667
+ case "AssignmentExpression":
1668
+ case "ForInStatement":
1669
+ case "ForOfStatement": return key === "left";
1670
+ default: return false;
1671
+ }
1672
+ }
1673
+ /**
1674
+ * ESTree positions where an identifier, outside a binding position, reads a
1675
+ * binding rather than naming a property, label, specifier, or meta property.
1676
+ * Positions not listed read a binding, so the binding positions must be ruled
1677
+ * out first (`isBindingPosition`).
1678
+ * @param parent - Parent of the identifier.
1679
+ * @param key - The parent property holding it.
1680
+ * @returns Whether the identifier reads a binding.
1681
+ */
1682
+ function isReadPosition(parent, key) {
1683
+ switch (parent.type) {
1684
+ case "MemberExpression": return key !== "property" || parent.computed === true;
1685
+ case "Property":
1686
+ case "MethodDefinition":
1687
+ case "PropertyDefinition": return key !== "key" || parent.computed === true;
1688
+ case "LabeledStatement":
1689
+ case "BreakStatement":
1690
+ case "ContinueStatement":
1691
+ case "ImportSpecifier":
1692
+ case "ImportDefaultSpecifier":
1693
+ case "ImportNamespaceSpecifier":
1694
+ case "ExportSpecifier":
1695
+ case "ExportAllDeclaration":
1696
+ case "MetaProperty": return false;
1697
+ default: return true;
1698
+ }
1699
+ }
1700
+ /**
1701
+ * @param parent - Parent of a statement.
1702
+ * @param key - The parent property holding it.
1703
+ * @returns Whether the statement sits in a statement list, where it can follow
1704
+ * a statement that ends without a semicolon.
1705
+ */
1706
+ function inStatementList(parent, key) {
1707
+ if (parent === void 0) return false;
1708
+ if (parent.type === "SwitchCase") return key === "consequent";
1709
+ return key === "body" && (parent.type === "Program" || parent.type === "BlockStatement" || parent.type === "StaticBlock");
1710
+ }
1475
1711
  /** Every binding an exported declaration introduces, including patterns. */
1476
1712
  function declaredBindings(declaration, fail) {
1477
1713
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
@@ -1564,6 +1800,8 @@ function lowerModuleSource(options) {
1564
1800
  *
1565
1801
  * - `GET /__boot__` answers from tunnel glue, never from the host API surface,
1566
1802
  * because the page needs the boot payload before its Cordis tree exists.
1803
+ * `GET /__assets__` answers the same way with every package asset of the
1804
+ * image, which the page maps before any Client bundle runs.
1567
1805
  * - Privileged `/api` methods take that same direct entry. The method set is not
1568
1806
  * restated here: a 401 or 403 from the route lane is retried on the direct
1569
1807
  * lane because the page owns the worker and needs no network authentication.
@@ -1965,6 +2203,10 @@ var TunnelServer = class {
1965
2203
  this.serveBoot(frame, sink);
1966
2204
  return;
1967
2205
  }
2206
+ if (path === "/__assets__") {
2207
+ this.servePackageAssets(frame, sink);
2208
+ return;
2209
+ }
1968
2210
  if (path.startsWith(`/api/`)) {
1969
2211
  await this.serveApi(frame, routed, path, sink);
1970
2212
  return;
@@ -2030,6 +2272,24 @@ var TunnelServer = class {
2030
2272
  }
2031
2273
  buffered.flushTo(sink);
2032
2274
  }
2275
+ servePackageAssets(frame, sink) {
2276
+ if (this.seams === void 0) throw new Error("webworker tunnel: package assets requested before the host tree is serving");
2277
+ if (frame.method !== "GET") {
2278
+ sink.end({
2279
+ status: 405,
2280
+ headers: { allow: "GET" }
2281
+ });
2282
+ return;
2283
+ }
2284
+ sink.end({
2285
+ status: 200,
2286
+ headers: {
2287
+ "content-type": "application/x-tar",
2288
+ "cache-control": "no-store"
2289
+ },
2290
+ body: this.seams.packageAssets()
2291
+ });
2292
+ }
2033
2293
  serveBoot(frame, sink) {
2034
2294
  if (this.seams === void 0) throw new Error("webworker tunnel: boot payload requested before the host tree is serving");
2035
2295
  if (frame.method !== "GET") {
@@ -2144,7 +2404,7 @@ const EXEC_PATH = "/dsh/bin/node";
2144
2404
  /** Node release whose internal-module members `node-addon-require-builtin` follows in the Worker. */
2145
2405
  const NODE_VERSION = "24.18.0";
2146
2406
  /**
2147
- * Publish `globalThis.process`.
2407
+ * Publish `globalThis.process` and Node's `global` alias for the worker lifetime.
2148
2408
  *
2149
2409
  * `versions.node` names the Node release the Worker's `node-addon-require-builtin` internals follow, so Cordis's
2150
2410
  * `ModuleLoader.fromInternal()` reaches them the way it reaches Node's; `execArgv` stays empty, without
@@ -2213,6 +2473,7 @@ function installProcessGlobal(options) {
2213
2473
  console.warn(`webworker process: exit(${String(code ?? 0)}) requested; the worker keeps running`);
2214
2474
  }
2215
2475
  };
2476
+ globalThis.global = globalThis;
2216
2477
  globalThis.process = shim;
2217
2478
  return shim;
2218
2479
  }
@@ -3597,6 +3858,50 @@ function requireActiveVfs() {
3597
3858
  return active;
3598
3859
  }
3599
3860
  //#endregion
3861
+ //#region src/package-assets.ts
3862
+ /**
3863
+ * Package assets for the preview page. A Client bundle names each image as a
3864
+ * document-relative reference, `plugins/<package>/assets/<path>`, and resolves
3865
+ * it through the module loader's synchronous `require.asset`. The page of the
3866
+ * Worker preview cannot request such a URL itself, so the Worker hands it every
3867
+ * asset the image holds in one archive and the page maps each reference to a
3868
+ * Blob URL before any bundle runs.
3869
+ * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/package-assets
3870
+ */
3871
+ /** Entries of one directory. */
3872
+ function entriesOf(vfs, directory) {
3873
+ return vfs.readdirSync(directory, { withFileTypes: true });
3874
+ }
3875
+ /** Files under one directory, as `/`-separated paths relative to it. */
3876
+ function filesBelow(vfs, directory, prefix = "") {
3877
+ return entriesOf(vfs, directory).flatMap((entry) => entry.isDirectory() ? filesBelow(vfs, join(directory, entry.name), `${prefix}${entry.name}/`) : entry.isFile() ? [`${prefix}${entry.name}`] : []);
3878
+ }
3879
+ /** Installed package directories under `node_modules`, scoped ones included. */
3880
+ function packageDirectories(vfs, modules) {
3881
+ if (!vfs.existsSync(modules)) return [];
3882
+ return entriesOf(vfs, modules).filter((entry) => entry.isDirectory()).flatMap((entry) => entry.name.startsWith("@") ? entriesOf(vfs, join(modules, entry.name)).filter((scoped) => scoped.isDirectory()).map((scoped) => join(modules, entry.name, scoped.name)) : [join(modules, entry.name)]);
3883
+ }
3884
+ /**
3885
+ * Archive every file a package of the image publishes through `"./assets/*": "./lib/assets/*"`.
3886
+ * @param vfs - Mounted image filesystem.
3887
+ * @param root - Image root holding `node_modules`.
3888
+ * @returns An uncompressed tar whose entries are named `plugins/<package>/assets/<path>` with the path unencoded,
3889
+ * so every name fits the ustar limits the base image already met.
3890
+ */
3891
+ function packPackageAssets(vfs, root) {
3892
+ const files = {};
3893
+ for (const directory of packageDirectories(vfs, join(root, "node_modules"))) {
3894
+ const manifestPath = join(directory, "package.json");
3895
+ if (!vfs.existsSync(manifestPath)) continue;
3896
+ const manifest = JSON.parse(vfs.readFileSync(manifestPath, "utf8"));
3897
+ if (typeof manifest.name !== "string" || manifest.exports?.["./assets/*"] !== "./lib/assets/*") continue;
3898
+ const assets = join(directory, "lib", "assets");
3899
+ if (!vfs.existsSync(assets)) continue;
3900
+ for (const path of filesBelow(vfs, assets)) files[`plugins/${manifest.name}/assets/${path}`] = vfs.readFileSync(join(assets, path));
3901
+ }
3902
+ return packTar(files);
3903
+ }
3904
+ //#endregion
3600
3905
  //#region src/image-manifest.ts
3601
3906
  /**
3602
3907
  * The base image's manifest: what the packer records about one image and what
@@ -3769,7 +4074,8 @@ function createWorkerHost(options) {
3769
4074
  const peer = connection.browserPeer;
3770
4075
  tunnel.serve({
3771
4076
  directFetch: (request) => handler.fetch(request, peer),
3772
- bootPayload: () => readBootPayload(ctx),
4077
+ bootPayload: () => readBootPayload(ctx, peer),
4078
+ packageAssets: () => packPackageAssets(mounted, root),
3773
4079
  openStream: (endpoint, payload, uplink, signal) => typertGateway.wireStream.open(endpoint, payload, uplink, peer, signal),
3774
4080
  streamFailure: typertGateway.wireStream.failure
3775
4081
  });
@@ -3911,12 +4217,13 @@ function prepareProfile(appBoot, vfs, manifest, root, env) {
3911
4217
  * from the in-process webserver service, never from the API surface, because
3912
4218
  * the page has no Cordis tree yet.
3913
4219
  * @param ctx - Booted host context.
4220
+ * @param peer - the browser Peer the page speaks for.
3914
4221
  * @returns Boot payload for `GET /__boot__`.
3915
4222
  */
3916
- function readBootPayload(ctx) {
4223
+ function readBootPayload(ctx, peer) {
3917
4224
  const webServer = ctx.get("webServer");
3918
4225
  if (webServer === void 0) throw new Error("webworker host: no webServer service, so the page cannot receive its boot injections");
3919
- return { injections: webServer.collectIndexInjections() };
4226
+ return { injections: webServer.collectIndexInjections(peer) };
3920
4227
  }
3921
4228
  //#endregion
3922
4229
  //#region src/fixture-manifest.ts
@@ -72,6 +72,11 @@ export declare class WorkerTunnel {
72
72
  * @returns The payload the page applies before the client tree loads.
73
73
  */
74
74
  bootPayload(): Promise<BootPayload>;
75
+ /**
76
+ * Read every package asset of the image and map each reference to a Blob URL.
77
+ * @returns The synchronous resolver the module system calls for `require.asset`.
78
+ */
79
+ packageAssets(): Promise<(reference: string) => string>;
75
80
  /**
76
81
  * `loadBundle` seam: take one client bundle through the tunnel and execute it
77
82
  * as a classic script, exactly like the shell's same-origin `<script src>`.
@@ -51,7 +51,7 @@ export declare class AsyncLocalStorage<T> {
51
51
  /**
52
52
  * Copy every live instance's current store. Not part of the Node face: this is
53
53
  * the shim's own mechanism, kept in the class so the overlay stays private.
54
- * @returns the snapshot, or undefined when no instance has a store.
54
+ * @returns the snapshot, or undefined when no instance has a store or explicit context.
55
55
  */
56
56
  static captureContext(): AsyncContextSnapshot | undefined;
57
57
  /**
@@ -90,8 +90,8 @@ export type AsyncContextSnapshot = readonly CapturedStore[];
90
90
  export type AmbientSnapshot = readonly CapturedStore[];
91
91
  /**
92
92
  * Copy every live instance's current store.
93
- * @returns the snapshot, or undefined when no instance has a store (the hook
94
- * layer then wraps nothing and callbacks inherit the stack top).
93
+ * @returns the snapshot, or undefined when no instance has a store or explicit
94
+ * context (the hook layer then wraps nothing and callbacks inherit the stack top).
95
95
  */
96
96
  export declare function captureAsyncContext(): AsyncContextSnapshot | undefined;
97
97
  /**
@@ -2,6 +2,13 @@
2
2
  * `node:buffer` for the worker, backed by the `buffer` npm package (feross), and
3
3
  * the matching `globalThis.Buffer` install. Node code treats Buffer as ambient,
4
4
  * so the global must exist before any host module evaluates.
5
+ *
6
+ * Loading this module replaces `Buffer.from`, `Buffer.prototype.toString`, and
7
+ * `toLocaleString` on the package's class, so every Buffer user in the Worker,
8
+ * including bundled third-party code, gets the same conversions. Whole-buffer
9
+ * UTF-8 conversions use `TextEncoder` and `TextDecoder` with Node's U+FFFD
10
+ * replacement and BOM retention; other encodings and ranged `toString` calls
11
+ * keep the package's implementation.
5
12
  */
6
13
  import { Buffer, kMaxLength } from 'buffer';
7
14
  export { Buffer, kMaxLength };
@@ -0,0 +1,12 @@
1
+ /** Node string decoding backed by the maintained npm implementation used by readable-stream. */
2
+ import '../../../string-decoder-types.ts';
3
+ import { StringDecoder } from 'string_decoder/';
4
+ export { StringDecoder };
5
+ /** CommonJS interop marker consumed by the worker module loader. */
6
+ export declare const __esModule = true;
7
+ /** CommonJS-compatible namespace for default imports. */
8
+ declare const _default: {
9
+ StringDecoder: typeof StringDecoder;
10
+ };
11
+ export default _default;
12
+ //# sourceMappingURL=string_decoder.d.ts.map
@@ -0,0 +1,10 @@
1
+ /** REPL construction is unavailable in the browser worker. */
2
+ export declare const start: typeof import('node:repl').start;
3
+ /** CommonJS interop marker consumed by the Worker module loader. */
4
+ export declare const __esModule = true;
5
+ /** CommonJS export for the REPL member used by the Node PTC evaluator. */
6
+ declare const _default: {
7
+ start: typeof import("repl").start;
8
+ };
9
+ export default _default;
10
+ //# sourceMappingURL=repl.d.ts.map