DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

ruazero /

ruazero/dsh-task-chime

Verified

DSH dynamic Cordis plugin: play a real Windows system sound when an agent task finishes — custom sound, 4 intensity levels, auto escalation for long tasks, temporary mute.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@65750575

dsh-task-chime

English | 中文

A plugin for DeepSeek Harness (DSH) that plays a real operating-system sound every time an agent turn finishes — so you can look away from the screen during a long task and still know the moment it is done.

Unlike a browser notification, the sound is produced by the Host process through the system audio device, so it reaches you when the DSH window is minimized, in the background, or on another virtual desktop.

turn ends  ──►  agent/status: idle  ──►  ctx.shell  ──►  🔔 C:\Windows\Media\notify.wav

Features

Real system sound Played by the Host through ctx.shell (PowerShell System.Media.SoundPlayer / [console]::Beep on Windows), not by the web page — audible while DSH is in the background
Two distinct moments Task finished and “the agent is blocked on you” (a question or a pending approval), each with its own sound and intensity so you can tell them apart by ear
13 sound choices System effects (notify, ding, chimes, tada, chord, calendar, messaging, exclamation, alarm, ring), two pure beep patterns that need no audio file, plus any custom audio path
4 intensity levels L1 light (1 play) · L2 standard (2 plays) · L3 strong (rising prelude + 3 plays) · L4 alarm (two-way prelude + 5 plays)
Auto escalation A task over 2 minutes rings one level higher; over 10 minutes, two levels higher (capped at L4)
Trigger scope Top-level sessions only (default), or include subagent sessions
Duration threshold Stay silent for turns shorter than N seconds, so quick answers do not beep
No spurious chimes A completion ring requires an observed running → idle transition, and an approval rings only if it is still pending after a grace period — so opening a session or an auto-answered policy check stays silent
task_chime tool An optional model tool, so you can just say "ring me" and the agent triggers a chime on demand

Why the second moment needs its own trigger

While the agent waits for you — ask_user_question, exit_plan_mode, an approval prompt — its status stays running. agent/status never reaches idle, so the completion chime cannot fire. Without a separate trigger, the one moment that actually needs you is the one moment that makes no sound.

Two ways to run it

The repository ships both halves of the same feature. Pick by how long you want it to live.

A · Profile bundle B · Dynamic plugin
Entry lib/index.js + cordis.patch.yml src/host.js + src/client.js
Install dsh plugin add, then restart cordis_define + cordis_run, no install
Survives a DSH restart yes no
Applies to every session / project yes that one session
Configuration a card in Settings → Plugins, settings.yaml (namespace task-chime), or the composition entry in-memory, lost on restart
UI a settings card in Settings → Plugins a panel in Settings and in the cordis_run card, additionally offering sound preview, temporary mute, and a chime log
Needs approval no yes (a Client half always asks)

Mode A is the one to install. Mode B stays useful for trying a change instantly, or for the richer panel.

A · Install as a profile bundle (permanent)

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

Then restart the Harness (quit and reopen DSH Desktop, or restart your dsh web process). That is all: the package declares dsh.bundle.patch, so its composition row is applied automatically — you never edit a composition file by hand.

Verify before restarting, without starting anything:

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

To remove it:

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

Configuration

Three layers, in order of authority:

  1. The settings card — Settings → Plugins → Task completion chime. Every control writes immediately; a changed field shows a customized badge whose Reset removes the override instead of writing a default over it.
  2. settings.yaml, namespace task-chime — the same durable document the card writes, watched live: a hand edit takes effect on the next turn without a restart.
  3. The composition entry in cordis.patch.yml — the base layer, and the fallback whenever no settings service is mounted.
# settings.yaml
task-chime:
  enabled: true            # master switch for every chime
  sound: notify            # notify | ding | chimes | tada | chord | calendar |
                           # messaging | exclamation | alarm | ring |
                           # beep-triad | beep-low | custom
  customPath: ''           # absolute audio path, used only when sound is `custom`
  level: 2                 # 1 light · 2 standard · 3 strong · 4 alarm
  autoEscalate: true       # +1 level past 2 min, +2 past 10 min
  minDurationSec: 0        # stay silent below this turn duration
  scope: roots             # roots = top-level sessions only · all = include subagents
  registerTool: true       # expose the on-demand `task_chime` tool

  # ring when the agent is blocked on you
  notifyOnInput: true      # questions and pending approvals
  inputSound: messaging    # same catalog; keep it different from `sound`
  inputLevel: 3            # its own intensity
  inputTools:              # tools that block until a human answers
    - ask_user_question
    - exit_plan_mode
  approvalDelayMs: 1200    # grace period; a policy-answered approval stays silent

To pin a value in the composition instead, override the row by id in your profile's own cordis.patch.yml:

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

Intensity levels

Level Prelude Plays Gap
L1 light — 1 —
L2 standard — 2 0.22s
L3 strong rising triad (C6–E6–G6) 3 0.4s
L4 alarm rising + falling triad 5 0.65s

B · Run as a dynamic plugin (this session only)

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

Then paste this into a DSH session that has the Cordis tools (the shipped cordis preset does):

Read src/host.js and src/client.js from <path to the clone>, then call cordis_define with plugin.kind: "new", idPrefix: "chime", code.host = the full contents of src/host.js, and code.client = the full contents of src/client.js. Then activate it with cordis_run in run mode.

Approve the run when the card appears. This mode adds a custom control panel — sound picker with preview, the four intensity buttons, trigger scope, duration threshold, temporary mute (10/30/60 min), and a log of the last 12 chimes — in Settings → 任务提示音 and inside the cordis_run card.

Each src/*.js file is the plain-JavaScript function body the dynamic evaluator expects — it ends with return { apply(ctx) { … } }. Do not wrap it in a module and do not add import/require: neither exists in that sandbox. Details and troubleshooting: INSTALL.md.

How it works

Task finished

  1. The Host listens to the event agent/status. It fires running ⇄ idle; idle means no driver remains scheduled or active — the turn is genuinely over, not merely between steps.
  2. A start timestamp is recorded on running. On idle the plugin computes the task duration and applies, in order: the enable flag, the trigger scope, the duration threshold, and long-task escalation. A ring requires a previously observed start, so a resumed session never rings on its own.
  3. It builds a small shell script — SoundPlayer.Load() plus repeated PlaySync(), or [console]::Beep patterns — and runs it through ctx.shell.resolve() + ctx.shell.run(), fire-and-forget: the agent never waits for the sound.
  4. The sandbox policy is resolved from the completing session. Since the command writes nothing, a read-only session is raised to workspace-write for the single purpose of keeping PowerShell in FullLanguage; nothing wider is ever requested.
  5. Every capability is probed rather than assumed. No shell, no agents, no settings, or no tools degrades one feature instead of failing the plugin row.

Blocked on you

  1. A question is caught on the tools/pre-execute waterfall: when the pending call's name is in inputTools, the input-request sound plays and the decision is delegated untouched — the plugin never influences whether a call is allowed.
  2. A pending approval is caught on the approval/request waterfall. Ringing immediately would fire for approvals a policy answers on its own, so the plugin arms a timer for approvalDelayMs and clears it in a finally block: only a request still open after the grace period — i.e. genuinely waiting for you — makes a sound. Armed timers are cleared on fiber teardown, so none outlives the plugin.

The settings card

The shipped Plugins settings section enumerates the settings namespaces the Host serves and dispatches settings.plugin.item keyed by each namespace, rendering whichever card claims that key — a served namespace that no card claims renders nothing at all. The Host half registers the namespace task-chime, so lib/client.js claims exactly that key and the card appears beside the shipped shell and agent-loop cards.

lib/client.js is a browser module written directly in the client wire format (window.__ModuleLoader__.load({ id, factory })) rather than produced by a bundler: it requires nothing but react, so a build step would add a toolchain without adding behaviour. Writes go through the bound settings scope (ctx.settingsScope.bind({ namespace: 'task-chime' }) → set / unset), which is the same durable document the Host half watches — so there is no private RPC between the halves and no second source of truth.

Platform support

Status
Windows 10/11 Verified. PowerShell System.Media.SoundPlayer + [console]::Beep, sounds from C:\Windows\Media\
macOS Best-effort, unverified: afplay with /System/Library/Sounds/*.aiff, osascript -e beep
Linux Best-effort, unverified: paplay (falling back to aplay) with /usr/share/sounds/freedesktop/stereo/*.oga

A report from macOS or Linux is a bug, not an expected limitation — please open an issue.

Developing

npm test      # 25 checks, silent: no sound is played

test/smoke.mjs (17) drives the Host apply() against a fake Cordis context and asserts the exact command shape per level, custom-path quoting, the no-observed-start guard, scope filtering, the duration threshold, sandbox-policy handling, live settings precedence, and every degraded path.

test/client.mjs (8) loads the browser module the way the module loader does and checks the slot registration contract, that each control writes its own field through the settings scope, that a reset clears an override instead of writing a value, that a read-only or still-loading document locks every control, and that the card renders under real React.

Both halves resolve their dependencies from the host profile. For a local run, link them into ./node_modules (git-ignored):

# Windows example; point at your own profile's 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"
}

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.

Roadmap

  • Verified macOS / Linux backends
  • Per-outcome sounds (success vs. error vs. awaiting input)
  • Optional Windows toast notification alongside the sound
  • Temporary mute and sound preview in the composed mode (mode B already has both)

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 657505755e9a

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout