DeepSeek Harness 每跑完一轮(模型停手、等你说下一句话)就弹一条系统通知:哪个项目、第几轮、用了多久、最后一句说了什么。点一下通知能把 DSH 窗口拉回前台。
切去干别的事、或者让它跑十几分钟的时候,不用一直盯着屏幕。
为什么需要它
DSH 桌面版自带的通知只做应用级提示:有新版本可升级、更新需要重启。它没有「这一轮任务跑完了」的提醒——而那恰好是最需要被提醒的一格。
自己盯着的代价很直接:
| 场景 | 没有它 |
|---|---|
| 让模型跑一个长任务,切去写文档 | 每一两分钟切回来瞄一眼,全在看进度条 |
| 同时开几个会话 | 忘了哪个跑完了,回来发现早就停在那儿 |
| 出错 / 被中断 | 以为还在跑,其实十分钟前就停了 |
| 一轮只花三秒 | 弹一条通知反而打断你 |
这四条对应插件里的四组开关:按结束原因分别开关、只在窗口不在前台时提醒、最短耗时门槛、子任务默认静默。
通知长什么样
三行模板:标题行带会话标题,正文第一行是「模型 · 轮数 · 用时」,第二行起是返回内容。
[任务处理完成通知] 已完成
deepseek-v4.1-flash-1017 · 第 2 轮 · 用时 5.3 秒
通知模板已经照你给的三行改好了:第一行 [会话标题] 已完成,……
- 会话标题取自 DSH 的
session/title事件(侧栏里显示的那个名字);还没有标题时退回项目目录名。 - 模型取自每轮请求的
request/header里的config.model;拿不到就不占位,第二行只留「第 N 轮 · 用时」。 - 返回内容是这一轮助手最后一条文本,压成一行后按
snippetChars(本插件默认 90 字)截断;出错时换成错误原因。 - 状态词跟着结束原因走:
已完成/出错/中断。
一条通知是怎么走出来的
flowchart LR
A["一轮结束:模型停手"] --> B{"结束原因开着吗"}
B -- 关闭 --> Z["不打扰"]
B -- 开着 --> C{"短于 minSeconds"}
C -- 是 --> Z
C -- 否 --> D{"窗口在前台"}
D -- 是 --> Z
D -- 否 --> E{"preferClient 且有页面连着"}
E -- 是 --> F["页面通道:渲染进程弹系统通知"]
E -- 否 --> G["本机原生命令:powershell / osascript / notify-send"]
F --> H["点一下 → DSH 回到前台"]
G --> H
两条通道:页面通道 + 本机原生命令
桌面版的宿主是一个 ELECTRON_RUN_AS_NODE 起的独立 Node 进程,不是 Electron 主进程:require("electron") 拿不到 Notification,拉 powershell 这类子进程还可能被进程策略拦掉(典型症状:接口返回「已发送」,屏幕上什么都没有,也没有任何报错)。
所以真正稳的一条路是页面通道:
- 页面(渲染进程)加载时用
EventSource挂到GET /api/dsh-notice-v1/events; - 宿主判完「该提示了」就把标题/正文推给连着的页面;
- 页面用 Web
Notification弹——在桌面版里落到的就是这台机器的系统通知。
preferClient(默认 true)开着时,只要有一个页面连着就走这条路,设置页会显示「已连接的页面:N」;关掉它、或者窗口根本没开(纯 dsh web / 终端里跑),才回退到本机原生命令。
「发送诊断」卡片会把宿主的真实身份(Node 还是 Electron、PowerShell 找没找到)、最近一次子进程的错误和脚本自己写的日志都显示出来,还带一个「诊断一次」按钮,点一下就知道问题卡在「没拉起子进程」还是「拉起来了但系统没弹」。
发送后端
三个平台共用一套 Electron 主通道,各自再带一个原生回退;auto 按平台自动拼链路。
| 后端 | 平台 | 场景 / 表现 |
|---|---|---|
client(首选) |
任意 | 页面开着时由渲染进程弹系统通知,最稳;不依赖子进程 |
electron |
Windows / macOS / Linux | 宿主是 Electron 主进程时直接用 Notification:系统通知署名 DeepSeek Harness,点一下回到窗口 |
powershell |
Windows | 纯 Node 宿主(dsh web)时先发 WinRT toast(DSH 自己的 AppUserModelID),失败再退托盘气泡;标题/正文走 base64 传 lib/notify.ps1,避开命令行中文编码问题 |
osascript |
macOS | display notification,标题和正文走 argv 传参,不拼进脚本文本,中文和引号都安全 |
notify-send |
Linux | libnotify 的系统通知,--app-name=DeepSeek Harness;没装就一路退到 none |
none |
任意 | 调试用:不发,只记失败 |
backend 默认 auto,链路按平台取:
- Windows:
electron → powershell - macOS:
electron → osascript - Linux:
electron → notify-send - 其它平台:只有
electron
onlyWhenUnfocused 依赖 Electron 的窗口 isFocused():桌面版判断准确;走页面通道时由页面用 document.hasFocus() 判断;两者都判断不出时按「不在前台」处理(照常提示)。
配置
两层:cordis.patch.yml 里的 config 是默认值,<DSH_HOME>/dsh-notice.json(设置页保存的)覆盖它。
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
onCompleted |
true |
正常完成时提示 |
onError |
true |
出错时提示 |
onAborted |
false |
被中断时提示 |
includeSubagents |
false |
子任务也提示 |
minSeconds |
0 |
短于这么多秒的一轮不提示 |
onlyWhenUnfocused |
true |
只在窗口不在前台时提示 |
silent |
false |
静音 |
snippetChars |
90 |
正文里带多少字的回复摘要,0 = 不带(lib/index.js 里的兜底默认是 200,patch 层设成 90) |
backend |
auto |
auto / electron / powershell / osascript / notify-send / none |
preferClient |
true |
页面开着时交给页面弹系统通知(桌面版最稳的一条路) |
改设置页任意一项都会立刻落盘到 dsh-notice.json,不用重启。
本机接口
GET /api/dsh-notice-v1/state—— 配置、平台、各后端就绪状态、宿主身份、最近一次发送、计数GET /api/dsh-notice-v1/events—— SSE 通道:页面挂着收通知(data: {"title","body",…})POST /api/dsh-notice-v1/config—— 改配置(部分字段),落盘并返回新状态POST /api/dsh-notice-v1/test—— 立刻发一条测试通知(有页面走页面通道)POST /api/dsh-notice-v1/probe—— 只走本机原生命令,等 2.6 秒把子进程结果一并返回(诊断用)
接口只接受本机回环地址请求。
安装
插件目录放进 <DSH_HOME>/plugins/dsh-notice,profile 的 package.json 加 "dsh-notice": "link:<绝对路径>" 与 dsh.profile.bundles 条目,再用运行时自带的 node + pnpm 在 profile 目录 install,重启 DSH 生效。
Linux 上想用原生回退,装一下 libnotify:Debian/Ubuntu apt install libnotify-bin,Fedora dnf install libnotify,Arch pacman -S libnotify。macOS 自带 osascript,无需安装。
已知边界
- 只覆盖「一轮结束」:权限确认、工具报错这类中途事件不会单独通知。
- 页面通道要窗口开着:窗口全关时只能靠原生命令,Linux 上没装 libnotify 就退到
none(设置页会显示「未就绪」)。 - 通知内容取最后一轮文本:长回答只截
snippetChars个字,摘要不换行。 - 接口只服务本机:只接受回环地址请求,不对外暴露。
- 发送失败不重试、不打断任务:只记一笔失败原因,供「发送诊断」查看。
自检
node --check lib/index.js
node --check lib/client.js
node test.mjs
No comments yet. Be the first to write one.