Vai al contenuto principale
L

dsh-status-chime

lijiawei255/dsh-status-chime

Speaks a short status line when DSH changes state — turn done, turn error, job done, job failed, goal complete, goal blocked, an approval waiting, or the agent needs your answer — using eight pre-recorded clips in Chinese (default) and English. The audio is played by the host process through ffplay, or through the Windows PowerShell player that ships with the OS, so it does not depend on a Web Audio tab. Windows only.

Installazione

dsh plugin --profile web add github:lijiawei255/dsh-status-chime

README

dsh-status-chime

中文 | English

给 DeepSeek Harness 加上「会说话的状态提示」:任务跑完、出错、后台任务结束、需要你回答时,用声音告诉你,而不是只让任务栏图标闪一下。

platform license Awesome DSH Plugin


为什么做这个

DSH 自带的通知是视觉的:任务栏图标闪烁 + 系统气泡。这类提示有一个隐含前提——你得正在看着屏幕

但真正需要被通知的时刻,往往正是你不在屏幕前的时候:

  • 一个跑了十几分钟的任务终于结束
  • 后台任务失败,而你已经切去干别的了
  • 代理停下来等你回答一个问题,却在原地等了半小时
  • 目标被卡住,需要你介入才能继续

于是提示被错过,时间被浪费。换成声音之后,「必须盯着」就变成了「听得见就行」——你可以去泡杯茶、看会儿书、或者在另一台机器上干活。

装好即用,不需要配置任何东西。

八个场景

时长是刻意设计的:长 = 有事需要你,短 = 有事情结束了

准确地说,它是三个带,而不是「每一条都比下一条长一截」:

时长范围场景
失败 / 受阻 —— 最需要你4.8 – 8.0sturn-errorjob-failedgoal-blocked
在等你动手3.0sapproval
事情结束(或只是简单问一句)1.6 – 2.6sjob-donegoal-completeneeds-inputturn-done

同一带内的两条刻意靠得很近,因为它们的紧急度本来就同级:goal-complete(2.16s)和 needs-input(2.09s)只差 0.072s,中文那套也一样(差 0.072s)。靠声音长短能分辨的是「带」,不是每一条。

场景什么时候响中文英文紧急度
turn-error本轮失败,或撞到 token 上限7.97s7.90s失败/受阻 —— 最长
job-failed后台任务失败5.95s6.46s失败/受阻
goal-blocked目标受阻,需要你介入4.78s4.85s失败/受阻
approval有操作在等你批准3.00s3.00s在等你动手
job-done后台任务完成2.59s2.59s已结束
goal-complete目标整体完成(只响一次,不是每轮)2.16s2.16s已结束
needs-input代理停下来等你回答2.09s2.09s简单问一句
turn-done你发起的那一轮正常结束1.58s1.92s已结束 —— 最短

⚠️ 两套时长不一样,别混用。 中文和英文是各自合成的(不同音色、不同语言),所以顺序一致但数值不同:英文 job-failed6.46s,中文是 5.95s。上表两列都给了,按你实际用的语言看那一列。

两条过滤规则值得单独说明,它们避免了这个插件变成噪音源:

  • turn-error 不看是否由你发起。自动续跑的回合出错,同样需要你知道。
  • turn-done 只看你发起的回合。否则一个跑 20 轮的目标会响 20 次「任务完成」。自动推进的进度由 goal-complete / goal-blocked目标级别汇报,而不是每一轮。

approval 只在审批策略为 ask(需要人工确认)时才会响 —— 策略是 never 时没有任何东西在等你,自然也不该出声。它监听的是会话事件 approval/asked,这条在 ask 策略下必定派发;另外还挂了作用域瀑布 approval/request 作兜底,两条路径播的是同一条音。

音频试听(GitHub 的 Markdown 不支持内嵌播放器,所以放在 Release 里,点开即可播放): 见 v0.3.0 release16 个音频附件——不带后缀的是中文(默认那套),带 .en 的是英文。

⚠️ 那些附件只是试听用的,不是安装产物:安装走 dsh plugin add,插件包里本来就带了全部 32 个文件(16 条 × mp3 + wav),不需要从 Release 下载任何东西。

两种语言:中文(默认)和英文

同一套安装包,八条场景各带中英两版音频,来回切换不需要重装。默认是中文。

/voice-alerts lang        # 看当前语言
/voice-alerts lang en     # 切到英文
/voice-alerts lang zh     # 切回中文

切换会写回配置文件(language 字段),重启后仍然生效。也可以直接改配置:

{ "language": "en" }

英文不是翻译,是重写的

这一点值得说明,因为它关系到这个插件的核心设计。

中文原文直译成英文会变长、变平,而**「时长 = 紧急度」正是这个插件唯一的信息载体** —— 一旦英文长度失控,光凭声音长短判断要不要过去就失效了。所以英文八条是按英语语境重写的,不是逐句翻译:

场景中文英文
turn-done任务完成。Turn complete.
needs-input需要你回答。Waiting for your answer.
goal-complete目标已完成。The goal is complete.
job-done后台任务完成。The background job has finished.
approval有操作等待你批准。An action is waiting for your approval.
goal-blocked目标受阻,需要你介入处理后才能继续。The goal is blocked. It needs you before it can continue.
job-failed后台任务失败,请回到 DSH 查看详情。The background job failed, and it needs your attention. Check DSH for details.
turn-error任务执行出错,本轮未能完成,请回到 DSH 查看错误详情。The turn failed, so this round did not finish. Open DSH to see the error details, then try again.

英文版的时长梯度是 1.92s → 7.90s,与中文同样单调(同样按严重度递增)。真正拉开的是三个紧急度带:跨进「失败/受阻」带的那一步有 38%(3.00s → 4.85s)。同一带内刻意靠得近 —— goal-complete(2.16s)与 needs-input(2.09s)只差 0.072s,中文那套也一样。所以靠长短分辨的是,不是每一条。

⚠️ 这里踩过一个坑,记下来:英文 turn-error 最初只有 16 个词、6.55s,而 job-failed 是 6.46s —— 只差 0.09s,耳朵根本分不出来。中文那边两者差 2.02s(25%,相对较长者;本文件所有百分比都按这个口径)。后来把英文错误文案加长到 19 个词,才恢复到 7.90s / 1.44s 的差距。如果你改文案,记得重新量一遍时长,别只看中文字数。

英文用的音色是 loongmary(温暖英音),不是让中文音色去读英文。试听过三个候选并做了排序(scripts/qa.mjs rank,产物写到 qa/,该目录不进版本控制;重跑一次名次不变)。中文音色读英文得分最低,评语是「明显合成感、节奏不自然」——自然度 4/10、音色 5/10(这两个分数容易记混,naturalness 是 4,character 才是 5)。

/voice-alerts status 会同时列出两种语言各自的音频是否齐备:

Clip sets: zh (active): all clips present  |  en: all clips present

装之前先知道三件事

  1. 平台:Windows 10/11。 保底播放器用的是 Windows 自带的 PowerShell + System.Media.SoundPlayer没有 macOS / Linux 支持。装了 ffmpeg 会优先用 ffplay,但那只是可选增强。
  2. 需要 DSH Desktop,以及能跑 dsh 命令的终端。装插件走 dsh plugin,它内部调用 pnpm(见下面的坑)。
  3. 音频不用装任何东西 —— 中英两套 32 个音频文件都在包里。

⚠️ 一次性提醒:包名和运行时名字不一样

这个容易让人找错地方,先说清楚:

名字
npm 包 / GitHub 仓库dsh-status-chime
斜杠命令/voice-alerts
日志前缀[voice-alerts]
配置文件$DSH_HOME/voice-alerts.config.json(主路径)
音频目录$DSH_HOME/voice-alerts/clips/
cordis idvoice-alerts

配置还有一个兼容用的旧位置 $DSH_HOME/voice-alerts/voice-alerts.config.json。插件只读先找到的那一个(先查主路径),所以:如果主路径的文件已存在,你改旧位置那个文件会被静默忽略

写入是写回它实际读到的那一个文件:主路径存在就写主路径,否则写旧位置;两个都不存在时才新建主路径。所以在"只有旧位置文件"的安装上,/voice-alerts langon/off 更新的是旧位置那个文件,不是主路径。拿不准时看启动日志里的 config <路径> 那一行——它写的就是真正在用的文件。

包名在 0.3.0 从 dsh-voice-alerts 改成了 dsh-status-chime(原来的名字和社区目录里另一个插件只差一个字母,会被市场规则隐藏)。改名只动包名和仓库名:命令、配置路径、日志前缀全都还是 voice-alerts,所以老用户升级不会坏。

排查问题时去日志里 grep voice-alerts,不是 dsh-status-chime

安装

方式一:交给你的 Agent(推荐)

把下面这句话发给你的 DSH:

https://github.com/lijiawei255/dsh-status-chime 装进我的 DSH desktop profile,装完提醒我重启。

仓库里有一份 INSTALL.md,写清了每一步该做什么。它存在的意义是:让不同的 Agent 装出同样的结果,而不是各自发挥。

方式二:自己敲命令

<PROFILE> 换成你自己的 profile 名(通常是 desktop)。先看一眼 $DSH_HOME/profiles/ 下有哪些目录再决定,不要照抄别人的:

# 从 GitHub 直接装
dsh plugin --profile <PROFILE> add github:lijiawei255/dsh-status-chime

# 或者先 clone / 下载 ZIP,再指向本地目录
git clone https://github.com/lijiawei255/dsh-status-chime
dsh plugin --profile <PROFILE> add .\dsh-status-chime

这条命令会做两件事:把包装进 profile,并把包登记为一个 profile 层(前提是包里声明了 dsh.bundle,本仓库已经声明了)。

⚠️ 如果报 ERR_PNPM_ADDING_TO_ROOT:profile 自带一个声明了 packages: [.]pnpm-workspace.yaml,pnpm 9 会因此拒绝裸 add。加一个 -w 即可:

dsh plugin --profile <PROFILE> add -w github:lijiawei255/dsh-status-chime

(实测过的是:pnpm 9 会报错、pnpm 11.8.0 正常;pnpm 10 没测过。所以最稳的是加 -w。)详见 TROUBLESHOOTING.md 第 11 条。

装完必须完全重启 DSH Desktop,否则插件不会加载。

重启之后

在聊天框里敲:

/voice-alerts status

看到 Voice alerts: on (v0.3.0)Language: zhScenes (8) 就说明装好了。想听一遍全部八条:

/voice-alerts

卸载

dsh plugin --profile <PROFILE> remove dsh-status-chime

卸载不会删除你 $DSH_HOME 下的东西。想彻底清干净,删这两处:

# 音频、play.ps1、clips.json
Remove-Item -Recurse -Force "$env:USERPROFILE\.dsh\voice-alerts"
# 运行时配置(配置**不在**上面那个目录里,是同级的一个文件)
Remove-Item -Force "$env:USERPROFILE\.dsh\voice-alerts.config.json"

卸载后需要完全重启 DSH Desktop 才生效。

⚠️ 质检报告不在 $DSH_HOME 下,也不随包发布qa/report.md 由开发脚本 scripts/qa.mjs 写进仓库检出目录里的 qa/。那个目录在 .gitignore 里、也不在 package.jsonfiles 中,所以用 dsh plugin add 装出来的用户没有这个文件(要跑质检得先克隆仓库)。

它怎么工作

DSH 事件  ──▶  lib/index.js  ──▶  聚合 / 节流 / 优先级  ──▶  播放器  ──▶  音频文件

事件来源(都是宿主进程里的公开事件):

事件用途
session/eventturn/start / user/message / turn/end判断这一轮是不是你发起的,以及它是怎么结束的
session/eventgoal/change目标的 completeblock
jobs.onJobDone后台任务的 completedfailed
tools/pre-execute工具名命中 waitingTools 时,说明代理要停下来等你
session/eventapproval/asked主路径:审批策略为 ask 时必定派发
approval/request兜底:作用域瀑布,同样只在 ask 下可能派发;两条路播同一条音

播放规则:同一 400 毫秒窗口内的多个事件只播优先级最高的那条;同一场景 1.5 秒内不重复;新的提示会打断正在播的那条。

运行时零网络:插件只读本地音频文件、只启动本地播放器。不联网、不上传、不校验。

技术栈:用大模型给声音做「预审」

这部分是我觉得最值得分享的地方。

给提示音挑一个合适的音色,传统做法是一个个盲听:试 10 个音色、改 20 次风格描述、听到耳朵疲劳,最后凭模糊印象拍板。这个项目换了个做法——让大模型先做初筛和排序,人只在筛出来的少数里做最终决定

具体是三件事:

1. 用全模态模型当评审(核心)

tools/qw_local_omni.py多段候选音频一次性交给 Qwen-Omni,让它横向比较并按维度打分:

python tools/qw_local_omni.py preview/flash-mary-en.mp3 preview/flash-eva-en.mp3 preview/flash-yuanfei-en.mp3 `
  --message "横向比较这几段录音,按清晰度/自然度/音色/干净度打分并排序"

拿到的是一个排序而不是一堆孤立分数——「A 比 B 更贴目标」这种相对判断,比「A 得 7 分」有用得多。实测给出的排序和理由相当具体:

A4 是唯一一个在保持清晰稳重的基础上,做到了低沉与气声质感的音色。A2 虽然成熟但不够有辨识度,A5 略显平淡,A1、A3、A6 则因音色过亮或过嫩而被排除。

2. 用 ASR 转写回读做客观校验

光听着顺耳不够——TTS 可能吞字、念错、把缩写拆开读。tools/qw_local_asr.py 把音频转回文字,与预期文案做字符级相似度比对:

python tools/qw_local_asr.py assets/clips/turn-error.mp3 --lang zh

这条是可以当硬门槛的客观指标:文案是已知的,转写对不对是机械可判的。本项目中英各 8 条、共 16 条音频的相似度都是 1.000

3. 时长梯度作为信息编码

上面表格里的时长不是随手定的,而是把「严重程度」编码进了音频自身:长 = 需要你出手。这样即使手机在旁边、屏幕没看,也能靠声音长短判断该不该放下手里的事。

由此得到的完整流程scripts/ 里两个脚本,可直接复现):

scripts/build.mjs audition    出多个音色候选
      ↓
scripts/qa.mjs rank           全模态模型横向排序(不做 ASR;回读在下一步)
      ↓
人听筛出来的前 2-3 个,拍板     ← 决策量被压缩到很少
      ↓
scripts/build.mjs build       批量生成 + 响度归一
      ↓
scripts/qa.mjs clips          全部质检过关

一点经验:不要把大模型的主观打分当硬门槛。实测同一段音频两次评分能从「自然 6 / 音色 4」跳到「自然 9 / 音色 7」,四段明显不同的音频甚至拿到过完全一样的分数。所以本项目把指标分了两层——客观项(ASR 相似度、有无削波、清晰度、干净度)当门槛,主观项(自然度、音色、成熟度)只作参考,最终由耳朵决定。这个分工是这套流程能稳定跑起来的关键。

后来补上的三条检查,都是先发现了具体漏洞才加的:

检查性质为什么加
静音下限(峰值 ≥ −30 dB、均值 ≥ −35 dB)硬门槛原来的峰值检查只抓削波,所以「时长正常但内容全静音」这种经典 TTS 失效能全项通过。实测成品峰值 −4.2…−1.9 dB、均值 −20.6…−16.6 dB,而全静音是 −91 dB。两条线的余量不一样:峰值门槛离最差成品 25.8 dB(−4.2 vs −30),均值门槛 14.4 dB(−20.6 vs −35)
净语速(去掉停顿后的单位/秒)参考项中文刻意放慢(rate 0.95),套用人类播报那个 3.2–5.5 字/秒会把八条里的七条判失败。对这个项目真正有意义的是条与条之间的一致性(因为时长承载紧急度),所以跟同语言中位数比;少于 5 个单位的短句豁免,否则两个词的 clip 会假报警
UTMOS(MOS 预测器)参考项给自然度一个可复现的数字,补上「大模型打分不稳定」那一环。必须按语言分别比:它给英文八条的分全部高于中文八条(4.38–4.51 vs 3.75–4.28),跨语言比会读成中文那套有缺陷

这三条都有反向验证scripts/qa-negative-control.mjs,离线运行、不花 API 额度):注入全静音、极安静、语速拉长三类缺陷,断言门槛确实报出来,同时确认健康音频不会被误伤。一个只会说「通过」的检查器没有价值。

顺带记一个坑:加静音下限之前,先用 ttsproof(一个现成的 TTS QA 工具)试过。它能抓削波和截断,但抓不到全静音 —— 它源码里的 empty_audio 只判断「文件不存在或 ≤44 字节」,不看音频内容。所以那条下限是自己加的,不是从工具里白拿的。

配置

配置文件:$DSH_HOME/voice-alerts.config.json$DSH_HOME 通常是 ~/.dsh)。 找不到这个文件时使用内置默认值,所以不配置也能正常用。文件按修改时间热读取,改完立即生效,不需要重启。

完整模板见 assets/voice-alerts.config.json。常用项:

配置项默认说明
enabledtrue总开关
volume850-100,只对 ffplay 生效
minIntervalMs1500同一场景的重复触发抑制窗口
coalesceMs400事件聚合窗口,窗口内只播最高优先级那条
interrupttrue新提示是否打断正在播的
scenes.<场景>.enabledtrue单独关掉某个场景
player"auto"auto / ffplay / powershell
ffplayPathnull显式指定 ffplay 路径(不在 PATH 上时用)
playPs1Pathnull显式指定回退脚本 play.ps1 的路径
commandName"voice-alerts"斜杠命令名。与别的插件重名时只丢命令,不影响出声
waitingTools["ask_user_question","exit_plan_mode"]命中即视为「在等你回答」;DSH 若改工具名可在此覆盖
language"zh"语音语言:zh / en;无法识别的值回退到 zh(不会静默)
watchApprovalstrue审批请求是否出声(只在策略为 ask 时可能触发)
clipsDirnull自定义音频目录,优先级最高

命令

命令作用
/voice-alerts依次播放全部八条(当前语言)
/voice-alerts on / off立即开关(会写回配置文件)
/voice-alerts lang <zh|en>切换语音语言,写回配置
/voice-alerts status看播放器探测结果、各语言各场景音频是否齐备
/voice-alerts test <场景>只播一条,用来排查某类事件有没有触发

换成你自己的声音

音频不是必须用仓库里这 8 条(中英各 8 条)。完整流程:

# 1. 改 assets/clips.json:文案在 clips.<场景>.text,音色在 model/voice/instruction
# 2. 换音色时先出样试听(把候选写在 voiceCandidates 里)
node scripts/build.mjs audition
node scripts/qa.mjs rank                 # 大模型帮你排序,人只挑最终那条
# 3. 把选定的 model/voice/instruction 写回 clips.json,然后批量生成
node scripts/build.mjs build
node scripts/qa.mjs clips                # 质检

生成音频需要:ffmpeg(响度归一与转码)、Python 3(质检脚本)、阿里云百炼 CLI(TTS/ASR/Omni)。这三样只有你想自己生成时才需要——用仓库自带音频的话,什么都不用装。

生成后会有 mp3wav 两份。不要删掉 wav:PowerShell 回退播放器只认未压缩 PCM,它是「干净 Windows 上零依赖」的保证。

自己生成的音频放在 $DSH_HOME/voice-alerts/clips/ 会自动优先于包内的(逐文件覆盖,所以只想换一条也可以)。

文件名规则要记住(想只替换一条英文音频时必须知道):

语言文件名
中文(默认)<场景>.mp3 / .wav,例如 turn-done.mp3
英文<场景>.en.mp3 / .en.wav,例如 turn-done.en.mp3

默认语言用不带后缀的名字,其它语言加 .<语言码> 中缀。 所以只想换英文的 approval,就放一个 approval.en.mp3(以及 .wav,如果你用 PowerShell 回退播放器)。

装了没声音?

按这个顺序排查,能覆盖绝大多数情况。完整版见 TROUBLESHOOTING.md

  1. 重启了吗? 插件文件不热加载,改完或装完必须完全重启 DSH Desktop。
  2. /voice-alerts statusPlayer 那一行。unavailable 说明两条播放路径都没探测到。
  3. Windows 音量合成器把 ffplay(或 powershell.exe)单独静音了——这是最常见的原因。右键任务栏音量图标 → 打开音量合成器,检查对应条目。
  4. 输出设备选错了。
  5. 某个场景被关掉了,或 enabledfalse
  6. 日志里有 no <语言> audio for <场景>(例如 no zh audio for turn-done)→ 该语言的音频文件缺失。
  7. 日志里有 throttled <场景> → 被节流窗口挡住了,属于正常行为。
  8. 命令打了没反应? 如果 /voice-alerts 单独打能播(试听 + 状态),但带上参数(lang enontest <场景>)就毫无反应、反而像发了一条普通消息,多半是中文输入法把 / 打成了全角 ,或者命令名里混进了大写(必须是全小写 voice-alerts)。这两种都会让整行不被认作命令。从自动补全列表里选中命令可以同时避开这两点。详见 TROUBLESHOOTING.md 第 14 节。

日志位置:%APPDATA%\DSH Desktop\logs\host\dsh-<日期>.log,搜 voice-alerts

⚠️ 一件事先说明:日志里没有 ffplay 不是故障。ffplay 属于 ffmpeg,需要另外安装;它只是让启动快一点、音量可独立调节。没有它时插件会用 Windows 自带的 PowerShell 播放器,功能完全正常。

平台与依赖

支持:Windows 10 / 11。

干净 Windows 上不需要安装任何第三方依赖就能出声,因为保底播放器用的是系统自带的 Windows PowerShell 5.1 + .NET System.Media.SoundPlayer

情况需要什么
干净 Windows 10/11,只装了 DSH什么都不用装,用系统自带播放器,启动开销约 0.4 秒
装了 ffmpeg无需配置,自动改用 ffplay:启动更快、volume 配置生效
想自己生成音频额外需要 ffmpeg + Python 3 + 阿里云百炼 CLI
运行时网络零网络

播放后端的选择顺序:

  1. ffplay(如果探测到)—— 直接播 mp3,支持独立音量
  2. Windows PowerShell + SoundPlayer —— 系统自带,只认 wav,音量跟随系统 先试 -File play.ps1(可审计的磁盘脚本);若被执行策略或 ACL 拦住,自动改用 -EncodedCommand 内联命令重试一次
  3. 两条都不通 → 静默降级,只在日志里记一行

宿主侧拿不到窗口是否聚焦(DSH 的 native 桥只暴露了 notifyAttention 等接口),所以这个插件任何时候都会出声,包括你正看着窗口的时候。如果觉得吵,用 /voice-alerts off 或调 volume

验证状态

我一向觉得,把「写过」和「验证过」分开讲清楚,比含糊地说「功能完整」有用得多。所以逐条列出,并且明确写出验证的边界

下面这张表是摘要。逐条的原始证据、测量值、以及「没验证到哪一步」都记在 docs/verification.md —— 想核对任何一个数字就去看那份。

验证边界(请先读这一段)

以下结论全部来自同一台机器上的验证

操作系统Windows 11
DSHDSH Desktop 2.0.13,@deepseek-ai/dsh 0.1.5-rc.2
该机器上的额外软件装有 ffmpeg、Python 3、阿里云百炼 CLI

这意味着

  • 代码正确性、事件映射、音频质量——与本机装了什么无关,结论可迁移到你的机器。
  • 「干净 Windows 零依赖」——保底播放器用的是 Windows 自带的 PowerShell 5.1 + .NET System.Media.SoundPlayer,两者都是 Win10/11 的操作系统组件;无 ffmpeg 的情形已通过模拟验证。
  • ⚠️ DSH 版本——只在这个版本上验证过。更高或更低的版本若改动了事件接口,某些场景可能不再触发。这是唯一真正未知、且我无法在本机消除的变数。
  • ⚠️ 在一台完全干净的、别人的 Windows 上从 GitHub 安装——我没有第二台机器,没有实测过这一步。CI(见下)在 GitHub 提供的干净 Windows 运行器上覆盖了「安装 + 加载 + 后端探测」,但运行器没有声卡,无法验证声音真的到了扬声器

如果你在别的 DSH 版本或别的机器上遇到问题,请提 Issue 并附上日志里的 [voice-alerts] active … 那一行(它会写明探测到的播放器和配置路径),那基本能一眼定位。

逐层状态

层级状态
事件层8 个场景中 7 个由真实事件触发验证过turn-doneturn-errorneeds-inputjob-donegoal-completegoal-blockedapproval
音频层中英各 8 条、共 16 条全部通过自动质检(ASR 相似度 1.000、清晰/干净 10/10、无削波)。其中中文那 8 条另做过逐条耳听确认英文那 8 条没做过这一步,只过了自动质检
后端层强制 PowerShell 会正确选 .wav;模拟「没装 ffmpeg」时自动回退且仍能播;显式指定不存在的 ffplay 会明确失败而不偷偷换后端
代码层scripts/selftest.mjs 用模拟上下文驱动插件,63 项检查覆盖事件映射、过滤规则、优先级、节流、命令、重名冲突、资产解析顺序、以及语言切换与回退
语言层默认中文、lang en 切换后确实改选英文文件(自测断言的是解析到的文件名,不只是状态文字);配置里写无法识别的语言会回退到中文而不是静默
CI.github/workflows/verify.yml 在干净的 windows-latest 上验证:真实安装并登记为 profile 层、清单无 BOM、只依赖 Node 内置模块、32 个音频齐备(8 场景 × 2 语言 × 2 格式)、无 ffplay 时 PowerShell 后端仍被探测到、63 项自检、隐私扫描

关于 approval 的验证要说清楚边界:触发时审批策略必须是 ask。我本人是在 ask 策略下听到提示音的,但当时无法区分它走的是会话事件 approval/asked 还是兜底的作用域瀑布 approval/request —— 两条路径播同一条音。会话事件那条的行为由自检覆盖(含「子代理会话的审批不出声」),但它是否在生产环境中被派发,我没有单独取证过。

CI 的详细「证明了什么 / 没证明什么」写在 workflow 文件头部——包括它不能证明声音到达扬声器这一点。

一个已知缺陷,必须说清楚

job-failed 对「后台 shell 命令非零退出」实际不可达。

实测:一个以后台方式运行、exit 7 结束的命令,DSH 把它的状态记为 completed,于是播的是「后台任务完成」。原因是 job 快照里没有退出码字段,而 shell 后台任务的生产者只会产出 completedkilledfailed 保留给「后台工具任务报告错误」或生产方合约违约的情况。

顺带一提:DSH 自带的 desktop-notifications 用的是同一个 status === 'failed' 判断,所以它有完全一样的盲区——这是框架行为,不是本插件的实现问题。

这个场景保留了(它对工具任务失败是有效的),但它的触发未被真实复现过,只有代码层面的核对。你可以用 /voice-alerts test job-failed 验证音频本身没问题。

目录结构

dsh-status-chime/
├── lib/index.js                  # 插件主体,唯一运行时代码
├── cordis.patch.yml              # 声明这个包是 profile 层(dsh.bundle 指向它)
├── package.json                  # 包名、dsh.bundle 声明、engines
├── assets/
│   ├── clips/                    # 8 场景 × 2 语言 = 16 条音频,每条 mp3 + wav(共 32 个文件)
│   ├── clips.json                # 文案/音色/参数的唯一事实源
│   ├── voice-alerts.config.json  # 配置模板
│   └── play.ps1                  # PowerShell 回退播放器(纯 ASCII,原因见文件头)
├── tools/                        # 百炼本地音频辅助脚本(ASR / Omni / UTMOS)
├── scripts/                      # 生成 / 质检 / 自检 / 负向验证 / 隐私扫描
│   ├── build.mjs                 # 生成音频(支持 --lang)
│   ├── qa.mjs                    # 质检(支持 --lang)
│   ├── selftest.mjs              # 离线自测,63 项
│   ├── qa-negative-control.mjs   # 证明质检门槛真的会拦下坏音频
│   ├── verify-local-install.mjs  # 验证已安装的单文件版本
│   ├── scan-sensitive.mjs        # 隐私/措辞扫描
│   └── scan-sensitive.verify.mjs # 证明扫描器真的抓得到
├── docs/verification.md          # 逐条验证记录
├── .github/workflows/verify.yml  # CI:干净 Windows 上跑安装 + 自测 + 扫描
├── INSTALL.md                    # 给 Agent 看的安装步骤
├── TROUBLESHOOTING.md            # 没声音时的排查清单
├── README.en.md                  # 英文说明
├── CHANGELOG.md
└── LICENSE                       # MIT

qa/preview/tmp/ 是脚本跑出来的中间产物,已在 .gitignore 里,不随仓库发布。

收录

已被 Awesome DSH Plugin 收录,分类 notify:条目见通知与集成,提交记录 #5553(已合并)。

那个列表是人工 + CI 审核制,不是爬虫采集:要求仓库声明 dsh.bundle、含真实可运行的代码、仓库创建满 1 天,而且条目描述必须和代码对得上

许可

代码与随包音频均以 MIT 发布。

随包音频是用阿里云百炼(Model Studio)的语音合成生成的,不是任何人的真人录音;如果你打算使用它们,请自行确认你的用法符合相应服务条款。仓库不授予任何底层声音模型的权利。你完全可以不用这套音频——scripts/build.mjs 可以生成你自己的,插件也会优先使用你放在 $DSH_HOME/voice-alerts/clips/ 的文件。

贡献

提交前请跑这两个脚本。第一个确认没有把个人路径或凭据带进来,第二个确认自检仍然全绿:

node scripts/scan-sensitive.mjs .
node scripts/selftest.mjs

scan-sensitive.mjs 只报告文件、行号与命中类别,绝不打印命中内容——所以你在 Issue 里贴它的输出是安全的。它自带的规则集可以用 $DSH_HOME/voice-alerts.scan.json(不提交)追加你自己机器上的私有字符串。想确认它真的能抓到东西,跑 node scripts/scan-sensitive.verify.mjs:它植入几段明显是伪造的凭据形态值并断言扫描器能报出来,同时断言报告不回显这些值。

欢迎提 Issue 与 PR。特别欢迎的:macOS / Linux 的播放后端(当前后端层是 Windows 专用的)。

Plugin correlati