dsh-plugin-notify-sounds
English | 中文
A soft chime when the agent is waiting for you — or when it is done.
Walk away from the window without missing anything: an OS notification in the background (click it to jump back), and a "Waiting for you" / "Finished" mark on the taskbar title. Once you have seen it, the plugin goes quiet — it will not nag you like an alarm clock.
Install: DSH → Settings → Plugins → Add plugin, paste the line below and press Install:
dsh-plugin-notify-sounds
🎨 Consistent with DSH's design language
Every visual detail of the settings section comes from DSH's own design system: row geometry, dividers, type sizes and spacing use the official design tokens, the sound picker is the official Menu primitive, the icons come from the official family, and the layout follows the native text-left, control-right row shape. Installing it does not introduce a visually disjoint third-party block into your settings.
✨ What it can do
| What | What you get |
|---|
| Your decision is needed | A chime when the agent raises an approval, a plan review, or a question — separate sounds for approvals and questions |
| Task finished | A chime when a session goes from running to idle |
| Aware even when you are away | An OS notification in the background (body is the approval reason or the question itself; click to return to the window) plus a taskbar title mark |
| Missed it? It reminds again | Unanswered decisions re-ping every 1/5/15/30 minutes — and stop for good once you have seen them, even if you switch away again |
| Only what you care about | Sub-agents (background tasks) are completely silent by default — only your own conversation is announced |
| Quiet when it should be | Quiet hours (e.g. 22:00–08:00, wrapping past midnight), notify-only-when-hidden, and a master switch |
| Your sound, your way | Nine built-in sounds, custom ringtone upload, volume — each feature with its own toggle |
| Update checks | Check npm for a new version and install it in one click: verified against the published digest before anything is replaced, restored on failure; restart DSH to load it |
💡 Why
DSH tells you things visually — status dots, sidebar marks. The moment you switch to another window, you can neither see nor hear anything, while the agent may be stuck waiting for one sentence from you.
This plugin turns "it is waiting for you" and "it is done" into sound. With system notifications and the title mark you know what happened even while away; and once you come back and see it, it goes quiet — the repeat reminder only chases decisions you have never seen.
🔔 Sounds
Bell · Chime · Ding · Drop · Pulse · Arc · Coin · Success · Knock — nine built-in sounds, all synthesized live with Web Audio: no audio files, no network requests.
Or upload your own ringtone (mp3/wav/ogg, ≤512KB, validated and volume-normalized automatically), or pick "None" for notifications only. Each trigger (approval / question / completion) gets its own sound, with preview right in the settings.
⚙️ Settings
Settings → Sounds (between General and Models). Every feature has its own toggle, styled exactly like DSH's own preference pages:
master switch and volume · per-trigger sounds with preview · sub-agent alerts · system notifications · window title alert · repeat interval · quiet hours · hidden-only mode · custom ringtone upload · update checks and one-click update
Everything applies immediately — no restart needed.
📦 Install
🖱️ Simplest: two clicks inside DSH (recommended)
-
In DSH open Settings → Plugins and press Add plugin;
-
Paste the package name into the box (this plugin is published on npm):
dsh-plugin-notify-sounds
(the GitHub repository address https://github.com/BillCx330/dsh-plugin-notify-sounds or a local directory path work too)
-
Press Install, then restart DSH once.
The "install source" dropdown above the box picks the download mirror (mainland China mirror by default; leave it alone unless downloads are slow). DSH warns that a plugin runs with your permissions — this one only reads session state and plays sounds locally; its single request goes to your own DSH to raise the window when you click a notification, and nothing is sent anywhere else.
⬆️ Upgrading
DSH does not update plugins on its own, but this plugin updates itself: Settings → Sounds → Updates can check for a new version and install it in one click. Uninstalling and installing again also works.
🛠️ Other ways
Command line — one command against the profile you actually use:
dsh plugin --profile <your profile> add github:BillCx330/dsh-plugin-notify-sounds
Release archive — download the .tgz from Releases and hand it to dsh plugin add.
Local directory — for development or trying unreleased changes:
dsh plugin --profile <your profile> add ./dsh-plugin-notify-sounds
By hand — add "dsh-plugin-notify-sounds": "github:BillCx330/dsh-plugin-notify-sounds" to dependencies in the profile's package.json, append "dsh-plugin-notify-sounds" to dsh.profile.bundles, then run a package install (e.g. pnpm install) in the profile directory so the dependency lands in node_modules.
The command line writes dsh.profile.bundles and installs the dependency for you. The package ships prebuilt artifacts and declares no lifecycle scripts, so nothing is built at install time. Restart DSH after installing (everyday upgrades that touch only the client half hot-reload).
⚠️ Known limitations
- Hand-edited config: preferences live in the profile's
cordis.patch.yml; values must match the schema (volume 0–100, a preset id, HH:MM times) or the plugin declines to load — just fix the value. The settings UI only ever writes valid values.
- One-click update only swaps files: it replaces the seven files this package ships and never touches the profile's configuration. Downloaded code is never executed — the new code loads on a DSH restart. Let the row report a result before closing the window (an interrupted update restores itself, but there is no reason to gamble); if a future release adds a dependency, that needs installing separately (this one has none).
- Multiple windows fire independently: the same event may chime once per window; turn on notify-only-when-hidden to avoid it.
- Click-to-return is fully implemented and tested on Windows; macOS / Linux fall back to the system deep link and are unverified.
- The window is found by its title — deliberately without inspecting other processes (that trips security software). If DSH ever changes its title format, click-to-return silently stops working; sounds and notifications are unaffected.
- The nav glyph depends on the settings panel's DOM structure and silently falls back to the official gear after a DSH redesign — cosmetic only.
- Notification permission is granted on the Desktop; under restrictive policies the row shows how to allow it manually, everything else keeps working.
- macOS system notifications: the desktop shell grants every permission request, so settings always say "granted" — whether a banner actually appears is up to macOS (System Settings → Notifications → DeepSeek Harness), and unsigned development builds may fail silently. "It says granted but nothing shows" is usually not this plugin.
- Upgrading from v1.0.0: since v1.1.0 approvals and questions are separate tracks, so questions default to "Chime" instead of "Bell" — change it back in settings if you prefer.
🧩 Design notes
For anyone hacking on the code (exact details live in the source comments):
- Triggers never touch the event waterfall: approval/question requests are waterfall events that a late listener can miss, so this plugin reads
uiSession.sessionStatus (the same aggregated pending/running facts the sidebar status dots use) and just observes.
- Repeat cadence: unanswered decisions are checked every 15s; seen means done — appearing while you are at the window, or coming back to it, ends the reminders for good (switching away does not re-arm); a new question replacing an old one resets the timer. A reminder that comes due during quiet hours is dropped rather than deferred.
- Click-to-return: a renderer cannot raise an OS window, so a click POSTs to the Host's fenced
/notify-sounds/focus route and the Host raises it. On Windows the Host calls user32.dll through DSH's own bundled koffi (no new dependency, window calls only, no PowerShell — that shape trips security software and gets blamed on DSH); Desktop vs browser is derived from the Origin header, so a browser click never drags the Desktop window forward.
- Notification collapsing: the
tag is not handed to the platform (a superseded toast swallows clicks aimed at it); the plugin closes the old notification itself and holds every generation's object so a late click always has a handler.
- Title mark: prefix add/remove only, so DSH's own title management is untouched; the written prefix is remembered verbatim, so a language switch neither stacks nor leaks it. "Finished" is unread news — set by a finish that happens while the page is hidden, retired when you return — not a standing idle fact, so it does not reappear on every later switch-away.
- Quiet hours mean forgotten, not queued: a decision or finish that arrives during the quiet range is not recorded at all — no sound, no notification, no title mark — and nothing that was missed overnight is delivered when the window ends.
- Cooldown and robustness: the same trigger does not fire twice within 2s for the same session (a second conversation's decision still rings); volume is guarded with
Number.isFinite (a hand-edited .nan falls back to the default); a refused write snaps the control back and shows an error row; toggles disable mid-write against double-tap races.
- The volume slider is uncontrolled: dragging paints one CSS custom property and only the release commits, so it never stutters or snaps back.
- Theme and layout:
--dsw-alias-* tokens throughout, section and row geometry copied from the official preference pages; sound pickers use the official Menu dropdown so nothing scrolls sideways.
📜 Changelog
-
v1.6.9 (2026-10-12) Fixes a README filename compatibility issue in v1.6.8: reverted to README.md (English) + README.zh.md (Chinese) so the update whitelist in every prior version can recognise them. A new filename would be refused by old users' updates — the whitelist is one-directional because the update runs old code.
-
v1.6.8 (2026-10-12) Review fixes and optimizations: the auto-check first-frame timing gap is closed (undefined is no longer treated as permission to reach out); an orphaned JSDoc is removed; the teardown assertion now actually verifies listener removal; normalizedGain samples every 8th frame (2.6M → 330K iterations for a 30-second upload); the master gain disconnects after playback ends (no more audio-node leak); swapNavIcon is wrapped in try/catch and preserves the original node for restore on dispose (React re-renders can no longer crash it); the cooldown table evicts the oldest entry instead of clearing all; and sessionRows caches one snapshot per synchronous block.
-
v1.6.7 (2026-10-12) Three fixes from the review: a macOS browser tab is no longer mislabeled "not supported" for the title alert (isDesktopShell is the right criterion, not isMac alone); the first-frame timing gap in the auto-check guard is closed (undefined is no longer treated as permission); and three comments that no longer matched the code are corrected. Plus BUG-1 regression tests covering the suspended AudioContext path.
-
v1.6.6 (2026-10-12) Nine fixes from an audit: sounds are no longer lost under the browser autoplay policy, and no longer "banked" to fire together at some later click (first-gesture unlock, and nothing is scheduled while the context cannot play); the macOS window raise now activates by bundle id with the deep link as fallback and logs its failures; update rollback is complete — files the update added are removed again, no temp file survives, and the backup is cleaned up once the swap succeeds; checks and updates are single-flight, so two pages clicking at once neither collide nor double-request; turning automatic checks off now really keeps the plugin quiet; a mobile browser that refuses to build a notification is reported as unsupported instead of showing a switch that can never work; and the nav glyph is back on the artwork's own 17×17 coordinate system. Platform notes are now labelled where they apply: the title alert says macOS does not support it, and repeat reminders note that background browser tabs throttle timers.
-
v1.6.5 (2026-10-11) Quiet hours now mean forgotten, not queued: decisions and finishes that arrive during the quiet range get no reminder and no title mark, and nothing that was missed overnight is delivered when the window ends; reminders that come due inside it are dropped rather than deferred. The update outcome now appears at the top of the section (visible the moment the page reloads, no scrolling), and the update row's copy is simpler.
💻 Development
npm install # once: the Host tests need the schemastery devDependency
node test/smoke.mjs # client half: assembly, triggers, cooldown, quiet hours, repeat reminders, notification clicks, sub-agent filtering
node test/host.mjs # host half: config schema, focus route, title matching, cross-half contracts
To publish to npm: bump the version, then npm publish (npm login and 2FA the first time). npm versions are immutable — bump for every change.
dsh-plugin-notify-sounds/
├── package.json # dsh.bundle.patch + dsh.client
├── cordis.patch.yml # Loader row: id: notify-sounds
├── lib/
│ ├── index.js # Host half: config schema + click-to-return route
│ └── client.js # Client half: sound engine, status watcher, settings section
└── test/
├── smoke.mjs # client half
└── host.mjs # host half
package.json is publishable as-is (files carries runtime artifacts only, @deepseek-ai/schemastery is a peerDependency plus a devDependency copy for the Host tests, pure ESM with no build step) — npm publish whenever you want it on npm.
📄 License
MIT © BillCx330