dsh-workflow-map 0.0.0-stage → 0.3.4

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.
@@ -0,0 +1,11 @@
1
+ # dsh-workflow-map — bundle 挂载声明
2
+ #
3
+ # 作用:把这个包的宿主半插入 DSH 的插件行。
4
+ # 浏览器半不由本文件声明 —— package.json 的 `dsh.client`(platform: web)指向本包的
5
+ # ./client,因此这里没有 client 行。
6
+ #
7
+ # id 在整个 profile 的 patch 层里必须唯一。
8
+
9
+ - insert:
10
+ - id: workflow-map
11
+ name: 'dsh-workflow-map'
@@ -0,0 +1,256 @@
1
+ # 发布指南
2
+
3
+ 本文件面向**维护者**:如何把 `dsh-workflow-map` 发布到 GitHub 与 npm,以及改代码后该跑哪些检查。
4
+
5
+ > 命令里的 `<repo>` 指本仓库的本地路径,`<你的用户名>` 指 GitHub 用户名。
6
+
7
+ ---
8
+
9
+ ## 一、发布前的产物清单
10
+
11
+ 仓库根目录应包含:
12
+
13
+ | 文件 | 作用 |
14
+ |---|---|
15
+ | `package.json` | 包元数据:`private: false`、`repository` / `homepage` / `bugs` / `keywords` / `engines` / `scripts` |
16
+ | `LICENSE` | MIT 全文 |
17
+ | `CHANGELOG.md` | Keep a Changelog 格式 |
18
+ | `README.md` | 面向使用者的说明 |
19
+ | `.gitignore` | 排除 `node_modules`、`*.bak*`、构建与测量产物 |
20
+ | `index.js` / `lib/` / `src/` | 宿主半与浏览器半源码 |
21
+ | `client.js` | 浏览器半生成物(`node tools/build-client.mjs` 产出,不手改) |
22
+ | `cordis.patch.yml` | 挂载声明 |
23
+ | `locale/` / `icon.svg` | 词典与图标 |
24
+ | `tools/` | 自测、探针、浏览器量测(**不随 npm 包发布**,进 Git) |
25
+
26
+ **发布前核对**:`package.json` 里 `repository.url`、`homepage`、`bugs.url` 三处必须指向真实的 GitHub 用户名与仓库名;
27
+ 仓库改名或 fork 后要同步改这三处。
28
+
29
+ ---
30
+
31
+ ## 二、发布到 GitHub
32
+
33
+ > 下面的 `v0.3.0` 只是示例版本号;**当前版本见 `package.json`** 的 `version` 字段。
34
+
35
+ ### 2.1 建仓库
36
+
37
+ 在 <https://github.com/new> 建一个**空**仓库,仓库名与 `package.json` 的 `name` 一致。
38
+
39
+ ⚠️ **不要**勾选 "Add a README / .gitignore / license":本地已有,勾选会导致首次推送冲突。
40
+
41
+ ### 2.2 初始化并推送
42
+
43
+ ```bash
44
+ cd <repo>
45
+
46
+ git init
47
+ git add -A
48
+ git status # 确认没有 *.bak* / node_modules / 临时文件
49
+ git commit -m "feat: v0.3.0 初始发布"
50
+ git branch -M main
51
+ git remote add origin https://github.com/<你的用户名>/dsh-workflow-map.git
52
+ git push -u origin main
53
+ ```
54
+
55
+ ### 2.3 打标签
56
+
57
+ ```bash
58
+ git tag -a v0.3.0 -m "v0.3.0"
59
+ git push origin v0.3.0
60
+ ```
61
+
62
+ 再到仓库页 **Releases → Draft a new release**,选中该 tag,把 `CHANGELOG.md` 里对应段落
63
+ 粘进发布说明,发布。
64
+
65
+ ### 2.4 建议补充的门面
66
+
67
+ 发布后到仓库页右侧 **About** 齿轮里填:
68
+
69
+ - **Description**:一句话说明这是什么、解决什么问题;
70
+ - **Topics**:`deepseek-harness`、`dsh`、`cordis`、`plugin`、`workflow`、`progress`;
71
+ - 一份**截图**(静态界面截图 + 一段动效 GIF 更能体现差异化)。
72
+
73
+ ---
74
+
75
+ ## 三、发布到 npm
76
+
77
+ > 当前版本见 `package.json` 的 `version`;`pnpm pack --dry-run` 的版本行、`npm publish`
78
+ > 与 `git tag` 都以该值为准。
79
+
80
+ ### 3.1 先自验包内容
81
+
82
+ ```bash
83
+ pnpm pack --dry-run
84
+ ```
85
+
86
+ 必须满足:
87
+
88
+ - 第一行为 `package: dsh-workflow-map@<版本>`;
89
+ - 含 `client.js`、`lib/`、`src/`、`index.js`、`cordis.patch.yml`、`locale/*.json`、`icon.svg`、
90
+ `README.md`、`CHANGELOG.md`、`LICENSE`、`docs/OPEN-SOURCE-GUIDE.md`、`docs/host-compatibility.md`;
91
+ - **不含** `tools/`、`evidence/`、`*.bak*` 与本地草稿。
92
+
93
+ `files[]` 是白名单:以**文件**(而非目录)列出,能避免整目录被连带发布。
94
+
95
+ ### 3.2 发布
96
+
97
+ ```bash
98
+ npm login
99
+ npm publish --access public
100
+ ```
101
+
102
+ 若报 **402**(包名被占用),把 `package.json` 的 `name` 改为 `@<你的用户名>/dsh-workflow-map`,
103
+ 再执行一次 `npm publish --access public`。
104
+
105
+ ### 3.3 检索
106
+
107
+ `keywords` 已含 `deepseek-harness` / `dsh` / `cordis` / `plugin` / `bundle` / `workflow` / `progress` / `ui`;
108
+ `description` 写明主要能力,这是搜索命中的主要文本。
109
+
110
+ ---
111
+
112
+ ## 四、安装方式(给使用者)
113
+
114
+ ### 方式一:让 agent 安装
115
+
116
+ ```
117
+ 用 plugin_manager 安装 dsh-workflow-map,路径是 <插件的绝对路径>
118
+ ```
119
+
120
+ 只有返回 `applied` 才算生效。
121
+
122
+ ### 方式二:命令行
123
+
124
+ ```bash
125
+ cd "$HOME/.dsh/profiles/desktop"
126
+ pnpm add dsh-workflow-map
127
+ ```
128
+
129
+ 再把 `"dsh-workflow-map"` 追加到该目录 `package.json` 的 `dsh.profile.bundles` 数组末尾,重启 DSH。
130
+
131
+ ### 方式三:本地源码
132
+
133
+ ```bash
134
+ cd "$HOME/.dsh/profiles/desktop"
135
+ pnpm add "dsh-workflow-map@link:<repo>"
136
+ ```
137
+
138
+ ### 装完验证三步
139
+
140
+ ```bash
141
+ # 1) 文件在位
142
+ ls "$HOME/.dsh/profiles/desktop/node_modules/dsh-workflow-map"
143
+
144
+ # 2) 宿主接口可用(期望 200 + JSON)
145
+ curl "http://127.0.0.1:<GUI端口>/dsh-workflow/state?sessionId=<会话ID>"
146
+
147
+ # 3) 界面上:左栏图标、会话标签栏入口、主内容区面板
148
+ ```
149
+
150
+ ---
151
+
152
+ ## 五、改代码后的检查
153
+
154
+ ```bash
155
+ # 改过 src/client/*.js 必须重新生成浏览器半
156
+ node tools/build-client.mjs
157
+
158
+ # 离线自测(10 套)
159
+ node tools/selftest-fold.mjs # 折叠语义
160
+ node tools/selftest-host.mjs # 注册、schema、投影接线
161
+ node tools/selftest-orchestrator.mjs # PAE 计划读取与合并
162
+ node tools/selftest-shape.mjs # 包骨架、配置、files 白名单
163
+ node tools/selftest-panel.mjs # 渲染树、样式、交互
164
+ node tools/selftest-detail.mjs # 节点/分步详情
165
+ node tools/selftest-effects.mjs # 动画与减少动效
166
+ node tools/selftest-settings.mjs # 设置与导出
167
+ node tools/probe-route.mjs # 真实 HTTP 路由
168
+ node tools/probe-steer.mjs # 注入 / 撤回通道
169
+ ```
170
+
171
+ 合计 **1533 条离线断言**。也可一条命令:
172
+
173
+ ```bash
174
+ npm test
175
+ ```
176
+
177
+ **真实日志只读回归**(不需要写权限):
178
+
179
+ ```bash
180
+ node tools/check-real-injects.mjs # 批量折叠历史会话,报告注入/审批/失败数
181
+ node tools/audit-real.mjs # 四条硬约束审计
182
+ ```
183
+
184
+ **浏览器量测**(需要一份 Chromium;脚本会自动探测 Chrome / Edge):
185
+
186
+ ```bash
187
+ pwsh -File tools/measure-panel.ps1 # 单页地基
188
+ pwsh -File tools/measure-timing.ps1 # 进度条第二行与 ETA 缺席
189
+ pwsh -File tools/measure-inject.ps1 # 热注入常驻条 / 历史三态 / 撤回
190
+ pwsh -File tools/measure-dock.ps1 # 常驻注入条几何
191
+ pwsh -File tools/measure-flow.ps1 # 类型页调用清单与上限提示
192
+ pwsh -File tools/measure-effects.ps1 # 动画与减少动效
193
+ pwsh -File tools/measure-branches.ps1 # 卫星圆点与侧位上限
194
+ pwsh -File tools/measure-detail.ps1 # 节点 / 分步详情
195
+ pwsh -File tools/measure-settings.ps1 # 设置卡与导出
196
+ pwsh -File tools/measure-main.ps1 # 单页几何与交互
197
+ ```
198
+
199
+ 合计 **323 条浏览器量测**(0.3.4 起 `measure-main` 另含 15 条浅色/深色主题对比检查)。
200
+
201
+ > `tools/retired/` 里是量「已移除界面」的历史脚本,**不属于验证链**,不期望通过。
202
+
203
+ ---
204
+
205
+ ## 六、四条硬约束
206
+
207
+ 改完代码后,用真实日志自查这四项:
208
+
209
+ | # | 约束 | 判定方法 |
210
+ |---|---|---|
211
+ | ① | 地图分步 == 对话当前 todo 表 | 视图分步标题集合 ⊆ todo 表标题集合,且顺序一致 |
212
+ | ② | 有且只有一个分步处于「进行中」 | `steps.filter(s => s.status === 'running').length <= 1` |
213
+ | ③ | 需人工确认的分步打「需确认」 | `step.pendingConfirm.length > 0 \|\| plan.confirmations[planIndex]` |
214
+ | ④ | 热注入可用且可追溯 | `view.injects[]` 里 `pickedUp` 区分「已接下 / 待拾取」 |
215
+
216
+ 一条命令:
217
+
218
+ ```bash
219
+ node tools/audit-real.mjs
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 七、已知边界
225
+
226
+ 1. **输入框(composer)不属于本插件。** 它由宿主在与面板并列的位置渲染,插件无法从自己这一侧移除它。
227
+ 会话标签栏席位用 `:has()` 规则让它在那一页收起,切回对话页自动恢复;这是样式层的处理,
228
+ 不改宿主结构。
229
+ 2. **宿主半改动需要重启 DSH。** `lib/index.js`、`src/fold.mjs`、`src/route.js` 属 ESM 模块缓存,
230
+ 禁用/启用 bundle 或开新会话都不生效。
231
+ 3. **每步节点有上限**:单步 300、全会话 1200。淘汰从节点最多的步开始,已完成步不会被清空;
232
+ 超出时界面会说明「另有 N 个节点宿主未发送」。
233
+ 4. **`git push` 与 `npm publish` 需要账号凭据**,本指南只描述流程,不在 CI 中自动执行。
234
+
235
+ ---
236
+
237
+ ## 八、目录速查
238
+
239
+ ```
240
+ dsh-workflow-map/
241
+ ├─ index.js 包入口,再导出 lib/index.js
242
+ ├─ lib/index.js 宿主半:会话投影 + 三条 HTTP 端点
243
+ ├─ src/
244
+ │ ├─ fold.mjs 事件 → 状态 → 视图(纯函数)
245
+ │ ├─ orchestrator.mjs 只读 PAE 计划文件
246
+ │ ├─ route.js /state · /steer · /unsteer
247
+ │ └─ client/ 浏览器半源码(构建时内联)
248
+ ├─ client.js 生成物,禁止手改
249
+ ├─ cordis.patch.yml 挂载声明
250
+ ├─ locale/{zh,en}.json 词典
251
+ ├─ docs/ 发布指南与宿主兼容性核验
252
+ ├─ tools/ 自测 · 探针 · 浏览器量测
253
+ ├─ CHANGELOG.md
254
+ ├─ LICENSE (MIT)
255
+ └─ README.md
256
+ ```
@@ -0,0 +1,320 @@
1
+ # 宿主兼容性核验(DSH `0.2.1-rc.1` – `0.2.1-rc.5`)
2
+
3
+ 本文件回答一个问题:本插件声明的宿主范围,在 `0.2.1` 全系列(`rc.1` 至 `rc.5`)里是否真的有依据。
4
+
5
+ 结论以 `package.json` 的 `engines.dsh` 为唯一声明口径;本文件记录该口径的判据、核验方法与逐条证据,供使用者自行复核。
6
+
7
+ ---
8
+
9
+ ## 一、声明
10
+
11
+ ```json
12
+ "engines": {
13
+ "node": ">=20",
14
+ "dsh": ">=0.2.1-rc.1 <0.3.0-0"
15
+ }
16
+ ```
17
+
18
+ 区间含义:
19
+
20
+ - 起点 `>=0.2.1-rc.1`:`0.2.1` 元组上的第一个预发布版本,带显式预发布标签。
21
+ - 上界 `<0.3.0-0`:排除 `0.3.0` 及其预发布(`0.3.0-0` 是 `0.3.0` 预发布的最小值),即不声称兼容下一 minor。
22
+
23
+ 本插件不使用 `dsh.host` 之类的自定义元数据字段:宿主包由 `peerDependencies` 声明(均为可选 peer),版本闸门由 `engines.dsh` 单独决定。
24
+
25
+ ---
26
+
27
+ ## 二、`engines.dsh` 由谁判定:两种判据
28
+
29
+ 同一个区间字符串会被两套判据读取,结论不完全相同,声明必须同时满足两者。
30
+
31
+ ### 2.1 DSH 自身的兼容性判定
32
+
33
+ `@deepseek-ai/dsh-app-boot` 的 `evaluatePluginCompatibility()` 是安装与启用期的判定入口:
34
+
35
+ ```js
36
+ // @deepseek-ai/dsh-app-boot lib/index.js:252-284(节选,省略参数校验与回落分支的具体实现)
37
+ function satisfies(runtimeVersion, range) {
38
+ return range.trim() !== "" && semver.satisfies(runtimeVersion, range, { includePrerelease: true });
39
+ }
40
+ function unmetRequirements(fields, runtimeVersion) {
41
+ const engines = Object.hasOwn(fields, "engines") ? objectOf$1(fields.engines, "Plugin manifest engines") : {};
42
+ if (Object.hasOwn(engines, "dsh")) {
43
+ const range = engines.dsh;
44
+ if (typeof range !== "string") throw new Error("Plugin manifest engines.dsh must be a string");
45
+ return satisfies(runtimeVersion, range) ? {} : { "engines.dsh": range };
46
+ }
47
+ // 没有 engines.dsh 时,回落到逐个 @deepseek-ai/dsh / @deepseek-ai/dsh-* peer 的 range 判定
48
+ }
49
+ ```
50
+
51
+ `@deepseek-ai/dsh-plugin-manager` 在三个位置调用它,未满足即抛 `incompatible-version`:
52
+
53
+ | 位置 | 场景 |
54
+ |---|---|
55
+ | `lib/types/index.js:368` | 列出 bundle 时逐个核对 |
56
+ | `lib/types/index.js:660` | 安装(`add`)前核对 |
57
+ | `lib/types/index.js:857` | 启用(`enable`)前核对 |
58
+
59
+ 同一包对外的 `requirements` 字段说明也把键名写死为 `engines.dsh`(`lib/typert.host.js:59`):
60
+
61
+ > "Only the requirements the running version does not satisfy, keyed `engines.dsh` or by DSH peer package name."
62
+
63
+ ### 2.2 npm / pnpm 自身的 engines 检查
64
+
65
+ npm 与 pnpm 用不带 `includePrerelease` 的默认 semver 语义检查 `engines`。
66
+
67
+ ### 2.3 两者对同一区间的判定(semver 7.8.5 实测)
68
+
69
+ | 运行时版本 | `>=0.2.1-rc.1 <0.3.0-0`(npm 默认) | 同区间(DSH 闸门,`includePrerelease`) | `>=0.1.0-rc.1 <0.3.0-0`(npm 默认) |
70
+ |---|---|---|---|
71
+ | `0.2.1-alpha.5` | 不通过 | 不通过 | 不通过 |
72
+ | `0.2.1-rc.1` | 通过 | 通过 | **不通过** |
73
+ | `0.2.1-rc.2` | 通过 | 通过 | **不通过** |
74
+ | `0.2.1-rc.3` | 通过 | 通过 | **不通过** |
75
+ | `0.2.1-rc.4` | 通过 | 通过 | **不通过** |
76
+ | `0.2.1-rc.5` | 通过 | 通过 | **不通过** |
77
+ | `0.2.1` | 通过 | 通过 | 通过 |
78
+ | `0.2.2-rc.1` | 不通过 | 通过 | 不通过 |
79
+ | `0.3.0-0` | 不通过 | 不通过 | 不通过 |
80
+ | `0.3.0` | 不通过 | 不通过 | 不通过 |
81
+
82
+ 由此得到两个写法上的硬要求:
83
+
84
+ 1. **起点必须落在 `0.2.1` 元组上。** 默认 semver 只允许「与区间内某个比较符同元组」的预发布版本参与匹配;写成 `>=0.1.0-rc.1`(元组 `0.1.0`)时,`0.2.1-rc.x` 全部被判为不匹配。所以在 npm/pnpm 的默认判据下,`>=0.1.0-rc.1 <0.3.0-0` 会静默排除全部 `0.2.1` 预发布版本。
85
+ 2. **上界取 `<0.3.0-0`。** 写成 `<0.3.0` 会在 DSH 闸门(`includePrerelease`)下把 `0.3.0-rc.x` 也算作通过,与「不声称兼容下一 minor」冲突。
86
+
87
+ DSH 闸门带 `includePrerelease`,因此 `0.2.2-rc.x` 这类后续 `0.2.x` 预发布也会通过。这是有意接受的结果:声明覆盖整个 `0.2.x` 预发布链,而不是逐版本列举。
88
+
89
+ ---
90
+
91
+ ## 三、逐版本核验表
92
+
93
+ | 版本 | 取样方式 | 结论 |
94
+ |---|---|---|
95
+ | `0.2.1-rc.1` | npm 闭包元数据(206 包)+ 9 个契约点的逐文件 SHA / 锚点窗口 | 契约面与 `rc.5` 一致;闭包比 `rc.3` 少 3 个包(见 §4.3) |
96
+ | `0.2.1-rc.2` | 同上 | 同上 |
97
+ | `0.2.1-rc.3` | 同上 + 核验机实机运行(插件加载、面板注入、视图路由) | 基线版本 |
98
+ | `0.2.1-rc.4` | npm 闭包元数据 + 5 个变动包逐文件 SHA + 契约锚点窗口 | 契约面无变化 |
99
+ | `0.2.1-rc.5` | npm 闭包元数据 + 逐文件比对 + **隔离 `DSH_HOME` 实机运行**(`--dump-config` 解析、web 宿主启动、`/dsh-workflow/*` 三种端点实测,见 §3.1) | 契约面无变化,且宿主半在 `rc.5` 上实机应答 |
100
+
101
+ `rc.4` 的结论来自与 `rc.3` 的逐文件比对,不是实机运行结论(见 §8)。`rc.5` 已补做隔离实机运行,记录见下节。
102
+
103
+ ### 3.1 `rc.5` 隔离实机运行记录(2026-10-30)
104
+
105
+ 核验机内核是 `rc.3`,因此不升级本机环境:在 `%TEMP%\wfmap-rc5-run\` 另起一套 `npm i @deepseek-ai/dsh@0.2.1-rc.5`,`DSH_HOME=%TEMP%\wfmap-rc5-run\home`(隔离),新 profile `wfmap5` 的 `dsh.profile.bundles` = `dsh-base` + `dsh-web-app` + `dsh-workflow-map`(后者以 `link:` 指向本仓库的工作副本,未改动源文件)。全程不写本机 `~/.dsh`、不动 `desktop` profile。
106
+
107
+ **A 级 —— `rc.5` 的 loader 解析本插件 manifest 与 patch:通过**
108
+
109
+ ```text
110
+ node <run>\node_modules\@deepseek-ai\dsh\lib\bin.js --profile wfmap5 --dump-config
111
+ # 退出码 0;stdout 1234 行;stderr 0 行
112
+ # 末尾三行即本插件层:
113
+ # # == dsh-workflow-map
114
+ # - id: workflow-map
115
+ # name: dsh-workflow-map
116
+ # stdout 全文无 unknown / invalid / unresolved / warn / error 类告警
117
+ ```
118
+
119
+ **B 级 —— `rc.5` 宿主上路由实机应答:通过**
120
+
121
+ 同一套 `rc.5` 起 web 宿主(`--profile wfmap5 --port 55999 --no-open --host 127.0.0.1`),逐端点实测(响应头均为 `content-type: application/json; charset=utf-8`,与 `src/route.js` 的契约一致):
122
+
123
+ | 请求 | 状态 | 响应体(原文) | 说明 |
124
+ |---|---|---|---|
125
+ | `GET /dsh-workflow/state?sessionId=none` | `404` | `{"ok":false,"error":"unknown session"}` | 前缀路由已挂上;`sessions` 服务在 `rc.5` 上可用(否则会是 503) |
126
+ | `GET /dsh-workflow/state`(无 `sessionId`) | `400` | `{"ok":false,"error":"sessionId is required"}` | 参数校验分支 |
127
+ | `POST /dsh-workflow/steer` | `404` | `{"ok":false,"error":"unknown session"}` | 热注入通道已挂上 |
128
+ | `POST /dsh-workflow/unsteer` | `404` | `{"ok":false,"error":"unknown session"}` | 撤回通道已挂上 |
129
+ | `GET /dsh-workflow/nope` | `404` | `{"ok":false,"error":"not found","path":"/dsh-workflow/state \| /dsh-workflow/steer \| /dsh-workflow/unsteer"}` | 前缀内部路由表,证明命中的是本插件的 handler,而非宿主兜底 404 |
130
+
131
+ 对照:同一宿主上 `GET /` 返回 `401 dsh web authentication required; reopen the URL printed by dsh web.`(`dsh-client-connection` 的页面鉴权闸门,`lib/index.js:576`),属宿主行为,不影响插件路由。
132
+
133
+ **两个会把人带偏的坑(实测)**
134
+
135
+ 1. **系统代理会把 `127.0.0.1` 也拦走。** 本机代理 `127.0.0.1:26561` 的 `ProxyOverride` 不含 `localhost`/`127.0.0.1`,于是 `Invoke-WebRequest http://127.0.0.1:55999/...` 全部返回 **404 且空体**——看着像「插件没挂」。改用裸 socket 或 `-NoProxy` 后立刻看到 `401`(页面)与本插件的 JSON(路由)。**结论:HTTP 404 不能单独作为「路由未注册」的证据。**
136
+ 2. **`--profile <name>` 后面不能再跟 `web`。** `dsh --profile wfmap5 web` 报 `too many arguments. Expected 0 arguments but got 1: web`(`web` 是 profile 名,不是子命令);正确写法是 `dsh --profile wfmap5 --port 55999 --no-open --host 127.0.0.1`。
137
+
138
+ **复跑命令**(升级到 `rc.5` 后任选一个临时目录执行,不动本机环境):
139
+
140
+ ```powershell
141
+ $run="$env:TEMP\wfmap-rc5-run"; $env:DSH_HOME="$run\home"
142
+ node "$run\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile wfmap5 --port 55999 --no-open --host 127.0.0.1
143
+ # 另开窗口(-NoProxy 是关键,否则会被系统代理拦成假 404):
144
+ Invoke-WebRequest 'http://127.0.0.1:55999/dsh-workflow/state?sessionId=none' -SkipHttpErrorCheck -NoProxy
145
+ # 期望:404 + {"ok":false,"error":"unknown session"}
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 四、闭包级别比对
151
+
152
+ ### 4.1 方法
153
+
154
+ 以 `@deepseek-ai/dsh-base` 与 `@deepseek-ai/dsh-web-app` 的依赖并集为闭包,共 **206 个包名**。对每个包从 npm registry 取 `dist.unpackedSize` 与 `dist.fileCount`,逐版本比较;任一版本组合不同即进入人工取证。
155
+
156
+ 用 registry 元数据做初筛、再用逐文件 SHA-256 取证,是因为元数据能覆盖全部 206 个包,而逐文件下载只对筛出的少数包做。
157
+
158
+ ### 4.2 差异分布
159
+
160
+ 206 个包中有 **24 个**在 `rc.1`–`rc.5` 之间存在差异,全部集中在三段边界上:
161
+
162
+ | 变化边界 | 包数 | 包 |
163
+ |---|---|---|
164
+ | `rc.1 → rc.2` | 5 | `dsh-web`、`dsh-web-search-deepseek`、`dsh-web-deepseek-api`、`dsh-client-ui-deepseek-model-label-1017`、`dsh-client-ui-settings-web-search` |
165
+ | `rc.1 → rc.2` 且 `rc.2 → rc.3` | 1 | `dsh-agent-preset` |
166
+ | `rc.2 → rc.3` | 13 | `dsh-llm-deepseek-api-key`、`dsh-api-session-controller`、`dsh-api-remotes`、`dsh-client-ui-entry-customization`、`dsh-client-ui-plugin-manager`、`dsh-client-ui-settings-general`、`dsh-client-ui-agent-preset`、`dsh-client-ui-sidebar-right`、`dsh-client-ui-workspace`、`dsh-client-ui-sidebar`、`dsh-tool-cordis`、`dsh-web-frontend`、`dsh-client-product-analytics` |
167
+ | `rc.2 → rc.3` 且 `rc.3 → rc.4` | 2 | `dsh-llm-deepseek-account`、`dsh-client-ui-conversation` |
168
+ | `rc.3 → rc.4` | 1 | `dsh-mcp-resources` |
169
+ | `rc.4 → rc.5` | 1 | `dsh-client-ui-chat` |
170
+ | 三个边界都有 | 1 | `dsh-cordis-client-runner` |
171
+
172
+ 其中 `dsh-client-ui-sidebar` 与 `dsh-client-ui-conversation` 属于本插件的契约面,其锚点级比对见 §5。
173
+
174
+ ### 4.3 版本缺席
175
+
176
+ | 包 | 缺席版本 | 说明 |
177
+ |---|---|---|
178
+ | `dsh-web-deepseek-api` | `rc.1` | 首次发布于 `rc.2` |
179
+ | `dsh-client-ui-deepseek-model-label-1017` | `rc.1` | 首次发布于 `rc.2` |
180
+ | `dsh-client-ui-entry-customization` | `rc.1`、`rc.2` | 首次发布于 `rc.3` |
181
+ | `cordis-plugin-timer`、`schemastery` | 全部 | 第三方依赖,不随 DSH 的 `rc` 编号发布 |
182
+
183
+ `rc.1` 的 profile 因此比 `rc.3` 少 3 个客户端包。这 3 个包都与本插件的契约面无关(模型标签角标、入口定制、API 通道包),缺失不影响本插件加载。
184
+
185
+ ### 4.4 `rc.3 → rc.5` 之间真正改动的 5 个包
186
+
187
+ | 包 | `rc.3` | `rc.4` | `rc.5` | 改动文件 | 是否触及本插件契约 |
188
+ |---|---|---|---|---|---|
189
+ | `dsh-llm-deepseek-account` | 35661 B / 10 | 35735 B / 10 | 35735 B / 10 | `lib/index.js`、三个 README、`package.json` | 否:`lib/index.js` 只改 1 行,日志级别 `console.info` → `console.error` |
190
+ | `dsh-mcp-resources` | 30771 B / 11 | 30844 B / 11 | 30844 B / 11 | 三个 README、`package.json` | 否:无代码改动 |
191
+ | `dsh-client-ui-chat` | 857882 B / 106 | 同 `rc.3` | 859933 B / 107 | `lib/client.js`、3 个 `*.d.ts`、新增 `reasoning-text.d.ts`、README、`package.json` | 否:改动集中在推理正文渲染(新增 `conversation.chat.reasoning.content` 槽、`ReasoningBodyOwnerProps.text` 拆成 `rawText` + 显示文本) |
192
+ | `dsh-client-ui-conversation` | 1130507 B / 89 | 1131117 B / 89 | 同 `rc.4` | `lib/client.js`、`locales.d.ts`、README、`package.json` | 否:92 个 bundle 分区中仅 `locales.js` 与 `skeleton/ConversationSession.js` 变化 |
193
+ | `dsh-cordis-client-runner` | 483933 B / 20 | 同 `rc.3` | 484021 B / 20 | `lib/client.js`、`package.json` | 否:12 行变化全部是槽文档文本与 `sources` 源行号(`slots.ts:441` → `:447`) |
194
+
195
+ `dsh-client-ui-conversation` 的改动按 bundle 内 `//#region` 分区逐一比对:`lib/types/client/locales.js`(词条增删)与 `lib/types/client/skeleton/ConversationSession.js`(输入框手势与文件选择绑定重构)。本插件依赖的槽注册、DOM 属性都不在这两个分区内。
196
+
197
+ ---
198
+
199
+ ## 五、契约面逐项核对
200
+
201
+ 本插件只依赖九个契约点。下表每个点都给出提供方、用法与 `rc.1` ↔ `rc.5` 的比对证据。
202
+
203
+ | 契约点 | 提供方 | 本插件用法 | `rc.1` ↔ `rc.5` 证据 | 结论 |
204
+ |---|---|---|---|---|
205
+ | `sidebar.panellist` 槽 | `dsh-client-ui-sidebar` | 注册左栏面板项(`kind: 'list'`) | 槽声明在 `rc.1` 与 `rc.3` 逐字节相同:`kind: 'list'; scope: 'root'; owner: SidebarPanelIconOwnerProps` | 未变 |
206
+ | `main` 槽 | `dsh-client-ui-layout` | 注册主内容面板(`kind: 'keyed'`) | 整包除 `package.json` 外逐文件 SHA-256 相同(`lib/client.js` = `686fb83c91c8ff8d…`) | 未变 |
207
+ | `conversation.view` 槽 | `dsh-client-ui-conversation` | 注册会话标签栏视图 | 4 处锚点窗口(`DefaultConversationViews` 的 `renderSlot`、`apply.js` 的 `entries` / `subscribe` / `children`)在 `rc.1`/`rc.3`/`rc.4`/`rc.5` 逐字节相同 | 未变 |
208
+ | 输入框座位 DOM | `dsh-client-ui-conversation` | 选中本插件视图时隐藏输入框 | `data-composer-seat`(1 处)、`data-conversation-content`(1 处)、`data-conversation-scroll`(3 处)的上下文窗口逐字节相同 | 未变 |
209
+ | `sessionProjections` 服务 | `dsh-session-projection` | 注册会话投影 | `lib/index.js` SHA-256 `7dc2b212819baf4f…` 相同 | 未变 |
210
+ | `sessions` 服务 | `dsh-session` | 按会话号取会话 | `lib/index.js` SHA-256 `2f5dd07cb5c744a2…` 相同 | 未变 |
211
+ | `webServer` 服务 | `dsh-host-webserver` | 注册面板数据路由 | `lib/index.js` SHA-256 `08907285802f0edd…` 相同 | 未变 |
212
+ | `agents` 服务 | `dsh-agent-loop` | 读运行中的 agent 状态 | `lib/index.js` SHA-256 `cf57e3ecd487ac5c…` 相同 | 未变 |
213
+ | 会话事件 / JSONL 格式 | `dsh-session-persistence-jsonl` | 折叠会话事件为步骤序列 | `lib/worker.cjs` SHA-256 `4b0f235e772792d7…` 相同 | 未变 |
214
+
215
+ 输入框座位的实测嵌套(`dsh-client-ui-conversation` `rc.5` 的 `lib/client.js`):
216
+
217
+ ```text
218
+ div[data-conversation-content]
219
+ └─ div[data-conversation-scroll]
220
+ ├─ Views ← 选中的 conversation.view 占据位
221
+ └─ div[data-composer-seat]
222
+ ```
223
+
224
+ 本插件依赖的 DOM 只有这一条父子链与两个 `data-*` 属性,不依赖 class 名或内部组件。
225
+
226
+ §4.2 表里其余 22 个变动包与本插件的九个契约点没有交集:不注册 `conversation.chat.*` 系列槽、不读设置页组件、不调用搜索工具、不依赖 API 网关包。
227
+
228
+ ---
229
+
230
+ ## 六、安装闸门生效性
231
+
232
+ 结论:`engines.dsh` 不是装饰性字段,安装与启用期都会被读取。
233
+
234
+ | 环节 | 证据 |
235
+ |---|---|
236
+ | 判定实现 | `dsh-app-boot` 的 `evaluatePluginCompatibility()` → `unmetRequirements()`(§2.1 引文) |
237
+ | 判定实现版本一致 | `dsh-app-boot` `rc.1` 与 `rc.5` 除 `package.json` 外逐文件 SHA-256 相同(`lib/index.js` = `5135179ac810e4f6…`) |
238
+ | 安装期调用 | `dsh-plugin-manager` 三处调用(§2.1 表),未满足抛 `incompatible-version`,并在 `requirements` 里给出 `engines.dsh` 键 |
239
+ | 调用方版本一致 | `dsh-plugin-manager` `rc.3` 与 `rc.5` 除 `package.json` 外逐文件 SHA-256 相同 |
240
+
241
+ 由此可推出两条可验证的后果:
242
+
243
+ 1. 在 `0.2.1-rc.1`–`0.2.1-rc.5` 任一内核上安装或启用,本插件的 `engines.dsh` 判定通过,不会有 `incompatible-version`。
244
+ 2. 在 `0.3.0` 及以后的内核上,`engines.dsh` 判定不通过(§2.3),安装或启用会被上述任一调用点拦下并给出明确的 `engines.dsh: ">=0.2.1-rc.1 <0.3.0-0"` 说明。这属于预期行为:不兼容时应显式失败,而不是勉强加载。
245
+
246
+ ---
247
+
248
+ ## 七、可复跑命令
249
+ 以下命令只需 Node.js 20 以上。前两条只需 npm registry 访问;第三条需要环境里能解析到 `semver`(`npm i semver`,或使用 DSH 安装目录自带的副本)。
250
+
251
+ **1) 闭包元数据对比(206 包 × 5 个版本)**
252
+
253
+ ```bash
254
+ node -e '
255
+ const { readFileSync } = require("node:fs");
256
+ const names = readFileSync("closure-names.txt", "utf8").split(/\r?\n/).filter(Boolean);
257
+ const V = ["0.2.1-rc.1","0.2.1-rc.2","0.2.1-rc.3","0.2.1-rc.4","0.2.1-rc.5"];
258
+ let i = 0; const rows = [];
259
+ (async () => {
260
+ await Promise.all(Array.from({ length: 8 }, async () => {
261
+ while (i < names.length) {
262
+ const name = names[i++];
263
+ const p = await (await fetch("https://registry.npmjs.org/" + name.replace("/", "%2f"))).json();
264
+ const pick = (v) => { const d = p.versions?.[v]?.dist; return d ? [d.unpackedSize, d.fileCount] : null; };
265
+ const row = V.map((v) => JSON.stringify(pick(v)));
266
+ if (new Set(row).size > 1) rows.push({ name, row });
267
+ }
268
+ }));
269
+ console.log(rows.length, JSON.stringify(rows, null, 1));
270
+ })();
271
+ '
272
+ ```
273
+
274
+ `closure-names.txt` 是 `dsh-base` 与 `dsh-web-app` 依赖并集的包名清单(每行一个)。换用别的清单会得到别的闭包,比较结果随之变化。
275
+
276
+ **2) 单包逐文件 SHA-256 比对**
277
+
278
+ ```bash
279
+ # 取两个版本的 tarball,解包后逐文件比对
280
+ npm pack @deepseek-ai/dsh-session-projection@0.2.1-rc.1
281
+ npm pack @deepseek-ai/dsh-session-projection@0.2.1-rc.5
282
+ tar -xzf deepseek-ai-dsh-session-projection-0.2.1-rc.1.tgz -C rc1
283
+ tar -xzf deepseek-ai-dsh-session-projection-0.2.1-rc.5.tgz -C rc5
284
+ diff -r rc1/package rc5/package # 期望:仅 package.json 不同
285
+ ```
286
+
287
+ **3) 区间判定矩阵**
288
+
289
+ ```bash
290
+ node -e '
291
+ const semver = require("semver");
292
+ const ranges = [">=0.2.1-rc.1 <0.3.0-0", ">=0.1.0-rc.1 <0.3.0-0"];
293
+ const versions = ["0.2.1-alpha.5","0.2.1-rc.1","0.2.1-rc.5","0.2.1","0.2.2-rc.1","0.3.0-0","0.3.0"];
294
+ for (const r of ranges) console.log(r, versions.map((v) => [v, semver.satisfies(v, r), semver.satisfies(v, r, { includePrerelease: true })]));
295
+ '
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 八、限制声明
301
+
302
+ - **动态核验范围**:截至 2026-10-30,`0.2.1-rc.3` 是本机实机运行版本;`0.2.1-rc.5` 已在隔离 `DSH_HOME` 的临时区实机启动并实测路由应答(§3.1),但不覆盖「真实会话内的投影内容」——`rc.5` 上没有跑过带会话的完整回合,`/state` 只验到「按契约返回 `unknown session`」。`rc.4` 仍只做了静态比对(闭包元数据、逐文件 SHA-256、契约锚点窗口),未实机启动。
303
+ - **比对对象**:只比对 npm 上已发布的产物,不覆盖未发布的仓库提交。
304
+ - **闭包口径**:206 个包名按 `dsh-base` 与 `dsh-web-app` 的依赖并集确定;更换清单会改变闭包边界。
305
+ - **第三方依赖**:闭包内含 `cordis`、`schemastery`、`zod` 等第三方包,它们不随 DSH 的 `rc` 编号发布,本文件不对其版本承诺做任何声明。
306
+ - **上界外版本**:`0.3.0` 及以后未做任何核验,声明上也不覆盖。
307
+ - **判定语义**:§2.3 的矩阵来自 semver 7.8.5;换用其他实现时 `includePrerelease` 的细节可能不同。
308
+
309
+ ---
310
+
311
+ ## 九、复核记录
312
+
313
+ | 项 | 取值 |
314
+ |---|---|
315
+ | 复核日期 | 2026-10-30 |
316
+ | 数据来源 | `registry.npmjs.org` 发布产物 |
317
+ | semver 实现 | 7.8.5(`satisfies`,分别带与不带 `includePrerelease`) |
318
+ | 闭包规模 | 206 个包名,24 个存在版本间差异 |
319
+ | 逐文件比对包 | `dsh-app-boot`、`dsh-session`、`dsh-session-projection`、`dsh-host-webserver`、`dsh-agent-loop`、`dsh-session-persistence-jsonl`、`dsh-plugin-manager`、`dsh-client-ui-layout`、`dsh-client-ui-sidebar`、`dsh-client-ui-conversation`、`dsh-client-ui-chat`、`dsh-cordis-client-runner`、`dsh-llm-deepseek-account`、`dsh-mcp-resources` |
320
+ | 实机运行内核 | `0.2.1-rc.3`(本机内核);`0.2.1-rc.5`(隔离 `DSH_HOME` 临时区,仅路由与 loader 层) |
Binary file
Binary file
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect x="6" y="8" width="24" height="48" rx="4" fill="none" stroke="#2dd4e8" stroke-width="3"/>
3
+ <rect x="34" y="8" width="24" height="20" rx="4" fill="none" stroke="#2dd4e8" stroke-width="3"/>
4
+ <rect x="34" y="36" width="24" height="20" rx="4" fill="none" stroke="#2dd4e8" stroke-width="3" opacity=".55"/>
5
+ <line x1="18" y1="20" x2="18" y2="44" stroke="#2dd4e8" stroke-width="3" stroke-linecap="round"/>
6
+ </svg>
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * dsh-workflow-map — package entry (the manifest's `exports["."]`).
3
+ *
4
+ * The host half itself lives in ./lib/index.js from on: this file only re-exports it,
5
+ * so the bundle manifest written at — and any profile that already points at
6
+ * ./index.js — keeps working unchanged while the implementation grows under lib/ and src/.
7
+ *
8
+ * What a profile actually loads is unchanged: `name`, `inject` and `apply` as named exports.
9
+ */
10
+
11
+ export * from "./lib/index.js";
12
+ export { default } from "./lib/index.js";