@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.
- package/README.md +55 -0
- package/bin/dsh-workbench +200 -0
- package/bin/opencli-isolated +61 -0
- package/client.js +5334 -0
- package/cordis.patch.yml +42 -0
- package/dist/client/client.body.js +5323 -0
- package/dist/client/index.js +5324 -0
- package/dist/host/index.js +5420 -0
- package/dist/types/app/client.d.ts +1476 -0
- package/dist/types/app/host.d.ts +4 -0
- package/dist/types/app/layouts/__tests__/panel-hint.test.d.ts +1 -0
- package/dist/types/app/layouts/__tests__/project-selection.test.d.ts +1 -0
- package/dist/types/app/layouts/env-gate/__tests__/force-plan.test.d.ts +1 -0
- package/dist/types/app/layouts/env-gate/__tests__/gate-hidden.test.d.ts +1 -0
- package/dist/types/app/layouts/env-gate/__tests__/gate-mocks.test.d.ts +1 -0
- package/dist/types/app/layouts/env-gate/boot-gate.d.ts +16 -0
- package/dist/types/app/layouts/env-gate/gate-dom.d.ts +4 -0
- package/dist/types/app/layouts/env-gate/gate-mock.d.ts +18 -0
- package/dist/types/app/layouts/env-gate/gate-text.d.ts +13 -0
- package/dist/types/app/layouts/market/panel.d.ts +4 -0
- package/dist/types/app/layouts/panel-hint.d.ts +9 -0
- package/dist/types/app/layouts/project-selection.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/BoardEmpty.d.ts +6 -0
- package/dist/types/app/layouts/rightbar/BoardMockStrip.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/Breadcrumbs.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/ExpandButton.d.ts +9 -0
- package/dist/types/app/layouts/rightbar/MarketFreshLines.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +14 -0
- package/dist/types/app/layouts/rightbar/ModulesPage.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/PanelModules.d.ts +14 -0
- package/dist/types/app/layouts/rightbar/Rightbar.d.ts +24 -0
- package/dist/types/app/layouts/rightbar/__tests__/board-mock.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/board-plan.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/board-selection.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/breadcrumbs.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-freshness.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-mock.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-rows-libs.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-rows.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-shared.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-mock.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/suite-world.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/use-panels.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/board-mock.d.ts +22 -0
- package/dist/types/app/layouts/rightbar/board-plan.d.ts +25 -0
- package/dist/types/app/layouts/rightbar/board-selection.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/market-freshness.d.ts +13 -0
- package/dist/types/app/layouts/rightbar/market-mock.d.ts +29 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +45 -0
- package/dist/types/app/layouts/rightbar/panel-mock.d.ts +26 -0
- package/dist/types/app/layouts/rightbar/registry.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/registry.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/store.d.ts +23 -0
- package/dist/types/app/layouts/rightbar/store.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/suite-world.d.ts +14 -0
- package/dist/types/app/layouts/rightbar/use-panels.d.ts +15 -0
- package/dist/types/app/layouts/sidebar/PanelNav.d.ts +13 -0
- package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +10 -0
- package/dist/types/app/layouts/sidebar/SessionList.d.ts +13 -0
- package/dist/types/app/layouts/sidebar/Sidebar.d.ts +25 -0
- package/dist/types/app/layouts/sidebar/WorkspaceSection.d.ts +7 -0
- package/dist/types/app/layouts/sidebar/__tests__/session-pick.test.d.ts +1 -0
- package/dist/types/app/layouts/sidebar/session-pick.d.ts +20 -0
- package/dist/types/app/units/host.d.ts +4 -0
- package/dist/types/app/units/index.d.ts +4 -0
- package/dist/types/app/units/registry.d.ts +12 -0
- package/dist/types/modules/panels/__tests__/config.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/face.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/market-dependency.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/market-tools.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/market.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/prefs.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/routes.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/service.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/tools.test.d.ts +1 -0
- package/dist/types/modules/panels/index.d.ts +2 -0
- package/dist/types/modules/panels/model/config.d.ts +18 -0
- package/dist/types/modules/panels/model/handoff.d.ts +8 -0
- package/dist/types/modules/panels/model/handoff.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-dependency.d.ts +41 -0
- package/dist/types/modules/panels/model/market-dispatch.d.ts +14 -0
- package/dist/types/modules/panels/model/market-tools.d.ts +3 -0
- package/dist/types/modules/panels/model/market.d.ts +49 -0
- package/dist/types/modules/panels/model/prefs.d.ts +13 -0
- package/dist/types/modules/panels/model/routes.d.ts +4 -0
- package/dist/types/modules/panels/model/routes.test.d.ts +1 -0
- package/dist/types/modules/panels/model/service.d.ts +25 -0
- package/dist/types/modules/panels/model/tools.d.ts +4 -0
- package/dist/types/modules/panels/unit.d.ts +4 -0
- package/dist/types/modules/sessions/index.d.ts +1 -0
- package/dist/types/modules/sessions/model/session-node.d.ts +25 -0
- package/dist/types/modules/sessions/model/session-node.test.d.ts +1 -0
- package/dist/types/shared/__tests__/i18n-keys.test.d.ts +1 -0
- package/dist/types/shared/api/__tests__/context-ref.test.d.ts +1 -0
- package/dist/types/shared/api/context-chips.d.ts +10 -0
- package/dist/types/shared/api/host/__tests__/capability-force.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/cli-error.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/context-inject.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/data-root.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/events.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/fact-writes.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/facts.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/long-task.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-source.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/preflight-fixtures.d.ts +19 -0
- package/dist/types/shared/api/host/__tests__/preflight-warm.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/preflight.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-cache.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-libs.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-units-cache.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-units-npm-source.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-units-release.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-units.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/probe-verdict.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/provision-dependency.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/release.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/tool-approval.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/tool-kit.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/tool-scope.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/tool-surface.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/unit-face.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/unit-readiness.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/unit-registry.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/value-handoff.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/version-range.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/workspace-store.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/write-result.test.d.ts +1 -0
- package/dist/types/shared/api/host/context-inject.d.ts +24 -0
- package/dist/types/shared/api/host/events.d.ts +9 -0
- package/dist/types/shared/api/host/git.test.d.ts +1 -0
- package/dist/types/shared/api/host/http.test.d.ts +1 -0
- package/dist/types/shared/api/host/mount-units.d.ts +52 -0
- package/dist/types/shared/api/host/opencli.test.d.ts +1 -0
- package/dist/types/shared/api/host/preflight.d.ts +58 -0
- package/dist/types/shared/api/host/probe-cache.d.ts +6 -0
- package/dist/types/shared/api/host/probe-libs.d.ts +11 -0
- package/dist/types/shared/api/host/probe-units.d.ts +17 -0
- package/dist/types/shared/api/host/probe-verdict.d.ts +7 -0
- package/dist/types/shared/api/host/release.d.ts +18 -0
- package/dist/types/shared/api/host/shell.test.d.ts +1 -0
- package/dist/types/shared/api/host/tool-scope.d.ts +32 -0
- package/dist/types/shared/api/host/unit-readiness.d.ts +7 -0
- package/dist/types/shared/api/host/unit-registry.d.ts +9 -0
- package/dist/types/shared/api/host/version-range.d.ts +2 -0
- package/dist/types/shared/components/BlockerState.d.ts +11 -0
- package/dist/types/shared/hooks/__tests__/mock-mode.test.d.ts +1 -0
- package/dist/types/shared/hooks/__tests__/use-api-resource.test.d.ts +1 -0
- package/dist/types/shared/hooks/__tests__/use-capabilities.test.d.ts +1 -0
- package/dist/types/shared/hooks/__tests__/use-visible-busy.test.d.ts +1 -0
- package/dist/types/shared/hooks/fact-transports.d.ts +2 -0
- package/dist/types/shared/hooks/mock-mode.d.ts +1 -0
- package/dist/types/shared/hooks/use-api-resource.d.ts +18 -0
- package/dist/types/shared/hooks/use-blocker-actions.d.ts +11 -0
- package/dist/types/shared/hooks/use-capabilities.d.ts +25 -0
- package/dist/types/shared/hooks/use-capability-gate.d.ts +20 -0
- package/dist/types/shared/hooks/use-card-summary.d.ts +8 -0
- package/dist/types/shared/hooks/use-facts.d.ts +2 -0
- package/dist/types/shared/hooks/use-fixture-pick.d.ts +20 -0
- package/dist/types/shared/hooks/use-host-write.d.ts +17 -0
- package/dist/types/shared/hooks/use-long-task.d.ts +14 -0
- package/dist/types/shared/hooks/use-unit-board.d.ts +32 -0
- package/dist/types/shared/hooks/use-visible-busy.d.ts +8 -0
- package/docs/HANDOFF.md +207 -0
- package/docs/OPERATIONS.md +109 -0
- package/icon.svg +29 -0
- package/package.json +97 -0
- package/profile/isolation.patch.yml +35 -0
- package/profile/sandbox.patch.yml +26 -0
- package/scripts/seed-profile.sh +199 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 操作
|
|
2
|
+
|
|
3
|
+
装机、起实例、验收、排障。命令本身在 `package.json` 的 `scripts` 里,这里只写**顺序**和**判据**。
|
|
4
|
+
|
|
5
|
+
## 装一次
|
|
6
|
+
|
|
7
|
+
一条命令版(②+④ 都在里面,幂等,可重复跑):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
./scripts/verify.sh --record # ① 记录 dsh web 的基线(越早越好)
|
|
11
|
+
./bin/dsh-workbench setup # ② 建 workbench profile 并把工作台装进 profile
|
|
12
|
+
./bin/dsh-workbench # ③ 起工作台(另开一个终端)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
想拆开做也行:`./scripts/seed-profile.sh` 只建 profile 骨架(继承 + 隔离层),
|
|
16
|
+
装 bundle 则在**工作台进程里**做 —— `plugin_manager` 只作用于当前 profile:
|
|
17
|
+
|
|
18
|
+
> 在工作台的会话里让 agent 执行 `install_bundle target=<本仓库绝对路径>`
|
|
19
|
+
|
|
20
|
+
`setup` 与 agent 那条路等价:都走 dsh 的 `runPluginCommand`(兼容性预检、profile 写锁、
|
|
21
|
+
装完把 bundle 登记进清单)。
|
|
22
|
+
|
|
23
|
+
**完成判据**:`:3081` 的页面出现工作台的左栏;`:3080` 一切照旧。
|
|
24
|
+
|
|
25
|
+
工作台用的 opencli 是它自己的**隔离运行时**(`~/.dsh/workbench/opencli/toolchain`,版本由
|
|
26
|
+
`OPENCLI_PIN` 锁),但它连的 **daemon 是整机一个** —— 见下面「隔离边界」。
|
|
27
|
+
|
|
28
|
+
前提:`dsh` 和 `pnpm` 在 PATH 上(`dsh plugin` 是转发给 pnpm 的),
|
|
29
|
+
且 `~/.dsh/profiles/web` 已存在(跑过一次 `dsh web` 即可)。
|
|
30
|
+
|
|
31
|
+
**给别人用**(发包到内网源、对方要登录内网源 / Coding)见 [`HANDOFF.md`](HANDOFF.md)。
|
|
32
|
+
|
|
33
|
+
## 日常
|
|
34
|
+
|
|
35
|
+
| 想做什么 | 怎么做 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| 开工作台 | `./bin/dsh-workbench` |
|
|
38
|
+
| 换端口 | `WORKBENCH_PORT=3091 ./bin/dsh-workbench` |
|
|
39
|
+
| 改工作台设置 | 在 `:3081` 的 Settings 页改,落在 `profiles/workbench/cordis.patch.yml` |
|
|
40
|
+
| 改 web 设置 | 在 `:3080` 改,落在 `profiles/web/cordis.patch.yml`。**两边都要的设置(模型供应商、搜索源、MCP)就在这一份里配** —— 工作台启动时会用 `--patch` 把它叠上来(web → 工作台,单向、实时),所以不必配两遍 |
|
|
41
|
+
| 改只有工作台用的设置 | 在 `:3081` 改,落在 `profiles/workbench/cordis.patch.yml`。这个文件**只剩工作台自己的差异**(会话/存储/附件隔离 + 沙箱);被 web 覆盖过的行在这里改不动(编辑器会写 `overridden`,那就是"这行由 web 管"的提示) |
|
|
42
|
+
| 重建 profile | `mv ~/.dsh/profiles/workbench ~/.dsh/profiles/workbench.bak` 后重跑 seed |
|
|
43
|
+
| **别在设置里关掉工作台** | Settings → 插件里 `@dsh-workbench/core` 那个开关**不要关**。关掉=把它从 profile 的 `bundles` 里移除 → 我们那份 bundle patch(含"禁用出厂左栏")**整份不再应用** → 页面退化成**原版 dsh web**(左栏回来、右侧面板消失),**数据一个都不丢**。恢复:插件页把开关打开(左栏的「插件」入口也在;进不去设置就按 `Meta+,`)。实测过这条降级路径,不是推断 |
|
|
44
|
+
|
|
45
|
+
## 开发循环(改完不用重启)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
./bin/dsh-workbench # 终端 A:工作台
|
|
49
|
+
pnpm run dev # 终端 B:监听 src/**,重建产物
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
改 `src/**` → 约 1 秒后界面就是新的,**不用重启、不用刷新**。两条链路都是 dsh 自带的:
|
|
53
|
+
|
|
54
|
+
| 半侧 | 链路 | 代价 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| 浏览器 | Vite 重建 `client.js` → `dsh-client-hmr` 每 500ms stat-poll 到 mtime/大小变化 → `/plugins/events` 发 `rebuilt` → 浏览器作废旧模块、预取新包、换 fiber | 插件内的 React 状态会丢;会话 / 工作区 / 连接状态保留 |
|
|
57
|
+
| 宿主 | Vite 重建 `dist/host/index.js` → `dsh-hmr` 按模块 realpath 匹配并重载插件 | 插件重建,进程与其它插件不受影响 |
|
|
58
|
+
|
|
59
|
+
**只有这两种改动要重启**:`cordis.patch.yml`(组合层)与 `package.json` 的 `dsh.profile.bundles`。
|
|
60
|
+
|
|
61
|
+
宿主侧监听为什么要环境变量:出厂 `hmr` 的 `root` 默认是 `[]`(不监听),
|
|
62
|
+
而相对路径以 profile 目录为基准 —— 我们的包在 profile 里只是一个指向本仓库的符号链接,
|
|
63
|
+
从 profile 目录出发走不到 `/Volumes/…`,chokidar 收不到事件(实测 `root: ['.']` 无效)。
|
|
64
|
+
所以启动器导出 `DSH_WORKBENCH_ROOT`,补丁里用 `!!js process.env.DSH_WORKBENCH_ROOT ?? '.'`。
|
|
65
|
+
|
|
66
|
+
## 验收
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
./scripts/verify.sh
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
五项检查,全部是"外部约束的证据"而不是自证:
|
|
73
|
+
|
|
74
|
+
0. **代码接线** —— 槽位名 / key / id / locale 是否对得上(语法检查抓不到这类错)
|
|
75
|
+
1. **web 是否被动过** —— 与 `--record` 的哈希逐一比对
|
|
76
|
+
2. **组合树差异** —— `dsh web --dump-config` vs `dsh workbench --dump-config`,
|
|
77
|
+
差异应恰好是隔离行 + 工作台行 + `ui-sidebar` 被禁用
|
|
78
|
+
3. **数据是否分家** —— `~/.dsh/sessions` 与 `~/.dsh/workbench/sessions` 各自独立
|
|
79
|
+
4. **隔离行是否在位** —— 三个根行都在 `profiles/workbench/cordis.patch.yml` 里
|
|
80
|
+
|
|
81
|
+
另外两项要 GUI / agent 才能验:
|
|
82
|
+
|
|
83
|
+
- **槽位注册**:让 agent 执行 `cordis_inspect_query` → `Slots.listSubTree`,
|
|
84
|
+
看对应 `root` 的 `occupants` 里有没有我们的条目。**这是权威证据**,别靠日志推断。
|
|
85
|
+
- **视觉一致性**:亮色 / 暗色各看一次,确认颜色全部来自 `--dsw-*` 令牌。
|
|
86
|
+
|
|
87
|
+
## 隔离边界
|
|
88
|
+
|
|
89
|
+
| 资源 | 是否隔离 | 落在哪 |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| 会话历史 | ✅ | `~/.dsh/workbench/sessions/` |
|
|
92
|
+
| 工作区 / 会话投影缓存 | ✅ | 走 `storage-domain` → `storage-json` 根 |
|
|
93
|
+
| 附件 | ✅ | `~/.dsh/workbench/attachments/v1/` |
|
|
94
|
+
| 模块配置 | ✅ | `~/.dsh/workbench/workspaces/<工作区>/<模块 id>/`(见 [ADR-0011](../../../docs/adr/0011-module-config-lives-in-its-own-folder.md)) |
|
|
95
|
+
| API 凭据 | ❌ **有意共享** | `~/.dsh/.credentials.yaml`,两边同一把 key |
|
|
96
|
+
| opencli 运行时(CLI + 插件) | ✅ | `~/.dsh/workbench/opencli/`(`HOME` 指到 `…/home`,见 [ADR-0014](../../../docs/adr/0014-opencli-cost-has-one-owner.md) §2.2) |
|
|
97
|
+
| opencli **daemon** | ❌ **整机一个,隔离不了** | 端口 **19825 被扩展写死**(`background.js` 里 `DAEMON_PORT = 19825`;CLI 侧 `OPENCLI_DAEMON_PORT` 设非默认值直接报错)。所以本机的全局 opencli 与工作台的隔离运行时**抢同一个 daemon**:两边版本不一致时,谁的命令后跑谁就把 daemon 重启成自己那版 |
|
|
98
|
+
|
|
99
|
+
## 排障
|
|
100
|
+
|
|
101
|
+
| 症状 | 原因 |
|
|
102
|
+
|---|---|
|
|
103
|
+
| 页面白屏、只显示 `Failed to load plugins` | 有插件在 `slots.register` 到未声明的槽(抛错即整个 entry 激活失败)。看控制台里我们打的 `[dsh-workbench] apply failed` |
|
|
104
|
+
| 面板一直空着、`/workbench/api/issues` 返回 `coding-not-logged-in` | opencli 登录态失效:在终端跑 `opencli coding team-current` 完成登录 |
|
|
105
|
+
| 面板显示"这个项目不是 Coding 仓库" | 该工作区的 git `origin` 不指向 `e.coding.net`(或没有 origin) |
|
|
106
|
+
| 打开面板/按刷新要等 5–8 秒 | 正常:opencli 每次调用有**固定约 5 秒**开销(起浏览器会话;接口本身只要 ~260ms)。**没有缓存**——数据一律实时,列表 = 缺陷 + 状态目录两次调用(限并发 2,实测约 7 秒) |
|
|
107
|
+
| 改了源码没变化 | 忘了 `pnpm run build`,或没重启进程(宿主缓存已加载的包) |
|
|
108
|
+
| 体检那屏「检测浏览器通道 · 没连上」,可面板照样能用 | 本机全局 opencli 与隔离运行时版本不一致 ⇒ daemon 被顶成对方那版(`Stale daemon detected: daemon vX != CLI vY`)。下一条浏览器命令会自动把它重启回来,体检也会复检后改回「已连接」([ADR-0024](../../../docs/adr/0024-warm-the-sites-before-the-panel.md) §3.2.1)。**要彻底不抖**:让两边同版本 —— 把 `OPENCLI_PIN`(`src/shared/config/constants.ts`)对齐到 `opencli --version`,再 `npm i --prefix ~/.dsh/workbench/opencli/toolchain @jackwener/opencli@<那个版本> --cache ~/.dsh/workbench/opencli/npm-cache` |
|
|
109
|
+
| Settings 页报 `overridden by a home patch` | 想在 Settings 里改的行被 `$DSH_HOME/cordis.patch.yml` 或 `--patch` 覆盖了 |
|
package/icon.svg
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- 占位图标:插件管理卡片与 bundle 详情页使用。
|
|
3
|
+
图标以图片形式渲染,因此 currentColor 在这里不生效,
|
|
4
|
+
需要主题自适应时请按亮/暗两套各出一版或改用中性色。 -->
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
viewBox="0 0 64 64"
|
|
8
|
+
width="64"
|
|
9
|
+
height="64"
|
|
10
|
+
role="img"
|
|
11
|
+
aria-label="Workbench"
|
|
12
|
+
>
|
|
13
|
+
<rect
|
|
14
|
+
x="8"
|
|
15
|
+
y="8"
|
|
16
|
+
width="48"
|
|
17
|
+
height="48"
|
|
18
|
+
rx="12"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke="#6b7280"
|
|
21
|
+
stroke-width="5"
|
|
22
|
+
/>
|
|
23
|
+
<path
|
|
24
|
+
d="M21 24h22M21 32h22M21 40h13"
|
|
25
|
+
stroke="#6b7280"
|
|
26
|
+
stroke-width="5"
|
|
27
|
+
stroke-linecap="round"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dsh-workbench/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"icon": "./icon.svg",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/types/app/host.d.ts",
|
|
10
|
+
"default": "./dist/host/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./client": {
|
|
13
|
+
"types": "./dist/types/app/client.d.ts",
|
|
14
|
+
"default": "./client.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json",
|
|
17
|
+
"./client/esm": {
|
|
18
|
+
"types": "./dist/types/app/client.d.ts",
|
|
19
|
+
"default": "./dist/client/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"dsh-workbench": "./bin/dsh-workbench"
|
|
24
|
+
},
|
|
25
|
+
"//files": "npm 的兜底是 .gitignore —— 而 dist/ 在 .gitignore 里,不显式列出就会**漏掉宿主入口**(exports['.'] 指向它)。发出去之前用 npm pack --dry-run 数一遍 dist/ 有几条。",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"client.js",
|
|
29
|
+
"cordis.patch.yml",
|
|
30
|
+
"icon.svg",
|
|
31
|
+
"bin",
|
|
32
|
+
"profile",
|
|
33
|
+
"scripts/seed-profile.sh",
|
|
34
|
+
"README.md",
|
|
35
|
+
"docs/HANDOFF.md",
|
|
36
|
+
"docs/OPERATIONS.md"
|
|
37
|
+
],
|
|
38
|
+
"meta": {
|
|
39
|
+
"title": "Workbench",
|
|
40
|
+
"description": "个人工作台"
|
|
41
|
+
},
|
|
42
|
+
"//scripts": "包内不跑 check:docs —— 那份文档管理块住在仓库根的 docs/ARCHITECTURE.md,由根 `pnpm run check` 用 --config 指到本包的 arch.config.mjs 校验。prepublishOnly 走 -w 跑根那一整套。",
|
|
43
|
+
"dsh": {
|
|
44
|
+
"bundle": {
|
|
45
|
+
"patch": "./cordis.patch.yml"
|
|
46
|
+
},
|
|
47
|
+
"client": {
|
|
48
|
+
"platform": "web",
|
|
49
|
+
"immediately": true,
|
|
50
|
+
"inject": [
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
52
|
+
"@deepseek-ai/dsh-client-locale"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": "*"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@arch-guard/core": "^0.10.0",
|
|
66
|
+
"@types/node": "^24.19.0",
|
|
67
|
+
"@types/react": "^19.3.0",
|
|
68
|
+
"@vitest/coverage-v8": "^5.0.2",
|
|
69
|
+
"esbuild": "^0.28.2",
|
|
70
|
+
"react": "^19.3.0",
|
|
71
|
+
"typescript": "5.9",
|
|
72
|
+
"vite": "^8.3.1",
|
|
73
|
+
"vitest": "^5.0.2"
|
|
74
|
+
},
|
|
75
|
+
"//publishConfig": "只影响**发布这个包**,不影响别人解析依赖。内网源地址在这一处(交接文档里写给对方的也是这一条)。",
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
78
|
+
},
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@dsh-workbench/shared": "0.1.0"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
|
84
|
+
"build:host": "node scripts/build.mjs host",
|
|
85
|
+
"build:client": "node scripts/build.mjs client",
|
|
86
|
+
"typecheck": "tsc --noEmit",
|
|
87
|
+
"arch": "arch-guard",
|
|
88
|
+
"selfcheck": "node scripts/selfcheck.mjs",
|
|
89
|
+
"check:host": "node scripts/check-host.mjs",
|
|
90
|
+
"check:runtimes": "node scripts/check-runtimes.mjs",
|
|
91
|
+
"check": "npm run typecheck && npm run coverage && npm run arch && npm run check:runtimes && npm run selfcheck",
|
|
92
|
+
"test": "vitest run",
|
|
93
|
+
"coverage": "vitest run --coverage",
|
|
94
|
+
"dev": "node scripts/dev.mjs",
|
|
95
|
+
"build:types": "node scripts/build-types.mjs"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# workbench profile 的会话/存储隔离层(片段)。
|
|
2
|
+
#
|
|
3
|
+
# 这个文件是"补丁层片段",不是独立补丁文件:seed-profile.sh 会把它
|
|
4
|
+
# 追加进 ~/.dsh/profiles/workbench/cordis.patch.yml。
|
|
5
|
+
#
|
|
6
|
+
# 为什么需要它:dsh-base 把会话与存储的根都挂在 $DSH_HOME 上
|
|
7
|
+
# (dshHomePath('sessions') / dshHomePath('storages')),所以默认情况下
|
|
8
|
+
# 两个 profile 共用同一份会话历史。工作台要提供自己的定制工具,等于一个
|
|
9
|
+
# 独立的 agent 环境,共享会话没有意义 —— 这三行把数据分开。
|
|
10
|
+
#
|
|
11
|
+
# 隔离后:
|
|
12
|
+
# 会话 ~/.dsh/workbench/sessions/
|
|
13
|
+
# 工作区 ~/.dsh/workbench/storages/ (workspace 走 storage-domain)
|
|
14
|
+
# 附件 ~/.dsh/workbench/attachments/v1/
|
|
15
|
+
# 有意不隔离:
|
|
16
|
+
# 凭据 ~/.dsh/.credentials.yaml (两边共用同一把 API key)
|
|
17
|
+
#
|
|
18
|
+
# 注意:patch 的 config 是整体替换、不深合并。这三行原本各自只声明了
|
|
19
|
+
# root / dshHome,所以只重述这一个字段是安全的;若你给这些行配过别的
|
|
20
|
+
# 字段(例如 session-persistence-jsonl 的 compression),必须一并重述。
|
|
21
|
+
|
|
22
|
+
- id: session-persistence-jsonl
|
|
23
|
+
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
24
|
+
config:
|
|
25
|
+
root: !!js dshHomePath('workbench/sessions')
|
|
26
|
+
|
|
27
|
+
- id: storage-json
|
|
28
|
+
name: '@deepseek-ai/dsh-storage-json'
|
|
29
|
+
config:
|
|
30
|
+
root: !!js dshHomePath('workbench/storages')
|
|
31
|
+
|
|
32
|
+
- id: attachment-local
|
|
33
|
+
name: '@deepseek-ai/dsh-attachment-local'
|
|
34
|
+
config:
|
|
35
|
+
dshHome: !!js dshHomePath('workbench')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 工作台运行时的**可写根**声明 —— 由启动器每次启动叠加(`--patch`)。
|
|
2
|
+
#
|
|
3
|
+
# 为什么必须有它:工作台自己要跑命令(启动体检自动装 opencli、模块市场装/升插件、
|
|
4
|
+
# 查源上有没有新版),而这些命令走 dsh 的**执行缝**,执行缝带**文件沙箱**。
|
|
5
|
+
# 插件发起的调用**没有会话**,于是回落到部署策略;dsh-base 出厂那一条写的是
|
|
6
|
+
# `workspaceRoot: !!js process.cwd()` —— 而安装目标是 `$DSH_HOME/workbench/...`,
|
|
7
|
+
# 不在启动目录下面,沙箱拒写(实测 EPERM:mkdir ~/.npm/_cacache、mkdir
|
|
8
|
+
# $DSH_HOME/workbench/...,见 .scratch/agent-issues/spec.md 第 10.3 节)。
|
|
9
|
+
# 症状:自动安装死在第一步,界面只能退化成"让用户去终端抄一条命令" ——
|
|
10
|
+
# 终端没有沙箱,抄过去反而能跑,于是这个缺口一直看不出来。
|
|
11
|
+
#
|
|
12
|
+
# 指到 `$DSH_HOME/workbench` 之后,沙箱放行的是**同一棵树**:
|
|
13
|
+
# 隔离运行时的工具链 + npm 缓存(opencli-runtime.ts)、
|
|
14
|
+
# 模块专属文件夹(ADR-0011 的 workspaces/)、模块市场的 plugins-npm。
|
|
15
|
+
# 也就是 ADR-0011 说的"启动器给插件声明的可写根"。
|
|
16
|
+
#
|
|
17
|
+
# 为什么用 `--patch` 叠加、而不是写进 profile 的 cordis.patch.yml:
|
|
18
|
+
# profile 可能是**早就建好**的(交接出去的机器尤其如此),没人会回头重跑
|
|
19
|
+
# seed-profile.sh —— 叠加层每次启动都生效,对新旧 profile 一视同仁。
|
|
20
|
+
#
|
|
21
|
+
# mode 与 base 出厂值**一致**(只重述,不改变语义):patch 的 config 是**整体替换**、
|
|
22
|
+
# 不深合并,所以两个字段都得写全,否则 workspaceRoot 会被替换掉 mode 丢失。
|
|
23
|
+
- id: sandbox-policy
|
|
24
|
+
config:
|
|
25
|
+
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
|
26
|
+
workspaceRoot: !!js dshHomePath('workbench')
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# 一次性引导:从 shipped 的 web 模板创建 workbench profile,
|
|
4
|
+
# 继承 web 的配置(bundle 清单 + 设置快照 + 设置引用到的依赖),
|
|
5
|
+
# 再加上会话 / 存储 / 附件的隔离层。
|
|
6
|
+
#
|
|
7
|
+
# 幂等 / 可续跑:profile 已存在就跳过创建,patch 已非空就跳过继承,
|
|
8
|
+
# 隔离层已存在就跳过追加,依赖已在 node_modules 就跳过安装
|
|
9
|
+
# —— 重复运行不会覆盖任何东西。
|
|
10
|
+
# 安全:全程只**读** web 的 profile 目录,只**写** workbench 自己的目录。
|
|
11
|
+
#
|
|
12
|
+
# 用法:
|
|
13
|
+
# ./scripts/seed-profile.sh
|
|
14
|
+
# WORKBENCH_PROFILE=wb2 ./scripts/seed-profile.sh # 建到别的 profile 名下(试装用)
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
|
|
19
|
+
PROFILE="${WORKBENCH_PROFILE:-workbench}"
|
|
20
|
+
SRC_PROFILE="$DSH_HOME/profiles/web"
|
|
21
|
+
DST_PROFILE="$DSH_HOME/profiles/$PROFILE"
|
|
22
|
+
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
23
|
+
GIT_TOP="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
24
|
+
ISOLATION="$REPO_DIR/profile/isolation.patch.yml"
|
|
25
|
+
|
|
26
|
+
# `verify.sh` 留在**仓库根**(它证明的是"dsh web 未被动过 / 组合树差异 / 数据分家",
|
|
27
|
+
# 与包边界无关),所以不能按 $REPO_DIR 拼。装成安装包(没有 git 工作树)时它不存在,
|
|
28
|
+
# 那就只说"见仓库的 scripts/verify.sh"。
|
|
29
|
+
VERIFY_DIR="${GIT_TOP:-$REPO_DIR}/scripts"
|
|
30
|
+
|
|
31
|
+
die() {
|
|
32
|
+
echo "seed-profile: $*" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[ -f "$SRC_PROFILE/package.json" ] ||
|
|
37
|
+
die "找不到 $SRC_PROFILE —— 请先跑一次 'dsh web',让 web profile 落地。"
|
|
38
|
+
[ -f "$ISOLATION" ] || die "找不到 $ISOLATION"
|
|
39
|
+
|
|
40
|
+
DST_PATCH="$DST_PROFILE/cordis.patch.yml"
|
|
41
|
+
|
|
42
|
+
# dsh 生成的 patch 文件带一段注释头,所以"是否还是空层"不能直接和 [] 比。
|
|
43
|
+
is_pristine_patch() {
|
|
44
|
+
[ "$(grep -v '^[[:space:]]*#' "$1" | tr -d '[:space:]')" = "[]" ]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# ── 第 1 步:创建 profile ────────────────────────────────────────────────
|
|
48
|
+
# --from-default-profile web 只复制 web 模板的 bundle 列表
|
|
49
|
+
# (dsh-base + dsh-web-app),**不**复制 web 的 cordis.patch.yml。
|
|
50
|
+
# 用 --dump-config 而不是直接启动:走同一条初始化路径,但只打印
|
|
51
|
+
# 组合树,不挂载、不监听端口。
|
|
52
|
+
if [ -f "$DST_PROFILE/package.json" ]; then
|
|
53
|
+
echo "→ profile '$PROFILE' 已存在,跳过创建"
|
|
54
|
+
else
|
|
55
|
+
echo "→ 创建 profile '$PROFILE'(模板: web)"
|
|
56
|
+
dsh "$PROFILE" --from-default-profile web --dump-config >/dev/null
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
[ -f "$DST_PATCH" ] || die "profile 缺少 $DST_PATCH"
|
|
60
|
+
|
|
61
|
+
# ── 第 2 步:继承 web 的 profile 清单 ───────────────────────────────────
|
|
62
|
+
# --from-default-profile 只复制**出厂模板**的 bundle 列表(dsh-base +
|
|
63
|
+
# dsh-web-app),而 web 的清单可能后来被 install_bundle / plugin_manager
|
|
64
|
+
# 改过(例如启用了某个可选 bundle)。所以这里把 web 清单里的 bundles
|
|
65
|
+
# 并进来,同时保留 workbench 自己已有的额外项(例如 @dsh-workbench/core),
|
|
66
|
+
# 使脚本可重复运行。
|
|
67
|
+
WB_MANIFEST="$DST_PROFILE/package.json" \
|
|
68
|
+
WEB_MANIFEST="$SRC_PROFILE/package.json" \
|
|
69
|
+
node -e '
|
|
70
|
+
const fs = require("node:fs");
|
|
71
|
+
const read = (p) => JSON.parse(fs.readFileSync(p, "utf8"));
|
|
72
|
+
const wb = read(process.env.WB_MANIFEST);
|
|
73
|
+
const web = read(process.env.WEB_MANIFEST);
|
|
74
|
+
const webProfile = (web.dsh && web.dsh.profile) || {};
|
|
75
|
+
const wbProfile = (wb.dsh && wb.dsh.profile) || {};
|
|
76
|
+
const webBundles = webProfile.bundles || [];
|
|
77
|
+
const wbBundles = wbProfile.bundles || [];
|
|
78
|
+
const merged = webBundles.concat(
|
|
79
|
+
wbBundles.filter((b) => webBundles.indexOf(b) === -1),
|
|
80
|
+
);
|
|
81
|
+
wb.dsh = Object.assign({}, wb.dsh, {
|
|
82
|
+
profile: Object.assign({}, webProfile, wbProfile, { bundles: merged }),
|
|
83
|
+
});
|
|
84
|
+
fs.writeFileSync(process.env.WB_MANIFEST, JSON.stringify(wb, null, 2) + "\n");
|
|
85
|
+
console.log("→ 已继承 web 的 bundle 列表: " + merged.join(", "));
|
|
86
|
+
'
|
|
87
|
+
|
|
88
|
+
# ── 第 3 步:把 web 的设置文档复制过来,作为「继承」的起点 ──────────────
|
|
89
|
+
# profiles/<name>/cordis.patch.yml 就是 Settings 页的活文档
|
|
90
|
+
# (config-editor.documentPath === profileContext.patchPath)。
|
|
91
|
+
# 复制一份快照,两边之后各自独立演进 —— 这正是「互不干扰」的实现方式。
|
|
92
|
+
if is_pristine_patch "$DST_PATCH"; then
|
|
93
|
+
cp "$SRC_PROFILE/cordis.patch.yml" "$DST_PATCH"
|
|
94
|
+
echo "→ 已继承 web 的配置快照"
|
|
95
|
+
else
|
|
96
|
+
echo "→ profile 的 patch 已非空,跳过继承(避免覆盖你自己的改动)"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# ── 第 4 步:追加会话 / 存储 / 附件隔离层 ───────────────────────────────
|
|
100
|
+
if grep -q 'id: session-persistence-jsonl' "$DST_PATCH"; then
|
|
101
|
+
echo "→ 隔离层已存在,跳过"
|
|
102
|
+
else
|
|
103
|
+
printf '\n' >>"$DST_PATCH"
|
|
104
|
+
cat "$ISOLATION" >>"$DST_PATCH"
|
|
105
|
+
echo "→ 已追加隔离层(sessions / storages / attachments 分家)"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# ── 第 5 步:继承「被 patch 点名」的依赖 ────────────────────────────────
|
|
109
|
+
# 第 3 步把 web 的设置文档整份复制了过来,而那份文档是**按 name: 点名**插件的。
|
|
110
|
+
# 出厂 @deepseek-ai/* 由 dsh 安装本体解析,不必装;但自己装的第三方包
|
|
111
|
+
# (例如 @deepseek-ai/dsh-web-search-exa)只住在 profile 的 node_modules 里
|
|
112
|
+
# —— 不跟着继承的话,那一行会以 "failed to import" 静默不激活,功能凭空少一块。
|
|
113
|
+
#
|
|
114
|
+
# 只继承**被 patch 引用**的那些:web 的 dependencies 里可能有装了却没有任何行
|
|
115
|
+
# 引用的包(本机就有 @aipanel/dsh-client 与 @aipanel/dsh-plugin),整份搬过来
|
|
116
|
+
# 只是死重量。反过来说,**不能只写进 package.json** —— node_modules 得跟上,
|
|
117
|
+
# 否则症状一模一样。
|
|
118
|
+
WANTED="$(
|
|
119
|
+
WB_MANIFEST="$DST_PROFILE/package.json" \
|
|
120
|
+
WEB_MANIFEST="$SRC_PROFILE/package.json" \
|
|
121
|
+
PATCH="$DST_PATCH" \
|
|
122
|
+
node -e '
|
|
123
|
+
const fs = require("node:fs");
|
|
124
|
+
const read = (p) => JSON.parse(fs.readFileSync(p, "utf8"));
|
|
125
|
+
const wb = read(process.env.WB_MANIFEST);
|
|
126
|
+
const web = read(process.env.WEB_MANIFEST);
|
|
127
|
+
|
|
128
|
+
// patch 里非注释行的 `name:` —— 注释掉的条目(# 开头)不算引用。
|
|
129
|
+
const referenced = new Set();
|
|
130
|
+
for (const line of fs.readFileSync(process.env.PATCH, "utf8").split("\n")) {
|
|
131
|
+
if (/^\s*#/.test(line)) continue;
|
|
132
|
+
const m = /^\s*-?\s*name:\s*"?([^"\s#]+)/.exec(line);
|
|
133
|
+
if (m) referenced.add(m[1]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// workbench 自己已有的声明优先 —— 重复 seed 不会覆盖你钉住的版本。
|
|
137
|
+
const own = wb.dependencies || {};
|
|
138
|
+
const missing = {};
|
|
139
|
+
for (const [name, version] of Object.entries(web.dependencies || {})) {
|
|
140
|
+
if (referenced.has(name) && !(name in own)) missing[name] = version;
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(missing).length > 0) {
|
|
143
|
+
wb.dependencies = Object.assign({}, missing, own);
|
|
144
|
+
fs.writeFileSync(process.env.WB_MANIFEST, JSON.stringify(wb, null, 2) + "\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 报出全部「被点名」的依赖(不只是这次新加的):node_modules 也可能被清过。
|
|
148
|
+
console.log(
|
|
149
|
+
Object.keys(web.dependencies || {})
|
|
150
|
+
.filter((name) => referenced.has(name))
|
|
151
|
+
.join(" "),
|
|
152
|
+
);
|
|
153
|
+
'
|
|
154
|
+
)"
|
|
155
|
+
|
|
156
|
+
WANTED_MISSING=0
|
|
157
|
+
if [ -n "$WANTED" ]; then
|
|
158
|
+
echo "→ patch 引用的依赖(须在 profile 内可解析): $WANTED"
|
|
159
|
+
for dep in $WANTED; do
|
|
160
|
+
[ -d "$DST_PROFILE/node_modules/$dep" ] || WANTED_MISSING=1
|
|
161
|
+
done
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [ "$WANTED_MISSING" = "1" ]; then
|
|
165
|
+
echo "→ node_modules 缺这些依赖,装进 profile(走 dsh 的包管理器)"
|
|
166
|
+
dsh plugin --profile "$(basename "$DST_PROFILE")" install
|
|
167
|
+
else
|
|
168
|
+
echo "→ 依赖已齐备,跳过安装"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# 装 bundle 的目标:开发态是这个仓库(link),安装包态是源上那个包。
|
|
172
|
+
# 判据是「git 顶层在不在」—— npm pack 的产物里不含 .git(见 package.json 的 files)。
|
|
173
|
+
# ⚠️ 不能判 `$REPO_DIR/.git`:workspace 之后 .git 在仓库根(同 bin/dsh-workbench)。
|
|
174
|
+
if [ -n "$GIT_TOP" ]; then
|
|
175
|
+
BUNDLE_TARGET="$REPO_DIR"
|
|
176
|
+
else
|
|
177
|
+
BUNDLE_TARGET="@dsh-workbench/core@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
cat <<EOF
|
|
181
|
+
|
|
182
|
+
profile '$DST_PROFILE' 的骨架好了(bundle 清单 + 设置快照 + 隔离层)。
|
|
183
|
+
|
|
184
|
+
装 bundle —— 两条**等价**的路,任选一条(都走 dsh 的 runPluginCommand,装完会
|
|
185
|
+
把 bundle 登记进 profile 清单):
|
|
186
|
+
|
|
187
|
+
① 一条命令(建 profile + 装 bundle,幂等,可重复跑)
|
|
188
|
+
$REPO_DIR/bin/dsh-workbench setup
|
|
189
|
+
|
|
190
|
+
② 在**工作台进程**的会话里让 agent 做(plugin_manager 只作用于当前 profile)
|
|
191
|
+
启动: $REPO_DIR/bin/dsh-workbench
|
|
192
|
+
让 agent 执行: install_bundle target=$BUNDLE_TARGET
|
|
193
|
+
|
|
194
|
+
然后验收(工作台占另一个端口,dsh web 继续用 3080,可同时开):
|
|
195
|
+
|
|
196
|
+
${VERIFY_DIR}/verify.sh --record # 先记录基线(可选,越早越好)
|
|
197
|
+
${VERIFY_DIR}/verify.sh
|
|
198
|
+
|
|
199
|
+
EOF
|