跳到正文
dsh-market 浏览插件 GitHub EN

pitetow/dsh-notify-on-complete

系统桌面通知:运行结束、模型提问与审批请求即时提醒,三平台提示音,零运行时依赖。

Star 数 ★ 4 分类 通知与集成 收录于 2026-08-15

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add github:pitetow/dsh-notify-on-complete

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

README

English · 中文文档

dsh-notify-on-complete

DeepSeek Harness 插件:每次 dsh 运行结束时向操作系统发送桌面通知,提示用户工作已完成;会话进行中模型提问或等待审批时也会即时通知提醒你回来处理。正文按结果区分(成功 / 失败 / 中止 / 达到 token 上限)。

作者:Luozy · 协议:MIT

  • 零运行时依赖:不依赖 dsh 内部包,也不依赖 ctx.shell 服务,通知用 child_process.spawn 以 detached 子进程发出,不阻塞、也不被 harness 退出流程影响
  • 跨平台:按 process.platform 自动选择通知命令(macOS osascript / Linux notify-sendkdialog / Windows PowerShell)。不支持的平台加载时跳过并打警告,不会在每个事件里抛错。
  • 通知带系统提示音:macOS 用系统默认提示音(sound name "Glass")、Windows 用 .NET SystemSounds、Linux 用 canberra-gtk-play(缺失时回退 paplay);可用 sound: false 关闭。
  • 会话中阻塞即时通知:模型调用 ask_user_question 提问、或沙箱提权/工具权限等待审批时立即弹通知提醒你回来(正文含问题文本 / 工具名与原因),可用 onBlocked / onQuestion / onApproval 精细控制。
  • 只通知顶层运行:子代理(subagent)会话被过滤(header.origin === 'subagent'),一次 CLI 运行只弹一条通知。

工作原理

插件监听两个事件,协同判定"一次运行结束":

  1. session/eventturn/end:记录根会话(origin !== 'subagent')最近一次轮次结束的 reason.kind。一次运行可能跨多个轮次(goal 多轮、follow-up、steering),每一轮都有自己的 turn/end,插件只记住最后一次的结果。
  2. agent/status'idle':这是 harness 自己定义的"运行结束"信号(web 界面的 running 指示器、agent.whenIdle() 都基于它)。根 agent 回到 idle 表示整段活动(含所有轮次)收敛完成,此时把记下的最终结果发出去,并清除记录。

所以每条通知对应一次完整的运行,而不是每一轮:多轮 goal run 只在整场跑完时弹一条,且正文是最终结果;中途的"任务已完成"不会提前弹出。通知正文格式:结果文本 — 会话标题 (session: 会话ID),例如 任务已完成 — 修复登录bug (session: 3f9a…);会话标题还没生成时退化为 结果文本 (session: 会话ID)。标题来自会话日志里最后一条 session/title 事件,是异步投影——极早期通知(如会话刚开始就提问)可能还没有标题,属预期。通知命令以 detached: true + unref() 发出,harness 正常退出或崩溃都不会影响通知送达。

reason.kind 通知正文
completed 任务已完成
error 任务失败
aborted 任务已中止
max-tokens 任务达到 token 上限
其他(未知) 任务结束

环境要求

  • Node.js ^22(与 DeepSeek Harness 一致)
  • 已安装的 dsh CLI(任意版本,插件通过 Cordis 事件注册,不依赖 CLI 特定版本)
  • peer 依赖 @deepseek-ai/cordis@^4.0.1(由 dsh CLI 自身提供,安装时 pnpm 会自动解析)

安装(一键脚本,GitHub 源码分发,无需 npm)

前置:已装好 DSH(dsh web 能正常运行),Node.js ^22 + pnpm。

macOS / Linux / Windows(Git Bash 或 WSL)

curl -fsSL https://raw.githubusercontent.com/pitetow/dsh-notify-on-complete/main/scripts/install.sh | bash

其他 profile(默认 web):

curl -fsSL https://raw.githubusercontent.com/pitetow/dsh-notify-on-complete/main/scripts/install.sh | bash -s -- --profile headless

脚本自动完成 4 件事(全部幂等,可安全重复执行):

  1. 下载源码到 ~/.dsh/plugins/dsh-notify-on-complete/(已存在则跳过,不会覆盖;加 --force 才覆盖更新,覆盖前会询问确认,--yes 跳过确认);
  2. pnpm install && pnpm build 构建产物;
  3. dsh plugin --profile <名> add link:<目录>:CLI 识别包内 dsh.bundle.patch 声明(cordis.patch.yml),自动注册进 profile 的 bundle 栈,下次启动自动挂载——不需要手动编辑任何配置文件;
  4. 幂等移除旧版残留的手动挂载行,避免双挂载(一次运行弹两条通知)。

curl | bash 会执行远程代码——脚本随仓库开源(scripts/install.sh),可先下载审阅。

验证

dsh --profile web --dump-config | grep -n notify-on-complete

能输出 - id: notify-on-complete 及其后的 name: dsh-notify-on-complete 行,说明插件已进入合成树。再跑一次真实任务,看到桌面通知弹出即安装成功。

重启生效:

  • CLI 一次性运行:下次运行 dsh --profile headless "任务" 时自然生效,无需额外操作。
  • Web GUI:重启 web 进程(结束当前 dsh web 进程后重新启动)。若部署启用了 HMR 热更新,保存文件后会自动生效。

更新

curl -fsSL https://raw.githubusercontent.com/pitetow/dsh-notify-on-complete/main/scripts/install.sh | bash -s -- --force

--force 会删除旧源码重新下载(该目录内的本地改动会丢失),覆盖前会询问确认;无人值守场景加 --yes 跳过确认: bash -s -- --force --yes

或手动:cd ~/.dsh/plugins/dsh-notify-on-complete && git pull && pnpm install && pnpm run build 后重跑 dsh plugin --profile web add link:.

卸载

dsh plugin --profile web remove dsh-notify-on-complete
rm -rf ~/.dsh/plugins/dsh-notify-on-complete

然后重启 dsh 进程。

把依赖指向本地源码(link: 是符号链接,改代码后重建即生效,适合调试):

cd /path/to/dsh-notify-on-complete
pnpm install
pnpm run build                              # 产物输出到 lib/
dsh plugin --profile web add link:/path/to/dsh-notify-on-complete

装完后检查 ~/.dsh/profiles/web/package.json,dependencies 里应出现 dsh-notify-on-complete

grep dsh-notify ~/.dsh/profiles/web/package.json

若 CLI 提示 declares no dsh.bundle — installed as a plain dependency,说明它没有自动挂载,需要在 profile 用户层手动声明。编辑 ~/.dsh/profiles/web/cordis.patch.yml,加入:

# 你的 profile 用户层(cordis.patch.yml)
- id: notify-on-complete
  name: dsh-notify-on-complete
  config:
    enabled: true              # 默认 true,不写也行
    title: DeepSeek Harness    # 通知标题,不写也行

若之前用一键脚本装过,再手动挂载会造成双挂载(一次运行弹两条通知)——切换通道前先 dsh plugin --profile web remove dsh-notify-on-complete


设置面板(Web GUI)

打开 dsh web → 设置 → 插件 → 配置,展开 运行完成通知 卡片即可可视化配置,无需手动编辑 cordis.patch.yml

  • enabled / title / sound / onBlocked / onQuestion / onApproval —— 与配置文件相同的开关。
  • sounds —— 每档事件音色(macOS 音色名如 Glass / Sosumi / Ping / Funk,或 default):完成、失败、提问/审批三档可分别更换。macOS 上 default 表示不响铃(可当作单档静音);Windows 与 Linux 会把 default 映射为各自的平台默认提示音。
  • quietHours —— 勿扰时段 "HH:MM-HH:MM"(开始晚于结束表示跨天);时段内完全不弹通知也不响铃。示例:22:00-08:00, 12:00-13:00(多个用逗号分隔)。

面板值优先于 profile 的 cordis.patch.yml;没动过的字段回退到配置文件,再到默认值。无设置服务的场景(如 CLI 一次性运行)按配置文件工作,行为不变。

设置卡片由插件的浏览器端(client half,lib/client.js)渲染,数据经插件自带的 JSON 路由 GET/POST /notify-on-complete/api/config 读写(harness 的设置 API 只对白名单命名空间开放,第三方插件需自带路由)。仅在 web profile 中生效。升级后需重启 dsh web 进程(见上文"重启生效")。

配置

配置写在 profile 的 cordis.patch.yml 里(用户层,最后应用、按行胜出):

profile 配置文件路径
web(默认,dsh web ~/.dsh/profiles/web/cordis.patch.yml
headlessdsh --profile headless ~/.dsh/profiles/headless/cordis.patch.yml
其它 <名> ~/.dsh/profiles/<名>/cordis.patch.yml

也可写在 home 级 $DSH_HOME/cordis.patch.yml(默认 ~/.dsh/cordis.patch.yml),所有 profile 共享。

配置方式是id: notify-on-complete 声明/覆盖这一行。注意:

  • 后应用的层会整体替换同名 id 行的 config(不是按键深度合并),所以要么写全 id + name + config,要么只写你想改的键、其余交给插件默认值。
  • cordis.patch.yml 必须是顶层 YAML 数组(以 - 开头);全部删光后请写 []

完整配置示例(所有字段 + 默认值):

# ~/.dsh/profiles/web/cordis.patch.yml
- id: notify-on-complete
  name: dsh-notify-on-complete
  config:
    enabled: true        # 总开关;false 完全关闭
    title: DeepSeek Harness
    sound: true          # 提示音;false 只弹通知不出声
    onBlocked: true      # 阻塞通知总开关(提问 + 审批)
    onQuestion: true     # 提问类通知(仅 onBlocked: true 时生效)
    onApproval: true     # 审批/权限类通知(仅 onBlocked: true 时生效)

常用场景:

# 仅提示、不要提示音
- id: notify-on-complete
  name: dsh-notify-on-complete
  config:
    sound: false

# 仅任务完成后提示,提问 / 审批等阻塞不提示
- id: notify-on-complete
  name: dsh-notify-on-complete
  config:
    onBlocked: false

# 完成后 + 提问都提示,但审批(沙箱提权 / 工具权限)不提示
- id: notify-on-complete
  name: dsh-notify-on-complete
  config:
    onApproval: false

字段表

字段 类型 默认值 说明
enabled boolean true 设为 false 时插件不注册任何监听,完全关闭
title string DeepSeek Harness 系统通知的标题
sound boolean true 通知时同时播放系统提示音;设为 false 只弹通知不出声
onBlocked boolean true 阻塞通知总开关;设为 false 完全关闭提问+审批通知
onQuestion boolean true 提问类(ask_user_question)通知开关;仅在 onBlocked: true 时生效
onApproval boolean true 审批/权限类通知开关;仅在 onBlocked: true 时生效
sounds object {completed: "Glass", error: "Sosumi", approval: "Ping"} 每档事件的音色(macOS 音色名或 default
quietHours string[] [] 勿扰时段 "HH:MM-HH:MM"(开始晚于结束表示跨天);时段内完全不通知

配置校验在加载时执行(fail loud):类型错误会在启动时报错,不会静默忽略。

改完配置需重启生效:CLI 一次性运行下次自然生效;dsh web 需重启 web 进程。

验证配置是否生效:

dsh --profile web --dump-config | grep -n -A 10 notify-on-complete

输出里能看到你写的 config: 值即已生效。

平台命令

平台 命令 备注
macOS osascript -e 'display notification …' 原生通知中心通知,带系统提示音(sound name "Glass"
Linux notify-send 缺失时自动回退 kdialog --passivepopup;提示音走 canberra-gtk-play(缺失时回退 paplay
Windows PowerShell WScript.Shell.Popup 无需额外模块,5 秒自动关闭,带 .NET SystemSounds 提示音

macOS 首次使用可能需要给终端应用授予"通知"权限(系统设置 → 通知)。

常见问题

Q:一次运行弹两条通知? 双挂载:profile 的 cordis.patch.yml 里还留着旧的手动挂载行。删掉那段 - id: notify-on-complete 条目(一键脚本会自动清理),只保留 bundle 自动挂载即可。注意 cordis.patch.yml 必须是顶层 YAML 数组——全部删光后请写 []

Q:装了但通知不弹?

  1. 先确认加载成功:dsh --profile web --dump-config | grep notify-on-complete
  2. 确认跑的是根会话任务(CLI 一次性运行一定满足;子代理/后台子任务不触发)。
  3. macOS 检查通知权限;Linux 确认有 notify-sendkdialog;Windows 确认 PowerShell 可用。
  4. 通知是 fire-and-forget 的,失败不会报错——可以在终端手动执行对应平台的命令验证系统侧可用。

Q:为什么只在根会话触发,子代理不通知? CLI 一次运行可能包含多个子代理会话,每个都有自己的 turn/endagent/status。插件用 session.header.origin === 'subagent' 过滤子代理(harness 自己的惯用口径),保证只对顶层运行通知。

Q:一次运行会弹几条通知? 一条。通知在根 agent 回到 idle(整段活动收敛、所有轮次结束)时才发出,多轮 goal run 也不会刷屏;中途轮次结束不会提前弹"任务已完成"。

Q:Web GUI 里任务跑完会通知吗? 会。Web GUI 中每次任务(一次运行)结束对应根 agent 的 idle 状态,与 CLI 行为一致;多轮 goal run 整场跑完才弹一条。

Q:dsh plugin add 报 peer 依赖错误? 插件 peer 依赖 @deepseek-ai/cordis@^4.0.1,需要能从 npm 解析。若你的网络环境访问不了 npm registry,改用 --offline 或在 profile 里预先安装 cordis。

Q:headless / approval 策略为 never 时也会弹「需要批准」吗? 可能。approval/asked 在策略为 never 或没有回答者(headless/CI)时同样会落日志,此时实际是立即拒绝而非真正等用户——纯插件无法从 session 事件分辨这一层。Web GUI 回答者恒在、策略默认 ask,信号可靠;headless 场景可用 onApproval: falseonBlocked: false 关闭。

开发

pnpm install
pnpm run test        # vitest 单元测试(结果映射 / 平台命令 / 运行结束状态机 / 插件入口)
pnpm run typecheck   # tsc --noEmit
pnpm run build       # tsc 产物到 lib/(prepare 钩子在 install 时自动执行)

源码结构:

src/index.ts     插件入口:name / Config 校验 / 平台门禁 / 事件接线
src/notifier.ts  运行结束状态机:记录最终 turn/end 结果,agent idle 时发一次
src/notify.ts    结果映射、平台命令构建、detached spawn(含 Linux 回退)
src/types.ts     结构事件类型(零依赖,不依赖 dsh 内部包)
cordis.patch.yml bundle 自动挂载声明(dsh.bundle.patch)
scripts/install.sh  一键安装脚本(GitHub 源码分发)
tests/           vitest 单元测试(结果映射 / 平台命令 / 状态机 / 插件入口)

内容来自项目 README(GitHub)↗