@dsh-workbench/core 0.1.9 → 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.
@@ -3788,16 +3788,43 @@ function withRunsRoute(result, unit) {
3788
3788
  }
3789
3789
  };
3790
3790
  }
3791
- 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) {
3792
3803
  const routes = {};
3793
3804
  const owner = /* @__PURE__ */ new Map();
3794
3805
  const routesOfUnit = /* @__PURE__ */ new Map();
3795
3806
  for (const item of mounted) {
3807
+ if (ledger.failed.has(item)) continue;
3796
3808
  const unit = item.face.manifest.id;
3809
+ const accepted = [];
3810
+ let problem;
3797
3811
  for (const [key, handler] of Object.entries(item.result.routes)) {
3798
- 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
+ }
3799
3816
  const previous = owner.get(key);
3800
- 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) {
3801
3828
  owner.set(key, unit);
3802
3829
  routes[key] = handler;
3803
3830
  const own = routesOfUnit.get(unit);
@@ -3811,45 +3838,64 @@ function collectRoutes(mounted) {
3811
3838
  routesOfUnit
3812
3839
  };
3813
3840
  }
3814
- function pickGate(mounted) {
3815
- const providers = mounted.filter((item) => item.result.gate !== void 0);
3816
- if (providers.length > 1) throw new Error(`启停闸门只能有一个提供者,现在是:${providers.map((item) => item.face.manifest.id).join("、")}`);
3817
- return providers[0]?.result.gate;
3818
- }
3819
- function pickProvision(mounted) {
3820
- const providers = mounted.filter((item) => item.result.provision !== void 0);
3821
- if (providers.length > 1) throw new Error(`机器供给只能有一个提供者,现在是:${providers.map((item) => item.face.manifest.id).join("、")}`);
3822
- return providers[0]?.result.provision;
3823
- }
3824
- function collectPrefs(mounted) {
3841
+ function collectNamed(mounted, ledger, pickMany, label) {
3825
3842
  const owner = /* @__PURE__ */ new Map();
3826
- const prefs = [];
3843
+ const out = [];
3827
3844
  for (const item of mounted) {
3828
- const entry = item.result.prefs;
3829
- if (entry === void 0) continue;
3830
- const previous = owner.get(entry.resource);
3831
- if (previous !== void 0) throw new Error(`偏好资源名冲突:${entry.resource} 由套件 ${previous} 与 ${item.face.manifest.id} 同时声明`);
3832
- owner.set(entry.resource, item.face.manifest.id);
3833
- prefs.push({
3834
- ...entry,
3835
- unit: item.face.manifest.id
3836
- });
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
+ }
3837
3861
  }
3838
- return prefs;
3862
+ return out;
3839
3863
  }
3840
- function collectActions(mounted) {
3864
+ function collectContextRenderers(mounted, ledger) {
3865
+ const table = /* @__PURE__ */ new Map();
3841
3866
  const owner = /* @__PURE__ */ new Map();
3842
- const actions = [];
3843
- for (const item of mounted) for (const entry of item.result.actions ?? []) {
3844
- const previous = owner.get(entry.resource);
3845
- if (previous !== void 0) throw new Error(`动作资源名冲突:${entry.resource} 由套件 ${previous} 与 ${item.face.manifest.id} 同时声明`);
3846
- owner.set(entry.resource, item.face.manifest.id);
3847
- actions.push({
3848
- ...entry,
3849
- unit: item.face.manifest.id
3850
- });
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}`);
3851
3897
  }
3852
- return actions;
3898
+ return chosen;
3853
3899
  }
3854
3900
  function collectWarmReads(mounted) {
3855
3901
  const declarations = mounted.map((item) => item.result.warmReads).filter((declare) => declare !== void 0);
@@ -3865,26 +3911,15 @@ function collectWarmReads(mounted) {
3865
3911
  return reads;
3866
3912
  };
3867
3913
  }
3868
- function collectContextRenderers(mounted) {
3869
- const table = /* @__PURE__ */ new Map();
3870
- const owner = /* @__PURE__ */ new Map();
3871
- for (const item of mounted) {
3872
- const context = item.result.context;
3873
- if (context === void 0) continue;
3874
- for (const kind of context.kinds) {
3875
- const previous = owner.get(kind);
3876
- if (previous !== void 0) throw new Error(`对话引用的 kind 冲突:${kind} 由套件 ${previous} 与 ${item.face.manifest.id} 同时认领`);
3877
- owner.set(kind, item.face.manifest.id);
3878
- table.set(kind, (ref) => context.render(ref));
3879
- }
3880
- }
3881
- return table;
3882
- }
3883
3914
  function mountUnits(faces, deps) {
3884
3915
  const store = deps.store ?? createWorkspaceStore({ root: null });
3885
3916
  const mounted = [];
3886
3917
  const skipped = [];
3887
- const longTaskDisposers = [];
3918
+ const longTaskDisposers = /* @__PURE__ */ new Map();
3919
+ const ledger = {
3920
+ failed: /* @__PURE__ */ new Set(),
3921
+ failures: []
3922
+ };
3888
3923
  for (const face of faces) {
3889
3924
  const readiness = unitReadiness(face.manifest, (dep) => deps.ctx.get(dep));
3890
3925
  if (!readiness.canMount) {
@@ -3900,48 +3935,71 @@ function mountUnits(faces, deps) {
3900
3935
  unit,
3901
3936
  bump: deps.longTasks.bump
3902
3937
  });
3903
- if (longTasks !== void 0) longTaskDisposers.push(longTasks.dispose);
3904
- const result = face.mount({
3905
- ctx: deps.ctx,
3906
- logger: deps.logger,
3907
- manifests: deps.manifests,
3908
- ...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
3909
- ...deps.probe === void 0 ? {} : { probe: deps.probe },
3910
- ...deps.values === void 0 ? {} : { values: deps.values },
3911
- ...longTasks === void 0 ? {} : { longTasks: longTasks.host },
3912
- store
3913
- });
3914
- 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 = {
3915
3959
  face,
3916
3960
  readiness,
3917
3961
  result: withRunsRoute(result, unit)
3918
- });
3962
+ };
3963
+ mounted.push(item);
3964
+ if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
3919
3965
  }
3920
- const collected = collectRoutes(mounted);
3921
- const prefs = collectPrefs(mounted);
3922
- 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 ?? [], "动作资源");
3923
3969
  const owners = new Map(collected.owners);
3924
3970
  for (const entry of [...prefs, ...actions]) owners.set(entry.resource, entry.unit);
3925
3971
  const factsOf = /* @__PURE__ */ new Map();
3926
3972
  for (const item of mounted) {
3973
+ if (ledger.failed.has(item)) continue;
3927
3974
  const declare = item.result.factsOf;
3928
3975
  if (declare !== void 0) factsOf.set(item.face.manifest.id, declare);
3929
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
+ }
3930
3986
  return {
3931
- mounted,
3987
+ mounted: alive,
3932
3988
  routes: collected.routes,
3933
3989
  owners,
3934
3990
  routesOfUnit: collected.routesOfUnit,
3935
3991
  factsOf,
3936
- gate: pickGate(mounted),
3992
+ gate,
3937
3993
  prefs,
3938
3994
  actions,
3939
- provision: pickProvision(mounted),
3940
- contextRenderers: collectContextRenderers(mounted),
3995
+ provision,
3996
+ contextRenderers,
3941
3997
  skipped,
3942
- warmReads: collectWarmReads(mounted),
3998
+ failed: ledger.failures,
3999
+ warmReads: collectWarmReads(alive),
3943
4000
  disposeLongTasks: () => {
3944
- for (const dispose of longTaskDisposers.splice(0)) dispose();
4001
+ for (const dispose of [...longTaskDisposers.values()]) dispose();
4002
+ longTaskDisposers.clear();
3945
4003
  }
3946
4004
  };
3947
4005
  }
@@ -4116,6 +4174,7 @@ function apply(ctx) {
4116
4174
  logger: ctx.logger
4117
4175
  });
4118
4176
  const mounted = /* @__PURE__ */ new Map();
4177
+ const mountFailures = /* @__PURE__ */ new Map();
4119
4178
  const factsOfMap = /* @__PURE__ */ new Map();
4120
4179
  const routesOfUnitMap = /* @__PURE__ */ new Map();
4121
4180
  const ownersMap = /* @__PURE__ */ new Map();
@@ -4127,6 +4186,7 @@ function apply(ctx) {
4127
4186
  const item = mounted.get(id);
4128
4187
  if (item === void 0) return;
4129
4188
  mounted.delete(id);
4189
+ mountFailures.delete(id);
4130
4190
  for (const dispose of item.toolDisposers) dispose?.();
4131
4191
  item.gateOff?.();
4132
4192
  item.plan.disposeLongTasks();
@@ -4137,34 +4197,72 @@ function apply(ctx) {
4137
4197
  for (const resource of item.plan.owners.keys()) ownersMap.delete(resource);
4138
4198
  }
4139
4199
  function mountUnit(face) {
4140
- const one = mountUnits([face], {
4141
- ctx,
4142
- logger: ctx.logger,
4143
- manifests: liveManifests,
4144
- store,
4145
- ...opencliEnv === void 0 ? {} : { opencliCommand: opencliEnv },
4146
- probe: (unit, options) => probeOne(unit, options?.force ?? false),
4147
- values,
4148
- longTasks: {
4149
- scope: process.env["DSH_HOME"] ?? "",
4150
- bump: (names) => facts.bump(names)
4151
- }
4152
- });
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
+ }
4153
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
+ }
4154
4232
  const slot = one.mounted[0];
4155
- if (slot === void 0) return;
4233
+ if (slot === void 0) {
4234
+ one.disposeLongTasks();
4235
+ return;
4236
+ }
4156
4237
  if (slot.readiness.missingSoft.length > 0) ctx.logger?.warn?.(`[dsh-workbench] ${face.manifest.id} 套件降级:缺 ${slot.readiness.missingSoft.join(", ")}`);
4157
- Object.assign(routes, one.routes);
4158
- for (const [kind, render] of one.contextRenderers) renderers.set(kind, render);
4159
- for (const [unit, fn] of one.factsOf) factsOfMap.set(unit, fn);
4160
- for (const [unit, list] of one.routesOfUnit) routesOfUnitMap.set(unit, list);
4161
- for (const [resource, unit] of one.owners) ownersMap.set(resource, unit);
4162
- mounted.set(face.manifest.id, {
4163
- result: slot.result,
4164
- plan: one,
4165
- toolDisposers: slot.result.registerTools(),
4166
- gateOff: one.gate?.subscribe(() => toolScope.reapply())
4167
- });
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
+ }
4168
4266
  }
4169
4267
  function rebindPost() {
4170
4268
  for (const dispose of undoPost.reverse()) dispose?.();
@@ -4187,7 +4285,13 @@ function apply(ctx) {
4187
4285
  function sync() {
4188
4286
  const present = new Set(units.all().map((face) => face.manifest.id));
4189
4287
  for (const id of [...mounted.keys()]) if (!present.has(id)) unmountUnit(id);
4190
- 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
+ }
4191
4295
  rebindPost();
4192
4296
  }
4193
4297
  if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
@@ -4209,6 +4313,14 @@ function apply(ctx) {
4209
4313
  ...item,
4210
4314
  released: releasedOf(item.unit)
4211
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
+ };
4212
4324
  return probeOne(unit, force);
4213
4325
  });
4214
4326
  }