@dsh-workbench/core 0.1.16 → 0.1.18

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 (68) hide show
  1. package/client.js +665 -127
  2. package/dist/board.css +31 -0
  3. package/dist/client/client.body.js +665 -127
  4. package/dist/client/index.js +665 -127
  5. package/dist/host/index.js +1510 -263
  6. package/dist/types/app/client.d.ts +124 -27
  7. package/dist/types/app/layouts/market/add-module-text.d.ts +2 -0
  8. package/dist/types/app/layouts/market/use-add-module.d.ts +4 -3
  9. package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -0
  10. package/dist/types/app/layouts/rightbar/MarketView.d.ts +3 -1
  11. package/dist/types/app/layouts/rightbar/__tests__/market-rows-plugins.test.d.ts +1 -0
  12. package/dist/types/app/layouts/rightbar/market-freshness.d.ts +1 -0
  13. package/dist/types/app/layouts/rightbar/market-mock.d.ts +9 -0
  14. package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +8 -0
  15. package/dist/types/app/layouts/rightbar/market-rows.d.ts +14 -3
  16. package/dist/types/app/layouts/rightbar/registry.d.ts +0 -1
  17. package/dist/types/app/units/__tests__/manifests.test.d.ts +1 -0
  18. package/dist/types/app/units/fixture-unit-mock.d.ts +3 -3
  19. package/dist/types/app/units/host.d.ts +2 -2
  20. package/dist/types/app/units/manifests.d.ts +5 -0
  21. package/dist/types/app/units/registry.d.ts +10 -2
  22. package/dist/types/modules/panels/__tests__/market-env.test.d.ts +1 -0
  23. package/dist/types/modules/panels/__tests__/market-plugin.test.d.ts +1 -0
  24. package/dist/types/modules/panels/__tests__/market-route.test.d.ts +1 -0
  25. package/dist/types/modules/panels/__tests__/plugin-actions.test.d.ts +1 -0
  26. package/dist/types/modules/panels/__tests__/plugin-ledger.test.d.ts +1 -0
  27. package/dist/types/modules/panels/__tests__/plugin-section.test.d.ts +1 -0
  28. package/dist/types/modules/panels/__tests__/scope.test.d.ts +1 -0
  29. package/dist/types/modules/panels/__tests__/service-release.test.d.ts +1 -0
  30. package/dist/types/modules/panels/index.d.ts +2 -2
  31. package/dist/types/modules/panels/model/config.d.ts +27 -9
  32. package/dist/types/modules/panels/model/market-dispatch.d.ts +9 -0
  33. package/dist/types/modules/panels/model/market-env.d.ts +20 -0
  34. package/dist/types/modules/panels/model/market-route.d.ts +26 -0
  35. package/dist/types/modules/panels/model/market-types.d.ts +71 -0
  36. package/dist/types/modules/panels/model/market.d.ts +7 -66
  37. package/dist/types/modules/panels/model/plugin-actions.d.ts +50 -0
  38. package/dist/types/modules/panels/model/plugin-ledger.d.ts +22 -0
  39. package/dist/types/modules/panels/model/plugin-name.d.ts +8 -0
  40. package/dist/types/modules/panels/model/plugin-name.test.d.ts +1 -0
  41. package/dist/types/modules/panels/model/plugin-section.d.ts +13 -0
  42. package/dist/types/modules/panels/model/service.d.ts +8 -6
  43. package/dist/types/shared/api/bundle-install.d.ts +8 -1
  44. package/dist/types/shared/api/host/__tests__/registry-declaration.test.d.ts +1 -0
  45. package/dist/types/shared/api/host/__tests__/unit-roster-facts.test.d.ts +1 -0
  46. package/dist/types/shared/api/host/bundle-routes.d.ts +1 -0
  47. package/dist/types/shared/api/host/installed-module.d.ts +7 -0
  48. package/dist/types/shared/api/host/mount-units.d.ts +5 -3
  49. package/dist/types/shared/api/host/package-content.d.ts +6 -0
  50. package/dist/types/shared/api/host/package-content.test.d.ts +1 -0
  51. package/dist/types/shared/api/host/package-kind.d.ts +21 -0
  52. package/dist/types/shared/api/host/package-kind.test.d.ts +1 -0
  53. package/dist/types/shared/api/host/package-route.d.ts +23 -0
  54. package/dist/types/shared/api/host/package-route.test.d.ts +1 -0
  55. package/dist/types/shared/api/host/registry-declaration.d.ts +23 -0
  56. package/dist/types/shared/api/host/unit-registry.d.ts +33 -4
  57. package/dist/types/shared/api/host/unit-roster-facts.d.ts +5 -0
  58. package/dist/types/shared/api/installed-plugins.d.ts +12 -0
  59. package/dist/types/shared/api/unit-manifests.d.ts +5 -0
  60. package/dist/types/shared/api/unit-manifests.test.d.ts +1 -0
  61. package/dist/types/shared/hooks/bundle-queries.d.ts +2 -0
  62. package/dist/types/shared/hooks/use-facts.d.ts +1 -0
  63. package/package.json +2 -2
  64. package/skills/workbench-module-diy/BUILD.md +5 -5
  65. package/skills/workbench-module-diy/DESIGN.md +7 -1
  66. package/skills/workbench-module-diy/REVIEW.md +4 -3
  67. package/skills/workbench-module-diy/TOOLING.md +2 -2
  68. package/skills/workbench-module-diy/TOOLS.md +13 -3
@@ -422,6 +422,133 @@ function probeKeyOf(error) {
422
422
  if (error === void 0) return null;
423
423
  return error.code === "" ? "unknown" : error.code;
424
424
  }
425
+ var SUPPORTED_API_VERSIONS = [1];
426
+ var DECLARATION_KEYS = [
427
+ "apiVersion",
428
+ "unit",
429
+ "card",
430
+ "tools",
431
+ "host",
432
+ "env"
433
+ ];
434
+ var DECLARATION_REQUIRED_KEYS = [
435
+ "apiVersion",
436
+ "unit",
437
+ "card",
438
+ "tools",
439
+ "host"
440
+ ];
441
+ var CARD_KEYS = ["titleKey", "order"];
442
+ var TOOL_KEYS = [
443
+ "name",
444
+ "writes",
445
+ "key"
446
+ ];
447
+ var TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
448
+ var HOST_KEYS = ["inject", "optional"];
449
+ function text(value) {
450
+ return typeof value === "string" ? value.trim() : "";
451
+ }
452
+ function isRecord(value) {
453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
454
+ }
455
+ function outsideKeys(value, allowed) {
456
+ return Object.keys(value).filter((key) => !allowed.includes(key));
457
+ }
458
+ function listOf(keys) {
459
+ return keys.map((key) => JSON.stringify(key)).join(" / ");
460
+ }
461
+ function stringArrayProblems(at, value) {
462
+ if (!Array.isArray(value)) return [`${at} 要是一串字符串,现在是 ${JSON.stringify(value)}`];
463
+ return value.flatMap((item, index) => text(item) === "" ? [`${at}[${index}] 不是非空字符串:${JSON.stringify(item)}`] : []);
464
+ }
465
+ function declarationProblemsOf(subject, raw) {
466
+ if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
467
+ const version = raw.apiVersion;
468
+ if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v1 起,写 \`"apiVersion": 1\`。缺它 = 外壳不认这个模块(ADR-0038 §2.3)`];
469
+ if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version)) return [`${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ${SUPPORTED_API_VERSIONS.join(" / ")}(要整数)`];
470
+ const problems = [];
471
+ const unknown = outsideKeys(raw, DECLARATION_KEYS);
472
+ if (unknown.length > 0) problems.push(`${subject}: dsh.workbench 里有协议 v1 不认识的键 ${listOf(unknown)} —— 只有 ${listOf(DECLARATION_KEYS)}。(拼错的键会被外壳**静默忽略**:那句声明等于没写)`);
473
+ for (const key of DECLARATION_REQUIRED_KEYS) if (raw[key] === void 0) problems.push(`${subject}: dsh.workbench 缺 ${key}` + (key === "card" ? "(这个模块没有卡片就写 `\"card\": null`)" : ""));
474
+ const card = raw.card;
475
+ if (card !== void 0 && card !== null) {
476
+ if (!isRecord(card)) problems.push(`${subject}: dsh.workbench.card 要么是对象、要么是 null,现在是 ${JSON.stringify(card)}`);
477
+ else {
478
+ const extra = outsideKeys(card, CARD_KEYS);
479
+ if (extra.length > 0) problems.push(`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
480
+ if (text(card.titleKey) === "") problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
481
+ if (typeof card.order !== "number" || !Number.isFinite(card.order)) problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
482
+ }
483
+ }
484
+ const tools = raw.tools;
485
+ if (tools !== void 0) {
486
+ if (!Array.isArray(tools)) problems.push(`${subject}: dsh.workbench.tools 要是一串声明,现在是 ${JSON.stringify(tools)}`);
487
+ else {
488
+ const seen = /* @__PURE__ */ new Set();
489
+ const seenKeys = /* @__PURE__ */ new Set();
490
+ tools.forEach((tool, index) => {
491
+ const at = `dsh.workbench.tools[${index}]`;
492
+ if (!isRecord(tool)) {
493
+ problems.push(`${subject}: ${at} 要是对象,现在是 ${JSON.stringify(tool)}`);
494
+ return;
495
+ }
496
+ const extra = outsideKeys(tool, TOOL_KEYS);
497
+ if (extra.length > 0) problems.push(`${subject}: ${at} 里有不认的键 ${listOf(extra)} —— tools 只有 ${listOf(TOOL_KEYS)}`);
498
+ const name = text(tool.name);
499
+ if (name === "") problems.push(`${subject}: ${at}.name 要是非空字符串`);
500
+ else if (seen.has(name)) problems.push(`${subject}: ${at}.name「${name}」与前面那条撞了 —— 工具名会进 dsh 的工具表,不许重`);
501
+ else seen.add(name);
502
+ if (typeof tool.writes !== "boolean") problems.push(`${subject}: ${at}.writes 要是 true / false(它是"这会写"的唯一声明,缺了审批无从认领)`);
503
+ if (tool.key !== void 0) {
504
+ const key = text(tool.key);
505
+ if (!TOOL_KEY_SHAPE.test(key)) problems.push(`${subject}: ${at}.key 要是 lowerCamelCase 的标识符(如 \`sync\` / \`podLogs\`),现在是 ${JSON.stringify(tool.key)} —— 它会变成生成物里的键名,所以不引号、不许带点或横线`);
506
+ else if (seenKeys.has(key)) problems.push(`${subject}: ${at}.key「${key}」与前面那条撞了 —— 生成物里的键只能有一个`);
507
+ else seenKeys.add(key);
508
+ }
509
+ });
510
+ }
511
+ }
512
+ const host = raw.host;
513
+ if (host !== void 0) {
514
+ if (!isRecord(host)) problems.push(`${subject}: dsh.workbench.host 要是对象,现在是 ${JSON.stringify(host)}`);
515
+ else {
516
+ const extra = outsideKeys(host, HOST_KEYS);
517
+ if (extra.length > 0) problems.push(`${subject}: dsh.workbench.host 里有不认的键 ${listOf(extra)} —— host 只有 ${listOf(HOST_KEYS)}`);
518
+ if (host.inject === void 0) problems.push(`${subject}: dsh.workbench.host.inject 没写 —— 它声明这个模块要外壳的哪几个服务,外壳靠它判"能不能挂"。一个都不要也要写 \`"inject": []\``);
519
+ else problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.inject`, host.inject));
520
+ if (host.optional !== void 0) problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.optional`, host.optional));
521
+ }
522
+ }
523
+ if (raw.env !== void 0 && !isRecord(raw.env)) problems.push(`${subject}: dsh.workbench.env 要是对象(command / plugins / libs)—— 语义由门禁的 env 判据查,这里只查形状`);
524
+ return problems;
525
+ }
526
+ function readDeclaration(subject, raw) {
527
+ const problems = declarationProblemsOf(subject, raw);
528
+ if (problems.length > 0) return { problems };
529
+ const declaration = raw;
530
+ const card = declaration.card;
531
+ return {
532
+ declaration,
533
+ problems: [],
534
+ manifest: {
535
+ id: text(declaration.unit),
536
+ card: card === null ? null : {
537
+ titleKey: text(card.titleKey),
538
+ order: card.order
539
+ },
540
+ tools: declaration.tools.map((tool) => ({
541
+ name: text(tool.name),
542
+ writes: tool.writes
543
+ })),
544
+ host: {
545
+ inject: [...declaration.host.inject ?? []],
546
+ ...declaration.host.optional === void 0 ? {} : { optional: [...declaration.host.optional] }
547
+ },
548
+ ...declaration.env === void 0 ? {} : { env: declaration.env }
549
+ }
550
+ };
551
+ }
425
552
  //#endregion
426
553
  //#region ../shared/dist/config/constants.js
427
554
  var PLUGIN_ID = "@dsh-workbench/core";
@@ -609,7 +736,7 @@ function ApprovalDetail(props) {
609
736
  if (owner?.approval !== void 0) {
610
737
  const Render = owner.approval.render;
611
738
  return h(CardBoundary, {
612
- unit: owner.manifest.id,
739
+ unit: owner.unit,
613
740
  resetKey: props.callId ?? "",
614
741
  t
615
742
  }, h(Render, {
@@ -1110,7 +1237,7 @@ var en = {
1110
1237
  "panels.market.missing": "not installed",
1111
1238
  "panels.market.unchecked": "unknown",
1112
1239
  "panels.market.checking": "checking",
1113
- "panels.market.sectionInstalled": "Installed",
1240
+ "panels.market.sectionInstalled": "Installed modules",
1114
1241
  "panels.market.sectionPending": "Not installed",
1115
1242
  "panels.market.sectionShared": "Shared dependencies",
1116
1243
  "panels.market.sharedUsedBy": "Used by {modules}",
@@ -1138,11 +1265,19 @@ var en = {
1138
1265
  "panels.market.noSource": "This module declares no install method — install it yourself",
1139
1266
  "panels.market.badSource": "The install method is incomplete — install it yourself",
1140
1267
  "panels.market.empty": "No modules available",
1141
- "panels.market.installedSection": "Installed",
1142
- "panels.market.sectionLocal": "Linked locally",
1268
+ "panels.market.sectionLocal": "Local modules",
1143
1269
  "panels.market.sectionUnlisted": "Not from the market",
1270
+ "panels.market.sectionPlugins": "Installed opencli plugins",
1144
1271
  "panels.market.moduleOutdated": "update available",
1272
+ "panels.market.moduleRefused": "This module was built for an older version of the workbench, so it does not work here. Update it, or uninstall.",
1273
+ "panels.market.moduleRefusedDetail": "Why it cannot be used: {reason}",
1145
1274
  "panels.market.moduleRestartHint": "installed but not active — restart dsh once to bring it up",
1275
+ "panels.market.pluginKeptInUse": "Files kept for now: {names} still uses it.",
1276
+ "panels.market.pluginKeptUnsure": "Files kept for now: some modules on this machine do not say what they use, so it is unclear who needs it.",
1277
+ "panels.market.pluginKeptHeld": "Files kept: this one is yours, and a module letting go will not remove it.",
1278
+ "panels.market.pluginKeptUnnamed": "Files kept for now: this one cannot be identified as a plugin, so it is unclear who needs it.",
1279
+ "panels.market.pluginAlsoKnownAs": "Its package is {pkg} (updates and uninstall use that)",
1280
+ "panels.market.pluginBroken": "This install is broken: it cannot be identified as a plugin. Uninstall it, or install it again by package name above.",
1146
1281
  "panels.market.restartNeeded": "The new version is installed — restart once to pick it up.",
1147
1282
  "panels.market.foot": "After installing, enable it in the panel page of that project.",
1148
1283
  "panels.market.recheck": "Check for updates",
@@ -1190,7 +1325,11 @@ var en = {
1190
1325
  "panels.market.add.problemInstalled": "This module is already installed",
1191
1326
  "panels.market.add.problemNotFound": "No such module found",
1192
1327
  "panels.market.add.problemNotPackage": "That path does not exist or is not a valid module package",
1193
- "panels.market.add.problemNotBundle": "This package declares no bundle, so it cannot be installed as a module: {reason}",
1328
+ "panels.market.add.problemNotBundle": "This package cannot be installed here: {reason}. If it is actually an opencli plugin, use its package name (for example opencli-plugin-cmdb) or a local folder — a git URL or tarball does not reveal what it is.",
1329
+ "panels.market.add.problemNotWorkbenchModule": "This package does not match the workbench you are running, so installing it would not work. Pick another version, or ask the module author.",
1330
+ "panels.market.add.problemNotModuleOrPlugin": "This package is neither a workbench module nor an opencli plugin, so installing it would do nothing. Pick another version, or ask the package author.",
1331
+ "panels.market.add.failureNotModuleOrPlugin": "This package is neither a workbench module nor an opencli plugin, so installing it would do nothing",
1332
+ "panels.market.add.installedPlugin": "Installed: the opencli plugin {name}",
1194
1333
  "panels.market.add.problemNetwork": "Cannot reach the module source; check the network and retry",
1195
1334
  "panels.market.add.problemUnknown": "Cannot fetch module information: {reason}",
1196
1335
  "panels.market.add.failureNetwork": "The network connection failed",
@@ -1221,6 +1360,7 @@ var en = {
1221
1360
  "panels.market.add.registryPnpm": "pnpm's own registry",
1222
1361
  "panels.market.add.check": "Check",
1223
1362
  "panels.market.add.checking": "Checking…",
1363
+ "panels.market.add.classifying": "Working out what it is…",
1224
1364
  "panels.market.add.change": "Edit",
1225
1365
  "panels.market.add.previewTitle": "About to install",
1226
1366
  "panels.market.add.fieldName": "Package",
@@ -1425,7 +1565,7 @@ var zh = {
1425
1565
  "panels.market.missing": "未装",
1426
1566
  "panels.market.unchecked": "状态未知",
1427
1567
  "panels.market.checking": "检测中",
1428
- "panels.market.sectionInstalled": "已安装",
1568
+ "panels.market.sectionInstalled": "已安装模块",
1429
1569
  "panels.market.sectionPending": "未安装",
1430
1570
  "panels.market.sectionShared": "共用依赖",
1431
1571
  "panels.market.sharedUsedBy": "供 {modules} 使用",
@@ -1453,11 +1593,19 @@ var zh = {
1453
1593
  "panels.market.noSource": "这个模块没声明怎么装,只能自己动手",
1454
1594
  "panels.market.badSource": "安装方式没写全,只能自己动手",
1455
1595
  "panels.market.empty": "暂无可用模块",
1456
- "panels.market.installedSection": "已安装",
1457
- "panels.market.sectionLocal": "本地装的",
1596
+ "panels.market.sectionLocal": "本地模块",
1458
1597
  "panels.market.sectionUnlisted": "不是从市场装的",
1598
+ "panels.market.sectionPlugins": "已安装 opencli 插件",
1459
1599
  "panels.market.moduleOutdated": "有新版",
1600
+ "panels.market.moduleRefused": "这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。",
1601
+ "panels.market.moduleRefusedDetail": "装不上的原因:{reason}",
1460
1602
  "panels.market.moduleRestartHint": "装好了但还没生效 —— 重启一次 dsh 就会出来",
1603
+ "panels.market.pluginKeptInUse": "文件先留着:{names} 还要用它。",
1604
+ "panels.market.pluginKeptUnsure": "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。",
1605
+ "panels.market.pluginKeptHeld": "文件留着:你自己装的那一份,模块放手不动它。",
1606
+ "panels.market.pluginKeptUnnamed": "文件先留着:看不出这是哪个插件,说不准谁在用它。",
1607
+ "panels.market.pluginAlsoKnownAs": "包名是 {pkg}(更新与卸载按它来)",
1608
+ "panels.market.pluginBroken": "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。",
1461
1609
  "panels.market.restartNeeded": "新版装好了,重启一次才生效。",
1462
1610
  "panels.market.foot": "装好后到那个项目的面板页勾选它,模块才会出现。",
1463
1611
  "panels.market.recheck": "重新检查更新",
@@ -1505,7 +1653,11 @@ var zh = {
1505
1653
  "panels.market.add.problemInstalled": "该模块已安装",
1506
1654
  "panels.market.add.problemNotFound": "未找到相关模块",
1507
1655
  "panels.market.add.problemNotPackage": "该路径不存在或不是有效的模块包",
1508
- "panels.market.add.problemNotBundle": "这个包没有声明组合包,无法作为模块安装:{reason}",
1656
+ "panels.market.add.problemNotBundle": "这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。",
1657
+ "panels.market.add.problemNotWorkbenchModule": "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。",
1658
+ "panels.market.add.problemNotModuleOrPlugin": "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。",
1659
+ "panels.market.add.failureNotModuleOrPlugin": "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上",
1660
+ "panels.market.add.installedPlugin": "装上了:opencli 插件 {name}",
1509
1661
  "panels.market.add.problemNetwork": "无法连接模块源,请检查网络后重试",
1510
1662
  "panels.market.add.problemUnknown": "无法获取模块信息:{reason}",
1511
1663
  "panels.market.add.failureNetwork": "网络连接失败",
@@ -1536,6 +1688,7 @@ var zh = {
1536
1688
  "panels.market.add.registryPnpm": "pnpm 自己配的源",
1537
1689
  "panels.market.add.check": "预检",
1538
1690
  "panels.market.add.checking": "正在预检…",
1691
+ "panels.market.add.classifying": "判定中…",
1539
1692
  "panels.market.add.change": "改一下",
1540
1693
  "panels.market.add.previewTitle": "将要安装",
1541
1694
  "panels.market.add.fieldName": "包名",
@@ -1812,6 +1965,8 @@ var dist_exports = /* @__PURE__ */ __exportAll({
1812
1965
  CenteredState: () => CenteredState,
1813
1966
  ChoiceGroup: () => ChoiceGroup,
1814
1967
  Component: () => Component,
1968
+ DECLARATION_KEYS: () => DECLARATION_KEYS,
1969
+ DECLARATION_REQUIRED_KEYS: () => DECLARATION_REQUIRED_KEYS,
1815
1970
  DEFAULT_WORKSPACE_TITLE: () => DEFAULT_WORKSPACE_TITLE,
1816
1971
  EVENTS_PATH: () => EVENTS_PATH,
1817
1972
  EmptyState: () => EmptyState,
@@ -1850,6 +2005,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
1850
2005
  STORAGE_KEY_PROJECT: () => STORAGE_KEY_PROJECT,
1851
2006
  STORAGE_KEY_RIGHTBAR: () => STORAGE_KEY_RIGHTBAR,
1852
2007
  STORAGE_KEY_RIGHTBAR_COLUMN: () => STORAGE_KEY_RIGHTBAR_COLUMN,
2008
+ SUPPORTED_API_VERSIONS: () => SUPPORTED_API_VERSIONS,
1853
2009
  SaveBar: () => SaveBar,
1854
2010
  SettingsSection: () => SettingsSection,
1855
2011
  SkeletonRows: () => SkeletonRows,
@@ -1860,6 +2016,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
1860
2016
  VERSION_CHECK_TIMEOUT_MS: () => VERSION_CHECK_TIMEOUT_MS,
1861
2017
  VERSION_CHECK_TTL_MS: () => VERSION_CHECK_TTL_MS,
1862
2018
  WARM_TIMEOUT_MS: () => WARM_TIMEOUT_MS,
2019
+ WORKBENCH_API_VERSION: () => 1,
1863
2020
  apiGet: () => apiGet,
1864
2021
  apiPost: () => apiPost,
1865
2022
  blockerActionLabel: () => blockerActionLabel,
@@ -1869,6 +2026,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
1869
2026
  buildMarker: () => buildMarker,
1870
2027
  createFactChannel: () => createFactChannel,
1871
2028
  cx: () => cx,
2029
+ declarationProblemsOf: () => declarationProblemsOf,
1872
2030
  decodeRef: () => decodeRef,
1873
2031
  dictionaries: () => dictionaries,
1874
2032
  en: () => en,
@@ -1892,6 +2050,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
1892
2050
  parseMarkers: () => parseMarkers,
1893
2051
  probeKeyOf: () => probeKeyOf,
1894
2052
  quoteShellArg: () => quoteShellArg,
2053
+ readDeclaration: () => readDeclaration,
1895
2054
  readFlag: () => readFlag,
1896
2055
  readText: () => readText,
1897
2056
  relativeTime: () => relativeTime,
@@ -2004,6 +2163,7 @@ function createAdaptiveTransport() {
2004
2163
  //#endregion
2005
2164
  //#region src/shared/hooks/use-facts.ts
2006
2165
  var use_facts_exports = /* @__PURE__ */ __exportAll({
2166
+ subscribeFactRevision: () => subscribeFactRevision,
2007
2167
  useFactInvalidate: () => useFactInvalidate,
2008
2168
  useFactRevision: () => useFactRevision
2009
2169
  });
@@ -2011,6 +2171,9 @@ var channel = createFactChannel({ transport: createAdaptiveTransport() });
2011
2171
  function useFactRevision(fact) {
2012
2172
  return useSyncExternalStore(useCallback((listener) => channel.subscribe(fact, listener), [fact]), useCallback(() => channel.revisionOf(fact), [fact]));
2013
2173
  }
2174
+ function subscribeFactRevision(fact, listener) {
2175
+ return channel.subscribe(fact, listener);
2176
+ }
2014
2177
  function useFactInvalidate() {
2015
2178
  return useCallback((fact) => channel.refresh(fact), []);
2016
2179
  }
@@ -2238,6 +2401,9 @@ function panelRowName(label, hint, t) {
2238
2401
  //#endregion
2239
2402
  //#region src/app/layouts/rightbar/market-freshness.ts
2240
2403
  var MARKET_STALE_MS = VERSION_CHECK_TTL_MS;
2404
+ function marketSettled(freshness, installedRead) {
2405
+ return freshness.loaded && installedRead !== false;
2406
+ }
2241
2407
  function shouldProbeOnEnter(input) {
2242
2408
  if (input.checkedAt === null) return true;
2243
2409
  return input.now - input.checkedAt >= input.staleMs;
@@ -2535,8 +2701,7 @@ function buildMarketMocks(roster) {
2535
2701
  ...mine,
2536
2702
  card: {
2537
2703
  titleKey: "一个名字特别特别长的模块用来验证省略号会不会把按钮挤下去",
2538
- order: 10,
2539
- enabledByDefault: true
2704
+ order: 10
2540
2705
  }
2541
2706
  }],
2542
2707
  capabilities: probe$1(id, "ok"),
@@ -2640,6 +2805,24 @@ function buildMarketMocks(roster) {
2640
2805
  installedActions: ["update-bundle", "remove-bundle"]
2641
2806
  }
2642
2807
  },
2808
+ {
2809
+ id: "installed-client-refused",
2810
+ label: "本机装了,但浏览器这半没挂上(拒因由客户端报告)",
2811
+ manifests: [found],
2812
+ capabilities: {},
2813
+ installed: [{
2814
+ unit: id,
2815
+ pkg: "@dsh-workbench/module-one",
2816
+ installed: "0.1.2",
2817
+ clientRefused: [`宿主没有担保 unit「${id}」的清单 —— 这个包要么没装进这份 profile,要么它的 dsh.workbench 没通过校验。卡片先不挂:清单只有一处真相(ADR-0038 §2.2)`]
2818
+ }],
2819
+ expect: {
2820
+ rows: 0,
2821
+ installed: 1,
2822
+ installedActions: ["remove-bundle"],
2823
+ installedNote: true
2824
+ }
2825
+ },
2643
2826
  {
2644
2827
  id: "installed-confirm",
2645
2828
  label: "卸载本机模块(Modal 确认,标题带模块名)",
@@ -2688,6 +2871,94 @@ function buildMarketMocks(roster) {
2688
2871
  actions: ["uninstall"]
2689
2872
  }
2690
2873
  },
2874
+ {
2875
+ id: "plugin-outdated",
2876
+ label: "用户装的 opencli 插件:有新版(给更新 / 卸载)",
2877
+ manifests: [],
2878
+ capabilities: {},
2879
+ plugins: [{
2880
+ pkg: "opencli-plugin-cmdb",
2881
+ name: "cmdb",
2882
+ installed: "1.0.24",
2883
+ latest: "1.0.25"
2884
+ }],
2885
+ expect: {
2886
+ rows: 0,
2887
+ plugins: 1,
2888
+ pluginActions: ["update-plugin", "uninstall-plugin"]
2889
+ }
2890
+ },
2891
+ {
2892
+ id: "plugin-local",
2893
+ label: "本地目录装的 opencli 插件:没有版本源,只给卸载",
2894
+ manifests: [],
2895
+ capabilities: {},
2896
+ plugins: [{
2897
+ pkg: "/Users/me/my-local-plugin",
2898
+ name: "my-local-plugin",
2899
+ local: true
2900
+ }],
2901
+ expect: {
2902
+ rows: 0,
2903
+ plugins: 1,
2904
+ pluginActions: ["uninstall-plugin"],
2905
+ pluginNote: false
2906
+ }
2907
+ },
2908
+ {
2909
+ id: "plugin-alias",
2910
+ label: "插件名 ≠ 包名:行上两个都写出来(更新 / 卸载按包名)",
2911
+ manifests: [],
2912
+ capabilities: {},
2913
+ plugins: [{
2914
+ pkg: "opencli-plugin-cmdb",
2915
+ name: "cmdb",
2916
+ installed: "1.0.24",
2917
+ latest: "1.0.25"
2918
+ }],
2919
+ expect: {
2920
+ rows: 0,
2921
+ plugins: 1,
2922
+ pluginActions: ["update-plugin", "uninstall-plugin"],
2923
+ pluginNoteKey: "panels.market.pluginAlsoKnownAs"
2924
+ }
2925
+ },
2926
+ {
2927
+ id: "plugin-broken",
2928
+ label: "装坏了的那一份(目录在但看不出是个插件):行上直说,给卸载与指路",
2929
+ manifests: [],
2930
+ capabilities: {},
2931
+ plugins: [{
2932
+ pkg: "opencli-plugin-cmdb",
2933
+ name: "cmdb",
2934
+ installed: "1.0.24",
2935
+ broken: true
2936
+ }],
2937
+ expect: {
2938
+ rows: 0,
2939
+ plugins: 1,
2940
+ pluginActions: ["uninstall-plugin"],
2941
+ pluginNoteKey: "panels.market.pluginBroken"
2942
+ }
2943
+ },
2944
+ {
2945
+ id: "plugin-kept",
2946
+ label: "卸掉用户装的插件,但还有模块在用:文件先留着(行上给一句为什么)",
2947
+ manifests: [],
2948
+ capabilities: {},
2949
+ plugins: [{
2950
+ pkg: "opencli-plugin-cmdb",
2951
+ name: "cmdb",
2952
+ installed: "1.0.24"
2953
+ }],
2954
+ notice: "文件先留着:cmdb、argocd 还要用它。",
2955
+ expect: {
2956
+ rows: 0,
2957
+ plugins: 1,
2958
+ pluginActions: ["uninstall-plugin"],
2959
+ notice: true
2960
+ }
2961
+ },
2691
2962
  {
2692
2963
  id: "empty",
2693
2964
  label: "市场空态",
@@ -2745,7 +3016,6 @@ function createBoardRegistry() {
2745
3016
  };
2746
3017
  },
2747
3018
  all: () => sorted,
2748
- visible: () => sorted.filter((module) => module.enabledByDefault),
2749
3019
  byId: (id) => index.get(id),
2750
3020
  stepOwner: (kind) => steps.get(kind),
2751
3021
  subscribe(listener) {
@@ -2757,20 +3027,21 @@ function createBoardRegistry() {
2757
3027
  }
2758
3028
  //#endregion
2759
3029
  //#region src/app/units/registry.ts
2760
- function toBoardModule(face) {
3030
+ function toBoardModule(face, manifest) {
2761
3031
  return {
2762
- id: face.manifest.id,
2763
- titleKey: face.manifest.card?.titleKey ?? face.manifest.id,
2764
- order: face.manifest.card?.order ?? 100,
2765
- enabledByDefault: face.manifest.card?.enabledByDefault ?? false,
3032
+ id: manifest.id,
3033
+ titleKey: manifest.card?.titleKey ?? manifest.id,
3034
+ order: manifest.card?.order ?? 100,
2766
3035
  View: face.card,
2767
3036
  ...face.steps === void 0 ? {} : { steps: face.steps }
2768
3037
  };
2769
3038
  }
2770
- function createClientUnitRegistry() {
2771
- const faces = [];
3039
+ function createClientUnitRegistry(options = {}) {
3040
+ const registrations = [];
3041
+ const refusals = [];
2772
3042
  const listeners = /* @__PURE__ */ new Set();
2773
3043
  const board = createBoardRegistry();
3044
+ let known = /* @__PURE__ */ new Map();
2774
3045
  let revision = 0;
2775
3046
  let manifestsCache;
2776
3047
  function notify() {
@@ -2781,28 +3052,86 @@ function createClientUnitRegistry() {
2781
3052
  console.warn(`[dsh-workbench] 套件注册的订阅者报错(不影响其它订阅者与这次注册):${String(error)}`);
2782
3053
  }
2783
3054
  }
3055
+ function warnRefusal(unit, reasons) {
3056
+ refusals.push({
3057
+ unit,
3058
+ reasons
3059
+ });
3060
+ console.warn(`[dsh-workbench] ${unit === "" ? "这个模块" : unit} 的卡片没挂上:${reasons.join(";")}`);
3061
+ }
3062
+ function takeBackRefusal(unit) {
3063
+ for (let at = refusals.length - 1; at >= 0; at -= 1) if (refusals[at]?.unit === unit) refusals.splice(at, 1);
3064
+ }
3065
+ function attach(entry) {
3066
+ const manifest = known.get(entry.unit);
3067
+ if (manifest === void 0) return false;
3068
+ entry.mounted = {
3069
+ manifest,
3070
+ unmount: board.register(toBoardModule(entry.face, manifest))
3071
+ };
3072
+ takeBackRefusal(entry.unit);
3073
+ return true;
3074
+ }
3075
+ function detach(entry) {
3076
+ entry.mounted?.unmount();
3077
+ entry.mounted = null;
3078
+ }
2784
3079
  return {
2785
3080
  register(face) {
2786
- const id = face.manifest.id;
2787
- if (faces.some((item) => item.manifest.id === id)) throw new Error(`套件 id 重复:${id}(id 会进持久化配置,不能撞)`);
2788
- const unregisterBoard = board.register(toBoardModule(face));
2789
- faces.push(face);
3081
+ const unit = typeof face?.unit === "string" ? face.unit.trim() : "";
3082
+ if (unit === "") {
3083
+ warnRefusal("", ["registerUnit({ unit, card }) 里的 unit 是空的 —— U1b 之前的旧形状是 registerUnit({ manifest, card });现在清单由外壳从**已装清单**读出来、按 unit 交给浏览器(ADR-0038 §2.2)"]);
3084
+ notify();
3085
+ return () => {};
3086
+ }
3087
+ if (registrations.some((entry) => entry.unit === unit)) throw new Error(`套件 id 重复:${unit}(id 会进持久化配置,不能撞)`);
3088
+ const entry = {
3089
+ face,
3090
+ unit,
3091
+ mounted: null
3092
+ };
3093
+ registrations.push(entry);
2790
3094
  manifestsCache = void 0;
3095
+ if (!attach(entry)) {
3096
+ warnRefusal(unit, [`宿主没有担保 unit「${unit}」的清单 —— 这个包要么没装进这份 profile,要么它的 dsh.workbench 没通过校验。卡片先不挂:清单只有一处真相(ADR-0038 §2.2)`]);
3097
+ options.onUnknownUnit?.(unit);
3098
+ }
2791
3099
  notify();
2792
3100
  return () => {
2793
- const at = faces.findIndex((item) => item.manifest.id === id);
3101
+ const at = registrations.indexOf(entry);
2794
3102
  if (at < 0) return;
2795
- faces.splice(at, 1);
3103
+ registrations.splice(at, 1);
3104
+ detach(entry);
3105
+ takeBackRefusal(unit);
2796
3106
  manifestsCache = void 0;
2797
- unregisterBoard();
2798
3107
  notify();
2799
3108
  };
2800
3109
  },
2801
- all: () => faces,
3110
+ all: () => registrations.map((entry) => entry.face),
2802
3111
  manifests: () => {
2803
- manifestsCache ??= faces.map((face) => face.manifest);
3112
+ manifestsCache ??= registrations.flatMap((entry) => entry.mounted === null ? [] : [entry.mounted.manifest]);
2804
3113
  return manifestsCache;
2805
3114
  },
3115
+ refusals: () => refusals,
3116
+ setManifests(manifests) {
3117
+ known = new Map(manifests.map((manifest) => [manifest.id, manifest]));
3118
+ let changed = false;
3119
+ for (const entry of registrations) {
3120
+ const next = known.get(entry.unit);
3121
+ if (entry.mounted === null) {
3122
+ if (next !== void 0 && attach(entry)) changed = true;
3123
+ continue;
3124
+ }
3125
+ if (next === void 0) {
3126
+ detach(entry);
3127
+ warnRefusal(entry.unit, [`宿主不再担保 unit「${entry.unit}」的清单(模块被卸了?)—— 这张卡片先摘下来`]);
3128
+ changed = true;
3129
+ }
3130
+ }
3131
+ if (!changed) return;
3132
+ manifestsCache = void 0;
3133
+ notify();
3134
+ },
2806
3135
  board,
2807
3136
  subscribe(listener) {
2808
3137
  listeners.add(listener);
@@ -2837,11 +3166,9 @@ var messages = {
2837
3166
  };
2838
3167
  var manifest = {
2839
3168
  id: FIXTURE_UNIT_ID,
2840
- kind: "board",
2841
3169
  card: {
2842
3170
  titleKey: KEY.cardTitle,
2843
- order: 900,
2844
- enabledByDefault: true
3171
+ order: 900
2845
3172
  },
2846
3173
  tools: [],
2847
3174
  host: { inject: [] }
@@ -2864,6 +3191,7 @@ function FixtureCard({ t, onRefresh }) {
2864
3191
  }))));
2865
3192
  }
2866
3193
  var FIXTURE_FACE = {
3194
+ unit: FIXTURE_UNIT_ID,
2867
3195
  manifest,
2868
3196
  messages,
2869
3197
  card: FixtureCard
@@ -2884,11 +3212,9 @@ var crashMessages = {
2884
3212
  };
2885
3213
  var crashManifest = {
2886
3214
  id: CRASH_UNIT_ID,
2887
- kind: "board",
2888
3215
  card: {
2889
3216
  titleKey: CRASH_KEY.cardTitle,
2890
- order: 899,
2891
- enabledByDefault: true
3217
+ order: 899
2892
3218
  },
2893
3219
  tools: [],
2894
3220
  host: { inject: [] }
@@ -2897,6 +3223,7 @@ function CrashingCard() {
2897
3223
  throw new Error("夹具:这张卡片按 wbCrash=1 故意渲染失败(守「一张卡片崩溃不带走面板」)");
2898
3224
  }
2899
3225
  var CRASH_FACE = {
3226
+ unit: CRASH_UNIT_ID,
2900
3227
  manifest: crashManifest,
2901
3228
  messages: crashMessages,
2902
3229
  card: CrashingCard
@@ -2909,8 +3236,8 @@ function fixtureRoster(roster) {
2909
3236
  return hasCard(roster) ? roster : SAMPLE_ROSTER;
2910
3237
  }
2911
3238
  function fixtureSeed(roster, all, crash = false) {
2912
- const base = hasCard(roster) ? all : [toBoardModule(FIXTURE_FACE)];
2913
- return crash ? [toBoardModule(CRASH_FACE), ...base] : base;
3239
+ const base = hasCard(roster) ? all : [toBoardModule(FIXTURE_FACE, FIXTURE_FACE.manifest)];
3240
+ return crash ? [toBoardModule(CRASH_FACE, CRASH_FACE.manifest), ...base] : base;
2914
3241
  }
2915
3242
  //#endregion
2916
3243
  //#region src/app/layouts/rightbar/market-rows.ts
@@ -2930,6 +3257,25 @@ var MARKET_DOT = {
2930
3257
  unchecked: "idle",
2931
3258
  unknown: "idle"
2932
3259
  };
3260
+ function marketRowTitle(row, t) {
3261
+ return row.label ?? t(row.titleKey);
3262
+ }
3263
+ function withClientRefusals(modules, refusals) {
3264
+ if (refusals.length === 0) return modules;
3265
+ const byUnit = new Map(refusals.map((one) => [one.unit, one.reasons]));
3266
+ return modules.map((module) => {
3267
+ const reasons = byUnit.get(module.unit);
3268
+ return reasons === void 0 ? module : {
3269
+ ...module,
3270
+ clientRefused: reasons
3271
+ };
3272
+ });
3273
+ }
3274
+ function refusalReasons(host, client) {
3275
+ const reported = host ?? [];
3276
+ if (reported.length > 0) return reported;
3277
+ return [...new Set(client ?? [])];
3278
+ }
2933
3279
  function mergeTitles(known, manifests) {
2934
3280
  let next = null;
2935
3281
  for (const manifest of manifests) {
@@ -2948,11 +3294,16 @@ function installedRows(modules, manifests, listed, known) {
2948
3294
  const latest = module.latest;
2949
3295
  const outdated = latest !== void 0 && latest !== module.installed;
2950
3296
  const loaded = titles.has(module.unit);
3297
+ const refused = refusalReasons(module.refused, module.clientRefused);
2951
3298
  rows.push({
2952
3299
  id: module.unit,
2953
3300
  titleKey: titles.get(module.unit) ?? known?.get(module.unit) ?? module.pkg,
2954
3301
  state: outdated ? "outdated" : "installed",
2955
- ...loaded ? {} : { hint: "panels.market.moduleRestartHint" },
3302
+ ...loaded || refused.length > 0 ? {} : { hint: "panels.market.moduleRestartHint" },
3303
+ ...refused.length === 0 ? {} : { note: {
3304
+ key: "panels.market.moduleRefused",
3305
+ params: { reason: refused.join(";") }
3306
+ } },
2956
3307
  ...module.local === true ? { local: true } : {},
2957
3308
  pkg: module.pkg,
2958
3309
  canInstall: false,
@@ -2971,6 +3322,7 @@ function installedRows(modules, manifests, listed, known) {
2971
3322
  return rows;
2972
3323
  }
2973
3324
  function marketActions$1(row) {
3325
+ if (row.plugin === true) return [...row.canUpdate ? ["update-plugin"] : [], ...row.canUninstall ? ["uninstall-plugin"] : []];
2974
3326
  return [
2975
3327
  ...row.canInstall ? ["install"] : [],
2976
3328
  ...row.moduleUpdate === void 0 ? row.canUpdate ? ["update"] : [] : ["update-bundle"],
@@ -2994,7 +3346,7 @@ function envNameOf(source) {
2994
3346
  if (source.kind === "npm") return source.registryEnv;
2995
3347
  return "";
2996
3348
  }
2997
- function noteOf(state, install, manifest) {
3349
+ function noteOf$1(state, install, manifest) {
2998
3350
  if (state === "installed") return void 0;
2999
3351
  if (install === "env-missing") return {
3000
3352
  key: "panels.market.needEnv",
@@ -3053,7 +3405,7 @@ function marketRows(manifests, capabilities, updates = {}) {
3053
3405
  const capability = capabilities?.[manifest.id];
3054
3406
  const install = capability?.install;
3055
3407
  const state = capabilities === null ? "checking" : stateOf(capability);
3056
- const note = noteOf(state, install, manifest);
3408
+ const note = noteOf$1(state, install, manifest);
3057
3409
  const onMachine = isOnMachine(state);
3058
3410
  const reAdopt = capability?.released === true && install === void 0;
3059
3411
  const entry = updates[manifest.id];
@@ -3225,6 +3577,60 @@ function useHostWrite(options) {
3225
3577
  };
3226
3578
  }
3227
3579
  //#endregion
3580
+ //#region src/app/layouts/rightbar/market-plugin-rows.ts
3581
+ function installedPluginKept(value) {
3582
+ if (typeof value !== "object" || value === null) return void 0;
3583
+ const kept = value.kept;
3584
+ if (typeof kept !== "object" || kept === null) return void 0;
3585
+ const kind = kept.kind;
3586
+ if (kind === "held") return { key: "panels.market.pluginKeptHeld" };
3587
+ if (kind === "unnamed") return { key: "panels.market.pluginKeptUnnamed" };
3588
+ if (kind === "unsure") return { key: "panels.market.pluginKeptUnsure" };
3589
+ if (kind !== "claimed") return void 0;
3590
+ const raw = kept.by;
3591
+ const names = Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : [];
3592
+ return names.length === 0 ? { key: "panels.market.pluginKeptUnsure" } : {
3593
+ key: "panels.market.pluginKeptInUse",
3594
+ names
3595
+ };
3596
+ }
3597
+ function noteOf(plugin) {
3598
+ if (plugin.broken === true) return {
3599
+ key: "panels.market.pluginBroken",
3600
+ params: {}
3601
+ };
3602
+ if (plugin.local === true) return void 0;
3603
+ if (plugin.name !== void 0 && plugin.name !== plugin.pkg) return {
3604
+ key: "panels.market.pluginAlsoKnownAs",
3605
+ params: { pkg: plugin.pkg }
3606
+ };
3607
+ }
3608
+ function installedPluginRows(plugins) {
3609
+ return plugins.map((plugin) => {
3610
+ const name = plugin.name ?? plugin.pkg;
3611
+ const outdated = plugin.installed !== void 0 && plugin.latest !== void 0 && plugin.latest !== plugin.installed;
3612
+ const note = noteOf(plugin);
3613
+ return {
3614
+ id: `plugin:${plugin.pkg}`,
3615
+ titleKey: plugin.pkg,
3616
+ label: name,
3617
+ plugin: true,
3618
+ state: outdated ? "outdated" : "installed",
3619
+ canInstall: false,
3620
+ canUpdate: outdated,
3621
+ canUninstall: true,
3622
+ ...plugin.local === true ? { local: true } : {},
3623
+ ...plugin.broken === true ? { broken: true } : {},
3624
+ ...note === void 0 ? {} : { note },
3625
+ pkg: plugin.pkg,
3626
+ ...plugin.installed === void 0 ? {} : { version: {
3627
+ installed: plugin.installed,
3628
+ ...plugin.latest === void 0 ? {} : { latest: plugin.latest }
3629
+ } }
3630
+ };
3631
+ });
3632
+ }
3633
+ //#endregion
3228
3634
  //#region src/app/layouts/market/add-module-text.ts
3229
3635
  var GUIDE_ITEMS = [
3230
3636
  {
@@ -3343,17 +3749,23 @@ var PROBLEM_KEYS = {
3343
3749
  "not-found": "panels.market.add.problemNotFound",
3344
3750
  "not-a-package": "panels.market.add.problemNotPackage",
3345
3751
  "not-a-bundle": "panels.market.add.problemNotBundle",
3752
+ "not-a-workbench-module": "panels.market.add.problemNotWorkbenchModule",
3753
+ "not-a-module-or-plugin": "panels.market.add.problemNotModuleOrPlugin",
3346
3754
  network: "panels.market.add.problemNetwork",
3347
3755
  unknown: "panels.market.add.problemUnknown"
3348
3756
  };
3349
3757
  function problemKey(problem) {
3350
3758
  return PROBLEM_KEYS[problem] ?? PROBLEM_KEYS.unknown;
3351
3759
  }
3760
+ function heldWizardPhase(phase, classifying) {
3761
+ return classifying && (phase === "preview" || phase === "idle") ? "inspecting" : phase;
3762
+ }
3352
3763
  var FAILURE_KEYS = {
3353
3764
  network: "panels.market.add.failureNetwork",
3354
3765
  "spec-host": "panels.market.add.failureNetworkHost",
3355
3766
  "not-found": "panels.market.add.failureNotFound",
3356
3767
  "no-matching-version": "panels.market.add.failureNoMatchingVersion",
3768
+ "not-a-module-or-plugin": "panels.market.add.failureNotModuleOrPlugin",
3357
3769
  "disk-full": "panels.market.add.failureDiskFull",
3358
3770
  permission: "panels.market.add.failurePermission",
3359
3771
  "build-blocked": "panels.market.add.failureBuildBlocked",
@@ -3458,7 +3870,9 @@ var PROGRESS_KEY = {
3458
3870
  "update-bundle": "panels.market.updating",
3459
3871
  "remove-bundle": "panels.market.uninstalling",
3460
3872
  uninstall: "panels.market.uninstalling",
3461
- "update-dependency": "panels.market.updating"
3873
+ "update-dependency": "panels.market.updating",
3874
+ "update-plugin": "panels.market.updating",
3875
+ "uninstall-plugin": "panels.market.uninstalling"
3462
3876
  };
3463
3877
  var ACTION_LABEL_KEY = {
3464
3878
  install: "panels.market.install",
@@ -3466,7 +3880,9 @@ var ACTION_LABEL_KEY = {
3466
3880
  "update-bundle": "panels.market.update",
3467
3881
  "remove-bundle": "panels.market.uninstall",
3468
3882
  uninstall: "panels.market.uninstall",
3469
- "update-dependency": "panels.market.update"
3883
+ "update-dependency": "panels.market.update",
3884
+ "update-plugin": "panels.market.update",
3885
+ "uninstall-plugin": "panels.market.uninstall"
3470
3886
  };
3471
3887
  //#endregion
3472
3888
  //#region src/app/layouts/rightbar/SharedSection.tsx
@@ -3528,7 +3944,11 @@ function PanelFoot({ text, size = "sm", children }) {
3528
3944
  }
3529
3945
  //#endregion
3530
3946
  //#region src/app/layouts/rightbar/MarketView.tsx
3531
- var CONFIRM_UNINSTALL = ["uninstall", "remove-bundle"];
3947
+ var CONFIRM_UNINSTALL = [
3948
+ "uninstall",
3949
+ "remove-bundle",
3950
+ "uninstall-plugin"
3951
+ ];
3532
3952
  var CONFIRM_REMOVE = ["remove-bundle"];
3533
3953
  function MarketLine({ t, row, pending, failure, onAct }) {
3534
3954
  const running = pending !== null && pending.id === row.id ? pending : null;
@@ -3538,7 +3958,7 @@ function MarketLine({ t, row, pending, failure, onAct }) {
3538
3958
  return /* @__PURE__ */ h("li", { className: MarketPage_module_default.marketRow }, /* @__PURE__ */ h("span", {
3539
3959
  className: MarketPage_module_default.marketIcon,
3540
3960
  "aria-hidden": "true"
3541
- }, /* @__PURE__ */ h(IconModules, { size: 18 })), /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketMain }, /* @__PURE__ */ h("span", { className: MarketPage_module_default.marketName }, t(row.titleKey), row.version === void 0 ? null : /* @__PURE__ */ h("span", { className: MarketPage_module_default.marketVersion }, row.version.installed !== void 0 && row.version.latest !== void 0 && row.version.installed !== row.version.latest ? t("panels.market.versionArrow", {
3961
+ }, /* @__PURE__ */ h(IconModules, { size: 18 })), /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketMain }, /* @__PURE__ */ h("span", { className: MarketPage_module_default.marketName }, marketRowTitle(row, t), row.version === void 0 ? null : /* @__PURE__ */ h("span", { className: MarketPage_module_default.marketVersion }, row.version.installed !== void 0 && row.version.latest !== void 0 && row.version.installed !== row.version.latest ? t("panels.market.versionArrow", {
3542
3962
  from: row.version.installed,
3543
3963
  to: row.version.latest
3544
3964
  }) : row.version.installed ?? row.version.latest ?? "")), /* @__PURE__ */ h("span", {
@@ -3563,11 +3983,14 @@ function MarketLine({ t, row, pending, failure, onAct }) {
3563
3983
  variant: action === "install" ? "primary" : "outline",
3564
3984
  className: danger ? "wb-dangerAction" : void 0,
3565
3985
  icon: danger ? /* @__PURE__ */ h(IconTrashOutlineRegular, { size: 13 }) : void 0,
3566
- "aria-label": danger ? t("panels.market.uninstallLabel", { name: t(row.titleKey) }) : void 0,
3986
+ "aria-label": danger ? t("panels.market.uninstallLabel", { name: marketRowTitle(row, t) }) : void 0,
3567
3987
  disabled: locked,
3568
3988
  onClick: () => onAct(action)
3569
3989
  }, t(ACTION_LABEL_KEY[action]));
3570
- }))), row.note === void 0 ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketNote }, t(row.note.key, row.note.params)), failure === null ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketFailure }, failure));
3990
+ }))), row.note === void 0 ? null : /* @__PURE__ */ h("div", {
3991
+ className: MarketPage_module_default.marketNote,
3992
+ title: row.note.params.reason === void 0 ? void 0 : t("panels.market.moduleRefusedDetail", row.note.params)
3993
+ }, t(row.note.key, row.note.params)), failure === null ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketFailure }, failure));
3571
3994
  }
3572
3995
  function ConfirmUninstall({ t, name, onConfirm, onCancel }) {
3573
3996
  const [acknowledged, setAcknowledged] = useState(false);
@@ -3596,7 +4019,48 @@ function MarketSection({ t, titleKey, rows, pending, failureOf, act, confirmActi
3596
4019
  onAct: (action) => confirmActions.includes(action) ? onConfirming(row.id) : void act(row.id, action)
3597
4020
  }))));
3598
4021
  }
3599
- function MarketView({ t, manifests, capabilities, freshness, onChanged, updates, installed, installedRead, settling, mock }) {
4022
+ function MarketBody({ t, shown, pluginList, installedList, shared, pending, failed, notice, act, onConfirming }) {
4023
+ const failureOf = (id) => failed?.id === id ? failed.message : null;
4024
+ return /* @__PURE__ */ h(Fragment, null, notice === null ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketNote }, notice), marketSections(shown).map((section) => /* @__PURE__ */ h(MarketSection, {
4025
+ key: section.id,
4026
+ t,
4027
+ titleKey: section.titleKey,
4028
+ rows: section.rows,
4029
+ pending,
4030
+ failureOf,
4031
+ act,
4032
+ confirmActions: CONFIRM_UNINSTALL,
4033
+ onConfirming
4034
+ })), /* @__PURE__ */ h(MarketSection, {
4035
+ t,
4036
+ titleKey: "panels.market.sectionPlugins",
4037
+ rows: pluginList,
4038
+ pending,
4039
+ failureOf,
4040
+ act,
4041
+ confirmActions: CONFIRM_UNINSTALL,
4042
+ onConfirming
4043
+ }), /* @__PURE__ */ h(MarketSection, {
4044
+ t,
4045
+ titleKey: installedList.length > 0 && installedList.every((row) => row.local === true) ? "panels.market.sectionLocal" : "panels.market.sectionUnlisted",
4046
+ rows: installedList,
4047
+ pending,
4048
+ failureOf,
4049
+ act,
4050
+ confirmActions: CONFIRM_REMOVE,
4051
+ onConfirming
4052
+ }), /* @__PURE__ */ h(PanelFoot, {
4053
+ size: "md",
4054
+ text: t("panels.market.foot")
4055
+ }), /* @__PURE__ */ h(SharedSection, {
4056
+ t,
4057
+ rows: shared,
4058
+ pending,
4059
+ failureOf,
4060
+ act
4061
+ }));
4062
+ }
4063
+ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates, installed, installedRead, plugins, settling, mock }) {
3600
4064
  const [titles, setTitles] = useState(/* @__PURE__ */ new Map());
3601
4065
  useEffect(() => {
3602
4066
  if (mock === void 0) setTitles((known) => mergeTitles(known, manifests));
@@ -3613,6 +4077,7 @@ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates,
3613
4077
  });
3614
4078
  const [confirming, setConfirming] = useState(mock?.confirming ?? null);
3615
4079
  const [failed, setFailed] = useState(mock?.failure ?? null);
4080
+ const [notice, setNotice] = useState(mock?.notice ?? null);
3616
4081
  const removing = pending !== null && pending.action === "remove-bundle" && failed?.id !== pending.id ? pending.id : null;
3617
4082
  const installedList = installedRows((mock?.installed ?? installed ?? []).filter((module) => !(settling?.has(module.unit) ?? false) && module.unit !== removing), catalog, new Set(rows.map((row) => row.id)), titles);
3618
4083
  const pendingRow = removing === null || pending === null || rows.some((row) => row.id === removing) ? void 0 : {
@@ -3626,7 +4091,12 @@ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates,
3626
4091
  ...pending.version === void 0 ? {} : { version: { installed: pending.version } }
3627
4092
  };
3628
4093
  const shown = pendingRow === void 0 ? rows : [...rows, pendingRow];
3629
- const rowById = new Map([...shown, ...installedList].map((row) => [row.id, row]));
4094
+ const pluginList = installedPluginRows(mock?.plugins ?? plugins ?? []);
4095
+ const rowById = new Map([
4096
+ ...shown,
4097
+ ...installedList,
4098
+ ...pluginList
4099
+ ].map((row) => [row.id, row]));
3630
4100
  const confirmingRow = confirming === null ? void 0 : rowById.get(confirming);
3631
4101
  const act = async (id, action) => {
3632
4102
  if (mock !== void 0) return;
@@ -3640,13 +4110,15 @@ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates,
3640
4110
  });
3641
4111
  setConfirming(null);
3642
4112
  setFailed(null);
4113
+ if (action !== "uninstall-plugin") setNotice(null);
3643
4114
  const dependency = action === "update-dependency";
4115
+ const byName = action === "remove-bundle" || action === "update-plugin" || action === "uninstall-plugin";
3644
4116
  const outcome = await provision.send(dependency ? {
3645
4117
  action,
3646
4118
  id
3647
- } : action === "remove-bundle" ? {
4119
+ } : byName ? {
3648
4120
  action,
3649
- name: rowById.get(id)?.pkg ?? ""
4121
+ name: row?.pkg ?? ""
3650
4122
  } : {
3651
4123
  action,
3652
4124
  unit: id
@@ -3660,6 +4132,10 @@ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates,
3660
4132
  });
3661
4133
  return;
3662
4134
  }
4135
+ if (action === "uninstall-plugin") {
4136
+ const kept = installedPluginKept(outcome.value);
4137
+ setNotice(kept === void 0 ? null : t(kept.key, kept.names === void 0 ? {} : { names: kept.names.join(t("panels.market.listSeparator")) }));
4138
+ }
3663
4139
  const reason = changeFailure(changeOf(outcome.value), t);
3664
4140
  onChanged();
3665
4141
  if (reason !== void 0) setFailed({
@@ -3667,46 +4143,32 @@ function MarketView({ t, manifests, capabilities, freshness, onChanged, updates,
3667
4143
  message: t("panels.market.failed", { reason })
3668
4144
  });
3669
4145
  };
3670
- if (!(freshness.loaded && installedRead !== false) || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ h(MarketChecking, { t });
3671
- if (shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ h(MarketEmpty, { t });
3672
- return /* @__PURE__ */ h(Fragment, null, marketSections(shown).map((section) => /* @__PURE__ */ h(MarketSection, {
3673
- key: section.id,
4146
+ const settled = marketSettled(freshness, installedRead);
4147
+ const nothing = shown.length === 0 && installedList.length === 0 && pluginList.length === 0;
4148
+ if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ h(MarketChecking, { t });
4149
+ if (nothing) return /* @__PURE__ */ h(MarketEmpty, { t });
4150
+ return /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h(MarketBody, {
3674
4151
  t,
3675
- titleKey: section.titleKey,
3676
- rows: section.rows,
3677
- pending,
3678
- failureOf: (id) => failed?.id === id ? failed.message : null,
3679
- act,
3680
- confirmActions: CONFIRM_UNINSTALL,
3681
- onConfirming: setConfirming
3682
- })), /* @__PURE__ */ h(MarketSection, {
3683
- t,
3684
- titleKey: installedList.length > 0 && installedList.every((row) => row.local === true) ? "panels.market.sectionLocal" : "panels.market.sectionUnlisted",
3685
- rows: installedList,
4152
+ shown,
4153
+ pluginList,
4154
+ installedList,
4155
+ shared,
3686
4156
  pending,
3687
- failureOf: (id) => failed?.id === id ? failed.message : null,
4157
+ failed,
4158
+ notice,
3688
4159
  act,
3689
- confirmActions: CONFIRM_REMOVE,
3690
4160
  onConfirming: setConfirming
3691
- }), /* @__PURE__ */ h(PanelFoot, {
3692
- size: "md",
3693
- text: t("panels.market.foot")
3694
- }), /* @__PURE__ */ h(SharedSection, {
3695
- t,
3696
- rows: shared,
3697
- pending,
3698
- failureOf: (id) => failed?.id === id ? failed.message : null,
3699
- act
3700
4161
  }), /* @__PURE__ */ h(ConfirmUninstall, {
3701
4162
  key: confirming ?? "",
3702
4163
  t,
3703
- name: confirmingRow === void 0 ? null : t(confirmingRow.titleKey),
4164
+ name: confirmingRow === void 0 ? null : marketRowTitle(confirmingRow, t),
3704
4165
  onCancel: () => setConfirming(null),
3705
4166
  onConfirm: () => {
3706
4167
  const id = confirming;
3707
4168
  setConfirming(null);
3708
4169
  if (id === null) return;
3709
- act(id, rowById.get(id)?.pkg === void 0 ? "uninstall" : "remove-bundle");
4170
+ const row = rowById.get(id);
4171
+ act(id, row?.plugin === true ? "uninstall-plugin" : row?.pkg === void 0 ? "uninstall" : "remove-bundle");
3710
4172
  }
3711
4173
  }));
3712
4174
  }
@@ -3716,6 +4178,9 @@ var BUNDLE_REGISTRIES_ROUTE = "bundle-registries";
3716
4178
  var BUNDLE_INSPECT_ROUTE = "bundle-inspect";
3717
4179
  var MODULE_VERSIONS_ROUTE = "module-versions";
3718
4180
  //#endregion
4181
+ //#region src/shared/api/installed-plugins.ts
4182
+ var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
4183
+ //#endregion
3719
4184
  //#region src/shared/hooks/bundle-queries.ts
3720
4185
  function readBundleRegistries() {
3721
4186
  return apiGet(BUNDLE_REGISTRIES_ROUTE);
@@ -3729,10 +4194,14 @@ function readBundleInspection(spec, registry) {
3729
4194
  function readModuleVersions(force = false) {
3730
4195
  return apiGet(MODULE_VERSIONS_ROUTE, force ? { force: "1" } : void 0);
3731
4196
  }
4197
+ function readInstalledPlugins(force = false) {
4198
+ return apiGet(INSTALLED_PLUGINS_ROUTE, force ? { force: "1" } : void 0);
4199
+ }
3732
4200
  //#endregion
3733
4201
  //#region src/app/layouts/market/use-module-versions.ts
3734
4202
  var state = {
3735
4203
  modules: null,
4204
+ plugins: null,
3736
4205
  read: false,
3737
4206
  checkedAt: null
3738
4207
  };
@@ -3757,10 +4226,13 @@ function runModuleVersionsRead(force = false) {
3757
4226
  if (inFlight !== null && inFlight.force === force) return inFlight.promise;
3758
4227
  const seq = readSeq += 1;
3759
4228
  if (force) newestForceSeq = seq;
3760
- const promise = readModuleVersions(force).catch(() => null).then((response) => {
4229
+ const promise = Promise.all([readModuleVersions(force), readInstalledPlugins(force)]).catch(() => null).then((responses) => {
3761
4230
  if (!force && seq < newestForceSeq) return;
4231
+ const versions = responses?.[0];
4232
+ const plugins = responses?.[1];
3762
4233
  publish({
3763
- modules: response?.ok === true ? response.modules : state.modules,
4234
+ modules: versions?.ok === true ? versions.modules : state.modules,
4235
+ plugins: plugins?.ok === true ? plugins.plugins : state.plugins,
3764
4236
  read: true,
3765
4237
  checkedAt: Date.now()
3766
4238
  });
@@ -3792,6 +4264,7 @@ function useModuleVersions() {
3792
4264
  }, []);
3793
4265
  return {
3794
4266
  modules: value.modules,
4267
+ plugins: value.plugins,
3795
4268
  read: value.read,
3796
4269
  checkedAt: value.checkedAt,
3797
4270
  reload
@@ -4010,7 +4483,7 @@ function AddModulePending({ t, kind }) {
4010
4483
  })), /* @__PURE__ */ h("h2", {
4011
4484
  className: AddModule_module_default.pendingTitle,
4012
4485
  role: "status"
4013
- }, kind === "inspecting" ? t("panels.market.add.checking") : t("panels.market.add.installingTitle"))));
4486
+ }, kind === "inspecting" ? t("panels.market.add.classifying") : t("panels.market.add.installingTitle"))));
4014
4487
  }
4015
4488
  function AddModulePreview({ t, state }) {
4016
4489
  const preview = state.preview;
@@ -4028,6 +4501,7 @@ function AddModuleResult({ t, state }) {
4028
4501
  const restart = outcome.change.application === "restart-required";
4029
4502
  const cancelled = outcome.change.application === "cancelled";
4030
4503
  const notModule = outcome.supply.state === "not-a-module";
4504
+ const plugin = outcome.plugin;
4031
4505
  const name = outcome.module.kind === "module" ? outcome.module.unit : outcome.module.pkg;
4032
4506
  return /* @__PURE__ */ h("div", { className: AddModule_module_default.body }, /* @__PURE__ */ h("div", { className: AddModule_module_default.hero }, /* @__PURE__ */ h("span", {
4033
4507
  className: AddModule_module_default.heroIcon,
@@ -4039,7 +4513,7 @@ function AddModuleResult({ t, state }) {
4039
4513
  }) : /* @__PURE__ */ h(IconCheckCircleFillRegular, { size: 28 })), /* @__PURE__ */ h("h2", {
4040
4514
  className: AddModule_module_default.wizardTitle,
4041
4515
  role: failed ? "alert" : "status"
4042
- }, failed ? t("panels.market.add.failedTitle") : cancelled ? t("panels.market.add.cancelledTitle") : t("panels.market.add.installedTitle"))), /* @__PURE__ */ h("div", { className: AddModule_module_default.subject }, /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectName }, outcome.module.pkg), name === outcome.module.pkg ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectMeta }, name), outcome.module.version === void 0 ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectMeta }, t("panels.market.add.installVersion", { version: outcome.module.version }))), failed ? /* @__PURE__ */ h("p", {
4516
+ }, failed ? t("panels.market.add.failedTitle") : cancelled ? t("panels.market.add.cancelledTitle") : t("panels.market.add.installedTitle"))), /* @__PURE__ */ h("div", { className: AddModule_module_default.subject }, /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectName }, outcome.module.pkg), name === outcome.module.pkg ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectMeta }, name), outcome.module.version === void 0 ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.subjectMeta }, t("panels.market.add.installVersion", { version: outcome.module.version }))), plugin !== void 0 ? /* @__PURE__ */ h("p", { className: AddModule_module_default.result }, t("panels.market.add.installedPlugin", { name: plugin.name ?? plugin.pkg })) : failed ? /* @__PURE__ */ h("p", {
4043
4517
  className: AddModule_module_default.resultWarn,
4044
4518
  role: "alert"
4045
4519
  }, t("panels.market.add.failed", { reason })) : restart ? /* @__PURE__ */ h("p", { className: AddModule_module_default.resultWarn }, t("panels.market.add.doneRestart")) : cancelled || notModule ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.result }, t("panels.market.add.tookEffect")), builds.length === 0 ? null : /* @__PURE__ */ h("div", { className: AddModule_module_default.approval }, /* @__PURE__ */ h("p", { className: AddModule_module_default.approvalTitle }, t("panels.market.add.approveTitle")), /* @__PURE__ */ h("p", { className: AddModule_module_default.approvalText }, t("panels.market.add.approveHint", { packages: builds.join("、") })), /* @__PURE__ */ h("p", { className: AddModule_module_default.approvalCaution }, t("panels.market.add.approveCaution"))), supply === void 0 || outcome.supply.state !== "failed" && (failed || cancelled) ? null : /* @__PURE__ */ h("p", { className: supplyTone(outcome.supply.state) }, t(supply, { reason: outcome.supply.reason ?? "" })), state.failure === null ? null : /* @__PURE__ */ h("p", { className: AddModule_module_default.problem }, state.failure), outcome.change.errorDetail === void 0 ? null : /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h("p", { className: AddModule_module_default.detailLabel }, t("panels.market.add.details")), /* @__PURE__ */ h("p", { className: AddModule_module_default.detail }, outcome.change.errorDetail)));
@@ -4110,6 +4584,7 @@ var ADD_MODULE_MOCKS = [
4110
4584
  refused("not-found"),
4111
4585
  refused("not-a-package"),
4112
4586
  refused("not-a-bundle"),
4587
+ refused("not-a-module-or-plugin", "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上"),
4113
4588
  refused("network", "getaddrinfo ENOTFOUND registry.example.com"),
4114
4589
  refused("unknown", "pnpm 的返回码是 1"),
4115
4590
  {
@@ -4191,6 +4666,25 @@ var ADD_MODULE_MOCKS = [
4191
4666
  spec: "@dsh-workbench/module-hello",
4192
4667
  outcome: outcome({})
4193
4668
  },
4669
+ {
4670
+ id: "done-plugin",
4671
+ label: "装好了(opencli 插件)",
4672
+ phase: "result",
4673
+ spec: "opencli-plugin-cmdb",
4674
+ outcome: {
4675
+ change: change({ target: "opencli-plugin-cmdb" }),
4676
+ module: {
4677
+ kind: "unknown",
4678
+ pkg: "opencli-plugin-cmdb"
4679
+ },
4680
+ supply: { state: "no-needs" },
4681
+ plugin: {
4682
+ kind: "opencli-plugin",
4683
+ pkg: "opencli-plugin-cmdb",
4684
+ name: "cmdb"
4685
+ }
4686
+ }
4687
+ },
4194
4688
  {
4195
4689
  id: "done-restart",
4196
4690
  label: "装好了(要重启)",
@@ -4285,6 +4779,42 @@ function stateOfMock(base, mock) {
4285
4779
  };
4286
4780
  }
4287
4781
  //#endregion
4782
+ //#region src/shared/hooks/use-visible-busy.ts
4783
+ var use_visible_busy_exports = /* @__PURE__ */ __exportAll({
4784
+ busyView: () => busyView,
4785
+ heldPhase: () => heldPhase,
4786
+ useVisibleBusy: () => useVisibleBusy
4787
+ });
4788
+ function heldPhase(phase, refreshing) {
4789
+ return refreshing && phase === "ready" ? "loading" : phase;
4790
+ }
4791
+ function busyView(active, visible) {
4792
+ return {
4793
+ busy: visible,
4794
+ settling: visible && !active
4795
+ };
4796
+ }
4797
+ function useVisibleBusy(active, floorMs) {
4798
+ const [visible, setVisible] = useState(active);
4799
+ const since = useRef(null);
4800
+ useEffect(() => {
4801
+ if (active) {
4802
+ if (since.current === null) since.current = Date.now();
4803
+ setVisible(true);
4804
+ return;
4805
+ }
4806
+ const held = holdBusy(since.current, Date.now(), floorMs);
4807
+ since.current = null;
4808
+ if (!held.visible) {
4809
+ setVisible(false);
4810
+ return;
4811
+ }
4812
+ const timer = setTimeout(() => setVisible(false), held.releaseInMs);
4813
+ return () => clearTimeout(timer);
4814
+ }, [active, floorMs]);
4815
+ return busyView(active, visible);
4816
+ }
4817
+ //#endregion
4288
4818
  //#region src/app/layouts/market/use-add-module.ts
4289
4819
  function newRequestId() {
4290
4820
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -4307,6 +4837,7 @@ function useAddModule(options) {
4307
4837
  const [failure, setFailure] = useState(null);
4308
4838
  const generationRef = useRef(0);
4309
4839
  const requestIdRef = useRef("");
4840
+ const { busy: classifying } = useVisibleBusy(phase === "inspecting", 500);
4310
4841
  const write = useHostWrite({
4311
4842
  channel: "provision",
4312
4843
  networkText: failedText
@@ -4404,7 +4935,8 @@ function useAddModule(options) {
4404
4935
  setOutcome({
4405
4936
  change: value.change,
4406
4937
  module: value.module,
4407
- supply: value.supply
4938
+ supply: value.supply,
4939
+ ...value.plugin === void 0 ? {} : { plugin: value.plugin }
4408
4940
  });
4409
4941
  setPhase("result");
4410
4942
  if (value.change.application === "applied" || value.change.application === "restart-required") onInstalled();
@@ -4449,7 +4981,7 @@ function useAddModule(options) {
4449
4981
  custom,
4450
4982
  registryOpen,
4451
4983
  guideOpen,
4452
- phase,
4984
+ phase: heldWizardPhase(phase, classifying),
4453
4985
  choices,
4454
4986
  problem,
4455
4987
  preview,
@@ -4607,10 +5139,10 @@ function makeMarketEntryIcon(units) {
4607
5139
  function MarketPanel({ t, units }) {
4608
5140
  const manifests = useLiveManifests(units);
4609
5141
  const { capabilities, checkedAt, checking, forceProbe, reload: reloadCapability } = useCapabilityMap(0);
4610
- const { modules, read: installedRead, reload: reloadVersions } = useModuleVersions();
5142
+ const { modules, plugins, read: installedRead, reload: reloadVersions } = useModuleVersions();
4611
5143
  const [adding, setAdding] = useState(false);
4612
5144
  const updates = useMemo(() => marketUpdatesOf(modules), [modules]);
4613
- const installed = modules ?? void 0;
5145
+ const installed = modules === null ? void 0 : withClientRefusals(modules, units.refusals());
4614
5146
  const reload = useCallback(() => {
4615
5147
  reloadCapability();
4616
5148
  reloadVersions();
@@ -4667,11 +5199,14 @@ function MarketPanel({ t, units }) {
4667
5199
  });
4668
5200
  const mock = fixturePick.enabled ? fixturePick.current : void 0;
4669
5201
  const mockNonce = fixturePick.nonce;
5202
+ const shownFreshness = mock === void 0 ? freshness : mock.loaded === false ? pendingFreshness : MOCK_FRESHNESS;
5203
+ const shownRead = mock === void 0 ? installedRead : mock.installedRead ?? true;
5204
+ const busy = !marketSettled(shownFreshness, shownRead) || shownFreshness.checking;
4670
5205
  return /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketPage }, /* @__PURE__ */ h("header", { className: MarketPage_module_default.marketPageHead }, /* @__PURE__ */ h("div", null, /* @__PURE__ */ h("h1", { className: MarketPage_module_default.marketPageTitle }, t("panels.tab.market")), /* @__PURE__ */ h("p", { className: MarketPage_module_default.marketPageIntro }, t("panels.market.lead"))), /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketPageTools }, /* @__PURE__ */ h(Tip, { label: t("panels.market.recheck") }, /* @__PURE__ */ h("button", {
4671
5206
  type: "button",
4672
- className: `${MarketPage_module_default.marketIconButton}${checking ? ` ${MarketPage_module_default.marketIconButtonSpinning}` : ""}`,
5207
+ className: `${MarketPage_module_default.marketIconButton}${shownFreshness.checking ? ` ${MarketPage_module_default.marketIconButtonSpinning}` : ""}`,
4673
5208
  "aria-label": t("panels.market.checkUpdates"),
4674
- disabled: checking,
5209
+ disabled: busy,
4675
5210
  onClick: () => {
4676
5211
  forceProbe();
4677
5212
  reloadVersions({ force: true });
@@ -4684,6 +5219,7 @@ function MarketPanel({ t, units }) {
4684
5219
  size: "sm",
4685
5220
  className: MarketPage_module_default.marketAddButton,
4686
5221
  icon: /* @__PURE__ */ h(IconPlusOutlineRegular, { size: 13 }),
5222
+ disabled: busy,
4687
5223
  onClick: () => setAdding(true)
4688
5224
  }, t("panels.market.add.open")))), fixturePick.enabled ? /* @__PURE__ */ h(MockStrip, {
4689
5225
  mocks: marketMocks,
@@ -4694,11 +5230,12 @@ function MarketPanel({ t, units }) {
4694
5230
  t,
4695
5231
  manifests,
4696
5232
  capabilities,
4697
- freshness: mock === void 0 ? freshness : mock.loaded === false ? pendingFreshness : MOCK_FRESHNESS,
5233
+ freshness: shownFreshness,
4698
5234
  onChanged: reload,
4699
5235
  updates,
4700
5236
  installed,
4701
- installedRead: mock === void 0 ? installedRead : mock.installedRead ?? true,
5237
+ installedRead: shownRead,
5238
+ plugins,
4702
5239
  settling: mock === void 0 ? settling : void 0,
4703
5240
  mock
4704
5241
  }), /* @__PURE__ */ h(AddModuleDialog, {
@@ -4754,6 +5291,41 @@ var CLIENT_UNITS = [];
4754
5291
  function assembleClientUnits(registry) {
4755
5292
  for (const face of CLIENT_UNITS) registry.register(face);
4756
5293
  }
5294
+ //#endregion
5295
+ //#region src/shared/api/unit-manifests.ts
5296
+ var UNIT_MANIFESTS_ROUTE = "unit-manifests";
5297
+ //#endregion
5298
+ //#region src/app/units/manifests.ts
5299
+ function createUnitManifestLoader(options = {}) {
5300
+ let inFlight = false;
5301
+ let queued = false;
5302
+ let watching = false;
5303
+ const subscribeFacts = options.subscribeFacts ?? subscribeFactRevision;
5304
+ const load = async (registry) => {
5305
+ if (inFlight) {
5306
+ queued = true;
5307
+ return;
5308
+ }
5309
+ inFlight = true;
5310
+ try {
5311
+ const response = await apiGet(UNIT_MANIFESTS_ROUTE).catch(() => null);
5312
+ if (response?.ok === true && Array.isArray(response.manifests)) registry.setManifests(response.manifests);
5313
+ } finally {
5314
+ inFlight = false;
5315
+ if (queued) {
5316
+ queued = false;
5317
+ load(registry);
5318
+ }
5319
+ }
5320
+ };
5321
+ return (registry) => {
5322
+ if (!watching) {
5323
+ watching = true;
5324
+ subscribeFacts(factOfRoute(UNIT_MANIFESTS_ROUTE), () => void load(registry));
5325
+ }
5326
+ load(registry);
5327
+ };
5328
+ }
4757
5329
  var BootGate_module_default = {
4758
5330
  actions: "_actions_jlw8j_1",
4759
5331
  progress: "_progress_jlw8j_2",
@@ -7431,42 +8003,6 @@ function useLongTask(options) {
7431
8003
  return { runs };
7432
8004
  }
7433
8005
  //#endregion
7434
- //#region src/shared/hooks/use-visible-busy.ts
7435
- var use_visible_busy_exports = /* @__PURE__ */ __exportAll({
7436
- busyView: () => busyView,
7437
- heldPhase: () => heldPhase,
7438
- useVisibleBusy: () => useVisibleBusy
7439
- });
7440
- function heldPhase(phase, refreshing) {
7441
- return refreshing && phase === "ready" ? "loading" : phase;
7442
- }
7443
- function busyView(active, visible) {
7444
- return {
7445
- busy: visible,
7446
- settling: visible && !active
7447
- };
7448
- }
7449
- function useVisibleBusy(active, floorMs) {
7450
- const [visible, setVisible] = useState(active);
7451
- const since = useRef(null);
7452
- useEffect(() => {
7453
- if (active) {
7454
- if (since.current === null) since.current = Date.now();
7455
- setVisible(true);
7456
- return;
7457
- }
7458
- const held = holdBusy(since.current, Date.now(), floorMs);
7459
- since.current = null;
7460
- if (!held.visible) {
7461
- setVisible(false);
7462
- return;
7463
- }
7464
- const timer = setTimeout(() => setVisible(false), held.releaseInMs);
7465
- return () => clearTimeout(timer);
7466
- }, [active, floorMs]);
7467
- return busyView(active, visible);
7468
- }
7469
- //#endregion
7470
8006
  //#region src/shared/hooks/use-unit-board.ts
7471
8007
  var use_unit_board_exports = /* @__PURE__ */ __exportAll({ useUnitBoard: () => useUnitBoard });
7472
8008
  function useUnitBoard(options) {
@@ -7680,7 +8216,7 @@ var kit = {
7680
8216
  };
7681
8217
  function messagesOf(units) {
7682
8218
  return units.map((face) => ({
7683
- unit: face.manifest.id,
8219
+ unit: face.unit,
7684
8220
  ...face.messages === void 0 ? {} : { messages: face.messages }
7685
8221
  }));
7686
8222
  }
@@ -7721,12 +8257,14 @@ function apply(ctx) {
7721
8257
  }
7722
8258
  }
7723
8259
  function mount(ctx) {
7724
- const units = createClientUnitRegistry();
8260
+ const loadUnitManifests = createUnitManifestLoader();
8261
+ const units = createClientUnitRegistry({ onUnknownUnit: () => loadUnitManifests(units) });
7725
8262
  ctx.provide(UNITS_SERVICE, {
7726
8263
  registerUnit: (face) => units.register(face),
7727
8264
  manifests: () => units.manifests()
7728
8265
  });
7729
8266
  assembleClientUnits(units);
8267
+ loadUnitManifests(units);
7730
8268
  const store = rightbarStore;
7731
8269
  const observable = {
7732
8270
  getSnapshot: () => store.getSnapshot(),