香菜的博客
全部项目

PROJECT · 2026

dsh-voice-notify · DSH 语音提示插件

给 DSH 加"说话"的能力:任务开始、任务完成、遇到问题、需要你确认时用语音播报,可指定音频输出设备、音量、语速和语音。

运行中 DSHCordis插件语音合成SAPIPowerShellWindows

它解决什么

用 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-* 主题变量。