前言¶
在使用 DeepSeek Harness(DSH)跑任务时,很容易忘记 Agent 在后台做了什么、什么时候卡住、什么时候需要你介入。这个插件会在 DSH 的 Agent 跑完任务、卡住、需要你批准某个操作、模型出错停下时,推一条通知给你。
它只支持两种接口:ntfy(推到手机)和你自己的 webhook。其他服务需要自己用 webhook 模板对接。
它是什么¶
这是 DeepSeek Harness 的一个插件,由 ztllll 维护。安装后,DSH 会在特定场景下向 ntfy.sh 或你自建的 webhook 发送 JSON 通知。它不依赖任何第三方登录,只做 HTTP 请求。
核心功能¶
通知场景¶
插件会在以下情况发送通知:
- done:一个任务真正做完(连续几轮算一次);
done:goal表示目标完成 - human:Agent 在等待你批准操作(
approval)、问你问题(question)、让你审阅计划(plan)、自己声明需要人(wait)、目标被阻塞(goal-blocked) - error:模型请求失败导致中断(认证失效/额度用完/限流/超时等,附一句可操作的提示);输出被长度上限截断(
max-tokens) - stuck:模型请求已连续重试 4 次(还没失败,但很可能要失败);工具连续失败;原地打转(同一调用重复);很久没任何动静;某个工具调用迟迟不返回
通知内容¶
所有通知以高优先级发送:ntfy 收到 priority: 4(等同 X-Priority: high);webhook 的 JSON 里 priority 字段也是 4。
通知以 JSON 格式发送,包含以下字段:
| 字段 | 含义 |
|---|---|
kind |
done / human / error / stuck |
sub |
细分类型,如 approval、goal、retry;没有就是空串 |
title / text |
现成的标题和正文 |
priority |
固定为 4 |
project |
项目名 |
cwd |
工作目录 |
session |
会话 ID 前 12 位 |
duration |
用时(如 3分12秒) |
durationSec |
用时秒数 |
time |
ISO 时间 |
label |
类别名 |
隐私设置¶
通知内容受 detail 配置控制:
minimal:只发固定短语、项目名、用时、错误码。没有任何对话内容normal(默认):以上 + Agent 的最后回复(完成时)、Agent 提的问题、目标描述、工具名full:以上 + 审批的命令原文、服务端返回的错误信息
安装与启用¶
在 DSH 侧栏 插件 → 添加插件,填:
github:ztllll/dsh-push-notify
装好后刷新页面。没配置任何接口时,插件完全空转,什么都不会发。
更新:请在插件页(插件管理器)里操作。命令行 dsh plugin add 只改磁盘,不会通知正在运行的 DSH 重新加载这个插件。
配置接口¶
方式一:插件面板(最简单)¶
侧栏 插件 → 已安装 → 推送通知,在「通知地址」里填一个 http:// 或 https:// 地址,点保存。下一条通知起生效,不用重启。
填 https://ntfy.sh/主题 就按 ntfy 发到手机;填其他地址就按 webhook 把 JSON POST 过去(格式见上文)。留空则不发。
面板只管这一个地址;多个接口、事件开关、阈值请用方式二或方式三。
方式二:对话里用 /notify(推荐,立刻生效)¶
/notify 查看状态
/notify add <url> [name=手机] [preset=ntfy|webhook] [token=…] [events=done,human]
/notify test [名字|#序号] 发一条测试通知
/notify rm <名字|#序号> 删除
/notify on | off 总开关
- 接口保存在
~/.dsh/dsh-push-notify/endpoints.json,权限0600(里面有 webhook 密钥) /notify命令本身不会被记入会话日志;回显、状态和日志里只显示地址的域名(如ntfy.sh/…),路径和参数一律不显示ntfy.sh的地址自动按 ntfy 发送;自建 ntfy 服务器请加preset=ntfy。其他地址都按 webhook 发送token=:ntfy 开了访问控制时填,插件会带上Authorization: Bearer <token>
方式三:写在插件配置里(可以多个、可以每个接口选不同事件)¶
dsh 的 profile 里(cordis.patch.yml):
- id: push-notify
config:
detail: normal # normal | minimal | full
lang: auto # auto | zh | en
endpoints:
- name: 手机
url: https://ntfy.sh/一个只有你知道的长随机主题
- name: 我的服务
url: https://example.com/dsh-hook
headers: { Authorization: "Bearer 你的密钥" }
events: [human, error] # 这个接口只收 需要人 和 出错
ntfy 配置¶
| 情况 | 怎么填 |
|---|---|
| 用 ntfy.sh | https://ntfy.sh/一个只有你知道的长随机主题。手机装 ntfy App,订阅同一个主题 |
| 自建 ntfy | /notify add https://你的域名/主题 preset=ntfy |
| 开了访问控制 | 再加 token=tk_… |
主题名就是口令,请用长随机串。插件把通知以 JSON 发布到服务器根地址,中文标题不会乱码。
Webhook 收到什么¶
不写 body 时,每条通知是一个 POST,Content-Type: application/json,正文如下:
{
"title": "✅ 任务完成 · demo",
"text": "已完成:修复了登录页的 bug。\n用时 3分12秒",
"kind": "done",
"priority": 4,
"tags": ["white_check_mark"],
"project": "demo",
"cwd": "/w/demo",
"session": "s1",
"sub": "",
"duration": "3分12秒",
"durationSec": 192,
"time": "2026-10-19T07:00:00.000Z",
"label": "任务完成"
}
发送成功判定¶
你的服务返回任意 2xx 就算成功,响应体不看。408、425、429 和 5xx 会重试,连接失败或超时也会重试:默认间隔 1 秒、3 秒,最多重试 2 次;对方给了 Retry-After 响应头或 JSON 正文里的 retry_after 秒数就按它等,但单次最多等 30 秒。其他状态码不重试,记一行日志。不跟随重定向,请直接填最终地址。
接入其他服务¶
飞书、钉钉、企业微信、Slack、Telegram 等都能收 HTTP 请求,但各家要的 JSON 格式不同、鉴权方式也不同。插件不内置它们,你可以用 webhook 的 url / headers / body 模板把请求改成对方要的样子:
- id: push-notify
config:
endpoints:
- name: 某个机器人
method: POST # 默认 POST;也可以 GET
url: https://example.com/hook?t={{title}} # url 里的变量会被 URL 编码
headers: { X-Token: 你的密钥 }
body:
msg_type: text
content: { text: "{{title}}\n{{text}}" }
level: "{{priority}}" # 整个值恰好是一个变量时保留原类型:这里是数字 4
可用变量就是 JSON 里的全部字段名,写成 {{字段名}}。先解析、再替换:变量是逐个替换到每个字符串里的,标题里有引号、换行也不会弄坏 JSON。写错的变量名会原样留在通知里,方便发现。对方要表单就加 headers: { Content-Type: application/x-www-form-urlencoded },body 的每个字段会变成一个表单项。body 也可以写成一段 JSON 字符串,效果相同。
对接前请自己确认:1. 对方的 JSON 格式(以它的官方文档为准);2. 是否需要签名——签名一般要用当前时间算哈希,模板做不到,需要你自己写个小转发服务,或者 fork 本插件;3. 对方「HTTP 200 但正文里报错」时,插件仍会当成功。
验证方法:配好后发 /notify test 名字,看对方有没有收到。
可靠性设计¶
- 发送失败不拖累 Agent:超时 10 秒、最多重试 2 次(尊重
Retry-After)、永不抛错;失败只写一行日志,且日志里抹掉密钥 - 不跟随重定向:POST 被 301/302 变成 GET 会悄悄丢消息,所以直接报错并提示你用最终的 https 地址
- 回复不会张冠李戴:只取根会话最近一次有文字的回复;新任务从空开始,不会带出上一个任务的回答;子代理写的文字不算
- 去重 + 限流:同一类错误 60 秒内不重复;
retry模型请求每会话每小时最多 1 次;每接口每分钟最多 20 条,超出的会在下一条里写明「被抑制了 N 条」 - 子代理不单独响;根会话要等整棵树都停了才算完成
- 配置写错不会让 DSH 起不来:坏值回落默认并在
/notify状态里点名 - 损坏的
endpoints.json绝不覆盖
已知限制¶
- dsh 进程崩溃或机器断电无法通知——插件和 DSH 同进程。需要这个请另配外部看门狗
- 通知不排队落盘:重启 DSH 时还没发出去的会丢
- 成功 = 服务端接受了请求,不保证手机上一定弹出(那取决于推送服务和你的设备)
- 只对 ntfy 做过真实推送验证;webhook 在本地 HTTP 服务上验证过。其他服务需要你按「接入其他服务」自己对接和验证
ntfy.sh上的主题名等同口令:请用长随机串,不要用Project这类好猜的名字
调参建议¶
主要阈值(均有范围保护):settleSec(8) 停多久算收工 · minDurationSec(60) 短于此的不算任务 · humanDelaySec(5) 给你多久在屏幕前答复 · remindAfterSec(0=不催) · stallSec(900) · toolStallSec(7200) · retryAt(4) · toolFailStreak(5) · loopRepeat(8) · cooldownSec(60) · quietSec。默认值来自对真实会话日志的实测。
可以先在历史会话上试跑:
node scripts/replay.mjs --limit 80 --detail # 用你的真实会话日志回放,看会推多少条(不联网、不打印对话内容)
node scripts/replay.mjs --set stallSec=600 # 改一个阈值再看
node scripts/live-ntfy.mjs # 往一个随机 ntfy 主题发每一类样例,并回读确认
适用场景与注意¶
适合需要实时掌握 Agent 状态、希望减少人工盯屏的开发者。插件以当前 DSH 进程的权限运行,安装前应检查源码与许可证。
插件不保证通知的送达率,也不提供离线存储。如果你需要这些能力,需要自己实现或另配外部看门狗。
结尾¶
这个插件为 DSH 提供了一种轻量级的通知机制,只在 Agent 完成任务、卡住、需要人工介入或模型出错时才发通知,避免刷屏。它通过 ntfy 或 webhook 将通知发送到你的设备或服务,配合模板可以对接多种第三方服务。