@dsh-workbench/core 0.1.0

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 (169) hide show
  1. package/README.md +55 -0
  2. package/bin/dsh-workbench +200 -0
  3. package/bin/opencli-isolated +61 -0
  4. package/client.js +5334 -0
  5. package/cordis.patch.yml +42 -0
  6. package/dist/client/client.body.js +5323 -0
  7. package/dist/client/index.js +5324 -0
  8. package/dist/host/index.js +5420 -0
  9. package/dist/types/app/client.d.ts +1476 -0
  10. package/dist/types/app/host.d.ts +4 -0
  11. package/dist/types/app/layouts/__tests__/panel-hint.test.d.ts +1 -0
  12. package/dist/types/app/layouts/__tests__/project-selection.test.d.ts +1 -0
  13. package/dist/types/app/layouts/env-gate/__tests__/force-plan.test.d.ts +1 -0
  14. package/dist/types/app/layouts/env-gate/__tests__/gate-hidden.test.d.ts +1 -0
  15. package/dist/types/app/layouts/env-gate/__tests__/gate-mocks.test.d.ts +1 -0
  16. package/dist/types/app/layouts/env-gate/boot-gate.d.ts +16 -0
  17. package/dist/types/app/layouts/env-gate/gate-dom.d.ts +4 -0
  18. package/dist/types/app/layouts/env-gate/gate-mock.d.ts +18 -0
  19. package/dist/types/app/layouts/env-gate/gate-text.d.ts +13 -0
  20. package/dist/types/app/layouts/market/panel.d.ts +4 -0
  21. package/dist/types/app/layouts/panel-hint.d.ts +9 -0
  22. package/dist/types/app/layouts/project-selection.d.ts +3 -0
  23. package/dist/types/app/layouts/rightbar/BoardEmpty.d.ts +6 -0
  24. package/dist/types/app/layouts/rightbar/BoardMockStrip.d.ts +5 -0
  25. package/dist/types/app/layouts/rightbar/Breadcrumbs.d.ts +11 -0
  26. package/dist/types/app/layouts/rightbar/ExpandButton.d.ts +9 -0
  27. package/dist/types/app/layouts/rightbar/MarketFreshLines.d.ts +5 -0
  28. package/dist/types/app/layouts/rightbar/MarketView.d.ts +14 -0
  29. package/dist/types/app/layouts/rightbar/ModulesPage.d.ts +15 -0
  30. package/dist/types/app/layouts/rightbar/PanelModules.d.ts +14 -0
  31. package/dist/types/app/layouts/rightbar/Rightbar.d.ts +24 -0
  32. package/dist/types/app/layouts/rightbar/__tests__/board-mock.test.d.ts +1 -0
  33. package/dist/types/app/layouts/rightbar/__tests__/board-plan.test.d.ts +1 -0
  34. package/dist/types/app/layouts/rightbar/__tests__/board-selection.test.d.ts +1 -0
  35. package/dist/types/app/layouts/rightbar/__tests__/breadcrumbs.test.d.ts +1 -0
  36. package/dist/types/app/layouts/rightbar/__tests__/market-freshness.test.d.ts +1 -0
  37. package/dist/types/app/layouts/rightbar/__tests__/market-mock.test.d.ts +1 -0
  38. package/dist/types/app/layouts/rightbar/__tests__/market-rows-libs.test.d.ts +1 -0
  39. package/dist/types/app/layouts/rightbar/__tests__/market-rows.test.d.ts +1 -0
  40. package/dist/types/app/layouts/rightbar/__tests__/market-shared.test.d.ts +1 -0
  41. package/dist/types/app/layouts/rightbar/__tests__/panel-mock.test.d.ts +1 -0
  42. package/dist/types/app/layouts/rightbar/__tests__/suite-world.test.d.ts +1 -0
  43. package/dist/types/app/layouts/rightbar/__tests__/use-panels.test.d.ts +1 -0
  44. package/dist/types/app/layouts/rightbar/board-mock.d.ts +22 -0
  45. package/dist/types/app/layouts/rightbar/board-plan.d.ts +25 -0
  46. package/dist/types/app/layouts/rightbar/board-selection.d.ts +15 -0
  47. package/dist/types/app/layouts/rightbar/market-freshness.d.ts +13 -0
  48. package/dist/types/app/layouts/rightbar/market-mock.d.ts +29 -0
  49. package/dist/types/app/layouts/rightbar/market-rows.d.ts +45 -0
  50. package/dist/types/app/layouts/rightbar/panel-mock.d.ts +26 -0
  51. package/dist/types/app/layouts/rightbar/registry.d.ts +11 -0
  52. package/dist/types/app/layouts/rightbar/registry.test.d.ts +1 -0
  53. package/dist/types/app/layouts/rightbar/store.d.ts +23 -0
  54. package/dist/types/app/layouts/rightbar/store.test.d.ts +1 -0
  55. package/dist/types/app/layouts/rightbar/suite-world.d.ts +14 -0
  56. package/dist/types/app/layouts/rightbar/use-panels.d.ts +15 -0
  57. package/dist/types/app/layouts/sidebar/PanelNav.d.ts +13 -0
  58. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +10 -0
  59. package/dist/types/app/layouts/sidebar/SessionList.d.ts +13 -0
  60. package/dist/types/app/layouts/sidebar/Sidebar.d.ts +25 -0
  61. package/dist/types/app/layouts/sidebar/WorkspaceSection.d.ts +7 -0
  62. package/dist/types/app/layouts/sidebar/__tests__/session-pick.test.d.ts +1 -0
  63. package/dist/types/app/layouts/sidebar/session-pick.d.ts +20 -0
  64. package/dist/types/app/units/host.d.ts +4 -0
  65. package/dist/types/app/units/index.d.ts +4 -0
  66. package/dist/types/app/units/registry.d.ts +12 -0
  67. package/dist/types/modules/panels/__tests__/config.test.d.ts +1 -0
  68. package/dist/types/modules/panels/__tests__/face.test.d.ts +1 -0
  69. package/dist/types/modules/panels/__tests__/market-dependency.test.d.ts +1 -0
  70. package/dist/types/modules/panels/__tests__/market-tools.test.d.ts +1 -0
  71. package/dist/types/modules/panels/__tests__/market.test.d.ts +1 -0
  72. package/dist/types/modules/panels/__tests__/prefs.test.d.ts +1 -0
  73. package/dist/types/modules/panels/__tests__/routes.test.d.ts +1 -0
  74. package/dist/types/modules/panels/__tests__/service.test.d.ts +1 -0
  75. package/dist/types/modules/panels/__tests__/tools.test.d.ts +1 -0
  76. package/dist/types/modules/panels/index.d.ts +2 -0
  77. package/dist/types/modules/panels/model/config.d.ts +18 -0
  78. package/dist/types/modules/panels/model/handoff.d.ts +8 -0
  79. package/dist/types/modules/panels/model/handoff.test.d.ts +1 -0
  80. package/dist/types/modules/panels/model/market-dependency.d.ts +41 -0
  81. package/dist/types/modules/panels/model/market-dispatch.d.ts +14 -0
  82. package/dist/types/modules/panels/model/market-tools.d.ts +3 -0
  83. package/dist/types/modules/panels/model/market.d.ts +49 -0
  84. package/dist/types/modules/panels/model/prefs.d.ts +13 -0
  85. package/dist/types/modules/panels/model/routes.d.ts +4 -0
  86. package/dist/types/modules/panels/model/routes.test.d.ts +1 -0
  87. package/dist/types/modules/panels/model/service.d.ts +25 -0
  88. package/dist/types/modules/panels/model/tools.d.ts +4 -0
  89. package/dist/types/modules/panels/unit.d.ts +4 -0
  90. package/dist/types/modules/sessions/index.d.ts +1 -0
  91. package/dist/types/modules/sessions/model/session-node.d.ts +25 -0
  92. package/dist/types/modules/sessions/model/session-node.test.d.ts +1 -0
  93. package/dist/types/shared/__tests__/i18n-keys.test.d.ts +1 -0
  94. package/dist/types/shared/api/__tests__/context-ref.test.d.ts +1 -0
  95. package/dist/types/shared/api/context-chips.d.ts +10 -0
  96. package/dist/types/shared/api/host/__tests__/capability-force.test.d.ts +1 -0
  97. package/dist/types/shared/api/host/__tests__/cli-error.test.d.ts +1 -0
  98. package/dist/types/shared/api/host/__tests__/context-inject.test.d.ts +1 -0
  99. package/dist/types/shared/api/host/__tests__/data-root.test.d.ts +1 -0
  100. package/dist/types/shared/api/host/__tests__/events.test.d.ts +1 -0
  101. package/dist/types/shared/api/host/__tests__/fact-writes.test.d.ts +1 -0
  102. package/dist/types/shared/api/host/__tests__/facts.test.d.ts +1 -0
  103. package/dist/types/shared/api/host/__tests__/long-task.test.d.ts +1 -0
  104. package/dist/types/shared/api/host/__tests__/plugin-source.test.d.ts +1 -0
  105. package/dist/types/shared/api/host/__tests__/preflight-fixtures.d.ts +19 -0
  106. package/dist/types/shared/api/host/__tests__/preflight-warm.test.d.ts +1 -0
  107. package/dist/types/shared/api/host/__tests__/preflight.test.d.ts +1 -0
  108. package/dist/types/shared/api/host/__tests__/probe-cache.test.d.ts +1 -0
  109. package/dist/types/shared/api/host/__tests__/probe-libs.test.d.ts +1 -0
  110. package/dist/types/shared/api/host/__tests__/probe-units-cache.test.d.ts +1 -0
  111. package/dist/types/shared/api/host/__tests__/probe-units-npm-source.test.d.ts +1 -0
  112. package/dist/types/shared/api/host/__tests__/probe-units-release.test.d.ts +1 -0
  113. package/dist/types/shared/api/host/__tests__/probe-units.test.d.ts +1 -0
  114. package/dist/types/shared/api/host/__tests__/probe-verdict.test.d.ts +1 -0
  115. package/dist/types/shared/api/host/__tests__/provision-dependency.test.d.ts +1 -0
  116. package/dist/types/shared/api/host/__tests__/release.test.d.ts +1 -0
  117. package/dist/types/shared/api/host/__tests__/tool-approval.test.d.ts +1 -0
  118. package/dist/types/shared/api/host/__tests__/tool-kit.test.d.ts +1 -0
  119. package/dist/types/shared/api/host/__tests__/tool-scope.test.d.ts +1 -0
  120. package/dist/types/shared/api/host/__tests__/tool-surface.test.d.ts +1 -0
  121. package/dist/types/shared/api/host/__tests__/unit-face.test.d.ts +1 -0
  122. package/dist/types/shared/api/host/__tests__/unit-readiness.test.d.ts +1 -0
  123. package/dist/types/shared/api/host/__tests__/unit-registry.test.d.ts +1 -0
  124. package/dist/types/shared/api/host/__tests__/value-handoff.test.d.ts +1 -0
  125. package/dist/types/shared/api/host/__tests__/version-range.test.d.ts +1 -0
  126. package/dist/types/shared/api/host/__tests__/workspace-store.test.d.ts +1 -0
  127. package/dist/types/shared/api/host/__tests__/write-result.test.d.ts +1 -0
  128. package/dist/types/shared/api/host/context-inject.d.ts +24 -0
  129. package/dist/types/shared/api/host/events.d.ts +9 -0
  130. package/dist/types/shared/api/host/git.test.d.ts +1 -0
  131. package/dist/types/shared/api/host/http.test.d.ts +1 -0
  132. package/dist/types/shared/api/host/mount-units.d.ts +52 -0
  133. package/dist/types/shared/api/host/opencli.test.d.ts +1 -0
  134. package/dist/types/shared/api/host/preflight.d.ts +58 -0
  135. package/dist/types/shared/api/host/probe-cache.d.ts +6 -0
  136. package/dist/types/shared/api/host/probe-libs.d.ts +11 -0
  137. package/dist/types/shared/api/host/probe-units.d.ts +17 -0
  138. package/dist/types/shared/api/host/probe-verdict.d.ts +7 -0
  139. package/dist/types/shared/api/host/release.d.ts +18 -0
  140. package/dist/types/shared/api/host/shell.test.d.ts +1 -0
  141. package/dist/types/shared/api/host/tool-scope.d.ts +32 -0
  142. package/dist/types/shared/api/host/unit-readiness.d.ts +7 -0
  143. package/dist/types/shared/api/host/unit-registry.d.ts +9 -0
  144. package/dist/types/shared/api/host/version-range.d.ts +2 -0
  145. package/dist/types/shared/components/BlockerState.d.ts +11 -0
  146. package/dist/types/shared/hooks/__tests__/mock-mode.test.d.ts +1 -0
  147. package/dist/types/shared/hooks/__tests__/use-api-resource.test.d.ts +1 -0
  148. package/dist/types/shared/hooks/__tests__/use-capabilities.test.d.ts +1 -0
  149. package/dist/types/shared/hooks/__tests__/use-visible-busy.test.d.ts +1 -0
  150. package/dist/types/shared/hooks/fact-transports.d.ts +2 -0
  151. package/dist/types/shared/hooks/mock-mode.d.ts +1 -0
  152. package/dist/types/shared/hooks/use-api-resource.d.ts +18 -0
  153. package/dist/types/shared/hooks/use-blocker-actions.d.ts +11 -0
  154. package/dist/types/shared/hooks/use-capabilities.d.ts +25 -0
  155. package/dist/types/shared/hooks/use-capability-gate.d.ts +20 -0
  156. package/dist/types/shared/hooks/use-card-summary.d.ts +8 -0
  157. package/dist/types/shared/hooks/use-facts.d.ts +2 -0
  158. package/dist/types/shared/hooks/use-fixture-pick.d.ts +20 -0
  159. package/dist/types/shared/hooks/use-host-write.d.ts +17 -0
  160. package/dist/types/shared/hooks/use-long-task.d.ts +14 -0
  161. package/dist/types/shared/hooks/use-unit-board.d.ts +32 -0
  162. package/dist/types/shared/hooks/use-visible-busy.d.ts +8 -0
  163. package/docs/HANDOFF.md +207 -0
  164. package/docs/OPERATIONS.md +109 -0
  165. package/icon.svg +29 -0
  166. package/package.json +97 -0
  167. package/profile/isolation.patch.yml +35 -0
  168. package/profile/sandbox.patch.yml +26 -0
  169. package/scripts/seed-profile.sh +199 -0
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # dsh-workbench
2
+
3
+ 基于 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 **Agent 工作台**。
4
+ 一个 dsh 插件包:换掉 dsh 的左栏,中列保留 dsh 的对话,右侧加一列工作台自研的数据面板。
5
+ **dsh 本体一行不改**,两个实例同时跑。
6
+
7
+ > **产品定位:所有事情都是「告诉 agent 去做」。**
8
+ > 工作台不是操作台,是交代任务 + 看 agent 干得怎么样的地方。
9
+ > 加任何功能前,先过 [`DESIGN.md`](../../DESIGN.md) 的四条硬规则与三个评审问题。
10
+
11
+ ```bash
12
+ dsh web # :3080 —— 参考基线,原样不动
13
+ ./bin/dsh-workbench # :3081 —— 工作台,与上面同时运行
14
+ pnpm run dev # 改 src/** 后自动重建并热换(不重启、不刷新)
15
+ ```
16
+
17
+ ```
18
+ dsh web (:3080) dsh workbench (:3081)
19
+ ┌ 左栏 ──────┬ 对话 ───┐ ┌ 左栏 ──────┬ 对话 ────┬ 数据面板 ──┐
20
+ │ ◆ 品牌 │ │ │ ◆ 品牌 │ │ 看板 │
21
+ │ [ + 新会话]│ │ │ [ + 新会话]│ │ 服务状态 │
22
+ │ ◧ 面板 │ │ │ ▸ 项目 │ │ 缺陷分布 │
23
+ │ 会话… │ │ │ ▸ 会话 │ (常驻) │ 最近缺陷 │
24
+ └────────────┴─────────┘ └────────────┴──────────┴────────────┘
25
+ ```
26
+
27
+ **当前进度**:宿主半侧(opencli 数据通路 + 只读投影 + agent 工具)已迁到 TypeScript 并跑通;
28
+ 左栏已实现并与 dsh 对齐;右侧数据面板列与「缺陷」看板模块正在从旧单文件 JS 迁到
29
+ `src/app/client.tsx` + `src/modules/**`(见 [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md))。
30
+
31
+ ## 装一次
32
+
33
+ ```bash
34
+ ./scripts/verify.sh --record # 记录 dsh web 的基线(用于事后证明它没被动过)
35
+ ./bin/dsh-workbench setup # 建 workbench profile + 把工作台装进去(幂等,可重复跑)
36
+ ./bin/dsh-workbench # 起工作台
37
+ ```
38
+
39
+ `setup` 最后那一步等价于"在工作台会话里让 agent 执行 `install_bundle target=<本目录>`"
40
+ —— 两条路都走 dsh 的 `runPluginCommand`,装完都会把 bundle 登记进 profile 清单。
41
+ 细节见 [`docs/OPERATIONS.md`](docs/OPERATIONS.md)。
42
+
43
+ **给别人用**(发到内网源、对方的前置条件、Coding 登录)见
44
+ [`docs/HANDOFF.md`](docs/HANDOFF.md)。
45
+
46
+ ## 从这里去哪
47
+
48
+ | 触发 | 读 |
49
+ |---|---|
50
+ | 装机、起实例、验收、排障 | [`docs/OPERATIONS.md`](docs/OPERATIONS.md) |
51
+ | 给别人用、发包到内网源、发版 | [`docs/HANDOFF.md`](docs/HANDOFF.md) |
52
+ | 结构、层序、两个运行时、扩展位 | [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md) |
53
+ | 该不该做、做成什么形状 | [`DESIGN.md`](../../DESIGN.md) |
54
+ | 术语 | [`CONTEXT.md`](../../CONTEXT.md) |
55
+ | 为什么是这样 | [`docs/adr/`](../../docs/adr/) |
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # 工作台:建 profile / 起工作台。
4
+ #
5
+ # ./bin/dsh-workbench setup # ① 建 profile 并把工作台装进去(幂等)
6
+ # ./bin/dsh-workbench # ② 起工作台,默认 :3081
7
+ # WORKBENCH_PORT=3091 ./bin/dsh-workbench # 换端口
8
+ # ./bin/dsh-workbench --no-open # 其余参数透传给 dsh web
9
+ # ./bin/dsh-workbench --help
10
+ #
11
+ # 与 `dsh web` 完全独立:不同的 profile 目录、不同的 node_modules、
12
+ # 不同的端口、独立的进程。两个可以同时开着,互不干扰。
13
+ #
14
+ # ── 这份脚本有两种身份,它自己分得清 ──────────────────────────────────────
15
+ # · **仓库副本**(这个包落在一个 git 工作树里):开发用。bundle 以 link 方式装本目录,
16
+ # 改源码立刻生效(配合 `pnpm run dev`)。
17
+ # · **安装包**(从内网源下下来的):`setup` 把 **registry 上那个包** 装进 profile。
18
+ # 判据是「git 顶层在不在」—— `npm pack` 的产物里不含 .git(见 package.json 的 files)。
19
+ # ⚠️ 不能判 `$REPO_DIR/.git`:workspace 之后 .git 在**仓库根**,不在这个包里(实测踩过,
20
+ # 判错的症状是"仓库副本却去源上装",改源码不再生效,而且不报错)。
21
+
22
+ set -euo pipefail
23
+
24
+ PKG='@dsh-workbench/core'
25
+ DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
26
+ PROFILE="${WORKBENCH_PROFILE:-workbench}"
27
+ PORT="${WORKBENCH_PORT:-3081}"
28
+ PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
29
+
30
+ # 本包的真实位置。**不能**直接 `dirname "$0"/..`:npx 是经 `node_modules/.bin/dsh-workbench`
31
+ # 这条软链调进来的,那样算出来会停在 `node_modules`(实测),于是下面
32
+ # `$REPO_DIR/bin/opencli-isolated` 与 `$REPO_DIR/profile/*` 全指着不存在的地方
33
+ # —— 症状是隔离运行时那条软链变成死链、启动体检一直说"没装好"。
34
+ # 所以先把软链解析到底,再取它的上一级。
35
+ SELF="${BASH_SOURCE[0]}"
36
+ while [ -L "$SELF" ]; do
37
+ SELF="$(cd "$(dirname "$SELF")" && pwd -P)/$(readlink "$SELF")"
38
+ done
39
+ REPO_DIR="$(cd "$(dirname "$SELF")/.." && pwd -P)"
40
+ GIT_TOP="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
41
+
42
+ if ! command -v dsh >/dev/null 2>&1; then
43
+ echo "dsh-workbench: PATH 上没有 dsh。先装:npm i -g @deepseek-ai/dsh" >&2
44
+ exit 1
45
+ fi
46
+
47
+ pkg_version() {
48
+ node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo "0.0.0"
49
+ }
50
+
51
+ # ── setup:建 profile + 把工作台装进去 ─────────────────────────────────────
52
+ #
53
+ # 交给 dsh 自己的 `plugin` 子命令装,而不是我们自己拼 pnpm:它走的是与
54
+ # install_bundle **同一条**通路(runPluginCommand)—— 兼容性预检、
55
+ # profile 写锁、装完把 bundle 登记进 dsh.profile.bundles 全都在里面。
56
+ # 自己拼 pnpm 会漏掉登记,症状是"包装上了但侧边栏不出现"。
57
+ run_setup() {
58
+ "$REPO_DIR/scripts/seed-profile.sh"
59
+
60
+ if [ -n "$GIT_TOP" ]; then
61
+ SPEC="$REPO_DIR"
62
+ echo "→ 仓库副本:以 link 方式装本目录(${SPEC})"
63
+ else
64
+ SPEC="$PKG@^$(pkg_version)"
65
+ echo "→ 从内网源装 ${SPEC}(源在 package.json 的 publishConfig.registry)"
66
+ fi
67
+
68
+ dsh plugin --profile "$PROFILE" add "$SPEC"
69
+
70
+ cat <<EOF
71
+
72
+ 装好了。起工作台:
73
+
74
+ $REPO_DIR/bin/dsh-workbench
75
+
76
+ 首次打开会走启动体检,它会自己把 opencli 的隔离运行时装好 —— 不用你动手。
77
+ EOF
78
+ }
79
+
80
+ usage() {
81
+ sed -n '3,10p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
82
+ }
83
+
84
+ case "${1:-}" in
85
+ setup)
86
+ shift
87
+ run_setup "$@"
88
+ exit 0
89
+ ;;
90
+ help | --help | -h)
91
+ usage
92
+ exit 0
93
+ ;;
94
+ esac
95
+
96
+ if [ ! -f "$PROFILE_DIR/package.json" ]; then
97
+ echo "dsh-workbench: profile '$PROFILE' 还不存在。" >&2
98
+ echo " 先运行: $REPO_DIR/bin/dsh-workbench setup" >&2
99
+ exit 1
100
+ fi
101
+
102
+ # 软提醒:bundle 没装的话面板不会出现,但服务照常起来。
103
+ if ! grep -q "\"$PKG\"" "$PROFILE_DIR/package.json"; then
104
+ echo "dsh-workbench: 提醒 —— 工作台 bundle 还没装进 profile '$PROFILE'," >&2
105
+ echo " 侧边栏不会出现「工作台」入口。运行: $REPO_DIR/bin/dsh-workbench setup" >&2
106
+ fi
107
+
108
+ # 清掉从父 dsh 会话继承来的身份变量。
109
+ #
110
+ # 它们描述的是**另一个** profile 和会话。如果你是在某个 dsh 会话的 shell 里
111
+ # 启动工作台(很常见),不清掉的话 workbench 进程会认错身份 ——
112
+ # 实测:跑在 :3081 的 workbench 进程继承了 DSH_PROFILE=web、
113
+ # DSH_PROFILE_DIR=…/profiles/web、DSH_WEB_URL=…:3080、
114
+ # 以及那个会话的 DSH_SESSION_ID。
115
+ #
116
+ # 保留 DSH_HOME:它是"harness 家目录在哪"的配置输入,应该被尊重。
117
+
118
+ unset DSH_PROFILE DSH_PROFILE_DIR DSH_WEB_URL DSH_SESSION_ID DSH_SHELL
119
+
120
+ # 告诉 profile 里的 hmr 条目该监听哪个目录(宿主侧的模块监听)。
121
+ #
122
+ # 监听**dsh 真正加载的那份代码**:profile 里那份优先,其次才是本目录 ——
123
+ # 用 npx 起的安装包里,本目录在 npx 缓存里(会被回收),而 profile 里那份才是常驻的。
124
+ # 那条通常是软链,用 `cd -P` 取真实路径:chokidar 收不到软链那头的事件。
125
+ #
126
+ # 为什么要环境变量:bundle 补丁里的相对路径以 profile 目录为基准,从 profile 目录
127
+ # 出发走不到真实路径(实测 root: ['.'] 无效),所以由启动器给出绝对路径。
128
+ PROFILE_COPY="$PROFILE_DIR/node_modules/$PKG"
129
+ if [ -d "$PROFILE_COPY" ]; then
130
+ export DSH_WORKBENCH_ROOT="$(cd -P "$PROFILE_COPY" && pwd)"
131
+ else
132
+ export DSH_WORKBENCH_ROOT="$REPO_DIR"
133
+ fi
134
+
135
+ # ── 把 web 的设置**叠加**上来(web → 工作台,单向、实时)────────────────────
136
+ #
137
+ # 为什么叠而不是复制:`seed-profile.sh` 是把 web 的设置**快照一份**过来,之后两边各自
138
+ # 演进 —— 于是"在 web 里加了个模型供应商,工作台却少一个"这种漂移迟早发生(实测撞过)。
139
+ # 叠加则是**同一个来源**:供应商 / 搜索源 / MCP 这类机器级设置只在 `dsh web` 里配一次。
140
+ #
141
+ # 为什么是这一层(`--patch`)而不是机器公共层 `$DSH_HOME/cordis.patch.yml`:
142
+ # 公共层对所有 profile 生效且**没有 UI 写入路径**(配置编辑器只写当前 profile 的 patch)
143
+ # —— 把设置放进去等于要求用户手编。而 `--patch` 叠的是 web 的 profile patch,
144
+ # 用户在 web 的 Settings 页里配的就是它,配完即生效。
145
+ #
146
+ # 副作用(刻意的):被这一层覆盖的行,在工作台的 Settings 页里会显示
147
+ # `overridden by a home patch or command-line overlay` —— 那正是"这行由 web 管"的提示。
148
+ # 反向不成立:工作台里配的只落在自己的 profile patch(差异层),web 看不到。
149
+ WEB_PATCH="$DSH_HOME/profiles/web/cordis.patch.yml"
150
+ PATCH_ARGS=()
151
+ if [ -f "$WEB_PATCH" ]; then
152
+ PATCH_ARGS=(--patch "$WEB_PATCH")
153
+ else
154
+ echo "dsh-workbench: 提醒 —— 找不到 ${WEB_PATCH},这次不叠加 web 的设置(供应商等可能不全)。" >&2
155
+ fi
156
+
157
+ # ── 声明工作台运行时的**可写根**(执行缝的沙箱只放行 process.cwd())──────────
158
+ #
159
+ # 工作台自己要跑命令:启动体检自动装 opencli、模块市场装/升插件、查源上新版。
160
+ # 这些命令走 dsh 的执行缝,而那条缝带**文件沙箱**;插件发起的调用**没有会话**,
161
+ # 回落到部署策略,出厂值是 `workspaceRoot: process.cwd()` —— 安装目标
162
+ # `$DSH_HOME/workbench/...` 不在启动目录下,被拒写(EPERM),"自动装"死在第一步,
163
+ # 界面只能退化成"让用户去终端抄一条命令"。这一层把可写根指到工作台运行时,
164
+ # 那一整棵树(隔离工具链 + npm 缓存 + 模块专属文件夹 + 市场 prefix)就都放行了。
165
+ #
166
+ # 为什么是 `--patch` 叠加而不是改 profile 的 cordis.patch.yml:profile 可能是旧的
167
+ # (交接出去的机器尤其如此),没人会回头重跑 seed;叠加层每次启动都生效。
168
+ # ⚠️ 必须排在 web 的 patch **之后**:工作台的可写根要赢。
169
+ SANDBOX_PATCH="$REPO_DIR/profile/sandbox.patch.yml"
170
+ if [ -f "$SANDBOX_PATCH" ]; then
171
+ PATCH_ARGS+=(--patch "$SANDBOX_PATCH")
172
+ else
173
+ echo "dsh-workbench: 提醒 —— 找不到 ${SANDBOX_PATCH},这次不声明可写根(自动装会失败)。" >&2
174
+ fi
175
+
176
+ # 把 opencli 指到**隔离运行时**:所有 opencli 调用(探测、取数、装/卸插件)都走它,
177
+ # 于是装/卸插件不碰用户本机的 ~/.opencli。
178
+ #
179
+ # 为什么隔离要用 HOME(而不是 OPENCLI_CONFIG_DIR):opencli 的**插件目录只认 HOME**
180
+ # —— 见 bin/opencli-isolated 开头的说明。
181
+ #
182
+ # 建一个**稳定路径**的软链:别处(包括给用户"复制这条命令"的文案)都能用
183
+ # $DSH_HOME/workbench/opencli/bin/opencli 引用它,不必知道本目录在哪 ——
184
+ # 而且从那里跑出来同样是隔离的。指向 profile 里那份,别指 npx 缓存(会回收)。
185
+ OPENCLI_ROOT="$DSH_HOME/workbench/opencli"
186
+ mkdir -p "$OPENCLI_ROOT/bin" "$OPENCLI_ROOT/home"
187
+ ln -sfn "$DSH_WORKBENCH_ROOT/bin/opencli-isolated" "$OPENCLI_ROOT/bin/opencli"
188
+ export DSH_WORKBENCH_OPENCLI="$OPENCLI_ROOT/bin/opencli"
189
+
190
+ # 软提醒:隔离运行时没装的话,启动体检会**自己装**(沙箱已放行工作台运行时,
191
+ # 见上面那层 SANDBOX_PATCH);这条只是提前说一声,免得盯着全屏 loading 猜。
192
+ if [ ! -x "$OPENCLI_ROOT/toolchain/node_modules/.bin/opencli" ]; then
193
+ echo "dsh-workbench: opencli 的隔离运行时还没装好 —— 启动体检会自己装," >&2
194
+ echo " 装不上时它会给一条能直接照抄到终端的命令。" >&2
195
+ fi
196
+
197
+ # ⚠️ 顺序有讲究:`--patch` 是 **dsh 本体**的选项,必须排在 `--port`(应用参数)**之前** ——
198
+ # 参数一旦进了应用的透传区(第一个应用自己的选项开始),后面的都直接交给应用,
199
+ # `--patch` 会被应用当成未知选项(实测:`--port` 在前时报 `unknown option '--patch'`)。
200
+ exec dsh "$PROFILE" ${PATCH_ARGS[@]+"${PATCH_ARGS[@]}"} --port "$PORT" "$@"
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # 工作台**隔离的** opencli:所有 opencli 调用都从这里过(探测、取数、装/卸插件)。
4
+ #
5
+ # 为什么需要它:opencli 的**插件目录只认 `HOME`** —— `dist/src/plugin.js` 里
6
+ #
7
+ # function getHomeDir() {
8
+ # return process.env.HOME || process.env.USERPROFILE || os.homedir();
9
+ # }
10
+ #
11
+ # 而 `OPENCLI_CONFIG_DIR` 只管配置 / trace / browser-profiles。两套开关,别混。
12
+ # 所以想让"工作台装的插件"与"用户本机装的插件"分开,唯一的开关是 `HOME`:
13
+ # 指到工作台自己的目录,`~/.opencli` 就一个字节都不会被碰。
14
+ #
15
+ # 为什么必须是**单一路径、不含空格**:`DSH_WORKBENCH_OPENCLI` 既被**原样**当命令跑
16
+ # (能力探测:`$DSH_WORKBENCH_OPENCLI plugin list -f json`),又被 `quoteShellArg`
17
+ # 整体单引号包住(一键修那条 line)。写 `HOME=/x opencli` 会被包成 `'HOME=/x opencli'`,
18
+ # 当成一个不存在的可执行文件。
19
+ #
20
+ # ⚠️ 落点必须与 `src/shared/api/host/opencli-runtime.ts` 一致(两边都从 $DSH_HOME 推)。
21
+ #
22
+ set -euo pipefail
23
+
24
+ DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
25
+ ROOT="$DSH_HOME/workbench/opencli"
26
+ REAL_HOME="$HOME"
27
+
28
+ mkdir -p "$ROOT/home"
29
+
30
+ # 隔离的 HOME:插件、plugins.lock.json、monorepo 克隆、trace 全落这下面。
31
+ export HOME="$ROOT/home"
32
+
33
+ # 换 HOME 会连带丢掉 ~/.npmrc —— 里面有私有源的 _authToken
34
+ # (内网源地址由使用者自己配)。不指回去,从私有源装插件会 404/401(实测)。
35
+ if [ -f "$REAL_HOME/.npmrc" ]; then
36
+ export NPM_CONFIG_USERCONFIG="$REAL_HOME/.npmrc"
37
+ fi
38
+
39
+ # npm 缓存**必须留在隔离运行时里**(与 `opencli-runtime.ts` 的 npmCacheDir() 同一落点)。
40
+ #
41
+ # ⚠️ **不能用 `${npm_config_cache:-…}`**:用 `npx` / `npm exec dsh-workbench` 起工作台时,
42
+ # npm 会往进程里**注入** `npm_config_cache=/Users/<你>/.npm`(实测:`npm exec -- node -e …`
43
+ # 打出来就是它),这个值一路继承到执行缝里的每条命令。带默认值等于"外面给什么就用什么",
44
+ # 于是隔离形同虚设 —— opencli 自己内部还会跑 npm(`plugin install` 就要,见它 dist 里
45
+ # `execFileSync('npm', ['install', …])`),那次会去写 `~/.npm`;而执行缝只放行
46
+ # `$DSH_HOME/workbench`,写入被拒 → 市场装插件**报错退出 1**,报的还是 npm 那句
47
+ # "cache folder contains root-owned files"(那台机器上其实一个 root 文件都没有,是假话术)。
48
+ # 所以这里是**无条件改写**:缓存落点只有这一处说了算。
49
+ #
50
+ # 另一条老账:`~/.npm` 里只要有一个 root 的文件(以前 `sudo npm` 留下的),npm 也会拒绝
51
+ # 使用整个缓存。隔离运行时本来就有自己的缓存,没理由去赌用户那份。
52
+ export npm_config_cache="$ROOT/npm-cache"
53
+
54
+ BIN="$ROOT/toolchain/node_modules/.bin/opencli"
55
+ if [ ! -x "$BIN" ]; then
56
+ echo "dsh-workbench: opencli 的隔离运行时还没装好($ROOT/toolchain)。" >&2
57
+ echo " 打开工作台:启动体检会给出一条能直接照做的安装命令。" >&2
58
+ exit 127
59
+ fi
60
+
61
+ exec "$BIN" "$@"