dsh
OpenPetsHQ/openpets将 DeepSeek Harness 的生命周期状态、错误与审批请求,桥接到本地运行的 OpenPets 桌面伙伴。
当 AI 询问澄清性问题时(带问号或调用 ask_user_question)播放提示音。Web Audio API 合成,零音频文件,跨 Windows/macOS/Linux。
Plays a small chime when the AI asks the user a clarifying question, so you don't miss it and can answer promptly. Built for DeepSeek Harness (DSH).
? / ? character in the assistant's message, or an ask_user_question tool call (the standard DSH tool for clarification).Settings ⚙️ → AI Question Chime. Toggle on/off, adjust volume, test the sound. Persists in localStorage.Architectural pattern inspired by
kaixinbaba/dsh-complete-notify. Thanks for sharing the standard DSH plugin layout.
English · 简体中文 · Tiếng Việt
# Global install (or let dsh plugin manage it for you)
npm install -g dsh-ai-question-chime
# Register into the web profile
dsh plugin --profile web add dsh-ai-question-chime
# Restart dsh web to apply
# macOS launchd:
launchctl kickstart -k gui/$(id -u)/com.dsh.dsh-web
# Otherwise just stop and start dsh web manually
dsh plugin --profile web add "github:lediemquynh/dsh-ai-question-chime"
# Restart dsh web
dsh plugin --profile web add link:/path/to/dsh-ai-question-chime
# Edit code → restart dsh web → changes take effect (no reinstall needed)
? in the message, or an ask_user_question tool call) → you'll hear a 2-note ascending chime (988 Hz → 1319 Hz, ≈0.2 s total).dsh-ai-question-chime showing Mounted / 已挂载.AudioContext (any click or keypress also works).dsh plugin --profile web remove dsh-ai-question-chime
# Restart dsh web
.
├── lib/
│ └── index.js # Host entry (Node, ESM) — empty body, just declares the name
├── client/
│ └── client.js # Client entry (browser, CJS self-register) — all real logic lives here
├── cordis.patch.yml # Bundle insert: tells dsh to mount this plugin into the web profile
├── package.json # Declares dsh.bundle.patch + peer dependencies
├── scripts/
│ └── simulate-loader.mjs # Stub of the dsh client module loader, used to test client.js locally
├── README.md # This file (English)
├── README.zh.md # 简体中文
├── LICENSE
└── .gitignore
DSH plugin model
window.__ModuleLoader__.load({ id, factory }). The factory receives a require function and returns a module.exports object.module.exports shape is { name, inject, apply(ctx) }. inject declares which Host services the plugin needs (slots, locale, conversationEvents); the Cordis loader injects them before calling apply.apply(ctx) runs once at plugin boot. Inside, you can:
ctx.locale.register(NS, { zh, en, vi }).ctx.get('conversationEvents').register({ kind, target, match, start, update, buildViewNode }).ctx.slots.inject('settings.section', …).ctx.effect(fn, label).Chime trigger logic
match(event) runs for every client-side conversation event. When event.type === 'assistant/message', it returns a match id (one per event.seq) so the loader calls update.update(context, match) receives the event payload. It reads event.data.message, runs isAssistantQuestion(msg), and if true, calls playChime(cfg.volume).isAssistantQuestion(msg) returns true if:
ask_user_question tool call (deep walk through content blocks), OR? or ? AND the message role is assistant.Sound synthesis
playChime(volume) creates an AudioContext (lazily, on first call), then schedules two sine-wave oscillators:
t=0, duration 0.09 s.t=0.10, duration 0.09 s.GainNode with exponential attack (12 ms) and decay to silence. Master volume is cfg.volume (0–1).AudioContext is unlocked lazily on the first pointerdown / keydown to comply with Chromium's autoplay policy.BEEP_COOLDOWN_MS) prevents repeated chimes when the AI sends multiple chunks in quick succession.npm run check # node --check for both lib/index.js and client/client.js
npm run test:loader # evaluate client.js in a sandboxed __ModuleLoader__ stub
The sandbox script (scripts/simulate-loader.mjs) verifies:
__ModuleLoader__.load is called exactly once with the right id.name, inject (including slots), and apply.apply(ctx) runs without throwing on a stub context.ctx.locale.register, ctx.get('conversationEvents').register, and ctx.slots.inject are all invoked.If you change the plugin, run both commands before committing. CI on awesome-dsh-plugin runs the same checks.
All audio parameters live in client/client.js at the top of the playChime function. Common tweaks:
| What you want | Change |
|---|---|
| Higher / lower pitch | const tones = [[988, 0], [1319, 0.10]]; — try [[659, 0], [880, 0.10]] (lower) or [[1319, 0], [1760, 0.10]] (higher) |
| Longer chime | const dur = 0.09; → 0.15 |
| Three notes instead of two | Add a third tone: tones.push([2093, 0.20]) |
| Louder default | const DEFAULT_CFG = { enabled: true, volume: 0.4 }; |
| No cooldown (chime on every event) | const BEEP_COOLDOWN_MS = 0; |
| Square / triangle / sawtooth wave | osc.type = 'sine'; → 'square', 'triangle', or 'sawtooth' |
After editing, restart dsh web. There's no need to reinstall.
webkitAudioContext fallback included for older Safari).The plugin does not use any experimental Web APIs.
AudioContext. If you switch away from dsh before the AI asks, you might miss the chime.assistant/message events. If the Host side filters or rewrites messages before sending, the detection might miss some questions.package.json (follow semver).git push origin main.npm publish.MIT