Skip to content

Releases: HerTa-st/Herta-dsh

v0.1.11 —— 跟住 DSH 0.2.1,黑塔又能上工了

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 17 Oct 11:49

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,sha1 b39f82fbcdebea17a9d7bdc2fbde358964656729)。
  • 插件市场:条目已收录,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:integration 28 项全过。
  • 发布预检: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 —— 中转站语音能配了、静音点了就闭嘴

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 10 Oct 05:43

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,sha1 4e2aa9ed5c4481dcc8e6cc9d2643b6280807dfb9)。
  • 插件市场:条目已收录,DSH 内置市场里搜 herta 一键安装。
  • 升级提示:从 0.1.9 升上来用同一条命令即可(npm latest 现在指 0.1.10),不需要卸载任何东西。

这一版的主体:中转站语音能配了

用户看到的现象:用第三方中转站的用户接不上云端语音 —— 密钥是好的、网络是通的、
中转站也是活的,但每一句都没声,日志里连中转站的痕迹都没有,看起来就像「静默回落本地」。

根因是三处写死加一处判据:

  1. MINIMAX_HOSTS 只有官方两条地址,没有任何配置入口;
  2. 认领按 isHertaVoiceId()(voiceId.includes("b1a43133"))过滤 —— 那个 tag 是作者账号上
    那个克隆
    的标记,中转站用户不可能有,于是过滤后为空、直接 fail("no_clone_key")
    (终局失败 + 10 分钟冷却);
  3. speech-2.8-hd 是常量,而中转站通常只认它自己清单里的模型名;
  4. 更隐蔽的一条:available() 要求「有 key 且 已认领克隆」—— 中转站用户前一件为真、
    后一件永远为假,于是 router 在 runAdapter() 里就早退了。

改法:四个可选字段,默认全空 = 与加它们之前逐字相同。

字段 作用
minimaxBaseUrl 中转站 / 自建网关地址
minimaxApi 请求形状(official / relay)
minimaxVoiceId 手填音色 id,填了就跳过「列克隆 + 筛 tag」整条路
minimaxModel 模型名,留空按形状各自的默认

请求形状收进新的 src/host/minimax/endpoint.ts:路径、请求体、响应解码、错误分类各一条
记录,api.ts 只认这个 interface(加第三种形状不用碰 synthesizePcm)。官方原生与中转站的
形状差异都是实测事实,逐条钉在文件头和测试里:

  • 路径 /v1/t2a_v2 vs /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:integration 28 项全过。
  • 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 的「黑塔」设置页白屏

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 05 Oct 06:55

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,sha1 06ce09d1c41b10c1e11e0695ca954df9d88ab247)。
  • 升级提示:从 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 —— 语音与界面各回各家

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 04 Oct 02:11

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,sha1 72ab2c43bebe5c7b4da8834e9edf032e2c8340e2)。
  • 升级提示:从 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

都是「全面检查」抓出来的,不属于那五个部分:

  1. 合成一失败,状态回执就断。 删旧代码时把紧邻的 reasonText 局部函数一起删了,
    而唯一调用点留着 —— 每次合成失败都抛 ReferenceError,被管线的 .catch 吞掉:
    试听没有回执、engineNote 不更新、noteState() 不执行,fish 档(按设计不回落)
    彻底静音。已补回,并给失败路径加了断言(此前单测全绿是因为没有一条测试
    走到失败路径
    )。
  2. 「没密钥」被静默说成「失败」。 SYNTH_CODES 漏了 no_key 这一项,
    而 codeOf() 那条判断求值成 undefined,又被兜底逻辑吞成 other。
    现在每档自报「我为什么用不了」(unavailableCode()):没密钥就是 no_key,
    被拒就是 refused,用户看到的那句话对得上实际原因。
  3. 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 —— 装完就有主题

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 03 Oct 14:53

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,sha1 102413a9a1c8f24799cd68fd54ee6cc265c1993b)。
  • 升级提示:从 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)的快照 —— 发布后次日才显示新版本号;
    安装始终取 npm latest,不受影响。
  • 条目内容(描述 / 工具数 / 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 —— 修掉「装完起不来」

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 02 Oct 12:37

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,sha1 732b063e44d97fe5aa42a38e9dbbbce8d06426a0)。
  • 升级提示: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 因此必须不带 --import hook
    (挂了 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)的快照 —— 发布后次日才显示新版本号;
    安装始终取 npm latest,不受影响。
  • 条目内容(描述 / 工具数 / owner / url)若有变化,另行提 PR。
  • 条目搬到新 owner 的 PR(awesome-dsh-plugin#6321)
    已于 2026-10-01 提交流程通过、等待合并 —— 它同时是「卡片上还没显示版本号」的原因:
    条目 url 指着旧 owner YUNmengyuan/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 语音接通 + 两处静默修复 + 设置页体检收口

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 01 Oct 14:48

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 插件清单规范收口

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 27 Sep 13:00

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 运行时

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 26 Sep 13:33

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 —— 叙述调度层 + 修复思考围栏外泄

Choose a tag to compare

@YUNmengyuan YUNmengyuan released this 19 Sep 15:04

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 的「验过 / 没验」表。