PROJECT · 2026
dsh-voice-notify · DSH 语音提示插件
给 DSH 加"说话"的能力:任务开始、任务完成、遇到问题、需要你确认时用语音播报,可指定音频输出设备、音量、语速和语音。
它解决什么
用 DSH 跑长任务时,眼睛不能一直盯着页面。这个插件让实例"开口说话":
| 时机 | 开关 | 默认 |
|---|---|---|
| 任务开始(每轮对话开始) | announceStart | 开 |
| 任务完成(一轮结束交回给你) | announceDone | 开 |
| 遇到问题(本轮出错) | announceError | 开 |
| 需要你确认(审批 / 提问在等你) | announceQuestion | 开 |
| 请求重试(模型请求失败后重试) | announceRetry | 关 |
| 子智能体是否也播报 | includeSubagents | 关(并行子任务时不吵) |
怎么用
- 设置页:设置 → 语音提示。那里能选输出设备、语音、音量(0–100)、语速(−10–10),改五条提示语文本,还有"按当前设置试听 / 刷新设备列表 / 恢复默认"。
- 侧栏底部:一键静音/取消静音。
- 对话里:直接说"音量调到 60""用耳机播报""关掉完成提示",我会通过
voice_notify工具(status/devices/set/test/reset)改。
试听会用还没保存的当前选择先播一遍,方便先听效果再决定。
结构
index.js Host 半侧:事件监听、设置文档、HTTP 路由、Agent 工具
speech.js 播报队列、同类提示间隔、设备/语音枚举与缓存、进程管理
speak.ps1 一次播报一个进程;请求与结果都是 base64 UTF-8 JSON
settings.js 默认值、归一化、范围钳制、设置文档读写
client.js 浏览器半侧:设置页 + 侧栏静音开关
播报走 Windows 自带 SAPI(SAPI.SpVoice),因此能选任意输出设备(耳机、扬声器、虚拟声卡都行),不只系统默认。每次播报起一个短命的 powershell.exe:卡住 90 秒会被杀掉,永远不会阻塞对话;播报串行,同类提示按间隔去重。设置存在 ~/.dsh/voice-notify.json,bundle patch 里的 config 只作初始默认值——页面改过的字段不会被启动覆盖。
两个踩到的坑(值得记下来)
1. 冷启动竞态。 Cordis 里所有行在同一轮并发激活,我最初用一次性探测 ctx.get('tools') / ctx.get('webServer')——冷启动时这两个服务还没就绪,探测拿到 undefined 就静默放弃了,于是路由 404、工具消失,而客户端页面照常渲染,只剩一句报错。安装那一刻是往活着的树里插行,服务早已就绪,所以"装的时候好、重启就废"。
正确写法是服务作用域子上下文:
export function apply(ctx, config) {
ctx.inject(['webServer'], (child) => {
child.effect(() => {
const webServer = child.get('webServer')
// 服务出现就注册,服务消失就摘掉
...
})
})
}
另外在 /voice-notify/status 里暴露了 wiring(routes / tool 是否已挂上)和 hostRevision,让"注册没发生"这种事可见而不是静默失败。
2. 重启自身会被自己的 Job Object 带走。 想用"分离进程重启 dsh web"来自动加载新的 Host 模块,但 Agent 的 shell 工具启动的一切都在宿主进程的 Windows Job Object 里;杀掉服务进程 → job 关闭 → 重启脚本自己也被带走,日志正好停在 stopped pid ... 就没有下一行。解法是用 WMI 创建进程(Win32_Process.Create),它挂在 WmiPrvSE.exe 下、位于 job 之外,因此能活到"停服务 → 起服务 → 验证路由"全部走完,把结论写进 .done 文件。
验证
dev/_toolcheck.mjs:用假 ctx 复现inject路径,工具 + 5 条路由全部注册,5 个工具动作全部通过 Harness 自己的 lossless-JSON 校验。dev/_smoke.mjs:设备/语音枚举 + 真实发声。- 线上:
GET /voice-notify/status→200,hostRevision=3,wiring={routes:true,tool:true},枚举到 6–7 个输出设备。
已知限制
- 仅 Windows(SAPI)。其他平台设置页会提示不支持,所有播报跳过。
- 客户端文案目前是中文硬编码,未走 locale 字典。
- 页面视觉效果尚未用浏览器截图验收(当前会话没有浏览器控制能力),样式只使用
--dsw-alias-*主题变量。