@dsh-workbench/core 0.1.9 → 0.1.11

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 (42) hide show
  1. package/bin/dsh-workbench +23 -0
  2. package/client.js +609 -84
  3. package/cordis.patch.yml +11 -3
  4. package/dist/client/client.body.js +609 -84
  5. package/dist/client/index.js +610 -85
  6. package/dist/host/index.js +406 -276
  7. package/dist/types/app/client.d.ts +140 -6
  8. package/dist/types/app/layouts/rightbar/BoardEmpty.d.ts +2 -1
  9. package/dist/types/app/layouts/rightbar/Rightbar.d.ts +1 -0
  10. package/dist/types/app/layouts/rightbar/board-plan.d.ts +2 -0
  11. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +2 -2
  12. package/dist/types/app/layouts/sidebar/Sidebar.d.ts +1 -1
  13. package/dist/types/app/layouts/sidebar/WorkspaceRenameDialog.d.ts +8 -0
  14. package/dist/types/app/layouts/sidebar/WorkspaceRowAction.d.ts +7 -0
  15. package/dist/types/app/layouts/sidebar/WorkspaceRowLabel.d.ts +8 -0
  16. package/dist/types/app/layouts/sidebar/WorkspaceSearchField.d.ts +9 -0
  17. package/dist/types/app/layouts/sidebar/__tests__/workspace-name.test.d.ts +1 -0
  18. package/dist/types/app/layouts/sidebar/workspace-items.d.ts +11 -0
  19. package/dist/types/app/layouts/sidebar/workspace-name.d.ts +16 -0
  20. package/dist/types/app/units/__tests__/registry.test.d.ts +1 -0
  21. package/dist/types/app/units/fixture-unit-mock.d.ts +3 -1
  22. package/dist/types/modules/panels/__tests__/approval.test.d.ts +1 -0
  23. package/dist/types/modules/panels/model/approval.d.ts +2 -0
  24. package/dist/types/shared/api/diy.d.ts +3 -0
  25. package/dist/types/shared/api/diy.test.d.ts +1 -0
  26. package/dist/types/shared/api/host/__tests__/approval-legacy.test.d.ts +1 -0
  27. package/dist/types/shared/api/host/approval-legacy.d.ts +2 -0
  28. package/dist/types/shared/api/host/diy.d.ts +3 -0
  29. package/dist/types/shared/api/host/diy.test.d.ts +1 -0
  30. package/dist/types/shared/api/host/mount-units.d.ts +13 -6
  31. package/dist/types/shared/hooks/mock-mode.d.ts +1 -0
  32. package/package.json +13 -5
  33. package/preset/diy.patch.yml +156 -0
  34. package/scripts/seed-profile.sh +116 -0
  35. package/skills/workbench-module-diy/BUILD.md +179 -0
  36. package/skills/workbench-module-diy/DESIGN.md +81 -0
  37. package/skills/workbench-module-diy/LINKAGE.md +113 -0
  38. package/skills/workbench-module-diy/PANEL.md +124 -0
  39. package/skills/workbench-module-diy/REVIEW.md +31 -0
  40. package/skills/workbench-module-diy/SKILL.md +54 -0
  41. package/skills/workbench-module-diy/STEPS.md +93 -0
  42. package/skills/workbench-module-diy/TOOLS.md +112 -0
@@ -892,171 +892,39 @@ function resolvePluginSource(source, env, options = {}) {
892
892
  }
893
893
  //#endregion
894
894
  //#region ../shared/dist/host/tool-approval.js
895
- function compose(detail, texts) {
896
- if (detail === void 0 || typeof detail.zh !== "string" || detail.zh.trim() === "") return texts.generic;
897
- return {
898
- en: `${detail.en} — ${texts.question.en}`,
899
- zh: `${detail.zh} —— ${texts.question.zh}`
900
- };
895
+ function messageOf(error) {
896
+ return error instanceof Error ? error.message : String(error);
901
897
  }
902
- function actionOf$1(call) {
903
- const raw = call.arguments;
904
- if (typeof raw !== "object" || raw === null) return {};
905
- const value = raw;
898
+ function compose(detail, verdict) {
899
+ if (detail === void 0 || typeof detail.zh !== "string" || detail.zh.trim() === "") return verdict.fallback;
906
900
  return {
907
- ...typeof value.action === "string" ? { action: value.action } : {},
908
- ...typeof value.id === "string" ? { id: value.id } : typeof value.unit === "string" ? { id: value.unit } : {}
901
+ en: `${detail.en} — ${verdict.question.en}`,
902
+ zh: `${detail.zh} —— ${verdict.question.zh}`
909
903
  };
910
904
  }
911
- var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set(["list", ""]);
912
- var READ = { kind: "read" };
913
- var UNKNOWN = { kind: "unknown" };
914
- function ask(spec) {
915
- return {
916
- kind: "ask",
917
- spec
918
- };
919
- }
920
- function confirmed(call) {
921
- const raw = call.arguments;
922
- if (typeof raw !== "object" || raw === null) return void 0;
923
- const value = raw.confirm;
924
- return typeof value === "boolean" ? value : void 0;
925
- }
926
- function effectOf(call, context) {
927
- const name = call.name ?? "";
928
- const { action, id } = actionOf$1(call);
929
- const readAction = action !== void 0 && action !== "" && READ_ONLY_ACTIONS.has(action);
930
- const what = () => {
931
- if (id === void 0 || id === "") return "一个模块";
932
- return `「${context.titleOf?.(id) ?? id}」`;
933
- };
934
- if (name === "workbench_cmdb_release") {
935
- if (confirmed(call) !== true) return READ;
936
- return ask({
937
- reason: "confirm=true releases a version to a real environment",
938
- generic: {
939
- en: "The agent wants to deploy a version for real (confirm: true) — it deploys on the platform (non-production). Allow?",
940
- zh: "agent 想在非生产环境上**真的部署一个版本**(confirm: true)—— 会在平台上部署。允许吗?"
941
- },
942
- question: {
943
- en: "it deploys on the platform for real (non-production). Allow?",
944
- zh: "会在平台上真的部署一次(非生产环境)。允许吗?"
945
- }
946
- });
947
- }
948
- if (name === "workbench_coding_issue_comment" || name === "workbench_coding_issue_transfer") {
949
- if (confirmed(call) !== true) return READ;
950
- const comment = name === "workbench_coding_issue_comment";
951
- return ask({
952
- reason: comment ? "confirm=true posts a comment on the platform" : "confirm=true moves an issue to another status on the platform",
953
- generic: comment ? {
954
- en: "The agent wants to post a comment on the platform for real (confirm: true) — it writes on the platform. Allow?",
955
- zh: "agent 想**真的给一条缺陷加评论**(confirm: true)—— 会写平台数据。允许吗?"
956
- } : {
957
- en: "The agent wants to move an issue to another status for real (confirm: true) — it writes on the platform. Allow?",
958
- zh: "agent 想**真的把一条缺陷流转到别的状态**(confirm: true)—— 会写平台数据。允许吗?"
959
- },
960
- question: comment ? {
961
- en: "it posts that comment on the platform for real. Allow?",
962
- zh: "会在平台上真的发表这条评论。允许吗?"
963
- } : {
964
- en: "it changes that issue status on the platform for real. Allow?",
965
- zh: "会在平台上真的改这条缺陷的状态。允许吗?"
966
- }
967
- });
968
- }
969
- if (name === "workbench_modules") {
970
- if (readAction) return READ;
971
- if (action === "update-dependency") return ask({
972
- reason: `workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`,
973
- generic: {
974
- en: `The agent wants to upgrade the shared dependency ${what()} (it changes what is installed on this machine). Allow?`,
975
- zh: `agent 想升级共用依赖 ${what()}(会改这台机器上装的东西)。允许吗?`
976
- },
977
- question: {
978
- en: "it upgrades that shared dependency on this machine. Allow?",
979
- zh: `会升级共用依赖 ${what()}。允许吗?`
980
- }
981
- });
982
- const verb = action === "install" ? "装" : action === "uninstall" ? "卸载" : action === "update" ? "更新" : "装 / 更新 / 卸载";
983
- return ask({
984
- reason: `workbench_modules ${action ?? "(unknown action)"} ${id ?? ""} changes plugins on this machine`.trim(),
985
- generic: {
986
- en: `The agent wants to ${verb === "装" ? "install" : verb === "卸载" ? "remove" : verb === "更新" ? "update" : "install/update/remove"} the module ${what()} (it changes plugins on this machine). Allow?`,
987
- zh: `agent 想${verb}模块 ${what()}(会改这台机器上的插件)。允许吗?`
988
- },
989
- question: {
990
- en: "it changes plugins on this machine. Allow?",
991
- zh: "会改这台机器上的插件。允许吗?"
992
- }
993
- });
994
- }
995
- if (name !== "workbench_panels") return UNKNOWN;
996
- if (readAction) return READ;
997
- if (action === "hide") return ask({
998
- reason: "workbench_panels hide removes a module from the panel",
999
- generic: {
1000
- en: `The agent wants to remove ${what()} from this project's panel (its agent tools go offline here too). Allow?`,
1001
- zh: `agent 想把 ${what()} 从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`
1002
- },
1003
- question: {
1004
- en: "it takes that module off this panel (its agent tools go offline here too). Allow?",
1005
- zh: `${what()}会从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`
1006
- }
1007
- });
1008
- if (action === "move") return ask({
1009
- reason: "workbench_panels move reorders the panel",
1010
- generic: {
1011
- en: `The agent wants to reorder this project's panel (put ${what()} first). Allow?`,
1012
- zh: `agent 想调整这个项目面板的顺序(把 ${what()} 放前面)。允许吗?`
1013
- },
1014
- question: {
1015
- en: `it reorders this panel (${what()} goes first). Allow?`,
1016
- zh: `会调整这个项目面板的顺序(${what()}放前面)。允许吗?`
1017
- }
1018
- });
1019
- if (action === "reset") return ask({
1020
- reason: "workbench_panels reset clears the panel",
1021
- generic: {
1022
- en: "The agent wants to clear this project's panel (every module goes offline here). Allow?",
1023
- zh: "agent 想清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?"
1024
- },
1025
- question: {
1026
- en: "it clears this panel (every module goes offline here). Allow?",
1027
- zh: "会清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?"
1028
- }
1029
- });
1030
- return ask({
1031
- reason: `workbench_panels ${action ?? "(unknown action)"} changes the user's panel`,
1032
- generic: {
1033
- en: `The agent wants to add ${what()} to this project's panel. Allow?`,
1034
- zh: `agent 想把 ${what()} 加到**这个项目**的面板上。允许吗?`
1035
- },
1036
- question: {
1037
- en: `it adds ${what()} to this project's panel. Allow?`,
1038
- zh: `会把 ${what()} 加到**这个项目**的面板上。允许吗?`
1039
- }
1040
- });
1041
- }
1042
905
  async function approvalFor(call, context = {}) {
1043
- const effect = effectOf(call, context);
1044
- if (effect.kind === "read") return void 0;
1045
- if (effect.kind === "unknown") {
1046
- if (context.writes !== true) return void 0;
906
+ if (context.writes !== true) return void 0;
907
+ let verdict;
908
+ try {
909
+ verdict = context.policy?.(call);
910
+ } catch (error) {
1047
911
  return {
1048
912
  kind: "deny",
1049
- reason: `${call.name ?? "这把工具"} 在清单里声明会写东西,但审批表没认领这次调用 —— 停下:给它在 tool-approval.ts 里补一条审批(含"这次会发生什么"),别让写静默过去`
913
+ reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf(error)}`
1050
914
  };
1051
915
  }
1052
- const { spec } = effect;
916
+ if (verdict?.kind === "read") return void 0;
917
+ if (verdict?.kind !== "ask") return {
918
+ kind: "deny",
919
+ reason: `${call.name ?? "这把工具"} 在清单里声明会写东西,但它的套件没认领这次调用 —— 停下:在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去`
920
+ };
1053
921
  let facts;
1054
922
  try {
1055
923
  facts = await context.detailOf?.(call);
1056
924
  } catch (error) {
1057
925
  return {
1058
926
  kind: "deny",
1059
- reason: `拿不到"这次会发生什么",已经停下:${error instanceof Error ? error.message : String(error)}`
927
+ reason: `拿不到"这次会发生什么",已经停下:${messageOf(error)}`
1060
928
  };
1061
929
  }
1062
930
  if (facts !== void 0 && "blocked" in facts) return {
@@ -1065,8 +933,8 @@ async function approvalFor(call, context = {}) {
1065
933
  };
1066
934
  return {
1067
935
  kind: "ask",
1068
- reason: spec.reason,
1069
- displayReason: compose(facts, spec)
936
+ reason: verdict.reason,
937
+ displayReason: compose(facts, verdict)
1070
938
  };
1071
939
  }
1072
940
  //#endregion
@@ -1588,6 +1456,101 @@ function createRegistryNotebook(dir) {
1588
1456
  }
1589
1457
  };
1590
1458
  }
1459
+ var panelsUnit = {
1460
+ id: "panels",
1461
+ kind: "ui",
1462
+ card: null,
1463
+ tools: [{
1464
+ name: "workbench_panels",
1465
+ writes: true
1466
+ }, {
1467
+ name: "workbench_modules",
1468
+ writes: true
1469
+ }],
1470
+ host: {
1471
+ inject: [],
1472
+ optional: ["tools", "shell"]
1473
+ }
1474
+ };
1475
+ //#endregion
1476
+ //#region src/modules/panels/model/approval.ts
1477
+ var READ$1 = { kind: "read" };
1478
+ var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set(["list", ""]);
1479
+ function actionOf$1(call) {
1480
+ const raw = call.arguments;
1481
+ if (typeof raw !== "object" || raw === null) return {};
1482
+ const value = raw;
1483
+ return {
1484
+ ...typeof value.action === "string" ? { action: value.action } : {},
1485
+ ...typeof value.id === "string" ? { id: value.id } : typeof value.unit === "string" ? { id: value.unit } : {}
1486
+ };
1487
+ }
1488
+ function whatOf(id) {
1489
+ if (id === void 0 || id === "") return "一个模块";
1490
+ return `「${id}」`;
1491
+ }
1492
+ function askOf(reason, question, fallback) {
1493
+ return {
1494
+ kind: "ask",
1495
+ reason,
1496
+ question,
1497
+ fallback
1498
+ };
1499
+ }
1500
+ var panelsApproval = (call) => {
1501
+ const { action, id } = actionOf$1(call);
1502
+ const readAction = action !== void 0 && action !== "" && READ_ONLY_ACTIONS.has(action);
1503
+ const what = whatOf(id);
1504
+ if (call.name === "workbench_modules") {
1505
+ if (readAction) return READ$1;
1506
+ if (action === "update-dependency") return askOf(`workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`, {
1507
+ en: "it upgrades that shared dependency on this machine. Allow?",
1508
+ zh: `会升级共用依赖 ${what}。允许吗?`
1509
+ }, {
1510
+ en: `The agent wants to upgrade the shared dependency ${what} (it changes what is installed on this machine). Allow?`,
1511
+ zh: `agent 想升级共用依赖 ${what}(会改这台机器上装的东西)。允许吗?`
1512
+ });
1513
+ const verb = action === "install" ? "装" : action === "uninstall" ? "卸载" : action === "update" ? "更新" : "装 / 更新 / 卸载";
1514
+ const verbEn = verb === "装" ? "install" : verb === "卸载" ? "remove" : verb === "更新" ? "update" : "install/update/remove";
1515
+ return askOf(`workbench_modules ${action ?? "(unknown action)"} ${id ?? ""} changes plugins on this machine`.trim(), {
1516
+ en: "it changes plugins on this machine. Allow?",
1517
+ zh: "会改这台机器上的插件。允许吗?"
1518
+ }, {
1519
+ en: `The agent wants to ${verbEn} the module ${what} (it changes plugins on this machine). Allow?`,
1520
+ zh: `agent 想${verb}模块 ${what}(会改这台机器上的插件)。允许吗?`
1521
+ });
1522
+ }
1523
+ if (call.name !== "workbench_panels") return void 0;
1524
+ if (readAction) return READ$1;
1525
+ if (action === "hide") return askOf("workbench_panels hide removes a module from the panel", {
1526
+ en: "it takes that module off this panel (its agent tools go offline here too). Allow?",
1527
+ zh: `${what}会从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`
1528
+ }, {
1529
+ en: `The agent wants to remove ${what} from this project's panel (its agent tools go offline here too). Allow?`,
1530
+ zh: `agent 想把 ${what} 从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`
1531
+ });
1532
+ if (action === "move") return askOf("workbench_panels move reorders the panel", {
1533
+ en: `it reorders this panel (${what} goes first). Allow?`,
1534
+ zh: `会调整这个项目面板的顺序(${what}放前面)。允许吗?`
1535
+ }, {
1536
+ en: `The agent wants to reorder this project's panel (put ${what} first). Allow?`,
1537
+ zh: `agent 想调整这个项目面板的顺序(把 ${what} 放前面)。允许吗?`
1538
+ });
1539
+ if (action === "reset") return askOf("workbench_panels reset clears the panel", {
1540
+ en: "it clears this panel (every module goes offline here). Allow?",
1541
+ zh: "会清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?"
1542
+ }, {
1543
+ en: "The agent wants to clear this project's panel (every module goes offline here). Allow?",
1544
+ zh: "agent 想清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?"
1545
+ });
1546
+ return askOf(`workbench_panels ${action ?? "(unknown action)"} changes the user's panel`, {
1547
+ en: `it adds ${what} to this project's panel. Allow?`,
1548
+ zh: `会把 ${what} 加到**这个项目**的面板上。允许吗?`
1549
+ }, {
1550
+ en: `The agent wants to add ${what} to this project's panel. Allow?`,
1551
+ zh: `agent 想把 ${what} 加到**这个项目**的面板上。允许吗?`
1552
+ });
1553
+ };
1591
1554
  //#endregion
1592
1555
  //#region src/shared/api/host/release.ts
1593
1556
  function parseLedgerVersion(stdout, name) {
@@ -2432,22 +2395,6 @@ function applyPanelsPref(panels, payload) {
2432
2395
  units: panels.move(action.id, action.index ?? 0, path)
2433
2396
  };
2434
2397
  }
2435
- var panelsUnit = {
2436
- id: "panels",
2437
- kind: "ui",
2438
- card: null,
2439
- tools: [{
2440
- name: "workbench_panels",
2441
- writes: true
2442
- }, {
2443
- name: "workbench_modules",
2444
- writes: true
2445
- }],
2446
- host: {
2447
- inject: [],
2448
- optional: ["tools", "shell"]
2449
- }
2450
- };
2451
2398
  //#endregion
2452
2399
  //#region src/modules/panels/model/handoff.ts
2453
2400
  var PANELS_FACT = "panels";
@@ -2843,6 +2790,7 @@ var HOST_UNITS = [{
2843
2790
  routes: panelRoutes(panels, values),
2844
2791
  registerTools: () => [...registerPanelTools(ctx, panels, values), ...registerMarketTools(ctx, market)],
2845
2792
  factsOf: (call) => panelsFactsOf(call, manifests()),
2793
+ needsApproval: panelsApproval,
2846
2794
  gate: {
2847
2795
  deniedTools: (workspace) => manifests().filter((manifest) => manifest.card !== null).filter((manifest) => !panels.isEnabled(manifest.id, workspace)).flatMap((manifest) => manifest.tools.map((tool) => tool.name)),
2848
2796
  subscribe: (listener) => panels.subscribe(listener)
@@ -3094,6 +3042,21 @@ function bundleInspectHandler(ctx) {
3094
3042
  };
3095
3043
  }
3096
3044
  //#endregion
3045
+ //#region src/shared/api/diy.ts
3046
+ var WORKSHOP_TITLE = "工作台DIY";
3047
+ //#endregion
3048
+ //#region src/shared/api/host/diy.ts
3049
+ var WORKSHOP_RELATIVE = "workbench/diy";
3050
+ function workshopPathOf(ctx) {
3051
+ const homePath = ctx.get("dshHomePath");
3052
+ if (typeof homePath !== "function") return null;
3053
+ try {
3054
+ return homePath(WORKSHOP_RELATIVE);
3055
+ } catch {
3056
+ return null;
3057
+ }
3058
+ }
3059
+ //#endregion
3097
3060
  //#region src/shared/api/dsh-plugins.ts
3098
3061
  var DSH_PLUGINS_ROUTE = "dsh-plugins";
3099
3062
  //#endregion
@@ -3788,16 +3751,43 @@ function withRunsRoute(result, unit) {
3788
3751
  }
3789
3752
  };
3790
3753
  }
3791
- function collectRoutes(mounted) {
3754
+ function failureReasonOf(error) {
3755
+ if (error instanceof Error) return error.message;
3756
+ return typeof error === "string" ? error : String(error);
3757
+ }
3758
+ function fail(ledger, item, reason) {
3759
+ ledger.failed.add(item);
3760
+ ledger.failures.push({
3761
+ id: item.face.manifest.id,
3762
+ reason
3763
+ });
3764
+ }
3765
+ function collectRoutes(mounted, ledger) {
3792
3766
  const routes = {};
3793
3767
  const owner = /* @__PURE__ */ new Map();
3794
3768
  const routesOfUnit = /* @__PURE__ */ new Map();
3795
3769
  for (const item of mounted) {
3770
+ if (ledger.failed.has(item)) continue;
3796
3771
  const unit = item.face.manifest.id;
3772
+ const accepted = [];
3773
+ let problem;
3797
3774
  for (const [key, handler] of Object.entries(item.result.routes)) {
3798
- if (key !== unit && !key.startsWith(`${unit}/`)) throw new Error(`路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`);
3775
+ if (key !== unit && !key.startsWith(`${unit}/`)) {
3776
+ problem = `路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`;
3777
+ break;
3778
+ }
3799
3779
  const previous = owner.get(key);
3800
- if (previous !== void 0) throw new Error(`路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`);
3780
+ if (previous !== void 0) {
3781
+ problem = `路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`;
3782
+ break;
3783
+ }
3784
+ accepted.push([key, handler]);
3785
+ }
3786
+ if (problem !== void 0) {
3787
+ fail(ledger, item, problem);
3788
+ continue;
3789
+ }
3790
+ for (const [key, handler] of accepted) {
3801
3791
  owner.set(key, unit);
3802
3792
  routes[key] = handler;
3803
3793
  const own = routesOfUnit.get(unit);
@@ -3811,45 +3801,64 @@ function collectRoutes(mounted) {
3811
3801
  routesOfUnit
3812
3802
  };
3813
3803
  }
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) {
3804
+ function collectNamed(mounted, ledger, pickMany, label) {
3825
3805
  const owner = /* @__PURE__ */ new Map();
3826
- const prefs = [];
3806
+ const out = [];
3827
3807
  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
- });
3808
+ if (ledger.failed.has(item)) continue;
3809
+ const unit = item.face.manifest.id;
3810
+ const entries = pickMany(item);
3811
+ if (entries.length === 0) continue;
3812
+ const clash = entries.find((entry) => owner.has(entry.resource));
3813
+ if (clash !== void 0) {
3814
+ fail(ledger, item, `${label}名冲突:${clash.resource} 由套件 ${owner.get(clash.resource)} 与 ${unit} 同时声明`);
3815
+ continue;
3816
+ }
3817
+ for (const entry of entries) {
3818
+ owner.set(entry.resource, unit);
3819
+ out.push({
3820
+ ...entry,
3821
+ unit
3822
+ });
3823
+ }
3837
3824
  }
3838
- return prefs;
3825
+ return out;
3839
3826
  }
3840
- function collectActions(mounted) {
3827
+ function collectContextRenderers(mounted, ledger) {
3828
+ const table = /* @__PURE__ */ new Map();
3841
3829
  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
- });
3830
+ for (const item of mounted) {
3831
+ if (ledger.failed.has(item)) continue;
3832
+ const unit = item.face.manifest.id;
3833
+ const context = item.result.context;
3834
+ if (context === void 0) continue;
3835
+ const clash = context.kinds.find((kind) => owner.has(kind));
3836
+ if (clash !== void 0) {
3837
+ fail(ledger, item, `对话引用的 kind 冲突:${clash} 由套件 ${owner.get(clash)} 与 ${unit} 同时认领`);
3838
+ continue;
3839
+ }
3840
+ for (const kind of context.kinds) {
3841
+ owner.set(kind, unit);
3842
+ table.set(kind, (ref) => context.render(ref));
3843
+ }
3844
+ }
3845
+ return table;
3846
+ }
3847
+ function pickProvider(mounted, ledger, pick, label) {
3848
+ let chosen;
3849
+ let chosenBy;
3850
+ for (const item of mounted) {
3851
+ if (ledger.failed.has(item)) continue;
3852
+ const value = pick(item);
3853
+ if (value === void 0) continue;
3854
+ if (chosen === void 0) {
3855
+ chosen = value;
3856
+ chosenBy = item.face.manifest.id;
3857
+ continue;
3858
+ }
3859
+ fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.face.manifest.id}`);
3851
3860
  }
3852
- return actions;
3861
+ return chosen;
3853
3862
  }
3854
3863
  function collectWarmReads(mounted) {
3855
3864
  const declarations = mounted.map((item) => item.result.warmReads).filter((declare) => declare !== void 0);
@@ -3865,26 +3874,15 @@ function collectWarmReads(mounted) {
3865
3874
  return reads;
3866
3875
  };
3867
3876
  }
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
3877
  function mountUnits(faces, deps) {
3884
3878
  const store = deps.store ?? createWorkspaceStore({ root: null });
3885
3879
  const mounted = [];
3886
3880
  const skipped = [];
3887
- const longTaskDisposers = [];
3881
+ const longTaskDisposers = /* @__PURE__ */ new Map();
3882
+ const ledger = {
3883
+ failed: /* @__PURE__ */ new Set(),
3884
+ failures: []
3885
+ };
3888
3886
  for (const face of faces) {
3889
3887
  const readiness = unitReadiness(face.manifest, (dep) => deps.ctx.get(dep));
3890
3888
  if (!readiness.canMount) {
@@ -3900,48 +3898,71 @@ function mountUnits(faces, deps) {
3900
3898
  unit,
3901
3899
  bump: deps.longTasks.bump
3902
3900
  });
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({
3901
+ let result;
3902
+ try {
3903
+ result = face.mount({
3904
+ ctx: deps.ctx,
3905
+ logger: deps.logger,
3906
+ manifests: deps.manifests,
3907
+ ...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
3908
+ ...deps.probe === void 0 ? {} : { probe: deps.probe },
3909
+ ...deps.values === void 0 ? {} : { values: deps.values },
3910
+ ...longTasks === void 0 ? {} : { longTasks: longTasks.host },
3911
+ store
3912
+ });
3913
+ } catch (error) {
3914
+ longTasks?.dispose();
3915
+ ledger.failures.push({
3916
+ id: unit,
3917
+ reason: failureReasonOf(error)
3918
+ });
3919
+ continue;
3920
+ }
3921
+ const item = {
3915
3922
  face,
3916
3923
  readiness,
3917
3924
  result: withRunsRoute(result, unit)
3918
- });
3925
+ };
3926
+ mounted.push(item);
3927
+ if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
3919
3928
  }
3920
- const collected = collectRoutes(mounted);
3921
- const prefs = collectPrefs(mounted);
3922
- const actions = collectActions(mounted);
3929
+ const collected = collectRoutes(mounted, ledger);
3930
+ const prefs = collectNamed(mounted, ledger, (item) => item.result.prefs === void 0 ? [] : [item.result.prefs], "偏好资源");
3931
+ const actions = collectNamed(mounted, ledger, (item) => item.result.actions ?? [], "动作资源");
3923
3932
  const owners = new Map(collected.owners);
3924
3933
  for (const entry of [...prefs, ...actions]) owners.set(entry.resource, entry.unit);
3925
3934
  const factsOf = /* @__PURE__ */ new Map();
3926
3935
  for (const item of mounted) {
3936
+ if (ledger.failed.has(item)) continue;
3927
3937
  const declare = item.result.factsOf;
3928
3938
  if (declare !== void 0) factsOf.set(item.face.manifest.id, declare);
3929
3939
  }
3940
+ const gate = pickProvider(mounted, ledger, (item) => item.result.gate, "启停闸门");
3941
+ const provision = pickProvider(mounted, ledger, (item) => item.result.provision, "机器供给");
3942
+ const contextRenderers = collectContextRenderers(mounted, ledger);
3943
+ const alive = mounted.filter((item) => !ledger.failed.has(item));
3944
+ for (const item of mounted) {
3945
+ if (!ledger.failed.has(item)) continue;
3946
+ longTaskDisposers.get(item)?.();
3947
+ longTaskDisposers.delete(item);
3948
+ }
3930
3949
  return {
3931
- mounted,
3950
+ mounted: alive,
3932
3951
  routes: collected.routes,
3933
3952
  owners,
3934
3953
  routesOfUnit: collected.routesOfUnit,
3935
3954
  factsOf,
3936
- gate: pickGate(mounted),
3955
+ gate,
3937
3956
  prefs,
3938
3957
  actions,
3939
- provision: pickProvision(mounted),
3940
- contextRenderers: collectContextRenderers(mounted),
3958
+ provision,
3959
+ contextRenderers,
3941
3960
  skipped,
3942
- warmReads: collectWarmReads(mounted),
3961
+ failed: ledger.failures,
3962
+ warmReads: collectWarmReads(alive),
3943
3963
  disposeLongTasks: () => {
3944
- for (const dispose of longTaskDisposers.splice(0)) dispose();
3964
+ for (const dispose of [...longTaskDisposers.values()]) dispose();
3965
+ longTaskDisposers.clear();
3945
3966
  }
3946
3967
  };
3947
3968
  }
@@ -3978,6 +3999,54 @@ function createUnitRegistry() {
3978
3999
  };
3979
4000
  }
3980
4001
  //#endregion
4002
+ //#region src/shared/api/host/approval-legacy.ts
4003
+ var READ = { kind: "read" };
4004
+ function confirmed(call) {
4005
+ const raw = call.arguments;
4006
+ if (typeof raw !== "object" || raw === null) return void 0;
4007
+ const value = raw.confirm;
4008
+ return typeof value === "boolean" ? value : void 0;
4009
+ }
4010
+ var legacyApproval = (call) => {
4011
+ const name = call.name ?? "";
4012
+ if (name === "workbench_cmdb_release") {
4013
+ if (confirmed(call) !== true) return READ;
4014
+ return {
4015
+ kind: "ask",
4016
+ reason: "confirm=true releases a version to a real environment",
4017
+ question: {
4018
+ en: "it deploys on the platform for real (non-production). Allow?",
4019
+ zh: "会在平台上真的部署一次(非生产环境)。允许吗?"
4020
+ },
4021
+ fallback: {
4022
+ en: "The agent wants to deploy a version for real (confirm: true) — it deploys on the platform (non-production). Allow?",
4023
+ zh: "agent 想在非生产环境上**真的部署一个版本**(confirm: true)—— 会在平台上部署。允许吗?"
4024
+ }
4025
+ };
4026
+ }
4027
+ if (name !== "workbench_coding_issue_comment" && name !== "workbench_coding_issue_transfer") return void 0;
4028
+ if (confirmed(call) !== true) return READ;
4029
+ const comment = name === "workbench_coding_issue_comment";
4030
+ return {
4031
+ kind: "ask",
4032
+ reason: comment ? "confirm=true posts a comment on the platform" : "confirm=true moves an issue to another status on the platform",
4033
+ question: comment ? {
4034
+ en: "it posts that comment on the platform for real. Allow?",
4035
+ zh: "会在平台上真的发表这条评论。允许吗?"
4036
+ } : {
4037
+ en: "it changes that issue status on the platform for real. Allow?",
4038
+ zh: "会在平台上真的改这条缺陷的状态。允许吗?"
4039
+ },
4040
+ fallback: comment ? {
4041
+ en: "The agent wants to post a comment on the platform for real (confirm: true) — it writes on the platform. Allow?",
4042
+ zh: "agent 想**真的给一条缺陷加评论**(confirm: true)—— 会写平台数据。允许吗?"
4043
+ } : {
4044
+ en: "The agent wants to move an issue to another status for real (confirm: true) — it writes on the platform. Allow?",
4045
+ zh: "agent 想**真的把一条缺陷流转到别的状态**(confirm: true)—— 会写平台数据。允许吗?"
4046
+ }
4047
+ };
4048
+ };
4049
+ //#endregion
3981
4050
  //#region src/shared/api/host/tool-scope.ts
3982
4051
  function agentOf(value) {
3983
4052
  return typeof value === "object" && value !== null ? value : void 0;
@@ -4082,6 +4151,14 @@ function apply(ctx) {
4082
4151
  const units = createUnitRegistry();
4083
4152
  ctx.provide(UNITS_SERVICE, units);
4084
4153
  assembleHostUnits(units);
4154
+ ctx.inject(["workspaceRegistry"], (scoped) => {
4155
+ const registry = scoped.get("workspaceRegistry");
4156
+ const workshop = workshopPathOf(scoped);
4157
+ if (registry === void 0 || workshop === null) return;
4158
+ registry.create(workshop, WORKSHOP_TITLE).catch((error) => {
4159
+ ctx.logger?.warn?.(`dsh-workbench: 工坊没能登记成工作区 —— ${String(error)}`);
4160
+ });
4161
+ });
4085
4162
  const opencliEnv = process.env.DSH_WORKBENCH_OPENCLI;
4086
4163
  const installEnv = process.env.DSH_WORKBENCH_OPENCLI_INSTALL;
4087
4164
  const liveManifests = () => units.manifests();
@@ -4116,6 +4193,7 @@ function apply(ctx) {
4116
4193
  logger: ctx.logger
4117
4194
  });
4118
4195
  const mounted = /* @__PURE__ */ new Map();
4196
+ const mountFailures = /* @__PURE__ */ new Map();
4119
4197
  const factsOfMap = /* @__PURE__ */ new Map();
4120
4198
  const routesOfUnitMap = /* @__PURE__ */ new Map();
4121
4199
  const ownersMap = /* @__PURE__ */ new Map();
@@ -4127,6 +4205,7 @@ function apply(ctx) {
4127
4205
  const item = mounted.get(id);
4128
4206
  if (item === void 0) return;
4129
4207
  mounted.delete(id);
4208
+ mountFailures.delete(id);
4130
4209
  for (const dispose of item.toolDisposers) dispose?.();
4131
4210
  item.gateOff?.();
4132
4211
  item.plan.disposeLongTasks();
@@ -4137,34 +4216,72 @@ function apply(ctx) {
4137
4216
  for (const resource of item.plan.owners.keys()) ownersMap.delete(resource);
4138
4217
  }
4139
4218
  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
- });
4219
+ const id = face.manifest.id;
4220
+ let one;
4221
+ try {
4222
+ one = mountUnits([face], {
4223
+ ctx,
4224
+ logger: ctx.logger,
4225
+ manifests: liveManifests,
4226
+ store,
4227
+ ...opencliEnv === void 0 ? {} : { opencliCommand: opencliEnv },
4228
+ probe: (unit, options) => probeOne(unit, options?.force ?? false),
4229
+ values,
4230
+ longTasks: {
4231
+ scope: process.env["DSH_HOME"] ?? "",
4232
+ bump: (names) => facts.bump(names)
4233
+ }
4234
+ });
4235
+ } catch (error) {
4236
+ mountFailures.set(id, {
4237
+ reason: failureReasonOf(error),
4238
+ face
4239
+ });
4240
+ ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(装配期):${failureReasonOf(error)}`);
4241
+ return;
4242
+ }
4153
4243
  for (const item of one.skipped) ctx.logger?.warn?.(`[dsh-workbench] ${item.id} 套件缺硬依赖,跳过:${item.missing.join(", ")}`);
4244
+ for (const failure of one.failed) {
4245
+ mountFailures.set(failure.id, {
4246
+ reason: failure.reason,
4247
+ face
4248
+ });
4249
+ ctx.logger?.warn?.(`[dsh-workbench] ${failure.id} 套件起不来,已摘掉它这一份:${failure.reason}`);
4250
+ }
4154
4251
  const slot = one.mounted[0];
4155
- if (slot === void 0) return;
4252
+ if (slot === void 0) {
4253
+ one.disposeLongTasks();
4254
+ return;
4255
+ }
4156
4256
  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
- });
4257
+ const added = [];
4258
+ try {
4259
+ Object.assign(routes, one.routes);
4260
+ added.push(...Object.keys(one.routes));
4261
+ for (const [kind, render] of one.contextRenderers) renderers.set(kind, render);
4262
+ for (const [unit, fn] of one.factsOf) factsOfMap.set(unit, fn);
4263
+ for (const [unit, list] of one.routesOfUnit) routesOfUnitMap.set(unit, list);
4264
+ for (const [resource, unit] of one.owners) ownersMap.set(resource, unit);
4265
+ mounted.set(face.manifest.id, {
4266
+ result: slot.result,
4267
+ plan: one,
4268
+ toolDisposers: slot.result.registerTools(),
4269
+ gateOff: one.gate?.subscribe(() => toolScope.reapply())
4270
+ });
4271
+ mountFailures.delete(id);
4272
+ } catch (error) {
4273
+ for (const route of added) delete routes[route];
4274
+ for (const kind of one.contextRenderers.keys()) renderers.delete(kind);
4275
+ for (const unit of one.factsOf.keys()) factsOfMap.delete(unit);
4276
+ for (const unit of one.routesOfUnit.keys()) routesOfUnitMap.delete(unit);
4277
+ for (const [resource, unit] of one.owners) if (ownersMap.get(resource) === unit) ownersMap.delete(resource);
4278
+ one.disposeLongTasks();
4279
+ mountFailures.set(id, {
4280
+ reason: failureReasonOf(error),
4281
+ face
4282
+ });
4283
+ ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(注册期):${failureReasonOf(error)}`);
4284
+ }
4168
4285
  }
4169
4286
  function rebindPost() {
4170
4287
  for (const dispose of undoPost.reverse()) dispose?.();
@@ -4187,7 +4304,13 @@ function apply(ctx) {
4187
4304
  function sync() {
4188
4305
  const present = new Set(units.all().map((face) => face.manifest.id));
4189
4306
  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);
4307
+ for (const id of [...mountFailures.keys()]) if (!present.has(id)) mountFailures.delete(id);
4308
+ for (const face of units.all()) {
4309
+ if (mounted.has(face.manifest.id)) continue;
4310
+ const already = mountFailures.get(face.manifest.id);
4311
+ if (already !== void 0 && already.face === face) continue;
4312
+ mountUnit(face);
4313
+ }
4191
4314
  rebindPost();
4192
4315
  }
4193
4316
  if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
@@ -4209,6 +4332,14 @@ function apply(ctx) {
4209
4332
  ...item,
4210
4333
  released: releasedOf(item.unit)
4211
4334
  })) };
4335
+ const failure = mountFailures.get(unit);
4336
+ if (failure !== void 0) return {
4337
+ unit,
4338
+ status: "failed",
4339
+ required: [],
4340
+ missing: [],
4341
+ detail: failure.reason
4342
+ };
4212
4343
  return probeOne(unit, force);
4213
4344
  });
4214
4345
  }
@@ -4253,20 +4384,19 @@ function apply(ctx) {
4253
4384
  const off = ctx.on?.("tools/pre-execute", async (exec, next) => {
4254
4385
  const decision = await next();
4255
4386
  if (decision.kind !== "allow") return decision;
4256
- const detailOf = async (call) => {
4257
- for (const { result } of mounted.values()) {
4258
- const facts = await result.approvalDetail?.(call);
4259
- if (facts !== void 0) return facts;
4260
- }
4261
- };
4387
+ const name = exec.name === void 0 ? void 0 : String(exec.name);
4388
+ const tool = name === void 0 ? void 0 : unitsOfTool().get(name);
4389
+ const owner = tool === void 0 ? void 0 : mounted.get(tool.unit)?.result;
4390
+ const detailOf = owner === void 0 ? void 0 : (call) => owner.approvalDetail?.(call);
4262
4391
  const workspace = sessionWorkspace(exec) ?? void 0;
4263
4392
  return await approvalFor({
4264
- ...exec.name === void 0 ? {} : { name: exec.name },
4393
+ ...name === void 0 ? {} : { name },
4265
4394
  arguments: exec.arguments,
4266
4395
  ...workspace === void 0 ? {} : { workspace }
4267
4396
  }, {
4268
4397
  detailOf,
4269
- writes: exec.name === void 0 ? false : unitsOfTool().get(String(exec.name))?.writes ?? false
4398
+ policy: owner?.needsApproval ?? legacyApproval,
4399
+ writes: tool?.writes ?? false
4270
4400
  }) ?? decision;
4271
4401
  });
4272
4402
  return () => off?.();