# dsh-notifier

> DSH 统一通知与远程控制插件：一个 notify() API 对接 27 个推送渠道，同时把审批、提问、会话从手机送回桌面，长任务自动心跳并支持一键停止。

## Metadata

- Author: [@THEWOLFWALKER](https://github.com/THEWOLFWALKER)
- Repo: <https://github.com/THEWOLFWALKER/dsh-notifier.git>
- GitHub: [THEWOLFWALKER/dsh-notifier](https://github.com/THEWOLFWALKER/dsh-notifier)
- Stars: 53
- Language: JavaScript
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `dsh-plugin`
- Forks: 4
- Open Issues: 8
- Last push: 2026-08-19T15:03:10.000Z
- Added: 2026-08-17T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:THEWOLFWALKER/dsh-notifier
```

## Wiki

## 一句话定位
DSH 的统一通知与远程控制插件：宿主事件（任务完成/出错/审批）和模型主动推送共用一个 `notify()` API，把消息送到 27 个推送渠道；再通过其中 6 个通道反向把审批、提问、消息从手机送回桌面，并把长任务变成带心跳和停止按钮的移动指挥中心。

## 核心能力
- **27 个出站渠道**：Telegram、Slack、Discord、飞书、钉钉、企业微信（群/应用）、QQ 机器人、OneBot、Teams、Mattermost、Google Chat、Bark、Pushover、PushDeer、Chanify、ntfy、Gotify、iGot、WxPusher、PushPlus、Server酱、Qmsg、息知、Webhook、终端响铃、系统桌面通知
- **自动状态推送**：宿主事件（任务开始、完成、出错、被阻塞、被中止、达 Token 上限、异常中断、需要审批）按级别自动转发到所有配置渠道；同会话轮次按 10 秒尾沿合并去重
- **分级路由**：紧急/活跃/被动三档语义映射到各渠道原生送达形式（静默推送、优先级、@提醒），配分档重试
- **远程审批与对话**：Telegram、飞书、QQ、WxPusher、微信、钉钉六通道支持回传，点按钮或回复 1/2 远程裁决审批；纯文本消息进入 agent 会话（追问/中途注入/steer）
- **移动指挥中心**：长任务 15 分钟首条心跳 + 每 15 分钟再来一条，10 分钟无事件报疑似卡住；Telegram/飞书卡片自带一键停止按钮
- **远程提问**：ask_user 工具把选择题推到手机（飞书/Telegram 选项卡，其他通道回复编号），用户作答后回传 agent；超时永不代答
- **本机 Web 管理台**：仅绑 127.0.0.1 + Bearer token，含总览、通知流、成员、配对码、绑定矩阵、会话台账、通道凭证 6 页，移动端自适应
- **开放事件源**：其他插件可注入 notifier 服务推送，或订阅 dsh-notifier/sent 事件监听每次广播结果；按 source 限流 10 次/分钟
- **身份体系**：运行时的配对码准入、复合键绑定（channel:userId 隔离渠道身份）、成员管理与扫码授权；空名单以引导态启动

## 技术实现
- **语言**: JavaScript ESM（`.mjs`，纯 ESM，零构建步骤）
- **关键依赖**: `@deepseek-ai/cordis`（宿主插件框架）、`@larksuiteoapi/node-sdk`（飞书长连接，懒加载可选）、`@tencent-connect/qqbot-connector`（QQ 机器人，可选）、`qrcode-terminal`（终端扫码，可选）
- **架构模式**: 声明式 spec 引擎（绝大多数渠道以数据声明表生成适配器）+ 12 个手写适配器；通过 `ctx.provide('notifier', facade)` 暴露服务给宿主和其他插件；事件总线监听 `turn/end`、`approval/asked`、`agent/error` 自动推送；HMAC 一次性 token 处理远程审批
- **入口文件**: `src/index.mjs`（apply 装配入口）、`src/config.mjs`（配置解析与默认值）、`src/notify.mjs`（出站核心）

## 适用场景
你装了 agent 跑 DSH，但任务动辄十几分钟起步，桌面挂着浏览器看进度很费神；想切到手机就能收到完成/出错通知，点卡片就能停掉跑飞的 turn，甚至能在路上回一条消息纠偏 agent 的执行方向。希望同一套配置既覆盖 Telegram、飞书这些海外/办公 IM，也覆盖 Server酱、Bark、PushPlus 这些国内推送 App，且新增渠道不用动主流程。

## 前置依赖与兼容性

| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 0.1.0-rc.6 | `dshWorkshop.compatibility.dshVersions` 显式声明；通过 `cordis.patch.yml` 注入插件行 |
| Node.js | >= 22 | `engines.node` 强制要求；飞书与 QQ 入站依赖原生 WebSocket |
| 平台 | 跨平台 | 桌面通知渠道在 macOS/Windows/Linux 各有原生实现（osascript/notify-send/PowerShell Toast），其余渠道纯网络无平台限制 |
| 原生模块 | 无 | 仅用 `node:crypto`、原生 WebSocket、fetch；不引入任何 native addon |

## 安装方式
```bash
dsh plugin --profile web add github:THEWOLFWALKER/dsh-notifier
```

## 配置项

写在 profile 的 `cordis.patch.yml` 中 dsh-notifier 行的 `config` 下。主要块如下（详细 schema 见 `src/config.mjs`）：

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `channels[]` | 数组 | 渠道列表，每项含 `type` 与渠道凭证字段（如 `telegram` 的 `botToken`+`chatId`） | 空数组（不报错，无操作） |
| `enabled` | 布尔 | 总开关，关掉后不订阅事件不注册工具，仅暴露 no-op stub 服务保证依赖方不卡启动 | 开 |
| `titlePrefix` | 字符串 | 自动推送标题前缀（可空） | 空 |
| `summaryMaxChars` | 数字 | 自动推送回复正文截断字符数 | 500 |
| `debounceMs` | 数字 | 同会话连续事件尾沿合并窗口（毫秒） | 10000 |
| `graceSeconds` | 数字 | turn 结束后的空闲宽限窗，期间用户在终端输入即取消打扰 | 0 |
| `events.turnStart.enabled` | 布尔 | 任务开始是否推送 | 关 |
| `events.longRunning` | 对象 | 长任务心跳（`firstAfterMs` 首条延迟、`everyMs` 后续间隔，下限 60s） | 开，900000 / 同值 |
| `events.stall` | 对象 | 疑似卡住提醒（`afterMs` 静默时长阈值，下限 60s） | 开，600000 |
| `events.turnEnd` | 布尔/对象 | 任务结束总开关，可按 reason.kind（completed/error/blocked/aborted/max-tokens/interrupted）分控 | 全开 |
| `events.approval` | 布尔 | 审批请求是否推送 | 开 |
| `events.agentError` | 布尔 | agent 报错是否推送 | 开 |
| `toolRateLimitPerMinute` | 数字 | 模型调 notify 工具每分钟上限（防注入刷屏） | 10 |
| `inbound.allowUsers` | 字符串数组 | 入站白名单用户 ID（首次 YAML 播撒后由管理台或 /pair 管理） | 空（进入引导态） |
| `inbound.telegram` / `feishu` / `qq` / `wxpusher` / `wechat` / `dingtalk` | 对象 | 各入站通道凭证（具体字段见 `FIELD_HINTS`，可省略走扫码/CLI 落盘凭证） | 各自缺省 |
| `inbound.tokenSecret` | 字符串 | HMAC 一次性 token 的对称密钥；不填则重启后旧 token 全废 | 进程随机 |
| `inbound.stateDir` | 字符串 | state.json / 账本 / 审计文件目录 | `~/.dsh-notifier/` |
| `inbound.conversation` | 对象 | 会话路由参数（`mergeWindowMs` 等） | 默认 |
| `approval.mode` | 字符串 | 审批远程响应模式（`answer` 远程可决 / `observe` 只旁观） | 未启用 |
| `approval.timeoutMs` | 数字 | 审批超时回落桌面的等待时长 | 120000 |
| `questions.enabled` | 布尔 | 是否启用 ask_user 远程提问工具 | 开 |
| `questions.timeoutMs` | 数字 | 提问超时（30s-30min 钳制） | 300000 |
| `questions.rateLimitPerMinute` | 数字 | 模型调 ask_user 每分钟上限 | 6 |
| `route.sessionTtlHours` | 数字 | 会话路由过期时长 | 24 |
| `admin.enabled` | 布尔 | 是否启动本机 Web 管理台（host 写死 127.0.0.1 不可配） | 关 |
| `admin.port` | 数字 | 管理台端口（1-65535） | 8104 |
| `admin.token` | 字符串 | 管理台 Bearer token；不填首启自动生成（stdout 打印一次） | 自动生成 |
| `digest.enabled` | 布尔 | 是否启用通知账本 + 每日摘要推送 | 关 |
| `digest.maxEntries` | 数字 | 账本保留条目上限 | 默认 |
| `segment.maxCodepoints` | 数字 | 超长消息按 Unicode 码点分段阈值 | 1200 |
| `public.enabled` | 布尔 | 是否对外暴露 notifier 服务 | 开 |
| `public.limitPerMinutePerSource` | 数字 | 每个调用方每分钟推送上限 | 10 |
| `public.emit` | 布尔 | 是否发射 dsh-notifier/sent 事件 | 开 |

## 常见问题

**Q: 安装后需要先做什么才能收到通知？**

A: 在 profile 的 `cordis.patch.yml` 里给 dsh-notifier 行的 `config` 加 `channels`，至少配置一个渠道的凭证。空 channels 时插件不会报错，只会订阅事件但不动作。

**Q: 怎么把审批从手机送回桌面？**

A: 启用 `inbound` 部分并配置至少一个入站通道（telegram/feishu/qq/wxpusher/wechat/dingtalk）。首次部署时在 `inbound.allowUsers` 写白名单用户 ID（YAML 首播撒后改由管理台或 `/pair` 管理）。后续在通道私聊里点按钮或回复 1/2 即可裁决。

**Q: 通知频率怎么控制？**

A: 三个维度叠加：`events` 段控制哪些宿主事件触发推送；`debounceMs` 控制同会话连续轮次尾沿合并（默认 10s）；`graceSeconds` 控制空闲宽限窗（默认 0）。三者互不影响，可独立调。

**Q: 长任务跑着没动静怎么知道还活着？**

A: 默认 `longRunning`（15 分钟首条心跳、之后每 15 分钟再来一条）和 `stall`（10 分钟无事件即疑似卡住）都开启。Telegram/飞书渠道的通知卡片自带一键停止按钮，点下去就取消 turn。`turnStart` 默认关闭，避免每个任务开始都响。

**Q: 其他插件可以借用这个通知系统吗？**

A: 可以。注入 `notifier` 服务（`ctx.inject(['notifier'], …)`）调 `push()`，或订阅 `dsh-notifier/sent` 事件监听每次广播结果。按 source 限流 10 次/分钟（`public.limitPerMinutePerSource`），超额返回结构化 skipped 不会抛错。

**Q: Web 管理台安全吗？**

A: host 写死 `127.0.0.1`，永不绑公网（不可配）。token 首启自动生成并在 stdout 打印一次，重启后用 SHA-256 哈希校验；显式配置 `admin.token` 时以其为准。需要远程访问请自行反代。

**Q: 卸载后状态数据会保留吗？**

A: `state.json` 留在 `$HOME/dsh-notifier/`（`stateDir` 可改），记录白名单、配对码、账本、审计日志。卸载插件不会自动清盘，重装可继续；如需彻底清理请手动删除该目录。

**Q: 渠道配额用完或者断网时会发生什么？**

A: 每渠道独立熔断（短时重试退避），未达熔断阈值时按分档重试；阈值耗尽则该渠道跳过，宿主继续运行，账本记录失败次数，其他渠道不受影响。

## 上手难度
进阶 — 配置矩阵大（27 渠道 × 入站出站双向 × 多级开关），新手建议从 Web 管理台入手（自动生成配置），手写 YAML 时注意 `${ENV:NAME}` 引用语义（出/入站均支持）。

## 已知问题与限制
- WxPusher 入站依赖 HTTP 回调，需要公网可达（frp/反代由用户自行解决）；其他 5 个入站通道走长连接/长轮询，免公网 IP
- QQ/钉钉/微信入站不支持按钮卡片，仅靠编号回复（回复 1 批准 / 2 拒绝）裁决
- Windows 系统桌面通知需要 PowerShell 的 BurntToast 模块，否则该渠道本地侧无效
- 钉钉 Stream 实现里复用了官方 SDK 字段拼写（`uesrAgent`，源码里已写明）——属上游约定，照搬
- 飞书入站依赖 `@larksuiteoapi/node-sdk`（optionalDependency），未安装时 SDK 加载失败给中文指引后静默降级
- 长任务心跳/卡住提醒的时距下限钳制 60s（`Math.max(60_000, Number(x) || 默认)`），不允许配 0 关闭——关闭请用 `enabled: false`
- `state.json` 多进程同写已加键级合并 + 跨进程写锁 + 读收敛三层防御，但极端情况下（同时大量写入）仍可能降级为无锁写入并 warn 一次
- v0.7.3 修复过飞书 WSClient `logger: null` 静默崩溃与卡片 message_id 取值错（CHANGELOG #1/#6），升级前请确认已修复
- Web 管理台 host 写死 127.0.0.1（不可配），需要远程访问由用户自行反代——任何暴露管理台的尝试都被代码层阻断

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-notifier](https://deepseek-plugin.org/plugins/THEWOLFWALKER/dsh-notifier)
Wiki generated by AI (model: `MiniMax-M3`)
