@deepseek-ai/dsh-app-boot 0.1.5-rc.2 → 0.1.6-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { createRequire } from "node:module";
1
+ import { createRequire, isBuiltin } from "node:module";
2
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { parseEnv } from "node:util";
5
- import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import * as yaml from "js-yaml";
7
7
  import { Context, Service } from "@deepseek-ai/cordis";
8
8
  import Loader, { EntryGroup, EntryTree, isJsExpr } from "@deepseek-ai/cordis-plugin-loader";
@@ -12,7 +12,8 @@ import Group from "@deepseek-ai/cordis-plugin-group";
12
12
  import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
13
13
  import { createLaunchEnvironmentSnapshot } from "@deepseek-ai/dsh-launch-environment";
14
14
  import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
15
- import { resolve as resolve$1 } from "resolve.exports";
15
+ import { imports, resolve as resolve$1 } from "resolve.exports";
16
+ import { getEnvironmentData, setEnvironmentData } from "node:worker_threads";
16
17
  //#region ../../../vendor/include/src/index.ts
17
18
  const JsExpr = new yaml.Type("tag:yaml.org,2002:js", {
18
19
  kind: "scalar",
@@ -45,8 +46,7 @@ function retryableWriteError(error) {
45
46
  * Apply patch lists to an entry list — THE patch semantics of this include,
46
47
  * shared by mounting (`applyPatches`) and offline config tooling
47
48
  * (`dsh --dump-config`) so a dump can never drift from what boots. The input
48
- * is never mutated and the result is always detached from it (even with no
49
- * patches): patching or mounting shared entry objects would bake earlier
49
+ * is never mutated: patching shared entry objects would bake earlier patch
50
50
  * values into the cached parse, so repeated application (config hot-reloads)
51
51
  * could never revert a removed or changed patch. Inserted entries are indexed
52
52
  * as they are added, so a later patch in the same list can target a row an
@@ -57,8 +57,8 @@ function retryableWriteError(error) {
57
57
  * @returns a detached entry list with every applicable patch applied.
58
58
  */
59
59
  function applyEntryPatches(data, patches, warn) {
60
+ if (!patches?.length) return [...data];
60
61
  data = structuredClone(data);
61
- if (!patches?.length) return data;
62
62
  const entryMap = /* @__PURE__ */ new Map();
63
63
  const buildMap = (entries) => {
64
64
  for (const entry of entries) {
@@ -106,14 +106,6 @@ function applyEntryPatches(data, patches, warn) {
106
106
  }
107
107
  return data;
108
108
  }
109
- var ConfigFileError = class extends Error {
110
- stage;
111
- constructor(stage, path, cause) {
112
- super(`failed to ${stage} config file ${path}`, { cause });
113
- this.stage = stage;
114
- this.name = "ConfigFileError";
115
- }
116
- };
117
109
  /** Loader entry tree backed by a YAML or JSON file. */
118
110
  var Include = class extends EntryTree {
119
111
  config;
@@ -127,7 +119,6 @@ var Include = class extends EntryTree {
127
119
  writeTask;
128
120
  pendingWrite;
129
121
  writeQueue = Promise.resolve();
130
- applyQueue = Promise.resolve();
131
122
  constructor(ctx, config) {
132
123
  super(ctx);
133
124
  this.config = config;
@@ -138,29 +129,15 @@ var Include = class extends EntryTree {
138
129
  this.type = writable[ext];
139
130
  this.readonly = !this.type;
140
131
  this.ctx.baseUrl = new URL(".", pathToFileURL(this.filename)).href;
141
- ctx.on("internal/update", async (config, _, next) => {
132
+ ctx.on("internal/update", (config, _, next) => {
142
133
  if (config.path !== this.config.path) return next();
143
- await this.enqueue(async () => {
144
- const data = this.applyPatches(this.data, config.patches);
145
- await this.root.update(data);
146
- this.config = config;
134
+ this.config = config;
135
+ this.root.update(this.applyPatches(this.data, config.patches)).catch((error) => {
136
+ this.ctx.logger.warn("config update at %C failed", this.filename);
137
+ this.ctx.logger.warn(error);
147
138
  });
148
139
  });
149
140
  }
150
- /**
151
- * Serialize one child-tree mutation behind every earlier one. The group's
152
- * transactional `update` is not reentrant: two concurrent applies (the init
153
- * apply racing an HMR-triggered refresh from the watcher's initial scan)
154
- * interleave create and rollback on the same entries and strand the include
155
- * fiber without settling, so every apply path funnels through this queue.
156
- * A predecessor's failure is its own caller's outcome and never gates the
157
- * next task.
158
- */
159
- enqueue(task) {
160
- const run = this.applyQueue.then(task, task);
161
- this.applyQueue = run.then(() => {}, () => {});
162
- return run;
163
- }
164
141
  async checkAccess() {
165
142
  if (!this.type) return;
166
143
  try {
@@ -170,77 +147,63 @@ var Include = class extends EntryTree {
170
147
  }
171
148
  }
172
149
  async read(forced = false) {
173
- let content;
174
- try {
175
- content = await readFile(this.filename, "utf8");
176
- } catch (error) {
177
- throw new ConfigFileError("read", this.filename, error);
178
- }
179
- if (!forced && this.content === content) return;
150
+ const content = await readFile(this.filename, "utf8");
151
+ if (!forced && this.content === content) return false;
180
152
  let data;
181
- try {
182
- if (this.type === "application/yaml") data = yaml.load(content, { schema });
183
- else if (this.type === "application/json") data = JSON.parse(content);
184
- else {
185
- const module = await import(
186
- /* @vite-ignore */
187
- this.filename
153
+ if (this.type === "application/yaml") data = yaml.load(content, { schema: entryListSchema });
154
+ else if (this.type === "application/json") data = JSON.parse(content);
155
+ else {
156
+ const module = await import(
157
+ /* @vite-ignore */
158
+ this.filename
188
159
  );
189
- data = module.default || module;
190
- }
191
- } catch (error) {
192
- throw new ConfigFileError("parse", this.filename, error);
160
+ data = module.default || module;
193
161
  }
194
- if (!Array.isArray(data)) throw new ConfigFileError("validate", this.filename, /* @__PURE__ */ new TypeError("config file must be a top-level array"));
195
- return {
196
- content,
197
- data
198
- };
162
+ if (!Array.isArray(data)) throw new TypeError(`config file must be a top-level array of entries: ${this.filename}`);
163
+ this.content = content;
164
+ this.data = data;
165
+ await this.checkAccess();
166
+ return true;
199
167
  }
200
- applyPatches(data, patches) {
168
+ applyPatches(data, patches = this.config.patches) {
201
169
  return applyEntryPatches(data, patches, (message, ...args) => {
202
170
  this.ctx.root.logger?.("loader").warn(message, ...args);
203
171
  });
204
172
  }
205
173
  async *[Service.init]() {
206
- let candidate;
207
174
  try {
208
- candidate = await this.read(true);
175
+ await this.read();
209
176
  } catch (error) {
210
- if (!(error instanceof ConfigFileError) || error.stage !== "read" || error.cause?.code !== "ENOENT") throw error;
177
+ if (error?.code !== "ENOENT") throw error;
211
178
  if (this.config.initial) {
212
179
  await this._writeFile(this.config.initial);
213
- candidate = await this.read(true);
180
+ await this.read(true);
214
181
  } else throw new Error(`config file not found: ${this.filename}`);
215
182
  }
216
183
  yield () => this.stop();
217
- await this.apply(candidate);
184
+ await this.root.update(this.applyPatches(this.data));
218
185
  }
219
186
  async stop() {
220
- await this.root.stop();
221
- await this.flushWrite();
187
+ try {
188
+ await this.flushWrite();
189
+ } finally {
190
+ this.root.stop();
191
+ await this.flushWrite();
192
+ }
222
193
  }
223
194
  /**
224
- * Re-read the file and transactionally refresh child entries when content changed.
225
- * @returns a promise resolving after the new tree commits, or immediately when unchanged.
226
- * @throws when reading, parsing, validation, application, or rollback fails; the last good tree remains active when rollback succeeds.
195
+ * Re-read the file and refresh child entries when content changed. An
196
+ * unreadable or unparsable file logs a warning and keeps the last good
197
+ * tree: a hot-reload of a live app must never take the process down.
227
198
  */
228
199
  async refresh() {
229
- await this.enqueue(async () => {
230
- const candidate = await this.read();
231
- if (!candidate) return;
232
- await this._apply(candidate);
233
- });
234
- }
235
- apply(candidate) {
236
- return this.enqueue(() => this._apply(candidate));
237
- }
238
- async _apply(candidate) {
239
- const data = this.applyPatches(candidate.data, this.config.patches);
240
- await this.root.update(data);
241
- this.content = candidate.content;
242
- this.data = candidate.data;
243
- await this.checkAccess();
200
+ try {
201
+ if (!await this.read()) return;
202
+ await this.root.update(this.applyPatches(this.data));
203
+ } catch (error) {
204
+ this.ctx.logger.warn("config reload at %C failed; keeping the running tree", this.filename);
205
+ this.ctx.logger.warn(error);
206
+ }
244
207
  }
245
208
  async _writeFile(config) {
246
209
  if (this.readonly) throw new Error(`cannot overwrite readonly config`);
@@ -283,6 +246,70 @@ var Include = class extends EntryTree {
283
246
  }
284
247
  };
285
248
  //#endregion
249
+ //#region lib/types/profile-resolution/legacy-links.js
250
+ /** Legacy profile-link inspection shared by the disk materializer and runtime resolver. */
251
+ /** Profile-private package links projected into its pnpm-managed node_modules. */
252
+ const PROFILE_MODULE_FALLBACK_DIR = ".dsh-module-fallback";
253
+ /**
254
+ * Return whether the process reads application modules from pkg's virtual filesystem.
255
+ * @returns whether pkg owns the module filesystem.
256
+ */
257
+ function isPackagedExecutable() {
258
+ return process.pkg !== void 0;
259
+ }
260
+ /**
261
+ * Resolve a directory through the active carrier's filesystem implementation.
262
+ * @param path - directory path to canonicalize.
263
+ * @returns the canonical directory path.
264
+ */
265
+ function realModuleDirectory(path) {
266
+ return isPackagedExecutable() ? realpathSync(path) : realpathSync.native(path);
267
+ }
268
+ /**
269
+ * Resolve a link target without following the final path component.
270
+ * @param path - candidate path whose parent is canonicalized.
271
+ * @returns the canonical candidate, or undefined when its parent is absent.
272
+ */
273
+ function canonicalLinkPath(path) {
274
+ try {
275
+ return join(realModuleDirectory(dirname(path)), basename(path));
276
+ } catch (error) {
277
+ /* v8 ignore next 2 -- a non-ENOENT realpath failure requires a host filesystem fault */
278
+ if (error.code === "ENOENT") return void 0;
279
+ /* v8 ignore next -- see the host-filesystem exception above */
280
+ throw error;
281
+ }
282
+ }
283
+ /**
284
+ * Return whether a symlink or junction points at the same path as `target`.
285
+ * @param link - symlink or junction to inspect.
286
+ * @param target - expected target path.
287
+ * @returns whether both paths identify the same entry.
288
+ */
289
+ function symlinkPointsTo(link, target) {
290
+ const canonicalActual = canonicalLinkPath(resolve(dirname(link), readlinkSync(link)));
291
+ const canonicalTarget = canonicalLinkPath(resolve(target));
292
+ return canonicalActual !== void 0 && canonicalActual === canonicalTarget;
293
+ }
294
+ /**
295
+ * Return whether an observed profile package must not claim local precedence.
296
+ * @param profileDir - profile directory containing the package projection.
297
+ * @param packageName - bare package name to inspect.
298
+ * @returns whether the entry is a managed fallback link or disappeared during inspection.
299
+ */
300
+ function isProfileModuleFallbackLink(profileDir, packageName) {
301
+ const link = join(profileDir, "node_modules", packageName);
302
+ const target = join(profileDir, PROFILE_MODULE_FALLBACK_DIR, "node_modules", packageName);
303
+ try {
304
+ return lstatSync(link).isSymbolicLink() && symlinkPointsTo(link, target);
305
+ } catch (error) {
306
+ /* v8 ignore next 2 -- a vanished candidate cannot claim local precedence */
307
+ if (error.code === "ENOENT") return true;
308
+ /* v8 ignore next -- non-ENOENT lstat/readlink failures require a host filesystem fault */
309
+ throw error;
310
+ }
311
+ }
312
+ //#endregion
286
313
  //#region lib/types/profile.js
287
314
  /**
288
315
  * Profile discovery, initialization, and patch-layer composition for the
@@ -312,8 +339,6 @@ var Include = class extends EntryTree {
312
339
  const PROFILES_DIR = "profiles";
313
340
  /** The user patch layer inside a profile directory (hot-reloaded on long-lived surfaces). */
314
341
  const PROFILE_PATCH_FILENAME = "cordis.patch.yml";
315
- /** Profile-private package links projected into its pnpm-managed node_modules. */
316
- const PROFILE_MODULE_FALLBACK_DIR = ".dsh-module-fallback";
317
342
  /**
318
343
  * Resolve a profile's directory under the Harness home.
319
344
  * @param name - the profile name (`dsh --profile <name>`).
@@ -326,26 +351,11 @@ function resolveProfileDir(name, home = resolveDshHome()) {
326
351
  }
327
352
  /** The shipped profile templates auto-initialized on first use, by name. */
328
353
  const PROFILE_TEMPLATES = {
329
- acp: {
330
- bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-acp-app"],
331
- patchReload: "startup"
332
- },
333
- web: {
334
- bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"],
335
- patchReload: "live"
336
- },
337
- headless: {
338
- bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"],
339
- patchReload: "startup"
340
- },
341
- sdk: {
342
- bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-sdk-app"],
343
- patchReload: "startup"
344
- },
345
- "sdk-minimal": {
346
- bundles: ["@deepseek-ai/dsh-sdk-minimal"],
347
- patchReload: "startup"
348
- }
354
+ acp: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-acp-app"] },
355
+ web: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] },
356
+ headless: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"] },
357
+ sdk: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-sdk-app"] },
358
+ "sdk-minimal": { bundles: ["@deepseek-ai/dsh-sdk-minimal"] }
349
359
  };
350
360
  /** Installation-owned bundle tuples normalized to the shipped template. */
351
361
  const INSTALLATION_OWNED_PROFILE_TUPLES = { headless: [
@@ -355,8 +365,13 @@ const INSTALLATION_OWNED_PROFILE_TUPLES = { headless: [
355
365
  ] };
356
366
  /** The bundle list a `dsh plugin` init uses for a name with no shipped template. */
357
367
  const DEFAULT_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base"];
358
- /** Custom profiles retain the historical live patch-file behavior. */
359
- const DEFAULT_PROFILE_PATCH_RELOAD = "live";
368
+ /**
369
+ * The bundles the dsh installation ships for a person to switch on: each a
370
+ * runtime dependency of the installation that declares `dsh.bundle.patch`,
371
+ * selected by no shipped template, and offered switched off by the plugin
372
+ * manager ([rationale](../../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)).
373
+ */
374
+ const OPTIONAL_BUNDLES = ["@deepseek-ai/dsh-experimental-agent-team-profile", "@deepseek-ai/dsh-experimental-agent-team-web-profile"];
360
375
  const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer:
361
376
  # a top-level YAML array of loader patch entries (id-targeted config
362
377
  # overrides, disables, and insert lists; \`!!js\` expressions allowed).
@@ -374,9 +389,8 @@ autoInstallPeers: false
374
389
  * so re-running is a no-op on an initialized profile.
375
390
  * @param dir - the profile directory from {@link resolveProfileDir}.
376
391
  * @param bundles - the initial `dsh.profile.bundles` layer list.
377
- * @param patchReload - user patch-file lifecycle; custom profiles default to live reload.
378
392
  */
379
- function initProfile(dir, bundles, patchReload = DEFAULT_PROFILE_PATCH_RELOAD) {
393
+ function initProfile(dir, bundles) {
380
394
  mkdirSync(dir, { recursive: true });
381
395
  const manifestPath = join(dir, "package.json");
382
396
  if (!existsSync(manifestPath)) {
@@ -384,10 +398,7 @@ function initProfile(dir, bundles, patchReload = DEFAULT_PROFILE_PATCH_RELOAD) {
384
398
  name: `dsh-profile-${basename(dir)}`,
385
399
  private: true,
386
400
  dependencies: {},
387
- dsh: { profile: {
388
- bundles: [...bundles],
389
- patchReload
390
- } }
401
+ dsh: { profile: { bundles: [...bundles] } }
391
402
  };
392
403
  writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2) + "\n");
393
404
  }
@@ -429,23 +440,6 @@ function ensureSymlink(link, target) {
429
440
  if (error.code !== "EEXIST" || !lstatSync(link).isSymbolicLink() || !symlinkPointsTo(link, target)) throw error;
430
441
  }
431
442
  }
432
- /** Resolve a link target without following the final path component. */
433
- function canonicalLinkPath(path) {
434
- try {
435
- return join(realpathSync.native(dirname(path)), basename(path));
436
- } catch (error) {
437
- /* v8 ignore next 2 -- a non-ENOENT realpath failure requires a host filesystem fault */
438
- if (error.code === "ENOENT") return void 0;
439
- /* v8 ignore next -- see the host-filesystem exception above */
440
- throw error;
441
- }
442
- }
443
- /** Return whether a symlink or junction points at the same path as `target`. */
444
- function symlinkPointsTo(link, target) {
445
- const canonicalActual = canonicalLinkPath(resolve(dirname(link), readlinkSync(link)));
446
- const canonicalTarget = canonicalLinkPath(resolve(target));
447
- return canonicalActual !== void 0 && canonicalActual === canonicalTarget;
448
- }
449
443
  /** Add one profile-owned fallback link without replacing a pnpm-managed entry. */
450
444
  function ensureProfileSymlink(link, target) {
451
445
  try {
@@ -481,10 +475,6 @@ function removeProfileSymlink(profileModulesDir, ownedModulesDir, packageName) {
481
475
  if (error.code !== "ENOENT") throw error;
482
476
  }
483
477
  }
484
- /** Return whether the process reads application modules from pkg's virtual filesystem. */
485
- function isPackagedExecutable() {
486
- return process.pkg !== void 0;
487
- }
488
478
  /** Resolve one available explicit package export under Node ESM import conditions. */
489
479
  function packageEntryFromPackage(packageName, packageDir, declared, subpath) {
490
480
  let candidates;
@@ -586,11 +576,17 @@ function profileDependencyNames(manifest) {
586
576
  return [...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.peerDependencies ?? {})];
587
577
  }
588
578
  /** Resolve the installation generation that every profile must find through the fallback directory. */
589
- function resolveModuleFallbackEntries(installAnchor) {
579
+ function resolveModuleFallbackEntries(installAnchor, materialize = true) {
590
580
  const appManifest = readModuleFallbackManifest(installAnchor);
591
581
  const links = /* @__PURE__ */ new Map();
582
+ const declarers = /* @__PURE__ */ new Map();
583
+ const versions = /* @__PURE__ */ new Map();
592
584
  /* v8 ignore next -- a real app manifest always declares its name */
593
- if (appManifest.name !== void 0) links.set(appManifest.name, dirname(installAnchor));
585
+ if (appManifest.name !== void 0) {
586
+ links.set(appManifest.name, dirname(installAnchor));
587
+ declarers.set(appManifest.name, installAnchor);
588
+ versions.set(appManifest.name, appManifest.version);
589
+ }
594
590
  const queue = [{
595
591
  anchor: installAnchor,
596
592
  manifest: appManifest
@@ -602,14 +598,17 @@ function resolveModuleFallbackEntries(installAnchor) {
602
598
  const dir = packageDirFromAnchor(next.anchor, dep);
603
599
  if (dir === void 0) continue;
604
600
  links.set(dep, dir);
601
+ declarers.set(dep, next.anchor);
605
602
  const manifestPath = join(dir, "package.json");
603
+ const manifest = readModuleFallbackManifest(manifestPath);
604
+ versions.set(dep, manifest.version);
606
605
  queue.push({
607
606
  anchor: manifestPath,
608
- manifest: readModuleFallbackManifest(manifestPath)
607
+ manifest
609
608
  });
610
609
  }
611
610
  return {
612
- entries: !isPackagedExecutable() ? [...links].map(([packageName, packageDir]) => ({
611
+ entries: !materialize ? [] : !isPackagedExecutable() ? [...links].map(([packageName, packageDir]) => ({
613
612
  kind: "symlink",
614
613
  packageName,
615
614
  packageDir
@@ -622,7 +621,10 @@ function resolveModuleFallbackEntries(installAnchor) {
622
621
  targets: source.targets
623
622
  }];
624
623
  }),
625
- packageNames: new Set(links.keys())
624
+ packageNames: new Set(links.keys()),
625
+ packageDirs: links,
626
+ declarers,
627
+ versions
626
628
  };
627
629
  }
628
630
  /** Return whether one existing fallback entry already matches its resolved installation generation. */
@@ -652,18 +654,85 @@ function moduleFallbackCurrent(modulesDir, entries) {
652
654
  * `node_modules`; pnpm-managed entries remain authoritative, and another
653
655
  * profile's links cannot change its resolution.
654
656
  * @param options - installation anchor, optional loaded profile, and Harness home.
655
- * @returns settlement after the shared fallback and profile-local links are current.
657
+ * @returns the computed fallback generation after optional materialization.
656
658
  */
657
659
  async function healProfilesModuleFallback(options) {
658
- const { installAnchor, profile, home = resolveDshHome() } = options;
659
- const modulesDir = join(join(home, PROFILES_DIR), "node_modules");
660
- mkdirSync(modulesDir, { recursive: true });
661
- const { entries, packageNames } = resolveModuleFallbackEntries(installAnchor);
662
- if (!moduleFallbackCurrent(modulesDir, entries)) await withFileLock(modulesDir, () => {
660
+ const { installAnchor, profile, home = resolveDshHome(), materialize = true } = options;
661
+ const profilesDir = join(home, PROFILES_DIR);
662
+ const modulesDir = join(profilesDir, "node_modules");
663
+ if (materialize) mkdirSync(modulesDir, { recursive: true });
664
+ const { entries, packageNames, packageDirs, declarers, versions } = resolveModuleFallbackEntries(installAnchor, materialize);
665
+ if (materialize && !moduleFallbackCurrent(modulesDir, entries)) await withFileLock(modulesDir, () => {
663
666
  if (!moduleFallbackCurrent(modulesDir, entries)) healProfilesModuleFallbackLocked(entries, modulesDir);
664
667
  return Promise.resolve();
665
668
  });
666
- if (profile !== void 0) healProfileModuleFallback(profile, packageNames);
669
+ const profileDeclarers = /* @__PURE__ */ new Map();
670
+ const profileVersions = /* @__PURE__ */ new Map();
671
+ const localPackageNames = profile === void 0 ? [] : installedProfilePackageNames(profile);
672
+ const profilePackages = profile === void 0 ? /* @__PURE__ */ new Map() : healProfileModuleFallback(profile, packageNames, materialize, profileDeclarers, profileVersions);
673
+ return Object.freeze({
674
+ profilesDir,
675
+ profileDir: profile?.dir,
676
+ localPackageNames: Object.freeze(localPackageNames),
677
+ entries: Object.freeze([...[...packageDirs].map(([name, packageDir]) => Object.freeze({
678
+ name,
679
+ packageDir,
680
+ version: versions.get(name),
681
+ declarer: declarers.get(name),
682
+ scope: "installation"
683
+ })), ...[...profilePackages].map(([name, packageDir]) => Object.freeze({
684
+ name,
685
+ packageDir,
686
+ version: profileVersions.get(name),
687
+ declarer: profileDeclarers.get(name),
688
+ scope: "profile"
689
+ }))])
690
+ });
691
+ }
692
+ /** Return installed direct dependencies that Node resolves before profile fallback. */
693
+ function installedProfilePackageNames(profile) {
694
+ let manifest;
695
+ try {
696
+ manifest = readModuleFallbackManifest(join(profile.dir, "package.json"));
697
+ } catch (error) {
698
+ if (error.code === "ENOENT") return [];
699
+ throw error;
700
+ }
701
+ return profileDependencyNames(manifest).filter((name) => {
702
+ if (!existsSync(join(join(profile.dir, "node_modules", name), "package.json"))) return false;
703
+ return !isProfileModuleFallbackLink(profile.dir, name);
704
+ });
705
+ }
706
+ /**
707
+ * Compute a profile resolution generation without materializing links or proxies.
708
+ * @param options - installation anchor, profile, and optional Harness home.
709
+ * @returns the complete immutable generation.
710
+ */
711
+ function createProfileResolutionGeneration(options) {
712
+ return healProfilesModuleFallback({
713
+ ...options,
714
+ materialize: false
715
+ });
716
+ }
717
+ /**
718
+ * Supply an application-owned profile with filesystem packages from its installation and selected bundles.
719
+ * All fallback links belong to the profile; no shared Harness-home directory is written.
720
+ * Existing pnpm-managed packages remain authoritative. The caller serializes profile mutations.
721
+ * @param options - owning installation package.json and the loaded application profile.
722
+ */
723
+ function healIsolatedProfileModuleFallback(options) {
724
+ const installationLinks = resolveModuleFallbackEntries(options.installAnchor, false).packageDirs;
725
+ healProfileModuleFallback(options.profile, new Set(installationLinks.keys()), true, void 0, void 0, installationLinks);
726
+ }
727
+ /**
728
+ * Detach this profile's fallback links before a package-manager mutation.
729
+ * Installed packages and links replaced by pnpm remain untouched; the next profile launch restores fallbacks.
730
+ * @param profileDir - profile directory whose package mutation is serialized by the caller.
731
+ */
732
+ function unlinkProfileModuleFallback(profileDir) {
733
+ const ownedModulesDir = join(profileDir, PROFILE_MODULE_FALLBACK_DIR, "node_modules");
734
+ if (!existsSync(ownedModulesDir)) return;
735
+ for (const name of ownedPackageNames(ownedModulesDir)) removeProfileSymlink(join(profileDir, "node_modules"), ownedModulesDir, name);
667
736
  }
668
737
  /** Heal one module-fallback generation while the cross-process writer lock is held. */
669
738
  function healProfilesModuleFallbackLocked(entries, modulesDir) {
@@ -675,17 +744,19 @@ function healProfilesModuleFallbackLocked(entries, modulesDir) {
675
744
  }
676
745
  }
677
746
  /** Collect the first resolvable package directory for each dependency name. */
678
- function dependencyClosure(anchors, reserved, exclude) {
747
+ function dependencyClosure(anchors, reserved, exclude, declarers, versions) {
679
748
  const links = /* @__PURE__ */ new Map();
680
749
  const visited = new Set(reserved);
681
750
  for (const anchor of anchors) {
682
- const canonicalAnchor = realpathSync.native(anchor);
751
+ const canonicalAnchor = join(realModuleDirectory(dirname(anchor)), basename(anchor));
683
752
  const manifest = readModuleFallbackManifest(canonicalAnchor);
684
753
  /* v8 ignore next -- an installable package manifest always declares its name */
685
754
  if (manifest.name === void 0) continue;
686
755
  if (!visited.has(manifest.name)) {
687
756
  visited.add(manifest.name);
688
757
  links.set(manifest.name, dirname(canonicalAnchor));
758
+ declarers?.set(manifest.name, canonicalAnchor);
759
+ versions?.set(manifest.name, manifest.version);
689
760
  }
690
761
  const queue = [{
691
762
  anchor: canonicalAnchor,
@@ -699,21 +770,26 @@ function dependencyClosure(anchors, reserved, exclude) {
699
770
  if (dir === void 0) continue;
700
771
  visited.add(dep);
701
772
  links.set(dep, dir);
773
+ declarers?.set(dep, next.anchor);
702
774
  const manifestPath = join(dir, "package.json");
775
+ const dependencyManifest = readModuleFallbackManifest(manifestPath);
776
+ versions?.set(dep, dependencyManifest.version);
703
777
  queue.push({
704
778
  anchor: manifestPath,
705
- manifest: readModuleFallbackManifest(manifestPath)
779
+ manifest: dependencyManifest
706
780
  });
707
781
  }
708
782
  }
709
783
  return links;
710
784
  }
711
785
  /** Reconcile packages carried only by selected bundles into one profile. */
712
- function healProfileModuleFallback(profile, installationPackageNames) {
786
+ function healProfileModuleFallback(profile, installationPackageNames, materialize = true, declarers, versions, installationLinks = /* @__PURE__ */ new Map()) {
713
787
  const profileModulesDir = join(profile.dir, "node_modules");
714
788
  const ownedModulesDir = join(profile.dir, PROFILE_MODULE_FALLBACK_DIR, "node_modules");
715
- mkdirSync(profileModulesDir, { recursive: true });
716
- mkdirSync(ownedModulesDir, { recursive: true });
789
+ if (materialize) {
790
+ mkdirSync(profileModulesDir, { recursive: true });
791
+ mkdirSync(ownedModulesDir, { recursive: true });
792
+ }
717
793
  const bundleLinks = dependencyClosure(profile.layers.filter((layer) => !installationPackageNames.has(layer.packageName)).map((layer) => join(layer.packageDir, "package.json")), installationPackageNames, (candidate, packageName) => {
718
794
  const profileLink = join(profileModulesDir, packageName);
719
795
  if (canonicalLinkPath(candidate) !== canonicalLinkPath(profileLink)) return false;
@@ -725,10 +801,12 @@ function healProfileModuleFallback(profile, installationPackageNames) {
725
801
  /* v8 ignore next -- see the host-filesystem exception above */
726
802
  throw error;
727
803
  }
728
- });
804
+ }, declarers, versions);
729
805
  for (const layer of profile.layers) bundleLinks.delete(layer.packageName);
730
- for (const packageName of ownedPackageNames(ownedModulesDir)) if (!bundleLinks.has(packageName)) removeProfileSymlink(profileModulesDir, ownedModulesDir, packageName);
731
- for (const [packageName, target] of bundleLinks) {
806
+ const links = new Map([...installationLinks, ...bundleLinks]);
807
+ if (!materialize) return links;
808
+ for (const packageName of ownedPackageNames(ownedModulesDir)) if (!links.has(packageName)) removeProfileSymlink(profileModulesDir, ownedModulesDir, packageName);
809
+ for (const [packageName, target] of links) {
732
810
  const ownedLink = join(ownedModulesDir, packageName);
733
811
  mkdirSync(dirname(ownedLink), { recursive: true });
734
812
  ensureSymlink(ownedLink, target);
@@ -736,6 +814,7 @@ function healProfileModuleFallback(profile, installationPackageNames) {
736
814
  mkdirSync(dirname(profileLink), { recursive: true });
737
815
  ensureProfileSymlink(profileLink, ownedLink);
738
816
  }
817
+ return links;
739
818
  }
740
819
  /**
741
820
  * Read a profile's manifest.
@@ -769,27 +848,21 @@ function sameBundles(left, right) {
769
848
  }
770
849
  /**
771
850
  * Normalize an exact installation-owned bundle tuple to its shipped template,
772
- * or add the shipped reload default to an exact current tuple. A changed value
773
- * is written back during profile loading while every other manifest field is
774
- * preserved; any other bundle list is user-owned and remains untouched.
851
+ * preserving all other manifest fields. Other bundle lists remain untouched.
775
852
  */
776
853
  function normalizeShippedProfile(name, dir, manifest) {
777
854
  const installationOwned = INSTALLATION_OWNED_PROFILE_TUPLES[name];
778
855
  const template = PROFILE_TEMPLATES[name];
779
856
  const bundles = manifest.dsh?.profile?.bundles;
780
857
  if (template === void 0 || bundles === void 0) return manifest;
781
- const isRetiredTuple = installationOwned !== void 0 && sameBundles(bundles, installationOwned);
782
- const isCurrentTuple = sameBundles(bundles, template.bundles);
783
- const needsReloadDefault = manifest.dsh?.profile?.patchReload === void 0 && isCurrentTuple;
784
- if (!isRetiredTuple && !needsReloadDefault) return manifest;
858
+ if (!(installationOwned !== void 0 && sameBundles(bundles, installationOwned))) return manifest;
785
859
  const normalized = {
786
860
  ...manifest,
787
861
  dsh: {
788
862
  ...manifest.dsh,
789
863
  profile: {
790
864
  ...manifest.dsh?.profile,
791
- bundles: [...template.bundles],
792
- patchReload: manifest.dsh?.profile?.patchReload ?? template.patchReload
865
+ bundles: [...template.bundles]
793
866
  }
794
867
  }
795
868
  };
@@ -841,12 +914,7 @@ function resolveBundleDir(binName, packageName, installAnchor, profileDir) {
841
914
  * @returns the resolved bundle layers and optional user patch layer.
842
915
  */
843
916
  function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
844
- const manifest = readProfileManifest(binName, dir);
845
- const bundles = manifest.dsh?.profile?.bundles ?? [];
846
- const rawPatchReload = manifest.dsh?.profile?.patchReload;
847
- if (rawPatchReload !== void 0 && rawPatchReload !== "live" && rawPatchReload !== "startup") throw new Error(`${binName}: profile manifest ${join(dir, "package.json")} dsh.profile.patchReload must be "live" or "startup"`);
848
- const patchReload = rawPatchReload ?? "live";
849
- const layers = bundles.map((packageName) => {
917
+ const layers = (readProfileManifest(binName, dir).dsh?.profile?.bundles ?? []).map((packageName) => {
850
918
  const packageDir = resolveBundleDir(binName, packageName, installAnchor, dir);
851
919
  const declared = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8")).dsh?.bundle?.patch;
852
920
  if (declared === void 0) throw new Error(`${binName}: profile bundle ${JSON.stringify(packageName)} declares no dsh.bundle in its package.json`);
@@ -865,8 +933,7 @@ function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
865
933
  dir,
866
934
  layers,
867
935
  patchPath,
868
- patches,
869
- patchReload
936
+ patches
870
937
  };
871
938
  }
872
939
  /**
@@ -888,7 +955,7 @@ function loadProfile(binName, name, installAnchor, home = resolveDshHome(), opti
888
955
  if (!existsSync(join(dir, "package.json"))) {
889
956
  const template = PROFILE_TEMPLATES[name];
890
957
  if (template === void 0) throw new Error(`${binName}: profile ${JSON.stringify(name)} does not exist; create it with 'dsh plugin --profile ${name} add <package>'`);
891
- initProfile(dir, template.bundles, template.patchReload);
958
+ initProfile(dir, template.bundles);
892
959
  }
893
960
  normalizeShippedProfile(name, dir, readProfileManifest(binName, dir));
894
961
  return loadProfileDirectory(binName, dir, installAnchor, options);
@@ -908,6 +975,961 @@ function composeEntries(layers, warn = () => {}) {
908
975
  });
909
976
  }
910
977
  //#endregion
978
+ //#region lib/types/profile-context.js
979
+ /** Launcher-owned profile locations and composition inputs. */
980
+ const TELEMETRY_ROW_ID = "session-telemetry-otel";
981
+ /**
982
+ * Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
983
+ * value (including `'0'`/`'false'`) disables: a privacy switch prefers
984
+ * off-by-mistake over on-by-mistake. A composition without the telemetry row
985
+ * exports nothing, so the switch is then trivially satisfied and no patch is
986
+ * generated — custom profiles need not mount telemetry to run with the
987
+ * switch set.
988
+ * @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
989
+ * @param hasRow - whether the composition carries the telemetry row.
990
+ * @returns the disable patch, or `undefined` when no hard-disable patch is required.
991
+ */
992
+ function resolveTelemetryPatch(disabledEnv, hasRow) {
993
+ if ((disabledEnv ?? "") === "" || !hasRow) return void 0;
994
+ return {
995
+ id: TELEMETRY_ROW_ID,
996
+ disabled: true
997
+ };
998
+ }
999
+ /** Read current bundle and user layers with the launch-time overlays.
1000
+ * @param binName Diagnostic prefix for malformed or missing configuration.
1001
+ * @param context Data supplied by the profile launcher.
1002
+ * @param initialProfile Already loaded startup profile; omitted reads the current files.
1003
+ * @returns Detached ordered patches; this function does not update the Loader.
1004
+ */
1005
+ function readProfilePatches(binName, context, initialProfile) {
1006
+ const profile = initialProfile ?? loadProfileDirectory(binName, context.dir, context.installAnchor, { userLayer: false });
1007
+ const patches = structuredClone([
1008
+ ...profile.layers.flatMap((layer) => layer.patches),
1009
+ ...initialProfile?.patches ?? loadOptionalPatches(binName, context.patchPath) ?? [],
1010
+ ...loadOptionalPatches(binName, join(context.home, "cordis.patch.yml")) ?? [],
1011
+ ...context.overlays
1012
+ ]);
1013
+ const telemetryPatch = resolveTelemetryPatch(context.telemetryDisabledEnv, composeEntries([patches]).some((row) => row.id === TELEMETRY_ROW_ID));
1014
+ if (telemetryPatch !== void 0) patches.push(telemetryPatch);
1015
+ return patches;
1016
+ }
1017
+ //#endregion
1018
+ //#region lib/types/profile-plugins.js
1019
+ /** Installed profile dependencies and their bundle activation after package-manager operations. */
1020
+ /** Unavailable installed metadata must not prevent listing or removing dependencies. */
1021
+ function optionalManifest(binName, packageDir) {
1022
+ try {
1023
+ return readProfileManifest(binName, packageDir);
1024
+ } catch {
1025
+ return;
1026
+ }
1027
+ }
1028
+ /** Resolve activation metadata with the same installation precedence as profile loading. */
1029
+ function bundleManifest(location, name) {
1030
+ let packageDir;
1031
+ try {
1032
+ packageDir = resolveBundleDir(location.binName, name, location.installAnchor, location.profileDir);
1033
+ } catch {
1034
+ return;
1035
+ }
1036
+ return optionalManifest(location.binName, packageDir);
1037
+ }
1038
+ /**
1039
+ * Read installed versions and bundle declarations without requiring loadable plugin code.
1040
+ * Missing or unreadable installed metadata leaves the dependency visible for repair or removal.
1041
+ * @param location - profile and installation resolution inputs.
1042
+ * @returns dependency records in package.json order and the profile manifest.
1043
+ */
1044
+ function readProfilePlugins(location) {
1045
+ const manifest = readProfileManifest(location.binName, location.profileDir);
1046
+ const bundles = manifest.dsh?.profile?.bundles ?? [];
1047
+ return {
1048
+ manifest,
1049
+ dependencies: Object.entries(manifest.dependencies ?? {}).map(([name, spec]) => {
1050
+ const installed = optionalManifest(location.binName, join(location.profileDir, "node_modules", name));
1051
+ return {
1052
+ name,
1053
+ version: typeof installed?.version === "string" ? installed.version : spec,
1054
+ bundle: bundleManifest(location, name)?.dsh?.bundle?.patch !== void 0,
1055
+ enabled: bundles.includes(name)
1056
+ };
1057
+ })
1058
+ };
1059
+ }
1060
+ /**
1061
+ * Write a bundle list while preserving the supplied profile's other metadata.
1062
+ * @param profileDir - directory whose package.json is updated.
1063
+ * @param manifest - current profile manifest, read after the package operation when applicable.
1064
+ * @param bundles - ordered active bundle names, including any template entries.
1065
+ * @returns the written manifest.
1066
+ */
1067
+ function writeProfileBundles(profileDir, manifest, bundles) {
1068
+ const updated = {
1069
+ ...manifest,
1070
+ dsh: {
1071
+ ...manifest.dsh,
1072
+ profile: {
1073
+ ...manifest.dsh?.profile,
1074
+ bundles: [...bundles]
1075
+ }
1076
+ }
1077
+ };
1078
+ writeProfileManifest(profileDir, updated);
1079
+ return updated;
1080
+ }
1081
+ /**
1082
+ * Reconcile installed bundle declarations after a successful package-manager operation.
1083
+ * Dependency-managed entries disappear when removed or when their package loses its declaration;
1084
+ * template entries retain their order and duplicates. New bundle dependencies activate automatically.
1085
+ * @param options - location, inventory captured before pnpm, and whether explicitly disabled bundles remain disabled.
1086
+ * @returns updated inventory and newly added ordinary dependencies for caller-owned warnings.
1087
+ */
1088
+ function reconcileProfilePlugins(options) {
1089
+ const after = readProfilePlugins(options);
1090
+ const beforeNames = new Set(options.before.dependencies.map((dependency) => dependency.name));
1091
+ const afterNames = new Set(after.dependencies.map((dependency) => dependency.name));
1092
+ const bundleNames = new Set(after.dependencies.filter((dependency) => dependency.bundle).map((dependency) => dependency.name));
1093
+ const disabled = new Set(options.preserveDisabled ? options.before.dependencies.filter((dependency) => dependency.bundle && !dependency.enabled).map((dependency) => dependency.name) : []);
1094
+ const previous = after.manifest.dsh?.profile?.bundles ?? [];
1095
+ const bundles = previous.filter((name) => !(beforeNames.has(name) || afterNames.has(name)) || bundleNames.has(name));
1096
+ for (const dependency of after.dependencies) if (dependency.bundle && !disabled.has(dependency.name) && !bundles.includes(dependency.name)) bundles.push(dependency.name);
1097
+ return {
1098
+ plugins: {
1099
+ manifest: bundles.length !== previous.length || bundles.some((name, index) => name !== previous[index]) ? writeProfileBundles(options.profileDir, after.manifest, bundles) : after.manifest,
1100
+ dependencies: after.dependencies.map((dependency) => ({
1101
+ ...dependency,
1102
+ enabled: bundles.includes(dependency.name)
1103
+ }))
1104
+ },
1105
+ addedPlainDependencies: after.dependencies.filter((dependency) => !dependency.bundle && !beforeNames.has(dependency.name)).map((dependency) => dependency.name)
1106
+ };
1107
+ }
1108
+ //#endregion
1109
+ //#region lib/types/profile-sanitize.js
1110
+ /** Filesystem recovery for callers that own profile shutdown and write exclusion. */
1111
+ /**
1112
+ * Back up the profile patch and retain only the caller's recovery bundles.
1113
+ * The caller must stop the profile and exclude concurrent profile writes.
1114
+ * Installed packages and other manifest fields are preserved; patches are never parsed.
1115
+ * Failures propagate and may leave completed changes in place for a retry.
1116
+ * @param binName - Diagnostic prefix for invalid profile manifests.
1117
+ * @param profileDir - Profile directory to recover without loading its plugins.
1118
+ * @param bundles - Ordered bundles to enable after recovery.
1119
+ * @returns Backup path with a Unix millisecond timestamp and optional collision ordinal, or undefined if absent.
1120
+ */
1121
+ function sanitizeProfile(binName, profileDir, bundles) {
1122
+ const manifest = existsSync(join(profileDir, "package.json")) ? readProfileManifest(binName, profileDir) : void 0;
1123
+ const patchPath = join(profileDir, PROFILE_PATCH_FILENAME);
1124
+ const backupBase = `${patchPath}.bak-${Date.now()}`;
1125
+ let backupPath = backupBase;
1126
+ let ordinal = 0;
1127
+ while (existsSync(backupPath)) backupPath = `${backupBase}-${++ordinal}`;
1128
+ try {
1129
+ renameSync(patchPath, backupPath);
1130
+ } catch (error) {
1131
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
1132
+ backupPath = void 0;
1133
+ }
1134
+ if (manifest !== void 0) writeProfileBundles(profileDir, manifest, bundles);
1135
+ return backupPath;
1136
+ }
1137
+ //#endregion
1138
+ //#region lib/types/profile-resolution/resolver.js
1139
+ /** In-memory profile package routing for Node's default ESM and CommonJS loaders. */
1140
+ const WORKER_RESOLUTION_KEY = "@deepseek-ai/dsh-app-boot/profile-resolution";
1141
+ const EMPTY_ATTRIBUTES = Object.freeze({});
1142
+ /**
1143
+ * Split a bare request into its package name without allocating path segments.
1144
+ * @param request - module specifier to classify.
1145
+ * @returns the bare package name, or undefined for non-package requests.
1146
+ */
1147
+ function barePackageName(request) {
1148
+ if (!request || request[0] === "." || request[0] === "/" || request[0] === "\\" || request[0] === "#" || request.includes(":") || isBuiltin(request)) return;
1149
+ const first = request.indexOf("/");
1150
+ if (request[0] !== "@") return first < 0 ? request : request.slice(0, first);
1151
+ if (first < 0) return;
1152
+ const second = request.indexOf("/", first + 1);
1153
+ return second < 0 ? request : request.slice(0, second);
1154
+ }
1155
+ function canonicalPath(path) {
1156
+ try {
1157
+ return realpathSync(path);
1158
+ } catch {
1159
+ return resolve(path);
1160
+ }
1161
+ }
1162
+ function prefixes(path) {
1163
+ const configured = resolve(path) + sep;
1164
+ const canonical = canonicalPath(path) + sep;
1165
+ return canonical === configured ? [configured] : [configured, canonical];
1166
+ }
1167
+ function compileGeneration(generation) {
1168
+ const profilePaths = prefixes(generation.profilesDir);
1169
+ const profile = generation.profileDir === void 0 ? [] : prefixes(generation.profileDir);
1170
+ return {
1171
+ entries: new Map(generation.entries.map((entry) => [entry.name, entry])),
1172
+ profilesDir: generation.profilesDir,
1173
+ profileDir: generation.profileDir,
1174
+ profilePaths,
1175
+ profileUrls: profilePaths.map((path) => pathToFileURL(path).href),
1176
+ profile,
1177
+ activeProfileUrls: profile.map((path) => pathToFileURL(path).href),
1178
+ localPackageNames: new Set(generation.localPackageNames),
1179
+ shared: new Set(profilePaths.map((prefix) => join(prefix, "node_modules"))),
1180
+ esmRoutes: /* @__PURE__ */ new Map(),
1181
+ cjsRoutes: /* @__PURE__ */ new Map()
1182
+ };
1183
+ }
1184
+ function startsWithin(path, roots) {
1185
+ for (const root of roots) if (path.startsWith(root)) return true;
1186
+ return false;
1187
+ }
1188
+ function nativePackageDir(parent, name) {
1189
+ for (const searchPath of createRequire(parent).resolve.paths(name)) {
1190
+ const candidate = join(searchPath, name);
1191
+ if (existsSync(join(candidate, "package.json"))) return candidate;
1192
+ }
1193
+ }
1194
+ function localPackageCandidate(searchPath, name, flavor) {
1195
+ const candidate = join(searchPath, name);
1196
+ const stat = statSync(candidate, { throwIfNoEntry: false });
1197
+ return (flavor === "esm" ? stat?.isDirectory() === true : stat !== void 0 || [
1198
+ ".js",
1199
+ ".json",
1200
+ ".node"
1201
+ ].some((extension) => existsSync(candidate + extension))) ? {
1202
+ packageDir: candidate,
1203
+ canBeManagedLink: stat !== void 0
1204
+ } : void 0;
1205
+ }
1206
+ function selfReferenceName(parent) {
1207
+ let current = dirname(parent);
1208
+ while (true) {
1209
+ const manifestPath = join(current, "package.json");
1210
+ if (existsSync(manifestPath)) {
1211
+ let manifest;
1212
+ try {
1213
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
1214
+ } catch (_error) {
1215
+ return null;
1216
+ }
1217
+ return typeof manifest.name === "string" && manifest.exports != null ? manifest.name : false;
1218
+ }
1219
+ /* v8 ignore next -- scoped module requests normally find an owning manifest before node_modules. */
1220
+ if (basename(current) === "node_modules") return false;
1221
+ const next = dirname(current);
1222
+ if (next === current) return false;
1223
+ current = next;
1224
+ }
1225
+ }
1226
+ function packageImportsTarget(parent, request, conditions) {
1227
+ let current = dirname(parent);
1228
+ while (true) {
1229
+ const manifestPath = join(current, "package.json");
1230
+ if (existsSync(manifestPath)) {
1231
+ let manifest;
1232
+ try {
1233
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
1234
+ } catch (_error) {
1235
+ /* v8 ignore next -- native resolution cannot report MODULE_NOT_FOUND after an invalid scope manifest */
1236
+ return;
1237
+ }
1238
+ try {
1239
+ const target = imports(manifest, request, {
1240
+ conditions: [...conditions],
1241
+ unsafe: true
1242
+ })?.[0];
1243
+ return target !== void 0 && barePackageName(target) !== void 0 ? {
1244
+ specifier: target,
1245
+ parentURL: pathToFileURL(manifestPath).href
1246
+ } : void 0;
1247
+ } catch (_error) {
1248
+ /* v8 ignore next -- native resolution cannot report MODULE_NOT_FOUND before selecting a valid mapping */
1249
+ return;
1250
+ }
1251
+ }
1252
+ if (basename(current) === "node_modules") return void 0;
1253
+ const next = dirname(current);
1254
+ /* v8 ignore next -- a MODULE_NOT_FOUND package-import target always has an owning package scope */
1255
+ if (next === current) return void 0;
1256
+ current = next;
1257
+ }
1258
+ }
1259
+ function packageSearchPaths(entry, request, cjs) {
1260
+ const name = barePackageName(request);
1261
+ /* v8 ignore next -- fallback routes are created only for bare package requests */
1262
+ if (name === void 0) return cjs._nodeModulePaths(dirname(entry.declarer));
1263
+ const suffix = sep + name.split("/").join(sep);
1264
+ return entry.packageDir.endsWith(suffix) ? [entry.packageDir.slice(0, -suffix.length)] : cjs._nodeModulePaths(dirname(entry.declarer));
1265
+ }
1266
+ function localCandidateOwnsResolution(candidate, resolved, request, name) {
1267
+ if (startsWithin(resolved, prefixes(candidate))) return true;
1268
+ if (sameResolution(candidate, resolved)) return true;
1269
+ if ([
1270
+ ".js",
1271
+ ".json",
1272
+ ".node"
1273
+ ].some((extension) => sameResolution(candidate + extension, resolved))) return true;
1274
+ /* v8 ignore next -- a bounded native lookup can escape a candidate only through its root legacy main */
1275
+ if (request !== name) return false;
1276
+ try {
1277
+ const manifest = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
1278
+ /* v8 ignore next -- a bounded native lookup outside the package directory requires a legacy main */
1279
+ if (typeof manifest.main !== "string") return false;
1280
+ return sameResolution(createRequire(join(candidate, "package.json")).resolve(resolve(candidate, manifest.main)), resolved);
1281
+ } catch (_error) {
1282
+ /* v8 ignore next -- this helper runs only after the same native resolution succeeded */
1283
+ return false;
1284
+ }
1285
+ }
1286
+ function isUnselectedPackageMiss(error) {
1287
+ const failure = error;
1288
+ return failure.code === "MODULE_NOT_FOUND" && failure.path === void 0;
1289
+ }
1290
+ function sameResolution(left, right) {
1291
+ if (left === right) return true;
1292
+ const leftPath = left.startsWith("file:") ? fileURLToPath(left) : left;
1293
+ const rightPath = right.startsWith("file:") ? fileURLToPath(right) : right;
1294
+ return canonicalPath(leftPath) === canonicalPath(rightPath);
1295
+ }
1296
+ /** One mutable pointer to immutable generation data. */
1297
+ var ResolutionRouter = class {
1298
+ current;
1299
+ constructor(generation) {
1300
+ this.current = compileGeneration(generation);
1301
+ }
1302
+ replace(generation) {
1303
+ const entries = new Map(generation.entries.map((entry) => [entry.name, entry]));
1304
+ if (generation.profilesDir !== this.current.profilesDir || generation.profileDir !== this.current.profileDir) throw new Error("profile resolution: a generation cannot change its profile scope");
1305
+ for (const [name, current] of this.current.entries) {
1306
+ const next = entries.get(name);
1307
+ if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || !sameResolution(current.declarer, next.declarer) || current.version !== next.version || current.scope !== next.scope) throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
1308
+ }
1309
+ const localPackageNames = new Set(generation.localPackageNames);
1310
+ for (const name of this.current.localPackageNames) if (!localPackageNames.has(name)) throw new Error(`profile resolution: removing local package ${JSON.stringify(name)} requires a process restart`);
1311
+ for (const name of localPackageNames) if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) throw new Error(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`);
1312
+ this.current = compileGeneration(generation);
1313
+ }
1314
+ routeScoped(request, parentRoutes, generation, flavor, nativeResolve, cacheable = false) {
1315
+ const { parent, profilesDir, requests } = parentRoutes;
1316
+ const name = barePackageName(request);
1317
+ if (name === void 0) return void 0;
1318
+ if (parentRoutes.selfReferenceName === void 0) parentRoutes.selfReferenceName = selfReferenceName(parent);
1319
+ if (parentRoutes.selfReferenceName === name || parentRoutes.selfReferenceName === null) {
1320
+ const state = { route: { kind: "native" } };
1321
+ requests.set(request, state);
1322
+ return state;
1323
+ }
1324
+ const target = generation.entries.get(name);
1325
+ const candidates = [];
1326
+ const localSearchPaths = [];
1327
+ for (const searchPath of createRequire(parent).resolve.paths(name)) {
1328
+ if (generation.shared.has(resolve(searchPath))) break;
1329
+ localSearchPaths.push(searchPath);
1330
+ const candidate = localPackageCandidate(searchPath, name, flavor);
1331
+ if (candidate !== void 0) {
1332
+ if (!(candidate.canBeManagedLink && generation.profile.some((prefix) => candidate.packageDir === join(prefix, "node_modules", name) && isProfileModuleFallbackLink(prefix.slice(0, -1), name)))) candidates.push(candidate);
1333
+ }
1334
+ }
1335
+ if (candidates.length > 0) if (flavor === "cjs" && nativeResolve !== void 0) try {
1336
+ const resolved = nativeResolve(localSearchPaths);
1337
+ const selected = candidates.find((candidate) => localCandidateOwnsResolution(candidate.packageDir, resolved, request, name));
1338
+ if (selected !== void 0) {
1339
+ const state = {
1340
+ route: {
1341
+ kind: "native",
1342
+ packageDir: selected.packageDir
1343
+ },
1344
+ packageDir: selected.packageDir,
1345
+ ...cacheable ? { cjs: resolved } : {}
1346
+ };
1347
+ requests.set(request, state);
1348
+ return state;
1349
+ }
1350
+ } catch (error) {
1351
+ if (!isUnselectedPackageMiss(error)) throw error;
1352
+ }
1353
+ else {
1354
+ const selected = candidates[0];
1355
+ const state = {
1356
+ route: {
1357
+ kind: "native",
1358
+ packageDir: selected.packageDir
1359
+ },
1360
+ packageDir: selected.packageDir
1361
+ };
1362
+ requests.set(request, state);
1363
+ return state;
1364
+ }
1365
+ const eligible = target?.scope === "installation" || target?.scope === "profile" && parentRoutes.activeProfile;
1366
+ const after = join(dirname(profilesDir), "package.json");
1367
+ const route = eligible ? {
1368
+ kind: "fallback",
1369
+ entry: target,
1370
+ after
1371
+ } : {
1372
+ kind: "after-fallback",
1373
+ parent: after
1374
+ };
1375
+ const state = { route };
1376
+ if (route.kind === "fallback") requests.set(request, state);
1377
+ return state;
1378
+ }
1379
+ routeLocalPackage(request, parentRoutes, generation) {
1380
+ const name = barePackageName(request);
1381
+ if (name === void 0 || !parentRoutes.activeProfile || !generation.localPackageNames.has(name)) return void 0;
1382
+ const state = { route: { kind: "native" } };
1383
+ parentRoutes.requests.set(request, state);
1384
+ return state;
1385
+ }
1386
+ routeUrl(request, parentURL) {
1387
+ const generation = this.current;
1388
+ let parentRoutes = generation.esmRoutes.get(parentURL);
1389
+ if (parentRoutes === false) return void 0;
1390
+ const cached = parentRoutes?.requests.get(request);
1391
+ if (cached !== void 0) return cached;
1392
+ if (parentRoutes === void 0) {
1393
+ const profileIndex = generation.profileUrls.findIndex((prefix) => parentURL.startsWith(prefix));
1394
+ const activeProfileIndex = generation.activeProfileUrls.findIndex((prefix) => parentURL.startsWith(prefix));
1395
+ if (profileIndex < 0 && activeProfileIndex < 0) {
1396
+ generation.esmRoutes.set(parentURL, false);
1397
+ return;
1398
+ }
1399
+ let parent;
1400
+ try {
1401
+ parent = fileURLToPath(parentURL);
1402
+ } catch {
1403
+ generation.esmRoutes.set(parentURL, false);
1404
+ return;
1405
+ }
1406
+ const profileRoot = profileIndex >= 0 ? generation.profilePaths[profileIndex] : generation.profile[activeProfileIndex];
1407
+ /* v8 ignore next -- one matching index was established above */
1408
+ if (profileRoot === void 0) return void 0;
1409
+ parentRoutes = {
1410
+ parent,
1411
+ profilesDir: profileRoot.slice(0, -1),
1412
+ activeProfile: activeProfileIndex >= 0,
1413
+ requests: /* @__PURE__ */ new Map()
1414
+ };
1415
+ generation.esmRoutes.set(parentURL, parentRoutes);
1416
+ }
1417
+ const local = this.routeLocalPackage(request, parentRoutes, generation);
1418
+ if (local !== void 0) return local;
1419
+ return this.routeScoped(request, parentRoutes, generation, "esm");
1420
+ }
1421
+ routePath(request, parent, nativeResolve, cacheable = false) {
1422
+ const generation = this.current;
1423
+ let parentRoutes = generation.cjsRoutes.get(parent);
1424
+ if (parentRoutes === false) return void 0;
1425
+ const cached = parentRoutes?.requests.get(request);
1426
+ if (cached !== void 0) return cached;
1427
+ if (parentRoutes === void 0) {
1428
+ const profilesDir = generation.profilePaths.find((prefix) => parent.startsWith(prefix));
1429
+ const activeProfile = generation.profile.find((prefix) => parent.startsWith(prefix));
1430
+ if (profilesDir !== void 0 || activeProfile !== void 0) {
1431
+ const profileRoot = profilesDir ?? activeProfile;
1432
+ /* v8 ignore next -- one matching root was established above */
1433
+ if (profileRoot === void 0) return void 0;
1434
+ parentRoutes = {
1435
+ parent,
1436
+ profilesDir: profileRoot.slice(0, -1),
1437
+ activeProfile: activeProfile !== void 0,
1438
+ requests: /* @__PURE__ */ new Map()
1439
+ };
1440
+ generation.cjsRoutes.set(parent, parentRoutes);
1441
+ }
1442
+ }
1443
+ if (parentRoutes === void 0) {
1444
+ generation.cjsRoutes.set(parent, false);
1445
+ return;
1446
+ }
1447
+ return this.routeScoped(request, parentRoutes, generation, "cjs", nativeResolve, cacheable);
1448
+ }
1449
+ explicitRoute(request, paths) {
1450
+ if (barePackageName(request) === void 0) return void 0;
1451
+ for (const [index, path] of paths.entries()) {
1452
+ const parent = join(resolve(path), ".dsh-profile-resolution.cjs");
1453
+ if (startsWithin(parent, this.current.profilePaths) || startsWithin(parent, this.current.profile)) return {
1454
+ index,
1455
+ parent
1456
+ };
1457
+ }
1458
+ }
1459
+ nativeSelfReference(request, parent) {
1460
+ const name = barePackageName(request);
1461
+ if (name === void 0) return false;
1462
+ const self = selfReferenceName(parent);
1463
+ return self === name || self === null;
1464
+ }
1465
+ packageDir(specifier, parentURL) {
1466
+ const name = barePackageName(specifier);
1467
+ if (name === void 0) return void 0;
1468
+ const state = this.routeUrl(specifier, parentURL);
1469
+ if (state?.route.kind === "fallback") return state.route.entry.packageDir;
1470
+ if (state?.packageDir !== void 0) return state.packageDir;
1471
+ let parent;
1472
+ try {
1473
+ parent = state?.route.kind === "after-fallback" ? state.route.parent : fileURLToPath(parentURL);
1474
+ } catch {
1475
+ return;
1476
+ }
1477
+ const found = nativePackageDir(parent, name);
1478
+ if (state !== void 0 && found !== void 0) state.packageDir = found;
1479
+ return found;
1480
+ }
1481
+ };
1482
+ function internalModules() {
1483
+ const addon = createRequire(import.meta.url)("node-addon-require-builtin");
1484
+ const esmModule = addon.requireBuiltin("internal/modules/esm/loader");
1485
+ const cjsModule = addon.requireBuiltin("internal/modules/cjs/loader");
1486
+ const cjsHelpers = addon.requireBuiltin("internal/modules/helpers");
1487
+ const esmUtils = addon.requireBuiltin("internal/modules/esm/utils");
1488
+ const esmResolve = addon.requireBuiltin("internal/modules/esm/resolve");
1489
+ const esm = esmModule.getOrInitializeCascadedLoader();
1490
+ const modern = "getOrCreateModuleJob" in esm;
1491
+ /* v8 ignore start -- the supported Node 22/24/26 matrix validates each available Internal interface */
1492
+ if (typeof esm.resolveSync !== "function" || typeof Reflect.get(esm, modern ? "getOrCreateModuleJob" : "getModuleJobForImport") !== "function" || !modern && typeof Reflect.get(esm, "resolve") !== "function" || typeof cjsModule.Module._resolveFilename !== "function" || typeof cjsHelpers.getCjsConditions !== "function" || typeof esmUtils.getDefaultConditions !== "function" || typeof esmResolve.defaultResolve !== "function") throw new Error("profile resolution: unsupported Node module loader");
1493
+ /* v8 ignore stop */
1494
+ return {
1495
+ esm,
1496
+ esmDefaultResolve: (specifier, context) => esmResolve.defaultResolve(specifier, context),
1497
+ esmConditions: esmUtils.getDefaultConditions(),
1498
+ cjs: cjsModule.Module,
1499
+ cjsConditions: cjsHelpers.getCjsConditions(),
1500
+ modern
1501
+ };
1502
+ }
1503
+ function throwWithImporter(error, routedParent, parent) {
1504
+ const code = error.code;
1505
+ if (error instanceof Error && (code === "ERR_MODULE_NOT_FOUND" || code === "ERR_PACKAGE_PATH_NOT_EXPORTED")) {
1506
+ const routedPath = fileURLToPath(routedParent);
1507
+ const parentPath = fileURLToPath(parent);
1508
+ const originalMessage = error.message;
1509
+ const message = originalMessage.replaceAll(routedParent, parent).replaceAll(routedPath, parentPath);
1510
+ const stack = error.stack;
1511
+ error.message = message;
1512
+ /* v8 ignore next -- Node's resolver errors always carry a stack */
1513
+ if (stack !== void 0) error.stack = stack.replace(originalMessage, message);
1514
+ }
1515
+ throw error;
1516
+ }
1517
+ function throwWithoutCjsAnchor(error, anchor) {
1518
+ const resolved = error;
1519
+ const requireStack = resolved.requireStack;
1520
+ if (error instanceof Error && resolved.code === "MODULE_NOT_FOUND" && requireStack?.[0] !== void 0 && sameResolution(requireStack[0], anchor)) {
1521
+ const originalMessage = error.message;
1522
+ const originalBlock = `\nRequire stack:\n${requireStack.map((path) => `- ${path}`).join("\n")}`;
1523
+ const remaining = requireStack.slice(1);
1524
+ /* v8 ignore next -- routed calls always retain the original importing module */
1525
+ const replacement = remaining.length === 0 ? "" : `\nRequire stack:\n${remaining.map((path) => `- ${path}`).join("\n")}`;
1526
+ error.message = originalMessage.replace(originalBlock, replacement);
1527
+ resolved.requireStack = remaining;
1528
+ const stack = error.stack;
1529
+ /* v8 ignore next -- Node's resolver errors always carry a stack */
1530
+ if (stack !== void 0) error.stack = stack.replace(originalMessage, error.message);
1531
+ }
1532
+ throw error;
1533
+ }
1534
+ function assertEquivalent(actual, expected, request, parent) {
1535
+ if (sameResolution(actual, expected)) return;
1536
+ throw new Error(`profile resolution mismatch for ${JSON.stringify(request)} from ${parent}: disk resolved ${actual}, generation resolved ${expected}`);
1537
+ }
1538
+ function assertOptionalEquivalent(actual, expected, request, parent) {
1539
+ if (actual === void 0 && expected === void 0) return;
1540
+ if (actual !== void 0 && expected !== void 0 && sameResolution(actual, expected)) return;
1541
+ throw new Error(`profile resolution mismatch for ${JSON.stringify(request)} from ${parent}: disk selected ${actual ?? "nothing"}, generation selected ${expected ?? "nothing"}`);
1542
+ }
1543
+ /**
1544
+ * Install one profile generation on Node's default ESM and CommonJS resolvers.
1545
+ * @param generation - complete package table and profile scope.
1546
+ * @param behavior - enforce the generation, or verify a materialized generation.
1547
+ * @returns a registration that replaces the generation or restores the native methods.
1548
+ */
1549
+ function installProfileResolution(generation, behavior = "enforce") {
1550
+ const router = new ResolutionRouter(generation);
1551
+ const { esm, esmDefaultResolve, esmConditions, cjs, cjsConditions, modern } = internalModules();
1552
+ const esmScope = /* @__PURE__ */ new Map();
1553
+ const profilePaths = [...prefixes(generation.profilesDir), ...generation.profileDir === void 0 ? [] : prefixes(generation.profileDir)];
1554
+ const profileUrls = profilePaths.map((path) => pathToFileURL(path).href);
1555
+ let recentEsmParent;
1556
+ let recentEsmScoped = false;
1557
+ let delegatedEsm;
1558
+ const adaptEsm = (native) => {
1559
+ const adapted = (request, parent, attributes) => {
1560
+ const delegated = delegatedEsm;
1561
+ /* v8 ignore next -- reentry requires a separate synchronous Node hook; supported launches install none */
1562
+ if (delegated !== void 0 && delegated.parent === parent && delegated.request === request) return native(request, parent, attributes);
1563
+ if (parent === void 0) return native(request, parent, attributes);
1564
+ let scoped = recentEsmParent === parent ? recentEsmScoped : esmScope.get(parent);
1565
+ if (scoped === void 0) {
1566
+ scoped = startsWithin(parent, profileUrls);
1567
+ esmScope.set(parent, scoped);
1568
+ }
1569
+ if (recentEsmParent !== parent) {
1570
+ recentEsmParent = parent;
1571
+ recentEsmScoped = scoped;
1572
+ }
1573
+ if (!scoped) return native(request, parent, attributes);
1574
+ const state = router.routeUrl(request, parent);
1575
+ if (state === void 0) {
1576
+ const target = request[0] === "#" ? packageImportsTarget(fileURLToPath(parent), request, esmConditions) : void 0;
1577
+ if (target === void 0) return native(request, parent, attributes);
1578
+ const restoreImporter = (error) => throwWithImporter(error, target.parentURL, parent);
1579
+ let expected;
1580
+ try {
1581
+ expected = adapted(target.specifier, target.parentURL, attributes);
1582
+ /* v8 ignore next -- Node 24+ resolves synchronously; the Node 22 matrix covers its Promise result */
1583
+ if (expected instanceof Promise) expected = expected.catch(restoreImporter);
1584
+ } catch (error) {
1585
+ return restoreImporter(error);
1586
+ }
1587
+ if (behavior === "enforce") return expected;
1588
+ const actual = native(request, parent, attributes);
1589
+ /* v8 ignore start -- Node 22 is the asynchronous adapter and is covered by the external version matrix */
1590
+ if (expected instanceof Promise || actual instanceof Promise) return Promise.all([actual, expected]).then(([resolved, wanted]) => {
1591
+ assertEquivalent(resolved.url, wanted.url, request, parent);
1592
+ return resolved;
1593
+ });
1594
+ /* v8 ignore stop */
1595
+ assertEquivalent(actual.url, expected.url, request, parent);
1596
+ return actual;
1597
+ }
1598
+ const cacheable = attributes === EMPTY_ATTRIBUTES || Object.keys(attributes).length === 0;
1599
+ if (cacheable && state.esm !== void 0) return state.esm;
1600
+ const route = state.route;
1601
+ if (route.kind === "native") {
1602
+ const result = native(request, parent, attributes);
1603
+ if (cacheable && !(result instanceof Promise)) state.esm = result;
1604
+ return result;
1605
+ }
1606
+ const routedParent = pathToFileURL(route.kind === "fallback" ? route.entry.declarer : route.parent).href;
1607
+ if (behavior === "enforce") {
1608
+ const previous = delegatedEsm;
1609
+ delegatedEsm = {
1610
+ parent: routedParent,
1611
+ request
1612
+ };
1613
+ const restoreImporter = (error) => throwWithImporter(error, routedParent, parent);
1614
+ try {
1615
+ let result;
1616
+ try {
1617
+ result = native(request, routedParent, attributes);
1618
+ } catch (error) {
1619
+ return restoreImporter(error);
1620
+ }
1621
+ /* v8 ignore next -- Node 24+ resolves synchronously; the Node 22 matrix covers its Promise result */
1622
+ if (result instanceof Promise) return result.catch(restoreImporter);
1623
+ if (cacheable) state.esm = result;
1624
+ return result;
1625
+ } finally {
1626
+ delegatedEsm = previous;
1627
+ }
1628
+ }
1629
+ const actual = native(request, parent, attributes);
1630
+ const previous = delegatedEsm;
1631
+ delegatedEsm = {
1632
+ parent: routedParent,
1633
+ request
1634
+ };
1635
+ const restoreImporter = (error) => throwWithImporter(error, routedParent, parent);
1636
+ try {
1637
+ let expected;
1638
+ try {
1639
+ const result = native(request, routedParent, attributes);
1640
+ expected = result;
1641
+ /* v8 ignore next -- Node 24+ resolves synchronously; the Node 22 matrix covers its Promise result */
1642
+ if (result instanceof Promise) expected = result.catch(restoreImporter);
1643
+ } catch (error) {
1644
+ return restoreImporter(error);
1645
+ }
1646
+ /* v8 ignore start -- Node 22 is the asynchronous adapter and is covered by the external version matrix */
1647
+ if (expected instanceof Promise || actual instanceof Promise) return Promise.all([actual, expected]).then(([resolved, wanted]) => {
1648
+ assertEquivalent(resolved.url, wanted.url, request, parent);
1649
+ if (cacheable) state.esm = resolved;
1650
+ return resolved;
1651
+ });
1652
+ /* v8 ignore stop */
1653
+ assertEquivalent(actual.url, expected.url, request, parent);
1654
+ if (cacheable) state.esm = actual;
1655
+ return actual;
1656
+ } finally {
1657
+ delegatedEsm = previous;
1658
+ }
1659
+ };
1660
+ return adapted;
1661
+ };
1662
+ let restoreEsm;
1663
+ /* v8 ignore else -- CI coverage runs Node 24 v2; the Node 22 matrix exercises the v1 adapter */
1664
+ if (modern) {
1665
+ const loader = esm;
1666
+ const original = Reflect.get(loader, "resolveSync");
1667
+ const resolveRequest = adaptEsm((request, parent, attributes) => original.call(loader, parent, {
1668
+ specifier: request,
1669
+ attributes
1670
+ }));
1671
+ const wrapped = (parent, request, ...rest) => rest.length ? Reflect.apply(original, loader, [
1672
+ parent,
1673
+ request,
1674
+ ...rest
1675
+ ]) : resolveRequest(request.specifier, parent, request.attributes ?? EMPTY_ATTRIBUTES);
1676
+ loader.resolveSync = wrapped;
1677
+ restoreEsm = () => {
1678
+ /* v8 ignore else -- registrations are disposed in reverse installation order */
1679
+ if (loader.resolveSync === wrapped) loader.resolveSync = original;
1680
+ };
1681
+ } else {
1682
+ const loader = esm;
1683
+ const original = Reflect.get(loader, "resolve");
1684
+ const originalSync = Reflect.get(loader, "resolveSync");
1685
+ const resolveRequest = adaptEsm((request, parent, attributes) => original.call(loader, request, parent, attributes));
1686
+ const resolveRequestSync = adaptEsm((request, parent, attributes) => originalSync.call(loader, request, parent, attributes));
1687
+ const wrapped = (request, parent, attributes = EMPTY_ATTRIBUTES) => resolveRequest(request, parent, attributes);
1688
+ const wrappedSync = (request, parent, attributes = EMPTY_ATTRIBUTES) => resolveRequestSync(request, parent, attributes);
1689
+ loader.resolve = wrapped;
1690
+ loader.resolveSync = wrappedSync;
1691
+ restoreEsm = () => {
1692
+ if (loader.resolve === wrapped) loader.resolve = original;
1693
+ if (loader.resolveSync === wrappedSync) loader.resolveSync = originalSync;
1694
+ };
1695
+ }
1696
+ const originalFilename = Reflect.get(cjs, "_resolveFilename");
1697
+ let delegatedCjs = 0;
1698
+ const resolveRoutedCjs = (request, routed, parent, main, options) => {
1699
+ const anchor = routed.kind === "fallback" ? routed.entry.declarer : routed.parent;
1700
+ const synthetic = new cjs(anchor);
1701
+ synthetic.parent = parent;
1702
+ synthetic.filename = anchor;
1703
+ synthetic.paths = routed.kind === "fallback" ? packageSearchPaths(routed.entry, request, cjs) : cjs._nodeModulePaths(dirname(anchor));
1704
+ try {
1705
+ return originalFilename.call(cjs, request, synthetic, main, options);
1706
+ } catch (error) {
1707
+ return throwWithoutCjsAnchor(error, anchor);
1708
+ }
1709
+ };
1710
+ const resolveNativeCjs = (request, searchPaths, parent, parentFilename, main, conditions) => {
1711
+ const synthetic = new cjs(parentFilename);
1712
+ synthetic.parent = parent;
1713
+ synthetic.filename = parentFilename;
1714
+ synthetic.paths = [...searchPaths];
1715
+ const options = conditions === void 0 ? void 0 : { conditions };
1716
+ return originalFilename.call(cjs, request, synthetic, main, options);
1717
+ };
1718
+ const resolvePackageImportCjs = (target, conditions) => {
1719
+ const state = router.routeUrl(target.specifier, target.parentURL);
1720
+ const resolveFrom = (parentURL) => fileURLToPath(esmDefaultResolve(target.specifier, {
1721
+ parentURL,
1722
+ conditions: [...conditions]
1723
+ }).url);
1724
+ /* v8 ignore next -- the target manifest was found inside the established profile scope */
1725
+ if (state === void 0) return resolveFrom(target.parentURL);
1726
+ if (state.route.kind === "native") return resolveFrom(target.parentURL);
1727
+ const route = state.route;
1728
+ if (route.kind === "after-fallback") return resolveFrom(pathToFileURL(route.parent).href);
1729
+ return resolveFrom(pathToFileURL(route.entry.declarer).href);
1730
+ };
1731
+ const wrappedFilename = (request, parent, main, options) => {
1732
+ if (delegatedCjs || !parent?.filename) return originalFilename.call(cjs, request, parent, main, options);
1733
+ const parentFilename = parent.filename;
1734
+ const cacheable = options?.paths === void 0 && options?.conditions === void 0;
1735
+ const explicitPaths = Array.isArray(options?.paths) ? options.paths : void 0;
1736
+ if (explicitPaths !== void 0 && router.nativeSelfReference(request, parentFilename)) return originalFilename.call(cjs, request, parent, main, options);
1737
+ const explicit = explicitPaths === void 0 ? void 0 : router.explicitRoute(request, explicitPaths);
1738
+ if (options?.paths !== void 0 && explicit === void 0) return originalFilename.call(cjs, request, parent, main, options);
1739
+ if (explicit !== void 0 && explicitPaths !== void 0 && explicit.index > 0) try {
1740
+ return originalFilename.call(cjs, request, parent, main, {
1741
+ ...options,
1742
+ paths: explicitPaths.slice(0, explicit.index)
1743
+ });
1744
+ } catch (error) {
1745
+ if (!isUnselectedPackageMiss(error)) throw error;
1746
+ }
1747
+ const state = router.routePath(request, explicit?.parent ?? parentFilename, (searchPaths) => resolveNativeCjs(request, searchPaths, parent, parentFilename, main, options?.conditions), explicit === void 0 && cacheable);
1748
+ if (state === void 0) {
1749
+ const scoped = startsWithin(parentFilename, profilePaths);
1750
+ const conditions = options?.conditions ?? cjsConditions;
1751
+ const target = request[0] === "#" && scoped ? packageImportsTarget(parentFilename, request, conditions) : void 0;
1752
+ if (target === void 0) return originalFilename.call(cjs, request, parent, main, options);
1753
+ const expected = resolvePackageImportCjs(target, conditions);
1754
+ if (behavior === "enforce") return expected;
1755
+ const actual = originalFilename.call(cjs, request, parent, main, options);
1756
+ assertEquivalent(actual, expected, request, parentFilename);
1757
+ return actual;
1758
+ }
1759
+ if (cacheable && state.cjs !== void 0) return state.cjs;
1760
+ const route = state.route;
1761
+ if (route.kind === "native") {
1762
+ const result = originalFilename.call(cjs, request, parent, main, options);
1763
+ if (cacheable) state.cjs = result;
1764
+ return result;
1765
+ }
1766
+ if (route.kind === "after-fallback" && explicit !== void 0 && explicitPaths !== void 0) try {
1767
+ return originalFilename.call(cjs, request, parent, main, {
1768
+ ...options,
1769
+ paths: [dirname(route.parent)]
1770
+ });
1771
+ } catch (error) {
1772
+ if (!isUnselectedPackageMiss(error)) throw error;
1773
+ const remaining = explicitPaths.slice(explicit.index + 1);
1774
+ if (remaining.length === 0) throw error;
1775
+ return wrappedFilename(request, parent, main, {
1776
+ ...options,
1777
+ paths: remaining
1778
+ });
1779
+ }
1780
+ delegatedCjs++;
1781
+ try {
1782
+ const routedOptions = options?.conditions === void 0 ? void 0 : { conditions: options.conditions };
1783
+ let expected;
1784
+ try {
1785
+ expected = resolveRoutedCjs(request, route, parent, main, routedOptions);
1786
+ } catch (error) {
1787
+ if (route.kind !== "fallback" || !isUnselectedPackageMiss(error)) throw error;
1788
+ try {
1789
+ expected = resolveRoutedCjs(request, {
1790
+ kind: "after-fallback",
1791
+ parent: route.after
1792
+ }, parent, main, routedOptions);
1793
+ } catch (afterError) {
1794
+ const remaining = explicit === void 0 || explicitPaths === void 0 ? [] : explicitPaths.slice(explicit.index + 1);
1795
+ if (!isUnselectedPackageMiss(afterError) || remaining.length === 0) throw afterError;
1796
+ delegatedCjs--;
1797
+ try {
1798
+ return wrappedFilename(request, parent, main, {
1799
+ ...options,
1800
+ paths: remaining
1801
+ });
1802
+ } finally {
1803
+ delegatedCjs++;
1804
+ }
1805
+ }
1806
+ }
1807
+ if (behavior === "enforce") {
1808
+ if (cacheable) state.cjs = expected;
1809
+ return expected;
1810
+ }
1811
+ const actual = originalFilename.call(cjs, request, parent, main, options);
1812
+ assertEquivalent(actual, expected, request, parentFilename);
1813
+ if (cacheable) state.cjs = actual;
1814
+ return actual;
1815
+ } finally {
1816
+ delegatedCjs--;
1817
+ }
1818
+ };
1819
+ cjs._resolveFilename = wrappedFilename;
1820
+ return {
1821
+ packageDir(specifier, parentURL) {
1822
+ const expected = router.packageDir(specifier, parentURL);
1823
+ if (behavior !== "verify" || !startsWithin(parentURL, profileUrls)) return expected;
1824
+ const name = barePackageName(specifier);
1825
+ if (name === void 0) return expected;
1826
+ let parent;
1827
+ try {
1828
+ parent = fileURLToPath(parentURL);
1829
+ } catch {
1830
+ return expected;
1831
+ }
1832
+ assertOptionalEquivalent(nativePackageDir(parent, name), expected, specifier, parentURL);
1833
+ return expected;
1834
+ },
1835
+ replace(next) {
1836
+ router.replace(next);
1837
+ },
1838
+ dispose() {
1839
+ /* v8 ignore else -- registrations are disposed in reverse installation order */
1840
+ if (cjs._resolveFilename === wrappedFilename) cjs._resolveFilename = originalFilename;
1841
+ restoreEsm();
1842
+ }
1843
+ };
1844
+ }
1845
+ /**
1846
+ * Publish one generation for Harness-owned Workers.
1847
+ * @param generation - complete package table and profile scope.
1848
+ * @param behavior - enforce or verify the generation in newly created Workers.
1849
+ * @returns a disposer restoring the previous thread environment data.
1850
+ */
1851
+ function registerWorkerResolution(generation, behavior = "enforce") {
1852
+ const previous = getEnvironmentData(WORKER_RESOLUTION_KEY);
1853
+ setEnvironmentData(WORKER_RESOLUTION_KEY, {
1854
+ generation,
1855
+ behavior
1856
+ });
1857
+ return () => {
1858
+ setEnvironmentData(WORKER_RESOLUTION_KEY, previous);
1859
+ };
1860
+ }
1861
+ //#endregion
1862
+ //#region lib/types/profile-resolution/service.js
1863
+ /** Package metadata resolved through one profile resolution registration. */
1864
+ function readPackage(dir, fallbackName) {
1865
+ const manifestPath = join(dir, "package.json");
1866
+ if (!existsSync(manifestPath)) return void 0;
1867
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
1868
+ const name = manifest.name;
1869
+ const version = manifest.version;
1870
+ return {
1871
+ name: typeof name === "string" ? name : fallbackName,
1872
+ version: typeof version === "string" ? version : void 0,
1873
+ dir,
1874
+ manifestPath,
1875
+ manifest
1876
+ };
1877
+ }
1878
+ /** Package lookup shared by metadata consumers in one profile process. */
1879
+ var PluginPackages = class extends Service {
1880
+ packages = /* @__PURE__ */ new Map();
1881
+ resolver;
1882
+ behavior;
1883
+ disposeWorkerResolution;
1884
+ constructor(ctx, config = {}) {
1885
+ super(ctx, "pluginPackages");
1886
+ this.behavior = config.behavior ?? "enforce";
1887
+ if (config.generation === void 0) return;
1888
+ const resolver = installProfileResolution(config.generation, this.behavior);
1889
+ this.disposeWorkerResolution = registerWorkerResolution(config.generation, this.behavior);
1890
+ this.resolver = resolver;
1891
+ ctx.effect(() => () => {
1892
+ this.disposeWorkerResolution?.();
1893
+ resolver.dispose();
1894
+ }, "profile package resolution");
1895
+ }
1896
+ /**
1897
+ * Publish an additive generation for this process and subsequently created Workers.
1898
+ * @param generation - fully constructed successor generation.
1899
+ */
1900
+ replace(generation) {
1901
+ if (this.resolver === void 0) throw new Error("plugin-packages: runtime resolution is not installed");
1902
+ this.resolver.replace(generation);
1903
+ this.packages = /* @__PURE__ */ new Map();
1904
+ this.disposeWorkerResolution?.();
1905
+ this.disposeWorkerResolution = registerWorkerResolution(generation, this.behavior);
1906
+ }
1907
+ /**
1908
+ * Locate the package named by a specifier without requiring a package export.
1909
+ * @param specifier - module specifier whose package owns the requested module.
1910
+ * @param parentURL - URL whose Node lookup order applies.
1911
+ * @returns the parsed package, or undefined when no package owns the request.
1912
+ */
1913
+ packageOf(specifier, parentURL) {
1914
+ const name = barePackageName(specifier);
1915
+ if (name === void 0) return void 0;
1916
+ const dir = this.resolver === void 0 ? packageDirFromParent(name, parentURL) : this.resolver.packageDir(name, parentURL);
1917
+ if (dir === void 0) return void 0;
1918
+ const key = JSON.stringify({
1919
+ dir,
1920
+ name
1921
+ });
1922
+ if (!this.packages.has(key)) this.packages.set(key, readPackage(dir, name));
1923
+ return this.packages.get(key);
1924
+ }
1925
+ };
1926
+ function packageDirFromParent(name, parentURL) {
1927
+ for (const searchPath of createRequire(parentURL).resolve.paths(name)) {
1928
+ const candidate = join(searchPath, name);
1929
+ if (existsSync(join(candidate, "package.json"))) return candidate;
1930
+ }
1931
+ }
1932
+ //#endregion
911
1933
  //#region lib/types/index.js
912
1934
  /**
913
1935
  * Shared boot glue for `dsh` profiles, including the CLI packaged by the Python runtime wheel: load the gitignored
@@ -1099,33 +2121,38 @@ function loadLayeredEnv(binName, cwd = process.cwd(), warn = (line) => void proc
1099
2121
  }
1100
2122
  const bootstrapIncludes = /* @__PURE__ */ new WeakMap();
1101
2123
  const userPatchesSchema = entryListSchema;
1102
- /**
1103
- * Watch the user patch layer through Cordis HMR and transactionally reapply it to the boot include.
1104
- * @param ctx - settled app context containing the root Include and an active HMR service.
1105
- * @param options - diagnostic, file, and patch-composition inputs.
1106
- * @returns an asynchronous disposer after the exact-path watcher is ready.
1107
- * @throws when HMR or the root Include is absent, watcher setup fails, or initial path resolution fails.
2124
+ /** Apply one complete patch generation and wait for Loader activation diagnostics.
2125
+ * @param ctx Booted root context.
2126
+ * @param patches Complete ordered patch list.
2127
+ * @param binName Diagnostic prefix.
2128
+ * @param requiredIds Explicit enablement targets whose existing failures also reject reconciliation.
2129
+ * @returns Diagnostics for unchanged pre-existing inactive entries; new or changed failures reject.
1108
2130
  */
1109
- async function watchUserPatches(ctx, options) {
1110
- const { binName, filename, compose = (patches) => patches } = options;
1111
- const hmr = ctx.get("hmr");
1112
- if (hmr === void 0) throw new Error(`${binName}: user patch-layer watching requires the Cordis HMR service`);
2131
+ async function reconcileProfilePatches(ctx, patches, binName, requiredIds = []) {
1113
2132
  const entry = bootstrapIncludes.get(ctx);
1114
- if (entry === void 0) throw new Error(`${binName}: user patch-layer watching requires the root Include entry`);
1115
- const register = hmr.registerConfig(filename, async () => {
1116
- const { patches: _previousPatches, ...includeConfig } = entry.options.config;
1117
- const patches = compose(loadOptionalPatches(binName, filename) ?? []);
1118
- await entry.update({ config: {
1119
- ...includeConfig,
1120
- patches
1121
- } });
1122
- });
1123
- try {
1124
- return await register;
1125
- } catch (error) {
1126
- if (error?.code === "INACTIVE_EFFECT") return async () => {};
1127
- throw error;
1128
- }
2133
+ if (entry === void 0) throw new Error(`${binName}: profile reload requires the root Include entry`);
2134
+ const previousFailures = (await inactiveEntries(ctx)).map((failure) => ({
2135
+ ...failure,
2136
+ diagnostic: inactiveDiagnostic(failure),
2137
+ fiber: failure.entry.fiber,
2138
+ options: JSON.stringify(failure.entry.options)
2139
+ }));
2140
+ const previousFibers = [...ctx.loader.entries()].flatMap((row) => row.fiber === void 0 ? [] : [{
2141
+ fiber: row.fiber,
2142
+ failed: row.fiber.state === FIBER_FAILED || row.fiber.state === FIBER_DISPOSED
2143
+ }]);
2144
+ const { patches: _previous, ...includeConfig } = entry.options.config;
2145
+ await entry.update({ config: {
2146
+ ...includeConfig,
2147
+ patches
2148
+ } });
2149
+ const results = await Promise.allSettled(previousFibers.map(({ fiber }) => fiber.await()));
2150
+ await ctx.loader.await();
2151
+ const failures = await inactiveEntries(ctx);
2152
+ const introduced = failures.filter((failure) => requiredIds.includes(failure.entry.options.id) || !previousFailures.some((previous) => previous.entry === failure.entry && previous.fiber === failure.entry.fiber && previous.options === JSON.stringify(failure.entry.options) && previous.diagnostic === inactiveDiagnostic(failure)));
2153
+ if (introduced.length > 0) throw new Error(activationDiagnostic(binName, introduced).trimEnd());
2154
+ for (const [index, result] of results.entries()) if (result.status === "rejected" && !previousFibers[index]?.failed) throw result.reason;
2155
+ return failures.map(inactiveDiagnostic);
1129
2156
  }
1130
2157
  /**
1131
2158
  * Load an optional patch-list file: a top-level YAML array of loader patch
@@ -1257,7 +2284,7 @@ function renderConfigDump(binName, absoluteConfigPath, layers, warn = (line) =>
1257
2284
  };
1258
2285
  let previous = base;
1259
2286
  let previousWarnings = [];
1260
- const provenance = base.map(() => ({
2287
+ const entryOrigins = base.map(() => ({
1261
2288
  origin: baseLabel,
1262
2289
  patchedBy: []
1263
2290
  }));
@@ -1270,18 +2297,18 @@ function renderConfigDump(binName, absoluteConfigPath, layers, warn = (line) =>
1270
2297
  composed = snapshot(count, warnings);
1271
2298
  for (const line of warnings.slice(previousWarnings.length)) warn(`${binName}: [${layer.label}] ${line}`);
1272
2299
  const before = previous.map((entry) => JSON.stringify(entry));
1273
- for (let index = 0; index < composed.length; index += 1) if (index >= before.length) provenance.push({
2300
+ for (let index = 0; index < composed.length; index += 1) if (index >= before.length) entryOrigins.push({
1274
2301
  origin: layer.label,
1275
2302
  patchedBy: []
1276
2303
  });
1277
- else if (JSON.stringify(composed[index]) !== before[index]) provenance[index]?.patchedBy.push(layer.label);
2304
+ else if (JSON.stringify(composed[index]) !== before[index]) entryOrigins[index]?.patchedBy.push(layer.label);
1278
2305
  previous = composed;
1279
2306
  previousWarnings = warnings;
1280
2307
  }
1281
- return groupedDump(composed, provenance);
2308
+ return groupedDump(composed, entryOrigins);
1282
2309
  }
1283
2310
  /** Render the composed rows grouped under one source-and-patches comment per contiguous run. */
1284
- function groupedDump(composed, provenance) {
2311
+ function groupedDump(composed, entryOrigins) {
1285
2312
  const lines = [];
1286
2313
  let currentLabel;
1287
2314
  let group = [];
@@ -1295,7 +2322,7 @@ function groupedDump(composed, provenance) {
1295
2322
  group = [];
1296
2323
  };
1297
2324
  for (let index = 0; index < composed.length; index += 1) {
1298
- const record = provenance[index];
2325
+ const record = entryOrigins[index];
1299
2326
  /* v8 ignore next -- this array is index-aligned with composed by construction */
1300
2327
  if (record === void 0) continue;
1301
2328
  const label = record.patchedBy.length === 0 ? record.origin : `${record.origin}, patched by ${record.patchedBy.join(", ")}`;
@@ -1317,7 +2344,7 @@ function groupedDump(composed, provenance) {
1317
2344
  * names; relative names continue to resolve beside the configuration file.
1318
2345
  * @returns the created root Include entry, or `undefined` when a surface
1319
2346
  * disposed the whole tree (taking the Loader service with it) while the
1320
- * transactional create was still settling entry lifecycle.
2347
+ * entry creation was in flight.
1321
2348
  */
1322
2349
  async function mountRootInclude(ctx, absoluteConfigPath, patches = [], bareModuleBaseUrl) {
1323
2350
  ctx.loader.builtins.include = bareModuleBaseUrl === void 0 ? Include : class HostResolvedRootInclude extends Include {
@@ -1372,7 +2399,7 @@ const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2e3;
1372
2399
  /**
1373
2400
  * Install before boot to turn a late unhandled plugin-init rejection into one
1374
2401
  * labelled stderr diagnostic and `exit(1)`. A rejection already included by
1375
- * {@link assertEntriesActivated} is ignored during its process checkpoint;
2402
+ * {@link auditStartupEntries} is ignored during its process checkpoint;
1376
2403
  * every other rejection remains fatal. Stdout remains untouched for ACP; the
1377
2404
  * returned function removes the handler.
1378
2405
  *
@@ -1425,50 +2452,99 @@ function installFailLoud(binName, proc = process, release) {
1425
2452
  return uninstall;
1426
2453
  }
1427
2454
  /**
1428
- * After the tree settles, reject entries with no fiber and name every plugin
1429
- * whose module failed to resolve. Disabled entries are the only valid
1430
- * fiber-less state.
1431
- * @param ctx - the settled context whose loader entries to audit.
1432
- * @param binName - the diagnostic prefix on the thrown error.
1433
- */
1434
- function assertEntriesLoaded(ctx, binName) {
1435
- const failed = [...ctx.loader.entries()].filter((entry) => entry.fiber === void 0 && !entry.disabled);
1436
- if (failed.length > 0) {
1437
- const names = failed.map((entry) => entry.options.name).join(", ");
1438
- throw new Error(`${binName}: plugin(s) failed to load: ${names}; Cordis startup failed because these plugin(s) could not be resolved (see the error(s) logged above)`);
1439
- }
1440
- }
1441
- /**
1442
2455
  * Value mirrors used because Cordis's const enum has no runtime object to import.
1443
- * Keep aligned with `packages/extensions/tool-cordis/src/fiber-state.ts` and
1444
- * `packages/client/web/src/loader-status.ts`.
2456
+ * Keep aligned with `packages/client/web/src/loader-status.ts`.
1445
2457
  */
1446
2458
  const FIBER_PENDING = 0;
1447
2459
  const FIBER_ACTIVE = 2;
1448
2460
  const FIBER_FAILED = 3;
1449
- /** Render a thrown plugin value without discarding an Error's original stack. */
2461
+ const FIBER_DISPOSED = 4;
2462
+ /**
2463
+ * Entry ids whose presence defines a usable DSH application.
2464
+ *
2465
+ * The list is global rather than profile metadata. Missing or disabled ids do
2466
+ * not affect startup; an enabled listed entry must activate. The list covers
2467
+ * shared Agent execution, application endpoints, and Web bootstrap/transport.
2468
+ */
2469
+ const requiredStartupEntryIds = new Set([
2470
+ "agent-loop",
2471
+ "webserver",
2472
+ "modules",
2473
+ "connection",
2474
+ "headless-runner",
2475
+ "acp",
2476
+ "sdk-jsonrpc-server"
2477
+ ]);
2478
+ /** Render plugin stacks, nested causes, and aggregate member failures once per error. */
1450
2479
  function formatActivationError(error) {
1451
- return error instanceof Error ? error.stack ?? error.message : String(error);
2480
+ const details = [];
2481
+ const seen = /* @__PURE__ */ new Set();
2482
+ function visit(value) {
2483
+ if (!(value instanceof Error)) {
2484
+ details.push(String(value));
2485
+ return;
2486
+ }
2487
+ if (seen.has(value)) return;
2488
+ seen.add(value);
2489
+ details.push(value.stack ?? value.message);
2490
+ if (value.cause !== void 0) visit(value.cause);
2491
+ if (value instanceof AggregateError) value.errors.forEach(visit);
2492
+ }
2493
+ visit(error);
2494
+ return details.join("\n");
1452
2495
  }
2496
+ /** Startup audit failure with non-enumerable metadata and original failures as its cause. */
2497
+ var StartupError = class extends Error {
2498
+ entries;
2499
+ /** Root configuration and startup logs, attached by boot after disposal. */
2500
+ startup;
2501
+ /**
2502
+ * @param message - concise terminal diagnostic.
2503
+ * @param entries - inactive plugin metadata and original failure values.
2504
+ */
2505
+ constructor(message, entries) {
2506
+ const failures = entries.flatMap(({ outcome }) => outcome.kind === "failed" ? [outcome.error] : []);
2507
+ super(message, failures.length > 0 ? { cause: new AggregateError(failures, "Plugin activation failures") } : void 0);
2508
+ this.entries = entries;
2509
+ Object.defineProperties(this, {
2510
+ entries: { enumerable: false },
2511
+ startup: { enumerable: false }
2512
+ });
2513
+ }
2514
+ };
1453
2515
  /**
1454
- * Reject a settled Loader tree when an enabled entry failed or remains inactive.
1455
- * Plugin failures include the original thrown stack; pending entries name their
1456
- * unresolved services because no plugin error exists for that state. Active
1457
- * entries require no further wait; only failed fibers are awaited to recover
1458
- * their private rejection reason.
1459
- * @param ctx - the settled context whose Loader entries to audit.
1460
- * @param binName - the diagnostic prefix on the thrown error.
1461
- * @returns nothing when every enabled entry is active.
1462
- * @throws after one process rejection checkpoint when an entry failed to
1463
- * import, rejected during activation, or did not become active.
2516
+ * Collect Loader activation failures and disabled-expression errors. Failed
2517
+ * fibers are awaited to recover their recorded rejection reason and coalesce
2518
+ * duplicate Loader notifications through the next process rejection checkpoint.
1464
2519
  */
1465
- async function assertEntriesActivated(ctx, binName) {
1466
- assertEntriesLoaded(ctx, binName);
2520
+ async function inactiveEntries(ctx) {
1467
2521
  const failures = [];
1468
2522
  const rejectionReasons = [];
1469
2523
  for (const entry of ctx.loader.entries()) {
2524
+ try {
2525
+ if (entry.disabled) continue;
2526
+ } catch (error) {
2527
+ failures.push({
2528
+ entry,
2529
+ outcome: {
2530
+ kind: "failed",
2531
+ error,
2532
+ phase: "disabled expression failed"
2533
+ }
2534
+ });
2535
+ continue;
2536
+ }
1470
2537
  const fiber = entry.fiber;
1471
- if (fiber === void 0 || entry.disabled) continue;
2538
+ if (fiber === void 0) {
2539
+ failures.push({
2540
+ entry,
2541
+ outcome: {
2542
+ kind: "failed",
2543
+ error: "failed to import"
2544
+ }
2545
+ });
2546
+ continue;
2547
+ }
1472
2548
  const state = fiber.state;
1473
2549
  if (state === FIBER_ACTIVE) continue;
1474
2550
  if (state === FIBER_FAILED) {
@@ -1476,21 +2552,104 @@ async function assertEntriesActivated(ctx, binName) {
1476
2552
  await fiber.await();
1477
2553
  } catch (error) {
1478
2554
  rejectionReasons.push(error);
1479
- failures.push(`${entry.options.name}: ${formatActivationError(error)}`);
2555
+ failures.push({
2556
+ entry,
2557
+ outcome: {
2558
+ kind: "failed",
2559
+ error
2560
+ }
2561
+ });
1480
2562
  }
1481
2563
  continue;
1482
2564
  }
1483
2565
  if (state === FIBER_PENDING) {
1484
2566
  const missing = Object.keys(fiber.inject).filter((service) => fiber.ctx.get(service) === void 0);
1485
- const subject = missing.length === 1 ? "service" : "services";
1486
- failures.push(`${entry.options.name}: pending (waiting for ${subject}: ${missing.join(", ") || "unknown"})`);
1487
- } else failures.push(`${entry.options.name}: fiber state ${String(state)}`);
2567
+ failures.push({
2568
+ entry,
2569
+ outcome: {
2570
+ kind: "pending",
2571
+ missing
2572
+ }
2573
+ });
2574
+ } else failures.push({
2575
+ entry,
2576
+ outcome: {
2577
+ kind: "failed",
2578
+ error: `fiber state ${String(state)}`
2579
+ }
2580
+ });
2581
+ }
2582
+ if (rejectionReasons.length > 0) await observeLoaderRejectionCheckpoint(rejectionReasons);
2583
+ return failures;
2584
+ }
2585
+ /** Render one failed plugin's original error and activation phase. */
2586
+ function failureDetail(outcome) {
2587
+ return `${outcome.phase === void 0 ? "" : `${outcome.phase}: `}${formatActivationError(outcome.error)}`;
2588
+ }
2589
+ /** Render optional-only warnings without changing startup policy. */
2590
+ function activationDiagnostic(binName, failures) {
2591
+ const noun = failures.length === 1 ? "entry" : "entries";
2592
+ return `${binName}: warning: ${String(failures.length)} ${noun} did not activate\n${failures.map(inactiveDiagnostic).join("\n")}\n`;
2593
+ }
2594
+ /** Stable per-entry text for reload comparisons and optional warnings. */
2595
+ function inactiveDiagnostic({ entry, outcome }) {
2596
+ const detail = outcome.kind === "failed" ? failureDetail(outcome) : `pending (waiting for ${outcome.missing.length === 1 ? "service" : "services"}: ${outcome.missing.join(", ") || "unknown"})`;
2597
+ return `${entry.options.id} (${entry.options.name}): ${detail}`;
2598
+ }
2599
+ /** Group startup failures and pending services, marking every required entry. */
2600
+ function startupDiagnostic(binName, failures, required) {
2601
+ const lines = [`${binName}: startup failed: ${String(required.size)} required ${required.size === 1 ? "plugin" : "plugins"} did not activate`];
2602
+ const failed = failures.flatMap(({ entry, outcome }) => outcome.kind === "failed" ? [{
2603
+ entry,
2604
+ outcome
2605
+ }] : []);
2606
+ const pending = failures.flatMap(({ entry, outcome }) => outcome.kind === "pending" ? [{
2607
+ entry,
2608
+ outcome
2609
+ }] : []);
2610
+ pending.sort((left, right) => Number(required.has(right.entry)) - Number(required.has(left.entry)));
2611
+ const label = (entry) => `${entry.options.id}${required.has(entry) ? " (required)" : ""}`;
2612
+ if (failed.length > 0) {
2613
+ lines.push("", `Failed plugins (${String(failed.length)}):`);
2614
+ for (const { entry, outcome } of failed) {
2615
+ lines.push(` ${label(entry)}`, ` Package: ${entry.options.name}`);
2616
+ lines.push(...failureDetail(outcome).split("\n").map((line) => ` ${line}`));
2617
+ }
1488
2618
  }
1489
- if (failures.length > 0) {
1490
- if (rejectionReasons.length > 0) await observeLoaderRejectionCheckpoint(rejectionReasons);
1491
- const noun = failures.length === 1 ? "entry" : "entries";
1492
- throw new Error(`${binName}: ${String(failures.length)} ${noun} did not activate\n${failures.join("\n")}`);
2619
+ if (pending.length > 0) {
2620
+ const width = Math.max(6, ...pending.map(({ entry }) => label(entry).length)) + 2;
2621
+ lines.push("", `Plugins waiting for services (${String(pending.length)}):`, ` ${"Plugin".padEnd(width)}Missing services`);
2622
+ for (const { entry, outcome } of pending) lines.push(` ${label(entry).padEnd(width)}${outcome.missing.join(", ") || "unknown"}`);
1493
2623
  }
2624
+ return lines.join("\n");
2625
+ }
2626
+ /**
2627
+ * Apply DSH startup policy to a settled Loader tree.
2628
+ *
2629
+ * Inactive entries from the global required list reject startup. Other
2630
+ * inactive entries join that failure diagnostic, or produce one warning when
2631
+ * no required entry failed and leave successful siblings running.
2632
+ * Required ids absent from the tree, and disabled required entries, are ignored.
2633
+ * A throwing disabled expression is an entry failure, not a disabled entry.
2634
+ * The bootstrap Include must activate so unreadable or invalid root config is fatal.
2635
+ * @param ctx - the settled context whose Loader entries to audit.
2636
+ * @param binName - the prefix on startup diagnostics.
2637
+ * @param warn - sink for optional-entry warnings.
2638
+ * @returns after optional warnings if required startup checks pass.
2639
+ * @throws {@link StartupError} when the bootstrap Include or a required entry is inactive or its disabled expression throws;
2640
+ * its message includes optional failures too.
2641
+ */
2642
+ async function auditStartupEntries(ctx, binName, warn = (line) => void process.stderr.write(line)) {
2643
+ const failures = await inactiveEntries(ctx);
2644
+ const required = new Set(failures.filter(({ entry }) => entry === bootstrapIncludes.get(ctx) || requiredStartupEntryIds.has(entry.options.id)).map(({ entry }) => entry));
2645
+ if (required.size > 0) throw new StartupError(startupDiagnostic(binName, failures, required), failures.map(({ entry, outcome }) => ({
2646
+ id: entry.options.id,
2647
+ module: entry.options.name,
2648
+ required: required.has(entry),
2649
+ fiberState: entry.fiber?.state,
2650
+ outcome
2651
+ })));
2652
+ if (failures.length > 0) warn(activationDiagnostic(binName, failures));
1494
2653
  }
1495
2654
  /**
1496
2655
  * Boot the Loader against `absoluteConfigPath` and return only after the whole
@@ -1500,12 +2659,11 @@ async function assertEntriesActivated(ctx, binName) {
1500
2659
  * is statically imported and mounted as the `cordis:include` builtin, loading
1501
2660
  * through the ambient module pipeline (vite/tsx/plain ESM). The package build
1502
2661
  * embeds Include while leaving Loader external, so the built include tree and
1503
- * host share one Loader peer. Loader
1504
- * settlement rejects startup failures, which `boot` wraps after disposing the
1505
- * partial context; a missing fiber or never-activating entry is rejected by
1506
- * the final audit, {@link assertEntriesActivated}, which rethrows a plugin's
1507
- * init rejection with its original stack; later unhandled rejections remain
1508
- * covered by {@link installFailLoud}. Built bins need the Loader's native
2662
+ * host share one Loader peer. Loader settlement drains entry work without
2663
+ * rejecting the whole tree. The final {@link auditStartupEntries} call rejects
2664
+ * failures in the global required list and warns about other failed, missing,
2665
+ * and pending entries while successful siblings remain active. Later unhandled
2666
+ * rejections remain covered by {@link installFailLoud}. Built bins need the Loader's native
1509
2667
  * helper for bare plugin specifiers; relative specifiers do not.
1510
2668
  * @param binName - the diagnostic prefix for load-failure errors.
1511
2669
  * @param absoluteConfigPath - the config to include; must already be absolute
@@ -1516,33 +2674,70 @@ async function assertEntriesActivated(ctx, binName) {
1516
2674
  * @param bareModuleBaseUrl - optional installed-host base for bare package
1517
2675
  * names; use it when the host, rather than the configuration project, owns the
1518
2676
  * complete plugin set.
1519
- * @returns the root context once every entry has started, or as soon as a
2677
+ * @returns the root context after the initial startup audit, or as soon as a
1520
2678
  * surface disposed the tree while startup was still in flight.
1521
- * @throws a labelled error after disposing the partial context — `host
2679
+ * @throws {@link StartupError} for an inactive required entry, including all inactive plugins in its message;
2680
+ * otherwise a labelled error after disposing the partial context — `host
1522
2681
  * preparation failed` when `prepare` threw before any config-tree entry
1523
- * mounted, `plugin tree failed to load` afterwards.
2682
+ * mounted, `plugin tree failed to load` afterwards. Cyclic causes terminate
2683
+ * diagnostic traversal without replacing the original cause.
1524
2684
  */
1525
2685
  async function boot(binName, absoluteConfigPath, patches, prepare, bareModuleBaseUrl) {
1526
2686
  const ctx = new Context();
2687
+ const startupLogs = [];
2688
+ const diagnostics = new Context();
2689
+ diagnostics.logger = ctx.logger;
2690
+ diagnostics.logger.exporter({
2691
+ levels: { default: 2 },
2692
+ export: ({ ts, name, type, args }) => {
2693
+ if (type === "warn" || type === "error") startupLogs.push({
2694
+ ts,
2695
+ name,
2696
+ type,
2697
+ args
2698
+ });
2699
+ }
2700
+ });
1527
2701
  let stage = "host preparation failed";
1528
2702
  try {
1529
2703
  ctx.baseUrl = pathToFileURL(dirname(absoluteConfigPath)).href + "/";
1530
2704
  ctx.provide("dshHomePath", dshHomePath);
2705
+ ctx.on("internal/update", (_config, _noSave, next) => {
2706
+ Promise.resolve(next()).catch((error) => {
2707
+ ctx.logger.error(error);
2708
+ });
2709
+ }, {
2710
+ global: true,
2711
+ prepend: true
2712
+ });
1531
2713
  await ctx.plugin(Loader);
1532
2714
  await prepare?.(ctx);
1533
2715
  stage = "plugin tree failed to load";
1534
2716
  await mountRootInclude(ctx, absoluteConfigPath, patches, bareModuleBaseUrl);
1535
2717
  await ctx.get("loader")?.await();
1536
2718
  if (ctx.get("loader") === void 0) return ctx;
1537
- await assertEntriesActivated(ctx, binName);
2719
+ await auditStartupEntries(ctx, binName);
1538
2720
  return ctx;
1539
2721
  } catch (cause) {
1540
2722
  await ctx.fiber.dispose();
2723
+ if (cause instanceof StartupError) {
2724
+ cause.startup = {
2725
+ configurationPath: absoluteConfigPath,
2726
+ messages: startupLogs
2727
+ };
2728
+ throw cause;
2729
+ }
1541
2730
  const detail = cause instanceof Error ? cause.message : String(cause);
1542
2731
  let deepest = cause;
1543
- while (deepest instanceof Error && deepest.cause !== void 0) deepest = deepest.cause;
2732
+ const seen = /* @__PURE__ */ new Set();
2733
+ while (deepest instanceof Error && !seen.has(deepest) && deepest.cause !== void 0) {
2734
+ seen.add(deepest);
2735
+ deepest = deepest.cause;
2736
+ }
1544
2737
  const stack = deepest instanceof AggregateError ? `\n${deepest.stack ?? deepest.message}\n${deepest.errors.map(formatActivationError).join("\n")}` : deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : "";
1545
2738
  throw new Error(`${binName}: ${stage}: ${detail}${stack}`, { cause });
2739
+ } finally {
2740
+ await diagnostics.fiber.dispose();
1546
2741
  }
1547
2742
  }
1548
2743
  /** Prompt-section name for the harness-source location line an app bin adds after boot. */
@@ -1572,4 +2767,4 @@ function addHarnessSourceSection(ctx, sourceRoot) {
1572
2767
  });
1573
2768
  }
1574
2769
  //#endregion
1575
- export { DEFAULT_PROFILE_BUNDLES, DEFAULT_PROFILE_PATCH_RELOAD, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, PROFILES_DIR, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, addHarnessSourceSection, assertEntriesActivated, assertEntriesLoaded, boot, composeEntries, healProfilesModuleFallback, initProfile, installFailLoud, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, readProfileManifest, renderConfigDump, resolveBundleDir, resolveConfigPath, resolveProfileDir, watchUserPatches, writeProfileManifest };
2770
+ export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, OPTIONAL_BUNDLES, PROFILES_DIR, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PluginPackages, StartupError, addHarnessSourceSection, auditStartupEntries, boot, composeEntries, createProfileResolutionGeneration, healIsolatedProfileModuleFallback, healProfilesModuleFallback, initProfile, installFailLoud, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, readProfileManifest, readProfilePatches, readProfilePlugins, reconcileProfilePatches, reconcileProfilePlugins, renderConfigDump, resolveBundleDir, resolveConfigPath, resolveProfileDir, resolveTelemetryPatch, sanitizeProfile, unlinkProfileModuleFallback, writeProfileBundles, writeProfileManifest };