DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Lon-dawn /

Lon-dawn/dsh-notify-whale

Verified

鲸吟(dsh-notify-whale):DSH 通知插件加固 fork。修复上游在 Node 22/24 上 HTTP 通道完全无法投递等 5 个 bug,新增等待审批/提问通知与事件级铃声。AI 主导修改。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@c8842d73

鲸吟 · dsh-notify-whale

DSH(DeepSeek Harness)通知插件:会话结束时推手机,卡在审批或提问等你操作时也推。

中文名 鲸吟;项目代号与 npm 包名均为 dsh-notify-whale。

  • macOS:系统通知中心(osascript,零依赖)
  • Windows:Toast 通知(PowerShell + WinRT,含 PNG 图标)
  • 手机:Bark(iOS)/ ntfy(Android·iOS)/ Server酱 / 任意 Webhook

纯 ESM .mjs、无构建步骤,基于 DSH 内置的 Cordis 插件体系。

这是一个加固 fork

上游:DeepseekHarnessPlugins/Notification(包名 dsh-task-notify,MIT)。

上游原版在 Node 22 / 24 上 HTTP 通道完全无法投递——不是配置问题,是代码问题(见下面「修了什么」第 1、2 条)。本 fork 修复后在真实 DSH 上跑通并验证:安卓(ntfy)+ iPhone(Bark)双端实测可收。

按 MIT 许可发布,保留原作者版权声明(见 LICENSE)。

⚙️ 本项目由 AI 主导修改

本仓库的代码改动、测试与文档由 AI 编程代理完成(跑在 DSH 上),人类负责决策、验收与发布。改动方向与取舍记录在提交信息与本文件中。

具体来说:上游只提供基础骨架——下面「修了什么」里的 5 个 bug 的定位与修复、2 项新能力的设计与实现、以及 154 项测试的补齐,都是 AI 在真实 DSH 上复现问题后改的。每一处修改都附根因说明与实测证据;拿不出证据的结论不会写进来(例如「上游在 Node 22/24 上 HTTP 通道完全发不出去」是实测复现的,不是读代码推测的)。

在上游与本 fork 之间选择时:本 fork 的价值在于它是"真跑过"的版本,而不是"功能更多"。


与上游的差异

修掉的 5 个必现 bug

# 现象 根因与修法
1 ntfy 通道报 Cannot convert argument to a ByteString 标题被放进 X-Title 头,而 Node 的 fetch/undici 强制 header 值为 Latin-1,中文标题必然抛错。→ 改用 ntfy 官方 JSON 发布(POST 到服务端根 URL,{topic,message,title,icon} 放 body,UTF-8 安全)
2 所有 HTTP 通道(bark / ntfy / serverchan / webhook)静默发不出去 send() 只把 headers / body 传给 httpPost,而后者是 fetch(url, {...init}) —— 没有 method → 变成「带 body 的 GET」→ undici 直接拒绝。→ 显式传 method: "POST"
3 正文永远取不到会话标题,只会显示兜底文案 插件读 agent.session.events,但 DSH 的 Session 没有 events 属性(运行时核对:'events' in Session.prototype === false)。公开 API 是方法 snapshotEvents() → 守卫永不成立,取标题 / 取用户消息两条路径从未执行过。→ 改调 snapshotEvents();兜底同时剥掉 session- 前缀并优先输出 header.cwd 的项目名
4 任务出错、被手动停止也报「任务完成」 本版 DSH 的 AgentStatus 只有 idle / running——出错和手动停止时 agent 同样回到 idle,标题因此恒为「任务完成」。这是误导性通知,比不通知更糟。→ 新增 latestTurnEndReason() 读取最后一条 turn/end 的 reason,据此决定标题(时序已核对:turn/end 在 turn 的 finally 里 append,早于 agent/status: idle 派发)
5 🐛 某些活跃会话的「完成」通知被静默丢弃(症状:只有「待回答」,从无「任务完成」) isRootAgent() 拿 session.header.parentSession 当"子代理"标志。但它的真实语义是上下文续接来源——DSH 续接/派生上下文时会带上它,而这类会话 delegationDepth 仍为 0、origin 也不是 subagent,是有真实人类交互的顶层会话(实测对象:129 条人类提问、104 个回合,完成通知 0 条)。→ 删除该判据。
安全性实测:本机 118 个会话中,origin === 'subagent' 与 delegationDepth > 0 完全等集(各 37 个),二者已是完整判据;删除后只放行 5 个「上下文续接」会话,不含任何真子代理,即该判据纯属误伤

另修一处仅影响测试的上游问题:paths.test.mjs 写死了 PACKAGE_DIR.endsWith('/'),在 Windows 上必失败(fileURLToPath 在 Windows 返回以 \ 结尾)。实现本身是对的,断言已改为跟随平台分隔符。上游全套测试在本 fork 中 154 / 154 通过(含上述断言改造后的 ntfy 传输测试)。

新增的 2 个能力

5. 等待人工介入时也通知

上游只监听 agent/status。等待审批 / 等待回答期间 agent 既不是 idle 也谈不上结束,所以手机端永远收不到——人一离开电脑就一直干等。

本 fork 改为监听 session/event:

  • 会话事件 approval/asked → 「待批准:<工具>(<原因>)」
  • 工具调用 ask_user_question → 「待回答:<问题>[N 选]」

⚠️ 为什么不用 approval/request / user-questions/request:这两个是 waterfall(链式) 事件——前面任一监听器一旦认领请求,后面的监听器根本不会执行。DSH 自带的 Web 桥接器排在前面先认领,第三方插件(在 profile bundles 里排末位)永远轮不到。这是实测结论,不是推测。改用 session/event(普通事件,与监听器顺序无关,也不会干扰审批链)。

6. 事件级铃声(仅 Bark 支持自定义铃声;sound 为全局默认,sounds 按事件覆盖)


通知对照表

结果 标题 event Bark 铃声 图标
正常完成 任务完成 idle fanfare 绿勾
出错 任务出错 error calypso 红叉
手动停止 已手动停止 stopped bell 灰点
被父会话中断 任务已停止 stopped bell 灰点
被中断 任务被中断 interrupted bloom 灰点
输出被截断 输出被截断 truncated fanfare 灰点
等待审批 待批准 awaiting-approval horn 灰点
等待选择 / 回答 待回答 awaiting-answer horn 灰点
未知 / 取不到 任务完成 idle fanfare 绿勾

正文格式:<会话标题或项目名> · <具体事由> · HH:mm。


安装

dsh plugin --profile web add dsh-notify-whale

装完 重启 DSH(插件在 boot 时读一次配置,改配置后同样必须重启)。

从本地目录安装:dsh plugin --profile web add /path/to/dsh-notify-whale。此时 pnpm 以 link: 方式接入、不会给被链接目录装依赖,需在插件目录里单独执行一次 npm install --omit=dev(否则报 ERR_MODULE_NOT_FOUND: js-yaml)。用 npm 安装没有这个问题。

若 pnpm 报 minimumReleaseAge 供应链策略拦截(v11 内置默认),命令末尾追加 --config.minimum-release-age=0 按次放行。


配置

写入 $DSH_HOME/settings.yaml 的 task-notify: 段:

task-notify:
  enabled: true
  notifyOn: [idle, error, blocked]   # blocked 供等待类通知复用
  agents: root                       # root=只通知顶层会话 | all;等待类通知不受此项限制
  coalesceWindowMs: 2000             # 同会话窗口内合并重复通知
  desktop:
    enabled: off                     # auto | on | off(注意:写 false 会警告并回退为 auto)
    sound: true
  format:
    time: short                      # hidden | short | full —— 正文尾部时间样式
    showDuration: true               # 负载携带 durationMs 时追加「用时 X」
  icons:
    enabled: true                    # 图形图标总开关
    urlTemplate: ""                  # 远程图标 URL 模板;含 {event} 则按事件取图,如 https://host/icons/{event}.png
  ntfy:
    enabled: true
    server: https://ntfy.sh
    topic: "你的随机主题名"           # 公共服务器上 topic 就是密码,别用可猜的名字
    token: ""                        # 自建实例需要鉴权时填
  bark:
    enabled: true
    server: https://api.day.app
    deviceKey: "你的BarkKey"
    sound: "fanfare"
    sounds:
      error: calypso
      stopped: bell
      interrupted: bloom
      awaiting-approval: horn
      awaiting-answer: horn
  serverchan:
    enabled: false
    sendKey: ""
  webhook:
    enabled: false
    url: ""
    headers: {}

配置优先级:cordis patch 显式传参 > settings.yaml > 环境变量(DSH_TASK_NOTIFY_*)> 内置默认。

架构与事件契约详见 SPEC.md。


验证

node self-test.mjs                     # 向全部启用通道发一条样例通知
node self-test.mjs --channel ntfy      # 只测单个通道

成功时静默(只有非 2xx 才 warn)。想确认服务端真的收到,从服务端反查:

curl -s "https://<你的ntfy>/<topic>/json?poll=1"

另有交互式菜单可直接编辑配置并当场发测试通知:

npm run menu

已知边界

  • 本版 DSH 的 AgentStatus 只有 idle / running,所以 notifyOn 里的 error / blocked 不会由 agent/status 触发;成败判断走 turn/end,blocked 走等待类路径。
  • 自建 ntfy 若开了 deny-all,手机端(ntfy App)必须配置访问凭据,否则订阅被 403 拒掉、表现为一直「Reconnecting」。
  • 自建服务端请用域名而非 IP:家用宽带 IPv6 前缀会变。
  • Bark 自定义铃声仅 iOS;ntfy 的铃声只能在手机 App 侧设置(其发布协议没有 sound 参数)。
  • self-test.mjs 是独立进程、读磁盘最新配置。它通过不代表运行中的 DSH 宿主进程已加载新配置——改完配置必须重启,并用一次真实事件验证。

License

MIT。本 fork 保留上游版权:

Copyright (c) 2026 DeepseekHarnessPlugins
Copyright (c) 2026 Lon-dawn (dsh-notify-whale fork)
—/ 5

No ratings yet

Verified DSH bundle

Commit c8842d73bfa9

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout