Repository navigation
Releases: HerTa-st/Herta-dsh
Release list
v0.1.11 —— 跟住 DSH 0.2.1,黑塔又能上工了
v0.1.11
DSH 0.2.1 把 {{cwd}} 从内置提示词变量里删了 —— 黑塔 preset 的 persona 后缀里还挂着那句
Your working directory is {{cwd}}.,新的插值是严格的,于是选黑塔 preset 的会话在发出任何
模型请求之前就抛错。这一版删掉它、给构建加了兜底,并把 preset 底本跟到官方 0.2.1
(工具清单随上游);同一版还接上了 MiMo 声音档。
面向 DSH 0.2.1-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.11(206 个文件 / 21.2 MB,sha1b39f82fbcdebea17a9d7bdc2fbde358964656729)。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。 - 升级提示:从 0.1.10 升上来用同一条命令即可(npm
latest现在指 0.1.11),不需要卸载任何东西。
这一版的主体:跟住 DSH 0.2.1
用户看到的现象:把桌面应用升到 DSH 0.2.1-rc.2 之后,只有黑塔 preset 起不来 ——
新建会话、选中「黑塔」,还没发出任何请求就直接报错;换成标准 preset 一切正常。
看起来像「插件挂了」,其实是宿主收紧了提示词插值。
根因:dsh-agent-loop 在 0.2.1-rc.2 只注册 provider / model 两个内置提示词变量
(0.2.0 时还有 cwd)。dsh-system-prompt 的插值是严格的:未注册引用会让组装直接抛错,
设计上「宁可明确失败,也不发格式错误的提示词」。黑塔 preset 的 persona 后缀末尾从 v0.1.5 起
就有 Your working directory is {{cwd}}.,于是组装在第一步就停下。官方 0.2.1 的
dsh-system-prompt/README.md 也明确要求:自定义 persona 模板不得依赖内置 {{cwd}},
并从 preset 的 suffix 里删掉这句话。
改法:
preset/herta.patch.yml的 suffix 删掉那一行;工作目录信息没有丢 —— 0.2.1 改由
working-directory 服务把目录作为 runtime context 注入(会话顶部那段
Current working directory: …就是它)。scripts/build-preset.mjs里那句「生成时追加 cwd 行」一并移除,并新增
assertPromptVariables():preset 里只允许{{provider}}/{{model}},
出现别的未注册变量时构建当场失败,不再等运行到会话才炸。- preset 底本换成官方 0.2.1-rc.2(逐字节导出,收进
scripts/baselines/):
工具清单随上游 —— 新增dsh-time-context(她终于知道当前时间了)、
dsh-tool-schedule、dsh-plugin-market/tools(后两个跟官方一样 disabled),
删掉 0.2.1 已移除的tool-subagent-codex/tool-subagent-claude-code/
tool-ralph行以及backgroundMode字段。
这一版的第二条线:MiMo 声音档接上了
此前 mimo 只是选择器里的一档空壳(状态行永远说「未接线」)。现在:
createMimoAdapter从空壳改成真转接(可用性、合成、取消都问mimo-tts.js的
synthesizer);宿主侧真正构造它 ——MIMO_API_KEY每次现读,参考音读一次就缓存
(那份是 174 秒的 wav,每次合成都重读一遍纯属浪费 I/O);mimo进了
SPEAKING_ENGINES。- 用不了的两种真原因分开报:
no_key(缺密钥)/no_reference(读不到参考音),
用户才知道该补哪一样,不再只有一句「未接线」。 - 参考音要 DataURL、要归一化成 24kHz 单声道 —— 这两条是实测出来的:不这么做
服务端回 400。key 支持环境变量与C:/herta-ai/mimokey.txt文件兜底;参考音按上游
Herta 桌面版的实际落点去找(装过上游应用的人自带那份素材)。 - 本机实测(真 key):合成成功 49920 样本 @ 24000 Hz、2.08 秒、往返约 10.3 秒。
验证
- 测试:
npm test链 31 组 1556 项全过、0 失败;npm run test:integration28 项全过。 - 发布预检:
node scripts/preflight-release.mjs八项全绿(版本号 / 工作区 / 测试 /
构建零差异 / 产物一致 / 打包内容 / 文案 / 宿主矩阵)。 - npm 制品核验:
dist.shasum = b39f82fbcdebea17a9d7bdc2fbde358964656729;
把 tag 那一刻的树重新打包与 registry 上那份逐文件比对一致(文本文件按 EOL 归一)。 - 部署核验:桌面 profile 里那份副本与仓库逐文件一致;修复后黑塔 preset 可正常组装。
仍未接 / 仍未决
- MiMo 缺 key 或参考音时只报原因、不会自己补。
- 主题
lib/opening/的上游授权未决。 - 「黑塔外观」那些值存在浏览器
localStorage,换机器不带走。 theme/deviceScene两个字段仍未接线(DSH 侧无消费方)。- SSE 没有断线重放:页面在回复中途刷新会丢掉已推的帧。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
本机那份 file: 安装只需重跑 deploy.mjs 后关窗重开。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.10 —— 中转站语音能配了、静音点了就闭嘴
v0.1.10
声音这条线一次收口 —— MiniMax 中转站可以配了、静音点了就闭嘴、语音工具条不再跟着滚;
外加两个「能跑但静默错」的真 bug:每一帧 tts 都在抛 ReferenceError(任何引擎都没声),
以及工具条那个静音键压根没接到音频链路上。
面向 DSH 0.2.0-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.10(206 个文件 / 21.2 MB,sha14e2aa9ed5c4481dcc8e6cc9d2643b6280807dfb9)。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。 - 升级提示:从 0.1.9 升上来用同一条命令即可(npm
latest现在指 0.1.10),不需要卸载任何东西。
这一版的主体:中转站语音能配了
用户看到的现象:用第三方中转站的用户接不上云端语音 —— 密钥是好的、网络是通的、
中转站也是活的,但每一句都没声,日志里连中转站的痕迹都没有,看起来就像「静默回落本地」。
根因是三处写死加一处判据:
MINIMAX_HOSTS只有官方两条地址,没有任何配置入口;- 认领按
isHertaVoiceId()(voiceId.includes("b1a43133"))过滤 —— 那个 tag 是作者账号上
那个克隆的标记,中转站用户不可能有,于是过滤后为空、直接fail("no_clone_key")
(终局失败 + 10 分钟冷却); speech-2.8-hd是常量,而中转站通常只认它自己清单里的模型名;- 更隐蔽的一条:
available()要求「有 key 且 已认领克隆」—— 中转站用户前一件为真、
后一件永远为假,于是 router 在runAdapter()里就早退了。
改法:四个可选字段,默认全空 = 与加它们之前逐字相同。
| 字段 | 作用 |
|---|---|
minimaxBaseUrl |
中转站 / 自建网关地址 |
minimaxApi |
请求形状(official / relay) |
minimaxVoiceId |
手填音色 id,填了就跳过「列克隆 + 筛 tag」整条路 |
minimaxModel |
模型名,留空按形状各自的默认 |
请求形状收进新的 src/host/minimax/endpoint.ts:路径、请求体、响应解码、错误分类各一条
记录,api.ts 只认这个 interface(加第三种形状不用碰 synthesizePcm)。官方原生与中转站的
形状差异都是实测事实,逐条钉在文件头和测试里:
- 路径
/v1/t2a_v2vs/v1/tts/speech; - 音色字段嵌套 vs 扁平(嵌套写法实测回 502);
- 回体是
data.audio(hex)vs 裸 s16le PCM,且回 PCM 时content-type: audio/mpeg
是假的 —— 所以判形状按字节,不按头。
「形状跟着地址定」是这一版最要紧的一条(shapeFor):地址是官方那两条 → 官方形状;
地址是用户自己填的第三方 → 中转站形状。它是四个字段里**唯一一个「不改也能填完其余三格」**的,
而症状是「看着连上了、每句都没声」—— 与音色无关,所以报上来像是「每个模型都连不上」。
实测矩阵(假中转站):形状漏配 → 官方路径打到中转站上回 404 → 静默无声;形状对了 → 出声。
同一趟挖出来并修掉的三个真问题:
codeOf()靠中文字符串猜 code —— 音色 id 填错时它归不进任何格子,界面只显示「失败」;
新增SYNTH_CODES.voiceMissing,并把平台的原因码原文留在那句中文里。classifyStatus认不出没有平台码的错误 —— 中转站回{"error":{"message":"invalid api key"}},
一个码都没有,于是「key 不对」既不显示也不进REFUSALS(会拿着同一把坏 key 把剩下每句都试一遍)。- 选了「中转站」形状却没填地址 —— 中转站形状的路径在官方站点上不存在,而 404 在
probeShape
里算「地址活着」(那是给中转站地址定的规矩),于是会「探通」一个根本发不出声的官方地址;
现在直接报no_host并说清缺哪一格。
另外新增 src/host/minimax/peak.ts:中转站回来的电平明显偏小(实测峰值 6231/32768),
所以只对明确偏轻的材料(峰值 < 0.5)放大到 0.99,本来够响的原样返回。
顺手修掉的(都是用户报的或一直红着的)
- 静音点了就闭嘴:原先静音只被两道「下一次」的门读(排下一段 / 合成前),
已经排进 WebAudio 时间轴的照播不误 —— 听感就是「等她念完这句」。
现在applyMuteNow()做三件事:增益归零(对已在播的源一样有效)、停掉已排的源并清游标、
给整机 iframe 推一条空 id 的ttsStop(既有协议里「停全部」的写法)。
解静音时按当前音量重算,不是简单置 1。没有suspend()AudioContext ——
那要等一次resume,而resume需要用户手势,静音一次反而把声音永久弄哑。
触发点接在设置表单的subscribe上(只转发真正变了的字段,且首次不发通知)。 - 工具条的静音键从来没接上音频链路:它只改了一个组件内
useState(只喂给按钮标签、
透明度、语气词判断),而真正拦声音的是设置里的voiceMuted。现在这个键写的是同一个真闸门,
并做了双向镜像(设置页改的、整机 iframe 改的,工具条都跟着变)。applyMuteNow()另记两条
诊断(minimaxMuteAppliedAt/minimaxGainNow),把「调用压根没发生」与「调用了但听不见」
分开。 - 语音工具条吸顶:那四个键原来长在会话流里,上下文一长就滚上去。现在
position: sticky+
一层不透明底板 +zIndex。底板取 DSH 的 base 令牌、不用backdrop-filter:后者在这个壳里
会强制新建合成层,滚动时和主题的背景图抢绘制(会抖)。 - 每一帧 tts 都在抛
ReferenceError:rememberSpokenAudio/awaitingSpokenTexts
在lib/client.js里两个调用点、零个定义 —— 是 0.1.8 拆 region 时漏的一行 import
(对照 0.1.7 产物:这个标识符从 3 次出现变 2 次)。症状是任何引擎的语音「点了没反应」,
而且连诊断标记都不留(抛在markMinimax("minimaxAudioPlays", …)之前)。 test-client-regions一直红着的那条:ui.ts里写的是./opus.js,磁盘上是opus.ts——
build.mjs的 jsToTs 插件认得(产物一直是对的),client-test-hook.mjs直连源码加载时不认,
于是整棵模块图解析失败。
新增的测试
scripts/test-client-bundle-bindings.mjs—— issue #14 点名要的「打包产物级」判据:
定义方必须export、使用方必须import、产物里必须有定义(不能只有调用点)。
判据成对、所以确定;不做通用静态分析(早先试过按模块边界做词法扫描,噪声大到不可用)。
验过不是空测试:摘掉voice.ts那行 import 当场变红 2 条。scripts/test-minimax-endpoint.mjs、test-minimax-voice.mjs第 13/14 节 —— 形状登记表、
路径与请求体、回体解码、错误分类、默认模型、峰值归一化,钉的都是实测事实。scripts/test-minimax-relay-config.mjs—— 假中转站 + 宿主真实装配的配置矩阵
(形状漏配 / 三格填对 / 音色 id 错 / 只填形状不填地址 / 全不填)。单层测试永远看不见这个故障
(每层自己都对),所以它测的是横跨几层的那件事。接进npm run test:minimax:e2e。scripts/test-voice-mute-instant.mjs—— 用注入的假表单驱动整条静音链。
产物重建(如实记一笔)
用 Herta-src(build.mjs 的默认来源)重建了 lib/client.js。与仓库里原来那份的差异只有两类:
- esbuild 的源路径注释 —— 原来记的是
../tools/_herta-src/…(这棵树在本机已经不存在),
现在是../Herta-src/…; - 上游 i18n 目录里多三条 MiMo 引擎的键(
voice.engine.mimo/voice.mimoKey/
voice.mimoKeyDesc)—— 当前代码里没有调用点,属随源码树带入。
没有别的行为差异(逐行核对过:lib/client.js 34 增 / 28 删 = 28 行源路径注释替换
- 6 行上游 i18n 键)。
验证
- 测试:
npm test链 31 组 1544 项全过、0 失败;npm run test:integration28 项全过。 - npm 制品核验:
dist.shasum = 4e2aa9ed5c4481dcc8e6cc9d2643b6280807dfb9;
把 tag 那一刻的树重新打包与 registry 上那份逐文件比对一致(文本文件按 EOL 归一)。 - 部署核验:桌面 profile 里那份副本与仓库逐文件一致。
仍未接 / 仍未决
mimo合成器仍无调用点:选择器里点得动、会如实说明为什么不发声(not_wired)。- 主题
lib/opening/的上游授权未决。 - 「黑塔外观」那些值存在浏览器
localStorage,换机器不带走。 theme/deviceScene两个字段仍未接线(DSH 侧无消费方)。- SSE 没有断线重放:页面在回复中途刷新会丢掉已推的帧。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
本机那份 file: 安装只需刷新页面即可(客户端 bundle 是页面加载时取的)。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.9 —— 修 v0.1.8 的「黑塔」设置页白屏
v0.1.9
设置页白屏修好了 —— v0.1.8 拆客户端时漏了三处 import,设置 ▸ 黑塔 那一页
打开是空的(本机与 npm 上那份都是)。这一版只有修复,没有新功能。
面向 DSH 0.2.0-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.9(204 个文件 / 21.2 MB,sha106ce09d1c41b10c1e11e0695ca954df9d88ab247)。 - 升级提示:从 0.1.8 升上来用同一条命令即可(npm
latest现在指 0.1.9),
不需要卸载任何东西;npm 上 0.1.8 已标记废弃。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。
这一版的主体:修 v0.1.8 的设置页白屏
用户看到的现象:打开 设置,左侧那栏「黑塔」在、也高亮,右边内容区一片空白 ——
连「正在读取设置…」都没有,背景里还能透出后面的聊天文字。
根因:b22ce9b(#2「拆出 settings 层」)把代码从 index.tsx 搬进
src/client/settings.ts 时,./machine.ts 与 ./voice.ts 的 import 没跟着搬过来。
于是 settings.ts 里有 13 个名字成了自由标识符(voiceModelState / miniMaxState /
voiceModelSubs / refreshVoiceModel / postVoiceModel / postMiniMaxAction /
subscribeMiniMax / miniMaxErrorText / miniMaxRetryText / credentialStatus /
saveCredential / clearCredential / subscribeCredentials),ui.ts 同样漏了 16 个、
voice.ts 漏了 1 个(machineField)。
esbuild 不会为未绑定的标识符报错(它假设那是全局),所以构建期一路静默;等设置快照
变成 ready、渲染走到「语音」那几行时抛 ReferenceError。DSH 0.2.0 的槽渲染器对每个
注册项有逐项错误边界(SlotErrorBoundary):抛错的那一项被降级成一个空的
<div data-slot-error="settings.section"> —— 而左侧导航的标签来自注册信息
(label: () => "黑塔"),不是来自组件。导航在、内容空,就是这么来的。
为什么测试全绿也没拦住:test-client-regions 只断言「能 import、导出的是接口」,
test-herta-settings 读的是源码文本 —— 没有任何一条测试真的渲染这一页;
而且 ReferenceError 只在子组件被调用时才出现(createElement 只是记下类型)。
修法:把三处 import 补回去。写 .ts 后缀(与文件里既有的 ./minimax-pcm.ts 同形)——
scripts/client-test-hook.mjs 不做 .js → .ts 映射,写成 ./machine.js 会把
test-client-regions 顶红(第一次修正是这么撞上的)。
验证
- 产物探针(新增,直接跑构建产物而不是源码):加载
lib/client.js,拿住注册进
settings.section的那个组件,用一份ready快照调它,再把树里所有子组件递归求值
一遍 —— 修前报 3 处ReferenceError(VoiceEngineRow/MiniMaxVoiceRow/
VoiceModelRow),修后全部通过;已部署到 desktop profile 的那份副本同样通过。 - 测试:
npm test链 29 组 1446 项全过、0 失败。 - 部署核验:桌面 profile 里那份副本与仓库逐文件一致(206 个文件,只有
lib/client.js一个内容变了)。
仍未接 / 仍未决
与 v0.1.8 相同,这一版没有动它们:
mimo合成器仍无调用点:选择器里点得动、会如实说明为什么不发声(not_wired)。- 主题
lib/opening/的上游授权未决。 - 「黑塔外观」那些值存在浏览器
localStorage,换机器不带走。 theme/deviceScene两个字段仍未接线(DSH 侧无消费方)。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
本机那份 file: 安装只需刷新页面即可(客户端 bundle 是页面加载时取的)。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.8 —— 语音与界面各回各家
v0.1.8
语音与界面各回各家 —— 架构审查的五个部分全部落地,顺手修掉三个真 bug
(其中一个是「合成一失败,状态回执就断」)。
面向 DSH 0.2.0-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.8(204 个文件 / 21.2 MB,sha172ab2c43bebe5c7b4da8834e9edf032e2c8340e2)。 - 升级提示:从 0.1.7 及更早升上来用同一条命令即可(取 npm
latest= 0.1.8),
不需要卸载任何东西。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。
这一版的主体:五个部分收口
来源是一次架构审查(candidate #1~#5)。五件事各自的验收标准都写在那份报告里,
下面按「改了什么、用户能感知到什么」写。
一、语音合成器只剩一个 interface(candidate #1)
新增 src/host/synth-registry.js:四档引擎(local / minimax / fish / mimo)
各是一个 adapter 工厂,name / available() / synthesize(req) → {audio, name, code}
是它们唯一的形状;选哪一档、失败要不要回落、取消转发给谁,全归一个 router。
原先压在 synthUnit 里的 95 行 if 链退休(synthUnitLegacy 已删)。
三条规则从此只有一处:只有 minimax → local 回落(fish 是用户明确选的档,
换个声音比没声音更糟);每次合成结束统一报一次状态;失败只给机器可读的
code,给用户看的中文在边界拼一次。
二、客户端 index.tsx 拆成四个 region(candidate #2)
| 文件 | 行数 | 管什么 |
|---|---|---|
index.tsx |
364 | 只做装配(installSettingsSection / apply + 全层共用常量) |
machine.ts |
278 | bridge / 凭据服务 / 机器字段 |
voice.ts |
708 | SSE / PCM / 播放 / 音色状态 |
ui.ts |
797 | 面板 / 视图 / 气泡 |
settings.ts |
1181 | 字段表 / 样式 / 各行 / 整个表单 |
opus.ts |
47 | .opus 播放那条路 |
拆分前是一个 3196 行的文件。四个 region 之间零依赖 —— 改 PCM 播放不必再碰
设置页的 diff。两条切分判据值得单独说:状态归它所在的层独占,外面只经显式入口
(两个外部写方改成 bindMachineForm / stopVoiceModelTimer,否则 esbuild 当场报
Cannot assign to import);断言看它该看的文件,不盯某个文件的字面。
三、测试从「抓源码文本」改成「穿过 interface」(candidate #3)
test-fish-proxy 那 11 条读源码文本的断言、test-fish-key 的几条,改成读值或调用接口;
test-subagent-skip.mjs 删除(三条断言已由运行时测试覆盖)。新增
scripts/test-client-regions.mjs —— 配套的 scripts/client-test-hook.mjs 把
react / react-dom/client / @gui/* 指到最小桩,于是未打包的客户端 module
可以被裸 Node import:客户端内部第一次有了可断言的行为,而不只是「bundle 之后只剩
一个 lib/client.js」。
四、引擎取值域收成一个声明(candidate #4)
四档的合法值与「会不会出声」各自只写一处(voice-engines.js):设置页的选项、
schema 的 enum、宿主的分发全部从它派生。回落规则也变成一张表 —— 表里没有的档
就是不回落,加第五档只需加一行。
五、叙述层的隐式 interface 收口(candidate #5)
依赖通道(host-deps.js:ctx / llm 两个槽)与 69 个诊断字段的总线
(host-marks.js)拆开 —— 在此之前它们挤在同一个 globalThis 对象里,外部无法区分
「谁写、何时就绪、哪些 key 合法」。子代理闸门收成一个 helper(三处调用)。
顺手修掉的三个真 bug
都是「全面检查」抓出来的,不属于那五个部分:
- 合成一失败,状态回执就断。 删旧代码时把紧邻的
reasonText局部函数一起删了,
而唯一调用点留着 —— 每次合成失败都抛ReferenceError,被管线的.catch吞掉:
试听没有回执、engineNote不更新、noteState()不执行,fish 档(按设计不回落)
彻底静音。已补回,并给失败路径加了断言(此前单测全绿是因为没有一条测试
走到失败路径)。 - 「没密钥」被静默说成「失败」。
SYNTH_CODES漏了no_key这一项,
而codeOf()那条判断求值成undefined,又被兜底逻辑吞成other。
现在每档自报「我为什么用不了」(unavailableCode()):没密钥就是no_key,
被拒就是refused,用户看到的那句话对得上实际原因。 - Fish 的文件兜底被掐死。 鱼档的可用性只认 DSH 凭据,于是
fish_config.json里那条 keyFile 兜底也被判成「不可用」。改问fish-tts.js
自己(判据是「密钥在不在」,不是「凭据在不在」)。
另外取消现在真的取消得到人:router 那句「转发给所有带 cancel 的 adapter」
此前转发给零个(四档都没实现这个可选方法,而 minimax 合成器的 cancel 一直存在)
—— 用户打断时在飞的云端请求不会被中断、继续计费。现在接上了,并用真工厂加了断言
(此前那组用的是假 adapter,只能证明「router 会转发」)。
测试
- 新增
scripts/test-synth-registry.mjs:router 的四档/回落/失败码/取消转发第一次有
行为覆盖(此前只有正则 grep 源码文本),并且专门有一组「每个 code 走完 router
都还有值」—— 防的就是上面第 2 类「属性名拼错只拿到 undefined」的静默降级。 - 全量:
npm test链 29 组 1446 项全过、0 失败。
仍未接 / 仍未决
mimo合成器仍无调用点:选择器里点得动、会如实说明为什么不发声(not_wired),
但填了密钥也不会出声。- 主题
lib/opening/的上游授权未决(PersonaCLI/Herta未标注标准许可证)。 - 「黑塔外观」那些值存在浏览器
localStorage,不是 profile 配置,换机器不带走;
要跨机器请用图片网址。 theme/deviceScene两个配置字段仍未接线:DSH 侧没有消费方,设置页那两行
写的是实话。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
之前单独装过
dsh-theme-herta的不用卸:两边是同一个包、同一个挂载点。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.7 —— 装完就有主题
v0.1.7
装完就有主题 —— 紫罗兰配色、开机 ASCII 开场、可换背景与「黑塔外观」设置页,
现在随插件一起装。
面向 DSH 0.1.7-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.7(202 个文件 / 21.2 MB,sha1102413a9a1c8f24799cd68fd54ee6cc265c1993b)。 - 升级提示:从 0.1.6 及更早升上来用同一条命令即可(取 npm
latest= 0.1.7),
不需要卸载任何东西。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。
这一版的主体:主题变成插件自带
主题包 dsh-theme-herta 已经发到 npm
(0.1.0);这一版把「装了插件就拿到主题」剩的三件事一次做完:
dependencies加dsh-theme-herta@^0.1.0,files加theme/;cordis.patch.yml里与id: herta并列插一条id: ui-theme-herta。
为什么 loader 行必须由本包给出:主题包自己的 cordis.patch.yml 不会被自动应用 ——
loader 只从 profile 的 dsh.profile.bundles 里挑 bundle 的 patch,而 dsh-theme-herta
只是本包的依赖,它的 patch 谁也不会去读,所以主题的挂载点只能在这里补上。
实测(dsh 0.2.0-rc.2):本包依赖装进 profile 后,装载器能按名字解析到 dsh-theme-herta、
宿主半侧挂载成功;即便 profile 的 bundles 里又单独收了主题,重复的 loader 条目 id
也只是被覆盖、不会炸启动。
主题给的是什么(细节见它自己的 README):
- 开机 ASCII 开场:全屏遮罩逐帧画(不是视频)。冷启动播一次(刷新不重播),
最长 15 秒强制收场,任何失败都会立刻撤掉遮罩。 - 配色令牌:整套深/浅两色
--dsw-alias-*,颜色取自角色原画量出来的那几档。
右上角那三个窗口键是 Windows 原生画的,网页盖不住 —— 主题能做的是让它们同色
(开机动画期间连同原生那条一起融掉)。 - 背景层:自带四张图(默认「魔女阳台」),也可以填图片网址、从本机导入一张、
用一个纯色,或者关掉。 - 「黑塔外观」设置页:挂在 DSH 设置的
settings.section上,换背景、调遮罩、
纯色预设色块 + 系统调色盘。
⚠️ 授权边界(未决,如实写在这里):主题包自己的NOTICE.md记着 ——
lib/opening/那 10 个文件移植自上游PersonaCLI/Herta,而那个上游
未标注标准许可证(GitHub 读作NOASSERTION/ Other)。也就是说不能假定它是
MIT 或任何通用条款:使用、再分发前请自行与上游确认,或只把lib/opening/
当作「本机自用」的部分。角色与美术素材的权利归米哈游,本项目仅限非商业同人使用。
顺手修掉的(都是主题接线后暴露出来的)
- 壁纸不再从左侧栏透出来:侧栏填充令牌是半透明的(
rgba(38,31,46,0.64),本意是
「面板浮在壁纸上」),但半透明底色本身挡不住壁纸,图案会从整栏、行、字里渗出来,
看着像重影。现在在那一栏下面垫一层等价的不透明底#261f2e(正好是那个 rgba 的
不透明形式,颜色不变)—— 令牌的透明度对着不透明底算,结果就是纯色。 - 「选项高光叠了两层」:鼠标停在选中行里时,侧栏底(0.64)× 选中行(0.16)×
一条后代悬停通配兜底(0.10)三层半透明相乘,壁纸从三层里渗出来,就成了重影。
填充令牌改回不透明,那条通配悬停兜底删掉 —— 悬停/选中本来就归
--dsw-specific-sidebar-nav-item-*三个令牌管。 - Windows 下品牌字被「收起侧边栏」按钮压住:DSH 0.2.0-rc.2 在 Windows 原生命中栏
那条支路上,收起按钮是position:fixed;left:12px(28px 见方),而品牌行
flex:1且padding:0—— 两块从同一个 x 起(gap:8px是 flex 间距,对 fixed 子项
不起作用)。给品牌行让出 40px 左内边距;收起态有更高特异性的padding:0规则,
且收起态该行高度为 0,不受影响。 - 信息条上只剩 emoji 看得见:会话里那条提示没有底板、直接压在主题铺的壁纸上,
它继承来的墨色出自主题的:host{color:var(--ink)},而--ink只在宿主带
data-theme="dark"时才是浅色;主题那边是锁深色的(盯住data-ds-dark-theme,
却不碰color-scheme,碰了会改原生标题栏的绘制)。于是「DSH 偏好浅色 + 主题锁深色」
这一档里两层打架:底是深的、墨是深色的,整条只剩 emoji(emoji 的颜色由字体给,
不吃color)。现在这条直接取 DSH 的--dsw-alias-label-primary—— 它正是当前胜出的
那层色板的墨色,底与墨从此同源。 - 本地
file:安装也会镜像主题:scripts/deploy.mjs的镜像集补上theme/,
否则「包里有主题、本地开发环境却看不到主题」。镜像集与package.json的files
保持对齐(这条当初就踩过:漏项会被当成陈旧文件删掉)。
工程面(用户看不见,但决定了以后还改不改得动)
- 产物不再可手工修补(ADR-0003):
esbuild 固定 0.25.12(两条构建链都删掉候选列表,找不到就报错并指出修法,
不再「换台机器就产出不同字节」);删除 8 个reapply-*.mjs(它们靠字符串锚点给
bundle 打补丁,锚点对不上就「不猜,放弃」静默漏改);pre-commit 强制
「npm run build之后lib/无 diff」。代价写在 ADR 里:没有 esbuild 的机器不能出产物。 - 一个设置字段 = 改一个描述符(ADR-0004):
此前加一个字段要同时改五处(settings-schema.js的FIELDS、settings-groups.js
的组数组,加客户端四张ENUM_LABELS/FIELD_HINTS/ENGINE_*表),漏一处就是渲染事故
—— 09-27「字段漏进组 → 整组一行不渲染」与 10-01「摘出分组时两边名单不同步 → 渲染两遍」
两次都真发生过。现在展示元数据(group/hint/enumLabels/widget)住进字段描述符,
settings-groups.js整个删除;测试从「两张名单对账」换成「单源自洽」+ 三条防复发断言。 - 叙述层:依赖通道与诊断总线拆开:原先挤在同一个
globalThis对象里 ——
依赖(marks.ctx/marks.llm)与 69 个诊断字段,外部无法区分「谁写、何时就绪、
哪些 key 合法」。拆成host-deps.js(依赖通道,就绪时序写在文件头)与host-marks.js
(诊断总线,那个对象只有这一处创建)。顺带把{ kind: "plugin:dsh-herta" }
收成唯一常量、设置命名空间收成单源(此前宿主与客户端各写一份字面量,只靠注释提醒
「必须一致」,没有守卫,写错的症状是设置页空白 + 写入静默无效)。 - 叙述层第一次被运行时守住:新增
scripts/test-narrative-layer.mjs—— 用假 ctx
记录on(event, fn)的清单与顺序,然后真的调用那些 handler,钉住
「第 0 条是复核、第 1 条是护栏」「护栏读复核结论、被否决过就不注入」
「三处闸门对子代理让过路」;此前这条顺序契约只有正则 grep 在守。 - 两个零覆盖的叶子 module 补了接口测试:
test-http-json.mjs(413 之后不再写第二份
响应 —— 那是被 TCP RST 截掉的真事故;空体是{}、坏 JSON 是 400 等)、
test-tar-extract.mjs(坏校验和 / 非 ustar / 未知类型 / 超限 /safeEntryPath
的完整拒绝形态)。test-subagent-skip.mjs删除,它的三条断言已由上面那个
运行时测试覆盖 —— 测试不再 grep 源码,而是穿过 interface。
验证
- 测试:核心 26 组 1020 项 + MiniMax 6 组 390 项 = 1410 项全过、0 失败。
- npm 制品核验:
dist.shasum = 102413a9a1c8f24799cd68fd54ee6cc265c1993b;把 tag 那一刻的树重新打包与 registry 上那份
逐文件比对一致(文本文件按 EOL 归一)。 - 部署核验:桌面 profile 里那份副本与仓库逐文件一致。
仍未接 / 仍未决
- 主题
lib/opening/的上游授权:未决(见上,PersonaCLI/Herta未标注标准许可证)。 - 外观设置存在浏览器本地:换背景那些值在
localStorage,不是 profile 配置,
换机器不带走;要跨机器请用图片网址。 - MiMo 语音合成器仍无调用点:密钥写得进凭据存储,但合成器只读环境变量、全仓零调用点,
所以填了暂时不会发声。 theme/deviceScene两个配置字段仍未接线:经核实 DSH 侧没有消费方,
设置页里那两行写的是实话。- 本地合成(
voiceEngine: local)的常驻化:设计见docs/本地合成常驻化.md,未落地。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
之前单独装过
dsh-theme-herta的不用卸:两边是同一个包、同一个挂载点。
目录与上游状态(如实说明)
- 条目的
version是上游每日构建(北京 10:23)的快照 —— 发布后次日才显示新版本号;
安装始终取 npmlatest,不受影响。 - 条目内容(描述 / 工具数 / owner / url)若有变化,另行提 PR。
- 条目 url 指着旧 owner
YUNmengyuan/Herta-dsh,而 npm 包的repository指
HerTa-st/Herta-dsh;上游的 npm 关联探测要求两者对得上,所以卡片上至今是
npm: null/version: null。搬到新 owner 的 PR(awesome-dsh-plugin#6321)
已于 2026-10-01 提交流程通过、等待合并。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.6 —— 修掉「装完起不来」
v0.1.6
修掉「装完起不来」:0.1.5 在宿主不给 @deepseek-ai/schemastery 时整个插件导入失败。
面向 DSH 0.1.7-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.6(172 个文件 / 17.0 MB,sha1732b063e44d97fe5aa42a38e9dbbbce8d06426a0)。 - 升级提示:npm 上的 0.1.5 是坏的 —— 装完
herta起不来。请直接升到 0.1.6。 - 插件市场:条目已收录,DSH 内置市场里搜
herta一键安装。
这一版的主体:入口不再硬依赖运行时的 schemastery
症状(用户报的):
dsh: warning: 1 entry did not activate herta (dsh-herta): failed to import
根因:入口第 22 行是静态导入:
import z from "@deepseek-ai/schemastery";而本包不声明任何依赖(dependencies / peerDependencies 全空)—— 它指望 DSH 运行时把
那个包递过来。运行时不给时,模块在解析阶段就抛(ERR_MODULE_NOT_FOUND),而静态导入
没法用 try/catch 兜,于是整个插件起不来。不是配置错、也不是版本区间错,是导入方式本身。
改法:新增 src/host/schema-compat.js 做动态导入 + 兜底:
- 拿得到真库 → 行为与从前完全一致;
- 拿不到 → 退到一个宽容的最小实现(覆盖用到的
object/string/number/boolean/union
与default/min/max/volatile,未知方法名也返回可链式调用,且保证不是 thenable,
免得被await挂住)。它不做真校验:DSH 那边拿到的是宽容描述 —— 这比「整个插件起不来」好。 - 入口那行随之改成
import z from "./schema-compat.js";(相对路径,永远解析得到)。 settings-schema.js里的FIELDS(字段名与默认值)不受影响,仍是唯一真相。
回归守卫:新增 scripts/test-schema-optional.mjs,已挂进 npm test —— 它把 lib/ 复制到
临时目录,只给 dsh-tools / dsh-llm 造替身、故意不给 schemastery,然后要求入口能加载、
Config 与 name 都在、且 usingFallbackSchema === true(证明走的是兜底,而不是偷偷用了真库)。
把补丁撤掉即红(已验)。
顺手修掉的
- 产物双向漂移收口:
lib/client.js是打包产物、真源是src/client/index.tsx;此前几批体检
(reapply-ux-texts*.mjs、reapply-fish-proxy-field.mjs)只改了产物、没回填源码,于是
直接npm run build会静默回退 8 处已经生效的界面文案(实测重建后与 HEAD 差 35 行)。
本次按脚本记录的原文把 8 处回填进源码后重建,措辞一并收口:
「暂未接线」不再断言「没有任何代码读它们」(bridge 确实会读closeToTray/dreamEnabled)、
MiMo 密钥行说实话(那个合成器只读环境变量)、网盘行补「不含语音模型」、下载失败带上原因、
语音状态行显示「已计费 N 字」、「Fish 代理」那一行补行内提示;
并给voice/pipeline的内联类型补上billedCharsTotal/lastCap(此前只存在于产物里)。 - 测试守卫:
test-fish-proxy原先把「esbuild 的转义风格」当契约(重建后
label: "Fish 代理"变成label: "Fish \u4EE3\u7406"就误报),改成解码后再比,并补一条
「产物里行内提示还在」的守卫;test-schema-optional因此必须不带--importhook
(挂了 hook 等于把真库递进去,测的就不是兜底)。 - 发版工程:新增
RELEASING.md(一句话触发 + 12 步清单 + 已知坑表)与
scripts/preflight-release.mjs(发布前八项只读预检;--verify-published <ver>做发布后核验),
以及docs/templates/三份文案模板。预检照package.json的test链跑测试,不再写死清单 ——
新测试接进链上即自动覆盖。 - 跨会话纪律:新增
AGENTS.md(改动两步:动src/就重建产物、跑测试)与CONTEXT.md
(产品四层、分工四域、协作词汇)及两份 ADR。
验证
- 测试:核心 24 组 913 项 + MiniMax 6 组 390 项 = 1303 项全过、0 失败。
- npm 制品核验:
dist.shasum = 732b063e44d97fe5aa42a38e9dbbbce8d06426a0;把 tag 那一刻的
树重新打包与 registry 上那份逐文件比对一致(文本文件按 EOL 归一)。 - 部署核验:桌面 profile 里那份副本与仓库逐文件一致。
仍未接
- MiMo 语音合成器仍无调用点:设置页那一行现在写的是实话 —— 密钥写得进凭据存储,
但合成器只读环境变量、全仓零调用点,所以填了暂时不会发声。 theme/deviceScene仍未接线:经核实 DSH 侧没有消费方,已从活字段降为「暂未接线」
并各写清原因。- 本地合成(
voiceEngine: local)的常驻化:设计见docs/本地合成常驻化.md,未落地。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
已经装着坏掉的 0.1.5 的:同一条命令即可(取 npm
latest= 0.1.6)。
目录与上游状态(如实说明)
- 条目的
version是上游每日构建(北京 10:23)的快照 —— 发布后次日才显示新版本号;
安装始终取 npmlatest,不受影响。 - 条目内容(描述 / 工具数 / owner / url)若有变化,另行提 PR。
- 条目搬到新 owner 的 PR(awesome-dsh-plugin#6321)
已于 2026-10-01 提交流程通过、等待合并 —— 它同时是「卡片上还没显示版本号」的原因:
条目 url 指着旧 ownerYUNmengyuan/Herta-dsh,而 npm 包的repository指HerTa-st/Herta-dsh,
上游的 npm 关联探测要求两者对得上,所以条目至今是npm: null/version: null。
来源与素材声明
这一段每版都要原样保留(第三方同人素材的声明义务,见
NOTICE.md/THIRD-PARTY.md)。
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.5 —— Fish 语音接通 + 两处静默修复 + 设置页体检收口
v0.1.5
让她真的出声,而且每一处「没声」都说得清原因 —— Fish 引擎补齐成可用的一档,
两条静默路径修掉,叙述层不再对子代理说话,设置页按体检结果收口。
面向 DSH 0.1.7-rc.2(engines.dsh 区间 >=0.1.7-rc.1 <0.3.0-0)。
- 发布到 npm:
dsh-herta@0.1.5(171 个文件 / 17.8 MB),dist-tags.latest已指向它。 - 插件市场已收录:条目 2026-09-29 进上游精选目录(PR #5946 合并),
DSH 内置市场里搜herta一键安装即可。
这一版的主体:Fish 语音(新的一档)
voiceEngine 取值域从 ["local", "minimax"] 扩到 ["local", "minimax", "fish", "mimo"],
这一版把 fish 打通:
- 密钥走凭据缝:
FISH_API_KEY由 DSH 凭据存储供给,C:/herta-ai/fish_key.txt
退化成「没填凭据时的兜底」,不再有第二份真相。 - 接口按可达性排序(这一版的关键修复):
fishaudio.org(官方现用域名,国内可直连)
优先,api.fish.audio(旧域名,国内会被按 SNI 重置 —— 同一个 Cloudflare IP 上只换 SNI
即可复现:registry.npmjs.org返 200、api.fish.audio直接 RST)兜底;两条共享同一个超时
预算,失败时把服务端返回的code记进日志,而不是只剩「不出声」。 - 「Fish 代理」可以留空:只有两条接口都不通时才需要填。代理不再写死本机端口;
空串不会冲掉fish_config.json里的值(那正是「填了密钥也不出声」的根因)。 ⚠️ 音色 id 跟域名绑定:默认「大黑塔」用的是新域名下的 id
(36e4d5f5-7654-43d4-b160-a1f15398116f);旧域名那个 id 在新域名上会返回
ERR_VOICE_NOT_FOUND。手动换域名时,「Fish 音色」那一行要跟着换。- 计费字符数收下并显示到语音状态行 —— 「这一档到底花了多少」第一次有了答案。
两条静默路径修掉
- 围栏之外的字是说话:以前「正常发言 + 内心话」会被整段吞掉,只剩静默。
- 空轮护栏:一轮什么都没说时注入一条看得见的通知(生产上已实测到它唯一的一次触发)。
叙述层对子代理让过路
分拍 / 复核 / 空轮护栏一律不拦子代理 —— 三处钩子都问在动作之前,每次跳过都留痕
(subagentSkip 进诊断)。
设置页体检(两批)与产线收口
- 记忆能取下、失败说人话、做梦账本不丢、模型下载失败说出原因、计费字符上状态行。
theme与deviceScene经核实在 DSH 侧没有消费方,从活字段降为「暂未接线」并各写清
原因。这一版顺手修掉它们渲染两遍的问题:页面同时遍历SETTINGS_GROUPS与
UNWIRED_FIELD_NAMES,两个名单必须互斥(源码 +lib/client.js产物 + 断言三处一起收口,
新增scripts/reapply-unwired-groups.mjs记录产物改法)。fishProxy去掉只有「未接线」才该有的note(那是本仓wired/note约定的另一半)。- 产物一致性上测试:
test-artifact-sync.mjs守着「src/host的每个文件与lib/逐字节相同」,
也守着「发布目录里不许留补丁脚本的.bak备份」——后者曾把一个 511 KB 的备份带进 npm 包。 - 每个工具的「形状声明必须覆盖它的返回值」;
lib/minimax/*.js与 TS 源码重新对齐。 scripts/deploy.mjs加--no-build(本机没有 esbuild 时只镜像),并把镜像集与
package.json的files对齐、陈旧清理限定在镜像集内 —— 否则一次部署会把locale、
LICENSE、NOTICE.md、THIRD-PARTY.md当陈旧文件删掉。
验证
- 测试:核心 23 组 906 项 + MiniMax 6 组 390 项 = 1296 项全过、0 失败。
- npm 制品核验:
dsh-herta@0.1.5,dist.shasum = aba781c1cc47dd9fe764f15d26636ca16cfebc76;
重新下载 registry 上的 tarball 复核 SHA1 一致、171 个文件、0 个.bak泄漏,
preset/herta.patch.yml与lib/index.js在位。 - 部署核验:桌面 profile 里那份副本 172 个文件与仓库逐字节一致。
仍未接
voiceEngine = local / mimo这两条(本地模型目前只服务herta_speak与 MiniMax 的回落;
mimo-tts.js仍未实例化)—— 这是决定,不是遗漏。- 记忆/语音工具仍未在真实会话里被逐个调用过(只验到进了工具表 + 纯逻辑单测)。
装
dsh plugin --profile desktop add dsh-herta # npm
或者直接在 DSH 内置插件市场里搜 herta 一键安装。
装完关掉桌面应用窗口、重新打开(dsh.profile.bundles 是启动期合成的,热重载不覆盖它)。
目录与上游状态(如实说明)
- 条目 2026-09-29 已收录(上游 PR #5946 合并)。市场搜索候选集只有那份上游目录
(2026-10-01 实测 4400 条),不含本机已装插件,也没有自定义目录源入口。 - 条目里
url仍是旧 owner(YUNmengyuan/Herta-dsh,301 正常重定向到HerTa-st/Herta-dsh);
已提上游 PR #6321 把它改成HerTa-st/Herta-dsh并同步重命名条目文件。 - 目录卡片的版本号由上游 CI 每日重建刷新;装到哪个版本以 npm 的
latest为准,
所以新版本发到 npm 后用户立刻能装到。 - 已知误报:上游能力扫描(
dsh-trust-check@0.1.13)给出红线
「uses plaintext http:// to www.xfa.org」—— 那是随包进来的 pdf.js 里的 XFA XML 命名空间
常量(同一张表里还有www.w3.org,后者已被扫描器的命名空间豁免覆盖),不是我们的网络请求。
已提 issue(dsh-trust-check#8)建议把www.xfa.org收进同一张豁免表。
来源与素材声明
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与
THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt;THIRD-PARTY.md已给出
对应源码的获得方式。
v0.1.4 —— MiniMax 语音接线 + 按 DSH 插件清单规范收口
v0.1.4
把 Herta 的 MiniMax 语音接通了,并把插件按 DSH 插件清单规范收口。面向 DSH 0.1.7-rc.2。
- 发布到 npm:
dsh-herta@0.1.4起,本插件同时发布到 npm(此前只在 GitHub 上)。 - 清单规范化:
dsh.manifestVersion、engines.dsh、locale/en.json+locale/zh.json,
并让法律声明随 npm 包一起分发。
这一版的主体:MiniMax 语音
从「设置里存得下密钥」到「她真的用那个声音说话」:
src/host/minimax/—— TS 移植件(api/segment/voice/synthesizer/pipeline/state),
单独一条编译缝scripts/build-minimax.mjs(用 Node 自带的stripTypeScriptTypes,不引 esbuild/tsc)。
其中segment.ts与上游真身做了 16164 次差分比对,0 处不一致。src/host/minimax-voice.js—— 宿主接线:凭据缝读密钥、启动认领 + 凭据变更重认领、
SSE/herta-minimax-events与状态端点/herta-minimax-state、按voiceEngine分发、
显式回落本地模型并在状态里写明回落原因。- 新工具
herta_say(用她自己那个克隆声音说一句):既是调试入口,也是语音链路的验收通道 ——
端到端哑掉时,先用它把「合成」与「推给界面」两段分开看。
voiceEngine与realtimeVoice从wired: false翻成wired: true。 src/client/minimax-pcm.ts—— 客户端 PCM 解码与播放状态机:乱序到达、重复 seq 丢弃、
新句打断旧句、迟到帧不复活、ttsStop只封锁那一条。- 密钥走凭据缝:
MINIMAX_API_KEY/MINIMAX_PLAN_API_KEY。没填就回落本地,
状态里说清是哪一种 unavailable。realtimeVoice是自动念回复的总开关(关掉省钱),
herta_say是明确要求说一句,不受它管。
按 DSH 插件清单规范收口
| 补了什么 | 为什么 |
|---|---|
dsh.manifestVersion: 1 |
清单格式标识,与包版本、会话格式版本无关 |
engines.dsh: ">=0.1.7-rc.1 <0.2.0-0" |
作者声明的兼容区间。必须带显式下界 —— 写宽区间时 0.1.7-rc.2 这类预发布版会被 semver 静默排除;插件市场的「按宿主发现」读的就是它 |
locale/en.json + locale/zh.json |
形状 { "meta": { "title", "description" } }:插件管理页与市场卡片不激活插件也能读到本地化标题与简介;配套 exports 补 ./locale/*.json |
files 补 NOTICE.md / THIRD-PARTY.md |
npm 包会带上 assets/voice/(80 条语音)与 preset/herta.patch.yml,这些第三方同人素材的法律声明必须随包一起走。TTS 运行时的许可原文本来就在 assets/tts-runtime/LICENSES/(含 espeak-ng 的 GPL-3.0 全文),随 assets 一起发 |
description 中英双语、keywords、publishConfig.access |
npm 侧的检索与发布元数据 |
刻意没加 peerDependencies。 规范允许「engines.dsh」与「同版本线 @deepseek-ai/dsh-*
peer 声明」二选一,市场两者都读。这里只声明 engines.dsh:本包当前安装形态是 file: 本地路径,
加 peer 会让 pnpm 在安装时去解析一批宿主包,收益与 engines.dsh 重合、风险不重合。
顺手修掉的
- 脚本里写死的本机绝对路径:
build.mjs的HERTA_SRC默认值改成同级相对路径
(resolve(root, "..", "Herta-src"),解析结果与原硬编码值相同);新增的
test-minimax-deployed.mjs里那个写死C:\Users\<本机用户名>\...的 profile 目录,
改成从DSH_HOME/DSH_PROFILE推导。 - 客户端工作区路径输入框的占位符不再显示作者本机路径。
.gitignore收_minimax-*.wav(测试落地的音频样本,不是交付物)。
验证
npm test= 20 组 1059 项全过、0 失败。
14 组逻辑用例 671 项(narrative 31 / dream 28 / mapping 41 / narrative-hints 54 /
supervisor 81 / session-surface 32 / beat-policy 61 / dream-distill 55 / mimo-tts 40 /
voice-settings 51 / voice-model 50 / tts-resident 39 / herta-settings 101 / source-kind 7)+
MiniMax 那 6 组 388 项(api 98 / segment 112 / voice+state 43 / synthesizer 37 /
pipeline 43 / pcm 55)。lib/与src/逐字节一致:重跑npm run build与npm run build:ui后,
产物 0 处差异(46 + 8 个文件)。npm pack=dsh-herta@0.1.4,167 个文件 / 17.7 MB,含locale/、
NOTICE.md、THIRD-PARTY.md与assets/tts-runtime/LICENSES/。- 运行时侧实测:插件行
include:herta在 profile 里带着投影出的 Config schema。
仍未接
voiceEngine = local / mimo这两条(本地模型目前只服务herta_speak与上面那条回落;
mimo-tts.js仍未实例化)—— 这是决定,不是遗漏。- 五个记忆/语音工具仍未在真实会话里被逐个调用过(只验到进了工具表 + 纯逻辑单测);
herta_say的合成链路有真机冒烟脚本,但「自动念回复」那一跳尚未在真实对话里走完。
装
# 插件市场(dshmarket)里搜 herta —— 需上游目录条目先合并,见下
dsh plugin --profile desktop add dsh-herta # npm
plugin_manager install_bundle file:<本仓库绝对路径> # 本地路径
然后关掉桌面应用窗口,重新打开(dsh.profile.bundles 是启动期合成的)。
目录收录状态(如实说明)
条目已提给上游精选目录 awesome-dsh-plugin
(PR #5946,状态:open / 待维护者合并)。市场的搜索候选集只有该目录的
plugins.json(由上游 CI 每日重建),不含本机已装插件,也没有自定义目录源入口 ——
所以 file: 装进来的插件不会出现在搜索结果里。
因此「能在 dshmarket 里搜到」以该 PR 合并为前提,不在本仓库的控制范围内。
本版做的是:把条目内容改正(工具数 5 → 6)并同步 v0.1.4 的实际能力,
并用市场自身的搜索代码在本机对「目录含此条目」的候选快照做过可搜到验证。
来源与素材声明
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)
的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、
Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及
各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:
仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与
THIRD-PARTY.md。
⚠️ assets/tts-runtime/里的 espeak-ng 是 GPL-3.0-or-later 且静态链接,许可原文随包
分发在assets/tts-runtime/LICENSES/espeak-ng-LICENSE.txt。
v0.1.3 —— 兼容 DSH 0.1.7-rc.2 + 随包本地 TTS 运行时
v0.1.3
面向 DSH 0.1.7-rc.2。 这一版把两批改动一起发布:
- 兼容性修复 —— README 里记在 v0.1.2 一节的内容,此前没有单独发过版。DSH 在 0.1.5 → 0.1.7 之间有两处破坏性 API 变更,旧版在 0.1.7 上是「界面能开、功能静默失效」;
- 本地 TTS 运行时随包分发与语音模型下载。
修的两处破坏性变更
1. preset 的载体变了
DSH 移除了 $DSH_HOME/.agent-presets/<name>/agent.cordis.yml 那套目录机制(运行时里已无任何代码引用 .agent-presets),preset 变成一条 @deepseek-ai/dsh-agent-preset loader 行。
本版改为生成 preset/herta.patch.yml,作为第二条 bundle patch 随插件一起装 —— dsh.bundle.patch 现在可以是数组,preset 不再需要单独安装,也不再有「preset 属于整个 home」那个副作用。
修之前的表现:--dump-config 一切正常,但「黑塔」这个 preset 根本不存在 —— 人设、记忆提示词段、五个工具、叙述调度层全部静默缺席。
2. 语音偏好的存放(设置域)变了
宿主 ctx.settings.register(ns, schema) 与客户端 settingsScope.bind({namespace}) 一起消失,换成基于插件 Config 的 SettingsForms/configForms,没有第三方命名空间入口。
本版改为自持:$DSH_HOME/dsh-herta-voice.json + 白名单端点 GET/PUT /herta-settings(body 上限 8 KB,只认 engine 与 realtimeVoice,非法字段被丢弃、非法 JSON 回 400、落盘走「临时文件 + rename」,读方看不到半截 JSON)。
修之前的表现:宿主打一行 settings.register is not a function,客户端那个 ctx.inject(["settingsScope"]) 永不回调 —— 面板能开能点,值永远是默认值。
其余用到的 API 在 0.1.7 上没变,实测可用:ctx.systemPrompt.section、ctx.tools.register、ctx.slots.inject/register、ctx.uiConversation.binding、ctx.on('agent/turn-stopping' | 'agent/error' | 'tools/result')、webServer.register({ kind: 'prefix' })。
新增
-
本地 TTS 运行时随包分发(
assets/tts-runtime/,22 MB:sherpa-onnx 1.13.6 + onnxruntime 1.27.1 + espeak-ng / piper-phonemize 的 fork,许可原文在LICENSES/)。宿主会真探测它(拉子进程把 addon 加载起来拿版本号,结果进程内缓存),探测通过才报runtime: true;设置面板把「下载模型」按钮与「实时语音」开关 gate 在这个标志上 —— 这两个标志都不是写死的。
合成跑在子进程里(src/host/tts-worker.cjs):sherpa 的 espeak 构建在 Windows 上处理不了非 ASCII 绝对路径,而本机路径里就有中文;顺带也避免了同步阻塞宿主、原生件崩溃拖死宿主。⚠️ espeak-ng 是 GPL-3.0-or-later 且静态链接,分发前请自行拍板 —— 见THIRD-PARTY.md。 -
本地语音模型的下载(
/herta-voice-model):归档参数从上游扒来并钉死在代码里(herta-best-e72/ 76,255,506 B / SHA-256,见src/host/tts-release.js),四段流程 —— 边下边算哈希(先比字节数、再比哈希)→ 解到最终目录旁边的.installing/(带解压炸弹上限,拒绝路径穿越)→ 拿 bundle 自带的manifest.json逐个文件比 size + SHA-256 → 前三段全过才rename就位。任何一段失败都不会留下半个可用的 bundle,已经装好的旧 bundle 在任何失败下都还活着。 -
插件图标
icon.png(384×384 / 173 KB):DSH 清单对图标有两条硬约束(≤256 KiB、必须位于 manifest 所在目录之内),原图两条都不满足,本版缩放重编码后在package.json里声明"icon";生成过程留在scripts/make-icon.py。 -
构建与测试脚本去掉全部写死的本机绝对路径:preset 底本改从
dsh-web-app/presets/standard.patch.yml取,探测不到会明确报错并告诉你该设哪个环境变量。
测试
本次发布前重跑 npm test:12 组纯逻辑用例,601 项全过、0 失败。
| 用例 | 项数 |
|---|---|
| test-narrative / test-dream / test-mapping | 31 / 28 / 41 |
| test-narrative-hints / test-supervisor / test-session-surface | 54 / 81 / 32 |
| test-beat-policy / test-dream-distill / test-mimo-tts | 61 / 55 / 40 |
| test-voice-settings / test-voice-model / test-herta-settings | 50 / 50 / 78 |
另有 LLM 路径的集成测试(28 项,用 mock 的 ctx.llm 把请求组装 / 流消费 / 判决解析 / 「失败一律放行」整条跑通):npm run test:integration。
安装
桌面应用的 DSH 运行时打包在 resources/app.asar 里(Electron 归档格式,普通 Node 读不到),走应用内置的插件管理器:
plugin_manager install_bundle file:<本仓库绝对路径>
然后关掉桌面应用窗口,重新打开 —— dsh.profile.bundles 的变更是启动期合成的,直接杀掉 dsh web 不会自动拉起。
有独立 DSH 安装时可以用命令行:dsh plugin --profile desktop add file:<本仓库绝对路径>;或用仓库里的 node scripts\install-web.mjs --profile desktop(前置检查 → 备份 → 装 → --dump-config 自校验,回滚用 --rollback)。
已知缺口
- 实时语音还差最后一跳:运行时与模型都是真的、引擎确实能合成(
scripts/test-tts-runtime.mjs用真实模型跑过:24 kHz / 5.08 s / 非静音),但「把她的回复自动送去合成、再推给整机 iframe 播放」还没接 —— 打开「实时语音」开关她暂时不会自己开口。 - 五个工具从未在真实会话里被调用过(只验到进了工具表 + 纯逻辑单测)。
- 叙述调度层的行为都在 lab 里用假模型服务实测过;真实 DeepSeek 的判断力与语气质量没验 —— 那需要真实凭据,且不属于代码正确性的范畴。
来源与素材声明
本项目是基于 Herta(https://www.herta-ai.com/#research,THE SELF THAT USES THE AGENT)的第三方改造,与原作者无任何隶属、合作或背书关系。插件代码(src/、lib/、scripts/、Cordis 配置)为本次改造新写,采用 MIT。
assets/voice/、preset/herta.patch.yml 与 icon.png 是第三方同人素材,权利归米哈游及各自所有者,按《崩坏:星穹铁道》同人衍生作品创作指引 V2.0 第三条放置法律声明后收录:仅限非商业使用,且不得作为独立素材包再分发。详见 NOTICE.md 与 THIRD-PARTY.md。
v0.1.1 —— 叙述调度层 + 修复思考围栏外泄
v0.1.1
新增:叙述调度层(方案 B)
把她的演员调度接到 DSH 的 agent loop 上 —— 不移植上游 1.6 MB 后端(DSH 就是那个后端):
(我 想)/(我 说)叙述语法(提示词资产逐字移植自上游narrative/)- 分拍:工具失败 / 验证跑完时她补一句点评
- thought tag:两种表面各自成段
- 自我收回:复核否决后先重想、再重说(rethink → respeak 两阶段)
- supervisor 复核:
agent/turn-stopping里一次独立复核 - 做梦蒸馏走 LLM:
herta_dream {distill:true},两阶段蒸馏 → 过门 → 落盘 → 记账
设计文档:docs/叙述调度层设计.md
修复
- 她的思考围栏被当成发言显示出来了(用户报):
mapping.js原来把 assistant 的整段文本无条件标成speech,没解析围栏,导致(我 想)…(/我 想)被原样发出。现已剥除,并有 10 项单测钉住。
可观测性
- 启动信标
$DSH_HOME/dsh-herta-narrative.json—— 让「叙述层在不在跑」变成可读证据(放行不留痕,光看会话记录分不出来) scripts/probe-session.mjs:解 zstd 多帧会话记录并检索特征
测试
- 314 项单元 + 28 项集成(用 mock
ctx.llm把 LLM 管道整条跑通) scripts/mock-llm-server.mjs:无 API Key 也能验真实链路
验证边界(如实说明)
五种行为都在 lab 用假模型服务实测过;真实 DeepSeek 的判断力与语气质量未验(需要真实凭据)。详见 README 的「验过 / 没验」表。