dsh-notified
Verifieddsh-notified · v0.1.0 · MIT
Desktop notifications for DeepSeek Harness: fire a native, brand-labelled notification when a conversation turn finishes, and click it to bring the DSH window back to the front. Windows toast and macOS banner.
Install
dsh plugin add dsh-notified Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-notified
桌面通知插件,专为 DeepSeek Harness (DSH) 打造。全面支持 Windows 与 macOS 双平台。
在一轮对话结束时,自动发送系统原生桌面通知:通知标题为当前会话名称、正文为回答摘要与轮次耗时、图标为 DeepSeek Harness 官方图标。点击通知弹窗即可将 DSH 窗口激活并置于前台。
💡 切换到其他窗口处理其他事务,无需长时间紧盯屏幕等待 Agent 响应。
| 平台 | 投递通道 | 署名显示 | 点击聚焦 |
|---|---|---|---|
| Windows | PowerShell 5.1 → WinRT Toast | 注册 AUMID 后署名为 "DeepSeek Harness" | ✅ |
| macOS | 插件内置 Swift Helper(UserNotifications 框架) | 原生 App 署名 "DeepSeek Harness" | ✅ |
目录
核心特性
- 原生系统级通知:Windows 采用 WinRT Toast API,macOS 采用 UserNotifications 框架,完美融入系统通知中心。
- 官方品牌化呈现:自动注册并关联 DeepSeek Harness 标识与官方图标,告别 PowerShell 或脚本编辑器的通用署名。
- 一键前台唤起:点击弹窗通过系统协议
dsh://open直接唤起并聚焦 DSH 主界面。 - 智能防打扰抑制:
- 前台焦点抑制:用户正在查看 DSH 界面时不触发通知打扰;
- 并发合并机制:时间窗口内相近结束的多个会话自动折叠合并;
- 精准过滤:跳过纯工具调用(无文本回复)轮次、可选择性过滤子代理(Subagent)会话或极短轮次。
- 文本清洗与预算防截断:自动剥离 Markdown 语法标记;耗时字符采用预留预算算法,正文截断时耗时尾串永不丢失。
- 非阻塞无感体验:投递任务完全由后台子进程或常驻 Helper 异步承载,绝不阻塞对话主流程。
- 免管理员权限:所有注册及 Helper 安装均在用户级目录完成,零系统级侵入。
效果预览
┌────────────────────────────────────────┐
│ DeepSeek Harness │
│ 修复解析器边界条件 │
│ 已定位到 off-by-one,补了 3 个单测…(12s)│
└────────────────────────────────────────┘
- 标题:当前会话标题(若尚未生成会话名,则智能回退至工作区目录名或预设名称)。
- 正文:本轮回答的纯文本摘要(自动剥除 Markdown 语法标记)+ 耗时标注。
- 图标:DeepSeek Harness 自身官方图标。
- 点击交互:触发
dsh://open,调用 DSH 桌面端内建逻辑拉起并聚焦窗口。
📌 耗时预留机制:正文截断前,系统会先在
bodyMaxChars预算中扣除如(12s)等耗时尾串的长度,正文仅在剩余预算中排布截断。因此无论正文多长,耗时信息绝不会被切掉。
安装指南
方式一:通过插件管理器安装(推荐)
打开 DeepSeek Harness 侧边栏,进入 插件 → 安装;
在输入框中填入本插件目录在本地的绝对路径:
Windows 示例:
C:\path\to\dsh-notifiedmacOS 示例:
/path/to/dsh-notified(请将上述路径替换为您本地存放
dsh-notified的实际绝对路径)⚠️ 注意:必须使用绝对路径。插件管理器出于安全与解析确定性考虑,会拒绝相对路径。
安装完成后,重启 DeepSeek Harness(原因参见下文 关于安装后生效)。
macOS 首次授权:首次触发通知时,macOS 系统会弹出一次系统授权弹窗(署名为 "DeepSeek Harness"),点击「允许」即可。该授权仅需一次。
方式二:手工配置 Profile
编辑 Profile 配置文件 ~/.dsh/profiles/desktop/package.json:
{
"dsh": {
"profile": {
"bundles": [
"...",
"dsh-notified"
]
}
},
"dependencies": {
"dsh-notified": "link:/path/to/dsh-notified"
}
}
在 Windows 下将 link: 路径替换为类似 link:C:\\path\\to\\dsh-notified 的绝对路径。
配置完成后,在 Profile 所在目录下执行安装并重启 DSH:
pnpm install
macOS 前置要求
| 依赖项 | 最低版本要求 | 说明 |
|---|---|---|
| Xcode 命令行工具 | 任意有效版本 | 需要提供 /usr/bin/swiftc 编译 Helper。运行 xcode-select --install 即可;未安装时仅在日志打印 warn,不影响对话正常运行 |
| macOS 系统版本 | macOS 13.0+ | Helper 的编译目标与 LSMinimumSystemVersion 均设定为 13.0 |
💡 自动构建机制:macOS Helper 会在首次投递通知时自动编译并安装到
~/Library/Application Support/dsh-notified/DSHNotify.app(耗时约 1.5 秒,且完全在后台异步进行,不阻塞任何对话)。无需管理员权限,签名采用 ad-hoc 本地自签名。
关于安装后生效
插件安装或更新后,必须重启 DeepSeek Harness 才能让新代码完全生效。
原因:DSH Host 进程中的模块代(Module Generation)会在安装时刻进行缓存。若直接修改源码,正在运行的 Host 进程不会自动重新读取,apply() 钩子仍按旧版本模块执行。这是 DSH 的既定设计(官方插件规范明确指明「替换已安装的包需要重启才能加载新的 JavaScript 模块代」)。
重启完成后,可通过后文的 dsh_notify_test 工具验证通道就绪状态。
配置说明
插件配置可在 DSH 界面中的 插件页 → dsh-notified 中可视化调整,配置会自动持久化至 ~/.dsh/profiles/desktop/cordis.patch.yml。
完整配置项
| 配置项 | 类型 | 默认值 | 平台 | 详细说明 |
|---|---|---|---|---|
enabled |
boolean |
true |
全部 | 插件总开关 |
notifyOn |
string[] |
["completed"] |
全部 | 允许触发通知的轮次结束原因:completed / aborted / error / interrupted |
suppressWhenFocused |
boolean |
true |
全部 | 当 DSH 窗口处于前台活动状态时抑制通知 |
foregroundProcessNames |
string[] |
["DeepSeek Harness"] |
全部 | 判定为"正在查看应用"的前台进程名 / 应用名列表 |
coalesceMs |
number |
1500 |
全部 | 通知合并时间窗口(毫秒)。窗口内多次完成合并为一条;设为 0 关闭合并 |
minTurnDurationMs |
number |
0 |
全部 | 轮次耗时阈值(毫秒)。短于此时长的轮次不发送通知 |
includeSubagents |
boolean |
false |
全部 | 是否允许子代理(Subagent)会话触发通知 |
bodyMaxChars |
number |
140 |
全部 | 正文最大字符数。耗时尾串预留在此预算空间内 |
showDuration |
boolean |
true |
全部 | 是否在通知末尾附加本轮耗时信息 |
launch |
string |
dsh://open |
全部 | 点击通知弹窗时系统打开的深层链接 URI |
duration |
string |
short |
全部 | 停留时长:short / long(macOS 上 long 映射为时效性通知 Time-Sensitive) |
sound |
boolean |
false |
全部 | 是否播放系统提示音 |
mergedTemplate |
string |
"{count} conversations finished" |
全部 | 多条会话合并通知时的正文模板({count} 会被替换为数量) |
emptyBody |
string |
"Turn finished" |
全部 | 当回答无文字内容时的兜底正文文案 |
verbose |
boolean |
false |
全部 | 是否在控制台打印投递详情与跳过原因日志 |
appId |
string |
DeepSeek.Harness.Notified |
仅 Windows | Windows Toast 归属的应用程序标识符(AUMID) |
registerAumid |
boolean |
true |
仅 Windows | 自动在当前用户注册表注册 AUMID,呈现专属应用名与图标 |
iconPath |
string |
"" |
全部 | 自定义图标绝对路径;留空时自动探测 DSH 官方图标 |
📌 跨平台配置细节:
- macOS 忽略
appId与registerAumid:macOS 系统的通知权限与应用程序的 Bundle Identifier 强绑定。若允许自定义 Bundle ID,用户修改配置会导致已获得的系统通知权限失效,因此 macOS Helper 的 Bundle ID 严格固定为com.deepseek.dsh-notified。- 前台智能识别:macOS 上的
foregroundProcessNames同时匹配应用名称与 Bundle Identifier,且内置将com.deepseek.dsh判定为 DSH 前台。因此默认值["DeepSeek Harness"]在 Windows 和 macOS 上均可直接开箱即用。
常见场景配置
- 仅在执行出错或被中断时唤醒我:
将
notifyOn设置为["error", "interrupted"]。 - 自动连续跑任务时减少打扰:
将
coalesceMs合并窗口调大至5000(5 秒)。 - 佩戴耳机工作,需要提示音提醒:
将
sound设置为true。 - 临时静音插件,仅保留后台运行:
将
enabled设置为false。
手动测试
插件内置了测试工具 dsh_notify_test,模型或开发者可直接调用以验证系统通知链路是否畅通:
# 发送默认测试通知
dsh_notify_test
# 发送自定义标题与正文的测试通知
dsh_notify_test title="构建完成" body="单元测试 147 项全绿"
- 该测试工具不受前台焦点抑制限制:即使你当前正聚焦在 DSH 窗口中,也能即时收到弹窗。
- 注意:测试工具依赖插件注册,首次安装后请确保已重启 DSH。
工作原理与架构设计
触发时机与生命周期
插件监听 Cordis 核心的 session/event 管道,按轮次(Turn)精确追踪会话状态:
| 捕获事件 | 处理逻辑 |
|---|---|
turn/start |
记录轮次序列号与开始时间戳,初始化本轮「输出文本」标记 |
assistant/message |
提取文本块;若包含有效文本则标记本轮存在实质性回复 |
turn/end |
结算判定:提取 reason.kind,当且仅当满足所有过滤条件时触发投递 |
为什么必须等待
assistant/message? 在 Agent 执行过程中,某一轮可能仅发起工具调用(Tool Call)而未输出最终文本,此时任务仍在演进中。只有包含实质性回复的轮次才会被纳入通知候选。
过滤判定链路
在结算阶段,系统按严格顺序执行快速短路过滤。首个未命中的规则将记录于日志(当 verbose: true 时输出):
[enabled 检查] ──► [notifyOn 匹配] ──► [实质文本输出检查] ──► [Subagent 策略] ──► [最低耗时阈值] ──► [触发投递]
enabled:确认总开关处于开启状态;notifyOn:验证结算原因(如completed、error)是否处于放行名单;- 文本产出:确认模型在本轮确实输出了回复文本;
includeSubagents:子代理会话默认静音,除非显式开启;minTurnDurationMs:过滤执行耗时低于设定阈值的瞬间轮次。
底层投递通道
DSH 的 Host 进程以环境变量 ELECTRON_RUN_AS_NODE=1 启动。在纯 Node 运行环境中,无法加载 Electron 原生 Notification 模块(直接 require("electron") 会抛出异常,而调用底层私有绑定会导致宿主崩溃)。因此,插件在双平台均独立构建了原生投递端。
统一调度模块通过 resolveChannel() 完成通道判定,上层共用同一套状态折叠、文案排版与防抖逻辑,仅在最后一跳(showToast / showMacToast)分流,返回相同结构的结果对象({ ok, suppressed, code, detail, reason })。
┌────────────────────────────────┐
│ session/event │
└───────────────┬────────────────┘
▼
┌────────────────────────────────┐
│ policy.js (状态追踪与过滤决策) │
└───────────────┬────────────────┘
▼
┌────────────────────────────────┐
│ text.js (Markdown 清洗与预算) │
└───────────────┬────────────────┘
▼
┌────────────────────────────────┐
│ resolveChannel() 跨平台通道派发 │
└───────┬────────────────┬───────┘
│ │
(Windows) ▼ ▼ (macOS)
┌──────────────────────┐ ┌──────────────────────┐
│ lib/toast.js │ │ lib/darwin.js │
│ PowerShell 5.1 │ │ DSHNotify.app │
│ WinRT Toast API │ │ UserNotifications │
└──────────────────────┘ └──────────────────────┘
Windows 通道
- 调用链条:拉起 Windows PowerShell 5.1 子进程调用底层 WinRT Toast 通知接口。
- 环境要求:必须调用系统内置的 Windows PowerShell 5.1(固定使用
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe),不可使用 PowerShell Core / 7(后者的 WinRT 类型系统投影不完整)。 - 进程生命周期(非 Detached):经实测,若使用
detached: true,Windows PowerShell 将因缺少控制台而在后台静默放弃 WinRT 脚本执行(虽然进程返回退出码 0,但 Toast 绝不会弹出)。因此,插件采用非 Detach 模式启动子进程,并通过超时间隔进行生命周期兜底。
macOS 通道
插件内置独立的 Swift Helper 源码(macos/main.swift,约 300 行)。首次使用时即时编译为系统标准 Bundle:
~/Library/Application Support/dsh-notified/DSHNotify.app
├── Contents/Info.plist # 配置 LSUIElement=true,不占 Dock 栏
├── Contents/MacOS/dsh-notified # swiftc -O -target arm64-apple-macosx13.0
└── Contents/Resources/icon.icns # 从 DSH 原生应用包提取复制
Helper 依托 Apple UserNotifications 框架,支持系统通知中心、历史归档与点击回调(回调直接通过 NSWorkspace.open(dsh://open) 聚焦窗口)。
macOS 实测踩坑关键约束:
- 必须通过 LaunchServices 启动:直接从 Node 进程
spawn可执行文件会导致通知守护进程(usernoted)无法验证客户端 Identity,权限申请直接返回失败且永不弹出授权提示。改用open -a <bundle>启动后,系统方能建立合法上下文,授权弹窗正常展示。 - Bundle 物理路径约束:Bundle 必须位于持久目录(如
~/Library/Application Support/)。若置于/tmp临时目录下,系统沙盒会拒绝扩展权限(报错sandbox_extension_issue_file_to_process failed: Operation not permitted)。 - 基于 Unix Domain Socket 通信:
open启动无法继承父进程的 stdin/stdout。Helper 采用常驻并在~/Library/Application Support/dsh-notified/helper.sock监听 Unix Socket 的机制传递指令(Unix Socket 路径长度上限约 104 字节,路径过长时自动平滑回退至$TMPDIR下的短路径,仅 Socket 转移,Bundle 始终保留在原目录)。 - 编译目标强制锁定:
swiftc默认以本地最高 SDK 版本(如 macOS 28.0)为目标,会导致低版本系统(如 macOS 27.x)报kLSIncompatibleSystemVersionErr (-10825)拒绝启动。因此显式锁定参数-target arm64-apple-macosx13.0。 - 无焦点启动:必须使用
open -g参数后台启动 Helper。否则启动瞬间 Helper 会抢占全局焦点,破坏前台判断逻辑。 - 主动查询授权状态:在用户未授权时,
UNUserNotificationCenter.add仍会返回err=nil。因此 Helper 内部通过getNotificationSettings()读取真实的authorizationStatus,被拒时返回reason: "denied"与退出码 5。 - 授权请求排队挂起:首次请求权限期间不丢弃当前通知,待用户点击系统授权后继续派发。
- 图标内嵌机制:UserNotifications 框架不允许在运行时动态覆盖 Bundle 图标,必须在 Bundle 创建时将 DSH 的
icon.icns拷入其 Resources 目录。
品牌化与图标呈现
- Windows:
在注册表
HKCU\SOFTWARE\Classes\AppUserModelId\DeepSeek.Harness.Notified中写入DisplayName("DeepSeek Harness")与IconUri(应用图标路径)。此操作运行于当前用户上下文,免管理员权限、支持幂等重写。若未配置该项,通知将显示为默认的 "Windows PowerShell"。 - macOS:
应用名与署名直接由 Bundle 的
CFBundleName/CFBundleDisplayName决定,系统通知中心显示为 "DeepSeek Harness",图标展示原生高清应用图标。
❓ 为什么 macOS 不采用
osascript -e 'display notification'?osascript弹出的通知署名固定为 "Script Editor"(脚本编辑器),且无法捕获点击回调唤醒主程序。只有具备专属 Bundle Identifier 的独立应用 Bundle 才能被系统识别为正规来源。
数据安全与防注入
所有动态文本内容均严格杜绝拼入命令行或执行上下文:
- Windows:通知标题与正文经 Base64 编码的 JSON 载荷 传递给 PowerShell 进程,由脚本在内存中解码;XML 实体字符进行严格转义,杜绝畸形 XML 破坏 Toast 结构。
- macOS:投递请求封装为标准 JSON 对象,经由本地 Unix Socket 发送至 Helper;Helper 使用 Swift 内置的
JSONDecoder解析,直接赋值给UNMutableNotificationContent对象。全程无任何 Shell 解释器介入。 - 支持 Emoji、换行符、HTML/XML 标签、多语言特殊符号安全传递。
完全异步与零阻塞
- 投递任务在子进程中拉起,插件事件监听器不会
await投递结果; - macOS Helper 的编译安装亦在后台静默预热;
- 无论网络状况、系统通知服务响应速度如何,通知逻辑均绝不阻塞对话主流程,亦不增加任何轮次延迟。
边界情况与容错机制
| 异常 / 边界场景 | 插件处理机制 |
|---|---|
| 非 Windows / 非 macOS 环境 | 插件自动静默空转,记录一条 Info 日志,不报错、不崩溃 |
| Windows:PowerShell 运行策略受限 | 自动降级为使用 PowerShell 默认标识重试;若依然失败则记录 warn,不影响对话 |
| Windows:系统处于专注助手 / 免打扰模式 | 遵从 Windows 系统级屏蔽策略,记录 warn 日志 |
| macOS:未安装 Xcode 命令行工具 | 输出友好 warn 日志提示安装指南(xcode-select --install),对话正常继续 |
| macOS:用户拒绝通知授权 | 记录 warn 日志并提示前往「系统设置 › 通知」手动放行;不会反复弹窗骚扰 |
| macOS:Helper 编译失败 / Socket 超时 | 超时 15 秒后优雅降级标记为投递失败,防止链路无限挂起 |
| macOS:DSH 安装于非标准路径 | 动态逆向推导当前运行进程路径定位 icon.icns;找不到时回退通用图标 |
| 会话尚未生成标题 | 自动平滑回退:会话标题 → 工作区目录名 → "DeepSeek Harness" |
turn/end 结算但无任何输出文本 |
判定为中间态轮次,自动静默跳过 |
| 同一轮次重复收到事件通知 | 基于轮次序号进行严格去重 |
| 插件被重复加载或热重载 | 标识注册完全幂等,所有监听器挂载于 Cordis 上下文生命周期,热重载时无内存泄漏 |
| 正文超出最大限制 | 耗时预留预算优先保护,截断仅作用于正文部分,末尾补齐省略号 … |
卸载指南
- 在 DeepSeek Harness 侧边栏的 插件 页面中卸载
dsh-notified; - 清理系统级环境残留:
Windows 环境(清理当前用户的 AUMID 注册表项):
Remove-Item 'HKCU:\SOFTWARE\Classes\AppUserModelId\DeepSeek.Harness.Notified' -Force
macOS 环境(移除编译生成的 Helper Bundle 及 Socket 文件):
rm -rf ~/Library/Application\ Support/dsh-notified
卸载后,若在 macOS「系统设置 › 通知」列表中发现已停用的条目,可根据需要手动移除。
开发与测试
环境准备与命令
本仓库使用 Node.js 原生测试运行器(Node Test Runner),包管理器推荐使用 pnpm:
# 安装开发依赖
pnpm install
# 运行全套单元测试(覆盖 toast、darwin、text、policy 与 plugin 五大模块)
pnpm test
代码结构与分工
| 源码路径 | 核心职责 |
|---|---|
lib/index.js |
Cordis 插件生命周期入口:通道派发、事件监听编排、测试工具注册与配置模式定义 |
lib/policy.js |
纯函数层:轮次状态聚合、判定是否满足通知策略、子代理会话识别 |
lib/text.js |
纯函数层:标题多级回退、Markdown 标签剔除、耗时预算预留与 UTF-8 安全字符截断 |
lib/toast.js |
Windows 投递层:AUMID 注册、PowerShell 脚本封装、子进程调用与退出码映射 |
lib/darwin.js |
macOS 投递层:Helper Bundle 构建与缓存、LaunchServices 启动、Socket 通信 |
macos/main.swift |
macOS 原生代码:基于 Swift / UserNotifications 构建的守护程序,负责原生弹窗与点击聚焦 |
cordis.patch.yml |
插件 Patch 模板:注入插件定义与默认设置声明 |
架构设计原则
- 纯函数优先,隔离 I/O:
lib/policy.js与lib/text.js均为无副作用的纯函数模块,过滤逻辑与排版截断算法可脱离特定环境实现 100% 确定性单测。 - 依赖注入便于测试:
两个平台投递模块对系统底层的依赖(如
spawn、reg.exe、swiftc、open、net.connect、文件系统与环境变量)均支持通过参数注入;apply()支持注入overrides.showToast,使核心逻辑在无 GUI、无真实通知的 CI/本地单测环境下也能全量覆盖验证。 - 跨平台统一契约:
toast.js与darwin.js对外导出相同结构的结果对象以及统一的退出状态码规范(0成功 /3焦点抑制 /4通道不可用 /5权限被拒),index.js无需编写平台特化分支。
依赖与编译策略
- 自包含 Schema 依赖:
将
@deepseek-ai/schemastery作为插件自身的直接依赖(dependencies),保证在 Profile 采用link:(符号链接)方式安装时,ESM 寻址机制能正确解析依赖,不受 Host 外部目录隔离影响。 - 动态 Native 编译:
macOS Helper 采用系统自带的
/usr/bin/swiftc在首次运行时本机编译,避免了预先分发二进制文件造成的架构适配(Intel / Apple Silicon)与签名难题。
环境验证矩阵
本插件已在以下典型环境全量实测通过:
- 宿主环境:DeepSeek Harness Desktop
0.1.7-rc.2(运行时版本0.2.0-rc.2) - 引擎版本:Electron 44 / Node 24
- Windows 测试平台:Windows 11 26H2(版本号
10.0.26300) - macOS 测试平台:macOS 27.0.1 / Apple Silicon (arm64)
Windows 实测结果确认:
- 原生 WinRT Toast 正常投递,准确写入系统通知中心历史;
- AUMID 品牌化配置生效,通知面板显示专属名称与应用图标;
- 点击弹窗经由
dsh://open正确唤醒并聚焦 DSH 主界面; - 前台焦点检测准确,工作于前台时返回退出码 3 并抑制弹窗。
macOS 实测结果确认:
- Helper 在 1.5 秒内完成本地编译、安装并在 LaunchServices 登记注册;
- 投递正常返回
{"ok":true,"code":0},系统usernoted日志确认 Banner 展示; - 品牌化与图标映射正常,Bundle ID 绑定与通知权限持久有效;
- 前台为其他应用时正常响应,前台为 DSH 时准确抑制,无需复杂 TCC 额外授权;
- 特殊符号与复杂字符集无畸形解析,中文与代码片段展示完好。
开源协议
本项目基于 MIT License 开源。