dsh-molbio-tools 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +161 -0
- package/README.md +7 -7
- package/benchmark/README.md +2 -1
- package/benchmark/_freeze-trace.mjs +92 -18
- package/benchmark/profile.mjs +2 -1
- package/benchmark/tasks/06-analysis.json +2 -1
- package/build/preset-patch.mjs +101 -41
- package/build/release-preflight.mjs +1 -1
- package/cordis.patch.yml +187 -25
- package/docs/history.md +56 -0
- package/docs/maintainer.md +6 -5
- package/docs/roadmap.md +12 -2
- package/docs/store-evidence.md +152 -0
- package/docs/store-preset-exemption-issue.md +46 -0
- package/docs/store-preset-exemption.md +231 -0
- package/docs/workflow.md +9 -7
- package/package.json +11 -7
- package/preset/install.mjs +4 -4
- package/preset/molbio-lab/agent.cordis.yml +37 -163
- package/test/contract.mjs +73 -14
- package/test/drift-probe.mjs +15 -6
- package/test/fixtures/benchmark-traces.json +20 -9
- package/test/preset-health.mjs +2 -2
- package/test/preset-rows.mjs +4 -3
- package/packages/molbio-panel/README.md +0 -63
- package/packages/molbio-panel/cordis.patch.yml +0 -7
- package/packages/molbio-panel/index.mjs +0 -33
- package/packages/molbio-panel/lib/client.js +0 -9113
- package/packages/molbio-panel/package.json +0 -57
- package/preset/molbio-lab/preset.patch.yml +0 -300
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,167 @@
|
|
|
10
10
|
改为按**包名**引用后模块缓存问题随之消失——没有拷贝,也就没有需要保持同步的版本目录。
|
|
11
11
|
下文历史条目里出现的 `vN` 目录保持原样,作为当时的记录。
|
|
12
12
|
|
|
13
|
+
## [0.21.1] — 2026-10-25(DSH STORE 提交前置:一个 patch、逐版本兼容声明、一个 tarball 一个清单)
|
|
14
|
+
|
|
15
|
+
**工具数不变(60),插件运行时行为零改动。** 这一版不加任何计算能力,只把"这个包以什么形状提交"
|
|
16
|
+
改成 DSH STORE 的提交预检与 DSH 自身的清单契约**都能接受、且不再互相矛盾**的那一种。三处改动都
|
|
17
|
+
来自同一次上架提交:预检里能靠改包解决的两条已经解决,剩下两条的原理与请求另写在
|
|
18
|
+
[docs/store-preset-exemption.md](docs/store-preset-exemption.md)。
|
|
19
|
+
|
|
20
|
+
### 1. 两层 patch 合并成单个 `cordis.patch.yml`
|
|
21
|
+
|
|
22
|
+
0.21.0 的 `dsh.bundle.patch` 是**有序数组**:`["./cordis.patch.yml", "./preset/molbio-lab/preset.patch.yml"]`。
|
|
23
|
+
DSH 运行时接受这种写法(`@deepseek-ai/dsh-app-boot` 的 `bundlePatchPaths`:one file, or an
|
|
24
|
+
ordered list of files),官方 `@deepseek-ai/dsh-web-app` 自己就列了 5 个文件;但 DSH STORE 的提交
|
|
25
|
+
预检(`scripts/check-plugin-submission.mjs` 的 `safePatchPath`)只接受字符串,数组直接判
|
|
26
|
+
`SUBMISSION_BUNDLE_PATCH_INVALID`。两边都自称官方契约,解法不必等预检放宽:
|
|
27
|
+
|
|
28
|
+
- `build/preset-patch.mjs` 现在直接产出**根目录的 `cordis.patch.yml`**,内容是**两个 insert**:
|
|
29
|
+
`molbio-client → ./host.mjs`(惰性宿主锚点)在前,`preset-molbio-lab`(由
|
|
30
|
+
`preset/molbio-lab/agent.cordis.yml` 生成的 preset 行)在后;顺序即应用顺序。
|
|
31
|
+
- 不再生成 `preset/molbio-lab/preset.patch.yml`(文件已删);`package.json` 的
|
|
32
|
+
`dsh.bundle.patch` 是**单字符串**。
|
|
33
|
+
- 四个读者改为跟单文件走:`test/preset-health.mjs`、`test/drift-probe.mjs`、
|
|
34
|
+
`preset/install.mjs`、`test/preset-rows.mjs`(后者在同一个文件的两个 insert 里找 preset 段,
|
|
35
|
+
不假设"preset.patch.yml 才是 preset")。
|
|
36
|
+
|
|
37
|
+
> 这一条同时守住一条更早的教训:**锚点行不能因为"文件变少了"而消失**。0.15.2 的两个侧栏 tab
|
|
38
|
+
> 就是锚点丢失后静默消失的;`test/client-mount.mjs` 仍然逐包断言"被选中的包必须有一个宿主行
|
|
39
|
+
> 能解析到它",`test/contract.mjs` 仍然断言"产物里的每一处 `register` 都在 `inject` 里"。
|
|
40
|
+
|
|
41
|
+
### 2. `engines.dsh` 与逐版本 `dshReleases`
|
|
42
|
+
|
|
43
|
+
两个 manifest 各声明一份:
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
"engines": { "node": ">=26", "dsh": ">=0.2.1-rc.2 <0.3.0-0" },
|
|
47
|
+
"dsh": { "compatibility": { "dshReleases": {
|
|
48
|
+
"0.2.1-rc.1": "unknown", "0.2.1-rc.2": "compatible", "0.2.1-rc.3": "unknown" } } }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
上界写 `<0.3.0-0` 而不是 `<0.3.0`:DSH 用 `semver.satisfies(..., { includePrerelease: true })`
|
|
52
|
+
判定(`@deepseek-ai/dsh-app-boot` 的 `unmetRequirements`),带 prerelease 选项时 `<0.3.0`
|
|
53
|
+
会放行 `0.3.0-alpha.1`,`-0` 不会。`rc.1` 早于本包基线、`rc.3` 未取得也未验证,两者都只能
|
|
54
|
+
标 `unknown`——**范围不等于验收**:`engines` 是安装时的粗筛,商城的 `dshReleases` 记录才是
|
|
55
|
+
逐版本的验收结论。
|
|
56
|
+
|
|
57
|
+
面板包随之 `0.1.3 → 0.1.4`:它的版本线独立于根包,但清单变了,不发这个版本号就没有能承载新
|
|
58
|
+
声明的发布物。
|
|
59
|
+
|
|
60
|
+
### 3. 根 tarball 不再内嵌面板包
|
|
61
|
+
|
|
62
|
+
`files` 白名单去掉 `packages`。此前根 tarball 里同时躺着 `package.json` 与
|
|
63
|
+
`packages/molbio-panel/package.json` 两个 DSH 插件清单,还重复携带 427 KB 相同的 `lib/client.js`;
|
|
64
|
+
商城把"一个包里出现第二个插件清单"视为提交歧义是对的。现在根 tarball **只有一个插件清单**:
|
|
65
|
+
**118 → 115 个文件、约 1.1 MB → 约 999 kB**(多出的 1 个文件是本次提交的
|
|
66
|
+
`docs/store-preset-exemption-issue.md`)。面板没有因此消失——根包的 `lib/client.js` 本来就带着它
|
|
67
|
+
(第 4 节的启动回读可以作证);面板包继续按自己的地址单独发布。
|
|
68
|
+
|
|
69
|
+
`test/contract.mjs` 的打包断言随之改向:不再要求 `files` 必须包含 `packages/`,而是断言
|
|
70
|
+
**一个 tarball 里恰好只有一个 DSH 清单**(把 `packages` 加回白名单会立刻 FAIL)。
|
|
71
|
+
|
|
72
|
+
### 4. 一次性 profile 证据(tarball 就是发布物)
|
|
73
|
+
|
|
74
|
+
证据记录在 [docs/store-evidence.md](docs/store-evidence.md),用临时 `DSH_HOME` 重跑,不碰真实
|
|
75
|
+
`~/.dsh`:`npm pack` → `--from-default-profile web` 建 profile → `plugin add` tarball →
|
|
76
|
+
`--dump-config`(`molbio-client` 解析到已安装包内,preset 行带 `tool-molbio`)→
|
|
77
|
+
`--no-open --port 0` 启动 → 带 token 回读页面(**HTTP 200**,`__DSH_BOOT__` 图里有
|
|
78
|
+
`dsh-molbio-tools/client.js`)→ `plugin remove` → 再 `--dump-config`(`molbio` 引用 **0** 处)。
|
|
79
|
+
装 tarball 而不是目录,是因为 tarball 同时证明 `files` 白名单带全了运行文件。
|
|
80
|
+
|
|
81
|
+
### 5. 未做 / 边界
|
|
82
|
+
|
|
83
|
+
- **`0.2.1-rc.3` 没有验证**,因此标 `unknown`。拿到该版本后按
|
|
84
|
+
[docs/workflow.md](docs/workflow.md) 第 4 节的升级流程重跑 `preset-health` / `drift-probe` /
|
|
85
|
+
一次性 profile,再把它改成 `compatible`。
|
|
86
|
+
- **预检的另外两条(保护命名空间、身份一致)不是改包能解决的**:本包已自行消掉
|
|
87
|
+
`SUBMISSION_BUNDLE_PATCH_INVALID`;剩下两条的证据、代码位置与三个可执行请求写在
|
|
88
|
+
[docs/store-preset-exemption.md](docs/store-preset-exemption.md),可直接贴到 issue 的短版在
|
|
89
|
+
[docs/store-preset-exemption-issue.md](docs/store-preset-exemption-issue.md)。
|
|
90
|
+
- **工具行为与 benchmark 一律不动**:60 个工具的输出与 0.21.0 相同,按规则 6 的"没变就不必跑
|
|
91
|
+
模型";`bench --offline` 仍在 `npm test` 里,期望值照样逐条校验。
|
|
92
|
+
|
|
93
|
+
### 6. 版本与验证
|
|
94
|
+
|
|
95
|
+
- 根包 `0.21.0 → 0.21.1`;面板 `0.1.4`(见 §2)。
|
|
96
|
+
- `npm test` 全绿:smoke、画面四套、客户端四套、contract 16 项、drift-probe 14 条突变、
|
|
97
|
+
preset-health 30 行、release 13 项、benchmark 四道守卫 + `bench --offline` 61 题。
|
|
98
|
+
- `node build/preset-patch.mjs --check` 与 `node build/client-bundle.mjs --check` 均报告与源一致。
|
|
99
|
+
- `node build/release-preflight.mjs --tag v0.21.1`:本地不变量全过;npm 上 `dsh-molbio-tools`
|
|
100
|
+
已有 0.21.0、本版按 `publish` 处理;面板 0.1.4 尚未发布。
|
|
101
|
+
- 文档同步:maintainer 的版本号、roadmap 的当前状态、history 的版本一览与本段、store-evidence
|
|
102
|
+
的 tarball 名与文件数。
|
|
103
|
+
|
|
104
|
+
## [0.21.0] — 2026-10-23(DSH 0.2.1-rc.2 适配;两条 benchmark 欠账清零)
|
|
105
|
+
|
|
106
|
+
**工具数不变(60),插件行为零改动。** 这是一次"只跟上游走"的版本:0.20.1 之后本机 harness
|
|
107
|
+
升到 0.2.1-rc.2,`npm test` 立刻红了三处——组合漂移、契约断言、以及一条依赖旧 fixture 的
|
|
108
|
+
漂移探针。三处都不是插件退化,但每一处都属于**守卫在说谎**,所以按发布阻断处理。
|
|
109
|
+
|
|
110
|
+
### 1. preset 行清单按 0.2.1-rc.2 逐行重抄(12 处结构差异)
|
|
111
|
+
|
|
112
|
+
- 上游 `standard` **新增** `time-context`、`tool-schedule`、`tool-plugin-market`;
|
|
113
|
+
- **删除** `tool-subagent-codex` / `tool-subagent-claude-code`(两个 `disabled: true` 的可选
|
|
114
|
+
provider)与 `tool-ralph`;
|
|
115
|
+
- **去掉** persona 的 `suffix: Your working directory is {{cwd}}.`,以及 `tool-subagent` /
|
|
116
|
+
`tool-subagent-fork` 的 `backgroundMode: continuable`;
|
|
117
|
+
- plan-mode 的段落文案改为 "until the user approves your plan through exit_plan_mode…"。
|
|
118
|
+
|
|
119
|
+
`agent.cordis.yml` 现在是上游 `config.plugins:` 块的**整体去缩进** + 末尾一行 `tool-molbio`;
|
|
120
|
+
0.2.1 的上游文件不带行内注释,因此行内注释随之消失,来源与升级规则集中写在文件头部。
|
|
121
|
+
`preset.patch.yml` 由 `build/preset-patch.mjs` 重新生成。
|
|
122
|
+
|
|
123
|
+
### 2. `test/contract.mjs`:两条断言不再锚在 minified 字面量上
|
|
124
|
+
|
|
125
|
+
- **client 入口**:改为按包自己的 `exports["./client"]`(`browser` 条件)解析。
|
|
126
|
+
0.2.1-rc.2 把 `@deepseek-ai/dsh-client-connection` 移到 `lib/client.browser.js`,旧断言
|
|
127
|
+
硬编码 `lib/client.js`,会把一个加载正常的包判死;同时 Web shell 已是打包产物、不再出现
|
|
128
|
+
`require("包名")`,"seeded into the shell" 兜底只剩代码路径。新增两个对照:没有
|
|
129
|
+
`./client` 导出的包必须解析为 `undefined`,以及移动过的入口必须解析到真实文件。
|
|
130
|
+
- **`presentationMeta`**:0.2.1 删除了 `exec.parent === void 0` 闸门(投影移进
|
|
131
|
+
`createSuccessResult`,对每次成功执行都做)。断言改挂到卡片真正消费的东西:投影被
|
|
132
|
+
`snapshotProjection` 记录、并以 `meta` 附到成功结果上。
|
|
133
|
+
|
|
134
|
+
### 3. `drift-probe`:突变样本换成上游仍存在的行
|
|
135
|
+
|
|
136
|
+
上游删掉 `tool-ralph` 后,那条"上游 `disabled: true` 被丢掉"的突变在**空转**(`withRow`
|
|
137
|
+
找不到目标,什么也没改,守卫却"通过")。改用上游仍以 `disabled: true` 发布的
|
|
138
|
+
`tool-plugin-manager`;14 条突变全部仍能被抓住。
|
|
139
|
+
|
|
140
|
+
### 4. benchmark:profile 重建、两条欠账清零
|
|
141
|
+
|
|
142
|
+
- preset 新增的两行宿主可承载行(`time-context`、`tool-schedule`)进入 headless profile,
|
|
143
|
+
`benchmark/profile.mjs` 重建 `molbio-bench`:**12 行宿主行,仍注册 60 个工具**。
|
|
144
|
+
- `methylation-block` 与 `qpcr-primers` 的冻结响应停在 0.16.0 之前,`benchmark-score` 每次
|
|
145
|
+
都点名。本版用一次完整真实模型跑重录,并用 `_freeze-trace.mjs --replace` **只替换这两条**,
|
|
146
|
+
其余 47 条保持原样;`run.amendments` 记录每条替换来自哪一次跑分。fixture 现为
|
|
147
|
+
**49 条记录、0 条 suspended**。
|
|
148
|
+
- 完整跑分(2026-10-23,DSH 0.2.1-rc.2,61 题):**58/61** 通过,tool selection **60/61**、
|
|
149
|
+
arguments **60/61**、final answer **59/61**;11.2M tokens / 2161 s。
|
|
150
|
+
- `protein-props` 的失败是**断言不认千分位逗号**(答案写 `1,490`,断言写 `/1490/`)——
|
|
151
|
+
与 0.20.0 修过的 `/3935/` 同一类,当时漏了这一条。已改为 `1[,.]?490`,`--replay` 后
|
|
152
|
+
**59/61**。
|
|
153
|
+
- 剩余两条是**真实的模型遗漏**,不是产品缺陷:`dilution-math` 答案正确(50 µL + 950 µL)
|
|
154
|
+
却从未调用 `molbio_lab_math`;`batch-analysis` 写出了 CSV,但最终回答只说"报告已生成",
|
|
155
|
+
没有报出指令要求的长度/特征数。
|
|
156
|
+
- `_freeze-trace.mjs` 新增 `--replace id[,id…]`:只重录被修复作废的条目,其余记录与顺序原样
|
|
157
|
+
保留,并在 `run.amendments` 里写明每条替换的来源跑分——**替换的是真实录制,不是手改**。
|
|
158
|
+
|
|
159
|
+
### 5. 未做 / 边界
|
|
160
|
+
|
|
161
|
+
- **`png_path` 仍然不做**:`contract` 的"工作区仍不能存二进制文件"断言在 0.2.1-rc.2 上**仍然
|
|
162
|
+
通过**,说明上游 fs 缝依旧是 text-only;触发条件未到。
|
|
163
|
+
- **面板版本保持 0.1.3**:`lib/client.js` 逐字节未变,`release-preflight` 复核为
|
|
164
|
+
"already published with identical bytes",因此不 bump。
|
|
165
|
+
|
|
166
|
+
### 6. 版本与验证
|
|
167
|
+
|
|
168
|
+
- 根包 `0.20.1 → 0.21.0`;DSH 基线钉 **0.2.1-rc.2**(`.github/workflows/{test,release}.yml`、
|
|
169
|
+
preset 头注、`docs/workflow.md`、`docs/roadmap.md`、`docs/maintainer.md`)。
|
|
170
|
+
- `npm test` 全绿:contract 16/16、drift-probe 14 条突变、preset-health 30 行全过、release
|
|
171
|
+
13 项、benchmark profile/score/coverage/changed、`bench --offline` 61 题。
|
|
172
|
+
- 文档:roadmap 状态、history 的 v0.21.0 段(三条教训)、workflow 的升级提示同步。
|
|
173
|
+
|
|
13
174
|
## [0.20.1] — 2026-10-18(插件专属图标)
|
|
14
175
|
|
|
15
176
|
**工具数不变(60),运行时零改动;包内只多一个 746 B 的 SVG。**
|
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ dsh plugin --profile web update dsh-molbio-tools
|
|
|
79
79
|
> **0.7.1 起不再需要"注册 preset 根目录"这一步。**
|
|
80
80
|
> DSH 0.1.7-alpha.1 移除了 `@deepseek-ai/dsh-agent-presets` 及其目录发现机制:preset 现在是
|
|
81
81
|
> bundle patch 里的一个 `@deepseek-ai/dsh-agent-preset` **行**(本包的是
|
|
82
|
-
> `
|
|
82
|
+
> `cordis.patch.yml` 里的第二个 insert,由 `dsh.bundle.patch` 声明)。
|
|
83
83
|
> 因此 preset 随 bundle 一起安装,**装完即出现**。
|
|
84
84
|
>
|
|
85
85
|
> 老版本遗留的两样东西可以清理(`node preset\install.mjs --profile web` 会替你查出来):
|
|
@@ -95,12 +95,12 @@ dsh plugin --profile web update dsh-molbio-tools
|
|
|
95
95
|
|
|
96
96
|
### 工具只在专属模式里出现(设计如此)
|
|
97
97
|
|
|
98
|
-
本包的 bundle patch
|
|
98
|
+
本包的 bundle patch 是**一个文件、两层 insert**,分工是固定的:
|
|
99
99
|
|
|
100
|
-
|
|
|
100
|
+
| 层(同一个 `cordis.patch.yml` 里的两段) | 作用 |
|
|
101
101
|
| --- | --- |
|
|
102
|
-
| `
|
|
103
|
-
| `preset
|
|
102
|
+
| `molbio-client` → `./host.mjs` | 只插一行**惰性宿主锚点**,不注册任何工具 |
|
|
103
|
+
| `preset-molbio-lab` → `@deepseek-ai/dsh-agent-preset` | 声明 **Molecular Biology Lab** 模式,60 个工具在这一层里 |
|
|
104
104
|
|
|
105
105
|
所以 60 个工具**只在选中该模式的会话里加载**,其它模式(standard / ptc / minimal / cordis)
|
|
106
106
|
不带它们,不占提示预算。bundle 安装 ≠ 工具全局可见:bundle 只是把"模式"和"面板"一起装进来。
|
|
@@ -741,11 +741,11 @@ dsh-molbio-tools/
|
|
|
741
741
|
├── lib/client.js # 客户端产物(exports["./client"],由 npm run build:client 生成)
|
|
742
742
|
├── packages/molbio-panel/ # 面板专用包(只面板、不带工具)
|
|
743
743
|
├── preset/molbio-lab/ # 专属模式 preset:agent.cordis.yml 是行清单(手改这里)
|
|
744
|
-
│ # preset.
|
|
744
|
+
│ # preset.yml 是选择器文案;两者由生成器写进下面的 cordis.patch.yml
|
|
745
745
|
├── benchmark/ # 可用性评估:tasks/*.json + 判分器 + headless profile 推导(见其 README)
|
|
746
746
|
├── test/ # 冒烟 + 光栅化器 + 客户端/组合检查(含 preset 漂移与 benchmark profile 守卫)
|
|
747
747
|
├── docs/ # 维护者与实现文档(rules / workflow / roadmap / history 是本目录的骨架)
|
|
748
|
-
└── cordis.patch.yml # bundle
|
|
748
|
+
└── cordis.patch.yml # 唯一 bundle patch(生成物):锚点 insert + preset insert
|
|
749
749
|
```
|
|
750
750
|
|
|
751
751
|
---
|
package/benchmark/README.md
CHANGED
|
@@ -159,6 +159,7 @@ benchmark/
|
|
|
159
159
|
├── _probe.mjs record ground truth (original suite)
|
|
160
160
|
├── _probe-all.mjs record ground truth (the rest of the catalog)
|
|
161
161
|
└── _freeze-trace.mjs freeze a run's responses into the scorer's regression fixture
|
|
162
|
+
(`--replace id[,id]` re-records only the named entries and keeps the rest)
|
|
162
163
|
```
|
|
163
164
|
|
|
164
165
|
A task's expected text lives in `tasks/`; the input that produces it lives in
|
|
@@ -221,7 +222,7 @@ session "…" runs under agent preset "molbio-lab", which the one-shot runner do
|
|
|
221
222
|
Presets are an agent-plane feature the Web surface composes; the one-shot runner
|
|
222
223
|
drives `agents.create()` directly and never joins one. So the tools must be
|
|
223
224
|
mounted as **host rows**, which is the one shape this package otherwise refuses
|
|
224
|
-
to ship (`cordis.patch.yml`
|
|
225
|
+
to ship (`cordis.patch.yml` only inserts the inert client anchor).
|
|
225
226
|
|
|
226
227
|
`benchmark/profile.mjs` therefore generates a dedicated profile
|
|
227
228
|
(`$DSH_HOME/profiles/molbio-bench`) whose patch layer is **derived** from
|
|
@@ -12,6 +12,20 @@
|
|
|
12
12
|
* Run this only when you WANT to move the golden fixture forward (for example
|
|
13
13
|
* after deliberately changing a task). Test failures point here when a recorded
|
|
14
14
|
* response stops satisfying its task.
|
|
15
|
+
*
|
|
16
|
+
* ── amending a subset (`--replace`) ─────────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* Re-recording the one or two entries a fix invalidated used to mean freezing a
|
|
19
|
+
* whole new run and discarding every other recording. `--replace` takes just
|
|
20
|
+
* those responses from the new report and keeps the rest, and records where each
|
|
21
|
+
* amended response came from in `run.amendments` — explicit provenance beats a
|
|
22
|
+
* fixture that pretends to be a single run:
|
|
23
|
+
*
|
|
24
|
+
* node benchmark/_freeze-trace.mjs benchmark/reports/<stamp>-suite.json \
|
|
25
|
+
* --replace methylation-block,qpcr-primers
|
|
26
|
+
*
|
|
27
|
+
* The replacement is a REAL recording from the named run, never a hand edit: the
|
|
28
|
+
* original ids, order and every untouched response survive.
|
|
15
29
|
*/
|
|
16
30
|
|
|
17
31
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
@@ -19,7 +33,16 @@ import { resolve } from 'node:path';
|
|
|
19
33
|
|
|
20
34
|
const source = process.argv[2];
|
|
21
35
|
if (source === undefined) {
|
|
22
|
-
console.error('usage: node benchmark/_freeze-trace.mjs <report.json>');
|
|
36
|
+
console.error('usage: node benchmark/_freeze-trace.mjs <report.json> [--replace id[,id…]]');
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const replaceAt = process.argv.indexOf('--replace');
|
|
41
|
+
const replaceIds = replaceAt === -1
|
|
42
|
+
? undefined
|
|
43
|
+
: (process.argv[replaceAt + 1] ?? '').split(',').map((id) => id.trim()).filter((id) => id !== '');
|
|
44
|
+
if (replaceAt !== -1 && (replaceIds === undefined || replaceIds.length === 0)) {
|
|
45
|
+
console.error('--replace needs a comma-separated list of task ids');
|
|
23
46
|
process.exit(2);
|
|
24
47
|
}
|
|
25
48
|
|
|
@@ -29,23 +52,74 @@ if (!Array.isArray(report.results) || report.results.length === 0) {
|
|
|
29
52
|
process.exit(1);
|
|
30
53
|
}
|
|
31
54
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
harness_version: report.harnessVersion,
|
|
38
|
-
profile: report.profile,
|
|
39
|
-
model: report.model,
|
|
40
|
-
summary: { total: report.summary.total, passed: report.summary.passed },
|
|
41
|
-
responses: report.results.map((result) => ({
|
|
42
|
-
id: result.id,
|
|
43
|
-
tool_calls: result.toolCalls,
|
|
44
|
-
answer: result.answer,
|
|
45
|
-
})),
|
|
46
|
-
},
|
|
47
|
-
};
|
|
55
|
+
const responses = report.results.map((result) => ({
|
|
56
|
+
id: result.id,
|
|
57
|
+
tool_calls: result.toolCalls,
|
|
58
|
+
answer: result.answer,
|
|
59
|
+
}));
|
|
48
60
|
|
|
49
61
|
const target = resolve(import.meta.dirname, '..', 'test', 'fixtures', 'benchmark-traces.json');
|
|
62
|
+
|
|
63
|
+
/** The whole-fixture shape: one run, every response from it. */
|
|
64
|
+
function wholeRun() {
|
|
65
|
+
return {
|
|
66
|
+
$comment:
|
|
67
|
+
'Real responses from one benchmark run, frozen so the scorer can be regression-tested without spending API budget. See test/benchmark-score.mjs for why each defect these cover was invisible to synthetic traces.',
|
|
68
|
+
run: {
|
|
69
|
+
recorded_at: report.startedAt,
|
|
70
|
+
harness_version: report.harnessVersion,
|
|
71
|
+
profile: report.profile,
|
|
72
|
+
model: report.model,
|
|
73
|
+
summary: { total: report.summary.total, passed: report.summary.passed },
|
|
74
|
+
responses,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The amended shape: keep the fixture, swap the named entries, say so. */
|
|
80
|
+
async function amendedRun() {
|
|
81
|
+
const existing = JSON.parse(await readFile(target, 'utf8'));
|
|
82
|
+
const byId = new Map(existing.run.responses.map((response) => [response.id, response]));
|
|
83
|
+
// Replacing the same id twice must not pile up amendment entries or repeat
|
|
84
|
+
// the provenance sentence: this command is re-run whenever a report is rebuilt.
|
|
85
|
+
const amendments = (existing.run.amendments ?? []).filter((entry) => !replaceIds.includes(entry.id));
|
|
86
|
+
for (const id of replaceIds) {
|
|
87
|
+
const fresh = responses.find((response) => response.id === id);
|
|
88
|
+
if (fresh === undefined) {
|
|
89
|
+
console.error(`--replace: the report has no result for "${id}" — nothing was written`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
if (!byId.has(id)) console.log(` note: "${id}" was not in the fixture; appending it`);
|
|
93
|
+
byId.set(id, fresh);
|
|
94
|
+
amendments.push({
|
|
95
|
+
id,
|
|
96
|
+
recorded_at: report.startedAt,
|
|
97
|
+
harness_version: report.harnessVersion,
|
|
98
|
+
model: report.model,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ordered = existing.run.responses.map((response) => byId.get(response.id));
|
|
103
|
+
for (const id of replaceIds) {
|
|
104
|
+
if (!existing.run.responses.some((response) => response.id === id)) ordered.push(byId.get(id));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
$comment: existing.$comment.includes('run.amendments names each one')
|
|
109
|
+
? existing.$comment
|
|
110
|
+
: `${existing.$comment} Some responses were re-recorded later; run.amendments names each one and the run it came from, and every other response is unchanged. A replaced entry loses any suspension marker it carried, because the replacement is a fresh recording.`,
|
|
111
|
+
run: {
|
|
112
|
+
...existing.run,
|
|
113
|
+
amendments,
|
|
114
|
+
responses: ordered,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const out = replaceIds === undefined ? wholeRun() : await amendedRun();
|
|
50
120
|
await writeFile(target, `${JSON.stringify(out, null, 2)}\n`, 'utf8');
|
|
51
|
-
console.log(
|
|
121
|
+
console.log(
|
|
122
|
+
replaceIds === undefined
|
|
123
|
+
? `froze ${out.run.responses.length} response(s) into ${target}`
|
|
124
|
+
: `amended ${replaceIds.length} response(s) (${replaceIds.join(', ')}) in ${target}; ${out.run.responses.length} total`,
|
|
125
|
+
);
|
package/benchmark/profile.mjs
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* agent-plane feature the Web surface composes; the one-shot runner drives
|
|
18
18
|
* `agents.create()` directly and never joins one. So the tools have to be
|
|
19
19
|
* mounted as HOST rows, which is the one shape this package otherwise refuses
|
|
20
|
-
* to ship (`cordis.patch.yml`
|
|
20
|
+
* to ship (`cordis.patch.yml` only inserts the inert client anchor — see the
|
|
21
|
+
* preset's header).
|
|
21
22
|
*
|
|
22
23
|
* ── What keeps the reconstruction honest ────────────────────────────────────
|
|
23
24
|
*
|
package/build/preset-patch.mjs
CHANGED
|
@@ -1,35 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-molbio-tools/build/preset-patch.mjs
|
|
3
3
|
*
|
|
4
|
-
* Generate the
|
|
4
|
+
* Generate the ONE bundle patch this package ships: the client anchor plus the
|
|
5
|
+
* "Molecular Biology Lab" agent preset.
|
|
5
6
|
*
|
|
6
|
-
* WHY
|
|
7
|
-
*
|
|
8
|
-
* DSH
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* WHY ONE FILE
|
|
8
|
+
* ------------
|
|
9
|
+
* DSH's runtime accepts `dsh.bundle.patch` as a file path or an ordered list of
|
|
10
|
+
* file paths (`@deepseek-ai/dsh-app-boot` → `bundlePatchPaths`), and the shipped
|
|
11
|
+
* `@deepseek-ai/dsh-web-app` uses the list form. DSH STORE's submission precheck
|
|
12
|
+
* only accepts a single string (`safePatchPath`), so the two layers this package
|
|
13
|
+
* used to ship as a list are emitted as one file with two top-level `insert`
|
|
14
|
+
* operations, in the same order they were applied:
|
|
14
15
|
*
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
* name: '@deepseek-ai/dsh-agent-preset'
|
|
18
|
-
* config:
|
|
19
|
-
* id: molbio-lab
|
|
20
|
-
* name: Molecular Biology Lab
|
|
21
|
-
* description: ...
|
|
22
|
-
* plugins: [ ...the rows that used to be the composition... ]
|
|
16
|
+
* 1. molbio-client → './host.mjs' (inert host anchor; see below)
|
|
17
|
+
* 2. preset-molbio-lab → '@deepseek-ai/dsh-agent-preset'
|
|
23
18
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
19
|
+
* Nothing about either layer changes: a patch file is an ordered list of
|
|
20
|
+
* operations, and DSH applies them in file order exactly as it applied the two
|
|
21
|
+
* files in list order.
|
|
22
|
+
*
|
|
23
|
+
* WHY THE ANCHOR EXISTS
|
|
24
|
+
* ---------------------
|
|
25
|
+
* DSH's client-module scan walks the HOST Loader tree only. It reads each live
|
|
26
|
+
* host row's package manifest, takes `dsh.client` and `exports["./client"]` from
|
|
27
|
+
* it, and composes the browser graph that becomes `window.__DSH_BOOT__`. A
|
|
28
|
+
* preset's rows live in an isolated subtree that scan never visits.
|
|
29
|
+
*
|
|
30
|
+
* So when the tools moved into the preset, this package lost its only host-plane
|
|
31
|
+
* row, its bundle stopped reaching the page, and the two right-sidebar tabs
|
|
32
|
+
* (Molbio / Papers) silently disappeared. `host.mjs` restores exactly one inert
|
|
33
|
+
* row so the browser half is discoverable again — without putting any tool into
|
|
34
|
+
* a session that did not choose the molbio-lab mode.
|
|
35
|
+
*
|
|
36
|
+
* WHY THE PRESET IS GENERATED
|
|
37
|
+
* ---------------------------
|
|
38
|
+
* DSH 0.1.7-alpha.1 removed the `<dshHome>/.agent-presets` directory scan: a
|
|
39
|
+
* preset is now a `@deepseek-ai/dsh-agent-preset` ROW in a bundle patch, which
|
|
40
|
+
* is what the second insert below declares. Because the BUNDLE registers it,
|
|
41
|
+
* `dsh plugin --profile <p> add dsh-molbio-tools` puts the mode in the picker
|
|
42
|
+
* with no copy step and no preset-root patch — see README「安装」.
|
|
43
|
+
*
|
|
44
|
+
* Keeping the rows in ONE place and generating the container from them is what
|
|
45
|
+
* stops the two from drifting, because `test/preset-health.mjs` validates every
|
|
46
|
+
* row against the INSTALLED harness's own schemas.
|
|
28
47
|
*
|
|
29
48
|
* source : preset/molbio-lab/agent.cordis.yml (top-level row list, comments intact)
|
|
30
|
-
*
|
|
49
|
+
* source : preset/molbio-lab/preset.yml (picker name + description)
|
|
50
|
+
* output : cordis.patch.yml (GENERATED; anchor + preset)
|
|
31
51
|
*
|
|
32
|
-
* The
|
|
52
|
+
* The rows are emitted as-is: no re-serialization, so the comments that explain
|
|
33
53
|
* each row survive and the file stays diffable against the shipped `standard`
|
|
34
54
|
* patch it descends from.
|
|
35
55
|
*
|
|
@@ -55,20 +75,56 @@ export function rowBlockOf(source) {
|
|
|
55
75
|
/** Indent every line of a block by `width` spaces. */
|
|
56
76
|
const indent = (block, width) => block.split('\n').map((line) => (line === '' ? line : ' '.repeat(width) + line)).join('\n');
|
|
57
77
|
|
|
78
|
+
const HEADER = [
|
|
79
|
+
'# dsh-molbio-tools bundle patch — the one file `dsh.bundle.patch` names.',
|
|
80
|
+
'#',
|
|
81
|
+
'# GENERATED by build/preset-patch.mjs. Edit the row list in',
|
|
82
|
+
'# preset/molbio-lab/agent.cordis.yml (and the picker strings in preset.yml), then',
|
|
83
|
+
'# re-run `node build/preset-patch.mjs`; hand edits here are reverted by the next',
|
|
84
|
+
'# run, and `node build/preset-patch.mjs --check` fails while the two disagree.',
|
|
85
|
+
'#',
|
|
86
|
+
'# Two top-level inserts, applied in this order:',
|
|
87
|
+
'# 1. molbio-client — inert host anchor for the browser half (see below)',
|
|
88
|
+
'# 2. preset-molbio-lab — the "Molecular Biology Lab" agent preset',
|
|
89
|
+
].join('\n');
|
|
90
|
+
|
|
91
|
+
const ANCHOR = [
|
|
92
|
+
'# ── 1. the CLIENT-BUNDLE ANCHOR ────────────────────────────────────────────',
|
|
93
|
+
'#',
|
|
94
|
+
'# `host.mjs` registers no tool and publishes no service. Its only job is to be a',
|
|
95
|
+
"# loader row DSH's client-module scan can see: that scan walks the HOST Loader",
|
|
96
|
+
"# tree, reads the row's package manifest, and takes `dsh.client` +",
|
|
97
|
+
'# `exports["./client"]` from it to compose `window.__DSH_BOOT__`. A preset\'s rows',
|
|
98
|
+
'# live in an isolated subtree the scan never visits, so a `dsh.client` package',
|
|
99
|
+
'# referenced only from inside a preset loses its only discoverable row and its',
|
|
100
|
+
'# right-sidebar tabs silently disappear.',
|
|
101
|
+
'#',
|
|
102
|
+
'# Keep the tool in the PRESET and only the anchor here. An earlier version of',
|
|
103
|
+
"# this file inserted a host row for the tool itself (`name: dsh-molbio-tools`),",
|
|
104
|
+
"# which both registered all 60 tools globally and collided with the preset's own",
|
|
105
|
+
'# copy of that row:',
|
|
106
|
+
'#',
|
|
107
|
+
'# broken: "tool-molbio (...): never started"',
|
|
108
|
+
'- insert:',
|
|
109
|
+
' - id: molbio-client',
|
|
110
|
+
" name: './host.mjs'",
|
|
111
|
+
].join('\n');
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build the bundle patch: anchor first, preset second.
|
|
115
|
+
*
|
|
116
|
+
* @param {{rows: string, description: string, displayName?: string}} input
|
|
117
|
+
* @returns {string} the complete `cordis.patch.yml` text (single trailing newline)
|
|
118
|
+
*/
|
|
58
119
|
export function buildPatch({ rows, description, displayName = 'Molecular Biology Lab' }) {
|
|
59
|
-
const
|
|
60
|
-
'#
|
|
120
|
+
const presetHeader = [
|
|
121
|
+
'# ── 2. the MOLECULAR BIOLOGY LAB PRESET ───────────────────────────────────',
|
|
61
122
|
'#',
|
|
62
|
-
'#
|
|
63
|
-
'#
|
|
64
|
-
|
|
65
|
-
'# `
|
|
66
|
-
'#',
|
|
67
|
-
'# DSH 0.1.7-alpha.1 removed the `<dshHome>/.agent-presets` directory scan: a preset is',
|
|
68
|
-
'# now a `@deepseek-ai/dsh-agent-preset` ROW in a bundle patch, which is what this',
|
|
69
|
-
'# layer inserts. Because the BUNDLE registers it, `dsh plugin --profile <p> add',
|
|
70
|
-
'# dsh-molbio-tools` puts the mode in the picker with no copy step and no',
|
|
71
|
-
'# preset-root patch — see README「安装」.',
|
|
123
|
+
'# DSH 0.1.7-alpha.1 removed the `<dshHome>/.agent-presets` directory scan: a',
|
|
124
|
+
'# preset is now a `@deepseek-ai/dsh-agent-preset` ROW in a bundle patch, which',
|
|
125
|
+
'# is what this insert declares. Because the BUNDLE registers it,',
|
|
126
|
+
'# `dsh plugin --profile <p> add dsh-molbio-tools` puts the mode in the picker',
|
|
127
|
+
'# with no copy step and no preset-root patch — see README「安装」.',
|
|
72
128
|
'#',
|
|
73
129
|
'# `order: 5` is deliberate: the shipped presets occupy 1..4 (standard, ptc,',
|
|
74
130
|
'# minimal, cordis) and the registry sorts by `order` with an id tie-break, so a',
|
|
@@ -76,7 +132,11 @@ export function buildPatch({ rows, description, displayName = 'Molecular Biology
|
|
|
76
132
|
].join('\n');
|
|
77
133
|
|
|
78
134
|
return [
|
|
79
|
-
|
|
135
|
+
HEADER,
|
|
136
|
+
'',
|
|
137
|
+
ANCHOR,
|
|
138
|
+
'',
|
|
139
|
+
presetHeader,
|
|
80
140
|
'- insert:',
|
|
81
141
|
' - id: preset-molbio-lab',
|
|
82
142
|
" name: '@deepseek-ai/dsh-agent-preset'",
|
|
@@ -95,7 +155,7 @@ function main() {
|
|
|
95
155
|
const check = process.argv.includes('--check');
|
|
96
156
|
const sourcePath = join(repoRoot, 'preset', 'molbio-lab', 'agent.cordis.yml');
|
|
97
157
|
const metaPath = join(repoRoot, 'preset', 'molbio-lab', 'preset.yml');
|
|
98
|
-
const outPath = join(repoRoot, '
|
|
158
|
+
const outPath = join(repoRoot, 'cordis.patch.yml');
|
|
99
159
|
|
|
100
160
|
const rows = rowBlockOf(readFileSync(sourcePath, 'utf8'));
|
|
101
161
|
if (rows === undefined || !rows.includes('- id:')) {
|
|
@@ -124,16 +184,16 @@ function main() {
|
|
|
124
184
|
process.exit(1);
|
|
125
185
|
}
|
|
126
186
|
if (current !== text) {
|
|
127
|
-
console.error('preset-patch: the committed
|
|
187
|
+
console.error('preset-patch: the committed bundle patch is STALE — run `node build/preset-patch.mjs`');
|
|
128
188
|
process.exit(1);
|
|
129
189
|
}
|
|
130
|
-
console.log('
|
|
190
|
+
console.log('bundle patch is current');
|
|
131
191
|
return;
|
|
132
192
|
}
|
|
133
193
|
|
|
134
194
|
writeFileSync(outPath, text, 'utf8');
|
|
135
|
-
const
|
|
136
|
-
console.log(`
|
|
195
|
+
const rowCount = rows.split('\n').filter((line) => /^\s*- id:/.test(line)).length;
|
|
196
|
+
console.log(`bundle patch written: ${outPath} (${rowCount} preset rows + anchor)`);
|
|
137
197
|
}
|
|
138
198
|
|
|
139
199
|
if (process.argv[1] !== undefined && import.meta.url === pathToFileURLSafe(process.argv[1])) main();
|
|
@@ -201,7 +201,7 @@ export function tagVersion(tag) {
|
|
|
201
201
|
* `rc.10 < rc.2`, so a release of `rc.2` after `rc.10` would look like a
|
|
202
202
|
* DOWNGRADE and the gate would refuse a legitimate publish (or, in the other
|
|
203
203
|
* direction, wave through a stale one). This repository does cut prereleases
|
|
204
|
-
* (the harness baseline itself is `0.2.
|
|
204
|
+
* (the harness baseline itself is `0.2.1-rc.2`), so the rule is implemented
|
|
205
205
|
* rather than approximated.
|
|
206
206
|
*
|
|
207
207
|
* Semver's rule: split on dots; numeric identifiers compare numerically and rank
|