dsh-daily-notify
v0.1.0
Published
Trigger × channel separated notification plugin for the DeepSeek Harness (developer preview)
Readme
notify
dsh(DeepSeek Harness)通知插件:触发 × 渠道分离——触发层监听 agent 运行结束 / 等待审批 / 运行出错三类事件,渠道层把通知投递到 webhook 或桌面通知,两侧独立配置、独立扩展。
状态:developer preview。已通过
tsc --noEmit类型检查(对齐@deepseek-ai/[email protected]/[email protected]的事件与工具签名)与全部纯函数/注入替身单测;尚未在真实 dsh 运行时中验证,手动验证步骤见下文。
功能
触发(事件监听,可独立开关)
以下事件名与载荷均核对自 dsh master 源码与文档(docs/subsystems/core.zh.md、approval.zh.md,及 npm 包 @deepseek-ai/dsh-agent / dsh-user-approval 的类型声明)。注意:dsh 没有 agent/step、agent/idle 事件,实际可观察的接缝是:
| 触发 | dsh 事件 | 说明 |
| --- | --- | --- |
| 运行结束 | agent/status(emit,{ agent, status }) | status 转为 idle 时触发;插件在 running 时记录起点,仅当本轮运行耗时 ≥ minRunMs(默认 30s)才通知,短任务不打扰 |
| 等待审批/输入 | session/event 中 type === 'approval/asked'({ id, toolName, reason? }) | 审批服务向用户提问时触发 |
| 运行出错 | agent/error(emit,{ agent, turn, step, error }) | step/turn 出错时触发 |
- 去重:同一去重键(如
run-finished:<agentId>、approval:<requestId>、run-error:<agentId>:<turn>)在dedupeWindowMs(默认 10s)内只通知一次;手动发送与测试发送不参与去重。 - 触发层只负责组装消息并扇出到已启用渠道;发送失败只写日志,不影响 agent 运行。
渠道(Provider 接口,易扩展)
内置两个零依赖渠道:
- webhook:把消息以 JSON(
{ source, kind, title, body, timestamp })POST 到配置的 URL;每次尝试带超时(默认 5s),网络错误或 5xx 自动重试一次,4xx 不重试。 - desktop:桌面通知,按平台调用子进程——Windows 用 PowerShell WinForms BalloonTip、macOS 用
osascript display notification、Linux 用notify-send;二进制缺失时报出平台相关的可读错误(如提示安装 libnotify)。其他平台明确报不支持。
新渠道(飞书 / 钉钉 / 邮件等)实现 NotificationProvider 接口({ id, send(message, context?) },失败不抛异常、返回 { ok, detail })即可接入;网络与进程访问均为可注入依赖(fetchFn / executor)。
工具
| 工具 | 作用 |
| --- | --- |
| notify_preview | 预览:按类型组装标题/正文,展示 webhook 将 POST 的 JSON 与目标渠道,不实际发送;wouldNotify 告知当前去重窗口下该事件是否会真正发出 |
| notify_send | 手动发送一条通知(L1 级副作用:向 webhook/桌面发出用户可见信息,已在描述中标注供审批门分级);channel 可指定单一渠道 |
| notify_history | 查询本进程内的发送历史(最新在前),可按类型过滤、限制条数 |
| notify_channels | 列出全部渠道及启用/配置状态;test: true 时向每个已启用渠道发送测试通知——首次启用新渠道后建议执行一次 |
所有工具返回 output.schema 声明的结构化 JSON;notify_send / notify_channels(test) 响应 exec.signal 取消;webhook 发送有超时;失败均转为可读中文错误。
历史
- 内存环形缓冲(默认 200 条,溢出淘汰最旧);可选
history.persist追加写入 JSONL 文件(auditLog 式开关,默认关)。 history.redact(默认开)在发送前与记录前对标题/正文做脱敏:内置 AWS AKIA / GitHub token / Bearer /sk-…/ PEM 私钥头 /.env风格KEY=value模式,extraPatterns可追加自定义正则。实现自包含,不依赖 approval-guard 包。
配置
- id: notify
name: dsh-daily-notify
config:
enabled: true
channels:
webhook:
enabled: false
url: '' # 例:https://open.feishu.cn/open-apis/bot/v2/hook/xxx
timeoutMs: 5000
retry: true
desktop:
enabled: true
triggers:
runFinished:
enabled: true
minRunMs: 30000 # 短于此耗时的运行不通知
approvalNeeded:
enabled: true
runError:
enabled: true
dedupeWindowMs: 10000
history:
persist: false
filePath: notify-history.jsonl
maxEntries: 200
redact: true
extraPatterns: []所有叶子字段都有默认值,空配置即可启用(默认仅 desktop 渠道)。
权限透明
- 读写位置:仅当
history.persist: true时向history.filePath(默认进程工作目录下的notify-history.jsonl)追加历史行;此外不读写任何文件。 - 网络域名:仅
channels.webhook.url配置的那一个 URL(默认未配置、不联网)。除此之外无任何网络请求。 - 凭证:不持有、不读取任何凭证;webhook URL 本身即凭证(如飞书/钉钉机器人 URL 含密钥),请按凭证标准保管 dsh 配置文件。URL 不会写入历史记录,仅出现在
notify_channels的状态输出中。 - 确认点:插件自身不发起 ask;
notify_send在描述中标注 L1 写语义,若同时启用 approval-guard,未知工具默认 L2(每次 ask),可用其overrides调整。 - 日志内容:历史记录(内存 + 可选 JSONL)含通知类型、标题、正文、渠道、状态、错误与时间;
history.redact开启时内容已脱敏。不记录 webhook URL、不记录事件原始载荷。
已知限制
- "运行结束"以
agent/status → idle近似:多轮连续运行会各触发一次(由去重窗口收敛);dsh 未提供"一整轮任务"的专用事件。 - desktop 渠道无显式超时:PowerShell 脚本自身含 2s 等待,
osascript/notify-send通常为秒级;若系统通知服务卡死,发送会挂到调用方取消。 - Windows 使用 BalloonTip 而非新式 Toast(Toast 需要注册 AppUserModelID,超出零依赖范围);通知需在系统设置中允许 PowerShell 气球提示。
approval/asked触发依赖 dsh 审批服务发出该会话事件;绕过审批服务的交互(如直接 UI 弹窗)不产生通知。- 脱敏基于正则模式,不能保证覆盖所有秘密形态;可用
history.extraPatterns补充。 - 历史为进程内状态,重启后仅 JSONL 文件(若开启持久化)留存。
手动验证
CI 只覆盖类型检查与纯函数/注入替身单测。接入真实 dsh 后请按以下步骤验证:
- 在 dsh 配置中启用
notify,启动日志应出现notify: loaded(渠道:desktop)。 - 让 agent 调用
notify_channels(test: true)→ 桌面应弹出测试通知;工具输出中 desktop 测试成功。 - 配置
channels.webhook(可用 webhook.site 之类的临时地址),再次notify_channels(test: true)→ 该 URL 应收到一条 JSON POST。 - 让 agent 调用
notify_send(title/body 任意)→ 两渠道均收到;随后notify_history应能看到对应记录。 - 触发一次审批(如配合 approval-guard 执行 L2 工具)→ 桌面/webhook 收到"等待你的审批"。
- 让 agent 跑一个超过 30s 的任务 → 结束后收到"任务运行结束";再跑一个秒级任务 → 不通知。
- 让 agent 触发一次模型错误(如配置无效 provider)→ 收到"运行出错"。
- 开启
history.persist后重复步骤 4 →notify-history.jsonl逐行追加;在 body 中放入AKIAIOSFODNN7EXAMPLE→ 记录与通知中均已替换为[REDACTED:aws-access-key]。
许可
MIT
