@citisen/dsh-notification
v0.2.0
Published
DeepSeek Harness plugin: per-state desktop notifications — a card per session state with its own bell, volume, and system-notification template
Maintainers
Readme
dsh-notification
一个 dsh 插件:会话需要你的时候响一声提醒。 为桌面版而做,浏览器里也能用。
你正看着界面的时候,它其实已经把话说完了:侧边栏每个会话有状态,对话自己有流式指示。这个插件补的 是另一件事 —— 不管你在不在看,状态到了就响一声:切到别的应用、窗口留在后面、或者就坐在它面前, 只要那张卡是开着的,它就出声。
它加了什么
设置 → 通用 里多出一行 会话通知:一个总音量,然后是每个状态一张卡片。
| 状态 | 什么时候响 | 出厂声音 | | --- | --- | --- | | 等待回答 | agent 提了问题 | 贝多芬《第五交响曲》—— 命运动机 | | 等待审批 | agent 申请权限 | 巴赫《d 小调托卡塔与赋格》BWV 565 | | 等待审阅计划 | agent 提了计划 | 同一个音连敲三下,然后跳到高八度并保持 | | 执行出错 | 会话的 agent 报了错 | 《末日经》(Dies irae)圣咏 | | 执行完成 | 一轮结束 | 贝多芬《第九交响曲》——「欢乐颂」 | | 开始执行 | 一轮开始了 | 莫扎特《弦乐小夜曲》K. 525 —— 默认关闭 |
每一段都是公有领域的古典乐句,按状态的含义挑的。开始执行默认关闭,因为"一轮开始"不值得打扰 任何人;想要的话在它自己的卡片里打开。
每张卡片有自己的开关、音量、音色和旋律。只有一个开关,因为一张卡片只能做一件事:出一声。 当这段声音长到不止一声的时候,旋律就值得改 —— 见旋律。
没有任何"什么时候才准响"的规则 —— 没有窗口聚焦、没有"我正在看的会话不提醒"、没有最小间隔。 状态对得上、卡片是开的,就响;要安静就把总音量拖到 0,或者关掉那张卡片。理由见 为什么没有那些开关。
安装
dsh plugin --profile desktop add @citisen/dsh-notification然后重启应用。浏览器 profile 用 --profile web。
卸载
dsh plugin --profile desktop remove @citisen/dsh-notification先把应用完全退出 —— desktop 这个 profile 由应用自己管理,占用中时命令会拒绝操作。这条命令
会移除包本身、dsh.profile.bundles 里的那一项,以及安装出来的目录。
然后还要自己删掉配置,这部分命令碰不到。插件那一行带着一个 config: 块,里面是你改过默认值的所有
设置,它在你自己的 patch 文件里:
# $DSH_HOME/profiles/desktop/cordis.patch.yml
- id: notification
name: "@citisen/dsh-notification"
config:
masterVolume: 1
# ...以及各个状态的设置把整段删掉。如果留着它,插件是没了,但配置还在,而且每次启动都会打印:
dsh: [.../cordis.patch.yml] patch: entry "notification" not found它只是警告不是失败 —— 应用照常启动,那些设置被忽略 —— 但在你删掉这段之前,每次启动都会报。 删掉之前想留个备份的话,先把这段复制出来。
如果你是从本地检出装的
用 link: 装出来的目录联接(junction),pnpm remove 不会清理,它会留在 node_modules 里、
指向你的检出。它不会让插件继续生效 —— 包已经从依赖表、bundles 表和插件名册里消失了 —— 但它
待在那里会让人以为还装着东西。手动删掉这个链接:
# 删的是链接,不是检出
cmd /c rmdir "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\@citisen\dsh-notification"对目录联接执行 rmdir 只会删掉链接,D:\path\to\dsh-notification 原封不动。不要在不确定的时候
用 Remove-Item -Recurse:某些 PowerShell 版本会跟着联接进去,把后面的检出一起删掉。
设置
只想暂时关掉,不想卸载
把总音量拖到 0:所有卡片立刻安静,设置都留着,拖回来就恢复。要彻底关掉插件本身,用 dsh 自己的 插件管理器(改的是 profile 的 bundles 表,需要重启应用)。
所有东西都在 设置 → 通用 的这一行里:
- 总音量 —— 与每张卡片自己的音量相乘,所以两个数字都有意义;0 就是静音。
- 状态选择器 —— 左边竖排的六个状态。每一项都显示当前有几个会话处于这个状态,关掉的状态是灰的。 选中一个就显示它的卡片。
全局设置只有这一个,因为它是唯一一个全局问题能诚实回答的:多响。每一张卡片自己的开关回答 要不要,而那些已经删掉的开关回答的是你在不在看 —— 插件不知道,猜错一次就少响一次。
卡片上的控件:
| 控件 | 含义 | | --- | --- | | 启用这个状态 | 这张卡片唯一的开关 | | 音量 | 这张卡片的音量,与总音量相乘 | | 音色 | 下面十一种之一 | | 旋律 | 要播的音,见下 | | 试听 | 真的播出来;没响才说话 —— 见下 | | 恢复默认 | 把这张卡片的每个字段恢复成出厂值 |
「试听」一次做两件事,所以只有一个按钮而不是两个。它用真实的声音路径把这段声音播出来 —— 真的音色、真的旋律、这张卡片的音量与总音量相乘之后的真实结果 —— 声音本身就是回答:播出来了 就不打印任何字。卡片只在什么都没响的时候说话("音量为 0,所以不会出声"),因为那是唯一一个 靠听诊断不出来的情况。在一个关掉的状态上按它照样有意义:它会把这个卡片当成开着的来测。
音频状态那一行同理,只在还没弄好的时候出现:音频还没解锁 —— 点一次「试听」即可,或者这台
机器根本没有 Web Audio。能正常出声之后这一行就消失了 —— 一切正常的事没什么可报的。
第一次按它也是解锁音频的那一次点击。
为什么没有那些开关
早先这里有五个全局规则,回答的都是同一个问题:你现在在不在看? —— 什么时候出声(窗口不在前台 时 / 总是 / 从不出声)、不要提醒我正在看的那个会话、窗口在前台时完全安静、两次提示音的最小间隔、 同一状态的最短重复间隔。
它们全都删了,理由是同一个:猜错的代价正好落在插件存在的那个场合。一个对话开在主视图里,长任务
在跑,人去了别的应用 —— 窗口还是可见的,于是窗口不在前台时拒绝了这一声。本来防打扰的规则,防掉
的是通知。那两条开关式的规则之间还自相矛盾:在窗口不在前台时之下,"我正在看的会话不提醒"只可能
在一个已经被静音的场合里生效。
删掉之后还剩什么一直没变:一轮里同一个瞬间只出一声(界面一次发布好几个状态变化时,取最紧急的 那个),而相隔一秒的两个状态变化是两个事件,各出各的声音。要安静是音量的事,不是模式的事。
已知限制
- 「执行完成」的判据是"一轮结束了",不是"这轮跑成功了"。 它是从会话安静下来这个边沿读的, 不是读 dsh 的未读标志 —— dsh 对主视图里那个会话抑制了这个标志,靠它的话,你正在看的那个对话 永远响不了。代价也写在明处:出错的一轮同样会"结束",如果错误报得比这一轮结束还晚,你可能听到 的是「执行完成」那一声,而它其实是「执行出错」。
- 同一状态连着响几次是可能的。 没有重复间隔限制了:会话在状态之间来回横跳,每一次都会响。 嫌吵就关掉那张卡片,或者把它的音量调小。
执行出错的含义是"这个会话的 agent 报了一个错"。 dsh 不会转发是哪一轮,而且这个事件在重连 之后不会重放。- 音频需要一次点击来解锁才能出第一声。这是浏览器的自动播放策略,绕不过去;随便哪张卡片的 试听就是那次点击。在那之前这一行会写着"音频尚未初始化"。
- 界面只有中英双语。
第一次按它也是解锁音频的那一次点击。
旋律
旋律写成音名加可选的时值:
A5:200ms E6:200ms 两个音,各响各自的时长
G4 G4 G4 Eb4:680ms 四个音;没写时值的用出厂步调
A5:120ms -:80ms E6:240ms 休止是不发声但占时间的一步
880 1318.5 赫兹,给没有音名的音高
off 这个状态不出声- 音名是
A5、C#4、Bb3—— 任意八度,升号降号都认。 - 写了时值的项目就占那么长:它响这么久,下一个在它结束时起音。
s、ms、m都行, 只写数字表示秒。 - 没写时值的项目沿用出厂步调 —— 响 130ms、下一个晚 90ms 起音 —— 这才让
A5 E6读起来是 一个音程,而不是两声分开的敲击。 - 休止(
-)占时间但不发声。 - 赫兹数在音名的位置同样可用。
读不出来的内容会在输入框下面报出来,而且只损失它自己那一项 —— 一个笔误不会让整段旋律哑掉。
音色
出厂十一种。四条是单音波形,另外七种由分音构成,这才让它们听起来像乐器而不只是一个音:
| 音色 | 听起来像 |
| --- | --- |
| sine | 纯音 —— 最朴素的一声 |
| triangle | 柔和长笛 —— 温和、泛音少 |
| square | 芯片音 —— 空洞、一听就是电子声 |
| sawtooth | 蜂鸣 —— 明亮刺耳,能从噪音里穿出来 |
| bell | 敲击金属,尾巴很长 |
| glass | 高、亮、短 |
| marimba | 木条,衰减快而均匀 |
| pluck | 短促的拨弦 |
| wood | 几乎没有音高的敲击 —— 这组里最不打扰人的 |
| blip | 极短的一声 |
| digital | 两个分音的电子提示音 |
生成旋律
旋律是纯文本,所以可以由模型生成、并且不用听就能检查。分析器会打印一段旋律到底会做什么:
node scripts/analyze-sound.mjs "G4:170ms G4:170ms G4:170ms Eb4:680ms" --voice bell
node scripts/analyze-sound.mjs "A5:220ms -:80ms E6:400ms" --voice marimba --json
node scripts/analyze-sound.mjs --voices它报告每个音的唱名、频率、起始时间、响多久,以及所选音色给它的包络。--json 输出同样的数据;
读不出来的 token 会让它以非零码退出 —— 于是生成器可以拿它当闸门。
开发
构建和设计说明见 DEVELOPMENT.md(英文)。
许可
MIT
