DSH Plugins Marketplace

DSH Plugins

Plugins

/

Tools & Capabilities

/

dsh-notify-linux

W

dsh-notify-linux

Manifest valid★ 1

DSH (DeepSeek Harness) Plugin: Send desktop notifications and play a sound when tasks are completed, selection is required, or approval is needed · Desktop notifications + sound for DSH on Linux

UI (client)hasBundlePatch

dsh-notify-linux

English | 中文文档

Desktop notifications and a chime for DSH (DeepSeek Harness) on Linux + GNOME. DSH tells you when it has finished a turn, when it is waiting for you to choose something, when a tool call needs your approval, and when something failed.

The notifications are sent by the DSH host process, not by the browser page — so they still arrive when the page is in a background tab, when the window is minimized, or when the page is closed entirely.

  • No npm runtime dependencies (the host half uses only Node built-ins)
  • Notifications via notify-send, sounds via paplay with a canberra-gtk-play fallback
  • Browser-side fallback (in-page toast, Web Notification, synthesized Web Audio chime) for environments where the system paths are unavailable
  • All switches configurable in the DSH Settings page or by editing a single JSON file

What it notifies you about

Four event kinds. Each one has its own toggle, its own urgency and icon, and its own sound (except turnEnd, which uses the theme you select).

KindTriggerNotification titleUrgencyIconSound
turnEndThe whole agent turn is stopping — the model no longer owes a responseDSH 已完成任务normaldialog-informationselected theme
questionThe model requests an answer from you (single choice / multi choice / free text)DSH 正在等待你的选择normaldialog-informationmessage-new-instant
approvalA tool call is waiting for a human to allow itDSH 需要你的批准normaldialog-informationdialog-information
errorA step or a whole turn failedDSH 执行出错criticaldialog-errordialog-error

The notification body identifies the session, so you can tell which piece of work it refers to. Format: <workspace> · <8-char session id> plus the event detail, e.g.

DSH 已完成任务
default-workspace · 44b6a957 的回合已结束

turnEnd fires on agent/turn-stopping — the point where the turn is genuinely over. Tool-call round trips do not trigger it; only the end of the whole turn does.

The notification strings are currently Chinese (they are emitted from the host half). The Settings page itself is fully localized (Chinese and English).


Why the host half sends the notifications

A browser-only notification scheme has a hard limitation: it only works while the page is open and the browser is alive. Switch to another window, minimize the browser, or close the tab, and the notification is simply never delivered.

The DSH host process lives longer than the page. notify-send goes through the GNOME notification center, so the notification — and the sound — still reach you when the page is unfocused, when the browser is in the background, or when the page is not open at all.

The browser half therefore does three different things instead of duplicating delivery:

HalfFileResponsibility
Hostindex.js (500 lines)Listens to the four events, sends the real system notification and sound, persists config, serves the HTTP API
Browserclient.js (682 lines)Settings page, in-page toast overlay, and fallback via Web Notification / Web Audio when the host could not deliver

Whether the browser falls back is decided by two booleans the host returns with each event — desktop and sound, meaning "this was actually dispatched". If the system notification did go out, the browser stays quiet, so you are never notified twice.


Requirements

ItemDetail
PlatformLinux + GNOME (verified on Manjaro GNOME, Wayland)
DSHAny DSH build with the Cordis plugin system (webServer, settings.section slots)
Node>= 22 (DSH's own requirement)
Notificationsnotify-send from libnotify
Soundspaplay from pulseaudio-utils, or canberra-gtk-play from libcanberra
Sound files/usr/share/sounds/freedesktop/stereo (the freedesktop sound theme)
npm dependenciesnone — package.json has no dependencies

Everything external is optional in the sense that the plugin degrades gracefully: if notify-send is missing, the plugin reports that capability as unavailable and the browser half can take over with Web Notifications. Nothing is ever awaited on the event path — a missing or failing command can never stall the agent.

The plugin probes for its tools at runtime and reports the result in the Settings page (notify-send, paplay, canberra-gtk-play — each shown as available or not).


Installation

This is a DSH Cordis plugin bundle. Install it through DSH's plugin manager (plugin_manager, action install_bundle), pointing it at either:

  • the local directory — /home/wlaeg/文档/deepseek-harness/default-workspace/dsh-notify
  • or the Git repository — https://github.com/Wlaeg/dsh-notify-linux

The bundle declares its own patch in package.json:

{
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" },
    "client": { "platform": "web", "immediately": true, "inject": [] }
  }
}

and the patch is a single insert that mounts the host half:

- insert:
    - id: dsh-notify-linux
      name: 'dsh-notify-linux'
      config: {}

The browser half is declared by dsh.client and bound to the row whose name is the bare package name — subpath rows do not carry a browser half.

After installing, the plugin is active. Reload the DSH Web UI page so the browser half registers its Settings section. There is no separate CLI install step.

Replacing an already-installed copy (for example after editing the source) needs a restart of the DSH host process. The plugin manager rewrites the dependency and the patch immediately, but the running process keeps the JS module generation it loaded at startup, so the old routes and handlers stay live until DSH restarts. A page reload is not enough. The host half's HTTP routes make this easy to see: while a stale copy is still running, GET /api/dsh-notify-linux/config answers 401 because the old /api/dsh-notify prefix route is still claiming the path.


Settings

The Settings page section is called Task Notifications (导航项「任务通知」, registered on the settings.section slot at order 30). It contains:

  • the master switch
  • the four event toggles
  • system notification / sound toggles and the completion-sound theme
  • subagent, focus-only and cooldown options
  • the browser-fallback group with the current browser notification permission
  • a Try it row with one button per event kind, using the exact same delivery path as a real notification
  • a capability row showing whether notify-send, paplay and canberra-gtk-play were found on this machine

Screenshot placeholder — no screenshot is included in this repository yet, so no image is referenced here. This block is an explicit placeholder, not a broken link.


Configuration

All options live in one file:

<DSH profile directory>/dsh-notify-linux.json

The profile directory is resolved as $DSH_PROFILE_DIR, or otherwise $DSH_HOME/profiles/$DSH_PROFILE (defaults: ~/.dsh/profiles/web). On this machine that is ~/.dsh/profiles/web/dsh-notify-linux.json.

The file is written atomically (temp file + rename) with mode 0600, so a reader never sees half-written JSON. You can edit it directly — it is re-read on every notification — or use the Settings page, which writes through the same code path.

Defaults

KeyTypeDefaultMeaning
enabledbooleantrueMaster switch. Off disables every notification.
systemNotifybooleantrueSend a desktop notification via notify-send.
systemSoundbooleantruePlay a sound from the system sound theme.
browserToastbooleantrueShow the in-page toast overlay.
browserNotifybooleantrueUse Web Notification as a fallback. Requires browser permission.
browserSoundbooleanfalseUse the synthesized Web Audio chime as a fallback.
soundThemestring'complete'Sound used for turnEnd. One of complete, message, bell, none.
events.turnEndbooleantrueNotify when a turn finishes.
events.questionbooleantrueNotify when the model needs you to choose.
events.approvalbooleantrueNotify when a tool call needs approval.
events.errorbooleantrueNotify when a step or turn fails.
includeSubagentsbooleanfalseAlso notify for subagent sessions (off by default to avoid parallel-task spam).
onlyWhenUnfocusedbooleanfalseSuppress system notification and sound while the DSH window is focused.
cooldownSecondsnumber3Per-kind minimum interval, clamped to 0–120. 0 disables the cooldown.

Unknown keys are dropped, wrong types fall back to the default, and a missing or corrupt file silently reverts to defaults — notification configuration can never break the plugin.

Example:

{
  "enabled": true,
  "systemNotify": true,
  "systemSound": true,
  "browserToast": true,
  "browserNotify": true,
  "browserSound": false,
  "soundTheme": "message",
  "events": { "turnEnd": true, "question": true, "approval": true, "error": true },
  "includeSubagents": false,
  "onlyWhenUnfocused": true,
  "cooldownSeconds": 5
}

Sound themes

turnEnd uses the theme you pick; the other three kinds are fixed.

ThemeFile under /usr/share/sounds/freedesktop/stereoLabel in the UI
completecomplete.oga轻快完成音
messagemessage.oga消息提示音
bellbell.oga铃声
none—静音 (silent)

The Settings page only offers themes whose .oga file actually exists on this machine. If a file is missing at play time, paplay is skipped and the plugin falls back to canberra-gtk-play -i <sound>.


Architecture

Host half (index.js)

LineWhat it does
:187sendDesktop(title, body, kind) — spawns notify-send with -a DSH, icon, urgency, -t 8000
:200playSound(sound) — paplay on the theme file, else canberra-gtk-play
:254fire(kind, title, body) — applies the master switch, per-event toggle and cooldown, then dispatches
:289agent/turn-stopping handler → turnEnd
:299user-questions/request waterfall handler → question
:317approval/request waterfall handler → approval
:334agent/error handler → error
:489ctx.inject(['webServer']) — registers the HTTP routes once webServer is available

Three design decisions worth knowing:

  1. Fire-and-forget. Notifications are spawned as detached child processes that are unref()ed. The event dispatch path never awaits an external process, and a failing notification never affects the agent. The plugin deliberately does not use DSH's subprocess service, whose contract is "the caller owns the handle and its lifecycle" — the opposite of what is wanted here.
  2. return next() in waterfalls. user-questions/request and approval/request are serial responsibility chains. This plugin is a pure observer: its listeners only perform side effects and then delegate next(). Forgetting the delegation would deadlock every question and approval flow in the host.
  3. Session-bus environment repair. If DSH was started from a systemd user unit or over SSH, DBUS_SESSION_BUS_ADDRESS / XDG_RUNTIME_DIR may be missing, and notify-send would then fail silently. The plugin fills in unix:path=/run/user/<uid>/bus and /run/user/<uid> before spawning.

Notifications use -h string:x-canonical-private-synchronous:dsh-notify-linux-<kind>, so a new notification of the same kind replaces the previous one instead of filling up the notification center during consecutive turns.

Browser half (client.js)

LineWhat it does
:238requestPermission() — requests Web Notification permission inside a user gesture
:254beep(kind) — synthesizes the chime with oscillators, one note sequence per kind
:279webNotify(title, body, kind) — Web Notification with a per-kind tag and silent: true
:308showToast(title, body, tone, onClick) — plain-DOM in-page overlay, auto-dismisses after 7 s
:631new EventSource('/api/dsh-notify-linux/events') — subscribes to the live event stream
:671ctx.slots.register({ name: 'settings.section', id: 'dsh-notify-linux', order: 30 })

The browser half is a hand-written ModuleLoader bundle: no build step, no dependency other than the react the shell already provides, and all colors come from theme variables so it follows light/dark mode.

Synthesized chime note sequences ([frequency Hz, offset s]):

turnEnd  880 → 1318
question 740 → 740
approval 988 → 988
error    300 → 220

The AudioContext is closed 800 ms after playback so it does not hold an audio channel.

HTTP API

The host half registers a same-origin route prefix on the DSH webServer:

MethodPathPurpose
GET/api/dsh-notify-linux/configCurrent config, tool capabilities, available sound themes, focus state
POST/api/dsh-notify-linux/configPartial config update (deep-merged, including events), persisted to disk
POST/api/dsh-notify-linux/testFire a real notification of a given kind through the normal path
POST/api/dsh-notify-linux/presenceReport whether the window is focused (feeds onlyWhenUnfocused)
GET/api/dsh-notify-linux/eventsSSE stream: hello (capabilities), notify (a real dispatch), config (config changed)

The SSE stream sends a : ping comment every 25 s to keep idle connections alive through proxies. The browser half backs off after 6 consecutive connection failures (30 s before retrying) rather than reconnecting in a tight loop.

Security boundary

DSH's webServer often binds 0.0.0.0, but these routes can change plugin configuration and trigger real notifications — they must not be open to the public internet. Every request passes through allowed(req), which permits only:

  • 127.0.0.1, ::1, localhost, or an empty address
  • 10.0.0.0/8, 127.0.0.0/8
  • 192.168.0.0/16
  • 172.16.0.0/12
  • IPv6 unique-local fc00::/7 and link-local fe80::/10

Anything else gets 403 {"error":"forbidden"}. Request bodies are capped at 64 KiB.


Verified behavior

The following were observed on real hardware (Manjaro GNOME, Wayland) with the plugin installed and a real DSH session running:

  • A real D-Bus notification call was captured. Monitoring the session bus during a triggered event showed an actual org.freedesktop.Notifications.Notify method call from the plugin, i.e. the notification went through the GNOME notification center and not through any simulation layer.

  • A real turn end produced a real notification. With the live agent session running, the notification body was:

    default-workspace · 44b6a957 的回合已结束
    

    — the workspace name derived from the session's cwd, followed by the first 8 characters of the session id.

  • A real sound was played. pactl list sink-inputs captured a paplay sink-input while the notification fired, confirming the audio path (rather than only the file lookup) works.

The Settings page "Try it" buttons call /test, which runs through the exact same fire() path as a real event, so what you see there is what you get in practice.


Troubleshooting

SymptomCheck
No desktop notificationIs notify-send installed and on PATH? The capability row in Settings shows this.
No soundIs paplay (or canberra-gtk-play) present, and does the .oga file exist under /usr/share/sounds/freedesktop/stereo?
Notifications only sometimescooldownSeconds suppresses repeats of the same kind — lower it or set it to 0.
Nothing at allenabled must be true, and the specific events.<kind> toggle must be on.
No notifications while watching DSHonlyWhenUnfocused is on — the window is focused, so system notification and sound are suppressed by design.
Settings page unreachableThe host half needs the webServer service; the page shows a retry button when the backend cannot be reached.
Subagent tasks silentincludeSubagents defaults to false on purpose. Turn it on to include them.

Uninstalling

Remove the bundle through DSH's plugin manager (plugin_manager, action remove_bundle), targeting dsh-notify-linux. Then delete the leftover config file:

rm -f ~/.dsh/profiles/web/dsh-notify-linux.json

Nothing else is installed on the system — no systemd units, no files outside the DSH profile and the plugin directory. The sound files the plugin plays belong to the freedesktop sound theme and are left untouched.


License

MIT © Wlaeg (wlaeg@users.noreply.github.com)

Comments

Loading…

From the same category

reactive-resume

DeepSeek Harness plugin for Reactive Resume: bridges your resumes and job applications into a Harness session over MCP.

Tools & CapabilitiesManifest valid

★ 41.7k

↓ 242/wk

MIT

Aug 24, 2026

dsh plugin --profile web add dsh-plugin-reactive-resume

by Tencent

Let AI agents use your real, logged-in browser without interrupting your work. CLI + extension for browser automation across any shell-capable AI agent.

Tools & CapabilitiesManifest valid

★ 8.8k

↓ 7.5k/wk

MIT

TypeScript

Oct 13, 2026

dsh plugin --profile terminal add @wxg-prc-cpg/browser-skill-dsh-plugin

by yjh051108

dsh-routing-suite — injector + router-standard kit: install the runtime injector first, then the task-aware reasoning-mode router preset (measured P1-P23).

Tools & CapabilitiesManifest valid

★ 7k

MIT

JavaScript

Sep 18, 2026

dsh plugin --profile web add @dsh-external/dsh-super-injector

by dsh-market

The plugin market inside DeepSeek Harness — browse, search, one-click install · DSH 可视化插件市场

Tools & CapabilitiesManifest valid

★ 6.4k

↓ 138.4k/wk

MIT

TypeScript

Oct 13, 2026

dsh plugin --profile web add dshmarket

by Q00

Agent OS: the agent gets smarter on its own. We just hold the line: Interview-gated, staged evaluation, budgeted evolution loop. MCP server, 14 runtimes: Claude Code, Codex CLI, Gemini CLI, OpenCode,

Tools & CapabilitiesManifest valid

★ 6.2k

MIT

Python

Oct 14, 2026

Index only — not installable

by superdesigndev

OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn

Tools & CapabilitiesManifest valid

★ 5.3k

NOASSERTION

Python

Oct 14, 2026

dsh plugin --profile web add treg-dsh