DeepSeek Harness Plugin

yimengsuper2432/dsh-notice

Stars ★ 0 Category Notifications & Integrations Added 2026-11-02

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/state
  • GET /api/dsh-notice-v1/events (SSE)
  • POST /api/dsh-notice-v1/config
  • POST /api/dsh-notice-v1/focus (bring the DSH window to the front; called when a notification is clicked)
  • POST /api/dsh-notice-v1/test
  • POST /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 powershell and Linux notify-send only 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

MIT

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.