dsh-task-complete-notifier — DSH Task Completion Notifier(DSH任务完成提醒)
English | 中文
A DeepSeek Harness plugin that pops up a topmost dark rounded toast card in the bottom-right corner of the screen when an agent task truly finishes. Host-half only, zero runtime dependencies, with a built-in ding (or your own sound file).
Made for DSH Desktop (Electron). Under plain
dsh web(browser) it degrades to host logs (see FAQ).
✨ Features
- Precise timing, fires once: watches the
agent/statuscordis event (running → idleedge). Intermediate turns of multi-turn tasks (goal loops) never fire; a task interrupted by a new message doesn't fire either - Topmost: the toast is an independent Electron
alwaysOnTopwindow — visible even when DSH is in the background or covered by other apps - Solid dark card: opaque
#181818background, 12px radius,#333333border, drop shadow, 30px margin from the bottom-right corner, 0.3s fadeInUp - Three ways to dismiss: click "稍后/Later", click outside the card, or wait for the auto-close
- ⌨️ Reply without switching (v1.1): the toast has an input box at the bottom — type your next instruction, hit Enter, and it's delivered to the session's agent via
agent.followup()(queued as the next turn even if the agent is busy). No need to bring DSH to the foreground - 📛 Shows the conversation title (v1.2): the toast displays the session's task title (folded from
session/titleevents), so you instantly know which task just finished — and which conversation your typed instruction will go to - Queue, never overwrite (v1.1): toasts show one at a time. A new completion queues behind the current toast, so an instruction you're typing is never wiped by the next toast — when you submit or dismiss, the next one appears
- Focuses for typing: the toast focuses its input box on show, so you can start typing immediately
- Skips subagents: only notifies when the main task finishes
- 🔔 Ding sound with a toggle, custom file & volume (v1.4–v1.5): plays a synthesized "ding" (Web Audio) when it appears; a 🔊/🔕 button in the top-right corner toggles it and persists (localStorage). Set
soundFileto any mp3/wav/ogg/m4a to play your own sound,soundVolume(0–1) to control loudness — with a 0.1s fade-in that starts 0.1s early so it never feels delayed - Configurable: text, settle delay, cooldown, auto-close, placeholder, labels — all via
cordis.patch.yml
🖼️ The toast
┌──────────────────────────────────────┐
│ ✓ Task Completed 🔊 │ ← title + sound toggle
│ │
│ [Conversation task title] │ ← #8AB4F8 14px
│ The current DeepSeek Harness task │ ← #AAAAAA 14px / 1.6
│ has finished. Please proceed to │
│ the next step. │
│ │
│ [ Type your next instruction… ] │ ← input box, Enter to send
│ [ 发送 ] [ 稍后 ] │ ← submit / dismiss
└──────────────────────────────────────┘
bg #181818 (opaque) · radius 12px · border #333 · shadow 0 8px 32px
⌨️ Reply right in the toast
When a task finishes, the toast's input box is auto-focused — type your next instruction and press Enter (or click 发送 / Send):
- The instruction is delivered to the session that just finished, through
agent.followup()— the same path DSH's own prompt uses - If that agent is already running something else, your instruction queues as its next turn
- If the session is gone, the toast shows an inline error and stays open so you don't lose what you typed
- Multiple tasks finishing at once? Toasts appear one at a time: while a toast is on screen (you may be typing), later completions wait in a queue (max 5, oldest dropped). Submit or dismiss the current toast and the next appears — your half-typed instruction is never overwritten
🔍 How it works
DSH's agent state machine: running (task executing, including every turn of a multi-turn goal loop) → idle (the whole task is done).
The plugin subscribes to the agent/status cordis event (a reliable host-side signal). On the running → idle edge it waits 3 seconds to confirm the agent doesn't jump straight back to running (goal-round-driver injects the next round quickly between goal turns), then opens an Electron topmost window rendering the card.
Pitfalls we hit along the way (see Development notes):
- ❌ Browser Tampermonkey script — Electron desktop never loads browser scripts
- ❌
session/event— exists only on the host side, client plugins never receive it - ❌ Turn-end signals — tasks often span multiple turns; turn end ≠ task done
- ✅ The
agent/statusidle edge — the precise "task truly finished" signal
📦 Installation
Option 1: tarball (recommended)
Download
dsh-task-complete-notifier-1.5.2.tgzfrom ReleasesInstall with the DSH CLI (
<profile>is your profile name, e.g.desktop):dsh plugin --profile desktop add file:D:\Downloads\dsh-task-complete-notifier-1.5.2.tgzThe command reconciles
dsh.profile.bundlesand installs dependencies for you.Restart DSH Desktop.
Option 2: one-command install script (auto-configures everything)
The bundled install.ps1 does the whole manual install for you — copies the plugin into ~/.dsh/plugins/, registers it in the profile's package.json (dependency + bundle, idempotent), writes the mount row + default config into cordis.patch.yml, runs pnpm install, and prints the restart reminder:
# extract the tarball, then inside the extracted folder:
powershell -ExecutionPolicy Bypass -File install.ps1
# or from a tarball directly / another profile:
powershell -ExecutionPolicy Bypass -File install.ps1 -Profile web -Tarball D:\dsh-task-complete-notifier-1.5.2.tgz
Option 3: manual install
Extract the tarball anywhere (e.g.
C:\Users\<you>\.dsh\plugins\dsh-task-complete-notifier)Edit
~/.dsh/profiles/desktop/package.json:{ "dependencies": { // ...existing deps... "dsh-task-complete-notifier": "link:C:/Users/<you>/.dsh/plugins/dsh-task-complete-notifier" }, "dsh": { "profile": { "bundles": [ // ...existing bundles... "dsh-task-complete-notifier" ] } } }Run
pnpm installinside the profile directory, then restart DSH Desktop.
Verify
After restart, the log shows:
[task-notifier] host half mounted (v9: env webServer=true agents=true electron=true port=61997)
Run a task to completion — the toast card should appear in the bottom-right corner.
🧩 Environment & compatibility
Every dependency is optional — the plugin activates even in a minimal deployment and degrades gracefully. Its mount log is a built-in environment self-check:
[task-notifier] host half mounted (v9: env webServer=true agents=true electron=true port=61997)
| Capability | Used for | When missing |
|---|---|---|
agent/status event (host) |
completion detection | always present in DSH — required in practice |
webServer service |
/task-notifier/* routes |
routes skipped; toast degrades to host logs |
agents service + agent.followup |
typing instructions into the toast | input box hidden from the card; detection still works |
Electron (desktopRuntime) |
topmost card window | toast degrades to host logs (plain dsh web) |
session/title events |
task title on the card | title row simply hidden |
Runtime requirements: Node ≥ 20, DSH with the agent loop (rc.6+ recommended for agent.followup). Zero runtime npm dependencies.
⚙️ Configuration
Add config to the plugin's mount row in your profile's cordis.patch.yml (id-targeted override):
- id: task-complete-notifier
config:
title: '✓ Task Completed' # title text
body: 'The current DeepSeek Harness task has finished. Please proceed to the next step.' # body text
settleMs: 3000 # confirm delay after idle (guards against goal-round false positives)
cooldownMs: 10000 # minimum gap between toasts
autoCloseMs: 60000 # auto-close timeout
placeholder: '输入下一步指令,Enter 发送…' # input placeholder
sendLabel: '发送' # submit button label
laterLabel: '稍后' # dismiss button label
soundEnabled: true # default sound state (card button overrides it and persists)
soundToggleTitle: '音效开关' # sound button tooltip
soundFile: '' # custom sound file (absolute path to an mp3/wav/ogg/m4a)
soundVolume: 1.0 # sound volume 0~1
🎛️ Full configuration reference
Every key lives under config: in the plugin's mount row. All keys are optional — omit any key to use its default.
| Key | Type | Default | What it does |
|---|---|---|---|
title |
string | '✓ Task Completed' |
Title text at the top of the card |
body |
string | 'The current DeepSeek Harness task has finished. Please proceed to the next step.' |
Body text |
settleMs |
number | 3000 |
Wait after the idle edge before showing the card — guards against goal-round false positives |
cooldownMs |
number | 10000 |
Minimum gap between two toasts |
autoCloseMs |
number | 60000 |
Auto-close timeout for the card |
placeholder |
string | '输入下一步指令,Enter 发送…' |
Input box placeholder |
sendLabel |
string | '发送' |
Submit button label |
laterLabel |
string | '稍后' |
Dismiss button label |
soundEnabled |
boolean | true |
Default sound state; the card's 🔊/🔕 button overrides it and persists |
soundToggleTitle |
string | '音效开关' |
Tooltip for the sound button |
soundFile |
string | '' |
Absolute path to your own sound file (mp3/wav/ogg/m4a); empty = built-in ding |
soundVolume |
number | 1.0 |
Sound volume, 0–1 (e.g. 0.5 = half volume) |
🔔 Custom sound
By default the toast plays a built-in synthesized "ding". You can replace it with your own audio file — set soundFile to an absolute path of any Chromium-supported audio (mp3 / wav / ogg / m4a …):
- id: task-complete-notifier
config:
soundFile: 'D:\Music\my-notification.mp3'
soundVolume: 1.0
The host half serves that file to the card over the same-origin /task-notifier/sound route (loopback-fenced). Leave soundFile empty to fall back to the built-in ding. The sound fades in over 0.1s and starts 0.1s early so the fade doesn't feel delayed.
🔊 Volume
soundVolume scales loudness from 0 (silent) to 1 (full). Use 0.5 for half volume — handy when the default ding or your custom sound is too loud. It applies to both the built-in ding and a custom soundFile:
- id: task-complete-notifier
config:
soundVolume: 0.5 # 50% volume
❓ FAQ
Q: Does it work with plain dsh web (browser)?
A: Detection works (the signal lives host-side), but the Electron topmost window is unavailable, so it degrades to host logs ([task-notifier] ✓ Task Completed — ...). Use DSH Desktop for the full experience.
Q: Black edges / blocks around the card?
A: A few GPU drivers render transparent windows poorly. The card itself is opaque #181818; if issues persist, open an issue with your GPU model.
Q: Does interrupting a task (stop button) notify?
A: Interrupts go through the aborted path, not the normal completion edge. If the agent still settles back to idle after the interrupt, you get one toast (the agent did stop).
Q: Multiple sessions running tasks at once — multiple toasts? A: Toasts show one at a time and queue behind the current one (max 5, oldest dropped). Submit or dismiss the current toast and the next appears — you'll never lose a half-typed instruction.
Q: Where does my typed instruction go?
A: To the session whose task just finished (the toast is bound to that session). It's delivered through agent.followup() — if the agent is busy it runs as the next turn; if the session is gone the toast shows an inline error.
Q: Is the input route secure?
A: Both plugin routes (/task-notifier/toast, /task-notifier/input) sit behind the same loopback + same-origin trust fence as DSH's own /api gateway. Cross-site pages and non-loopback hosts are refused (403).
🛠️ Development notes
| Version | Approach | Result |
|---|---|---|
| v0 | Browser Tampermonkey userscript | ❌ Electron desktop never loads it |
| v1 | Client half + session/event listening for turn/end |
❌ event exists host-side only; client never receives it |
| v2 | Client half + turnTail slot / running edge | ⚠️ flaky, fired mid-task, re-fired on session switch, not topmost |
| v3 | Host half + agent/status + system notification |
✅ precise & topmost, but native styling |
| v4 | Host half + agent/status + Electron topmost card window |
✅ precise + custom card + topmost |
| v5 | v4 + reply input box + toast queue | ✅ type the next instruction right in the toast; completions queue instead of overwriting |
| v6 | v5 + conversation title on the toast | ✅ shows which task finished (session/title fold) |
| v7 | v6 + ding sound with per-card toggle | ✅ audible cue; toggle persists across toasts |
| v8 | v7 + soften the ding (660Hz triangle + fade-in) | ✅ pleasant, not harsh |
| v9 | v8 + custom sound file + soundVolume + 0.1s early trigger |
✅ your own audio, volume 0–1, no perceived delay |
📄 License
MIT © 2026 YEYU
还没有评论,来写第一条。