@dsh-workbench/core 0.1.8 → 0.1.10

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.
Files changed (37) hide show
  1. package/client.js +983 -391
  2. package/dist/client/client.body.js +983 -391
  3. package/dist/client/index.js +984 -392
  4. package/dist/host/index.js +366 -106
  5. package/dist/types/app/client.d.ts +140 -12
  6. package/dist/types/app/layouts/env-gate/__tests__/gate-deadline.test.d.ts +1 -0
  7. package/dist/types/app/layouts/env-gate/gate-deadline.d.ts +4 -0
  8. package/dist/types/app/layouts/rightbar/MarketStates.d.ts +7 -0
  9. package/dist/types/app/layouts/rightbar/MarketView.d.ts +2 -1
  10. package/dist/types/app/layouts/rightbar/SharedSection.d.ts +11 -0
  11. package/dist/types/app/layouts/rightbar/__tests__/market-change.test.d.ts +1 -0
  12. package/dist/types/app/layouts/rightbar/__tests__/market-live.test.d.ts +1 -0
  13. package/dist/types/app/layouts/rightbar/__tests__/market-settle.test.d.ts +1 -0
  14. package/dist/types/app/layouts/rightbar/board-plan.d.ts +2 -0
  15. package/dist/types/app/layouts/rightbar/market-change.d.ts +4 -0
  16. package/dist/types/app/layouts/rightbar/market-rows.d.ts +3 -1
  17. package/dist/types/app/layouts/rightbar/market-settle.d.ts +2 -0
  18. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +10 -0
  19. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +2 -2
  20. package/dist/types/app/layouts/sidebar/Sidebar.d.ts +1 -1
  21. package/dist/types/app/layouts/sidebar/WorkspaceRenameDialog.d.ts +8 -0
  22. package/dist/types/app/layouts/sidebar/WorkspaceRowAction.d.ts +7 -0
  23. package/dist/types/app/layouts/sidebar/WorkspaceRowLabel.d.ts +8 -0
  24. package/dist/types/app/layouts/sidebar/WorkspaceSearchField.d.ts +9 -0
  25. package/dist/types/app/layouts/sidebar/__tests__/workspace-name.test.d.ts +1 -0
  26. package/dist/types/app/layouts/sidebar/workspace-items.d.ts +11 -0
  27. package/dist/types/app/layouts/sidebar/workspace-name.d.ts +16 -0
  28. package/dist/types/app/units/__tests__/registry.test.d.ts +1 -0
  29. package/dist/types/app/units/fixture-unit-mock.d.ts +3 -1
  30. package/dist/types/modules/panels/__tests__/market-registries.test.d.ts +1 -0
  31. package/dist/types/modules/panels/model/market.d.ts +2 -0
  32. package/dist/types/shared/api/host/__tests__/profile-registries.test.d.ts +1 -0
  33. package/dist/types/shared/api/host/mount-units.d.ts +13 -6
  34. package/dist/types/shared/api/host/profile-registries.d.ts +21 -0
  35. package/dist/types/shared/hooks/mock-mode.d.ts +1 -0
  36. package/docs/OPERATIONS.md +2 -0
  37. package/package.json +2 -2
@@ -1380,7 +1380,7 @@ function readBundleManager(ctx) {
1380
1380
  }
1381
1381
  //#endregion
1382
1382
  //#region src/shared/api/host/installed-module.ts
1383
- var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
1383
+ var PACKAGE_NAME$1 = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
1384
1384
  function asRecord$1(value) {
1385
1385
  return typeof value === "object" && value !== null ? value : void 0;
1386
1386
  }
@@ -1388,7 +1388,7 @@ function asText$1(value) {
1388
1388
  return typeof value === "string" ? value : "";
1389
1389
  }
1390
1390
  function isPackageName(value) {
1391
- return value.length <= 214 && PACKAGE_NAME.test(value);
1391
+ return value.length <= 214 && PACKAGE_NAME$1.test(value);
1392
1392
  }
1393
1393
  function profileDirOf(ctx) {
1394
1394
  const profile = ctx.get("profileContext");
@@ -1477,6 +1477,118 @@ function readInstalledModule(profileDir, pkg) {
1477
1477
  };
1478
1478
  }
1479
1479
  //#endregion
1480
+ //#region src/shared/api/host/profile-registries.ts
1481
+ var REGISTRY_MARKER = "# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)";
1482
+ function scopeOf(pkg) {
1483
+ const slash = pkg.indexOf("/");
1484
+ return pkg.startsWith("@") && slash > 1 ? pkg.slice(0, slash) : null;
1485
+ }
1486
+ function normalizeRegistry(url) {
1487
+ const trimmed = url.trim();
1488
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
1489
+ }
1490
+ function sameRegistry(a, b) {
1491
+ if (a === null || a === void 0 || b === null || b === void 0) return false;
1492
+ if (a.trim() === "" || b.trim() === "") return false;
1493
+ return normalizeRegistry(a) === normalizeRegistry(b);
1494
+ }
1495
+ function registryRootOf(name, tarball) {
1496
+ const at = tarball.indexOf(`/${name}/-/`);
1497
+ if (at > 0) return normalizeRegistry(tarball.slice(0, at));
1498
+ try {
1499
+ return normalizeRegistry(new URL(tarball).origin);
1500
+ } catch {
1501
+ return null;
1502
+ }
1503
+ }
1504
+ var PACKAGE_KEY = /^ {2}'?(\S+)'?:\s*$/;
1505
+ var PACKAGE_NAME = /^(?:@[^/\s]+\/)?[^/\s@]+$/;
1506
+ var TARBALL = /tarball:\s*(\S+?)\}?\s*$/;
1507
+ function sourcesFromLockfile(text) {
1508
+ const found = /* @__PURE__ */ new Map();
1509
+ let name = null;
1510
+ for (const line of text.split("\n")) {
1511
+ const key = PACKAGE_KEY.exec(line);
1512
+ if (key !== null) {
1513
+ const raw = key[1] ?? "";
1514
+ const at = raw.lastIndexOf("@");
1515
+ const candidate = at > 0 ? raw.slice(0, at) : "";
1516
+ name = PACKAGE_NAME.test(candidate) ? candidate : null;
1517
+ continue;
1518
+ }
1519
+ if (name === null) continue;
1520
+ const tarball = TARBALL.exec(line);
1521
+ if (tarball === null) continue;
1522
+ const scope = scopeOf(name);
1523
+ const root = registryRootOf(name, tarball[1] ?? "");
1524
+ name = null;
1525
+ if (scope === null || root === null) continue;
1526
+ const seen = found.get(scope);
1527
+ if (seen === void 0) found.set(scope, root);
1528
+ else if (!sameRegistry(seen, root)) found.set(scope, null);
1529
+ }
1530
+ return [...found.entries()].filter((entry) => entry[1] !== null).map(([scope, registry]) => ({
1531
+ scope,
1532
+ registry
1533
+ }));
1534
+ }
1535
+ function mergeRegistryConfig(text, entries) {
1536
+ const lines = text.split("\n");
1537
+ const present = (key) => lines.some((line) => line.trim().startsWith(`${key}=`));
1538
+ const appended = [];
1539
+ const added = [];
1540
+ for (const entry of entries) {
1541
+ const key = `${entry.scope}:registry`;
1542
+ if (entry.registry.trim() === "" || present(key)) continue;
1543
+ lines.push(`${key}=${normalizeRegistry(entry.registry)}`);
1544
+ appended.push(`${key}=${normalizeRegistry(entry.registry)}`);
1545
+ added.push(key);
1546
+ }
1547
+ if (appended.length === 0) return {
1548
+ text,
1549
+ added: []
1550
+ };
1551
+ const base = text === "" ? "" : text.endsWith("\n") ? text : `${text}\n`;
1552
+ return {
1553
+ text: `${base}${base.includes("# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)") ? "" : `${REGISTRY_MARKER}\n`}${appended.join("\n")}\n`,
1554
+ added
1555
+ };
1556
+ }
1557
+ function registryFailure(change) {
1558
+ if (change.application !== "failed") return false;
1559
+ if (change.packageResult?.kind === "not-found") return true;
1560
+ const output = change.packageResult?.output ?? change.errorDetail ?? "";
1561
+ return /ERR_PNPM_[A-Z_]*FETCH/.test(output);
1562
+ }
1563
+ function createRegistryNotebook(dir) {
1564
+ return {
1565
+ sources() {
1566
+ if (dir === null) return [];
1567
+ try {
1568
+ return sourcesFromLockfile(fs.readFileSync(path.join(dir, "pnpm-lock.yaml"), "utf8"));
1569
+ } catch {
1570
+ return [];
1571
+ }
1572
+ },
1573
+ remember(entries) {
1574
+ if (dir === null || entries.length === 0) return [];
1575
+ const file = path.join(dir, ".npmrc");
1576
+ let before;
1577
+ try {
1578
+ before = fs.readFileSync(file, "utf8");
1579
+ } catch {
1580
+ before = "";
1581
+ }
1582
+ const merged = mergeRegistryConfig(before, entries);
1583
+ if (merged.added.length === 0) return [];
1584
+ const temp = `${file}.dsh-workbench.tmp`;
1585
+ fs.writeFileSync(temp, merged.text, { mode: 384 });
1586
+ fs.renameSync(temp, file);
1587
+ return merged.added;
1588
+ }
1589
+ };
1590
+ }
1591
+ //#endregion
1480
1592
  //#region src/shared/api/host/release.ts
1481
1593
  function parseLedgerVersion(stdout, name) {
1482
1594
  const parsed = parseJsonLoose(stdout);
@@ -1703,7 +1815,7 @@ function bundleNameOf(change, spec) {
1703
1815
  return at > 0 ? spec.slice(0, at) : spec;
1704
1816
  }
1705
1817
  function createMarketService(options) {
1706
- const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules } = options;
1818
+ const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, registries } = options;
1707
1819
  const command = options.command ?? "opencli";
1708
1820
  const catalog = () => (manifestsNow?.() ?? manifests).filter((manifest) => (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0);
1709
1821
  function requireEntry(unit) {
@@ -1850,6 +1962,38 @@ function createMarketService(options) {
1850
1962
  };
1851
1963
  }
1852
1964
  }
1965
+ async function rememberInstalledSource(manager, pkg, registry) {
1966
+ if (registries === void 0 || registry === void 0) return;
1967
+ const scope = scopeOf(pkg);
1968
+ if (scope === null) return;
1969
+ const known = await manager.registries().catch(() => null);
1970
+ if (known !== null && sameRegistry(known.resolved, registry)) return;
1971
+ try {
1972
+ const added = registries.remember([{
1973
+ scope,
1974
+ registry
1975
+ }]);
1976
+ if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
1977
+ } catch (error) {
1978
+ logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
1979
+ }
1980
+ }
1981
+ async function retryAfterRegistryFix(manager, pkg, change) {
1982
+ if (registries === void 0 || !registryFailure(change)) return change;
1983
+ const known = await manager.registries().catch(() => null);
1984
+ const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
1985
+ if (missing.length === 0) return change;
1986
+ let added;
1987
+ try {
1988
+ added = registries.remember(missing);
1989
+ } catch (error) {
1990
+ logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
1991
+ return change;
1992
+ }
1993
+ if (added.length === 0) return change;
1994
+ logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
1995
+ return await manager.remove(pkg).catch(() => null) ?? change;
1996
+ }
1853
1997
  const service = {
1854
1998
  async list() {
1855
1999
  return snapshot();
@@ -1955,7 +2099,8 @@ function createMarketService(options) {
1955
2099
  };
1956
2100
  },
1957
2101
  async installBundle(spec, options) {
1958
- const change = await requireBundles().install({
2102
+ const manager = requireBundles();
2103
+ const change = await manager.install({
1959
2104
  spec,
1960
2105
  ...options?.registry === void 0 ? {} : { registry: options.registry },
1961
2106
  ...options?.requestId === void 0 ? {} : { requestId: options.requestId },
@@ -1967,6 +2112,7 @@ function createMarketService(options) {
1967
2112
  kind: "unknown",
1968
2113
  pkg
1969
2114
  };
2115
+ await rememberInstalledSource(manager, pkg, options?.registry);
1970
2116
  return {
1971
2117
  change,
1972
2118
  module,
@@ -1982,8 +2128,9 @@ function createMarketService(options) {
1982
2128
  const unit = module.kind === "module" ? module.unit : void 0;
1983
2129
  const manifest = unit === void 0 ? void 0 : catalog().find((item) => item.id === unit);
1984
2130
  const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
1985
- const change = await manager.remove(pkg);
1986
- if (change === null) throw new Error("插件管理器回的卸载结果看不懂。");
2131
+ const first = await manager.remove(pkg);
2132
+ if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
2133
+ const change = await retryAfterRegistryFix(manager, pkg, first);
1987
2134
  if (change.application !== "applied" || unit === void 0) return {
1988
2135
  change,
1989
2136
  keptFor: []
@@ -2682,6 +2829,7 @@ var HOST_UNITS = [{
2682
2829
  bundles: readBundleManager(ctx),
2683
2830
  installed: (pkg) => readInstalledModule(profileDir, pkg),
2684
2831
  modules: () => profileModules(profileDir),
2832
+ registries: createRegistryNotebook(profileDir),
2685
2833
  onUninstalled: (unit) => {
2686
2834
  panels.release(unit);
2687
2835
  },
@@ -3640,16 +3788,43 @@ function withRunsRoute(result, unit) {
3640
3788
  }
3641
3789
  };
3642
3790
  }
3643
- function collectRoutes(mounted) {
3791
+ function failureReasonOf(error) {
3792
+ if (error instanceof Error) return error.message;
3793
+ return typeof error === "string" ? error : String(error);
3794
+ }
3795
+ function fail(ledger, item, reason) {
3796
+ ledger.failed.add(item);
3797
+ ledger.failures.push({
3798
+ id: item.face.manifest.id,
3799
+ reason
3800
+ });
3801
+ }
3802
+ function collectRoutes(mounted, ledger) {
3644
3803
  const routes = {};
3645
3804
  const owner = /* @__PURE__ */ new Map();
3646
3805
  const routesOfUnit = /* @__PURE__ */ new Map();
3647
3806
  for (const item of mounted) {
3807
+ if (ledger.failed.has(item)) continue;
3648
3808
  const unit = item.face.manifest.id;
3809
+ const accepted = [];
3810
+ let problem;
3649
3811
  for (const [key, handler] of Object.entries(item.result.routes)) {
3650
- if (key !== unit && !key.startsWith(`${unit}/`)) throw new Error(`路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`);
3812
+ if (key !== unit && !key.startsWith(`${unit}/`)) {
3813
+ problem = `路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`;
3814
+ break;
3815
+ }
3651
3816
  const previous = owner.get(key);
3652
- if (previous !== void 0) throw new Error(`路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`);
3817
+ if (previous !== void 0) {
3818
+ problem = `路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`;
3819
+ break;
3820
+ }
3821
+ accepted.push([key, handler]);
3822
+ }
3823
+ if (problem !== void 0) {
3824
+ fail(ledger, item, problem);
3825
+ continue;
3826
+ }
3827
+ for (const [key, handler] of accepted) {
3653
3828
  owner.set(key, unit);
3654
3829
  routes[key] = handler;
3655
3830
  const own = routesOfUnit.get(unit);
@@ -3663,45 +3838,64 @@ function collectRoutes(mounted) {
3663
3838
  routesOfUnit
3664
3839
  };
3665
3840
  }
3666
- function pickGate(mounted) {
3667
- const providers = mounted.filter((item) => item.result.gate !== void 0);
3668
- if (providers.length > 1) throw new Error(`启停闸门只能有一个提供者,现在是:${providers.map((item) => item.face.manifest.id).join("、")}`);
3669
- return providers[0]?.result.gate;
3670
- }
3671
- function pickProvision(mounted) {
3672
- const providers = mounted.filter((item) => item.result.provision !== void 0);
3673
- if (providers.length > 1) throw new Error(`机器供给只能有一个提供者,现在是:${providers.map((item) => item.face.manifest.id).join("、")}`);
3674
- return providers[0]?.result.provision;
3675
- }
3676
- function collectPrefs(mounted) {
3841
+ function collectNamed(mounted, ledger, pickMany, label) {
3677
3842
  const owner = /* @__PURE__ */ new Map();
3678
- const prefs = [];
3843
+ const out = [];
3679
3844
  for (const item of mounted) {
3680
- const entry = item.result.prefs;
3681
- if (entry === void 0) continue;
3682
- const previous = owner.get(entry.resource);
3683
- if (previous !== void 0) throw new Error(`偏好资源名冲突:${entry.resource} 由套件 ${previous} 与 ${item.face.manifest.id} 同时声明`);
3684
- owner.set(entry.resource, item.face.manifest.id);
3685
- prefs.push({
3686
- ...entry,
3687
- unit: item.face.manifest.id
3688
- });
3845
+ if (ledger.failed.has(item)) continue;
3846
+ const unit = item.face.manifest.id;
3847
+ const entries = pickMany(item);
3848
+ if (entries.length === 0) continue;
3849
+ const clash = entries.find((entry) => owner.has(entry.resource));
3850
+ if (clash !== void 0) {
3851
+ fail(ledger, item, `${label}名冲突:${clash.resource} 由套件 ${owner.get(clash.resource)} 与 ${unit} 同时声明`);
3852
+ continue;
3853
+ }
3854
+ for (const entry of entries) {
3855
+ owner.set(entry.resource, unit);
3856
+ out.push({
3857
+ ...entry,
3858
+ unit
3859
+ });
3860
+ }
3689
3861
  }
3690
- return prefs;
3862
+ return out;
3691
3863
  }
3692
- function collectActions(mounted) {
3864
+ function collectContextRenderers(mounted, ledger) {
3865
+ const table = /* @__PURE__ */ new Map();
3693
3866
  const owner = /* @__PURE__ */ new Map();
3694
- const actions = [];
3695
- for (const item of mounted) for (const entry of item.result.actions ?? []) {
3696
- const previous = owner.get(entry.resource);
3697
- if (previous !== void 0) throw new Error(`动作资源名冲突:${entry.resource} 由套件 ${previous} 与 ${item.face.manifest.id} 同时声明`);
3698
- owner.set(entry.resource, item.face.manifest.id);
3699
- actions.push({
3700
- ...entry,
3701
- unit: item.face.manifest.id
3702
- });
3867
+ for (const item of mounted) {
3868
+ if (ledger.failed.has(item)) continue;
3869
+ const unit = item.face.manifest.id;
3870
+ const context = item.result.context;
3871
+ if (context === void 0) continue;
3872
+ const clash = context.kinds.find((kind) => owner.has(kind));
3873
+ if (clash !== void 0) {
3874
+ fail(ledger, item, `对话引用的 kind 冲突:${clash} 由套件 ${owner.get(clash)} 与 ${unit} 同时认领`);
3875
+ continue;
3876
+ }
3877
+ for (const kind of context.kinds) {
3878
+ owner.set(kind, unit);
3879
+ table.set(kind, (ref) => context.render(ref));
3880
+ }
3881
+ }
3882
+ return table;
3883
+ }
3884
+ function pickProvider(mounted, ledger, pick, label) {
3885
+ let chosen;
3886
+ let chosenBy;
3887
+ for (const item of mounted) {
3888
+ if (ledger.failed.has(item)) continue;
3889
+ const value = pick(item);
3890
+ if (value === void 0) continue;
3891
+ if (chosen === void 0) {
3892
+ chosen = value;
3893
+ chosenBy = item.face.manifest.id;
3894
+ continue;
3895
+ }
3896
+ fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.face.manifest.id}`);
3703
3897
  }
3704
- return actions;
3898
+ return chosen;
3705
3899
  }
3706
3900
  function collectWarmReads(mounted) {
3707
3901
  const declarations = mounted.map((item) => item.result.warmReads).filter((declare) => declare !== void 0);
@@ -3717,26 +3911,15 @@ function collectWarmReads(mounted) {
3717
3911
  return reads;
3718
3912
  };
3719
3913
  }
3720
- function collectContextRenderers(mounted) {
3721
- const table = /* @__PURE__ */ new Map();
3722
- const owner = /* @__PURE__ */ new Map();
3723
- for (const item of mounted) {
3724
- const context = item.result.context;
3725
- if (context === void 0) continue;
3726
- for (const kind of context.kinds) {
3727
- const previous = owner.get(kind);
3728
- if (previous !== void 0) throw new Error(`对话引用的 kind 冲突:${kind} 由套件 ${previous} 与 ${item.face.manifest.id} 同时认领`);
3729
- owner.set(kind, item.face.manifest.id);
3730
- table.set(kind, (ref) => context.render(ref));
3731
- }
3732
- }
3733
- return table;
3734
- }
3735
3914
  function mountUnits(faces, deps) {
3736
3915
  const store = deps.store ?? createWorkspaceStore({ root: null });
3737
3916
  const mounted = [];
3738
3917
  const skipped = [];
3739
- const longTaskDisposers = [];
3918
+ const longTaskDisposers = /* @__PURE__ */ new Map();
3919
+ const ledger = {
3920
+ failed: /* @__PURE__ */ new Set(),
3921
+ failures: []
3922
+ };
3740
3923
  for (const face of faces) {
3741
3924
  const readiness = unitReadiness(face.manifest, (dep) => deps.ctx.get(dep));
3742
3925
  if (!readiness.canMount) {
@@ -3752,48 +3935,71 @@ function mountUnits(faces, deps) {
3752
3935
  unit,
3753
3936
  bump: deps.longTasks.bump
3754
3937
  });
3755
- if (longTasks !== void 0) longTaskDisposers.push(longTasks.dispose);
3756
- const result = face.mount({
3757
- ctx: deps.ctx,
3758
- logger: deps.logger,
3759
- manifests: deps.manifests,
3760
- ...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
3761
- ...deps.probe === void 0 ? {} : { probe: deps.probe },
3762
- ...deps.values === void 0 ? {} : { values: deps.values },
3763
- ...longTasks === void 0 ? {} : { longTasks: longTasks.host },
3764
- store
3765
- });
3766
- mounted.push({
3938
+ let result;
3939
+ try {
3940
+ result = face.mount({
3941
+ ctx: deps.ctx,
3942
+ logger: deps.logger,
3943
+ manifests: deps.manifests,
3944
+ ...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
3945
+ ...deps.probe === void 0 ? {} : { probe: deps.probe },
3946
+ ...deps.values === void 0 ? {} : { values: deps.values },
3947
+ ...longTasks === void 0 ? {} : { longTasks: longTasks.host },
3948
+ store
3949
+ });
3950
+ } catch (error) {
3951
+ longTasks?.dispose();
3952
+ ledger.failures.push({
3953
+ id: unit,
3954
+ reason: failureReasonOf(error)
3955
+ });
3956
+ continue;
3957
+ }
3958
+ const item = {
3767
3959
  face,
3768
3960
  readiness,
3769
3961
  result: withRunsRoute(result, unit)
3770
- });
3962
+ };
3963
+ mounted.push(item);
3964
+ if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
3771
3965
  }
3772
- const collected = collectRoutes(mounted);
3773
- const prefs = collectPrefs(mounted);
3774
- const actions = collectActions(mounted);
3966
+ const collected = collectRoutes(mounted, ledger);
3967
+ const prefs = collectNamed(mounted, ledger, (item) => item.result.prefs === void 0 ? [] : [item.result.prefs], "偏好资源");
3968
+ const actions = collectNamed(mounted, ledger, (item) => item.result.actions ?? [], "动作资源");
3775
3969
  const owners = new Map(collected.owners);
3776
3970
  for (const entry of [...prefs, ...actions]) owners.set(entry.resource, entry.unit);
3777
3971
  const factsOf = /* @__PURE__ */ new Map();
3778
3972
  for (const item of mounted) {
3973
+ if (ledger.failed.has(item)) continue;
3779
3974
  const declare = item.result.factsOf;
3780
3975
  if (declare !== void 0) factsOf.set(item.face.manifest.id, declare);
3781
3976
  }
3977
+ const gate = pickProvider(mounted, ledger, (item) => item.result.gate, "启停闸门");
3978
+ const provision = pickProvider(mounted, ledger, (item) => item.result.provision, "机器供给");
3979
+ const contextRenderers = collectContextRenderers(mounted, ledger);
3980
+ const alive = mounted.filter((item) => !ledger.failed.has(item));
3981
+ for (const item of mounted) {
3982
+ if (!ledger.failed.has(item)) continue;
3983
+ longTaskDisposers.get(item)?.();
3984
+ longTaskDisposers.delete(item);
3985
+ }
3782
3986
  return {
3783
- mounted,
3987
+ mounted: alive,
3784
3988
  routes: collected.routes,
3785
3989
  owners,
3786
3990
  routesOfUnit: collected.routesOfUnit,
3787
3991
  factsOf,
3788
- gate: pickGate(mounted),
3992
+ gate,
3789
3993
  prefs,
3790
3994
  actions,
3791
- provision: pickProvision(mounted),
3792
- contextRenderers: collectContextRenderers(mounted),
3995
+ provision,
3996
+ contextRenderers,
3793
3997
  skipped,
3794
- warmReads: collectWarmReads(mounted),
3998
+ failed: ledger.failures,
3999
+ warmReads: collectWarmReads(alive),
3795
4000
  disposeLongTasks: () => {
3796
- for (const dispose of longTaskDisposers.splice(0)) dispose();
4001
+ for (const dispose of [...longTaskDisposers.values()]) dispose();
4002
+ longTaskDisposers.clear();
3797
4003
  }
3798
4004
  };
3799
4005
  }
@@ -3968,6 +4174,7 @@ function apply(ctx) {
3968
4174
  logger: ctx.logger
3969
4175
  });
3970
4176
  const mounted = /* @__PURE__ */ new Map();
4177
+ const mountFailures = /* @__PURE__ */ new Map();
3971
4178
  const factsOfMap = /* @__PURE__ */ new Map();
3972
4179
  const routesOfUnitMap = /* @__PURE__ */ new Map();
3973
4180
  const ownersMap = /* @__PURE__ */ new Map();
@@ -3979,6 +4186,7 @@ function apply(ctx) {
3979
4186
  const item = mounted.get(id);
3980
4187
  if (item === void 0) return;
3981
4188
  mounted.delete(id);
4189
+ mountFailures.delete(id);
3982
4190
  for (const dispose of item.toolDisposers) dispose?.();
3983
4191
  item.gateOff?.();
3984
4192
  item.plan.disposeLongTasks();
@@ -3989,34 +4197,72 @@ function apply(ctx) {
3989
4197
  for (const resource of item.plan.owners.keys()) ownersMap.delete(resource);
3990
4198
  }
3991
4199
  function mountUnit(face) {
3992
- const one = mountUnits([face], {
3993
- ctx,
3994
- logger: ctx.logger,
3995
- manifests: liveManifests,
3996
- store,
3997
- ...opencliEnv === void 0 ? {} : { opencliCommand: opencliEnv },
3998
- probe: (unit, options) => probeOne(unit, options?.force ?? false),
3999
- values,
4000
- longTasks: {
4001
- scope: process.env["DSH_HOME"] ?? "",
4002
- bump: (names) => facts.bump(names)
4003
- }
4004
- });
4200
+ const id = face.manifest.id;
4201
+ let one;
4202
+ try {
4203
+ one = mountUnits([face], {
4204
+ ctx,
4205
+ logger: ctx.logger,
4206
+ manifests: liveManifests,
4207
+ store,
4208
+ ...opencliEnv === void 0 ? {} : { opencliCommand: opencliEnv },
4209
+ probe: (unit, options) => probeOne(unit, options?.force ?? false),
4210
+ values,
4211
+ longTasks: {
4212
+ scope: process.env["DSH_HOME"] ?? "",
4213
+ bump: (names) => facts.bump(names)
4214
+ }
4215
+ });
4216
+ } catch (error) {
4217
+ mountFailures.set(id, {
4218
+ reason: failureReasonOf(error),
4219
+ face
4220
+ });
4221
+ ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(装配期):${failureReasonOf(error)}`);
4222
+ return;
4223
+ }
4005
4224
  for (const item of one.skipped) ctx.logger?.warn?.(`[dsh-workbench] ${item.id} 套件缺硬依赖,跳过:${item.missing.join(", ")}`);
4225
+ for (const failure of one.failed) {
4226
+ mountFailures.set(failure.id, {
4227
+ reason: failure.reason,
4228
+ face
4229
+ });
4230
+ ctx.logger?.warn?.(`[dsh-workbench] ${failure.id} 套件起不来,已摘掉它这一份:${failure.reason}`);
4231
+ }
4006
4232
  const slot = one.mounted[0];
4007
- if (slot === void 0) return;
4233
+ if (slot === void 0) {
4234
+ one.disposeLongTasks();
4235
+ return;
4236
+ }
4008
4237
  if (slot.readiness.missingSoft.length > 0) ctx.logger?.warn?.(`[dsh-workbench] ${face.manifest.id} 套件降级:缺 ${slot.readiness.missingSoft.join(", ")}`);
4009
- Object.assign(routes, one.routes);
4010
- for (const [kind, render] of one.contextRenderers) renderers.set(kind, render);
4011
- for (const [unit, fn] of one.factsOf) factsOfMap.set(unit, fn);
4012
- for (const [unit, list] of one.routesOfUnit) routesOfUnitMap.set(unit, list);
4013
- for (const [resource, unit] of one.owners) ownersMap.set(resource, unit);
4014
- mounted.set(face.manifest.id, {
4015
- result: slot.result,
4016
- plan: one,
4017
- toolDisposers: slot.result.registerTools(),
4018
- gateOff: one.gate?.subscribe(() => toolScope.reapply())
4019
- });
4238
+ const added = [];
4239
+ try {
4240
+ Object.assign(routes, one.routes);
4241
+ added.push(...Object.keys(one.routes));
4242
+ for (const [kind, render] of one.contextRenderers) renderers.set(kind, render);
4243
+ for (const [unit, fn] of one.factsOf) factsOfMap.set(unit, fn);
4244
+ for (const [unit, list] of one.routesOfUnit) routesOfUnitMap.set(unit, list);
4245
+ for (const [resource, unit] of one.owners) ownersMap.set(resource, unit);
4246
+ mounted.set(face.manifest.id, {
4247
+ result: slot.result,
4248
+ plan: one,
4249
+ toolDisposers: slot.result.registerTools(),
4250
+ gateOff: one.gate?.subscribe(() => toolScope.reapply())
4251
+ });
4252
+ mountFailures.delete(id);
4253
+ } catch (error) {
4254
+ for (const route of added) delete routes[route];
4255
+ for (const kind of one.contextRenderers.keys()) renderers.delete(kind);
4256
+ for (const unit of one.factsOf.keys()) factsOfMap.delete(unit);
4257
+ for (const unit of one.routesOfUnit.keys()) routesOfUnitMap.delete(unit);
4258
+ for (const [resource, unit] of one.owners) if (ownersMap.get(resource) === unit) ownersMap.delete(resource);
4259
+ one.disposeLongTasks();
4260
+ mountFailures.set(id, {
4261
+ reason: failureReasonOf(error),
4262
+ face
4263
+ });
4264
+ ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(注册期):${failureReasonOf(error)}`);
4265
+ }
4020
4266
  }
4021
4267
  function rebindPost() {
4022
4268
  for (const dispose of undoPost.reverse()) dispose?.();
@@ -4039,7 +4285,13 @@ function apply(ctx) {
4039
4285
  function sync() {
4040
4286
  const present = new Set(units.all().map((face) => face.manifest.id));
4041
4287
  for (const id of [...mounted.keys()]) if (!present.has(id)) unmountUnit(id);
4042
- for (const face of units.all()) if (!mounted.has(face.manifest.id)) mountUnit(face);
4288
+ for (const id of [...mountFailures.keys()]) if (!present.has(id)) mountFailures.delete(id);
4289
+ for (const face of units.all()) {
4290
+ if (mounted.has(face.manifest.id)) continue;
4291
+ const already = mountFailures.get(face.manifest.id);
4292
+ if (already !== void 0 && already.face === face) continue;
4293
+ mountUnit(face);
4294
+ }
4043
4295
  rebindPost();
4044
4296
  }
4045
4297
  if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
@@ -4061,6 +4313,14 @@ function apply(ctx) {
4061
4313
  ...item,
4062
4314
  released: releasedOf(item.unit)
4063
4315
  })) };
4316
+ const failure = mountFailures.get(unit);
4317
+ if (failure !== void 0) return {
4318
+ unit,
4319
+ status: "failed",
4320
+ required: [],
4321
+ missing: [],
4322
+ detail: failure.reason
4323
+ };
4064
4324
  return probeOne(unit, force);
4065
4325
  });
4066
4326
  }