Desktop notifications for DeepSeek Harness — a web notification while a browser page holds the plugin (turn completion and pending questions), and a native Windows toast from the host for finished turns once the browser is closed.
Install
# from a prebuilt release tarball
dsh plugin --profile web add "https://github.com/TaoYe599/dsh-notify/releases/download/v0.1.0/dsh-notify-0.1.0.tgz"
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:TaoYe599/dsh-notify
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
System notifications for DeepSeek Harness: know when a task finishes, or when DSH is waiting on you.
A web notification while a browser page is open; a native Windows toast raised by the DSH host when the browser is closed.
What it solves
DSH needs you in exactly two states:
| State | How it shows up | Notification |
|---|---|---|
| It is waiting for you | ask_user_question, an approval request, a plan confirmation |
"Needs your reply / confirmation" |
| It is done with you | A conversation goes from running to idle (the turn finished) |
"Task finished" |
Coverage follows one rule: whichever process is still alive when the turn ends raises the notification.
| Where you are | Who notifies | Channel |
|---|---|---|
| Looking at DSH | nobody | the state change is already on screen |
| Another tab / another app / browser minimised | the page | web notification (click focuses the tab) + (N) unread badge in the tab title |
| Browser closed | the DSH host | native Windows toast via PowerShell 5.1 + WinRT ToastNotificationManager |
Design notes
- Layered by surviving process. A web notification is physically impossible once the page is gone, so the host half takes over — the only process still able to speak.
- No double notifications. A presence handshake keeps the two apart: each page pings
/dsh-notify/presenceevery 20s while it holds the plugin and sends asendBeaconfarewell onpagehide. While any page is alive the host stays silent and lets the client own delivery — only the page knows whether you are actually looking. - Invisible by default. No permanent settings row. One small card asks once for notification permission; if the browser refuses to grant it (Edge's "quiet notification requests" can swallow the prompt entirely) the card comes back with the reason instead of failing silently.
- No lost events. Detection reads the authoritative client stores through the
shell.overlaystandard props, so delivery is push-driven by DSH's own connection — no polling, and therefore no background-tab timer throttling. - Subagents do not notify. The host only reports top-level sessions (
agents.roots()); a subagent finishing inside a running turn is not news.
Install
This package is a standard DSH bundle (it declares dsh.bundle.patch and ships one configuration layer). Install it with the official CLI — dsh plugin forwards its arguments to pnpm inside the profile directory and appends the bundle to dsh.profile.bundles automatically.
# from a local checkout
dsh plugin --profile web add <path-to-this-package>
# from a tarball (handy for sharing)
pnpm pack
dsh plugin --profile web add ./dsh-notify-0.1.0.tgz
# from npm or GitHub
dsh plugin --profile web add dsh-notify
dsh plugin --profile web add github:TaoYe599/dsh-notify
Verify without booting:
dsh --profile web --dump-config # should print a "# == dsh-notify" layer
Restart dsh web after installing or updating. The bundle list and the host half are read at profile boot; client-bundle-only changes just need a page refresh.
Uninstall
dsh plugin --profile web remove dsh-notify
then restart. To disable without removing, set disabled: true on this plugin's row in the profile's cordis.patch.yml.
Why this is a community plugin rather than a PR
The official repository has has_pull_requests: false — pull requests are disabled at the repository level (issues too; only Discussions are open). Its CONTRIBUTING.md states that external pull requests cannot be accepted at the moment and asks contributors to grow the ecosystem instead, associating their project with the dsh-plugin topic, adding that "we do not believe that packages in the official repository are inherently more important than packages created by the community."
Troubleshooting
The host half appends one diagnostic line per event to ~/.dsh/dsh-notify.log.jsonl (or under $DSH_HOME):
| event | meaning |
|---|---|
presence-route |
the presence route registered (the handshake is live) |
toast |
a native notification was raised (sessionId / title / presenceReady) |
skip + reason: page-alive |
a page is alive, so the client owns this one |
skip + reason: subagent |
a subagent session — not reported |
skip + reason: repeat-window |
same session went idle twice within 10s |
toast-failed |
the PowerShell call failed (with the error) |
no-web-server |
the webServer service was unavailable, so presence cannot gate delivery |
Client-side logging goes to the browser console with a [dsh-notify] prefix.
Known boundaries
- Native notifications are Windows-only for now. Detection still runs elsewhere; only the display step is skipped.
- Presence TTL is 150 seconds. A clean browser exit sends the farewell beacon immediately; if the browser crashes, the host takes over within 150 seconds.
- Remote access may double-notify. When DSH is reached through a non-localhost origin, the presence ping is a same-origin request; if that path is not proxied the host concludes "no page" and both channels fire. Local
127.0.0.1is unaffected. - Subagent sessions are intentionally silent.
Layout
dsh-notify/
├── package.json # dsh.bundle.patch + dsh.client(platform: web)
├── cordis.patch.yml # inserts this plugin's row into the profile composition
├── lib/
│ ├── index.js # host half: presence route + turn-end detection + Windows toast
│ └── client.js # client bundle (hand-written; no build toolchain)
├── LICENSE
└── README.md # 中文说明
Links
More in this category
xmanrui/dsh-im★ 1420
Connect IM bots to DeepSeek Harness via QR codes or bot credentials (9 channels: Feishu, WeChat, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp).
shaobeichen/dsh-pocket★ 1259
Remote phone access to the DSH Web UI: scan a QR code for LAN or public (cloudflared tunnel) access with real-time sync, a mobile-adaptive layout, and a settings tab.
alvinunreal/openpets#dsh★ 1217
Bridges DeepSeek Harness lifecycle status, errors, and approval requests to a locally running OpenPets desktop companion.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 557
Connects DeepSeek Harness to Avernet's Bot Collaboration Network over WebSocket V2, with automatic onboarding, isolated agent sessions, tool-call events, and multi-bot routing tools.
omdsh-dev/dsh-notification★ 83
Desktop notifications for turn completions, with per-outcome controls and keyword rules.
omdsh-dev/dsh-lark★ 55
Lark/Feishu bot channel for DeepSeek Harness: each chat drives its own agent, and tool approvals, model questions, and plan reviews return as cards answered by a button or a reply. Switch workspace and model from the chat (`/cd`, `/model`, `/new`), and run several bots that keep separate sessions and can hand turns to each other in one group.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.