Repository navigation
Releases: lovy2004/dsh-molbio-tools
Release list
v0.22.1
工具数不变(60);没有凭据库的组合,行为与 0.22.0 相同。 0.22.0 只从 api_key 参数与
process.env 读 key;这一版把 DSH 的凭据 seam(ctx.credentials)插到两者之间,并把环境变量
降为最后一级。
1. 环境变量为什么不够好
环境变量会被每个子进程继承——包括 agent 自己的 shell 工具:一句 echo $env:NCBI_API_KEY
就能把 key 读进对话;它还会出现在进程环境块、崩溃转储与用户的注册表环境里(Windows 的
HKCU\Environment 是明文)。对任何云端凭证这都是不必要的暴露面;NCBI 的 key 虽然只用于
归属与限速,泄漏仍意味着别人可以用你的配额、顶着你的身份发请求。
DSH 自带的凭据库(@deepseek-ai/dsh-credentials-local)就是为这件事存在的:值存在
~/.dsh/.credentials.yaml 里,不注入环境、文件仅属主可读、改动热重载,
describe() 只报告是否已设置与来源、从不回显值,而且存储值优先于启动环境。
2. 新的解析顺序
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | api_key 参数 |
单次调用显式指定;会出现在对话记录与工具卡片里 |
| 2 | 凭据库 NCBI_API_KEY |
推荐;不进入环境,agent 的 shell 子进程读不到 |
| 3 | 凭据库 MOLBIO_NCBI_API_KEY |
同上,插件专用命名 |
| 4 | 进程环境 NCBI_API_KEY / MOLBIO_NCBI_API_KEY |
兼容 0.22.0 的用法;暴露面最大 |
实现上调用 ctx.credentials.resolve(ref)(存在性检查后才调用),因此凭据库自身的回退层
(启动环境、项目 .env、harness home 的 .env)也一并继承;provider 不存在或拒绝该引用时
静默回退到 process.env,没有凭据库的组合行为不变。
3. key 放进凭据库的三种方式
- 插件详情页(推荐):插件 → 已安装插件 →
dsh-molbio-tools→ PubMed API key 一节,
粘贴后点 Save;见 §4。 - 编辑
~/.dsh/.credentials.yaml(产品创建、仅属主可读、热重载):在refs:下加一行
NCBI_API_KEY: 你的key;下一次工具调用即生效,无需重启。 - 在 DSH 的设置界面保存同名凭据(该界面只显示“是否已设置/来源”,不显示值)。
这不是对 agent 的硬隔离。 凭据库自己的文档写得很清楚:agent 的工具进程以同一个 OS 用户
运行,刻意去读那个文件仍然读得到。它解决的是自动泄漏:环境变量会被每个 shell 子进程
无条件继承,而凭据库不会。真正要把密钥挡在 agent 之外,需要换提供方(OS 钥匙串/KMS),
当前产品没有随附这类 provider。
4. 图形入口:插件详情页里的 PubMed API key
「改 YAML」对没有计算机基础的用户不是可接受的入口。浏览器半因此注册了一个
plugins.detail.section 条目(id: molbio-ncbi-key),只在本包的 bundle 页与它声明的行页
上渲染,其它插件返回 null:
- 输入框是
type=password,只写不读;状态行来自ctx.remote.credentials.describe(['NCBI_API_KEY']),
只显示「已配置 / 未配置 / 来源」,不回显值。 - Save 走
ctx.remote.credentials.set('NCBI_API_KEY', value),Remove 走unset;
保存后输入框立即清空,成功/拒绝消息原样显示(credential/rejected的 provider 原文)。 - 它和 CLI/工具读的是同一个凭据引用,所以两边不需要同步;没有凭据 provider 的部署会显示
Unavailable,而工具仍按 §2 的顺序回退到环境变量。
测试中发现并修掉的一处真实缺陷(同一版内):第一版把
ctx.remote.credentials当成普通
属性直接访问,而它是 fiber injection——官方设置页在inject列表里显式声明
remote.credentials。没有声明时该命名空间是undefined,点 Save 什么也不会写(状态看起来
只是"没保存")。现在整段的注册被ctx.inject(['remote.credentials'], …)门控:有命名空间才
出现,没有则整段不注册(面板不受影响);写入之后还会重新describe,若 Host 仍报"未配置"
会把这句话显示出来,而不是假装成功。
5. 边界与验证
- 默认路径(无 key)仍是 web 检索;有 key 仍是 esearch + esummary + efetch,速率上限不变。
- 客户端产物因此变了:
lib/client.js与packages/molbio-panel/lib/client.js重新构建,
面板包0.1.5 → 0.1.6。 - smoke 新增三条:凭据库压过环境变量、显式参数压过凭据库、provider 抛错时回退到环境;
原有的“无 key 不碰 E-utilities”与“坏 key 报错”保留。test/client.mjs新增详情页座位的
等待/注册、本包 subject 渲染、其它 subject 返回 null,以及凭据视图/失败信息三个纯函数。 npm test全绿;client-bundle.mjs --check与preset-patch.mjs --check与源一致。
npm entries published by this release
dsh-molbio-tools@0.22.1—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.6(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.22.0
工具数不变(60)。新增一条通路:molbio_pubmed_search / molbio_pubmed_abstract 可以带着
NCBI API key 走 PubMed 官方 E-utilities。 没有 key 时两条工具的行为与 0.21.1 相同——检索仍走
harness 的 web 检索,摘要仍走 ctx.web.fetch。
1. 为什么要有官方通路
原来的检索完全依赖 harness 的 web 检索服务:它返回的是搜索引擎索引到的页面,不是 PubMed 的
题录。三个具体代价:命中的 PMID 要从 URL 里猜、拿不到期刊/年份/作者/DOI 这些结构化字段、也
无法用 NCBI 的 API key 提升速率上限(无 key 3 次/秒,有 key 10 次/秒)。官方 API 是两步:
esearch(term → PMID 列表)→ esummary(PMID → 题录),摘要另走 efetch。
2. 通路选择与 key 的来源
| 条件 | 检索通路 | 结果字段 |
|---|---|---|
| 有 key(参数或环境变量) | 官方 E-utilities:esearch + esummary | PMID、标题、期刊、年份、作者、DOI,另含 total_matches |
| 无 key | 原来的 harness web 检索(行为不变) | 标题、URL、snippet、从 URL 提取的 PMID |
key 的优先级是 api_key 参数 > NCBI_API_KEY > MOLBIO_NCBI_API_KEY;空白值视为未提供
(不会发出 api_key= 这种被 NCBI 以 400 拒绝的空参数)。一次带 key 的检索是 2 个请求,
molbio_pubmed_abstract 是 1 个 efetch 请求。
内联传 key 会出现在对话记录和工具调用卡片里,这是参数通路的固有代价;长期使用应设环境
变量。工具的输出、渲染和错误信息都不回显 key。
3. 实现
papers.mjs增加纯函数:pubmedApiKey(参数/环境变量优先级)、pubmedSearchUrl/
pubmedSummaryUrl/pubmedFetchUrl(三个 URL 构造器,统一带tool=dsh-molbio-tools)、
parseEsearch(ids + count)、parseEsummaryRecords(按uids顺序归一化题录,
DOI 从articleids里取idtype: doi)、esummarySource(转成工具返回的 source 形状)。- 传输仍走
ctx.web.fetch:官方 API 不是一条绕开沙箱/同意机制的新网络通路。没有web.fetch
而用户又给了 key 时,工具明确报「需要 ctx.web.fetch」,而不是悄悄退回 web 检索。 - 错误处理:NCBI 的
{"error":"API key invalid"}会变成PubMed E-utilities error: API key invalid,不会被当成「没有结果」——「搜不到」和「key 错了」是两件事。空结果返回
sources: []与total_matches: 0。 - 输出 schema 扩展:
backend(pubmed-eutils/web-search)、total_matches,以及 source 上的
doi/authors/journal/year;全部为可选字段,旧调用的输出形状不变。
4. rc.3 适配:这次上游什么都没动,但仍要走完流程
@deepseek-ai/dsh-web-app@0.2.1-rc.3的standard.patch.yml与 rc.2 逐字节一致(git diff --no-index验证),所以 preset 行清单、cordis.patch.yml、preset-health的期望都没有改。- 在 rc.3 上全套守卫全绿:
preset-health(30 行)、drift-probe(14 条突变)、contract(16 项,
含客户端入口与presentationMeta的行为断言)、benchmark-profile(12 行 / 60 工具)。 - 声明:
engines.dsh仍为>=0.2.1-rc.2 <0.3.0-0(rc.2 仍可用);dshReleases把 rc.3 改成
compatible;CI 的 test/release 两条工作流钉到0.2.1-rc.3;面板包0.1.4 → 0.1.5(清单的
兼容声明变了,客户端产物字节未变,因此只 bump 面板版本线)。 - 证据用 rc.3 的 CLI 重跑:
docs/store-evidence.md(0.22.0 tarball 安装 → 启动 → 卸载)。
5. benchmark 的处理(以及为什么没有重跑模型)
- 默认通路(无 key)的输出与 0.21.1 相同,
bench --offline的 61 题期望值逐条照跑。 literature-search仍是NETWORK_TASKS里唯一的一条:它断言的是模型的最终回答(PMID、标题、
摘要措辞),工具结果本来就是当天的 live PubMed 数据,没有可以钉住的值。- 带 key 的新通路由
test/smoke.mjs覆盖,而不是 benchmark:benchmark 不能要求真实 key、
也不该把 live 记录冻结成期望值。smoke 用 mock 的 esearch/esummary/efetch 断言了四件事——
key 同时出现在两个请求里、题录字段解析正确、环境变量能回退、无效 key 会报错;另有一条
「无 key 时不碰 E-utilities 传输」的对照。
6. 版本与验证
- 根包
0.21.1 → 0.22.0;面板0.1.4 → 0.1.5。 npm test(DSH 0.2.1-rc.3)全绿:smoke、画面四套、客户端四套、contract 16、drift-probe 14 条
突变、preset-health 30 行、release 13 项、benchmark 四道守卫 +bench --offline61 题。node build/preset-patch.mjs --check与node build/client-bundle.mjs --check均报告与源一致
(rc.3 适配没有动任何生成物)。- 一次性 Profile:tarball 115 个文件 / 约 1.0 MB;安装 571 ms;启动
HTTP 200、38180 字节,
__DSH_BOOT__含dsh-molbio-tools/client.js&rev=a25c3919676b;卸载 421 ms,molbio引用 0、
lockfile 与node_modules无残留。
npm entries published by this release
dsh-molbio-tools@0.22.0—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.5(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.21.1
工具数不变(60),插件运行时行为零改动。 这一版不加任何计算能力,只把"这个包以什么形状提交"
改成 DSH STORE 的提交预检与 DSH 自身的清单契约都能接受、且不再互相矛盾的那一种。三处改动都
来自同一次上架提交:预检里能靠改包解决的两条已经解决,剩下两条的原理与请求另写在
docs/store-preset-exemption.md。
1. 两层 patch 合并成单个 cordis.patch.yml
0.21.0 的 dsh.bundle.patch 是有序数组:["./cordis.patch.yml", "./preset/molbio-lab/preset.patch.yml"]。
DSH 运行时接受这种写法(@deepseek-ai/dsh-app-boot 的 bundlePatchPaths:one file, or an
ordered list of files),官方 @deepseek-ai/dsh-web-app 自己就列了 5 个文件;但 DSH STORE 的提交
预检(scripts/check-plugin-submission.mjs 的 safePatchPath)只接受字符串,数组直接判
SUBMISSION_BUNDLE_PATCH_INVALID。两边都自称官方契约,解法不必等预检放宽:
build/preset-patch.mjs现在直接产出根目录的cordis.patch.yml,内容是两个 insert:
molbio-client → ./host.mjs(惰性宿主锚点)在前,preset-molbio-lab(由
preset/molbio-lab/agent.cordis.yml生成的 preset 行)在后;顺序即应用顺序。- 不再生成
preset/molbio-lab/preset.patch.yml(文件已删);package.json的
dsh.bundle.patch是单字符串。 - 四个读者改为跟单文件走:
test/preset-health.mjs、test/drift-probe.mjs、
preset/install.mjs、test/preset-rows.mjs(后者在同一个文件的两个 insert 里找 preset 段,
不假设"preset.patch.yml 才是 preset")。
这一条同时守住一条更早的教训:锚点行不能因为"文件变少了"而消失。0.15.2 的两个侧栏 tab
就是锚点丢失后静默消失的;test/client-mount.mjs仍然逐包断言"被选中的包必须有一个宿主行
能解析到它",test/contract.mjs仍然断言"产物里的每一处register都在inject里"。
2. engines.dsh 与逐版本 dshReleases
两个 manifest 各声明一份:
"engines": { "node": ">=26", "dsh": ">=0.2.1-rc.2 <0.3.0-0" },
"dsh": { "compatibility": { "dshReleases": {
"0.2.1-rc.1": "unknown", "0.2.1-rc.2": "compatible", "0.2.1-rc.3": "unknown" } } }上界写 <0.3.0-0 而不是 <0.3.0:DSH 用 semver.satisfies(..., { includePrerelease: true })
判定(@deepseek-ai/dsh-app-boot 的 unmetRequirements),带 prerelease 选项时 <0.3.0
会放行 0.3.0-alpha.1,-0 不会。rc.1 早于本包基线、rc.3 未取得也未验证,两者都只能
标 unknown——范围不等于验收:engines 是安装时的粗筛,商城的 dshReleases 记录才是
逐版本的验收结论。
面板包随之 0.1.3 → 0.1.4:它的版本线独立于根包,但清单变了,不发这个版本号就没有能承载新
声明的发布物。
3. 根 tarball 不再内嵌面板包
files 白名单去掉 packages。此前根 tarball 里同时躺着 package.json 与
packages/molbio-panel/package.json 两个 DSH 插件清单,还重复携带 427 KB 相同的 lib/client.js;
商城把"一个包里出现第二个插件清单"视为提交歧义是对的。现在根 tarball 只有一个插件清单:
118 → 115 个文件、约 1.1 MB → 约 999 kB(多出的 1 个文件是本次提交的
docs/store-preset-exemption-issue.md)。面板没有因此消失——根包的 lib/client.js 本来就带着它
(第 4 节的启动回读可以作证);面板包继续按自己的地址单独发布。
test/contract.mjs 的打包断言随之改向:不再要求 files 必须包含 packages/,而是断言
一个 tarball 里恰好只有一个 DSH 清单(把 packages 加回白名单会立刻 FAIL)。
4. 一次性 profile 证据(tarball 就是发布物)
证据记录在 docs/store-evidence.md,用临时 DSH_HOME 重跑,不碰真实
~/.dsh:npm pack → --from-default-profile web 建 profile → plugin add tarball →
--dump-config(molbio-client 解析到已安装包内,preset 行带 tool-molbio)→
--no-open --port 0 启动 → 带 token 回读页面(HTTP 200,__DSH_BOOT__ 图里有
dsh-molbio-tools/client.js)→ plugin remove → 再 --dump-config(molbio 引用 0 处)。
装 tarball 而不是目录,是因为 tarball 同时证明 files 白名单带全了运行文件。
5. 未做 / 边界
0.2.1-rc.3没有验证,因此标unknown。拿到该版本后按
docs/workflow.md 第 4 节的升级流程重跑preset-health/drift-probe/
一次性 profile,再把它改成compatible。- 预检的另外两条(保护命名空间、身份一致)不是改包能解决的:本包已自行消掉
SUBMISSION_BUNDLE_PATCH_INVALID;剩下两条的证据、代码位置与三个可执行请求写在
docs/store-preset-exemption.md,可直接贴到 issue 的短版在
docs/store-preset-exemption-issue.md。 - 工具行为与 benchmark 一律不动:60 个工具的输出与 0.21.0 相同,按规则 6 的"没变就不必跑
模型";bench --offline仍在npm test里,期望值照样逐条校验。
6. 版本与验证
- 根包
0.21.0 → 0.21.1;面板0.1.4(见 §2)。 npm test全绿:smoke、画面四套、客户端四套、contract 16 项、drift-probe 14 条突变、
preset-health 30 行、release 13 项、benchmark 四道守卫 +bench --offline61 题。node build/preset-patch.mjs --check与node build/client-bundle.mjs --check均报告与源一致。node build/release-preflight.mjs --tag v0.21.1:本地不变量全过;npm 上dsh-molbio-tools
已有 0.21.0、本版按publish处理;面板 0.1.4 尚未发布。- 文档同步:maintainer 的版本号、roadmap 的当前状态、history 的版本一览与本段、store-evidence
的 tarball 名与文件数。
npm entries published by this release
dsh-molbio-tools@0.21.1—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.4(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.21.0
工具数不变(60),插件行为零改动。 这是一次"只跟上游走"的版本:0.20.1 之后本机 harness
升到 0.2.1-rc.2,npm test 立刻红了三处——组合漂移、契约断言、以及一条依赖旧 fixture 的
漂移探针。三处都不是插件退化,但每一处都属于守卫在说谎,所以按发布阻断处理。
1. preset 行清单按 0.2.1-rc.2 逐行重抄(12 处结构差异)
- 上游
standard新增time-context、tool-schedule、tool-plugin-market; - 删除
tool-subagent-codex/tool-subagent-claude-code(两个disabled: true的可选
provider)与tool-ralph; - 去掉 persona 的
suffix: Your working directory is {{cwd}}.,以及tool-subagent/
tool-subagent-fork的backgroundMode: continuable; - plan-mode 的段落文案改为 "until the user approves your plan through exit_plan_mode…"。
agent.cordis.yml 现在是上游 config.plugins: 块的整体去缩进 + 末尾一行 tool-molbio;
0.2.1 的上游文件不带行内注释,因此行内注释随之消失,来源与升级规则集中写在文件头部。
preset.patch.yml 由 build/preset-patch.mjs 重新生成。
2. test/contract.mjs:两条断言不再锚在 minified 字面量上
- client 入口:改为按包自己的
exports["./client"](browser条件)解析。
0.2.1-rc.2 把@deepseek-ai/dsh-client-connection移到lib/client.browser.js,旧断言
硬编码lib/client.js,会把一个加载正常的包判死;同时 Web shell 已是打包产物、不再出现
require("包名"),"seeded into the shell" 兜底只剩代码路径。新增两个对照:没有
./client导出的包必须解析为undefined,以及移动过的入口必须解析到真实文件。 presentationMeta:0.2.1 删除了exec.parent === void 0闸门(投影移进
createSuccessResult,对每次成功执行都做)。断言改挂到卡片真正消费的东西:投影被
snapshotProjection记录、并以meta附到成功结果上。
3. drift-probe:突变样本换成上游仍存在的行
上游删掉 tool-ralph 后,那条"上游 disabled: true 被丢掉"的突变在空转(withRow
找不到目标,什么也没改,守卫却"通过")。改用上游仍以 disabled: true 发布的
tool-plugin-manager;14 条突变全部仍能被抓住。
4. benchmark:profile 重建、两条欠账清零
- preset 新增的两行宿主可承载行(
time-context、tool-schedule)进入 headless profile,
benchmark/profile.mjs重建molbio-bench:12 行宿主行,仍注册 60 个工具。 methylation-block与qpcr-primers的冻结响应停在 0.16.0 之前,benchmark-score每次
都点名。本版用一次完整真实模型跑重录,并用_freeze-trace.mjs --replace只替换这两条,
其余 47 条保持原样;run.amendments记录每条替换来自哪一次跑分。fixture 现为
49 条记录、0 条 suspended。- 完整跑分(2026-10-23,DSH 0.2.1-rc.2,61 题):58/61 通过,tool selection 60/61、
arguments 60/61、final answer 59/61;11.2M tokens / 2161 s。protein-props的失败是断言不认千分位逗号(答案写1,490,断言写/1490/)——
与 0.20.0 修过的/3935/同一类,当时漏了这一条。已改为1[,.]?490,--replay后
59/61。- 剩余两条是真实的模型遗漏,不是产品缺陷:
dilution-math答案正确(50 µL + 950 µL)
却从未调用molbio_lab_math;batch-analysis写出了 CSV,但最终回答只说"报告已生成",
没有报出指令要求的长度/特征数。
_freeze-trace.mjs新增--replace id[,id…]:只重录被修复作废的条目,其余记录与顺序原样
保留,并在run.amendments里写明每条替换的来源跑分——替换的是真实录制,不是手改。
5. 未做 / 边界
png_path仍然不做:contract的"工作区仍不能存二进制文件"断言在 0.2.1-rc.2 上仍然
通过,说明上游 fs 缝依旧是 text-only;触发条件未到。- 面板版本保持 0.1.3:
lib/client.js逐字节未变,release-preflight复核为
"already published with identical bytes",因此不 bump。
6. 版本与验证
- 根包
0.20.1 → 0.21.0;DSH 基线钉 0.2.1-rc.2(.github/workflows/{test,release}.yml、
preset 头注、docs/workflow.md、docs/roadmap.md、docs/maintainer.md)。 npm test全绿:contract 16/16、drift-probe 14 条突变、preset-health 30 行全过、release
13 项、benchmark profile/score/coverage/changed、bench --offline61 题。- 文档:roadmap 状态、history 的 v0.21.0 段(三条教训)、workflow 的升级提示同步。
npm entries published by this release
dsh-molbio-tools@0.21.0—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.3(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.20.1
工具数不变(60),运行时零改动;包内只多一个 746 B 的 SVG。
1. 插件管理器为什么一直显示默认插画
DSH 的插件管理器从包根的清单 icon 或导出的 ./icon 读取展示图,读到的字节会转成
base64 data URL 交给 PackageArtwork;两者都没有时保留面板默认插画。本包此前既没有
icon 字段也没有 ./icon 导出,因此卡片和插件行一直是默认图——不是缓存,也不是
安装坏了,而是展示元信息根本没声明。
2. 图标:透明底双螺旋,按 30/36 px 的展示尺寸设计
icon.svg(64×64 viewBox,746 B)是原创的双螺旋:青绿 #0D9488 与蓝 #2563EB
两条主链、四条琥珀 #F59E0B 碱基对,圆头描边、无渐变/字体/外部引用。透明底是刻意的:
插件管理器把它放进 40×40(行)和 48×48(卡片)的圆角框里,图本身再加一层背景会变成
“框中框”;不留背景也让同一张图在浅色和深色主题下都成立。主链可视范围约 28×58(64 单位
画布),在 36 px 卡片和 30 px 行图标上仍能读出双螺旋,20 px 时退化为可辨的轮廓。
3. 接入:导出、白名单,以及一个 gitignore 例外
package.json的exports增加"./icon": "./icon.svg",files白名单加入
icon.svg;本包files是白名单而不是默认全带,漏了它npm pack就会静默丢掉图标。- 根目录的
/*.svg规则原本用来挡住工具生成的图(logo.svg、质粒图等),新增
!/icon.svg例外,图标才能作为源文件提交;git 忽略与 npmfiles是两套规则,不能只改
一处。 - 不新增依赖、不改
dsh.client、不改 preset 行清单;已安装的 profile 是指向本目录的
junction,重新读取 manifest 即可看到,不需要重装。
4. 验证
createRequire(...).resolve('dsh-molbio-tools/icon')解析到包内icon.svg;
npm pack --dry-run --json的 118 个文件中包含它。- 用本包自己的
svgpng.mjs光栅化:64×64,unsupported: [];另按 36/30/20 px
下采样检查过轮廓。 test/contract.mjs16/16;test/release.mjs13/13(本机沙箱下按该测试自带的
npm_config_user_agent兜底跑通)。- 根包
0.20.0 → 0.20.1;面板0.1.3不动(lib/client.js未变,发布前置检查不会
要求面板 bump)。标题/描述仍回退到package.json的包名与长描述;本次没有新增
locale/*.json,如果要本地化展示文案,应作为独立改动。
npm entries published by this release
dsh-molbio-tools@0.20.1—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.3(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.20.0
工具数 58 → 60,benchmark 56 → 61 题(60/60 覆盖)。 这一版把 0.19.1 计划的四项硬化直接并入,
并落地 roadmap 2.3 / 2.5 / 2.7 / 2.8 与 survey #8。Cas12a/Cas13 不是"再加一个 PAM 字符串":
原来的 findProtospacers 把 3' PAM、20 nt 和 3' 种子写死在几何里,对 Cas12a/Cas13 会给出生物学上错误的
坐标与评分,因此这一版把家族几何做成一张表,并补上了反向链脱靶定位的静默错误。
1. 并入的 0.19.1 硬化(原计划单发 patch,现随 0.20.0 一起)
- Smith-Waterman 的 DP 矩阵上限:
molbio_align在分配前拒绝超过 50,000,000 cells 的输入对,
而不是让两个几百 kb 的序列把宿主进程的内存吃光;工具描述同步说明"对齐窗口或改用
molbio_verify_sanger"。test/smoke.mjs用 10 kb × 10 kb 的输入证明拒绝发生在分配之前。 - SnapGene/PubMed XML 数值实体:
�曾让String.fromCodePoint抛出 RangeError
并逃出整个解析器;现在越界码点与代理项按字面文本保留,合法实体(含星形平面)照常解码。 molbio_sequence_logo的并发声明:它写共享默认路径logo.svg,之前却默认safe;
现与其他画图工具一致声明safe: false。- npm
files白名单:不再整目录带benchmark,改为benchmark/*.mjs、benchmark/tasks、
benchmark/README.md,因此.gitignore里的本机benchmark/reports/不会再被打进包
(此前约 1.4 MB)。test/contract.mjs直接禁止裸benchmark条目。
2. roadmap 2.5:Cas12a/Cas13 家族几何(以及反向脱靶定位缺陷)
crispr.mjs新增CAS_FAMILIES表:SpCas9(3' NGG / 20 nt / 3' 种子)、SaCas9
(3' NNGRRT / 21 nt)、Cas12a(5' TTTV / 23 nt / 5' 种子)、Cas13a(RNA / 3' H PFS / 28 nt)、
Cas13b(RNA / 5' D + 3' NAN / 30 nt)、Cas13d(RNA / 无 PFS / 22 nt)。PAM 校验现在按
PAM 的哪一侧贴近 protospacer 检查简并位,而不是一律要求首位为 N——TTTV因此不再被误拒。- Cas13 不是 PAM:它有 PFS 偏好,工具报告 PFS、按缺失扣分并明确说明"不是硬性 PAM";
只有require_pfs=true才过滤。Cas13 的tm_celsius返回null,因为对 RNA guide 报 DNA
NN Tm 是另一个分子;工具描述与渲染都写明这一点。 - 反向链脱靶定位是真 bug:索引把每个 k-mer 记在 top-strand 起点,旧
finalizeSites却对
反向命中减去guideLength-1,使它们整体偏移 19 nt 并大多越界丢弃——正向 fixture 永远看不见。
现在两种链都从entry.position起算,PAM 按 5'/3' 侧与链向取 top-strand 窗口;新增的
smoke fixture 在 forward guide 与 reverse perfect copy 之间互指,断言反向脱靶start/end/pam
与 0 mismatch。 - 新 benchmark:
crispr-cas12a(5' TTTV、23 nt、坐标 9-31)与crispr-cas13(RNA、PFS A、28 nt)。
test/smoke.mjs另钉住 Cas12a/Cas13a 的几何、PFS 过滤与错误路径。
3. roadmap 2.8:TaqMan MGB 与订购 CSV
probe_chemistry: 'mgb'使用 13-20 nt、未修饰 NN Tm 48-60 °C 的窗口;MGB 修饰的 Tm 不被
NN 模型预测,因此报告的是未修饰 Tm,并以tm_model与 notes 明确要求向供应商复核。
MGB 不施加"探针 Tm 高于引物"的未修饰差值罚分(那会把每个 MGB 候选都罚掉)。- 新增
taqmanOrderRows/taqmanOrderCsv:每条 assay 三行(F/R/P),列含 5'/3' 修饰、规模、
纯化与说明;MGB 默认NFQ-MGB,5' 默认 FAM。save_path写 CSV;不传则只返回结构化 order。 - 标准 chemistry 的默认值、排序与已发布数值保持不变(smoke 的 9 assays 与 pinned probe 仍绿)。
4. roadmap 2.7:多重 PCR 的退火温度与浓度配平建议
checkMultiplex新增annealing:最低引物 Tm −annealing_offset_c(默认 3 °C),
给出 ±annealing_window_c范围、最低 Tm 引物、Tm 离散度与是否超过警告阈值。- 新增
primer_concentrations与concentration_note:默认 200 nM;当扩增子大小比 ≥ 2 时,
最短的降到 150 nM、最长的升到 300 nM,并明确这是起始点而非实测最优。 - 这些是清晰标注的规则性建议,notes 写明"不是热循环优化,需跑梯度"。
5. roadmap 2.3:批量分析 + CSV/TSV
- 新模块
batch.mjs与新工具molbio_batch_analyze:扫描目录(可递归)或显式 paths,复用
同一批 FASTA/FASTQ/GenBank/SnapGene 解析器,输出一张 CSV/TSV。 - 每行:格式、拓扑、条目数、总长、GC%、N%、特征数、平均长度;
analysis: orf加 ORF 数/最长
ORF,analysis: enzymes加指定酶的位点数,all两者都加。单个坏文件变成一行error,
其余文件继续;文件/字节上限与truncated都显式报告。 - 拓扑按 GenBank/SnapGene 声明优先(环状质粒的位点计数不会被当成线性);无声明时才用
circular参数。新 benchmarkbatch-analysis同时读pUC118.gb与pUC118.dna。
6. survey #8:静默酶切位点突变
- 新模块
silent-site.mjs与新工具molbio_silent_site_mutagenesis:只在同义密码子内枚举,
因此蛋白序列按构造不变;输出逐碱基变化、密码子前后与氨基酸、可直接喂给
molbio_mutagenesis_primers的 mutation texts、以及突变后序列。 - 支持 remove/add、frame 1-3、可选
host(按 e_coli/yeast/human 密码子偏好排序)与区域限制;
只接受识别序列无简并碱基的酶(GGTNACC这类直接拒绝而不是猜)。 - 新 benchmark
silent-site钉住 EcoRI 的C12T(TTC→TTT,Phe)与突变后序列。
7. 版本、基线与文档
- 根包
0.19.0 → 0.20.0,面板0.1.2 → 0.1.3(客户端产物因 crispr/taqman/multiplex 改动而变,
两个 npm 条目需分别发布)。 - DSH 基线
0.1.7-rc.1 → 0.2.0-rc.2:.github/workflows/test.yml的 pin、roadmap/maintainer/
workflow 与 preset 头注释同步;preset-health在 0.2.0-rc.2 上仍为 30 行全过(行清单无需改动)。 - README/preset 描述/工具表/benchmark 计数同步到 60 tools / 61 tasks;新增模块进入 npm
files白名单与目录树。 - 未做:
benchmark-score里 2 条 0.16.0 之前的冻结响应(methylation-block、qpcr-primers)
仍是 suspended;本次没有用真实模型重录,测试输出会继续点名这两条,而不是假装它们已通过。
8. 发布前完整 benchmark(2026-10-17,真实模型)
- 61 题首跑 55/61:tool selection 61/61,arguments 59/61,final answer 57/61;
12.7M tokens / 1747 s。 - 新功能任务全部通过:
taqman-mgb、crispr-cas12a、crispr-cas13、silent-site、
batch-analysis。multiplex-panel在把"退火温度/浓度"明确写进指令后复跑通过。 - 4 个失败是断言写死格式,不是产品缺陷:
plasmid-digest(模型写2,840)、
protein-props(3,935)、codon-optimize(EcoRI-free)、chart-volcano
(任务要求de.svg,断言却写volcano.svg)。修正后--replay四题全部转为 PASS。 - 1 个真实可用性缺陷:
molbio_extract_region的 schema 广告sequence,但
resolveVector只读vector,于是文档里写的参数被静默忽略、调用报
"provide either vector or vector_path"。现在vector/sequence/reference三个已声明别名
都接受,smoke 增加回归断言。该任务的真实模型复跑因 DSH 沙箱对 headless 子进程的审批限制
未完成,因此发布时的实测口径是 60/61(这一题以 smoke 回归 + 修复代码为准,而不是假装已跑)。 - benchmark 工具本身未改;全量首跑使用了一次性更宽沙箱来启动 headless DSH 子进程。
npm entries published by this release
dsh-molbio-tools@0.20.0—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.3(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.19.0
工具数 57 → 58。 这一版是 roadmap 2.4 与 2.2 两条候选的落地:
前者修一个画面正确性缺陷,后者补一个比对之后该问的问题;另外把发布本身做成了 CD。
1. 环形/扇形布局的长叶名曾整行画出画布(roadmap 2.4)
症状:molbio_phylogenetic_tree 的 rectangular 布局早在 v20 就把长叶名折成
<tspan> 多行,而 circular / fan 一直是整行旋转着画出去的。位置靠
labelLength * leafFontSize * 0.62 这个猜出来的余量决定,名字比它长就直接跑到画布
外面——没有任何裁剪、也没有任何提示,只是墨迹落在框外。
修法(三层):
font-metrics.mjs新增rotatedDepth(width, angle, height):一段文字绕基点转 θ 后
在原竖直方向上占的深度是W·|cos θ| + H·|sin θ|。这个数是"标签还剩多少地方"
的度量,也是折行预算与旋转之间唯一正确的换算。svgio.mjs:textSpanLines增加rotate——transform 加在父<text>上,而不是
<tspan>上。这不是选择:光栅化器把<text>的 transform 应用到它排出的每一行,而
对<tspan>自带的 transform 是计入 unsupported 并上报(不猜位置)。wrapTextLines
增加depth/angle,把深度预算按1/cos θ换算回基线宽度。phylo.mjs的径向分支:折行 → 以锚点自身为旋转中心旋转整块 → 用四个角是否都
落在绘图矩形内做精确判定 → 不满足就逐级缩小字号。半径不再是面板的固定比例,而是
由实测的标签深度反推(halfExtent - deepestLabelDepth)。
实现时才暴露的四个偏差(计划里没有的,每一个都是量出来的、不是想出来的):
- 余量公式的坐标系搞错了。"标签沿半径还有多少地方"是旋转后局部 x 轴上的参数,
不是页面上的像素距离;把两者混着算,就会在接近竖直的方向算出 1.4 px 而真实答案是
149 px——于是每个折行预算都被夹到下限,长名字变成 6 px 的窄列。 axisDistanceToEdge的分子写反了:dy < 0(朝上)时用了下边界的距离。这条
与上面那条同源,症状也一样。- 折行宽度不能随角度变。试过三种角度相关规则,全部实测失败并被记录在代码注释里:
按深度预算折(有的角度窄列、有的角度长行)、按射线余量折(竖直方向射线极长,50 字符
名字折成九行柱)、以及最小化W·|cos θ| + H·|sin θ|(数学上的极值,也是窄列)。
眼睛要的是每个角度都差不多长的几行,所以宽度是固定的,随角度变的只是行数,
由角点判定与缩字循环处理。 - 旋转文字的墨迹比几何宽度宽约 1 px。光栅化器用
max(0.7, size·0.11)宽的圆角描边
画字形,所以预算必须再留strokeMargin + LABEL_INSET——这是逐标签量像素发现的,
靠推公式发现不了。
回归守卫(test/svgpng.mjs,22 → 23 项):12 个叶、50 字符名字的树,断言每个标签都是
多行 <tspan> 块、每行共享同一个基线 x、非水平方向的标签真的带 rotate 与正确的
anchor,并且没有任何标签墨迹进入面板边框带——后者与"只有面板装饰"的同结构文档比对,
所以标题和边框不会被误当标签。这条守卫能证明自己会失败:把 anchor 全部强制成
start(v19 的症状)即红。同一批改动里还试过"关掉缩字循环"与"去掉余量内缩"两种变异,
前者不红——因为 12 个叶的 fixture 在 11 px 本来就放得下——于是把 fixture 从 8 个叶改成
12 个,并改用请求 15 px 的对照来钉住"能放下就不缩"这一侧。
2. 新增 molbio_alignment_postprocess:比对之后该问的四件事(roadmap 2.2)
新模块 align-post.mjs(纯计算、零依赖,IUPAC 一律从 lib.mjs 取,不新增第八份字母表):
- 共识,三种口径由
consensus_mode选:plurality(默认,得票达
consensus_threshold就给该残基,否则给观察到的碱基的并集码)、union(一律并集码)、
strict(达阈值给残基,否则给N——混合位点绝不能伪装成一个碱基)。平票取字母序
首个,与conservationAnalysis一致,是确定性约定而不是并集码。 - 按缺口比例修剪:
max_gap_fraction(默认 0.5)以上的列被报出,列号是修剪前的
原始 1-based 坐标,所以与输入仍然对得上;只报"哪些列会走、还剩多少列",
不返回修剪后的比对。 - 两两同一性矩阵:分母是"两行中至少一行有残基"的列,双缺口列被排除。
- 逐列覆盖度:非缺口比例、均值/最小值、完全覆盖列数、低于
coverage_threshold
(默认 0.8)的列清单。
为什么另起一个工具而不是扩 molbio_conservation:其中两件事需要 msa.mjs 刻意
没有的语义,改动它就会悄悄挪动已发布版本报出的数字。文档与渲染文本都点名了这一点:
| 统计量 | 本工具 | molbio_conservation |
|---|---|---|
| 同一性分母 | 非双缺口列 | 全部列 |
| 同一 fixture 的均值 | 37.17% | 32% |
| 逐列 identity 均值 | —— | 70.34%(第三个不同的统计量) |
test/smoke.mjs 把这三个数并排钉住,附逐对手算表(1v2 80、1v3 60、1v4 40、2v3 75、
3v4 66.67…),因此"两套口径合并"会红。超 50 条序列时矩阵省略而非截断,摘要仍覆盖每一对。
benchmark:新增 alignment-post 任务(56 题,58/58 覆盖)。它刻意钉住两个阈值互相
独立:fixture 里 103 列低于 0.8 覆盖度,但缺口比例只有 0.25,默认阈值下一列都不修剪
——把两者混为一谈的模型会报出一个被修剪的比对。期望值全部由 benchmark/_probe.mjs 实测,
两条离线门(--offline 与 benchmark-coverage)全绿。
4. 持续交付(CD):推一个 tag 就发完
首发必须手动做一次,这是 npm 可信发布的固有限制,不是配置错误。 推 tag 跑 CD 会以
npm error code ENEEDAUTH/need auth失败——Trusted Publisher 是逐包在 npmjs.com 上
配置的,包名还不存在时 npm 没有可认证的对象。0.19.0 的第一次真实运行就是这样失败的。
更值得记的是当时的 preflight 已经检测出"包不存在",却只打印一条 note 就放行:一个检测到
条件却不据此行动的守卫不是守卫,只是一行日志。现在publishPlan()把每条注册表状态落成
publish/skip/refuse三个动作,首发与"npm 已有更新版本"都是refuse,workflow 的发布
步骤读steps.preflight.outputs.publish_*,于是在碰注册表之前停下并打印准确的首发步骤。
完整顺序(含必要时如何安全地强制移动 tag)见 workflow.md 第 4 节。
.github/workflows/release.yml 把「发布流程」第 4 节那套手工步骤变成流水线。触发条件是推
v* tag,不是推 main——版本号就是"这是一次发布"的决定,而 tag 是唯一不可能被顺手推出去的东西。
流水线在被打 tag 的那个提交上重跑全套 npm test(tag 可以指向任何提交,而 test.yml 是被
并行触发的,所以"测试过了"不是这个 job 能假设的前提),再过发布前置检查,然后分两步发布
两个 npm 条目(在仓库根跑两次 npm publish 会把根包发两遍——这是 v20 已经记录过的坑),
最后把 CHANGELOG 里该版本那一段抽出来建成 GitHub Release。
凭据是没有凭据:用 npm 的 Trusted Publishing——job 出示 GitHub OIDC token
(id-token: write),npm 换成一张限于本仓库 + release.yml 的短期凭证。仓库里不存在任何
长期密钥。代价是 npm 必须先知道这个包,所以两个包的首次发布必须由人手动做一次,然后在
npmjs.com 上为每个包配置 Trusted Publisher。三条坑都写进了守卫或文档:
- 设了
NODE_AUTH_TOKEN反而会关掉可信发布(npm 见到 token 就不走 OIDC),所以 workflow
故意不设它,test/release.mjs直接断言文件里没有对它的赋值、也没有任何secrets.引用; - 首发时
npm publish的报错是 404,看不出真正原因——所以release-preflight.mjs先问注册表
"这个包存在吗",包不存在就明确说"这是首发,必须手动做一次"; - 面板包的版本线是独立的(v20 计划:
0.1.1 → 0.1.2),但两者共用同一份客户端源码,所以
浏览器半一改面板产物就变,版本不动就会被 npm 以"同版本不可重复发布"拒绝。preflight 因此把
已发布版本的lib/client.js通过注册表 tarball 抓下来,在进程内解 tar,与工作区逐字节比对。
新增 build/release-preflight.mjs(只检查只有发布时才会问的问题;产物新鲜度与 files
白名单已经在 test/contract.mjs 里,不重复)、build/release-notes.mjs(发布说明从 CHANGELOG
派生,不存在第二份描述)与 test/release.mjs(把每个决定函数用变异输入驱动:tag 写错、
版本漂移、CHANGELOG 缺段、面板版本没 bump、tar 读错文件、根变成 workspace、workflow 里出现
token——见「守卫必须被证明会失败」)。
刻意不做:自动版本号(版本是人的决定)、自动回滚(npm 的 unpublish 有 72 小时窗口且会
破坏已装用户;正确做法是发更高版本,必要时 npm deprecate 标记坏版本)。
3. 文档漂移与平台(0.18.4 的同类,本次一并结清)
font-metrics.mjs被两处现行规则文档写成"宿主侧专用、不进客户端产物"——它明明在产物里。
build/client-bundle.mjs --check列出它在 19 个模块之中,test/contract.mjs断言它
必须在模块图里,而它自己的头注释就写着"This module IS part of the client bundle"。
已改正docs/rules.md第 5 节(并写明照错的声明去"修好"它就会删掉一个面板必需的模块)、
docs/maintainer.md与README.md的目录树。历史文档(CHANGELOG、v19-plan、
v20-plan)保持原样。docs/maintainer.md的包版本0.18.3→0.19.0。- CI 的 node 矩阵、
engines.node与该处注释三处一致,都是 26(本机开发版本),
不再是"矩阵测下限、注释说下限、engines 写下限"的三方各说各话。 - 工具数变化同步的四处(README 表与计数、
preset.yml描述、index.mjs顶部注释、
agent.cordis.yml的tool-molbio注释)与 benchmark 任务数(55 → 56)一并更新。
npm entries published by this release
dsh-molbio-tools@0.19.0—npm install -g dsh-molbio-toolsdsh-molbio-panel@0.1.2(the panel package; its version line is independent of the root package) —npm install -g dsh-molbio-panel
Install in the harness — dsh plugin --profile <profile> add dsh-molbio-tools; the tools load only in the Molecular Biology Lab mode. See README.md for the panel-only package and the manual install path.
Full entry — CHANGELOG.md.
v0.7.3 — 真机验证:面板与图谱调用卡在页面里确认可用
内容
本版本没有产品改动,只增加两件东西:真机验证的记录,以及做这次验证的工具。
build/cdp.mjs:最小的 Chrome DevTools Protocol 驱动(导航 / 求值 / 截图),
基于 Node 自带的全局 WebSocket——本仓库没有浏览器自动化依赖,而"页面里到底长什么样"
是任何 Node 侧测试都回答不了的问题。build/cdp-drive.mjs:把提示词真正打进输入框、发送、等到这一轮结束,再回报对话内容。
一个坑值得记下来:composer 是contenteditable的 div,textContent = …不会被
React 看到(发送按钮会一直是禁用的),必须走document.execCommand('insertText')。
真机验证结论(专用 profile:dsh-base + dsh-web-app + dsh-molbio-tools,独立实例与 token)
- 启动正常:页面 boot 成功,boot graph 含本包,没有
Failed to load plugins
——即 v0.7.1 的故障面不复现; - 图谱调用卡:驱动一次真实对话调用
molbio_plasmid_map,卡片在对话里画出图谱——
摘要pCARD-LIVE · 212 bp · circular · 1 feature(s) · 2 cut mark(s)、
1 个viewBox="0 0 840 840"的 SVG(520×520 渲染)、以及写入路径; - 右栏面板:引导页出现 Molbio 与 Papers 两个胶囊及其描述。
工具数量不变(46 个);客户端产物与 v0.7.2 逐字节相同。
安装请使用 v0.7.3(或 v0.7.2)。不要使用 v0.7.1:它会让 Web GUI 拒绝启动。
v0.7.1 — ⚠️ DEPRECATED(会弄坏 Web GUI 启动,请用 v0.7.2)
Warning
不要安装这个版本。 v0.7.1 会让整个 DSH Web GUI 拒绝启动(顶栏 HARNESS / Failed to load plugins)。
请使用 v0.7.2 或更新版本。
出了什么问题
v0.7.1 为图谱调用卡注册 tool.call.toolview 座位时用了裸 slots.register。该座位是
dsh-client-ui-tool 的 conversation.chat.node entry 声明的子座位,只在其声明之后才存在;
本包与那个包的 entry 之间没有加载顺序保证,于是注册早于声明,抛出:
slot "tool.call.toolview" is not declared (a parent entry's children table must declare it)
异常从插件浏览器半的 apply() 逃出 → 客户端加载器把该 entry 标为失败 → shell 中止 boot,
整个 Web GUI 无法启动。工具侧(46 个 molbio 工具)不受影响;从 profile 里移除该插件行并重启即可恢复。
修复
v0.7.2(提交 aba2c0b)把全部座位注册改为
ctx.effect(() => ctx.slots.inject(座位, () => ctx.slots.register({ name: 座位, … }, 组件)), 标签),
即官方所有客户端包使用的"先等声明、再注册"形状。
防复发(0.7.2 起生效)
test/slots-stub.mjs:复刻 shell 的 SlotCore 守卫与inject的等待/重放语义;test/client.mjs:从零座位已声明的最坏顺序启动,断言apply()不抛、调用卡在 ui-tool 声明前保持等待;test/contract.mjs:第 10 项钉住"产物里每一处slots.register都落在对应座位的slots.inject里",
第 11 项(后续补)每次重建产物并比对哈希,堵住"改了源码忘重建产物"。
完整事故记录(症状/根因/定位过程/验证/防复发):仓库 D:\deepseek-harness\崩溃历史\2026-09-11-tool-call-toolview-seat-race.md。