DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-plugin-notify

T

dsh-plugin-notify

Manifest valid

WxPusher + real browser notifications (sound, requireInteraction) for DeepSeek Harness session state changes — session stopped, exited with an error, or asked you a question. Native settings page.

UI (client)hasBundlePatch

dsh-plugin-notify

中文 | English

Session-state notifications for the DeepSeek Harness (DSH): a WxPusher push plus a real browser notification — with sound and requireInteraction — whenever a session stops, exits with an error, or asks you a question. Everything is configured from a native settings page (设置 → 通知 / Settings → Notifications).

  • Two independent channels, one payload. The Host detects the transition once and delivers the same content to WxPusher and to every open browser page.
  • Real system notifications. new Notification(...) with a PNG icon, an optional WebAudio chime, and requireInteraction so the banner stays until you dismiss it.
  • Native settings page. A settings.section page built from the plugin's own Config schema; every field is volatile, so edits apply live — no restart, no plugin remount.
  • Write-only secret. The WxPusher appToken is declared role('secret'): it is stored in the profile, never returned over the wire, and edited through a path-addressed write.
  • Zero runtime dependencies beyond the schema library. One Host file, one Client file, no build step.

What triggers a notification

EventSourceDetail shown
Session stoppedapi-session/status → running: falsethe folded session title
Exited with an errorapi-session/error, agent/errorthe error message
Question askeduser-questions/request (observed, then delegated)the first question
Approval neededapproval/request (observed, then delegated)the tool name

Two filters keep the noise down:

  • minRunMs — a run shorter than this never reports a stop.
  • stopDebounceMs — after the agent goes idle the Host waits this long; if the session starts running again the report is dropped. A stop is also suppressed when an error notification for the same session just fired, because that one is more precise.

user-questions/request and approval/request are Cordis waterfalls: this plugin only observes them and always delegates with next(), so it can never swallow a request meant for another answerer.

Requirements

  • A DSH Host with the Web surface (@deepseek-ai/dsh-web-app), i.e. any normal dsh web profile. ctx.webServer is used for the notification stream; without it the plugin still pushes to WxPusher but delivers nothing to the browser.
  • Node.js ≥ 20.
  • A browser that supports the Notification API (Chrome, Edge, Firefox, Safari 16+).
  • A WxPusher account, only if you want phone pushes.

Install

Through the plugin manager (recommended)

Add the package spec to the Plugins page — a registry name, a git URL, or an absolute path to a checkout — and enable the bundle it adds:

dsh-plugin-notify

or, from a clone:

/path/to/dsh-plugin-notify

The plugin's row is declared by the bundle's own cordis.patch.yml, so nothing else needs editing. If the profile was already running, reload it (or restart) so the new package enters the module-resolution table.

Manually, in a profile

# in the profile directory, e.g. ~/.dsh/profiles/web
pnpm add dsh-plugin-notify

Then add the bundle to dsh.profile.bundles in that profile's package.json:

{
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-plugin-notify"]
    }
  }
}

Configure

Open Settings → Notifications (设置 → 通知). The page writes straight into the plugin's profile entry.

FieldDefaultMeaning
enabledtrueMaster switch for both channels.
events.stoppedtrueReport a session that stopped working.
events.errortrueReport a session or step that errored.
events.questiontrueReport a question from the agent.
events.approvalfalseReport a pending approval decision.
browser.enabledtrueRaise browser notifications at all.
browser.soundtruePlay a short two-tone chime (WebAudio, no asset).
browser.requireInteractiontrueKeep the banner until dismissed.
browser.onlyWhenHiddentrueStay quiet while the tab is visible.
wxpusher.enabledfalseEnable the WxPusher channel.
wxpusher.appToken''WxPusher appToken (write-only secret).
wxpusher.uids[]Receiving UIDs, one per line (UID_xxxxxxxx).
wxpusher.topicIds[]Receiving topic ids, one per line.
wxpusher.url''Optional URL a tapped push opens.
advanced.stopDebounceMs1200Idle grace period before reporting a stop.
advanced.minRunMs2000Runs shorter than this never notify.
advanced.rootSessionsOnlytrueSkip subagent sessions.
advanced.includeSessionTitletrueInclude the folded session title.
advanced.maxDetailChars300Truncate error/question detail.

The same values live in the profile patch, so they survive upgrades:

- id: dsh-plugin-notify
  config:
    events:
      approval: true
    wxpusher:
      enabled: true
      uids:
        - UID_xxxxxxxx

WxPusher credentials

  1. Sign in at https://wxpusher.zjiecode.com, create an application, and copy its appToken.
  2. Follow the app's QR code with WeChat to subscribe, then copy your UID from the app's user list (or use a topic id to reach many subscribers).
  3. Paste the appToken and the UIDs into the settings page and press Save.

The appToken input is always empty on load — the Host only ever reports whether a token is set.

Browser permission

The page shows the current Notification.permission. If it is default, press Request permission; if it is denied, re-allow notifications for the page in your browser's site settings. Sound needs one user gesture on the page before the browser lets an AudioContext start — the plugin hooks the first click/keypress to unlock it.

Test

Each channel card owns its own test button, so a test never leaves you guessing which card it belongs to:

CardButtonWhat it does
Browser notificationsSend a test notificationBroadcasts a frame to every connected page and pushes one WxPusher message — a full-stack probe.
WxPusher pushSend a test pushPushes one WxPusher message only. It raises no system notification, and it works while wxpusher.enabled is still off, so credentials can be verified before switching the channel on.

Each button reports its own outcome underneath:

Browser: delivered to 1 page(s) · WxPusher: sent
WxPusher: no appToken
WxPusher: refused (appToken不正确)
Browser: no connected page (browser notifications may be off) · WxPusher: no UID or topic id

The buttons call the same endpoint with a channel selector — see below.

How it works

Host                                    Browser
────                                    ───────
api-session/status ─┐
api-session/error  ─┤
agent/error        ─┼─► detect ─► frame ─┬─► WxPusher HTTPS push
user-questions/…   ─┤                    │
approval/request   ─┘                    └─► GET /dsh-plugin-notify/events (SSE)
                                                        │
                                                        ▼
                                            new Notification(title, {
                                              body, icon, tag,
                                              requireInteraction, silent,
                                            })  +  WebAudio chime
  • The Host plugin registers two exact routes on ctx.webServer: the SSE stream at /dsh-plugin-notify/events and the test endpoint at /dsh-plugin-notify/test.
  • The Client half opens an EventSource on the document-relative path dsh-plugin-notify/events, so it resolves under whatever mount served the page and inherits the page's own authentication. EventSource reconnects by itself; the Host sends a : ping heartbeat every 25 s.
  • The settings page resolves its namespace from ctx.configForms.describe() — by entry id first, then by the schema it knows it owns — so it keeps working when a profile nests the bundle patch and prefixes the entry id.
  • Writes go through ConfigForm.mutate() with path-addressed operations, so a secret that the wire never returned is never restated.

Endpoints

MethodPathPurpose
GET/dsh-plugin-notify/eventsServer-Sent Events stream of notification frames.
POST/dsh-plugin-notify/testProbe the notification path. ?channel=browser broadcasts without pushing, ?channel=wxpusher pushes without broadcasting, and omitting it does both. Answers with { ok, channel, browser, wxpusher }, where the channel that was not asked for reports skipped.

Known limitations

  • WxPusher content is composed in Chinese (the WxPusher audience is the phone); the browser notification is localized through the Harness locale service.
  • The "stopped" signal is the agent going idle. A session that idles mid-conversation between turns is reported once, by design — stopDebounceMs and minRunMs are the knobs.
  • Nothing is persisted beyond the profile config: no history, no replay of missed notifications.
  • The plugin does not create a Remote namespace, so the browser half talks to the Host over the two HTTP routes above rather than over the typed Remote channel.

License

MIT — see LICENSE.

Comments

Loading…