AGImentu/dsh-turn-chime0

dsh-turn-chime

DSH Web plugin: play a chime when the agent finishes a turn, with a settings page to enable it, pick the volume and upload your own audio file. Host-stored configuration, no core changes.

包名
dsh-turn-chime
版本
0.1.0
许可证
MIT
最近更新
2026年9月12日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:AGImentu/dsh-turn-chime

dsh-turn-chime

任务做完了,喊你一声。

一个 DSH(DeepSeek Harness)Web 插件:每次智能体把一个任务做完,浏览器里播一段提示音—— 你可以随时关掉它、调音量,也可以上传自己的 MP3 当提示音。默认自带一段两音铃声。

不读凭据、不访问外网、不改 DSH 内核,只用官方公开的两个插槽。


✨ 它怎么知道"任务做完了"

这一点决定了插件非常小:DSH 的 conversation.chat.assistant-actions 插槽有个天然特性—— 它只在回合收尾之后才渲染(回合进行中,官方根本不渲染那一行)。

所以本插件注册一个渲染 null 的隐形条目,「我被挂载」就等于「任务做完了」。

为了不吵到你,又加了三道保险:

保险作用
新鲜度窗口(60 秒)只有"刚结束"的回合才响。你翻历史、切会话时挂载的旧回合不会响
页面稳定窗口(3 秒)刚打开页面时渲染出来的最后一个回合不算"完成",不响
每回合只响一次同一回合无论重渲染多少次,最多响一次(带 200 条上限的记忆)

📦 安装

前置:DSH 已能正常运行(dsh web 起得来);Node.js ≥ 20,pnpm ≥ 10。

支持的 DSH 版本:在 DSH 0.1.5-rc.2 上真机验证(只用公开插槽与平台种子模块,不 import 内部包)。

方式一:本机 tarball 安装(推荐,现在就能用)

git clone https://github.com/AGImentu/dsh-turn-chime && cd dsh-turn-chime
pnpm install && pnpm build && pnpm pack          # 产出 dsh-turn-chime-.tgz
dsh plugin --profile web add ./dsh-turn-chime-0.1.0.tgz   # 文件名按上一步输出替换

方式二:源码 link: 安装(改代码即时生效)

git clone https://github.com/AGImentu/dsh-turn-chime && cd dsh-turn-chime
pnpm install && pnpm build
dsh plugin --profile web add "link:$PWD"        # Windows PowerShell: "link:$($PWD.Path)"

或用仓库里的一键脚本(等价的两步,并在 CLI 不在 PATH 时兜底):

node scripts/install-local.mjs --profile web

方式三:从 npm 安装(包发布后可用)

dsh plugin --profile web add dsh-turn-chime@latest

方式四:交给 DSH 自己装

把下面这段原样发给任意一个 DSH 会话:

帮我装 dsh-turn-chime 插件(DSH 任务完成提示音),步骤:
1. git clone https://github.com/AGImentu/dsh-turn-chime 到 ~/Code/dsh-turn-chime
2. 在该目录执行 pnpm install && pnpm build && pnpm pack(记下产出的 .tgz 文件名)
3. 执行 dsh plugin --profile web add ./
4. 完成后提醒我:重启 dsh web,然后硬刷新浏览器(Ctrl/Cmd+Shift+R)
遇到报错先查 https://github.com/AGImentu/dsh-turn-chime 的 README「常见问题」表。

装完必须做的一步

重启 dsh web,然后硬刷新浏览器(Ctrl/Cmd+Shift+R)。

规则:lib/client.js(浏览器半边)改动,硬刷新即可;lib/index.js(宿主半边)改动必须重启。 本插件的设置与音频存在宿主半边,所以第一次安装请重启一次。

更新

cd ~/Code/dsh-turn-chime && git pull && pnpm install && pnpm build && pnpm pack
dsh plugin --profile web add ./     # tarball 方式
# 或 link: 方式:重新 pnpm run build 即可,profile 里已经是符号链接

卸载

dsh plugin --profile web remove dsh-turn-chime

remove 会按已安装状态对账 dsh.profile.bundles,自动摘掉本包;之后重启 dsh web。 已上传的音频与设置仍留在 ~/.dsh/turn-chime/,想彻底清掉就删掉这个目录。

常见问题

现象原因与解决
设置里点「试听」提示被浏览器拦截浏览器要求页面先有一次点击或按键才允许出声。在页面上随便点一下再试听即可(插件会自动解锁,之后任务完成时就能正常响)
任务做完了但没响① 插件开关是关的;② 你切到了别的会话(提示音只对当前打开的会话生效);③ 页面刚打开不到 3 秒;④ 浏览器还没被解锁(见上一条)
上传音频报"只支持 mp3 / wav / ogg..."该文件既没有受支持的扩展名,浏览器也没报出音频 MIME 类型。换成 mp3/wav/ogg/m4a 再试
上传报"内容超过上限"上限 8 MB。长音频请先剪辑或用码率更低的格式
设置页显示「读取设置失败」宿主半边没加载:必须重启 dsh web(只刷新页面不够)
页面出现两个「提示音」页双挂载:profile 的 cordis.patch.yml 里还留着手写挂载行,删掉那段
换浏览器后设置没了设置与音频存在 ~/.dsh/turn-chime/(本机),所以换浏览器应该还在;若不在,检查 DSH_HOME 是否变了
提示 dsh: command not foundnpx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-turn-chime@latest,或走方式一/二

🔊 设置页(设置 → 提示音)

控件说明
开启提示音关掉后不再播放任何声音(设置保留)
音量0–100%,试听与正式播放共用
试听立刻按当前音量播一次,用来确认效果
上传音频选一个本地音频文件,上传即启用;上限 8 MB
恢复内置回到自带的两音铃声

补充说明(页面底部也写着):

  • 提示音只对当前打开的会话生效;后台任务、其它会话的任务不会响(要跨会话提醒需要监听全局会话列表,属于后续可做项)。
  • 音频与设置在 ~/.dsh/turn-chime/(config.json + sound.),所以换浏览器、清缓存都还在,且只在本机。
  • 任务失败/中断也能响,但目前不区分成功与失败的声音(可作为后续项)。

🎵 内置提示音

assets/default-chime.wav用脚本合成的(仓库里不带任何第三方音频):

pnpm run make-chime     # → assets/default-chime.wav

一段 0.9 秒的两音铃声(E6 → A6,上行四度),每个音由基频 + 两个非谐波分音叠加、 指数衰减包络,归一化后留 15% 余量——像铃铛而不像"滴"声。合成参数见 scripts/make-default-chime.mjs,想换音色改那里的 NOTES 即可重新生成。


🛠️ 开发

pnpm install
pnpm run typecheck   # tsc --noEmit
pnpm test            # 26 项单测:触发时机(三道保险/去重/上限)、设置校验、磁盘状态
pnpm run build       # lib/index.js(host 半边:路由 + 存储) + lib/client.js(浏览器半边)
pnpm run smoke       # 产物契约冒烟:注册 id / 插件形状 / 两个插槽 / 抛错隔离 / 渲染为空 / 取设置
pnpm run verify      # typecheck + test + build + smoke
pnpm run watch       # 开发时增量重建(配合 dsh 的 client HMR;host 改动仍需重启)

目录结构

src/
  shared.ts              两半边共享的 wire 类型与上限(设置形状 / 支持格式 / 8 MB)
  routes.ts              路由常量(避免两半边字符串漂移)
  index.ts               host 半边:注册 /turn-chime/* 路由,并作为一行 live Loader row
  host/
    config.ts            设置校验(纯函数):补默认值、夹取音量、清洗文件名
    store.ts             磁盘状态:$DSH_HOME/turn-chime/config.json + sound.(原子写)
    routes.ts            四个端点:读/写设置、上传/下发/删除音频(边读边限流)
    contract.ts          host 侧契约镜像(webServer 的 req/res)
  client/
    index.tsx            浏览器半边:注入样式、注册字典、解锁音频、注册两个插槽条目
    Trigger.tsx          隐形触发器(渲染 null):挂载即"任务完成"
    decide.ts            触发时机(纯函数 + 去重日志):三道保险都在这里
    play.ts              播放与"首次手势解锁"(浏览器自动播放策略)
    config-store.ts      设置缓存(30 秒 TTL、并发合并)+ 上传/恢复
    SettingsSection.tsx  设置页:开关 / 音量 / 试听 / 上传 / 恢复
    select.ts            从聊天快照取"本回合结束时刻"的选择器(必须返回存储引用)
    locales.ts           中英字典 + 内置中文兜底
    styles.ts            插件自有样式(仅消费 DSH 设计令牌)
    Boundary.tsx         错误边界:渲染失败只影响自己那一块
    contract.ts          本插件读取的 DSH 浏览器契约的镜像类型
assets/default-chime.wav 内置提示音(脚本合成)
cordis.patch.yml         bundle patch:把本包挂成 profile 的一层
tsdown.config.ts         产出 host 半边(ESM)与浏览器半边(CJS 闭包工厂)
scripts/
  make-default-chime.mjs 合成内置提示音
  install-local.mjs      本地 link 安装 + bundles 对账
  smoke-client-bundle.mjs 产物契约冒烟(无浏览器)

🧩 为什么这么设计

取舍理由
插槽挂载判断"任务完成",不监听事件官方插槽的渲染时机本身就是"回合已收尾"(回合进行中 closing === null),这是公开且稳定的信号;去猜事件流反而脆弱
三道保险(新鲜度 / 页面稳定 / 去重)每一条都对应一种"会很烦"的场景:翻历史响、开页面响、一次任务响好几遍
音频与设置放宿主而不是浏览器"上传一次,换浏览器也还在"是用户对声音偏好的预期;浏览器侧还要处理 IndexedDB 与容量限制
上传边读边限流而不是先收完再判断8 MB 上限是保护进程的,必须在流里生效;一个同源页面不该能用超大请求把宿主打爆
只消费 DSH 设计令牌画界面设置列是官方界面的一部分,插件是客人:跟随主题与字号,不引入自己的色板与字体
条目包在错误边界触发器就渲染在官方复制/分支/用量那一行里;插件抛错必须只让自己消失
上传格式白名单,并拒绝未知服务端要给出正确的 content-type,白名单是唯一能保证这件事的方式;不做魔数嗅探,是因为代价大于收益(播放失败会明确回报)

⚠️ 已知边界

  • 只对当前打开的会话生效:后台/其它会话的任务完成不会响。
  • 需要一次页面交互解锁音频(浏览器策略),之后自动生效。
  • 只在浏览器页面里响:DSH 窗口最小化时仍会响;若浏览器把标签页静音(整站静音),则不响。
  • 不区分成功与失败:任务失败/中断也会响(后续可加第二种声音)。
  • 上传上限 8 MB,格式白名单见上;超大音频请先压缩。

📄 License

MIT