它做什么
dsh-notifier 把一个 notify() 调用摆在 27 个投递渠道前面,然后补上了「通知」本身做不到的那几件事:应答一次审批、回答一个问题、掐掉一个失控的轮次——全都在那台已经收到消息的手机上完成。
两条触发线喂给同一个内核:harness 自己推送会话事件(turn/end、approval/asked、agent/error,带防抖与去重),以及模型直接调用 notify 工具,传 { message, channel, title }。每条消息随后走同一条链——级别(timeSensitive / active / passive)→ 路由(多 agent × 渠道矩阵)→ 渠道适配器——所以「静默推送、优先级头、@提及」这类按渠道的投递语义是由级别推导出来的,而不是靠每次调用写特例。
只做出站,它已经是一个完整的插件。让它超出「通知」范畴的,是反方向的那一半。
不只是一台通知机的那一半
27 个渠道里有 6 个同时开放入站,走长连接或长轮询,所以不需要公网 IP 或端口转发(唯一例外是 WxPusher 的回调)。经由它们:
- 远程审批。 在手机上应答审批。Telegram 与飞书有原生卡片,QQ 在一对一聊天里有原生按钮,没有卡片能力的渠道回退到编号回复。群聊目标使用安全的纯文本回退。README 的规则很直白,而且是对的:沉默从不等于批准,且决定是一次性、fail-closed 的——未知来源与 QQ 群控制默认拒绝。
- 远程提问(
ask_user)。模型在手机上向你出选择题:1 到 4 个问题,每题 2 到 5 个选项,支持多选。Telegram 的单选题卡片还带「自定义作答」与「跳过」按钮,走 token 校验、先到先得。越界作答会重新追问而不会作废该问题,超时也绝不伪造一个答案。 - 远程对话。 纯文本变成
followup或inject;!前缀在轮次中途转向;一个合并窗口把「人在手机上打字」的真实节奏拼回完整句子。 - 会话控制。
/stop取消当前轮次,/quiet与/unquiet静音或恢复某个会话的推送,/agent系列重新绑定这个聊天正在驱动哪个会话。
长任务也被覆盖了,且自 v0.5 起默认开启:15 分钟后开始心跳、此后每 15 分钟一次,静默 10 分钟后发卡死告警,Telegram 与飞书卡片上带 ⏹ 停止按钮,用的是与审批同一套一次性 token 链。turnStart 刻意默认关闭,README 给的理由很好——你坐在桌前时,每轮一条消息就是噪声;你不在桌前时再打开它。
身份是一个运行时对象,而不是一份 YAML 列表:配对码(任意私聊里 /pair <code>,第一个兑换者成为 owner)、复合键绑定(channel:userId,所以绑定到 Telegram 的 id 不会放行一条飞书消息)、角色管理,以及会告诉未绑定发送者「怎么才能进来」的拒绝回执。白名单为空时不会拒绝启动,而是进入引导态,并把一个引导配对码写进本地 0600 文件——日志只打印路径,从不打印配对码。
27 个出站渠道,6 个能回话
渠道矩阵由代码生成(node scripts/gen-channel-matrix.mjs),这是个好信号:它是注册表,而不是一张会漂移的 README 表格。覆盖三类目的地:
- IM 平台——Telegram、Slack、Discord、飞书、钉钉、企业微信(群机器人与应用消息)、QQ 机器人、OneBot、Microsoft Teams、Mattermost、Google Chat。
- 推送服务——Bark、Pushover、PushDeer、Chanify、ntfy、Gotify、iGot、WxPusher、PushPlus、Server酱、Qmsg酱、息知。其中不少是国内应用,这也是它比大多数同类覆盖更宽的地方。
- 本地与通用——终端响铃、原生桌面通知(
osascript/notify-send/ PowerShell toast),以及任意 webhook。
入站就是上面那六个:telegram、feishu、qq-bot、wxpusher、wechat、dingtalk。
要求与安装
-
Node
>=22。 -
Harness:README 说明从 DSH
0.1.0-rc.6+起--profile是必需的,而清单里的兼容性块列出的是0.1.0-rc.6。本条把下界记为>=0.1.0-rc.6——即作者原话0.1.0-rc.6+的 semver 范围写法——正是这份声明让本条达到 L3。 -
从 npm 安装(已发布,撰写时为
0.10.2):dsh plugin --profile web add dsh-notifierREADME 写的是同一条命令、只是把参数放在后面(
dsh plugin add dsh-notifier --profile <profile-name>);顺序无关紧要,profile 名字才紧要。 -
然后重启 DSH,打开启动行打印出的链接——
http://127.0.0.1:<port>/#token=...。token 只存在于 URL 片段里,且只打印一次;端口冲突会自动回退到一个空闲端口。首次访问向导会带你走完「选渠道 → 填凭证 → 真发一条测试通知」,手机一震就说明配好了。 -
卸载:
dsh plugin --profile web remove dsh-notifier。
它声明了什么,以及代价是什么
装之前有两件事值得读,而且都来自包本身而不是宣传语。
它要的权限不少。 清单声明了 harness:tool、harness:event、network:outbound、network:listen、file:read、file:write、subprocess:exec 与 credentials:write。其中一部分无法避免——控制台需要一个 HTTP 监听、27 个渠道 token 需要凭证存储、桌面通知需要子进程——但 credentials:write 加上 subprocess:exec 这个组合值得你注意。缓解措施是结构性的,而不是口头承诺:密钥带 role('secret') 类型,在所有会被渲染的地方都被脱敏,${ENV:NAME} 引用则让值可以留在环境变量里。
「零运行时依赖」在这里不是口号。 dependencies 是空的。optionalDependencies 里那两个条目被精确锁版并惰性导入,缺失时只会让某一个渠道报 missing-sdk 并给出安装提示,而不会拖垮启动。还有一个值得称道的许可决定:@tencent-connect/qqbot-connector 不是依赖,因为 npm 把它标记为 UNLICENSED——README 说明它只作设计参考,从不复制、不再分发、也不作为依赖引入。
一个测试数量,由作者报告。 包里声明 dshQuality.testCount: 1616,README 也重复了「1616 个自动化契约测试(1616 通过)」。本站没有运行它们;那是作者的说法,本条按「作者的说法」记录。
配置
渠道位于 config.channels 之下:
insert:
- id: dsh-notifier
config:
channels:
- type: telegram
botToken: "123456:ABC-DEF..."
chatId: "987654321"
- type: feishu
webhook: "https://open.feishu.cn/open-apis/bot/v2/hook/..."
- type: serverchan
sct: "SCT..."
可选块各自用自己的键开启——inbound(允许哪些用户;仅首次导入,之后在运行时管理)、approval(mode: answer)、conversation(mergeWindowMs)、route(sessionTtlHours)、admin(默认开,仅回环)、events/keywords/graceSeconds(防打扰闸门)、digest(账本加一份每日摘要),以及 v0.5 的状态线(longRunning.firstAfterMs: 900000)。
状态线的默认值比看上去更要紧:longRunning 与 stall 默认开启,首次心跳 15 分钟、卡死阈值 10 分钟,所有时间都被钳到 60 秒下限。这意味着开箱即用时,长任务不再是黑箱——同时也意味着一个安静下去的会话会来找你。两者都可配置,都可以用 enabled: false 关掉。
已知限制
- 作者考试期到 2026-10-01。 README 开头是一则维护公告:在该日期之前,作者无法及时维护项目或查看 PR/Issue,回复会延迟。这是一个明确声明、且会结束的窗口,而不是弃坑——最后一次推送是 2026-09-13,也就是本条收录前三天——但如果你这个月需要上游响应速度,请把延迟算进去。
- 出站配置是「视图热、投递冷」。 在管理台保存一个渠道会立刻反映到 UI,并给卡片打上「重启后生效」的标记;而投递层(出站路由器与渠道实例)只会在下一次插件启动时合并运行时配置。测试发送是例外,会立即用最新配置执行。这一点被诚实地写明了,但它仍然是最可能让你困惑的地方。
- Web 管理台是第二个控制面,而且它就是一个控制面。 它只绑回环,并用 URL 片段里携带的 Bearer token 鉴权,设计上说得过去——但它仍然是你机器上的一个 HTTP 监听,背后还接着
credentials:write。请把打印出来的链接当作秘密对待。 - 六个渠道能驱动会话,其中五个是聊天平台。 身份系统确实做得很细致(一次性决定、fail-closed、复合键绑定、拒绝群控制)。剩下的风险是那种最普通的:谁拿着你的手机,谁就能批准。
- 星标数字自相矛盾。 本条记录实时值 49。夜间索引为同一个仓库记录的是 95——这是下降,不是笔误,也提醒我们星标衡量的是累积曝光量,而且会双向移动。
- 命名空间极其拥挤。 在夜间索引里,有 144 个仓库名字带 “notif”,其中 119 个停在 0–2 star;本条是唯二超过 50 的之一。它离
dsh-notification只差几个字符,而那是另一位作者的另一个项目、另一条安装路径。请读完最后四个字符。 - 49 个 star 是关注度,不是审查。 本条记为
security: unverified;没有第三方扫描或独立审计在案,本站也没有任何条目声称 L4 或 L5。
何时用 / 何时不用
当会话比你的注意力活得更久、而你想让决定落在口袋里的那台手机上时,就用它。真正有意思的能力不是那 27 个渠道,而是审批、选择题和一个停止按钮都跟着通知一起来到,于是你人不用回工位也能让一个长任务继续往前走。如果你本来就在用国内的推送应用,它的覆盖比这一片的大多数都宽。
不要因为「它是这里最大的通知插件」就装它。它要 credentials:write 和 subprocess:exec,它保存渠道凭证,并且它为六个平台开了一条通往「带工具的会话」的入站通路。如果跑 harness 的机器就是你正看着的机器,dsh-notification 不需要任何凭证、也不往模型上下文里加任何东西就能把这件事做完。如果你要的是从聊天窗口和会话真正对话,dsh-im 是更直接的形态——代价是它自己那套更重的访问控制义务。
FAQ
它是通知插件还是远程控制?
都是,而且分界很干净。出站是通知器:27 个渠道、级别路由、重试、分段、防打扰规则、账本。入站是其中六个渠道的远程控制:审批、提问、对话与 /stop。你可以只用前一半、永远不开后一半——inbound 是可选的。
它需要公网 IP 或端口转发吗?
不需要。每个渠道都走长连接或长轮询向外出。README 里点名的唯一例外是 WxPusher 回调,那个确实需要可达。
渠道配错了会怎样?
启动不受影响。配错的渠道会被静默跳过并留一行日志——对一个通知插件来说这是正确的失败模式;反面是「某个推送服务改了 API,于是插件拒绝启动」。
别的插件能通过它发消息吗?
能,而且这是一份有文档的契约,不是偶然。别的插件可以注入 notifier 服务(ctx.inject(['notifier'], …)),共享配置、路由、账本、限流与 flush;也可以订阅 ctx.on('dsh-notifier/sent') 拿到投递元数据。广播与定向发送各自产生一条可审计事件,消息正文永不外露。按来源限流是每分钟 10 条,消息上限 20000 个码点,接口永不拒绝。
凭证存在哪里?
在 profile 与运行时存储里,并支持 ${ENV:NAME} 引用,好让值留在环境变量中。密钥带 role('secret') 类型,在所有会被渲染的地方都被脱敏,管理台的凭证表单显示的是掩码值——未被改动的掩码字段永远不会被重新提交。