dsh-gateway-notify
A DeepSeek Harness plugin that pushes to your phone or team chat when an agent turn ends after running a while, when a turn fails, or when the agent has been waiting on your approval.
中文:dsh 的长任务结束、失败或卡在等你审批时,推送到 ntfy、Bark、Gotify、Server酱、飞书、钉钉、企业微信、Telegram 或任意 webhook。默认不发送提示词和回复正文。
Ported from @magpie-community/gateway-notify (MIT, © magpie-community contributors). The channel code is theirs, nearly unchanged.
When it pushes
| dsh event | Push |
|---|---|
turn/end with completed or max-tokens, turn ran ≥ minMinutes |
✅ done, with duration and model |
turn/end with error |
❌ failed, with the error message (any duration, if errors is on) |
approval/asked not decided within approvalDelaySeconds |
⏳ needs approval, with the tool name |
turn/end with aborted |
nothing: you pressed stop, so you're at the keyboard |
blocked, interrupted, forked |
nothing |
Subagent sessions (those with a parent session) stay quiet unless subagents: true. At most one push per session per cooldownSeconds.
Install
dsh plugin --profile web add dsh-gateway-notify
Requires Node ≥ 24.9 (the same as dsh). Tested with dsh 0.2.1-rc.5.
Configure
# $DSH_HOME/profiles/web/cordis.patch.yml
- id: gateway-notify
config:
minMinutes: 3
channels:
- type: ntfy
topic: env:NTFY_TOPIC # any string field may be env:NAME
token: env:NTFY_TOKEN # optional, for a protected topic
- type: bark
key: env:BARK_KEY
- type: feishu
webhook: env:FEISHU_WEBHOOK
secret: env:FEISHU_SECRET
Put tokens, keys and webhook URLs in environment variables and reference them as env:NAME; then nothing secret sits in the profile. This also works inside nested values such as a generic webhook's headers (authorization: env:HOOK_AUTH). If the variable is unset or empty and the field is required, that channel is skipped (nothing is sent to it); other channels still send.
| Option | Default | Meaning |
|---|---|---|
channels |
[] |
where to push (below) |
minMinutes |
3 |
only push for turns at least this long |
cooldownSeconds |
30 |
at most one push per session in this window |
errors |
true |
push failed turns, however short |
approvals |
true |
push approvals left waiting |
approvalDelaySeconds |
60 |
how long an approval waits before the push |
subagents |
false |
also push for subagent sessions |
includeText |
false |
add the start of the final reply |
maxTextLength |
200 |
how much of it |
lang |
en |
en or zh |
Channels
type |
Needs | Optional |
|---|---|---|
ntfy |
topic |
server (default ntfy.sh), token, priority, tags |
bark |
key |
server (default api.day.app), group, sound |
gotify |
server, token |
priority |
serverchan |
key (SCT… or sctp…) |
|
feishu |
webhook |
secret |
dingtalk |
webhook |
secret |
wecom |
webhook |
|
telegram |
token, chatId |
server |
webhook |
url |
headers; body is { title, body, event } |
Add off: true to keep a channel in config without sending to it.
Each request times out after 10 seconds. A channel that times out, refuses the connection, answers an HTTP error, or answers 200 with an error code in the body (as the Feishu, DingTalk, WeCom and Telegram bots do) counts as a failed send. The failure is swallowed: it never throws into the agent and never stops the other channels.
Privacy
By default a push carries only the project folder name, the duration, the model, and for failures the error message. Prompts and replies are not sent unless you turn on includeText. Each channel is one HTTPS POST to the service you configured; nothing goes anywhere else.
Differences from the magpie gateway version
The magpie version sits in an API gateway and has to infer from raw requests when a turn starts and ends, which requests belong to a subagent or a Claude Code background agent, and whether a failure is a retry. dsh reports these directly, so this port:
- listens to dsh's
session/eventstream (turn/start,turn/endand its reason,assistant/message,approval/asked,approval/decided) instead of request/response hooks; - identifies subagents by the session's
parentSessioninstead of heuristics; - drops the failure hold window: dsh's own retry layer has finished by the time
turn/endreports an error; - drops the
agentsandallOriginsfilters, which picked clients in a shared gateway; - adds the approval-waiting push and
env:NAMEsecrets; - drops a session's open turn and cooldown stamp on dsh's
session/disposed, so a long-running host does not keep state for every session it has seen.
Development
npm install
npm test # node --test: state machine with a fake clock, each channel's request format against a
# local HTTP server (Server酱 via a stubbed fetch), failures and timeouts. No real network.
License
MIT. See LICENSE for both copyright lines.
No comments yet. Be the first to write one.