Sends one system notification when a DSH turn ends: per-outcome switches for completed, error and interrupted, a minimum-duration threshold, silent subagents and unfocused-window-only delivery. Works on Windows, macOS and Linux, delivered through a connected page or a native fallback (PowerShell toast, osascript, notify-send; the Linux native fallback needs libnotify). Clicking the notification brings DSH back to the front on the page/Electron channel; PowerShell and notify-send notifications are not clickable.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:yimengsuper2432/dsh-notice
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
dsh-notice
A system notification every time DeepSeek Harness finishes a turn
When a turn ends (the model stops and waits for you), the plugin fires one system notification: which project, which turn, how long it took, and the last line of the answer. Clicking it brings the DSH window back to the front.
So you can go do something else for fifteen minutes instead of watching the progress indicator.
Why
The desktop app's own notifications are application-level only — "a new version is available", "restart to update". There is no "this turn is done" notification, which is exactly the gap this plugin fills.
| Situation | Without it |
|---|---|
| A long task, while you write something else | You switch back every minute to check |
| Several sessions at once | You forget which one finished |
| An error or an interruption | You think it is still running |
| A three-second answer | A notification would just interrupt you |
Those map to four switches: per-outcome toggles, only when the window is unfocused, a minimum-duration threshold, and subagents silent by default.
What a notification looks like
Three lines: the title carries the session title, the first body line is model · turn · duration, the rest is the answer snippet.
[任务处理完成通知] 已完成
deepseek-v4.1-flash-1017 · 第 2 轮 · 用时 5.3 秒
通知模板已经照你给的三行改好了:第一行 [会话标题] 已完成,……
The session title comes from the session/title event (falling back to the project directory name); the model from config.model in each turn's request/header; the snippet is the last assistant text collapsed to one line and truncated to snippetChars characters.
How a notification travels
flowchart LR
A["Turn ends"] --> B{"Outcome enabled?"}
B -- no --> Z["stay silent"]
B -- yes --> C{"Shorter than minSeconds?"}
C -- yes --> Z
C -- no --> D{"Window focused?"}
D -- yes --> Z
D -- no --> E{"preferClient and a page is connected?"}
E -- yes --> F["Page channel: renderer shows a system notification"]
E -- no --> G["Native fallback: powershell / osascript / notify-send"]
F --> H["Click brings DSH back"]
G --> H
Two channels
The desktop host is a standalone Node process (ELECTRON_RUN_AS_NODE), not the Electron main process: require("electron") gives no Notification, and spawning powershell can be blocked by process policy — the classic symptom is an API that reports "sent" while nothing appears.
So the reliable path is the page channel: the renderer subscribes to GET /api/dsh-notice-v1/events over SSE, the host pushes title/body to connected pages, and the page raises a Web Notification, which lands as a real system notification.
With preferClient (default true), any connected page is used; turning it off, or running without a window (dsh web), falls back to the native commands.
Clicking a notification works because the host grabs the foreground: a renderer window.focus() is only a request and cannot pull back a minimized or covered window. On click the page calls POST /api/dsh-notice-v1/focus; the Electron host uses BrowserWindow (restore() + show() + focus()), a bare Node host on Windows runs lib/focus.ps1 (ShowWindow(SW_RESTORE) + SetForegroundWindow, after clearing the foreground lock with an Alt tap and AttachThreadInput), and macOS activates through osascript. You can call the same route by hand: curl -X POST http://127.0.0.1:<port>/api/dsh-notice-v1/focus. The "send diagnostics" card shows the host identity (Node vs Electron, whether PowerShell was found), the last child-process error and the script log.
Backends
| Backend | Platform | Behaviour |
|---|---|---|
client (preferred) |
any | renderer raises the system notification; no child process involved |
electron |
Windows / macOS / Linux | native Notification when the host is the Electron main process |
powershell |
Windows | WinRT toast with DSH's AppUserModelID, falling back to a tray balloon; title/body passed as base64 |
osascript |
macOS | display notification with arguments, never string-interpolated |
notify-send |
Linux | libnotify, --app-name=DeepSeek Harness; degrades to none if missing |
none |
any | debug: record failures without sending |
backend: auto resolves per platform: Windows electron → powershell, macOS electron → osascript, Linux electron → notify-send.
Configuration
Defaults live in cordis.patch.yml; <DSH_HOME>/dsh-notice.json (written by the settings page) overrides them.
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
master switch |
onCompleted |
true |
notify on normal completion |
onError |
true |
notify on error |
onAborted |
false |
notify when interrupted |
includeSubagents |
false |
also notify for subagent turns |
minSeconds |
0 |
skip turns shorter than this |
onlyWhenUnfocused |
true |
only when the window is not focused |
silent |
false |
send silently |
snippetChars |
90 |
characters of the answer in the body (0 disables; the code-level fallback is 200) |
backend |
auto |
auto / electron / powershell / osascript / notify-send / none |
preferClient |
true |
let a connected page show the notification |
Local API
GET /api/dsh-notice-v1/stateGET /api/dsh-notice-v1/events(SSE)POST /api/dsh-notice-v1/configPOST /api/dsh-notice-v1/focus(bring the DSH window to the front; called when a notification is clicked)POST /api/dsh-notice-v1/testPOST /api/dsh-notice-v1/probe
Loopback only.
Install
Put the plugin at <DSH_HOME>/plugins/dsh-notice, add "dsh-notice": "link:<absolute path>" to the profile package.json plus a dsh.profile.bundles entry, install with the bundled node + pnpm, then restart DSH.
On Linux install libnotify for the native fallback (apt install libnotify-bin / dnf install libnotify / pacman -S libnotify). macOS needs nothing.
Known limits
- Only the end of a turn is covered — permission prompts and mid-turn failures produce no notification.
- The page channel needs an open window; Linux without libnotify degrades to
none. - Native fallbacks cannot focus the window: WinRT toasts from
powershelland Linuxnotify-sendonly display, and the OS gives them no click callback. Click-to-focus needs the page channel (keep a window open). - The body is a one-line snippet, not the whole answer.
- Failures are recorded, never retried, and never break the turn.
Checks
node --check lib/index.js
node --check lib/client.js
node test.mjs
License
Links
More in this category
xmanrui/dsh-im★ 3118
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★ 2685
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.
OpenPetsHQ/openpets#dsh★ 1373
Bridges DeepSeek Harness lifecycle status, errors, and approval requests to a locally running OpenPets desktop companion.
c1overzz/dsh-bili-scroll★ 1150
Let your DSH agent browse Bilibili on its own, read subtitles, write a daily digest, and @ you when it finds something worth your attention.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 599
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.
zaofan-make/dsh-qqbot★ 213
QQ official bot channel for DeepSeek Harness (feature-rich fork of tencent-connect/dsh-qqbot): group and private chat with the full agent, sticker library with auto-tagging and AI meme replies, group management (join approval and mute), botplay interactive card events with paginated catalogs, user extension registry (custom slash commands and QQ tools), scheduled wakeups, remote approvals over QQ, and a floating dock web UI.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.