npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-completion-alert

v1.8.4

Published

DSH 工作完成提示:一轮工作结束播放「冰冰冰」提示音,并在右下角弹出可点击跳转的通知条;声音、音量、提示范围与自定义音源都在设置页里

Readme

dsh-completion-alert

中文 | English

一个 dsh(DeepSeek Harness)客户端插件:一轮工作结束时告诉你。

  • 播放提示音 —— 就是那段循环的「冰冰冰」梗音效,裁成一轮 1.06 秒的三连音,会话从"忙"变"闲"的那一刻响一次;
  • 右下角弹出通知卡,写明哪个会话完成了、跑了多久;
  • 点一下卡片就切到那个会话;
  • 一切都能在设置里改:总开关、提示范围、是否出声、音量、试听,以及上传自定义提示音。

提示音内嵌在客户端 bundle 里,所以插件不需要宿主路由、不需要磁盘资源、也不联网。通知层和设置页全部使用 dsh 自己的主题变量与 slot 体系,和桌面端观感一致。

一轮工作结束(会话 running: true -> false)
        │
        ├─ 按设置播放一次提示音
        └─ 右下角通知卡:「压缩图标」已完成 · 用时 1 分 12 秒
                              └─ 点击 -> uiWorkspace.openSession(id)

安装

从 registry 装(插件管理器的做法)

dsh-completion-alert

把这个名字填进 设置 → 内置插件 → 安装。插件管理器会在 profile 里跑 pnpm add,记下依赖,并把这个包列进 dsh.profile.bundles。

从本仓库安装(本地检出,不走包管理器)

git clone https://github.com/yimengqingfeng3-debug/dsh-completion-alert.git
cd dsh-completion-alert
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1            # desktop profile
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -Profile web

安装脚本是幂等的,改动前会备份:

  1. 把包复制到 <DSH_HOME>/profiles/<profile>/node_modules/dsh-completion-alert;
  2. 向该 profile 的 cordis.patch.yml 追加 completion-alert 行(如果 profile 已在本包的 dsh.profile.bundles 里列出,则由本包自带的 bundle patch 插入,脚本不会重复插入——重复的行 id 会直接导致启动失败);
  3. 缺少 dsh.profile.patchReload 时写入 live,之后改插件不必重启应用。

装完刷新一次 dsh 窗口(Ctrl+R),让浏览器加载新的客户端 bundle。

手动安装

把 lib/、assets/、package.json、cordis.patch.yml 复制到 profile 的 node_modules/dsh-completion-alert,再往 profile 的 cordis.patch.yml 加:

- insert:
    - id: completion-alert
      name: 'dsh-completion-alert'

卸载

三条路都实测过,都不需要插件本身配合,也都不碰其他插件。

1. 插件面板自带的卸载按钮

设置 → 内置插件 → dsh-completion-alert → 卸载。 它会摘掉 bundle 登记与 patch 行(插件立刻不再加载),然后让 pnpm 删包。

最后那一步有个已知问题:插件管理器调用的是 app 自带的 pnpm(11.7.0),而这个版本在包发布不满 24 小时时会忽略 profile 里的 minimumReleaseAgeExclude 豁免名单,于是 pnpm remove 可能报 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION,界面显示"卸载失败" —— 尽管插件其实已经卸载了。PATH 上的 pnpm 12 认这份名单,同一条命令手跑就能过。

无论哪种结果,界面自身状态是对的;可能留下的是 node_modules 里的那份拷贝和 package.json / pnpm-lock.yaml 里的条目 —— 下面两条路专门清这个。

2. 彻底清理脚本(界面报错时推荐)

关掉 dsh,然后:

powershell -NoProfile -ExecutionPolicy Bypass -File .\uninstall-all.ps1                 # desktop profile
powershell -NoProfile -ExecutionPolicy Bypass -File .\uninstall-all.ps1 -Profile web
powershell -NoProfile -ExecutionPolicy Bypass -File .\uninstall-all.ps1 -WhatIf        # 只打印计划,不改任何文件

它删除并验证以下每一处痕迹:

| 位置 | 清掉什么 | | --- | --- | | node_modules/dsh-completion-alert | 包目录 | | cordis.patch.yml | 所有 completion-alert insert 行(安装失败可能留下不止一份) | | package.json | 依赖条目(如果插件管理器加过) | | pnpm-lock.yaml | importer 条目 + packages: / snapshots: 块 | | pnpm-workspace.yaml | 本包的 minimumReleaseAgeExclude 那一行 |

每个被改写的文件都会备份到 <profile>\.completion-alert-backup\;它会逐项打印故意没动的东西(余额插件的目录、依赖、豁免、lockfile 条目都报告为 kept),最后再跑一次"应为空"的扫描,列出任何没清掉的引用。务必在 dsh 关闭时运行:应用运行期间占着 profile,退出时还可能把同样的文件写回去。

3. 手动删

删掉 node_modules/dsh-completion-alert,从 cordis.patch.yml 移除它的 - insert: 行,从 package.json 里去掉 "dsh-completion-alert"(dsh.profile.bundles 与 dependencies 两处),再清掉 pnpm-lock.yaml 里的条目。留着 lockfile 条目不会致命(pnpm 会报"lockfile 不是最新",而不是做错事),上面那个脚本存在的意义就是让你不必手动做这些。

确认是否挂载

宿主半边提供一个诊断路由,浏览器半边会把自身的激活结果 POST 上去(只含挂载事实,没有会话内容、没有偏好数据):

GET http://127.0.0.1:<端口>/api/completion-alert.diag
-> { "report": { "facts": { "watcher": true, "overlay": true, "settings": true } } }

watcher: true 表示完成检测接上了,settings: true 表示偏好已经绑到宿主设置文档。回环端口带鉴权,所以请在应用自己的控制台 / DevTools Network 里看,而不是用 curl。


设置项

设置 → 工作完成提示(与「通用设置」「内置插件」并列的独立一页):

| 设置项 | 说明 | | --- | --- | | 启用工作完成提示 | 总开关。关掉后不出声也不弹通知 | | 提示范围 | 全部会话:任何会话完成都提示;仅后台会话:当前正在看的会话完成后不打扰 | | 后台时全部提示 | 只在选了「仅后台会话」时有意义:应用被切到后台或最小化后,屏幕上其实什么都没在看,于是任何会话完成都提示。这一行会显示当前状态;选「全部会话」时它是灰的 | | 播放提示音 | 只关声音,通知照常弹 | | 音量 | 0–100%,试听和正式提示音同时生效 | | 重复次数 | 每次提示连播几遍,1~4 遍。重复播放的是你裁好的那一段;点「停止」会取消还没播的遍数 | | 提示音 | ‹ 当前音效 › 左右箭头切换(切换即试听),点名字重播;右侧下拉箭头打开全部音效 | | 全部音效 | 覆盖页列出所有内置音效,每行带独立试听键;下方是自定义音效入口 | | 自定义音效 | 选择本地音频 → 在波形上拖选范围 → 试听这段 → 保存并使用 |

偏好存在插件自己的设置命名空间 completion-alert(写进 profile 的设置文档),所以重启保留、多窗口同步。宿主不提供设置服务时插件照常工作,选择只在当前页面生命周期内有效。


实现要点

1. 完成检测:订阅 uiSession.sessionStatus

不抓 DOM、不轮询。插件订阅客户端自己的会话状态投影 —— 侧边栏状态点、Stop 快捷键的守卫用的是同一份:

status.subscribe(() => {
  // running: true -> false 就是一轮工作结束
});

这份投影的数据源是宿主的 api-session/status 事件(agent/status → status === "running"),所以主会话、后台会话、子代理会话都会上报。

两个刻意的规则:

  • 首个快照只当基线。窗口打开时已经有会话在跑,那不是"完成",不会响。
  • 只认 true → false 的边沿。等审批结束的一轮、被用户 Stop 的一轮,同样是"结束了",一样提示。

2. 提示音:内嵌 Ogg + Web Audio

bundle 里带一段 base64 的 Ogg(//#region embedded-tone 标记块),首次播放时 decodeAudioData 解一次并缓存;同一时刻只允许一个音源,第二次完成不会叠音。

Chromium 在页面收到用户手势前不允许启动 AudioContext,而这正是刚打开窗口的状态。插件不丢弃这一声,而是注册一次性手势解锁并在手势到来后补播,同时在角落显示"点击窗口任意位置即可开启提示音"的小胶囊。窗口被点过之后就不会再出现。

3. 通知与跳转

通知层注册在 shell.overlay(框架自带的浮层槽,position: absolute; inset: 0),卡片自己 fixed 钉在右下角,pointer-events 只开在卡片本身,不挡界面。

点卡片调用 uiWorkspace.openSession(sessionId) —— 会话浏览器、fork 会话用的是同一个入口。悬停会暂停自动消失计时,右上角 × 可以手动关掉。

4. 卡片配色

设置页整体跟随主题的 --dsw-alias-* 变量。右下角卡片额外读一次页面自身的背景色(这些变量在浮层里取不到值),深浅模式各解析一次,让卡片和当前皮肤融合,而不是一块贴上去的白块。


提示音

插件自带三个音效。其中两个是从零做加法合成的 —— 一声干净的钟式「叮」,也就是系统通知音的那种质感 —— 不是从任何产品里抓的采样,所以可以合法随包分发:

| 音效 | 素材 | 说明 | | --- | --- | --- | | 冰冰冰 (bingbingbing) | assets/bingbingbing.ogg | 梗音效,裁成一轮 1.06 秒的三连音,12 642 字节 | | 清脆提示 (crisp-a) | assets/crisp-a.ogg,合成 | 两音上行(F#6 → F#7),付款确认那种干脆感,0.50 秒,6 477 字节 | | 清脆短音 (crisp-b) | assets/crisp-b.ogg,合成 | 三音上行马林巴(D4 → A4 → D5),短信提示那种,0.58 秒,7 068 字节 |

怎么加一个音效

tools/tones.json 是声明音效的唯一地方:

  1. 把 Ogg 放进 assets/;
  2. 在 tools/tones.json 加一行 —— id、label、hint、source,以及 kind (自己合成的写 synth,第三方素材写 recording,后者的来源必须写进 NOTICE);
  3. powershell -NoProfile -ExecutionPolicy Bypass -File tools\embed-tones.ps1;
  4. 升版本号并刷新窗口。

lib/client.js 的音效列表由生成块里的 TONE_DEFINITIONS 构建,所以不用改代码;两个漂移检查 (tools/check-embedded-tone.mjs 与 PowerShell 的 -Check)读的都是这份注册表。

合成脚本是 tools/synthesize_tones.py:每个音是"一叠衰减分音 + 起音处一个极短的带限噪声爆发"(后者就是它让铃声"脆"起来的原因),按手放的起音位置叠进轨道,再混音。改写时有两个坑值得记住:

  • 敲击音要在 dB 上衰减,不是线性幅度。 线性的 exp(-t / tau) 在前一个 tau 内几乎不降,短音听起来就是"从无声渐强"而不是"敲一下"。这里的每个音都按 10 ** (-3 * t / tau) 下落,峰值就落在起音上 —— 开发过程中正是这个错误让最后一个音出现了明显的"渐强",而且渲染包络一看就露馅。
  • 马林巴的高次分音比基频衰减更快,所以 crisp-b 的敲击瞬间听感高五度,再落回基频。第 3、5 分音是整叠里最响的。

重新生成:

python tools/synthesize_tones.py assets            # 生成 WAV 母版
python tools/synthesize_tones.py assets <ffmpeg>   # 再生成插件内嵌用的 Ogg

重新内嵌进 bundle 里那段 //#region embedded-tones 标记块:

powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\embed-tones.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\embed-tones.ps1 -Check   # 素材与生成物不一致时失败
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1

脚本写入前会逐个校验 OggS magic。必须是 Ogg:Chromium 的 decodeAudioData 不解码 mp3,内嵌 mp3 也没有意义。跨平台等价命令:node tools/check-embedded-tone.mjs。

自定义音效:就地裁切

设置里有一项自定义音效。选中本地文件后会解码、画出波形,并打开裁切对话框:拖动首尾两个把手、试听选中的那一段、保存。只有选中的片段会被编码(16-bit PCM WAV —— 浏览器不借助库能写出的唯一容器)并存入设置文档;建议 3 秒以内,超过约 2 MB 会被直接拒绝而不是悄悄截断。

梗音效的来源与再分发注意事项见 NOTICE;两个合成音效没有这个问题,终端用户也随时可以上传自己的。


目录结构

dsh-completion-alert/
├─ package.json             dsh.pluginType=client、dsh.client.inject、bundle patch
├─ cordis.patch.yml         把 completion-alert 行插进 profile
├─ install.ps1              安装(备份、patchReload=live、幂等),-Uninstall 亦可
├─ uninstall-all.ps1        把插件痕迹从 profile 里彻底清掉,不碰别的插件
├─ assets/                  内嵌工具读取的音源
│  ├─ bingbingbing.ogg      梗音效(来源见 NOTICE)
│  ├─ crisp-a.ogg/.wav      合成:两音上行付款提示(F#6 → F#7)
│  └─ crisp-b.ogg/.wav      合成:三音上行短信提示(D4 → A4 → D5)
├─ lib/
│  ├─ index.js              宿主半边:volatile 设置 schema + 诊断路由
│  ├─ client.js             浏览器半边:完成检测 / 播放 / 通知层 / 设置页

├─ tools/
│  ├─ synthesize_tones.py   从零合成清脆音效(numpy)
│  ├─ embed-tones.ps1       把 assets/ 重新嵌进 lib/client.js(-Check 查漂移)
│  ├─ embed-audio.ps1       转发到 embed-tones.ps1 的兼容壳
│  └─ check-embedded-tone.mjs  漂移 + Ogg magic 检查(跨平台,CI 用)
└─ test/
   ├─ host.test.mjs         schema 表面、volatile 标记、诊断路由
   ├─ client.test.mjs       音效库、设置清洗、完成边沿、提示范围、持久化、跳转
   └─ loader.mjs / -hooks   给测试解析 schemastery 这个 peer 依赖

开发

npm install          # 拉取宿主半边 import 的 schemastery peer 依赖
npm test             # 45 项测试
node tools/check-embedded-tone.mjs

测试是行为测试而不是结构快照:客户端测试把真实 bundle 载入 vm 沙箱,配一个 React 桩、生成好的音效模块和伪造的 dsh 客户端上下文,然后直接驱动 store 去断言真正决定行为的那些点 —— 音效库与打包素材逐字节一致、首个快照基线、忙→闲边沿、仅后台会话 范围、去抖写入自己的命名空间、经 uiWorkspace 跳转、以及通知队列。宿主测试校验 schema 表面(包括 volatile 节点位于固定路径、其内部不再套 volatile —— 这是应用会直接拒绝的形状)和诊断路由的往返。

CI(.github/workflows/test.yml)在 Node 24 上跑测试与漂移检查。

已知限制

  • 自定义音效存成 WAV。裁切对话框写 16-bit PCM,因为那是浏览器不借助库能编码的唯一容器;3 秒以内可以保证设置文档不会太大。
  • 只有内嵌那一条路必须是 Ogg。自定义上传 mp3/wav/ogg 都能解码(Chromium 解 mp3 没问题),但构建期内嵌的 payload 必须是 Ogg。
  • 同时只响一声。上一声还没放完时来的新完成会替换它,而不是混在一起。
  • 不走系统级通知。通知是 dsh 自己的浮层卡片,因此不需要 Electron 通知权限,Web 与桌面端表现一致。
  • 提示音有三个,其中一个是梗素材。两个清脆音是我自己合成的,没有版权顾虑;「冰冰冰」按 NOTICE 里的说明对待。

许可

代码 MIT —— 见 LICENSE。内嵌的音频片段是第三方素材,再分发前请读 NOTICE。