@dsh-workbench/core 0.3.7 → 0.3.9
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 +2 -1
- package/bin/dsh-workbench +67 -1
- package/client.js +95 -9
- package/dist/client/client.body.js +95 -9
- package/dist/client/index.js +95 -9
- package/dist/host/index.js +141 -5
- package/dist/types/app/client.d.ts +2 -1
- package/dist/types/app/layouts/env-gate/boot-gate.d.ts +8 -0
- package/dist/types/shared/api/host/diy-preset-health.d.ts +35 -0
- package/dist/types/shared/api/host/installed-module.d.ts +9 -0
- package/dist/types/shared/api/host/preflight-route.d.ts +15 -0
- package/dist/types/shared/api/host/preflight.d.ts +12 -1
- package/dist/types/shared/hooks/use-long-task.d.ts +3 -0
- package/docs/HANDOFF.md +23 -18
- package/docs/OPERATIONS.md +3 -0
- package/package.json +6 -2
- package/scripts/seed-profile.sh +34 -0
- package/skills/workbench-module-diy/KIT.md +8 -9
package/docs/HANDOFF.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
| # | 东西 | 为什么 | 怎么有 |
|
|
21
21
|
| --- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
|
|
22
|
-
| 1 | `dsh` | 本体。本仓库在 `0.1
|
|
22
|
+
| 1 | `dsh` | 本体。本仓库在 `0.2.1-rc.2` 上验过,**低于它的会被拒绝** —— 见第八节 | `npm i -g @deepseek-ai/dsh@latest`(公网 npm 上有) |
|
|
23
23
|
| 2 | `node` + **`pnpm`** | dsh 的 `plugin` 子命令(以及 web 的 Plugins 页)都是**转发给 pnpm** 的;没有 pnpm 会直接报 `dsh: pnpm was not found` | 各自装 |
|
|
24
24
|
| 3 | 跑过一次 `dsh web` | 走 A 需要它给出那个 profile;走 B 时 `setup` 拿 `~/.dsh/profiles/web` 当模板。没有它会**明确报错**,不会静默建出半个 profile | `dsh web` |
|
|
25
25
|
| 4 | 一个能解析这个包的源 | 包在**公网 npm** 上,默认源即可。只有将来改发内网源时,才要 `npm config set @<scope>:registry …` + `npm login`(带 scope 的包只去 `.npmrc` 里给这个 scope 指的源找) | `npm view @dsh-workbench/core version` 出号即可 |
|
|
@@ -239,9 +239,26 @@ manifest;彩排结束、以及「零上传」的失败,都会自动回滚。
|
|
|
239
239
|
|
|
240
240
|
## 八、版本与兼容(实话)
|
|
241
241
|
|
|
242
|
-
`package.json`
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
外壳在 `package.json` 里声明了 **`engines.dsh: ">=0.2.1-rc.2"`**(本仓验过的那一代,2026-10-18)。
|
|
243
|
+
这条声明是后补的,来历见
|
|
244
|
+
[`.scratch/dsh-version-floor/spec.md`](../../../.scratch/dsh-version-floor/spec.md):真实用户装了
|
|
245
|
+
`dsh 0.2.0-rc.2`(当时 npm `latest` 还是它)+ 工作台,工作台照常起得来,**只有 DIY 模式整个不见、
|
|
246
|
+
一句错都不报** —— `preset/diy.patch.yml` 是从 dsh 0.2.1-rc.2 那份 `standard.patch.yml` 派生的,
|
|
247
|
+
里面一行 `tool-schedule` 在 0.2.0 上根本不存在,preset 挂不起来;而 dsh 的模式选择器**只列挂得起来的**
|
|
248
|
+
(healthy presets only),于是"工作台 DIY"从用户面前消失。同一代的 `uiWorkspace.startSession()`
|
|
249
|
+
也**不收** `agentPreset`(0.2.1-alpha.3 才收),所以这不是"少一把工具"的降级档,是 DIY 模式整块不可用。
|
|
250
|
+
|
|
251
|
+
因此**声明一个下限**(而不是"谁都能装"):dsh 自 `0.2.1-alpha.4` 起预检**先读 `engines.dsh`**
|
|
252
|
+
(声明了它,peer 就被整体忽略;写 `workspace:` 一律判不兼容;`engines` 不是对象或 `engines.dsh`
|
|
253
|
+
不是字符串会让清单不可读并**直接拒绝**),不满足就拒绝安装,
|
|
254
|
+
可用 `dsh plugin --profile workbench allow-version <包@版本> --dsh-version <版本> --accept-risk` 豁免。
|
|
255
|
+
旧到不认识 `engines.dsh` 的 dsh(0.2.0 及更早只读 `peerDependencies`)由**启动器那道硬门禁**兜
|
|
256
|
+
(`bin/dsh-workbench`,与"bundle 没被选中"同一档:拒绝启动 + 打印升级命令)。
|
|
257
|
+
|
|
258
|
+
⚠️ **别把下限改写成 `peerDependencies['@deepseek-ai/dsh']`**:pnpm 的 `auto-install-peers`
|
|
259
|
+
(v11 实测:连 `peerDependenciesMeta.optional` 的 peer 也照装)会把**整套 dsh**拖进 profile。
|
|
260
|
+
下限只有一处真相(`package.json` 的 `engines.dsh`),启动器读它、不另写版本号 —— selfcheck 有一条判据
|
|
261
|
+
钉着"下限 = `@deepseek-ai/dsh-client-ui-primitives` 那个 pin"。
|
|
245
262
|
|
|
246
263
|
代价要清楚:工作台依赖 dsh 的注入面(`@deepseek-ai/dsh-client-ui-slots`、
|
|
247
264
|
`@deepseek-ai/dsh-client-locale`,见 `dsh.client.inject`)与一批**按名字取的服务**
|
|
@@ -253,20 +270,8 @@ dsh 现在发的是预发布(npm 的 `next` 频道;本仓**验过的**是 `0
|
|
|
253
270
|
**devDependency 里的 `@deepseek-ai/dsh-client-ui-primitives` 跟着"验过的 dsh 版本"走**
|
|
254
271
|
([ADR-0028 §4.1](../../../docs/adr/0028-workbench-ui-uses-dsh-primitives.md)):它只出类型,
|
|
255
272
|
但类型比运行时旧一代,编译期就替不了你把关。当前两份都钉 `0.2.1-rc.2`
|
|
256
|
-
(`packages/workbench` / `packages/shared`;改完要跑一次 `pnpm install` 更新 `pnpm-lock.yaml
|
|
257
|
-
|
|
258
|
-
想改成"装的时候就拦住",在 `package.json` 里声明:
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
"peerDependencies": { "@deepseek-ai/dsh": "^0.2.1-rc.2" }
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
dsh 会用这条做**安装预检**,不满足就拒绝安装,
|
|
265
|
-
可用 `dsh plugin --profile workbench allow-version <包@版本> --dsh-version <版本> --accept-risk` 豁免。
|
|
266
|
-
**自 0.2.1-alpha.4 起预检先读包自己的 `engines.dsh`**:声明了它,上面这条 peer 就被整体忽略
|
|
267
|
-
(只有没有 `engines.dsh` 时才看 peer;`engines.dsh` 里写 `workspace:` 一律判不兼容,
|
|
268
|
-
`engines` 不是对象或 `engines.dsh` 不是字符串会让清单不可读并**直接拒绝**)。要支持一段 dsh 版本,
|
|
269
|
-
写 `engines.dsh` 比写 peer 更直接。
|
|
273
|
+
(`packages/workbench` / `packages/shared`;改完要跑一次 `pnpm install` 更新 `pnpm-lock.yaml`),
|
|
274
|
+
**升这一份时要连 `engines.dsh` 一起升**(selfcheck 那条判据把两者钉在一起)。
|
|
270
275
|
|
|
271
276
|
> `NPM_REGISTRY` 是**你们自己的**内网 npm 源地址(形如 `https://<源主机>/<路径>/`)。
|
|
272
277
|
> 本仓不写它:开源包里不该出现任何内网地址。
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
`OPENCLI_PIN` 锁),但它连的 **daemon 是整机一个** —— 见下面「隔离边界」。
|
|
27
27
|
|
|
28
28
|
前提:`dsh` 和 `pnpm` 在 PATH 上(`dsh plugin` 是转发给 pnpm 的),
|
|
29
|
+
**`dsh` 不低于验过的那一代**(`engines.dsh`;旧到不认识它的 dsh 由启动器当场拒绝并给
|
|
30
|
+
`npm i -g @deepseek-ai/dsh@latest` —— 低于它的 dsh 上 DIY 模式整个不出现且不报错,见
|
|
31
|
+
[`.scratch/dsh-version-floor/spec.md`](../../../.scratch/dsh-version-floor/spec.md)),
|
|
29
32
|
且 `~/.dsh/profiles/web` 已存在(跑过一次 `dsh web` 即可)—— seed 拿它当**模板**
|
|
30
33
|
(bundle 清单),之后也是「配置同步」的默认来源。
|
|
31
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
+
"//engines": "DIY 模式(preset/diy.patch.yml)是从 **dsh 出厂那份 standard.patch.yml** 派生的,所以这份外壳只跟着它派生的那一代 dsh 走:dsh 太旧时那个 preset 挂不起来,而 dsh 的模式选择器**只列挂得起来的** ⇒ 用户面前「工作台 DIY」整个不见,一句错都不报(实测 0.2.0-rc.2:缺 dsh-tool-schedule)。这里声明下限,让 dsh 自己的兼容性预检在**装的时候**拒绝(自 0.2.1-alpha.4 起 engines.dsh 优先于 peer)。⚠️ **不写 peerDependencies['@deepseek-ai/dsh']**:pnpm 的 auto-install-peers(v11 连 optional 也装)会把整套 dsh 拖进 profile。旧到不认识 engines.dsh 的 dsh(0.2.0 及更早)由启动器那道硬门禁兜 —— 见 bin/dsh-workbench。判据:selfcheck 的「声明下限 = 验过的 dsh 版本」那条。",
|
|
66
|
+
"engines": {
|
|
67
|
+
"dsh": ">=0.2.1-rc.2"
|
|
68
|
+
},
|
|
65
69
|
"peerDependencies": {
|
|
66
70
|
"react": "*"
|
|
67
71
|
},
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
"access": "public"
|
|
88
92
|
},
|
|
89
93
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.3.
|
|
94
|
+
"@dsh-workbench/shared": "0.3.9"
|
|
91
95
|
},
|
|
92
96
|
"scripts": {
|
|
93
97
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
package/scripts/seed-profile.sh
CHANGED
|
@@ -28,6 +28,12 @@ DST_PROFILE="$DSH_HOME/profiles/$PROFILE"
|
|
|
28
28
|
PKG='@dsh-workbench/core'
|
|
29
29
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
30
30
|
GIT_TOP="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
31
|
+
# 同 bin/dsh-workbench 那一处(那边写清了为什么):`--show-toplevel` 只说明**某个祖先**是仓库,
|
|
32
|
+
# 装出来的那份可能刚好落在某个仓库里(`$HOME` 是 dotfiles 仓库时最常见)—— 还要证明这份
|
|
33
|
+
# package.json 本身被那个仓库跟踪,才算是仓库副本。
|
|
34
|
+
if [ -n "$GIT_TOP" ] && ! git -C "$REPO_DIR" ls-files --error-unmatch package.json >/dev/null 2>&1; then
|
|
35
|
+
GIT_TOP=""
|
|
36
|
+
fi
|
|
31
37
|
ISOLATION="$REPO_DIR/profile/isolation.patch.yml"
|
|
32
38
|
|
|
33
39
|
# `verify.sh` 留在**仓库根**(它证明的是"dsh web 未被动过 / 组合树差异 / 数据分家",
|
|
@@ -134,6 +140,34 @@ fs.writeFileSync(process.env.WB_MANIFEST, JSON.stringify(wb, null, 2) + "\n");
|
|
|
134
140
|
console.log("→ 已继承 web 的 bundle 列表: " + merged.join(", "));
|
|
135
141
|
'
|
|
136
142
|
|
|
143
|
+
# ── 第 2.5 步:修掉旧版启动器留下的"装出来的那份被当成仓库副本" ──────────
|
|
144
|
+
#
|
|
145
|
+
# 旧判据只问 `git rev-parse --show-toplevel`("**某个祖先**是不是仓库"),于是包目录落在任何
|
|
146
|
+
# 仓库里的机器(最常见:`$HOME` 是 dotfiles 仓库,而 npm/nvm 的前缀就在 `$HOME` 下)在 setup
|
|
147
|
+
# 时会把**全局安装目录**以 `link:` 装进 profile —— 市场里工作台那一行因此说「这份是本仓正在
|
|
148
|
+
# 开发的那一份,不从源上取。」(不比对源、不提示更新),而 `npm i -g …@latest` 还会悄悄换掉
|
|
149
|
+
# profile 跑的东西。判据窄到只认那一个坏形状:**我自己是装出来的那份**(GIT_TOP 为空)
|
|
150
|
+
# **且 profile 里那条 `link:` 指着的正是我自己这份目录** —— 指别处的 link:(用户自己链的)不动。
|
|
151
|
+
if [ -z "$GIT_TOP" ] && [ -f "$DST_PROFILE/package.json" ]; then
|
|
152
|
+
WB_MANIFEST="$DST_PROFILE/package.json" WB_PKG="$PKG" WB_SELF="$REPO_DIR" \
|
|
153
|
+
WB_TARGET="$PKG@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)" \
|
|
154
|
+
node -e '
|
|
155
|
+
const fs = require("node:fs");
|
|
156
|
+
const path = require("node:path");
|
|
157
|
+
const file = process.env.WB_MANIFEST;
|
|
158
|
+
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
159
|
+
const deps = manifest.dependencies ?? {};
|
|
160
|
+
const spec = String(deps[process.env.WB_PKG] ?? "");
|
|
161
|
+
if (!spec.startsWith("link:")) process.exit(0);
|
|
162
|
+
const real = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
|
|
163
|
+
const target = path.resolve(path.dirname(file), spec.slice("link:".length));
|
|
164
|
+
if (real(target) !== real(process.env.WB_SELF)) process.exit(0);
|
|
165
|
+
manifest.dependencies = Object.assign({}, deps, { [process.env.WB_PKG]: process.env.WB_TARGET });
|
|
166
|
+
fs.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
167
|
+
console.log("→ profile 里那条 link: 指的是我这份装出来的副本(旧启动器判错的产物)—— 已改回 " + process.env.WB_TARGET);
|
|
168
|
+
'
|
|
169
|
+
fi
|
|
170
|
+
|
|
137
171
|
# ── 第 3 步:追加会话 / 存储 / 附件隔离层 ───────────────────────────────
|
|
138
172
|
if grep -q 'id: session-persistence-jsonl' "$DST_PATCH"; then
|
|
139
173
|
echo "→ 隔离层已存在,跳过"
|
|
@@ -105,7 +105,7 @@ h(Form<{ branch: string }>, {
|
|
|
105
105
|
|
|
106
106
|
**`ConfirmBar`** —— 「先问一句再动手」那一条(标题 / 说明 / 键值 / 失败那句 / 动作区一次收口):
|
|
107
107
|
键值走 `Rows` / `Row`、错走 `Note tone="problem"`、动作走 `FormActions` —— **别自己拼这四个槽**。
|
|
108
|
-
|
|
108
|
+
**要不要问归调用点**(`danger` 只标"破坏性")。口径见 ADR-0073。
|
|
109
109
|
|
|
110
110
|
怎么排(标签位置、错误态、尺寸)见 §5。
|
|
111
111
|
|
|
@@ -120,7 +120,7 @@ kit 的 hook 也分四个场合,**别自己 `useState` + `setTimeout` 造一
|
|
|
120
120
|
| "至少转一会儿"的忙态 | `useVisibleBusy`(投影那两件 `busyView` / `heldPhase`) |
|
|
121
121
|
| 一格**卡片**起步(gate + 忙 + 探针 + 上报 + blocker 一次叠好) | `useUnitBoard`(`read(gated)` 里自己调 `useApiResource`) |
|
|
122
122
|
| 只要一格的能力门 / 整张能力表 | `useCapabilityGate` / `useCapabilityMap` |
|
|
123
|
-
| 写(**别在组件里 `fetch`**) / 轮询长任务 | `useHostWrite` / `useLongTask`(画用 `LongTaskRow`
|
|
123
|
+
| 写(**别在组件里 `fetch`**) / 轮询长任务 | `useHostWrite` / `useLongTask`(画用 `LongTaskRow`;判「还在跑」`longTaskIsLive`、写完让这一格立刻重读 `refresh()`) |
|
|
124
124
|
|
|
125
125
|
取数那条链的图见 [LINKAGE.md](LINKAGE.md) §8,写通道与配置见 [PANEL.md](PANEL.md) §5。
|
|
126
126
|
|
|
@@ -131,8 +131,8 @@ kit 的 hook 也分四个场合,**别自己 `useState` + `setTimeout` 造一
|
|
|
131
131
|
它自己组 `[...extra, ...cancel, submit(primary)]`(提交钮永远在右;「一会在左一会在右」就是自己摆的结果);
|
|
132
132
|
其余口径(时机 / 第一处错误 / `busy` / `disabled`)看它的 JSDoc 与 ADR-0068 / ADR-0074。
|
|
133
133
|
|
|
134
|
-
`FormRows` + `FormRow` + `FormActions`
|
|
135
|
-
|
|
134
|
+
`FormRows` + `FormRow` + `FormActions` 留给**不是整张表单**的地方:单行(一格字段嵌在别处)、
|
|
135
|
+
非表单的一组动作(工具栏、卡头那几颗钮)—— 那里才自己拼这三件:
|
|
136
136
|
|
|
137
137
|
```tsx
|
|
138
138
|
h(FormRows, null,
|
|
@@ -145,7 +145,7 @@ h(FormRows, null,
|
|
|
145
145
|
- **`hint` 与 `problem` 二选一**:有错先显示错。
|
|
146
146
|
|
|
147
147
|
**错误态**:位置与长相归 `FormRow.problem` + 控件的 `invalid`;**算错的逻辑留在模块**(纯函数 + 文案,
|
|
148
|
-
交给 `FormField.validate`)—— **别指望外壳给校验引擎**(不做 `rules` / 异步 /
|
|
148
|
+
交给 `FormField.validate`)—— **别指望外壳给校验引擎**(不做 `rules` / 异步 / 联动),
|
|
149
149
|
**"提交过没有"与聚焦第一处错也归 `Form`**(别自己记布尔、别自己调 `revealFirstProblem`)。
|
|
150
150
|
|
|
151
151
|
**选择类用哪件**:候选多 / 要搜索 / 要分组 / 名单可能读不到 ⇒ `SelectField`;候选少、要一眼看全 / 多选 ⇒
|
|
@@ -172,7 +172,7 @@ h('form', …) // ← 裸 <form> / 手写行槽
|
|
|
172
172
|
- **`Link`** —— **唯一**的外链写法(`h(Link, { href, children })`):**别去掉箭头、别在文案里自带 `→`、
|
|
173
173
|
别手写 `<a>`**(判据红)。
|
|
174
174
|
- **`SettingsSection` / `Stack` / `BlockerState` / `Tip`** —— 一屏设置里的一节 / 竖排间距 / 阻断屏 /
|
|
175
|
-
Tooltip
|
|
175
|
+
Tooltip(只有图的地方**必须**有它或 `aria-label`);prop 看 `.d.ts`。
|
|
176
176
|
- **真机上造不出来的那几态**:拿你自己那份 `plugin.test.ts` 把五态各喂一遍(`check --only cards`
|
|
177
177
|
拿七种外壳侧输入各喂一遍,只问"退化得干不干净")。
|
|
178
178
|
- **图标** —— `IconPanelScreen` / `IconModuleMark` / `IconNpm` / `IconOpencli`;其余图形一律从 dsh 的
|
|
@@ -189,11 +189,10 @@ h('form', …) // ← 裸 <form> / 手写行槽
|
|
|
189
189
|
|
|
190
190
|
## 8 kit 里没有、但你确定要标准控件
|
|
191
191
|
|
|
192
|
-
**先问工具**:DIY 会话里调 `workbench_diy_components
|
|
193
|
-
回它的类型文件与声明)。工程域用 `dsh-wb-module check --only primitives`
|
|
192
|
+
**先问工具**:DIY 会话里调 `workbench_diy_components`(不填给清单;`name=Button` 回它的类型与声明)。工程域用 `dsh-wb-module check --only primitives`
|
|
194
193
|
(`--all` 全表 / `--json` 机器档)—— 分桶导出 + 与 kit 求交(`kit 里已有` 的优先用)+
|
|
195
194
|
该写进 `package.json` 的那两行;骨架默认已备好。
|
|
196
|
-
**要看某一件**:`--file <名字>`(`--files | grep
|
|
195
|
+
**要看某一件**:`--file <名字>`(`--files | grep <名字>`)。
|
|
197
196
|
|
|
198
197
|
```jsonc
|
|
199
198
|
"dsh": { "client": { "external": [ "@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives" ] } },
|