dsh-macos-notify
让 DeepSeek Harness(DSH)在 macOS 上以应用自身身份弹出系统原生通知,点击即可跳回对应会话。
English summary: Native macOS notifications for DeepSeek Harness, emitted by the app's own GUI process (not an external osascript process), so they are never silently dropped by macOS signature attribution and clicking one jumps straight to the originating session.
问题背景
社区现有的 DSH 通知插件(CrombastiC/dsh-macos-notify、jypjypjypjyp/dsh-notifier、dsh-complete-notify 等)全部通过外部进程通道发通知——通常是 osascript -e 'display notification ...'。在现代 macOS 上这条路径有两个硬伤:
- 静默丢弃:通知实际归属的是发起 osascript 的父进程身份,与 DSH 应用签名不匹配,系统可能不做任何提示地丢弃它,用户只看到"没有通知"。
- 错误归属 + 不可交互:即使显示出来,通知图标也不是 DSH,点击也无法跳回会话。
正确的做法只有一个:由 DSH 应用自身的 GUI 进程发出通知。Electron 渲染进程里的 new Notification() 正是这样——通知归属 DSH 自己的名字和图标,支持 onclick 跳转。本插件即基于此实现。
架构
插件是 DSH 的双半(宿主 + 客户端)插件:
┌─ 宿主半边 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" 拉起窗口 │
└──────────────────────────────────────────────────────────────────────┘
特性
- 回合完成 / 失败通知:
turn/end时按reason.kind分派,失败时附错误消息(截断至 120 字符),并显示回合耗时。 - 提问通知:Agent 调用
ask_user_question时通知第一个问题(含 header),多个问题时显示(+N more)。 - 审批通知:
approval/asked时通知工具名与原因。 - 点击跳转会话:通知
onclick→uiWorkspace.openSession(sessionId)+window.focus()。 - 无 GUI 回退:页面关闭超过
clientQuietMs时,宿主直接afplay播系统音效;提问/审批还会拉起 DSH 窗口。 - 智能过滤:跳过子代理、fork、team 成员会话;短于
minTurnSeconds的回合不通知(避免快速连续回合刷屏);超过 10 分钟的过期通知不补发。 - 全部可配置(见下表)。
配置项
在 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 |
安装
推荐使用 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):
- insert:
- id: macos-notify
name: 'dsh-macos-notify'
config:
titlePrefix: DSH
minTurnSeconds: 10
soundCompleted: Glass
与现有插件对比
| 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 | 部分 | 部分 | 部分 |
| 提问/审批/失败分类通知 | ✅ | 部分 | 部分 | 部分 |
已知限制
- Dock 图标数字角标:需要 DSH 官方在 Electron 主进程暴露
app.dock.setBadgeCountIPC,插件层(渲染进程)无法实现。欢迎在 DSH 主仓提 feature request。 - 跨工作区会话跳转:
uiWorkspace.openSession当前仅支持当前工作区内的导航,跨工作区跳转尚未实现。 - 宿主回退音效依赖
/System/Library/Sounds下的系统音效文件。
开发
node --check index.js && node --check client.js
License
MIT © 2026 Chanceux-L
No comments yet. Be the first to write one.