全部插件

DSH / BUNDLE / CLIENT-UI

dsh-task-chime

v1.3.0ruazero / dsh-task-chime657505755e

可安装组合包UI 与客户端插件社区 · Topic 自动分析Web UI

概览

dsh-task-chime

Ring a real system sound when a DeepSeek Harness agent finishes a task OR needs your input: custom sounds, 4 intensity levels, auto escalation, and a settings card in the GUI.

README / ZH

插件文档

dsh-task-chime

English | 中文

一个用于 DeepSeek Harness (DSH) 的插件:每次对话任务完成时播放真正的操作系统提示音,让你在长任务期间可以放心离开屏幕,任务一结束立刻知道。

它不是浏览器里的提示音——声音由 Host 进程经系统音频设备发出,所以 DSH 窗口最小化、切到后台、甚至在另一个虚拟桌面时,你照样听得见。

一轮对话结束  ──►  agent/status: idle  ──►  ctx.shell  ──►  🔔 C:\Windows\Media\notify.wav

功能

真·系统提示音 由 Host 经 ctx.shell 播放(Windows 上是 PowerShell 的 System.Media.SoundPlayer / [console]::Beep),而非网页播放,后台也能听见
两个时刻分开提醒 任务完成「Agent 卡在等你」(提问或等待授权)各有独立音效与强度,一听就能分辨
13 种音效 系统音效(notifydingchimestadachordcalendarmessagingexclamationalarmring)、两种不依赖音频文件的纯蜂鸣,以及任意自定义音频路径
4 级提醒强度 L1 轻提示(响 1 次)· L2 标准(响 2 次)· L3 强提醒(升调前奏 + 响 3 次)· L4 闹钟级(双向前奏 + 响 5 次)
长任务自动升级 任务超过 2 分钟自动 +1 级,超过 10 分钟再 +1 级(上限 L4)
触发范围 默认仅主会话,可选包含子代理会话
时长门槛 低于 N 秒的快速回答不响铃
不会误响 完成提醒必须观察到 running → idle 完整跃迁;授权提醒要过了宽限期仍未落定才响 —— 所以打开会话、或被策略自动应答的授权,都是安静的
task_chime 工具 可选的模型工具,你直接说"响一下",Agent 就能按需触发

为什么第二个时刻需要单独的触发点

Agent 在等你的时候 —— ask_user_questionexit_plan_mode、授权弹窗 —— 它的状态仍然是 runningagent/status 永远到不了 idle,所以完成提醒根本不会响。没有这个单独触发点,最需要你出现的那一刻恰恰是最安静的一刻。

两种运行方式

仓库同时提供同一功能的两个半边,按你希望它活多久来选。

A · Profile Bundle B · 动态插件
入口 lib/index.js + cordis.patch.yml src/host.js + src/client.js
安装 dsh plugin add 后重启 cordis_define + cordis_run,无需安装
重启后仍生效
对所有会话/项目生效 仅当前会话
配置 设置 → 插件 里的卡片settings.yaml(命名空间 task-chime)或 composition 条目 内存态,重启即失
界面 设置 → 插件 里的设置卡片 设置页 + cordis_run 卡片内的面板,另有试听、临时静音、提醒日志
需要授权 是(含 Client 半必须授权)

要长期用就装 A。B 仍然有用:改一行立刻生效,而且面板更丰富。

A · 作为 Profile Bundle 安装(永久生效)

dsh plugin --profile web add github:ruazero/dsh-task-chime

然后重启 Harness(退出并重开 DSH Desktop,或重启你的 dsh web 进程)。就这样:包内的 dsh.bundle.patch 声明会让它的 composition 行自动生效,你不需要手工编辑任何 composition 文件

重启前可以先离线校验(不启动任何东西):

dsh --profile web --dump-config | grep -A2 task-chime

卸载:

dsh plugin --profile web remove dsh-task-chime

配置

三层,按优先级从高到低:

  1. 设置卡片 —— 设置 → 插件 → 任务完成提示音。每个控件即改即写;被改过的字段会显示「已自定义」徽标,其恢复默认是删除覆盖值,而不是把默认值写进去。
  2. settings.yamltask-chime 命名空间 —— 卡片写的就是这份持久化文档,且被实时监听:手工改完下一轮就生效,无需重启。
  3. cordis.patch.yml 里的 composition 条目 —— 基础层,也是没有 settings 服务时的兜底。
# settings.yaml
task-chime:
  enabled: true            # 总开关,管住所有提醒
  sound: notify            # notify | ding | chimes | tada | chord | calendar |
                           # messaging | exclamation | alarm | ring |
                           # beep-triad | beep-low | custom
  customPath: ''           # 仅当 sound 为 custom 时使用的音频绝对路径
  level: 2                 # 1 轻提示 · 2 标准 · 3 强提醒 · 4 闹钟级
  autoEscalate: true       # 超 2 分钟 +1 级,超 10 分钟 +2 级
  minDurationSec: 0        # 低于该时长不响
  scope: roots             # roots = 仅主会话 · all = 含子代理
  registerTool: true       # 是否暴露按需触发的 task_chime 工具

  # Agent 卡在等你时提醒
  notifyOnInput: true      # 提问与待授权
  inputSound: messaging    # 同一音效表;建议与 sound 不同
  inputLevel: 3            # 独立强度
  inputTools:              # 这些工具一被调用就会等你回答
    - ask_user_question
    - exit_plan_mode
  approvalDelayMs: 1200    # 宽限期;被策略自动应答的授权不会响

若想固定写进 composition,在你 profile 自己的 cordis.patch.yml 里按 id 覆盖:

- id: task-chime
  config:
    level: 3
    sound: chimes

强度对照

等级 前奏 播放次数 间隔
L1 轻提示 1
L2 标准 2 0.22s
L3 强提醒 升调三音(C6–E6–G6) 3 0.4s
L4 闹钟级 升调 + 降调三音 5 0.65s

B · 作为动态插件运行(仅当前会话)

git clone https://github.com/ruazero/dsh-task-chime.git

然后在具备 Cordis 工具的 DSH 会话里(自带的 cordis preset 即可)粘贴:

读取 <克隆目录> 下的 src/host.jssrc/client.js,然后调用 cordis_defineplugin.kind: "new"idPrefix: "chime"code.hostsrc/host.js 全文、code.clientsrc/client.js 全文。随后用 cordis_runrun 模式激活。

出现卡片时点允许。这种方式额外提供一个自定义控制面板——音效选择与试听、四个强度按钮、触发范围、时长门槛、临时静音(10/30/60 分钟)、最近 12 次提醒日志——位于设置 → 任务提示音以及 cordis_run 卡片内。

两个 src/*.js 都是动态求值器所需的函数体纯 JavaScript —— 以 return { apply(ctx) { … } } 结尾。不要包成模块,也不要加 import/require:那个沙箱里没有这些东西。细节与排错见 INSTALL.md

工作原理

  1. Host 订阅 agent/status 事件。该事件在 runningidle 间切换;idle 表示已无 driver 在排队或运行 —— 这一轮真的结束了,而不是步骤之间的间隙。
  2. running 时记录起始时间戳。idle 时算出任务用时,并依次判断:总开关、触发范围、时长门槛、长任务升级。必须先观察到起始事件才会响,因此恢复会话不会自己响。
  3. 拼出一小段 shell 脚本(SoundPlayer.Load() + 多次 PlaySync(),或 [console]::Beep 音型),经 ctx.shell.resolve() + ctx.shell.run() 以 fire-and-forget 方式执行:Agent 绝不等待声音播完。
  4. 沙箱策略取自完成的那个会话。由于该命令不写任何文件,仅当会话为 read-only 时提升为 workspace-write,唯一目的是让 PowerShell 保持 FullLanguage;绝不申请更宽的权限。
  5. 所有能力都是探测而非假定:缺 shell、缺 agents、缺 settings、缺 tools,都只降级一个特性,而不会让插件行加载失败。

卡在等你

  1. 提问tools/pre-execute 瀑布上捕获:待执行调用的名字命中 inputTools 就播放操作提醒音,然后原样把决策交给下游 —— 插件绝不干预某个调用是否被允许。
  2. 待授权approval/request 瀑布上捕获。立刻响会把被策略自动应答的授权也一起响掉,所以插件先武装一个 approvalDelayMs 定时器,并在 finally 里清除:只有过了宽限期仍然挂着(即真的在等你)的请求才会发声。已武装的定时器在 fiber 拆卸时统一清除,不会有残留。

设置卡片是怎么出现的

内置的「插件」设置分区会枚举 Host 供给的 settings 命名空间,并以每个命名空间为 key 派发 settings.plugin.item,渲染认领该 key 的那张卡片 —— 一个被供给但没有卡片认领的命名空间,什么都不渲染。Host 半注册了 task-chime 命名空间,因此 lib/client.js 只要认领这个 key,卡片就会出现在内置的 shell、agent-loop 卡片旁边。

lib/client.js 是直接按客户端 wire 格式(window.__ModuleLoader__.load({ id, factory }))手写的浏览器模块,没有经过打包器:它除了 react 什么都不 require,引入构建链只会增加工具负担而不增加任何行为。写入走绑定的 settings scope(ctx.settingsScope.bind({ namespace: 'task-chime' })set / unset),也就是 Host 半已经在监听的那份持久化文档 —— 因此两个半边之间不需要私有 RPC,也不存在第二份"真相"。

平台支持

状态
Windows 10/11 已验证。 PowerShell System.Media.SoundPlayer + [console]::Beep,音效取自 C:\Windows\Media\
macOS 尽力实现、未经验证:afplay + /System/Library/Sounds/*.aiffosascript -e beep
Linux 尽力实现、未经验证:paplay(回退 aplay)+ /usr/share/sounds/freedesktop/stereo/*.oga

macOS / Linux 上的问题请当 bug 提 issue,而不是"已知限制"。

开发

npm test      # 25 项检查,全程静音,不会真的播放

test/smoke.mjs(17 项)用 fake Cordis 上下文驱动 Host 的 apply(),逐项断言:各等级的命令形状、自定义路径的引号转义、"未观察到起始就不响"的守卫、触发范围过滤、时长门槛、沙箱策略处理、settings 的实时优先级,以及各条降级路径。

test/client.mjs(8 项)按模块加载器的方式加载浏览器模块,断言:插槽注册合同、每个控件是否只写自己那个字段、恢复默认是否走 unset 而不是写值、只读/加载中时是否锁死全部控件,以及在真实 React 下能否渲染。

两个半边的依赖都来自 host profile。本地跑测试时链接进 ./node_modules(已被 git 忽略):

# Windows 示例;请指向你自己 profile 的 node_modules
$profile = "$env:APPDATA\dsh-desktop\harness\profiles\node_modules"
New-Item -ItemType Directory node_modules\@deepseek-ai -Force
foreach ($m in 'schemastery','dsh-tools','dsh-settings') {
  New-Item -ItemType Junction "node_modules\@deepseek-ai\$m" -Target "$profile\@deepseek-ai\$m"
}
foreach ($m in 'react','react-dom','scheduler') {
  New-Item -ItemType Junction "node_modules\$m" -Target "$profile\$m"
}

限制

  • 方式 B 是临时的。 动态插件及其配置只存活于当前 DSH 进程的内存中;方式 A 才是永久形态。
  • 卡片里没有试听。 试听需要从浏览器调 Host,而组合插件只能通过发布 Remote 服务做到;想试听就让 Agent 调用 task_chime
  • registerTool 需要重启。 它在插件行激活时只读一次,所以在卡片里改它要下次启动 Harness 才生效;其余字段都是下一轮即生效。
  • PlaySync 会阻塞它自己的子进程直到播放结束 —— 这是刻意的,重复播放的节奏正是这样计时的。它不会阻塞 Agent。
  • 所有结果共用一个音效。 成功、报错、等待你输入,目前响法相同。

路线图

  • 经过验证的 macOS / Linux 后端
  • 按结果区分音效(成功 / 出错 / 等待输入)
  • 可选的 Windows 通知气泡(与声音并存)
  • 组合模式下的临时静音与试听(方式 B 两者都有)

许可

MIT

LIMITATIONS

已知限制

- **Mode B is ephemeral.** A dynamic plugin and its configuration live in memory for the life of the DSH process. Mode A is the permanent form. - **No sound preview in the card.** Previewing would need a Host call from the browser, which a composed plugin can only do through a published Remote service; ask the agent to call `task_chime` instead. - **`registerTool` needs a restart.** It is read once when the row activates, so toggling it in the card takes effect on the next Harness start. Every other field applies to the next turn. - **`PlaySync` blocks its own child process** for the duration of the sound — by design, since that is how the repeat gaps stay accurate. It never blocks the agent. - **One sound for every outcome.** Success, error, and "waiting for your input" all ring the same way today.