- Home
- Plugins
- Integrations & Remote
- dsh-notify-hub
dsh-notify-hub
alotop/dsh-notify-hub
通知集合(Notification Hub):DSH Host 端通知中枢,整合 Bark 推送、多路 Webhook(飞书自定义关键词与签名校验 / 企微 / 钉钉 / Slack / Discord / 自定义)、原生桌面通知,并新增「移动新消息」5G 消息推送通道。设置页通过专用 RPC 读写 Host 配置,全部密钥脱敏不回传浏览器。
Install
dsh plugin --profile web add github:alotop/dsh-notify-hubREADME
dsh-notify-hub
English | 简体中文
One notification hub for DeepSeek Harness: turn endings, questions, approvals and plan reviews are pushed to Bark, China Mobile 5G messages(移动新消息), native desktop notifications, and any number of webhooks — at the same time, from the Host, so closing the browser changes nothing.
It is a combination plugin: it merges the strengths of two reference plugins and adds a channel neither of them has.
| Capability | Origin |
|---|---|
Host-side session/event listener, nine events, Bark pushes (level/group), schema-declared secrets + loopback RPC + settings section | dsh-notify-bark |
| Live turn accumulation, completion gate, dedupe, include/exclude content rules, webhook presets (Feishu/WeCom/DingTalk/Slack/Discord/custom), native desktop delivery, timeouts with exponential retries, zh/en rendering | dsh-notify-center |
| China Mobile 5G message channel — WebSocket long connection, auth handshake, heartbeat, reconnect ladder, rich-media upload | ported from @openclaw/cmcc-newmsg-channel |
| Delivery history, channel routes (split by session/content), one-shot migration of an existing Bark endpoint | new in this plugin |
Features
Channels
| Channel | Notes |
|---|---|
| Bark | Official or self-hosted. Group aggregation, push level (active / timeSensitive / passive / critical), custom sound |
| 移动新消息 | China Mobile 5G message. Long-connection push of text and (optionally) rich media |
| Desktop | Windows toast (AppUserModelId registered on first use, NotifyIcon balloon fallback), macOS osascript, Linux notify-send |
| Feishu / WeCom / DingTalk / Slack / Discord | Each channel's native text payload — paste the group-bot webhook URL. Feishu also supports the bot's security settings: custom keyword and signature verification (below) |
| Custom webhook | Structured JSON: kind, title, sessionId, turn, durationMs, reason, tools, time |
Every channel is independent: it must be switched on and fully configured, or the section reports it as unconfigured.
Feishu bot security
A Feishu custom bot offers three security settings; the plugin implements the two that need the caller's cooperation:
| Feishu console | Setting | Behaviour |
|---|---|---|
| 自定义关键词 (custom keyword) | Custom keyword | The message text must contain the keyword or Feishu answers 19024 Key Words Not Found. A configured keyword is prepended as [keyword] (skipped when the body already contains it) |
| 签名校验 (signature) | Signature secret | Every request carries timestamp (seconds, string) and sign. Algorithm: stringToSign = timestamp + "\n" + secret, then Base64(HMAC-SHA256(key = stringToSign, message = "")) — the joined string is the key, over an empty message. The secret stays on the Host; the UI shows a masked status only |
| IP 白名单 (IP allowlist) | — | Needs nothing from the plugin (Feishu sees the source IP); add your proxy's egress IP in the console |
WeCom / DingTalk / Slack / Discord render neither field: those providers either do not offer it or (DingTalk) sign in the URL query string, which is left for a later change against the same capability table.
Events
completed, error, blocked, aborted, max-tokens, interrupted,
question (ask_user_question), approval (approval/asked), plan-review
(exit_plan_mode).
- The body of a turn is folded live (assistant text + tool list + duration)
instead of re-reading the session log — DSH 0.1.5 no longer exposes
session.events. - Completion gate: a
turn/endnotification waits until the agent is actuallyidle, so a finishing turn cannot outrun queued follow-up work. - Dedupe keyed by
session:seq(24 h window, 2000 entries): a reload or a repeated dispatch never doubles a push. - Subagent sessions stay silent by default (switchable).
Policy
- Content rules match title / summary / reason / kind / tool names in order.
An
excludehit stays silent; whenincluderules exist, only hits notify. Literal or regex, case sensitivity per rule. - Channel routes split notifications across channels, e.g. "title contains deploy → Feishu only", "contains mobile → 移动新消息 only". The first matching route wins; with no match every enabled channel is used.
- Locale, body length bound, and whether to append the model's last reply.
Settings section
Everything is configured in Settings → 通知集合:
- live status (platform, desktop backend, delivery-queue occupancy);
- one card per channel — enable switch, write-only credential field with a masked
status (
••••••••last4), and a Test button; - 移动新消息: connection probe (real connect + handshake) and a rich-media test (upload a local image, then push it);
- event checkboxes, content options, rule and route editors;
- recent deliveries — time, channel, success/failure with a redacted reason, plus a clear button.
Security
- Every credential lives on the Host and is a schema-declared
secret: the browser never receives a value, only a masked status, and can only write new ones. - The section talks over the
/dsh-notify-hubloopback RPC, reusing Connection's Host/Origin fence (401/403), with a 256 KiB body cap and allow-list + type validation on every patch. - Logs and error messages are scrubbed: any configured secret is replaced with
[redacted], including in the delivery history.
Install
Requires DSH ≥ 0.1.5-rc.2 (web profile). There is no build step: lib/ holds the
runnable ESM host half and the browser bundle.
Option A — from npm (recommended)
dsh plugin --profile web add @alotop/dsh-notify-hub
Option B — from a tarball (offline / pinned)
npm pack # produces alotop-dsh-notify-hub-0.3.0.tgz
dsh plugin --profile web add .\alotop-dsh-notify-hub-0.3.0.tgz
Do not use
link:to this checkout: Node resolves dependencies from the linked real path, so the repository's ownnode_modulesis invisible to the profile and@deepseek-ai/schemasterywould not resolve at runtime. An npm package or a tarball is copied into the profile by pnpm, which is what works.
Option C — wire it into the web profile by hand
Edit %USERPROFILE%\.dsh\profiles\web\package.json:
{
"dsh": { "profile": { "bundles": [ /* …existing… */ "@alotop/dsh-notify-hub" ] } },
"dependencies": {
"@alotop/dsh-notify-hub": "^0.3.0"
}
}
then run pnpm install inside %USERPROFILE%\.dsh\profiles\web.
Activate
A new bundle needs a dsh web restart; later installs/updates need
pnpm install plus a restart (changes to cordis.patch.yml itself hot-apply).
Development and release
npm ci # dev/test dependency only (@deepseek-ai/schemastery); DSH supplies it at runtime
npm run check # client-bundle guard + repository hygiene guard + the whole test suite
npm run pack:check # audit what would be published to npm
Publishing is driven by a tag push (.github/workflows/release.yml):
# bump package.json's version, commit, then
git tag v0.3.0 && git push origin v0.3.0
The run verifies tag ↔ package.json, installs with npm ci, runs
npm run check and npm run pack:check, publishes with OIDC Trusted Publishing
and provenance (no npm token is stored in the repository), and creates the GitHub
Release. ci.yml runs the same checks on every branch push and pull request and
never publishes.
One-time setup on npmjs.com (package → Settings → Trusted Publisher): repository
alotop/dsh-notify-hub, workflow release.yml, environment empty. To publish
with a token instead, add an NPM_TOKEN secret and pass registry-url +
NODE_AUTH_TOKEN to setup-node.
Credentials and local live checks
This repository is public, which makes two rules non-negotiable:
- Credentials live in
.env(already gitignored); tracked files carry only obvious placeholders..env.exampleis the committed template and every value in it is fake. - Never put a real value — key, phone number, path, email — into a tracked file, including test fixtures and documentation examples. A value that reaches git history cannot be removed reliably, and it may also ship in the npm tarball.
To exercise real providers, pass credentials through the environment instead of pasting them into code:
Copy-Item .env.example .env # fill in the channels you want to verify; others are skipped
npm run live-check # one real notification per configured channel, secrets masked
npm run check includes check:hygiene (scripts/check-repo-hygiene.mjs), which
fails on Bark device keys, 移动新消息 API keys, phone numbers, API tokens, private
keys, JWTs, concrete user-profile paths (C:\Users\<real name>, /Users/…,
/home/…), email addresses, and any tracked .env file. Fixtures should use
obvious fakes such as EXAMPLEKEY1234, ak_replace_me, 13800138000 and
/path/to/....
Living with dsh-notify-bark
- On first run, if this plugin's Bark URL is empty, the endpoint is migrated
once from the old
bark:section of$DSH_HOME/settings.yaml(read-only on the old file) and a log line says so. SetmigrateLegacyBark: falseto opt out. - After migrating, remove
dsh-notify-bark— otherwise each turn pushes twice.
Configuration
The settings section covers everything; the composition layer accepts the same shape:
- insert:
- id: notify-hub
name: dsh-notify-hub
config:
locale: zh
notifySubagents: false
events: { completed: true, error: true, aborted: false, planReview: false }
bark: { enabled: true, url: 'https://api.day.app/yourKey', group: DSH, level: active }
local: { enabled: true, sound: true }
cmcc:
enabled: true
apiKey: ak_xxxx
to: '13800138000'
prefix: DSH
webhooks:
feishu: { enabled: true, url: 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx' }
delivery: { timeoutMs: 5000, retries: 2, retryBaseMs: 500 }
rules:
- { mode: exclude, pattern: 'heartbeat check' }
routes:
- { pattern: mobile, channels: [cmcc] }
Credential fields may equally live only in $DSH_HOME/settings.yaml (where the
section writes them): values resolve schema defaults → composition base → user
layer.
移动新消息 (China Mobile 5G message) protocol
Ported from the @openclaw/cmcc-newmsg-channel reference; endpoints default to
its config.json:
WebSocket : wss://5gvas01.cmicmaap.com/gtw-ai/openclaw/ws/msg
Upload : https://5gvas01.cmicmaap.com/gtw-ai/openclaw/api/upload
Version : 2.0
- Connect — the
wspackage is preferred (it can carry theX-API-Keyhandshake header); Node ≥ 22's built-in WebSocket is the fallback. Both paths send{ type: 'auth', apiKey, version }and wait up to 10 s forauth_ok. - Heartbeat —
{ type: 'ping' }every 15 s; a missingpongwithin 10 s drops the socket. - Reconnect — exponential backoff 3 s → 60 s with ±10 % jitter.
- Text —
{ type: 'send', apiKey, to, content, messageId }; the body is flattened from Markdown first (**bold**,`code`, list markers, …). - Rich media —
POST {uploadUrl}/upload(multipart:file+apiKey) returning{ code: 10200, data: mediaUrl }, then{ type: 'send', apiKey, to, mediaType, mediaUrl, content, messageId, … }. The section's "Send image" button exercises exactly this path. - Outbound only: inbound messages are not consumed (a notification hub does not need them).
Architecture
lib/
index.js host entry: settings namespace, agent integration, listener, hub, RPC
types.js event/channel vocabulary (kinds, flags, channel ids, cmcc defaults)
settings.js notify-hub schema, defaults, rule compilation, masked views
events.js live event folding + dedupe ledger + completion gate
policy.js event flags → content rules → channel routes
render.js local / text / Bark rendering + Markdown flattening
hub.js fan-out, in-flight bound, delivery accounting
history.js bounded delivery history for the section
status.js browser-safe channel status projection
migration.js one-shot adoption of the legacy `bark:` section
rpc-contract.js /dsh-notify-hub contract + patch validation/normalization
rpc.js loopback RPC route (client-request / server-response)
channels/
http.js shared POST + timeout + retry ladder + secret redaction
bark.js Bark V2
webhook.js six webhook presets
local.js Windows toast / osascript / notify-send
cmcc.js 移动新消息 long-connection channel
client.js browser half: module-loader bundle + settings section
The host half only imports Node builtins and @deepseek-ai/schemastery (peer);
the client bundle uses only the shell-seeded react and
@deepseek-ai/dsh-client-store.
Tests
npm test # 10 files / 103 cases, run serially (sandbox-friendly)
npm run check # client-bundle guard, then the suite
Covered: settings schema and masked views, event folding and the completion
gate, rules and routes, HTTP retries and redaction, Bark/webhook payloads,
Windows toast script escaping, 移动新消息 handshake/send/heartbeat/reconnect/upload,
hub fan-out and history, RPC validation, real loading and rendering of the
client bundle, and a set of apply()-level end-to-end cases (listener →
collector → hub → RPC read-back).
Known limitations
- Windows desktop notifications run through
powershell.exe. The script is written to a temp.ps1and launched with-File, so nothing is piped into the child — that form works even where a sandbox forbids pipes or extra process handles. A policy that blocks WinRT toasts falls back to a NotifyIcon balloon. - Rich media needs a gateway that serves
/upload; failures surface the server's own reason in the section. - 移动新消息 is outbound-only; replies from the phone are not consumed.
- The section's image test needs an absolute local path (the host reads it).
Credits
- dsh-notify-bark (MIT) — host-side listener, Bark push, secret-masked section, and the loopback RPC shape.
- dsh-notify-center (MIT) — event accumulation, completion gate, content rules, webhook presets, and cross-platform desktop delivery.
@openclaw/cmcc-newmsg-channel— the China Mobile 5G message transport. That package ships no licence, so this repository does not redistribute it: see THIRD-PARTY-NOTICES.md for exactly what was derived.
Full copyright and licence details: THIRD-PARTY-NOTICES.md.
License
MIT
Related plugins
dsh-web (dsh-ssh)
zhu1090093659/dsh-web
dsh-web (dsh-remote-web-ui)
zhu1090093659/dsh-web
dsh-web-ui (dsh-ssh)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-remote-web-ui)
zhu1090093659/dsh-web-ui