dsh-macos-notify:用 DSH 自身身份发 macOS 原生通知

前言

在 macOS 上给 DeepSeek Harness(DSH)加通知,社区里常见的做法是开一个外部进程跑 osascript -e 'display notification ...'。这条路在新版 macOS 上有两个实打实的问题:通知实际归属发起 osascript 的父进程,与 DSH 的签名对不上,系统可能一声不响把通知丢掉;就算显示出来,图标也不是 DSH,点击也回不到会话。

dsh-macos-notify 换的思路是:让 DSH 应用自己的 GUI 进程来发通知。插件由 Chanceux-L 维护,属于客户端类插件,通过 DSH 的双半(宿主 + 客户端)结构实现,目标是通知归属 DSH 自己的名字和图标,并且点一下就能跳回对应会话。

架构

插件分宿主半边和客户端半边,中间靠 Web 接口轮询连接:

┌─ 宿主半边 index.js(Node,inject: ['sessions','webServer'])──────────┐
│ 监听顶级会话事件:turn/end(completed/error)、ask_user_question、   │
│ approval/asked                                                        │
│  → 通知写入内存队列(上限 100 条)                                    │
│  → ctx.webServer.register 暴露:                                      │
│      GET /macos-notify/now          → 当前最新通知 id                 │
│      GET /macos-notify/pending?since=N → 增量拉取队列                 │
│ 子代理 / fork / team 成员会话带 parentSession / delegationDepth 头,  │
│ 直接跳过——只有用户可见的顶级会话才通知。                             │
└──────────────────────────────────────────────────────────────────────┘
                              │ 轮询(2 秒)
┌─ 客户端半边 client.js(Web GUI 页面,inject: ['uiWorkspace'])───────┐
│ window.__ModuleLoader__.load 注册,每 2s 拉取队列                     │
│ → new Notification() 发出——Electron 渲染进程中即 DSH 应用自身的     │
│   macOS 原生通知                                                      │
│ → onclick 调 uiWorkspace.openSession(sessionId) 跳转会话 +           │
│   window.focus()                                                      │
│ Notification 不可用/被拒时回退:页内 AudioContext 提示音 + 标题闪烁  │
└──────────────────────────────────────────────────────────────────────┘
                              │ 页面关闭(超过 clientQuietMs 无轮询)
┌─ 宿主回退通道 ──────────────────────────────────────────────────────┐
│ afplay 播 /System/Library/Sounds 系统音效                             │
│ 阻塞输入(提问/审批)时额外 open -a "DeepSeek Harness" 拉起窗口     │
└──────────────────────────────────────────────────────────────────────┘

宿主半边负责监听会话事件并维护队列,客户端半边在渲染进程里执行 new Notification(),这正是通知能归属 DSH 签名的原因。子代理、fork 出来的会话和 team 成员会话会被跳过,只有用户直接看到事件的顶级会话才通知。

核心功能

  • 回合完成 / 失败通知:turn/end 时按 reason.kind 分派,失败附错误消息(截断至 120 字符),并显示回合耗时。
  • 提问通知:Agent 调用 ask_user_question 时通知第一个问题(含 header),多个问题时显示 (+N more)。
  • 审批通知:approval/asked 时通知工具名与原因。
  • 点击跳转会话:通知 onclick → uiWorkspace.openSession(sessionId) + window.focus()。
  • 无 GUI 回退:页面关闭超过 clientQuietMs 未轮询时,宿主直接 afplay 播系统音效;提问/审批还会 open -a 拉起 DSH 窗口。
  • 智能过滤:跳过子代理、fork、team 成员会话;短于 minTurnSeconds 的回合不通知;超过 10 分钟的过期通知不补发。

全部行为都可通过配置关闭或调整,见下文配置项。

安装与启用

推荐用 DSH CLI 以 github: 源码形式安装:

dsh plugin --profile web add github:Chanceux-L/dsh-macos-notify

也可以手动把目录链接或克隆到 profile 的 node_modules 下:

git clone https://github.com/Chanceux-L/dsh-macos-notify \
  ~/.dsh/profiles/desktop/node_modules/dsh-macos-notify
# 或者开发时用符号链接:
ln -s /path/to/dsh-macos-notify ~/.dsh/profiles/desktop/node_modules/dsh-macos-notify

插件通过 cordis.patch.yml 以 insert 方式注册(id: macos-notify),不需要手动改配置文件。重启 DSH 后,第一次发通知会触发系统的通知权限弹窗,允许即可。

配置项

在 profile patch 的 config: 下覆盖,默认值定义在 index.js 的 DEFAULTS。

键 类型 默认 说明
onCompleted boolean true 回合完成时通知
onError boolean true 回合失败时通知
onQuestion boolean true Agent 提问时通知
onApproval boolean true 需要工具审批时通知
minTurnSeconds number 5 回合短于该秒数不通知
titlePrefix string "DSH" 通知标题前缀,格式 前缀 · 会话标题
soundCompleted string "Glass" 宿主回退音效(/System/Library/Sounds 下的文件名,不带 .aiff;'' 关闭)
soundQuestion string "Ping" 提问回退音效
soundApproval string "Funk" 审批回退音效
soundError string "Basso" 失败回退音效
activateOnInput boolean true 无 GUI 且提问/审批阻塞时 open -a 拉起 DSH 窗口
clientQuietMs number 15000 客户端超过该毫秒未轮询,宿主启用回退通道
debug boolean false 追加投递诊断到 ~/.dsh/macos-notify.log

配置示例:

- insert:
    - id: macos-notify
      name: 'dsh-macos-notify'
      config:
        titlePrefix: DSH
        minTurnSeconds: 10
        soundCompleted: Glass

典型用法

按上面的命令装好并重启 DSH 后,最小可用配置就是默认值,不需要额外写 patch。下面给出常见调整的示例。

想让快速连续的回合别刷屏,把阈值抬高:

- insert:
    - id: macos-notify
      config:
        minTurnSeconds: 30

页面常驻、不需要宿主回退音效时,可以关掉对应开关:

- insert:
    - id: macos-notify
      config:
        onCompleted: true
        onError: true
        onQuestion: true
        onApproval: true
        clientQuietMs: 600000
        activateOnInput: false

排障时打开 debug,投递诊断会追加到 ~/.dsh/macos-notify.log:

- insert:
    - id: macos-notify
      config:
        debug: true

与现有插件对比

dsh-macos-notify(本插件) CrombastiC/dsh-macos-notify jypjypjypjyp/dsh-notifier dsh-complete-notify
通知通道 应用自身 GUI 进程(Web Notification) 外部 osascript 进程 外部 osascript 进程 外部 osascript 进程
macOS 签名校验 通过,归属 DSH 自身 可能被静默丢弃 可能被静默丢弃 可能被静默丢弃
通知图标归属 DeepSeek Harness 其他应用身份 其他应用身份 其他应用身份
点击跳转会话 ✅ uiWorkspace.openSession ❌ ❌ ❌
页面关闭回退 ✅ afplay + open -a 部分 部分 部分
提问/审批/失败分类通知 ✅ 部分 部分 部分

适用场景与注意

适合在 macOS 上日常用 DSH 图形界面、希望回合结束、Agent 提问或需要审批时收到可点击通知的开发者。插件本身只做通知投递,不改变会话逻辑。

几点需要注意:

  • 插件以当前 dsh 进程的权限运行,安装前应先检查源码与许可证(本仓库为 MIT)。
  • 插件依赖 Electron 渲染进程里的 Web Notification API,需要 macOS 允许 DSH 发通知;首次触发会弹系统权限框。
  • Dock 图标数字角标需要 DSH 官方在 Electron 主进程暴露 app.dock.setBadgeCount IPC,插件层无法实现。
  • 跨工作区会话跳转尚未实现,uiWorkspace.openSession 当前只支持当前工作区内的导航。
  • 宿主回退音效依赖 /System/Library/Sounds 下的系统音效文件。

小结

dsh-macos-notify 解决的问题很具体:让 DSH 的通知在 macOS 上真正归属于应用自己,并且能点回会话。它用双半插件结构把事件监听、队列和原生通知串起来,配套了过滤、回退和全套配置。如果需要在 macOS 上给 DSH 加通知,这是一个可以直接落地的方案。

目录页:https://www.skillhub.cn/plugins/Chanceux-L/dsh-macos-notify

GitHub:https://github.com/Chanceux-L/dsh-macos-notify

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜