@deepseek-ai/dsh-app-boot 0.2.1-alpha.2 → 0.2.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +16 -19
- package/README.md +9 -13
- package/README.zh.md +9 -13
- package/lib/index.js +339 -80
- package/lib/types/index.d.ts +41 -20
- package/lib/types/profile-context.d.ts +7 -0
- package/lib/types/profile-resolution/resolver.d.ts +14 -6
- package/lib/types/profile-resolution/service.d.ts +3 -1
- package/lib/worker/profile-resolution-bootstrap.js +133 -11
- package/package.json +18 -19
package/lib/index.js
CHANGED
|
@@ -1095,6 +1095,15 @@ function resolveTelemetryPatch(disabledEnv, hasRow) {
|
|
|
1095
1095
|
disabled: true
|
|
1096
1096
|
};
|
|
1097
1097
|
}
|
|
1098
|
+
/** Read the selected bundles' patches, which apply before the profile patch file.
|
|
1099
|
+
* @param binName Diagnostic prefix for malformed or missing configuration.
|
|
1100
|
+
* @param context Data supplied by the profile launcher.
|
|
1101
|
+
* @param initialProfile Already loaded startup profile; omitted reads the current files.
|
|
1102
|
+
* @returns Bundle patches in application order.
|
|
1103
|
+
*/
|
|
1104
|
+
function readBundlePatches(binName, context, initialProfile) {
|
|
1105
|
+
return (initialProfile ?? loadProfileDirectory(binName, context.dir, context.installAnchor, { userLayer: false })).layers.flatMap((layer) => layer.patches);
|
|
1106
|
+
}
|
|
1098
1107
|
/** Read current bundle and user layers with the launch-time overlays.
|
|
1099
1108
|
* @param binName Diagnostic prefix for malformed or missing configuration.
|
|
1100
1109
|
* @param context Data supplied by the profile launcher.
|
|
@@ -1102,9 +1111,8 @@ function resolveTelemetryPatch(disabledEnv, hasRow) {
|
|
|
1102
1111
|
* @returns Detached ordered patches; this function does not update the Loader.
|
|
1103
1112
|
*/
|
|
1104
1113
|
function readProfilePatches(binName, context, initialProfile) {
|
|
1105
|
-
const profile = initialProfile ?? loadProfileDirectory(binName, context.dir, context.installAnchor, { userLayer: false });
|
|
1106
1114
|
const patches = structuredClone([
|
|
1107
|
-
...
|
|
1115
|
+
...readBundlePatches(binName, context, initialProfile),
|
|
1108
1116
|
...initialProfile?.patches ?? loadOptionalPatches(binName, context.patchPath) ?? [],
|
|
1109
1117
|
...loadOptionalPatches(binName, join(context.home, "cordis.patch.yml")) ?? [],
|
|
1110
1118
|
...context.overlays
|
|
@@ -1238,6 +1246,14 @@ function sanitizeProfile(binName, profileDir, bundles) {
|
|
|
1238
1246
|
/** In-memory profile package routing for Node's default ESM and CommonJS loaders. */
|
|
1239
1247
|
const WORKER_RESOLUTION_KEY = "@deepseek-ai/dsh-app-boot/profile-resolution";
|
|
1240
1248
|
const EMPTY_ATTRIBUTES = Object.freeze({});
|
|
1249
|
+
/** A successor resolution the running process cannot adopt, such as a retained mapping in another directory; it waits for a restart. */
|
|
1250
|
+
var ResolutionRestartRequired = class extends Error {
|
|
1251
|
+
/** @param message Diagnostic naming the rejected changes. */
|
|
1252
|
+
constructor(message) {
|
|
1253
|
+
super(message);
|
|
1254
|
+
this.name = "ResolutionRestartRequired";
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1241
1257
|
/**
|
|
1242
1258
|
* Split a bare request into its package name without allocating path segments.
|
|
1243
1259
|
* @param request - module specifier to classify.
|
|
@@ -1251,6 +1267,15 @@ function barePackageName(request) {
|
|
|
1251
1267
|
const second = request.indexOf("/", first + 1);
|
|
1252
1268
|
return second < 0 ? request : request.slice(0, second);
|
|
1253
1269
|
}
|
|
1270
|
+
/** The JavaScript and native real paths of an existing directory; none for a directory that no longer exists. */
|
|
1271
|
+
function realSpellings(path) {
|
|
1272
|
+
try {
|
|
1273
|
+
const native = Reflect.get(realpathSync, "native");
|
|
1274
|
+
return [realpathSync(path), ...typeof native === "function" ? [String(Reflect.apply(native, realpathSync, [path]))] : []];
|
|
1275
|
+
} catch (_error) {
|
|
1276
|
+
return [];
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1254
1279
|
function canonicalPath(path) {
|
|
1255
1280
|
try {
|
|
1256
1281
|
return realpathSync(path);
|
|
@@ -1265,7 +1290,10 @@ function prefixes(path) {
|
|
|
1265
1290
|
}
|
|
1266
1291
|
function compileResolution(resolution) {
|
|
1267
1292
|
return {
|
|
1268
|
-
entries: new Map(resolution.entries.map((entry) => [entry.name,
|
|
1293
|
+
entries: new Map(resolution.entries.map((entry) => [entry.name, {
|
|
1294
|
+
...entry,
|
|
1295
|
+
realDir: canonicalPath(entry.packageDir)
|
|
1296
|
+
}])),
|
|
1269
1297
|
profilesDir: resolution.profilesDir,
|
|
1270
1298
|
profileDir: resolution.profileDir,
|
|
1271
1299
|
profilePaths: prefixes(resolution.profilesDir),
|
|
@@ -1428,11 +1456,65 @@ function sameResolution(left, right) {
|
|
|
1428
1456
|
if (left === right) return true;
|
|
1429
1457
|
return canonicalPath(left) === canonicalPath(right);
|
|
1430
1458
|
}
|
|
1459
|
+
/** A directory as given and in its JavaScript and native real paths, each with a trailing separator. */
|
|
1460
|
+
function spellings(dir) {
|
|
1461
|
+
const result = new Set([dir + sep]);
|
|
1462
|
+
for (const real of realSpellings(dir)) result.add(real + sep);
|
|
1463
|
+
return [...result];
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* The previous real directories of packages that moved, each mapped to the current one. Node keeps the real path of
|
|
1467
|
+
* every link it resolved through and never refreshes it, so a lookup through a link that now points elsewhere still
|
|
1468
|
+
* selects a file below the target Node saw first; {@link MovedDirectories.relocate} selects the same file below the
|
|
1469
|
+
* current target instead.
|
|
1470
|
+
*/
|
|
1471
|
+
var MovedDirectories = class {
|
|
1472
|
+
/** Previous directory to current directory, both with a trailing separator. */
|
|
1473
|
+
targets = /* @__PURE__ */ new Map();
|
|
1474
|
+
/**
|
|
1475
|
+
* Record that a package moved from one directory to another. Every spelling of the previous directory leads to the
|
|
1476
|
+
* current one. A directory that an earlier move led to the previous one now leads to the current one as well, since
|
|
1477
|
+
* Node still selects files below the earliest target.
|
|
1478
|
+
* @param from Previous real directory.
|
|
1479
|
+
* @param to Current real directory.
|
|
1480
|
+
*/
|
|
1481
|
+
record(from, to) {
|
|
1482
|
+
const previous = spellings(from);
|
|
1483
|
+
const current = to + sep;
|
|
1484
|
+
for (const [key, value] of this.targets) if (previous.includes(value)) this.targets.set(key, current);
|
|
1485
|
+
for (const key of previous) this.targets.set(key, current);
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Forget the previous directories that are current again, in any spelling, and records that lead to themselves.
|
|
1489
|
+
* @param current The real directories the published resolution maps.
|
|
1490
|
+
*/
|
|
1491
|
+
forget(current) {
|
|
1492
|
+
const active = /* @__PURE__ */ new Set();
|
|
1493
|
+
for (const dir of current) for (const spelling of spellings(dir)) active.add(spelling);
|
|
1494
|
+
for (const [key, value] of [...this.targets]) if (active.has(key) || key === value) this.targets.delete(key);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* The same file below the current directory when it exists, for a file below a previous directory that an importer
|
|
1498
|
+
* outside that directory selected. An importer inside the previous directory keeps its own package's files. The
|
|
1499
|
+
* innermost previous directory decides.
|
|
1500
|
+
* @param filename Selected file.
|
|
1501
|
+
* @param parent Importer path.
|
|
1502
|
+
* @returns The file to load.
|
|
1503
|
+
*/
|
|
1504
|
+
relocate(filename, parent) {
|
|
1505
|
+
let from = "";
|
|
1506
|
+
for (const dir of this.targets.keys()) if (filename.startsWith(dir) && dir.length > from.length) from = dir;
|
|
1507
|
+
if (from === "" || parent?.startsWith(from) === true) return filename;
|
|
1508
|
+
const relocated = this.targets.get(from) + filename.slice(from.length);
|
|
1509
|
+
return existsSync(relocated) ? relocated : filename;
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1431
1512
|
/** One mutable pointer to the immutable runtime resolution. */
|
|
1432
1513
|
var ResolutionRouter = class {
|
|
1433
1514
|
nodeModulePaths;
|
|
1434
1515
|
current;
|
|
1435
1516
|
linkedTargets;
|
|
1517
|
+
moved = new MovedDirectories();
|
|
1436
1518
|
constructor(resolution, nodeModulePaths) {
|
|
1437
1519
|
this.nodeModulePaths = nodeModulePaths;
|
|
1438
1520
|
this.current = compileResolution(resolution);
|
|
@@ -1441,21 +1523,49 @@ var ResolutionRouter = class {
|
|
|
1441
1523
|
replace(successor) {
|
|
1442
1524
|
const entries = new Map(successor.entries.map((entry) => [entry.name, entry]));
|
|
1443
1525
|
if (successor.profilesDir !== this.current.profilesDir || successor.profileDir !== this.current.profileDir) throw new Error("profile resolution: a successor cannot change its profile scope");
|
|
1526
|
+
const rejected = [];
|
|
1527
|
+
const moves = [];
|
|
1444
1528
|
for (const [name, current] of this.current.entries) {
|
|
1445
1529
|
const next = entries.get(name);
|
|
1446
1530
|
if (next === void 0 && current.scope === "profile") continue;
|
|
1447
|
-
if (next
|
|
1531
|
+
if (next !== void 0 && current.scope === "profile" && next.scope === "profile") {
|
|
1532
|
+
if (current.realDir === canonicalPath(next.packageDir)) continue;
|
|
1533
|
+
rejected.push(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || current.scope === "installation" && !sameResolution(current.declarer, next.declarer) || current.scope === "installation" && current.version !== next.version || current.scope !== next.scope) rejected.push(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
|
|
1448
1537
|
}
|
|
1449
1538
|
const localPackageNames = new Set(successor.localPackageNames);
|
|
1450
|
-
for (const name of localPackageNames) if (!this.current.localPackageNames.has(name) && this.current.entries.has(name))
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1539
|
+
for (const name of localPackageNames) if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) rejected.push(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`);
|
|
1540
|
+
const targets = new Map(this.linkedTargets);
|
|
1541
|
+
for (const [name, previous] of this.linkedTargets) {
|
|
1542
|
+
const root = successor.linkedRoots.find((candidate) => candidate.name === name);
|
|
1543
|
+
const entry = entries.get(name);
|
|
1544
|
+
const target = root?.realPath ?? (entry?.scope === "profile" ? canonicalPath(entry.packageDir) : void 0);
|
|
1545
|
+
if (target === void 0 || sameResolution(previous, target)) continue;
|
|
1546
|
+
moves.push([previous, target]);
|
|
1547
|
+
targets.set(name, target);
|
|
1454
1548
|
}
|
|
1549
|
+
if (rejected.length > 0) throw new ResolutionRestartRequired(rejected.join("; "));
|
|
1455
1550
|
const next = compileResolution(successor);
|
|
1456
|
-
for (const { name, realPath } of successor.linkedRoots)
|
|
1551
|
+
for (const { name, realPath } of successor.linkedRoots) targets.set(name, realPath);
|
|
1552
|
+
this.linkedTargets = targets;
|
|
1553
|
+
for (const [from, to] of moves) this.moved.record(from, to);
|
|
1554
|
+
const current = [];
|
|
1555
|
+
for (const entry of next.entries.values()) current.push(entry.realDir);
|
|
1556
|
+
for (const root of successor.linkedRoots) current.push(root.realPath);
|
|
1557
|
+
this.moved.forget(current);
|
|
1457
1558
|
this.current = next;
|
|
1458
1559
|
}
|
|
1560
|
+
/**
|
|
1561
|
+
* The file to load instead of one Node selected through a link that has moved since; see {@link MovedDirectories}.
|
|
1562
|
+
* @param filename Selected file.
|
|
1563
|
+
* @param parent Importer path.
|
|
1564
|
+
* @returns The file to load.
|
|
1565
|
+
*/
|
|
1566
|
+
relocate(filename, parent) {
|
|
1567
|
+
return this.moved.relocate(filename, parent);
|
|
1568
|
+
}
|
|
1459
1569
|
/** Whether a module path has an interception layer: it lies in the profiles tree, the active profile, or a linked root. */
|
|
1460
1570
|
hasInterceptionLayerForPath(path) {
|
|
1461
1571
|
return findInterceptionLayer(path, this.current) !== void 0;
|
|
@@ -1774,7 +1884,23 @@ function installRuntimeInterception(resolution) {
|
|
|
1774
1884
|
delegatedEsm = previous;
|
|
1775
1885
|
}
|
|
1776
1886
|
};
|
|
1777
|
-
|
|
1887
|
+
const relocate = (result, parent) => {
|
|
1888
|
+
if (!result.url.startsWith("file:")) return result;
|
|
1889
|
+
const url = new URL(result.url);
|
|
1890
|
+
const filename = fileURLToPath(url);
|
|
1891
|
+
const relocated = router.relocate(filename, parent?.startsWith("file:") === true ? fileURLToPath(parent) : void 0);
|
|
1892
|
+
if (relocated === filename) return result;
|
|
1893
|
+
url.pathname = pathToFileURL(relocated).pathname;
|
|
1894
|
+
return {
|
|
1895
|
+
...result,
|
|
1896
|
+
url: url.href
|
|
1897
|
+
};
|
|
1898
|
+
};
|
|
1899
|
+
return (request, parent, attributes) => {
|
|
1900
|
+
const result = adapted(request, parent, attributes);
|
|
1901
|
+
/* v8 ignore next -- Node 24+ resolves synchronously; the Node 22 matrix covers its Promise result */
|
|
1902
|
+
return result instanceof Promise ? result.then((resolved) => relocate(resolved, parent)) : relocate(result, parent);
|
|
1903
|
+
};
|
|
1778
1904
|
};
|
|
1779
1905
|
let restoreEsm;
|
|
1780
1906
|
/* v8 ignore else -- CI coverage runs Node 24 v2; the Node 22 matrix exercises the v1 adapter */
|
|
@@ -1898,7 +2024,11 @@ function installRuntimeInterception(resolution) {
|
|
|
1898
2024
|
delegatedCjs--;
|
|
1899
2025
|
}
|
|
1900
2026
|
};
|
|
1901
|
-
|
|
2027
|
+
const relocatedFilename = (request, parent, main, options) => {
|
|
2028
|
+
const filename = wrappedFilename(request, parent, main, options);
|
|
2029
|
+
return isAbsolute(filename) ? router.relocate(filename, parent?.filename ?? void 0) : filename;
|
|
2030
|
+
};
|
|
2031
|
+
cjs._resolveFilename = relocatedFilename;
|
|
1902
2032
|
return {
|
|
1903
2033
|
packageDir(specifier, parentURL) {
|
|
1904
2034
|
return router.packageDir(specifier, parentURL);
|
|
@@ -1908,7 +2038,7 @@ function installRuntimeInterception(resolution) {
|
|
|
1908
2038
|
},
|
|
1909
2039
|
dispose() {
|
|
1910
2040
|
/* v8 ignore else -- interceptions are disposed in reverse installation order */
|
|
1911
|
-
if (cjs._resolveFilename ===
|
|
2041
|
+
if (cjs._resolveFilename === relocatedFilename) cjs._resolveFilename = originalFilename;
|
|
1912
2042
|
restoreEsm();
|
|
1913
2043
|
}
|
|
1914
2044
|
};
|
|
@@ -3362,10 +3492,14 @@ var PluginPackages = class extends Service {
|
|
|
3362
3492
|
interception.dispose();
|
|
3363
3493
|
}, "profile package resolution");
|
|
3364
3494
|
}
|
|
3495
|
+
/** The published runtime resolution, or `undefined` when Node resolves packages alone. */
|
|
3496
|
+
get resolution() {
|
|
3497
|
+
return this.current;
|
|
3498
|
+
}
|
|
3365
3499
|
/**
|
|
3366
3500
|
* Publish a complete successor generation for this process and subsequently created Workers.
|
|
3367
3501
|
* Profile mappings and local package names may be removed after their plugins stop; linked roots may also be removed.
|
|
3368
|
-
* Retained profile mappings may change their declarer, but not their normalized directory
|
|
3502
|
+
* Retained profile mappings may change their declarer and version, but not their normalized directory or scope;
|
|
3369
3503
|
* installation mappings must remain unchanged. Publication does not unload modules or clear Node caches.
|
|
3370
3504
|
* @param successor - fully constructed generation accepted by {@link RuntimeInterception.replace}.
|
|
3371
3505
|
*/
|
|
@@ -3613,7 +3747,30 @@ function loadLayeredEnv(binName, cwd = process.cwd(), warn = (line) => void proc
|
|
|
3613
3747
|
}
|
|
3614
3748
|
const bootstrapIncludes = /* @__PURE__ */ new WeakMap();
|
|
3615
3749
|
const userPatchesSchema = entryListSchema;
|
|
3616
|
-
/**
|
|
3750
|
+
/**
|
|
3751
|
+
* Recompose the profile from its complete patch list, together with its runtime resolution. The rows the generation no
|
|
3752
|
+
* longer lists are removed first and their disposal finishes while their packages are still mapped; the latest
|
|
3753
|
+
* generation of an installed, recomputable resolution is published; the compatibility preflight reads the published
|
|
3754
|
+
* packages; the generation is applied to the root Include; entries that ran, and enabled entries whose import failed,
|
|
3755
|
+
* start afterwards when the update left them without a running fiber; and activation is judged. A failure before the generation applies,
|
|
3756
|
+
* including a publication the resolution rejects, which reports `package-moved`, puts the removed rows back at their
|
|
3757
|
+
* places, starting them again, and starts the entries that ran again. An entry waiting for a service is reported as a
|
|
3758
|
+
* warning unless it is required. Patches are copied with `structuredClone`, so they hold no functions.
|
|
3759
|
+
* @param ctx Booted root context.
|
|
3760
|
+
* @param patches Complete ordered patch list.
|
|
3761
|
+
* @param binName Diagnostic prefix.
|
|
3762
|
+
* @param options What the caller already did and requires.
|
|
3763
|
+
* @returns Diagnostics for unchanged pre-existing inactive entries, and the restart reason of a rejected publication.
|
|
3764
|
+
* @throws When the root Include is missing; the latest resolution cannot be read or published; preparation or the
|
|
3765
|
+
* Loader update rejects; or for a new or changed failure, an inactive required entry, a failed entry that ran, or a
|
|
3766
|
+
* healthy fiber whose disposal rejected.
|
|
3767
|
+
*/
|
|
3768
|
+
function recomposeProfile(ctx, patches, binName, options = {}) {
|
|
3769
|
+
return recompose(ctx, patches, binName, options, true);
|
|
3770
|
+
}
|
|
3771
|
+
/**
|
|
3772
|
+
* Apply a complete patch list to the root Include and judge activation, leaving the runtime resolution and entries
|
|
3773
|
+
* whose import failed as they are.
|
|
3617
3774
|
* @param ctx Booted root context.
|
|
3618
3775
|
* @param patches Complete ordered patch list.
|
|
3619
3776
|
* @param binName Diagnostic prefix.
|
|
@@ -3621,33 +3778,141 @@ const userPatchesSchema = entryListSchema;
|
|
|
3621
3778
|
* @returns Diagnostics for unchanged pre-existing inactive entries; new or changed failures reject.
|
|
3622
3779
|
*/
|
|
3623
3780
|
async function reconcileProfilePatches(ctx, patches, binName, requiredIds = []) {
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3781
|
+
return (await recompose(ctx, patches, binName, { requiredIds }, false)).warnings;
|
|
3782
|
+
}
|
|
3783
|
+
async function recompose(ctx, patches, binName, options, withResolution) {
|
|
3784
|
+
const { requiredIds = [], running = [] } = options;
|
|
3785
|
+
let applying = false;
|
|
3786
|
+
let stopped = [];
|
|
3787
|
+
try {
|
|
3788
|
+
const entry = bootstrapIncludes.get(ctx);
|
|
3789
|
+
if (entry === void 0) throw new Error(`${binName}: profile reload requires the root Include entry`);
|
|
3790
|
+
const previousFailures = (await inactiveEntries(ctx, false)).map((failure) => ({
|
|
3791
|
+
entry: failure.entry,
|
|
3792
|
+
fiber: failure.entry.fiber,
|
|
3793
|
+
options: JSON.stringify(failure.entry.options),
|
|
3794
|
+
diagnostic: failure.entry.fiber?.uid ? reloadDiagnostic(failure) : void 0
|
|
3795
|
+
}));
|
|
3796
|
+
const previousFibers = [...ctx.loader.entries()].flatMap((row) => row.fiber === void 0 ? [] : [{
|
|
3797
|
+
fiber: row.fiber,
|
|
3798
|
+
failed: row.fiber.state === FIBER_FAILED || row.fiber.state === FIBER_DISPOSED
|
|
3799
|
+
}]);
|
|
3800
|
+
const { patches: previous = [], ...includeConfig } = entry.options.config;
|
|
3801
|
+
const current = withResolution ? ctx.get("pluginPackages")?.resolution : void 0;
|
|
3802
|
+
const successor = current instanceof ProfileRuntimeResolution ? await current.computeLatestResolution() : void 0;
|
|
3803
|
+
if (successor !== void 0) {
|
|
3804
|
+
const listed = rowIds(structuredClone(patches));
|
|
3805
|
+
const composed = rowIds(structuredClone(previous));
|
|
3806
|
+
const tree = entry.subgroup?.tree;
|
|
3807
|
+
const dropped = [...ctx.loader.entries()].filter((row) => row.parent.tree === tree && composed.has(row.options.id) && !listed.has(row.options.id));
|
|
3808
|
+
const ids = new Set(dropped.map((row) => row.options.id));
|
|
3809
|
+
const topmost = dropped.filter((row) => !ids.has(row.parent.ctx.fiber.entry?.options.id));
|
|
3810
|
+
stopped = topmost.map((row) => ({
|
|
3811
|
+
group: row.parent,
|
|
3812
|
+
options: structuredClone(row.options),
|
|
3813
|
+
index: row.parent.data.indexOf(row.options)
|
|
3814
|
+
}));
|
|
3815
|
+
if (topmost.length > 0) {
|
|
3816
|
+
const fibers = dropped.flatMap((row) => row.fiber === void 0 ? [] : [row.fiber]);
|
|
3817
|
+
for (const row of topmost) {
|
|
3818
|
+
row.options.disabled = true;
|
|
3819
|
+
row.parent.remove(row.options.id);
|
|
3820
|
+
}
|
|
3821
|
+
await Promise.allSettled(fibers.map((fiber) => fiber.await()));
|
|
3822
|
+
await ctx.loader.await();
|
|
3823
|
+
}
|
|
3824
|
+
try {
|
|
3825
|
+
publish(ctx, successor);
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
if (!(error instanceof ResolutionRestartRequired)) throw error;
|
|
3828
|
+
return {
|
|
3829
|
+
warnings: [],
|
|
3830
|
+
restartReason: "package-moved"
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
const parentURL = new URL(".", new URL(includeConfig.path, entry.parent.tree.ctx.baseUrl)).href;
|
|
3835
|
+
const prepared = prepareProfilePatches(ctx, patches, parentURL, binName);
|
|
3836
|
+
applying = true;
|
|
3837
|
+
const retried = [...new Set([...running, ...withResolution ? [...ctx.loader.entries()].filter(importFailed) : []])];
|
|
3838
|
+
try {
|
|
3839
|
+
await entry.update({ config: {
|
|
3840
|
+
...includeConfig,
|
|
3841
|
+
patches: prepared
|
|
3842
|
+
} });
|
|
3843
|
+
} finally {
|
|
3844
|
+
await resume(ctx, retried);
|
|
3845
|
+
}
|
|
3846
|
+
const results = await Promise.allSettled(previousFibers.map(({ fiber }) => fiber.await()));
|
|
3847
|
+
await ctx.loader.await();
|
|
3848
|
+
const failures = await inactiveEntries(ctx);
|
|
3849
|
+
const introduced = failures.filter((failure) => {
|
|
3850
|
+
if (requiredIds.includes(failure.entry.options.id)) return true;
|
|
3851
|
+
if (failure.outcome.kind === "pending") return false;
|
|
3852
|
+
if (running.includes(failure.entry)) return true;
|
|
3853
|
+
return !previousFailures.some((previous) => sameFailure(previous, failure));
|
|
3854
|
+
});
|
|
3855
|
+
if (introduced.length > 0) throw new Error(activationDiagnostic(binName, introduced, false).trimEnd());
|
|
3856
|
+
for (const [index, result] of results.entries()) if (result.status === "rejected" && !previousFibers[index]?.failed) throw result.reason;
|
|
3857
|
+
ctx.emit("app-boot/config-reload");
|
|
3858
|
+
return { warnings: failures.map(reloadDiagnostic) };
|
|
3859
|
+
} finally {
|
|
3860
|
+
if (!applying && (stopped.length > 0 || running.length > 0)) try {
|
|
3861
|
+
for (const { group, options, index } of stopped.toSorted((left, right) => left.index - right.index)) {
|
|
3862
|
+
group.data.splice(index, 0, options);
|
|
3863
|
+
await group.create(options);
|
|
3864
|
+
}
|
|
3865
|
+
await resume(ctx, running);
|
|
3866
|
+
const ran = new Set(running.map((row) => row.options.id));
|
|
3867
|
+
for (const failure of await inactiveEntries(ctx)) if (ran.has(failure.entry.options.id)) ctx.logger.warn(`${binName}: ${reloadDiagnostic(failure)}`);
|
|
3868
|
+
} catch (error) {
|
|
3869
|
+
ctx.logger.warn(error);
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
/** Whether an inactive entry is the failure recorded before: the same entry, fiber and options, and a matching diagnostic. */
|
|
3874
|
+
function sameFailure(previous, failure) {
|
|
3875
|
+
return previous.entry === failure.entry && previous.fiber === failure.entry.fiber && previous.options === JSON.stringify(failure.entry.options) && (previous.diagnostic === void 0 || previous.diagnostic === reloadDiagnostic(failure));
|
|
3876
|
+
}
|
|
3877
|
+
/** The ids of the rows a patch list inserts, including the rows of groups. */
|
|
3878
|
+
function rowIds(patches) {
|
|
3879
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3880
|
+
const visit = (rows) => {
|
|
3881
|
+
for (const row of rows) {
|
|
3882
|
+
ids.add(row.id);
|
|
3883
|
+
if ((row.group === true || row.name === "cordis:group") && Array.isArray(row.config)) visit(row.config);
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
visit(applyEntryPatches([], patches, () => {}));
|
|
3887
|
+
return ids;
|
|
3888
|
+
}
|
|
3889
|
+
/** Publish a generation unless it maps exactly what is published, which would only drop the package caches. */
|
|
3890
|
+
function publish(ctx, next) {
|
|
3891
|
+
const table = ({ localPackageNames, entries, linkedRoots }) => JSON.stringify([
|
|
3892
|
+
localPackageNames,
|
|
3893
|
+
entries,
|
|
3894
|
+
linkedRoots
|
|
3895
|
+
]);
|
|
3896
|
+
const packages = ctx.pluginPackages;
|
|
3897
|
+
if (table(packages.resolution) !== table(next)) packages.replace(next);
|
|
3898
|
+
}
|
|
3899
|
+
/** Whether an enabled entry below a running parent has no fiber or keeps the disposed fiber of a failed import. */
|
|
3900
|
+
function importFailed(entry) {
|
|
3901
|
+
return enabledEntry(entry) && entry.parent.ctx.fiber.uid !== null && (entry.fiber === void 0 || entry.fiber.state === FIBER_DISPOSED);
|
|
3902
|
+
}
|
|
3903
|
+
/** Whether an entry is enabled now; a throwing `disabled` expression counts as disabled. */
|
|
3904
|
+
function enabledEntry(entry) {
|
|
3905
|
+
try {
|
|
3906
|
+
return !entry.disabled;
|
|
3907
|
+
} catch (_error) {
|
|
3908
|
+
return false;
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
/** Start invalidated entries the Loader still holds without a running fiber, then wait until it settles. */
|
|
3912
|
+
async function resume(ctx, invalidated) {
|
|
3913
|
+
const entries = new Set(ctx.loader.entries());
|
|
3914
|
+
for (const row of invalidated) if (entries.has(row) && !row.fiber?.uid) await row.update({}, false, true);
|
|
3644
3915
|
await ctx.loader.await();
|
|
3645
|
-
const failures = await inactiveEntries(ctx);
|
|
3646
|
-
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)));
|
|
3647
|
-
if (introduced.length > 0) throw new Error(activationDiagnostic(binName, introduced).trimEnd());
|
|
3648
|
-
for (const [index, result] of results.entries()) if (result.status === "rejected" && !previousFibers[index]?.failed) throw result.reason;
|
|
3649
|
-
ctx.emit("app-boot/config-reload");
|
|
3650
|
-
return failures.map(inactiveDiagnostic);
|
|
3651
3916
|
}
|
|
3652
3917
|
/**
|
|
3653
3918
|
* Load an optional patch-list file: a top-level YAML array of loader patch
|
|
@@ -3999,7 +4264,7 @@ const requiredStartupEntryIds = new Set([
|
|
|
3999
4264
|
"sdk-jsonrpc-server"
|
|
4000
4265
|
]);
|
|
4001
4266
|
/** Render plugin stacks, nested causes, and aggregate member failures once per error. */
|
|
4002
|
-
function formatActivationError(error) {
|
|
4267
|
+
function formatActivationError(error, stacks = true) {
|
|
4003
4268
|
const details = [];
|
|
4004
4269
|
const seen = /* @__PURE__ */ new Set();
|
|
4005
4270
|
function visit(value) {
|
|
@@ -4009,7 +4274,7 @@ function formatActivationError(error) {
|
|
|
4009
4274
|
}
|
|
4010
4275
|
if (seen.has(value)) return;
|
|
4011
4276
|
seen.add(value);
|
|
4012
|
-
details.push(value.stack ?? value.message);
|
|
4277
|
+
details.push(stacks ? value.stack ?? value.message : String(value));
|
|
4013
4278
|
if (value.cause !== void 0) visit(value.cause);
|
|
4014
4279
|
if (value instanceof AggregateError) value.errors.forEach(visit);
|
|
4015
4280
|
}
|
|
@@ -4035,12 +4300,23 @@ var StartupError = class extends Error {
|
|
|
4035
4300
|
});
|
|
4036
4301
|
}
|
|
4037
4302
|
};
|
|
4303
|
+
/** The error importing an entry's module raises now, which Node returns from its cache for a module that failed before. */
|
|
4304
|
+
async function importFailure(entry) {
|
|
4305
|
+
try {
|
|
4306
|
+
await entry.parent.tree.import(entry.options.name);
|
|
4307
|
+
return;
|
|
4308
|
+
} catch (error) {
|
|
4309
|
+
return error;
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4038
4312
|
/**
|
|
4039
4313
|
* Collect Loader activation failures and disabled-expression errors. Failed
|
|
4040
4314
|
* fibers are awaited to recover their recorded rejection reason and coalesce
|
|
4041
4315
|
* duplicate Loader notifications through the next process rejection checkpoint.
|
|
4316
|
+
* An entry whose import failed reports the error importing it raises now; without
|
|
4317
|
+
* `importNow` nothing is imported and the entry reports a failed import without the import error.
|
|
4042
4318
|
*/
|
|
4043
|
-
async function inactiveEntries(ctx) {
|
|
4319
|
+
async function inactiveEntries(ctx, importNow = true) {
|
|
4044
4320
|
const failures = [];
|
|
4045
4321
|
const rejectionReasons = [];
|
|
4046
4322
|
for (const entry of ctx.loader.entries()) {
|
|
@@ -4058,12 +4334,17 @@ async function inactiveEntries(ctx) {
|
|
|
4058
4334
|
continue;
|
|
4059
4335
|
}
|
|
4060
4336
|
const fiber = entry.fiber;
|
|
4061
|
-
if (fiber === void 0) {
|
|
4337
|
+
if (fiber === void 0 || fiber.state === FIBER_DISPOSED) {
|
|
4338
|
+
const error = importNow ? await importFailure(entry) : void 0;
|
|
4062
4339
|
failures.push({
|
|
4063
4340
|
entry,
|
|
4064
|
-
outcome: {
|
|
4341
|
+
outcome: error === void 0 ? {
|
|
4342
|
+
kind: "failed",
|
|
4343
|
+
error: fiber === void 0 ? "failed to import" : `fiber state ${String(fiber.state)}`
|
|
4344
|
+
} : {
|
|
4065
4345
|
kind: "failed",
|
|
4066
|
-
error
|
|
4346
|
+
error,
|
|
4347
|
+
phase: "failed to import"
|
|
4067
4348
|
}
|
|
4068
4349
|
});
|
|
4069
4350
|
continue;
|
|
@@ -4106,17 +4387,21 @@ async function inactiveEntries(ctx) {
|
|
|
4106
4387
|
return failures;
|
|
4107
4388
|
}
|
|
4108
4389
|
/** Render one failed plugin's original error and activation phase. */
|
|
4109
|
-
function failureDetail(outcome) {
|
|
4110
|
-
return `${outcome.phase === void 0 ? "" : `${outcome.phase}: `}${formatActivationError(outcome.error)}`;
|
|
4390
|
+
function failureDetail(outcome, stacks = true) {
|
|
4391
|
+
return `${outcome.phase === void 0 ? "" : `${outcome.phase}: `}${formatActivationError(outcome.error, stacks)}`;
|
|
4111
4392
|
}
|
|
4112
4393
|
/** Render optional-only warnings without changing startup policy. */
|
|
4113
|
-
function activationDiagnostic(binName, failures) {
|
|
4394
|
+
function activationDiagnostic(binName, failures, stacks = true) {
|
|
4114
4395
|
const noun = failures.length === 1 ? "entry" : "entries";
|
|
4115
|
-
return `${binName}: warning: ${String(failures.length)} ${noun} did not activate\n${failures.map(inactiveDiagnostic).join("\n")}\n`;
|
|
4396
|
+
return `${binName}: warning: ${String(failures.length)} ${noun} did not activate\n${failures.map((failure) => inactiveDiagnostic(failure, stacks)).join("\n")}\n`;
|
|
4397
|
+
}
|
|
4398
|
+
/** Per-entry reload text, which people read through the Web UI and agents, so it carries error messages without stack frames. */
|
|
4399
|
+
function reloadDiagnostic(failure) {
|
|
4400
|
+
return inactiveDiagnostic(failure, false);
|
|
4116
4401
|
}
|
|
4117
4402
|
/** Stable per-entry text for reload comparisons and optional warnings. */
|
|
4118
|
-
function inactiveDiagnostic({ entry, outcome }) {
|
|
4119
|
-
const detail = outcome.kind === "failed" ? failureDetail(outcome) : `pending (waiting for ${outcome.missing.length === 1 ? "service" : "services"}: ${outcome.missing.join(", ") || "unknown"})`;
|
|
4403
|
+
function inactiveDiagnostic({ entry, outcome }, stacks = true) {
|
|
4404
|
+
const detail = outcome.kind === "failed" ? failureDetail(outcome, stacks) : `pending (waiting for ${outcome.missing.length === 1 ? "service" : "services"}: ${outcome.missing.join(", ") || "unknown"})`;
|
|
4120
4405
|
return `${entry.options.id} (${entry.options.name}): ${detail}`;
|
|
4121
4406
|
}
|
|
4122
4407
|
/** Group startup failures and pending services, marking every required entry. */
|
|
@@ -4257,37 +4542,11 @@ async function boot(binName, absoluteConfigPath, patches, prepare, bareModuleBas
|
|
|
4257
4542
|
seen.add(deepest);
|
|
4258
4543
|
deepest = deepest.cause;
|
|
4259
4544
|
}
|
|
4260
|
-
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}` : "";
|
|
4545
|
+
const stack = deepest instanceof AggregateError ? `\n${deepest.stack ?? deepest.message}\n${deepest.errors.map((error) => formatActivationError(error)).join("\n")}` : deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : "";
|
|
4261
4546
|
throw new Error(`${binName}: ${stage}: ${detail}${stack}`, { cause });
|
|
4262
4547
|
} finally {
|
|
4263
4548
|
await diagnostics.fiber.dispose();
|
|
4264
4549
|
}
|
|
4265
4550
|
}
|
|
4266
|
-
/** Prompt-section name for the harness-source location line an app bin adds after boot. */
|
|
4267
|
-
const HARNESS_SOURCE_SECTION = "harness:source";
|
|
4268
|
-
/**
|
|
4269
|
-
* Add a global prompt section naming the on-disk harness source checkout while
|
|
4270
|
-
* explicitly distinguishing it from the task workspace and current working
|
|
4271
|
-
* directory. The self-referential `dsh-tool-cordis` toolset reads and edits this
|
|
4272
|
-
* checkout. Call once on the settled boot context ({@link boot}); the section
|
|
4273
|
-
* uses the shared first-party placement after reusable instructions
|
|
4274
|
-
* and before the Web surface and persona suffix. A booted tree with no
|
|
4275
|
-
* `systemPrompt` service has no prompt to augment, so this is then a no-op
|
|
4276
|
-
* that returns `undefined`. The section is
|
|
4277
|
-
* registered against the `systemPrompt` service's fiber, so a dev HMR reload of
|
|
4278
|
-
* that plugin drops it until the next boot.
|
|
4279
|
-
* @param ctx - the settled boot context whose global system prompt to augment.
|
|
4280
|
-
* @param sourceRoot - the absolute path to the harness checkout root.
|
|
4281
|
-
* @returns the section disposer, or `undefined` when no `systemPrompt` service is mounted.
|
|
4282
|
-
*/
|
|
4283
|
-
function addHarnessSourceSection(ctx, sourceRoot) {
|
|
4284
|
-
const systemPrompt = ctx.get("systemPrompt");
|
|
4285
|
-
if (systemPrompt === void 0) return void 0;
|
|
4286
|
-
return systemPrompt.section({
|
|
4287
|
-
name: HARNESS_SOURCE_SECTION,
|
|
4288
|
-
order: systemPrompt.getSectionOrder("HARNESS_SOURCE"),
|
|
4289
|
-
text: `The DeepSeek Harness implementation checkout is at ${sourceRoot}. The checkout location and current working directory are separate values and may differ; never infer the working directory from this path. Use pwd to determine the current working directory. Use this checkout only to inspect or extend DSH itself.`
|
|
4290
|
-
});
|
|
4291
|
-
}
|
|
4292
4551
|
//#endregion
|
|
4293
|
-
export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS,
|
|
4552
|
+
export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS, LOADER_EXPRESSION_SCHEMA, OFFICIAL_ON_DEMAND_CATALOG, ON_DEMAND_BUNDLES, OPTIONAL_BUNDLES, PROFILES_DIR, PROFILE_COMPATIBILITY_FILENAME, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PluginPackages, ProfileRuntimeResolution, ResolutionRestartRequired, StartupError, auditStartupEntries, boot, bundlePatchFiles, bundlePatchPaths, composeEntries, createConfigProjector, createRuntimeResolution, evaluatePluginCompatibility, generateConfigSchema, getDshRuntimeVersion, initProfile, installFailLoud, isNativeConfigSchema, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, pluginCompatibilityWarning, prepareProfileEntries, prepareProfilePatches, readBundlePatches, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfilePlugins, readProfileVersionExemptions, recomposeProfile, reconcileProfilePatches, reconcileProfilePlugins, removeLinkProjections, renderConfigDump, reportSkippedBundles, resolveBundleDir, resolveConfigPath, resolvePluginResource, resolveProfileDir, resolveTelemetryPatch, sanitizeProfile, setProfileVersionExemption, writeProfileBundles, writeProfileManifest };
|