@dsh-workbench/core 0.2.16 → 0.3.1

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 (193) hide show
  1. package/README.md +6 -2
  2. package/bin/dsh-workbench +17 -15
  3. package/client.js +359 -64
  4. package/dist/client/client.body.js +359 -64
  5. package/dist/client/index.js +658 -2978
  6. package/dist/host/index.js +443 -385
  7. package/dist/types/app/client.d.ts +3 -0
  8. package/dist/types/app/layouts/rightbar/BoardGrid.d.ts +23 -0
  9. package/dist/types/app/layouts/rightbar/board-columns.d.ts +17 -4
  10. package/dist/types/app/layouts/rightbar/rightbar-width.d.ts +16 -0
  11. package/dist/types/app/layouts/rightbar/use-board-split.d.ts +8 -0
  12. package/dist/types/modules/panels/model/diy-report.d.ts +13 -2
  13. package/dist/types/shared/api/host/diy-workshop.d.ts +13 -0
  14. package/dist/types/shared/api/host/runtime-seed.d.ts +0 -2
  15. package/docs/HANDOFF.md +43 -10
  16. package/docs/OPERATIONS.md +52 -0
  17. package/package.json +4 -3
  18. package/preset/diy.patch.yml +3 -0
  19. package/scripts/seed-workshop.sh +461 -13
  20. package/skills/workbench-module-diy/BUILD.md +11 -7
  21. package/skills/workbench-module-diy/DATASOURCE.md +3 -4
  22. package/skills/workbench-module-diy/DESIGN.md +3 -4
  23. package/skills/workbench-module-diy/KIT.md +11 -12
  24. package/skills/workbench-module-diy/LINKAGE.md +6 -7
  25. package/skills/workbench-module-diy/PANEL.md +9 -10
  26. package/skills/workbench-module-diy/REVIEW.md +2 -3
  27. package/skills/workbench-module-diy/SKILL.md +2 -3
  28. package/skills/workbench-module-diy/TOOLING.md +3 -8
  29. package/skills/workbench-module-diy/TOOLS.md +4 -9
  30. package/skills/workbench-module-diy/WIRING.md +3 -3
  31. package/dist/types/app/layouts/__tests__/async-feedback.test.d.ts +0 -1
  32. package/dist/types/app/layouts/__tests__/kit-preview-nav.test.d.ts +0 -1
  33. package/dist/types/app/layouts/__tests__/panel-hint.test.d.ts +0 -1
  34. package/dist/types/app/layouts/__tests__/project-selection.test.d.ts +0 -1
  35. package/dist/types/app/layouts/env-gate/__tests__/force-plan.test.d.ts +0 -1
  36. package/dist/types/app/layouts/env-gate/__tests__/gate-deadline.test.d.ts +0 -1
  37. package/dist/types/app/layouts/env-gate/__tests__/gate-hidden.test.d.ts +0 -1
  38. package/dist/types/app/layouts/env-gate/__tests__/gate-mocks.test.d.ts +0 -1
  39. package/dist/types/app/layouts/env-gate/gate-mock.d.ts +0 -18
  40. package/dist/types/app/layouts/market/__tests__/add-module-mock.test.d.ts +0 -1
  41. package/dist/types/app/layouts/market/__tests__/add-module-stages.test.d.ts +0 -1
  42. package/dist/types/app/layouts/market/__tests__/add-module-text.test.d.ts +0 -1
  43. package/dist/types/app/layouts/market/add-module-mock.d.ts +0 -14
  44. package/dist/types/app/layouts/rightbar/BoardMockStrip.d.ts +0 -5
  45. package/dist/types/app/layouts/rightbar/__tests__/board-fact.test.d.ts +0 -1
  46. package/dist/types/app/layouts/rightbar/__tests__/board-mock.test.d.ts +0 -1
  47. package/dist/types/app/layouts/rightbar/__tests__/board-plan.test.d.ts +0 -1
  48. package/dist/types/app/layouts/rightbar/__tests__/board-selection.test.d.ts +0 -1
  49. package/dist/types/app/layouts/rightbar/__tests__/breadcrumbs.test.d.ts +0 -1
  50. package/dist/types/app/layouts/rightbar/__tests__/market-catalog.test.d.ts +0 -1
  51. package/dist/types/app/layouts/rightbar/__tests__/market-change.test.d.ts +0 -1
  52. package/dist/types/app/layouts/rightbar/__tests__/market-dots.test.d.ts +0 -1
  53. package/dist/types/app/layouts/rightbar/__tests__/market-freshness.test.d.ts +0 -1
  54. package/dist/types/app/layouts/rightbar/__tests__/market-hint-visible.test.d.ts +0 -1
  55. package/dist/types/app/layouts/rightbar/__tests__/market-installed.test.d.ts +0 -1
  56. package/dist/types/app/layouts/rightbar/__tests__/market-live.test.d.ts +0 -1
  57. package/dist/types/app/layouts/rightbar/__tests__/market-mock.test.d.ts +0 -1
  58. package/dist/types/app/layouts/rightbar/__tests__/market-order.test.d.ts +0 -1
  59. package/dist/types/app/layouts/rightbar/__tests__/market-row-icon.test.d.ts +0 -1
  60. package/dist/types/app/layouts/rightbar/__tests__/market-rows-libs.test.d.ts +0 -1
  61. package/dist/types/app/layouts/rightbar/__tests__/market-rows-plugins.test.d.ts +0 -1
  62. package/dist/types/app/layouts/rightbar/__tests__/market-rows.test.d.ts +0 -1
  63. package/dist/types/app/layouts/rightbar/__tests__/market-running-display.test.d.ts +0 -1
  64. package/dist/types/app/layouts/rightbar/__tests__/market-section-icon.test.d.ts +0 -1
  65. package/dist/types/app/layouts/rightbar/__tests__/market-settle.test.d.ts +0 -1
  66. package/dist/types/app/layouts/rightbar/__tests__/market-shared.test.d.ts +0 -1
  67. package/dist/types/app/layouts/rightbar/__tests__/market-state-rules.test.d.ts +0 -1
  68. package/dist/types/app/layouts/rightbar/__tests__/market-workbench-act.test.d.ts +0 -1
  69. package/dist/types/app/layouts/rightbar/__tests__/market-workbench.test.d.ts +0 -1
  70. package/dist/types/app/layouts/rightbar/__tests__/panel-dismiss.test.d.ts +0 -1
  71. package/dist/types/app/layouts/rightbar/__tests__/panel-drag-target.test.d.ts +0 -1
  72. package/dist/types/app/layouts/rightbar/__tests__/panel-mock.test.d.ts +0 -1
  73. package/dist/types/app/layouts/rightbar/__tests__/panel-plugin-rows.test.d.ts +0 -1
  74. package/dist/types/app/layouts/rightbar/__tests__/panel-scope-ui.test.d.ts +0 -1
  75. package/dist/types/app/layouts/rightbar/__tests__/panel-write.test.d.ts +0 -1
  76. package/dist/types/app/layouts/rightbar/__tests__/suite-world.test.d.ts +0 -1
  77. package/dist/types/app/layouts/rightbar/__tests__/use-panels.test.d.ts +0 -1
  78. package/dist/types/app/layouts/rightbar/board-mock.d.ts +0 -22
  79. package/dist/types/app/layouts/rightbar/kit-preview-controls.d.ts +0 -11
  80. package/dist/types/app/layouts/rightbar/kit-preview-frame.d.ts +0 -18
  81. package/dist/types/app/layouts/rightbar/kit-preview-hash.d.ts +0 -1
  82. package/dist/types/app/layouts/rightbar/kit-preview-rows.d.ts +0 -6
  83. package/dist/types/app/layouts/rightbar/kit-preview.d.ts +0 -3
  84. package/dist/types/app/layouts/rightbar/market-installed-mock.d.ts +0 -9
  85. package/dist/types/app/layouts/rightbar/market-mock-types.d.ts +0 -58
  86. package/dist/types/app/layouts/rightbar/market-mock.d.ts +0 -4
  87. package/dist/types/app/layouts/rightbar/panel-mock.d.ts +0 -27
  88. package/dist/types/app/layouts/sidebar/__tests__/current-workspace.test.d.ts +0 -1
  89. package/dist/types/app/layouts/sidebar/__tests__/debug-ui.test.d.ts +0 -1
  90. package/dist/types/app/layouts/sidebar/__tests__/session-pick.test.d.ts +0 -1
  91. package/dist/types/app/layouts/sidebar/__tests__/session-row-ui.test.d.ts +0 -1
  92. package/dist/types/app/layouts/sidebar/__tests__/workspace-groups.test.d.ts +0 -1
  93. package/dist/types/app/layouts/sidebar/__tests__/workspace-name.test.d.ts +0 -1
  94. package/dist/types/app/units/__tests__/fixture-unit-mock.test.d.ts +0 -1
  95. package/dist/types/app/units/__tests__/manifests.test.d.ts +0 -1
  96. package/dist/types/app/units/__tests__/registry.test.d.ts +0 -1
  97. package/dist/types/app/units/fixture-unit-mock.d.ts +0 -7
  98. package/dist/types/dev/MockStrip.d.ts +0 -15
  99. package/dist/types/dev/surface-types.d.ts +0 -53
  100. package/dist/types/dev/surface.d.ts +0 -2
  101. package/dist/types/dev/surface.stub.d.ts +0 -2
  102. package/dist/types/modules/panels/__tests__/approval.test.d.ts +0 -1
  103. package/dist/types/modules/panels/__tests__/config.test.d.ts +0 -1
  104. package/dist/types/modules/panels/__tests__/diy-harness.d.ts +0 -105
  105. package/dist/types/modules/panels/__tests__/diy-report.test.d.ts +0 -1
  106. package/dist/types/modules/panels/__tests__/diy-tools-health.test.d.ts +0 -1
  107. package/dist/types/modules/panels/__tests__/diy-tools-panel.test.d.ts +0 -1
  108. package/dist/types/modules/panels/__tests__/diy-tools-status.test.d.ts +0 -1
  109. package/dist/types/modules/panels/__tests__/diy-tools.test.d.ts +0 -1
  110. package/dist/types/modules/panels/__tests__/face.test.d.ts +0 -1
  111. package/dist/types/modules/panels/__tests__/market-bundle.test.d.ts +0 -1
  112. package/dist/types/modules/panels/__tests__/market-dependency.test.d.ts +0 -1
  113. package/dist/types/modules/panels/__tests__/market-dispatch.test.d.ts +0 -1
  114. package/dist/types/modules/panels/__tests__/market-env.test.d.ts +0 -1
  115. package/dist/types/modules/panels/__tests__/market-plugin.test.d.ts +0 -1
  116. package/dist/types/modules/panels/__tests__/market-registries.test.d.ts +0 -1
  117. package/dist/types/modules/panels/__tests__/market-route.test.d.ts +0 -1
  118. package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +0 -45
  119. package/dist/types/modules/panels/__tests__/market-tools.test.d.ts +0 -1
  120. package/dist/types/modules/panels/__tests__/market-update.test.d.ts +0 -1
  121. package/dist/types/modules/panels/__tests__/market-workbench-update.test.d.ts +0 -1
  122. package/dist/types/modules/panels/__tests__/market.test.d.ts +0 -1
  123. package/dist/types/modules/panels/__tests__/plugin-actions.test.d.ts +0 -1
  124. package/dist/types/modules/panels/__tests__/plugin-ledger.test.d.ts +0 -1
  125. package/dist/types/modules/panels/__tests__/plugin-panel-market-parity.test.d.ts +0 -1
  126. package/dist/types/modules/panels/__tests__/plugin-panel.test.d.ts +0 -1
  127. package/dist/types/modules/panels/__tests__/plugin-section.test.d.ts +0 -1
  128. package/dist/types/modules/panels/__tests__/plugin-tool-units-harness.d.ts +0 -69
  129. package/dist/types/modules/panels/__tests__/plugin-tool-units.test.d.ts +0 -1
  130. package/dist/types/modules/panels/__tests__/prefs.test.d.ts +0 -1
  131. package/dist/types/modules/panels/__tests__/routes.test.d.ts +0 -1
  132. package/dist/types/modules/panels/__tests__/scope-machine.test.d.ts +0 -1
  133. package/dist/types/modules/panels/__tests__/scope-support.d.ts +0 -51
  134. package/dist/types/modules/panels/__tests__/scope.test.d.ts +0 -1
  135. package/dist/types/modules/panels/__tests__/service-release.test.d.ts +0 -1
  136. package/dist/types/modules/panels/__tests__/service.test.d.ts +0 -1
  137. package/dist/types/modules/panels/__tests__/tools.test.d.ts +0 -1
  138. package/dist/types/shared/__tests__/host-import-barrel.test.d.ts +0 -1
  139. package/dist/types/shared/__tests__/i18n-keys.test.d.ts +0 -1
  140. package/dist/types/shared/api/__tests__/context-ref.test.d.ts +0 -1
  141. package/dist/types/shared/api/host/__tests__/approval-note-injection.test.d.ts +0 -1
  142. package/dist/types/shared/api/host/__tests__/capability-force.test.d.ts +0 -1
  143. package/dist/types/shared/api/host/__tests__/cli-error.test.d.ts +0 -1
  144. package/dist/types/shared/api/host/__tests__/config-sync.test.d.ts +0 -1
  145. package/dist/types/shared/api/host/__tests__/context-inject.test.d.ts +0 -1
  146. package/dist/types/shared/api/host/__tests__/data-root.test.d.ts +0 -1
  147. package/dist/types/shared/api/host/__tests__/diy-debug-act.test.d.ts +0 -1
  148. package/dist/types/shared/api/host/__tests__/events.test.d.ts +0 -1
  149. package/dist/types/shared/api/host/__tests__/fact-writes.test.d.ts +0 -1
  150. package/dist/types/shared/api/host/__tests__/facts.test.d.ts +0 -1
  151. package/dist/types/shared/api/host/__tests__/host-args.test.d.ts +0 -1
  152. package/dist/types/shared/api/host/__tests__/long-task.test.d.ts +0 -1
  153. package/dist/types/shared/api/host/__tests__/panel-state-route.test.d.ts +0 -1
  154. package/dist/types/shared/api/host/__tests__/plugin-catalog.test.d.ts +0 -1
  155. package/dist/types/shared/api/host/__tests__/plugin-login-hint.test.d.ts +0 -1
  156. package/dist/types/shared/api/host/__tests__/plugin-source.test.d.ts +0 -1
  157. package/dist/types/shared/api/host/__tests__/plugin-tools.test.d.ts +0 -1
  158. package/dist/types/shared/api/host/__tests__/png-fixtures.d.ts +0 -17
  159. package/dist/types/shared/api/host/__tests__/post-channels.test.d.ts +0 -1
  160. package/dist/types/shared/api/host/__tests__/preflight-fixtures.d.ts +0 -19
  161. package/dist/types/shared/api/host/__tests__/preflight-warm.test.d.ts +0 -1
  162. package/dist/types/shared/api/host/__tests__/preflight.test.d.ts +0 -1
  163. package/dist/types/shared/api/host/__tests__/probe-cache.test.d.ts +0 -1
  164. package/dist/types/shared/api/host/__tests__/probe-libs.test.d.ts +0 -1
  165. package/dist/types/shared/api/host/__tests__/probe-units-cache.test.d.ts +0 -1
  166. package/dist/types/shared/api/host/__tests__/probe-units-npm-source.test.d.ts +0 -1
  167. package/dist/types/shared/api/host/__tests__/probe-units-release.test.d.ts +0 -1
  168. package/dist/types/shared/api/host/__tests__/probe-units.test.d.ts +0 -1
  169. package/dist/types/shared/api/host/__tests__/probe-verdict.test.d.ts +0 -1
  170. package/dist/types/shared/api/host/__tests__/profile-registries.test.d.ts +0 -1
  171. package/dist/types/shared/api/host/__tests__/provision-dependency.test.d.ts +0 -1
  172. package/dist/types/shared/api/host/__tests__/registry-declaration.test.d.ts +0 -1
  173. package/dist/types/shared/api/host/__tests__/release.test.d.ts +0 -1
  174. package/dist/types/shared/api/host/__tests__/self-update.test.d.ts +0 -1
  175. package/dist/types/shared/api/host/__tests__/tool-approval.test.d.ts +0 -1
  176. package/dist/types/shared/api/host/__tests__/tool-kit.test.d.ts +0 -1
  177. package/dist/types/shared/api/host/__tests__/tool-scope.test.d.ts +0 -1
  178. package/dist/types/shared/api/host/__tests__/tool-surface.test.d.ts +0 -1
  179. package/dist/types/shared/api/host/__tests__/unit-face.test.d.ts +0 -1
  180. package/dist/types/shared/api/host/__tests__/unit-readiness.test.d.ts +0 -1
  181. package/dist/types/shared/api/host/__tests__/unit-registry.test.d.ts +0 -1
  182. package/dist/types/shared/api/host/__tests__/unit-roster-facts.test.d.ts +0 -1
  183. package/dist/types/shared/api/host/__tests__/value-handoff.test.d.ts +0 -1
  184. package/dist/types/shared/api/host/__tests__/version-range.test.d.ts +0 -1
  185. package/dist/types/shared/api/host/__tests__/workspace-store.test.d.ts +0 -1
  186. package/dist/types/shared/api/host/__tests__/write-result.test.d.ts +0 -1
  187. package/dist/types/shared/hooks/__tests__/mock-mode.test.d.ts +0 -1
  188. package/dist/types/shared/hooks/__tests__/use-api-resource.test.d.ts +0 -1
  189. package/dist/types/shared/hooks/__tests__/use-capabilities.test.d.ts +0 -1
  190. package/dist/types/shared/hooks/__tests__/use-visible-busy.test.d.ts +0 -1
  191. package/dist/types/shared/hooks/mock-mode.d.ts +0 -2
  192. package/dist/types/shared/hooks/use-fixture-pick.d.ts +0 -20
  193. /package/dist/types/app/{__tests__/workbench-styles.test.d.ts → layouts/rightbar/rightbar-width.test.d.ts} +0 -0
@@ -1,10 +1,10 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
4
4
  import { deflateSync, inflateSync } from "node:zlib";
5
- import os from "node:os";
6
5
  import { spawn } from "node:child_process";
7
6
  import { fileURLToPath } from "node:url";
7
+ import os from "node:os";
8
8
  //#region ../shared/dist/config/constants.js
9
9
  var PLUGIN_ID = "@dsh-workbench/core";
10
10
  var RELEASE_SIBLINGS = ["@dsh-workbench/shared", "@dsh-workbench/module-tools"];
@@ -2965,6 +2965,43 @@ function inWorkshopSession(ctx, workspace) {
2965
2965
  const root = workshopPathOf(ctx);
2966
2966
  return root !== null && workspace !== null && insideWorkshop(root, workspace);
2967
2967
  }
2968
+ function workshopSyncOf(workshopRoot) {
2969
+ let parsed;
2970
+ try {
2971
+ parsed = JSON.parse(readFileSync(join(workshopRoot, ".sync.json"), "utf8"));
2972
+ } catch {
2973
+ return;
2974
+ }
2975
+ if (typeof parsed !== "object" || parsed === null) return void 0;
2976
+ const record = parsed;
2977
+ const state = asText$2(record["state"]);
2978
+ if (state !== "ok" && state !== "syncing" && state !== "failed") return void 0;
2979
+ const reason = asText$2(record["reason"]);
2980
+ return {
2981
+ state,
2982
+ version: asText$2(record["version"]),
2983
+ at: asText$2(record["at"]),
2984
+ ...reason === "" ? {} : { reason }
2985
+ };
2986
+ }
2987
+ function workshopToolchainOf(workshopRoot) {
2988
+ const versionOf = (name) => {
2989
+ try {
2990
+ return asText$2(JSON.parse(readFileSync(join(workshopRoot, "node_modules", "@dsh-workbench", name, "package.json"), "utf8")).version);
2991
+ } catch {
2992
+ return "";
2993
+ }
2994
+ };
2995
+ const core = versionOf("core");
2996
+ const moduleTools = versionOf("module-tools");
2997
+ const shared = versionOf("shared");
2998
+ if (core === "" || moduleTools === "" || shared === "") return void 0;
2999
+ return {
3000
+ core,
3001
+ moduleTools,
3002
+ shared
3003
+ };
3004
+ }
2968
3005
  //#endregion
2969
3006
  //#region src/modules/panels/unit.ts
2970
3007
  var PANELS_LIST_TOOL = "workbench_panels_list";
@@ -5761,134 +5798,425 @@ function moduleBuildFacts(dir) {
5761
5798
  };
5762
5799
  }
5763
5800
  //#endregion
5764
- //#region src/modules/panels/model/diy-report.ts
5765
- function healthOf(options) {
5766
- const { registry, unit, installed } = options;
5767
- const refused = registry?.refusals().find((one) => one.unit === unit);
5768
- if (refused !== void 0) return {
5769
- state: "failed",
5770
- reason: refused.reasons.join(";")
5801
+ //#region src/shared/api/host/runtime-seed.ts
5802
+ function runtimeSeedPaths(root) {
5803
+ const opencliRoot = join(root, "opencli");
5804
+ const opencliBinDir = join(opencliRoot, "bin");
5805
+ const workshop = join(root, "diy");
5806
+ return {
5807
+ root,
5808
+ entryDir: join(root, "bin"),
5809
+ opencliRoot,
5810
+ opencliBinDir,
5811
+ opencliBin: join(opencliBinDir, "opencli"),
5812
+ opencliHome: join(opencliRoot, "home"),
5813
+ workshop,
5814
+ workshopPackages: join(workshop, "packages"),
5815
+ workshopManifest: join(workshop, "package.json")
5771
5816
  };
5772
- const failed = registry?.failureOf(unit);
5773
- if (failed !== void 0) return {
5774
- state: "failed",
5775
- reason: failed
5817
+ }
5818
+ function runtimeSeedRootOf(ctx) {
5819
+ const homePath = ctx.get("dshHomePath");
5820
+ if (typeof homePath !== "function") return null;
5821
+ try {
5822
+ return homePath("workbench");
5823
+ } catch {
5824
+ return null;
5825
+ }
5826
+ }
5827
+ function packageRootOf(moduleUrl) {
5828
+ return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
5829
+ }
5830
+ function isInstalledCopy(packageRoot) {
5831
+ return packageRoot.split(sep).includes("node_modules");
5832
+ }
5833
+ function opencliWrapperScript(packageRoot, home) {
5834
+ const script = join(packageRoot, "bin", "opencli-isolated");
5835
+ return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
5836
+ }
5837
+ function workshopSeedPlan(packageRoot, home, root) {
5838
+ const script = join(packageRoot, "scripts", "seed-workshop.sh");
5839
+ const env = {
5840
+ DSH_HOME: home,
5841
+ DSH_WB_SHELL_ROOT: packageRoot
5776
5842
  };
5777
- if (registry?.all().some((one) => one.unit === unit) === true) return { state: "ok" };
5843
+ const log = join(root, "seed.log");
5778
5844
  return {
5779
- state: "absent",
5780
- reason: installed ? "工作台还没读到它的声明 —— 新装的模块要重启一次工作台" : "还没装"
5845
+ entry: {
5846
+ file: "bash",
5847
+ args: [script, "entry"],
5848
+ env,
5849
+ log
5850
+ },
5851
+ full: {
5852
+ file: "bash",
5853
+ args: [script],
5854
+ env,
5855
+ log
5856
+ }
5781
5857
  };
5782
5858
  }
5783
- function healthText(health) {
5784
- if (health.state === "ok") return "是";
5785
- if (health.state === "failed") return `不是 —— ${health.reason ?? "(没给原因)"}`;
5786
- return `还没有(${health.reason ?? "还不知道"})`;
5859
+ function runDetached(command, onError, spawnImpl = spawn) {
5860
+ let fd = null;
5861
+ try {
5862
+ fd = openSync(command.log, "a");
5863
+ const child = spawnImpl(command.file, [...command.args], {
5864
+ env: {
5865
+ ...process.env,
5866
+ ...command.env
5867
+ },
5868
+ detached: true,
5869
+ stdio: [
5870
+ "ignore",
5871
+ fd,
5872
+ fd
5873
+ ]
5874
+ });
5875
+ child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
5876
+ child.unref();
5877
+ } catch (error) {
5878
+ onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
5879
+ } finally {
5880
+ if (fd !== null) closeQuietly(fd);
5881
+ }
5787
5882
  }
5788
- function ageText(at, now) {
5789
- const seconds = Math.max(0, Math.round((now - at) / 1e3));
5790
- if (seconds < 10) return "刚刚";
5791
- if (seconds < 60) return `${String(seconds)} 秒前`;
5792
- const minutes = Math.round(seconds / 60);
5793
- if (minutes < 60) return `${String(minutes)} 分钟前`;
5794
- return `${String(Math.round(minutes / 60))} 小时前`;
5883
+ function closeQuietly(fd) {
5884
+ try {
5885
+ closeSync(fd);
5886
+ } catch {
5887
+ return;
5888
+ }
5795
5889
  }
5796
- function stampOf(at) {
5797
- return `${new Date(at).toISOString().slice(11, 19)}Z`;
5890
+ function prependPath(entryDir, env = process.env) {
5891
+ const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
5892
+ if (parts.includes(entryDir)) return false;
5893
+ env.PATH = [entryDir, ...parts].join(delimiter);
5894
+ return true;
5798
5895
  }
5799
- function readText$1(read, now) {
5800
- const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
5801
- if (read.phase === "ready") return `${read.route} 读到了(${when})`;
5802
- if (read.phase === "loading") return `${read.route} 正在读(${when})`;
5803
- if (read.phase === "idle") return `${read.route} 没在取数(${when})`;
5804
- const code = read.error?.code ?? "unknown";
5805
- const message = read.error?.message ?? "";
5806
- return `${read.route} 读不到(${code})${message === "" ? "" : `:${message}`}(${when})`;
5896
+ function seedRuntimeFiles(options) {
5897
+ const paths = runtimeSeedPaths(options.root);
5898
+ const done = [];
5899
+ try {
5900
+ for (const dir of [
5901
+ paths.entryDir,
5902
+ paths.opencliBinDir,
5903
+ paths.opencliHome,
5904
+ paths.workshopPackages
5905
+ ]) mkdirSync(dir, { recursive: true });
5906
+ const wanted = opencliWrapperScript(options.packageRoot, options.home);
5907
+ if (readText$1(paths.opencliBin) !== wanted) {
5908
+ rmSync(paths.opencliBin, { force: true });
5909
+ writeFileSync(paths.opencliBin, wanted, "utf8");
5910
+ chmodSync(paths.opencliBin, 493);
5911
+ done.push(paths.opencliBin);
5912
+ }
5913
+ } catch (error) {
5914
+ options.log?.(`自举没做完:${String(error)}`);
5915
+ }
5916
+ return done;
5807
5917
  }
5808
- function renderPanel(options) {
5809
- const { packagesDir, workspace, rows, board, at, now } = options;
5810
- const head = `工坊:${packagesDir}\n这个工作区:${workspace ?? "(会话不在任何工作区上)"}`;
5811
- const note = at === 0 ? "\n还没有一条面板运行态上报 —— 面板上一个取数 / 渲染都没发生过:要么这个工作区的面板上没开卡片,要么用户那一页还是刷新前的旧版本(上报要页面刷一次才带上)。" : `\n面板上有 ${String(board.length)} 张卡|运行态来自用户那一页(最后一次上报 ${stampOf(at)} · ${ageText(at, now)})`;
5812
- const body = rows.map((row) => {
5813
- const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
5814
- const report = row.report;
5815
- if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
5816
- const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$1(one, now)}`);
5817
- const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
5818
- const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
5819
- return [
5820
- title,
5821
- ...reads,
5822
- ...more,
5823
- crash
5824
- ].join("\n");
5825
- });
5826
- return body.length === 0 ? `${head}${note}` : `${head}${note}\n${body.join("\n")}`;
5918
+ function readText$1(file) {
5919
+ try {
5920
+ return readFileSync(file, "utf8");
5921
+ } catch {
5922
+ return null;
5923
+ }
5827
5924
  }
5828
- function statusOf(options) {
5829
- const { dir, unit, name, installed, known, open, health } = options;
5830
- const facts = moduleBuildFacts(dir);
5831
- const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:workbench_diy_install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 workbench_diy_show" : !open ? "上板:workbench_diy_show(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
5925
+ //#endregion
5926
+ //#region src/shared/api/host/self-update.ts
5927
+ function runningVersionOf(moduleUrl) {
5928
+ try {
5929
+ const read = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8"));
5930
+ return typeof read.version === "string" && read.version !== "" ? read.version : void 0;
5931
+ } catch {
5932
+ return;
5933
+ }
5934
+ }
5935
+ function selfUpdatePaths(dshHome) {
5936
+ const dir = join(dshHome, "workbench");
5832
5937
  return {
5833
- unit,
5834
- name,
5835
5938
  dir,
5836
- installed,
5837
- known,
5838
- open,
5839
- artifacts: facts.artifacts,
5840
- stale: facts.stale,
5841
- health,
5842
- next
5939
+ script: join(dir, "self-update.sh"),
5940
+ marker: join(dir, "self-update.json"),
5941
+ log: join(dir, "self-update.log")
5843
5942
  };
5844
5943
  }
5845
- function renderStatus(packagesDir, lines) {
5846
- if (lines.length === 0) return renderList(packagesDir, []);
5847
- return `工坊:${packagesDir}\n${lines.map((one) => {
5848
- const head = `—— ${one.unit}(${one.name})`;
5849
- const facts = ` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`;
5850
- const artifacts = one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`;
5851
- return [
5852
- head,
5853
- facts,
5854
- ` 起得来:${healthText(one.health)}`,
5855
- artifacts,
5856
- ` 下一步:${one.next}`
5857
- ].join("\n");
5858
- }).join("\n")}`;
5859
- }
5860
- function renderList(packagesDir, modules) {
5861
- if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
5862
- const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
5863
- const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy_install(unit=<上面那个 unit>)";
5864
- return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
5944
+ function recoveryCommand(input) {
5945
+ return `dsh plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
5865
5946
  }
5866
- //#endregion
5867
- //#region src/modules/panels/model/diy-tools.ts
5868
- function registerDiyTools(ctx, market, panels, debug) {
5869
- const tools = ctx.get("tools");
5870
- if (tools === void 0) {
5871
- ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
5872
- return [];
5873
- }
5874
- const listTool = {
5875
- name: DIY_LIST_TOOL,
5876
- description: "DIY 工坊里有哪些模块、装没装 —— 先跑它拿到 unit。普通项目里装模块用 workbench_modules_install。",
5877
- parameters: {
5878
- type: "object",
5879
- properties: {},
5880
- additionalProperties: false
5881
- },
5882
- output: OUTPUT$2,
5883
- async execute(_raw, exec) {
5884
- const { packagesDir } = workshopOf(ctx, exec);
5885
- const { modules } = modulesOf(ctx, packagesDir);
5886
- return plainJson({
5887
- workshop: packagesDir,
5888
- modules,
5889
- text: renderList(packagesDir, modules)
5890
- });
5891
- }
5947
+ function selfUpdateMarker(request, state, version, at) {
5948
+ return {
5949
+ ...request,
5950
+ state,
5951
+ version,
5952
+ at
5953
+ };
5954
+ }
5955
+ function quoted(value) {
5956
+ return `'${value.replaceAll("'", "'\\''")}'`;
5957
+ }
5958
+ function selfUpdateScript(request, paths) {
5959
+ const rescue = recoveryCommand({
5960
+ profile: request.profile,
5961
+ pkg: request.pkg,
5962
+ version: request.from
5963
+ });
5964
+ const lastsOne = recoveryCommand({
5965
+ profile: request.profile,
5966
+ pkg: request.pkg,
5967
+ version: request.to === "" ? "latest" : request.to
5968
+ });
5969
+ const target = request.to === "" ? "latest" : request.to;
5970
+ const landed = request.to === "" ? "is_other" : "is_to";
5971
+ return `#!/usr/bin/env bash
5972
+ set -uo pipefail
5973
+ DSH_HOME=${quoted(request.dshHome)}
5974
+ PKG=${quoted(request.pkg)}
5975
+ PROFILE=${quoted(request.profile)}
5976
+ FROM=${quoted(request.from)}
5977
+ TO=${quoted(target)}
5978
+ MARKER=${quoted(paths.marker)}
5979
+ LOG=${quoted(paths.log)}
5980
+ PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
5981
+ say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
5982
+ on_disk() {
5983
+ node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
5984
+ "$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
5985
+ }
5986
+ write_marker() {
5987
+ printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
5988
+ "$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
5989
+ }
5990
+ install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }
5991
+ is_to() { [ "$(on_disk)" = "$TO" ]; }
5992
+ is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
5993
+ if ! command -v dsh >/dev/null 2>&1; then
5994
+ say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"
5995
+ write_marker failed "$FROM"
5996
+ exit 1
5997
+ fi
5998
+ say "开始 \${FROM} -> \${TO}(不卸任何一行)"
5999
+ write_marker running "$FROM"
6000
+ if install "$TO" && ${landed}; then
6001
+ say "换好了:$(on_disk) 在盘上(目标 \${TO}),重启一次 dsh 才用得上"
6002
+ write_marker ok "$(on_disk)"
6003
+ exit 0
6004
+ fi
6005
+ say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
6006
+ if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
6007
+ say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
6008
+ write_marker rolled-back "$FROM"
6009
+ exit 0
6010
+ fi
6011
+ say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
6012
+ write_marker failed "$(on_disk)"
6013
+ exit 1
6014
+ `;
6015
+ }
6016
+ function planSelfUpdate(request, now = /* @__PURE__ */ new Date()) {
6017
+ const paths = selfUpdatePaths(request.dshHome);
6018
+ return {
6019
+ paths,
6020
+ script: selfUpdateScript(request, paths),
6021
+ marker: selfUpdateMarker(request, "running", request.from, now.toISOString()),
6022
+ command: {
6023
+ file: "bash",
6024
+ args: [paths.script],
6025
+ env: {},
6026
+ log: paths.log
6027
+ }
6028
+ };
6029
+ }
6030
+ function startSelfUpdate(request, onError, spawnImpl = spawn) {
6031
+ const plan = planSelfUpdate(request);
6032
+ mkdirSync(plan.paths.dir, { recursive: true });
6033
+ writeFileSync(plan.paths.script, plan.script, { mode: 493 });
6034
+ chmodSync(plan.paths.script, 493);
6035
+ writeFileSync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
6036
+ runDetached(plan.command, onError, spawnImpl);
6037
+ const forward = recoveryCommand({
6038
+ profile: request.profile,
6039
+ pkg: request.pkg,
6040
+ version: request.to === "" ? "latest" : request.to
6041
+ });
6042
+ return {
6043
+ started: true,
6044
+ paths: plan.paths,
6045
+ forward
6046
+ };
6047
+ }
6048
+ function fixText() {
6049
+ return " 修法:cd \"$DSH_HOME/workbench/diy\" && pnpm install;还不行就把这个工坊目录删掉再重启一次工作台(骨架会自己重建;`packages/` 下你造的模块先留着或另存)。";
6050
+ }
6051
+ function syncingStillRunning(at, now, windowMs) {
6052
+ const started = Date.parse(at);
6053
+ if (!Number.isFinite(started)) return true;
6054
+ return now - started <= windowMs;
6055
+ }
6056
+ function toolchainGap(toolchain, shellVersion) {
6057
+ if (toolchain === void 0) return void 0;
6058
+ const gaps = [
6059
+ ["core", toolchain.core],
6060
+ ["module-tools", toolchain.moduleTools],
6061
+ ["shared", toolchain.shared]
6062
+ ].filter((one) => one[1] !== shellVersion);
6063
+ return gaps.length === 0 ? void 0 : gaps.map((one) => `${one[0]} ${one[1]}`).join(" / ");
6064
+ }
6065
+ function syncNotice(input) {
6066
+ const { sync, shellVersion, toolchain } = input;
6067
+ const now = input.now ?? Date.now();
6068
+ const windowMs = input.windowMs ?? 9e5;
6069
+ if (shellVersion === void 0) return void 0;
6070
+ const gap = toolchainGap(toolchain, shellVersion);
6071
+ if (toolchain !== void 0 && gap === void 0) return void 0;
6072
+ const about = sync !== void 0 && sync.version === shellVersion ? sync : void 0;
6073
+ if (about === void 0 && gap === void 0) return void 0;
6074
+ const where = gap === void 0 ? "工坊里读不到那三个包的版本" : `工坊里 ${gap}`;
6075
+ if (about !== void 0 && about.state === "failed") return [`⚠️ 工坊工具链还是上一代(${where};外壳已经是 ${shellVersion}):${about.reason ?? "(没给原因)"}`, fixText()].join("\n");
6076
+ if (about !== void 0 && about.state === "syncing") {
6077
+ if (syncingStillRunning(about.at, now, windowMs)) return `⏳ 工坊工具链正在后台同步到 ${shellVersion} —— 这段期间按上一代在跑,不用等它。`;
6078
+ return [`⚠️ 工坊工具链上一次同步没走完(这条停在 ${about.at},超过 ${String(Math.round(windowMs / 6e4))} 分钟了):${where},外壳已经是 ${shellVersion}。`, fixText()].join("\n");
6079
+ }
6080
+ if (gap === void 0) return void 0;
6081
+ return `⏳ 工坊工具链还是上一代(工坊里 ${gap},外壳 ${shellVersion})—— 下一轮工作台启动时会对齐到 ${shellVersion};一直这样就看 $DSH_HOME/workbench/seed.log。`;
6082
+ }
6083
+ function healthOf(options) {
6084
+ const { registry, unit, installed } = options;
6085
+ const refused = registry?.refusals().find((one) => one.unit === unit);
6086
+ if (refused !== void 0) return {
6087
+ state: "failed",
6088
+ reason: refused.reasons.join(";")
6089
+ };
6090
+ const failed = registry?.failureOf(unit);
6091
+ if (failed !== void 0) return {
6092
+ state: "failed",
6093
+ reason: failed
6094
+ };
6095
+ if (registry?.all().some((one) => one.unit === unit) === true) return { state: "ok" };
6096
+ return {
6097
+ state: "absent",
6098
+ reason: installed ? "工作台还没读到它的声明 —— 新装的模块要重启一次工作台" : "还没装"
6099
+ };
6100
+ }
6101
+ function healthText(health) {
6102
+ if (health.state === "ok") return "是";
6103
+ if (health.state === "failed") return `不是 —— ${health.reason ?? "(没给原因)"}`;
6104
+ return `还没有(${health.reason ?? "还不知道"})`;
6105
+ }
6106
+ function ageText(at, now) {
6107
+ const seconds = Math.max(0, Math.round((now - at) / 1e3));
6108
+ if (seconds < 10) return "刚刚";
6109
+ if (seconds < 60) return `${String(seconds)} 秒前`;
6110
+ const minutes = Math.round(seconds / 60);
6111
+ if (minutes < 60) return `${String(minutes)} 分钟前`;
6112
+ return `${String(Math.round(minutes / 60))} 小时前`;
6113
+ }
6114
+ function stampOf(at) {
6115
+ return `${new Date(at).toISOString().slice(11, 19)}Z`;
6116
+ }
6117
+ function readText(read, now) {
6118
+ const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
6119
+ if (read.phase === "ready") return `${read.route} 读到了(${when})`;
6120
+ if (read.phase === "loading") return `${read.route} 正在读(${when})`;
6121
+ if (read.phase === "idle") return `${read.route} 没在取数(${when})`;
6122
+ const code = read.error?.code ?? "unknown";
6123
+ const message = read.error?.message ?? "";
6124
+ return `${read.route} 读不到(${code})${message === "" ? "" : `:${message}`}(${when})`;
6125
+ }
6126
+ function renderPanel(options) {
6127
+ const { packagesDir, workspace, rows, board, at, now } = options;
6128
+ const head = `工坊:${packagesDir}\n这个工作区:${workspace ?? "(会话不在任何工作区上)"}`;
6129
+ const note = at === 0 ? "\n还没有一条面板运行态上报 —— 面板上一个取数 / 渲染都没发生过:要么这个工作区的面板上没开卡片,要么用户那一页还是刷新前的旧版本(上报要页面刷一次才带上)。" : `\n面板上有 ${String(board.length)} 张卡|运行态来自用户那一页(最后一次上报 ${stampOf(at)} · ${ageText(at, now)})`;
6130
+ const body = rows.map((row) => {
6131
+ const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
6132
+ const report = row.report;
6133
+ if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
6134
+ const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText(one, now)}`);
6135
+ const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
6136
+ const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
6137
+ return [
6138
+ title,
6139
+ ...reads,
6140
+ ...more,
6141
+ crash
6142
+ ].join("\n");
6143
+ });
6144
+ return body.length === 0 ? `${head}${note}` : `${head}${note}\n${body.join("\n")}`;
6145
+ }
6146
+ function statusOf(options) {
6147
+ const { dir, unit, name, installed, known, open, health } = options;
6148
+ const facts = moduleBuildFacts(dir);
6149
+ const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:workbench_diy_install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 workbench_diy_show" : !open ? "上板:workbench_diy_show(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
6150
+ return {
6151
+ unit,
6152
+ name,
6153
+ dir,
6154
+ installed,
6155
+ known,
6156
+ open,
6157
+ artifacts: facts.artifacts,
6158
+ stale: facts.stale,
6159
+ health,
6160
+ next
6161
+ };
6162
+ }
6163
+ function renderStatus(packagesDir, lines, options = {}) {
6164
+ const root = dirname(packagesDir);
6165
+ const notice = syncNotice({
6166
+ sync: workshopSyncOf(root),
6167
+ shellVersion: options.shellVersion ?? runningVersionOf(import.meta.url),
6168
+ toolchain: workshopToolchainOf(root)
6169
+ });
6170
+ if (lines.length === 0) {
6171
+ const list = renderList(packagesDir, []);
6172
+ return notice === void 0 ? list : `${notice}\n${list}`;
6173
+ }
6174
+ const block = lines.map((one) => {
6175
+ const head = `—— ${one.unit}(${one.name})`;
6176
+ const facts = ` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`;
6177
+ const artifacts = one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`;
6178
+ return [
6179
+ head,
6180
+ facts,
6181
+ ` 起得来:${healthText(one.health)}`,
6182
+ artifacts,
6183
+ ` 下一步:${one.next}`
6184
+ ].join("\n");
6185
+ });
6186
+ return `${notice === void 0 ? `工坊:${packagesDir}` : `工坊:${packagesDir}\n${notice}`}\n${block.join("\n")}`;
6187
+ }
6188
+ function renderList(packagesDir, modules) {
6189
+ if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
6190
+ const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
6191
+ const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy_install(unit=<上面那个 unit>)";
6192
+ return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
6193
+ }
6194
+ //#endregion
6195
+ //#region src/modules/panels/model/diy-tools.ts
6196
+ function registerDiyTools(ctx, market, panels, debug) {
6197
+ const tools = ctx.get("tools");
6198
+ if (tools === void 0) {
6199
+ ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
6200
+ return [];
6201
+ }
6202
+ const listTool = {
6203
+ name: DIY_LIST_TOOL,
6204
+ description: "DIY 工坊里有哪些模块、装没装 —— 先跑它拿到 unit。普通项目里装模块用 workbench_modules_install。",
6205
+ parameters: {
6206
+ type: "object",
6207
+ properties: {},
6208
+ additionalProperties: false
6209
+ },
6210
+ output: OUTPUT$2,
6211
+ async execute(_raw, exec) {
6212
+ const { packagesDir } = workshopOf(ctx, exec);
6213
+ const { modules } = modulesOf(ctx, packagesDir);
6214
+ return plainJson({
6215
+ workshop: packagesDir,
6216
+ modules,
6217
+ text: renderList(packagesDir, modules)
6218
+ });
6219
+ }
5892
6220
  };
5893
6221
  const statusTool = {
5894
6222
  name: DIY_STATUS_TOOL,
@@ -6857,276 +7185,6 @@ async function installRouted(deps) {
6857
7185
  });
6858
7186
  }
6859
7187
  //#endregion
6860
- //#region src/shared/api/host/runtime-seed.ts
6861
- var WORKSHOP_SKILLS_DIR = "workbench-module-diy";
6862
- function runtimeSeedPaths(root) {
6863
- const opencliRoot = join(root, "opencli");
6864
- const opencliBinDir = join(opencliRoot, "bin");
6865
- const workshop = join(root, "diy");
6866
- return {
6867
- root,
6868
- entryDir: join(root, "bin"),
6869
- opencliRoot,
6870
- opencliBinDir,
6871
- opencliBin: join(opencliBinDir, "opencli"),
6872
- opencliHome: join(opencliRoot, "home"),
6873
- workshop,
6874
- workshopPackages: join(workshop, "packages"),
6875
- workshopManifest: join(workshop, "package.json"),
6876
- skillsLink: join(workshop, ".agents", "skills", WORKSHOP_SKILLS_DIR)
6877
- };
6878
- }
6879
- function runtimeSeedRootOf(ctx) {
6880
- const homePath = ctx.get("dshHomePath");
6881
- if (typeof homePath !== "function") return null;
6882
- try {
6883
- return homePath("workbench");
6884
- } catch {
6885
- return null;
6886
- }
6887
- }
6888
- function packageRootOf(moduleUrl) {
6889
- return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
6890
- }
6891
- function isInstalledCopy(packageRoot) {
6892
- return packageRoot.split(sep).includes("node_modules");
6893
- }
6894
- function opencliWrapperScript(packageRoot, home) {
6895
- const script = join(packageRoot, "bin", "opencli-isolated");
6896
- return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
6897
- }
6898
- function workshopSeedPlan(packageRoot, home, root) {
6899
- const script = join(packageRoot, "scripts", "seed-workshop.sh");
6900
- const env = { DSH_HOME: home };
6901
- const log = join(root, "seed.log");
6902
- return {
6903
- entry: {
6904
- file: "bash",
6905
- args: [script, "entry"],
6906
- env,
6907
- log
6908
- },
6909
- full: {
6910
- file: "bash",
6911
- args: [script],
6912
- env,
6913
- log
6914
- }
6915
- };
6916
- }
6917
- function runDetached(command, onError, spawnImpl = spawn) {
6918
- let fd = null;
6919
- try {
6920
- fd = openSync(command.log, "a");
6921
- const child = spawnImpl(command.file, [...command.args], {
6922
- env: {
6923
- ...process.env,
6924
- ...command.env
6925
- },
6926
- detached: true,
6927
- stdio: [
6928
- "ignore",
6929
- fd,
6930
- fd
6931
- ]
6932
- });
6933
- child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
6934
- child.unref();
6935
- } catch (error) {
6936
- onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
6937
- } finally {
6938
- if (fd !== null) closeQuietly(fd);
6939
- }
6940
- }
6941
- function closeQuietly(fd) {
6942
- try {
6943
- closeSync(fd);
6944
- } catch {
6945
- return;
6946
- }
6947
- }
6948
- function prependPath(entryDir, env = process.env) {
6949
- const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
6950
- if (parts.includes(entryDir)) return false;
6951
- env.PATH = [entryDir, ...parts].join(delimiter);
6952
- return true;
6953
- }
6954
- function seedRuntimeFiles(options) {
6955
- const paths = runtimeSeedPaths(options.root);
6956
- const done = [];
6957
- try {
6958
- for (const dir of [
6959
- paths.entryDir,
6960
- paths.opencliBinDir,
6961
- paths.opencliHome,
6962
- paths.workshopPackages
6963
- ]) mkdirSync(dir, { recursive: true });
6964
- const wanted = opencliWrapperScript(options.packageRoot, options.home);
6965
- if (readText(paths.opencliBin) !== wanted) {
6966
- rmSync(paths.opencliBin, { force: true });
6967
- writeFileSync(paths.opencliBin, wanted, "utf8");
6968
- chmodSync(paths.opencliBin, 493);
6969
- done.push(paths.opencliBin);
6970
- }
6971
- if (linkSkills(paths.skillsLink, join(options.packageRoot, "skills", "workbench-module-diy"), options.log)) done.push(paths.skillsLink);
6972
- } catch (error) {
6973
- options.log?.(`自举没做完:${String(error)}`);
6974
- }
6975
- return done;
6976
- }
6977
- function readText(file) {
6978
- try {
6979
- return readFileSync(file, "utf8");
6980
- } catch {
6981
- return null;
6982
- }
6983
- }
6984
- function readLink(link) {
6985
- try {
6986
- return readlinkSync(link);
6987
- } catch {
6988
- return null;
6989
- }
6990
- }
6991
- function linkSkills(link, source, log) {
6992
- if (!existsSync(source) || readLink(link) === source) return false;
6993
- try {
6994
- mkdirSync(dirname(link), { recursive: true });
6995
- rmSync(link, {
6996
- force: true,
6997
- recursive: true
6998
- });
6999
- symlinkSync(source, link, "dir");
7000
- return true;
7001
- } catch (error) {
7002
- log?.(`DIY 指引没挂上(${link}):${String(error)}`);
7003
- return false;
7004
- }
7005
- }
7006
- //#endregion
7007
- //#region src/shared/api/host/self-update.ts
7008
- function runningVersionOf(moduleUrl) {
7009
- try {
7010
- const read = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8"));
7011
- return typeof read.version === "string" && read.version !== "" ? read.version : void 0;
7012
- } catch {
7013
- return;
7014
- }
7015
- }
7016
- function selfUpdatePaths(dshHome) {
7017
- const dir = join(dshHome, "workbench");
7018
- return {
7019
- dir,
7020
- script: join(dir, "self-update.sh"),
7021
- marker: join(dir, "self-update.json"),
7022
- log: join(dir, "self-update.log")
7023
- };
7024
- }
7025
- function recoveryCommand(input) {
7026
- return `dsh plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
7027
- }
7028
- function selfUpdateMarker(request, state, version, at) {
7029
- return {
7030
- ...request,
7031
- state,
7032
- version,
7033
- at
7034
- };
7035
- }
7036
- function quoted(value) {
7037
- return `'${value.replaceAll("'", "'\\''")}'`;
7038
- }
7039
- function selfUpdateScript(request, paths) {
7040
- const rescue = recoveryCommand({
7041
- profile: request.profile,
7042
- pkg: request.pkg,
7043
- version: request.from
7044
- });
7045
- const lastsOne = recoveryCommand({
7046
- profile: request.profile,
7047
- pkg: request.pkg,
7048
- version: request.to === "" ? "latest" : request.to
7049
- });
7050
- const target = request.to === "" ? "latest" : request.to;
7051
- const landed = request.to === "" ? "is_other" : "is_to";
7052
- return `#!/usr/bin/env bash
7053
- set -uo pipefail
7054
- DSH_HOME=${quoted(request.dshHome)}
7055
- PKG=${quoted(request.pkg)}
7056
- PROFILE=${quoted(request.profile)}
7057
- FROM=${quoted(request.from)}
7058
- TO=${quoted(target)}
7059
- MARKER=${quoted(paths.marker)}
7060
- LOG=${quoted(paths.log)}
7061
- PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
7062
- say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
7063
- on_disk() {
7064
- node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
7065
- "$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
7066
- }
7067
- write_marker() {
7068
- printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
7069
- "$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
7070
- }
7071
- install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }
7072
- is_to() { [ "$(on_disk)" = "$TO" ]; }
7073
- is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
7074
- if ! command -v dsh >/dev/null 2>&1; then
7075
- say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"
7076
- write_marker failed "$FROM"
7077
- exit 1
7078
- fi
7079
- say "开始 \${FROM} -> \${TO}(不卸任何一行)"
7080
- write_marker running "$FROM"
7081
- if install "$TO" && ${landed}; then
7082
- say "换好了:$(on_disk) 在盘上(目标 \${TO}),重启一次 dsh 才用得上"
7083
- write_marker ok "$(on_disk)"
7084
- exit 0
7085
- fi
7086
- say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
7087
- if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
7088
- say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
7089
- write_marker rolled-back "$FROM"
7090
- exit 0
7091
- fi
7092
- say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
7093
- write_marker failed "$(on_disk)"
7094
- exit 1
7095
- `;
7096
- }
7097
- function planSelfUpdate(request, now = /* @__PURE__ */ new Date()) {
7098
- const paths = selfUpdatePaths(request.dshHome);
7099
- return {
7100
- paths,
7101
- script: selfUpdateScript(request, paths),
7102
- marker: selfUpdateMarker(request, "running", request.from, now.toISOString()),
7103
- command: {
7104
- file: "bash",
7105
- args: [paths.script],
7106
- env: {},
7107
- log: paths.log
7108
- }
7109
- };
7110
- }
7111
- function startSelfUpdate(request, onError, spawnImpl = spawn) {
7112
- const plan = planSelfUpdate(request);
7113
- mkdirSync(plan.paths.dir, { recursive: true });
7114
- writeFileSync(plan.paths.script, plan.script, { mode: 493 });
7115
- chmodSync(plan.paths.script, 493);
7116
- writeFileSync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
7117
- runDetached(plan.command, onError, spawnImpl);
7118
- const forward = recoveryCommand({
7119
- profile: request.profile,
7120
- pkg: request.pkg,
7121
- version: request.to === "" ? "latest" : request.to
7122
- });
7123
- return {
7124
- started: true,
7125
- paths: plan.paths,
7126
- forward
7127
- };
7128
- }
7129
- //#endregion
7130
7188
  //#region src/modules/panels/model/market-workbench-update.ts
7131
7189
  async function updateWorkbenchOnce(deps) {
7132
7190
  const asked = deps.asked.trim();