← Back to home@WsttXm

DshNotice

给 DeepSeek Harness 桌面版加上 macOS 系统通知:需要你授权、提问、计划待审或一轮任务结束时弹横幅 | Native macOS notifications for DeepSeek Harness

Stars
0
Language
JavaScript
Created
Oct 10, 2026
Updated
Oct 10, 2026

Introduction

DshNotice

让 DeepSeek Harness 桌面版在需要你介入时,用 macOS 系统通知把你叫回来。

English · 简体中文

License: MIT CI Platform Dependencies

把 Harness 丢在后台跑长任务,然后去干别的事——需要你点确认、需要你回答问题、或者这一轮跑完的时候, 横幅会自己找到你。点一下横幅,Harness 窗口就回到前台。

┌────────────────────────────────────────────────────┐
│  DeepSeek Harness                                  │
│  需要你的授权                                      │
│  「重构登录流程」想要执行 bash,请回到应用确认。   │
└────────────────────────────────────────────────────┘

特性

  • 三种时刻会提醒:请求授权、向你提问、提交计划待审;外加一轮任务完成。
  • 通知的署名就是 DeepSeek Harness:走应用自己的通知身份,带应用图标、进通知中心、 遵守「系统设置 → 通知 → DeepSeek Harness」里的开关——不是伪装成脚本编辑器的野通知。
  • 绝不碰你的审批和回答:插件只旁读会话状态,不挂任何请求回调,不存在"通知插件把审批吞了" 这种可能。
  • 不打扰:子代理的内部工作不提醒;你已经盯着那个会话看的时候也不提醒。
  • 中英文自动跟随应用语言,也可以手动锁定。
  • 零依赖、零构建:没有 npm install,没有打包步骤,一个文件就是全部逻辑。
  • 安装可逆:写配置前自动备份,卸载后不留残余。

快速开始

环境要求

项目要求
系统macOS(已在 arm64 上验证)
应用DeepSeek Harness 桌面版,且至少启动过一次(安装脚本要往 ~/.dsh/profiles/desktop 里写东西,这个目录由应用首次启动时创建)
运行时Node.js ≥ 20(安装脚本和测试都只用 Node 内置模块)

三步装好

git clone https://github.com/WsttXm/DshNotice.git
cd DshNotice
node plugin.mjs install        # 等价于 npm run plugin:install

然后回到 Harness 窗口按一次 ⌘R 刷新。

看到「通知已开启」横幅就成功了。 这条欢迎横幅只在首次安装时弹一次,它的意义就是让你在 系统层面确认整条链路是通的——包括 macOS 有没有真的把横幅画出来。

装完不会立刻生效的那几秒是正常的:应用在监视 cordis.patch.yml,文件稳定后会自己重新 组合插件树。如果刷新后仍然没有横幅,重启一次应用即可。

安装脚本是幂等的,重复跑不会写坏任何东西;它也不会碰你 profile 里原有的任何配置, 只追加一段带标记的、自己能认出来的块。

确认装好了

node plugin.mjs status
profile        : /Users/you/.dsh/profiles/desktop
patch file     : /Users/you/.dsh/profiles/desktop/cordis.patch.yml
row installed  : yes
package linked : yes
resolves from the profile: /path/to/DshNotice/packages/dsh-notice/package.json

两行都是 yes 就说明安装到位。

也可以在 Harness 窗口里按 ⌥⌘I 打开开发者工具,在控制台里主动试一发:

window.__DSH_NOTICE__.test()              // 立刻弹一条样例横幅
window.__DSH_NOTICE__.test("complete")    // 弹"任务已完成"那条
window.__DSH_NOTICE__.state()             // 权限、挂载状态、最近发出的通知

升级

cd DshNotice
git pull

改动只在 lib/client.js 里,拉完重启一次 Harness 应用就生效了,不需要重装。

卸载

node plugin.mjs uninstall      # 移除配置行与软链,保留备份

然后刷新窗口或重启应用,让 client 模块从页面里卸载掉。


配置

所有偏好都存在渲染进程的 localStorage 里(键名 dsh-notice:prefs),改完立即生效,不需要刷新。

怎么改

在 Harness 窗口按 ⌥⌘I 打开控制台:

window.__DSH_NOTICE__.set({ sound: false })               // 静音,只留横幅
window.__DSH_NOTICE__.set({ onComplete: false })          // 只在需要我介入时提醒,跑完别打扰
window.__DSH_NOTICE__.set({ onlyWhenUnfocused: false })   // 前台也提醒(默认不提醒)
window.__DSH_NOTICE__.set({ language: "zh" })             // 锁定中文文案
window.__DSH_NOTICE__.set({ enabled: false })             // 全部关掉

window.__DSH_NOTICE__.prefs                               // 看当前配置
window.__DSH_NOTICE__.reset()                             // 恢复出厂默认

配置项

配置项类型默认说明
enabledbooleantrue总开关。false 时以下所有通道全部静默
onInteractionbooleantrue请求授权 / 向你提问 / 计划待审时提醒
onCompletebooleantrue一轮任务结束时提醒
soundbooleantrue横幅是否带系统提示音
onlyWhenUnfocusedbooleantrue窗口在前台、且当前显示的正是要通知的那个会话时不提醒
announceOnFirstRunbooleantrue首次安装时弹一条欢迎横幅(用来验证链路)
language"auto" | "zh" | "en""auto"通知文案语言。auto = 跟随应用界面语言

sound 只决定本插件发出的通知要不要响,与「系统设置 → 通知 → DeepSeek Harness」里的 声音开关互不影响。

什么时候会提醒

插件把每个会话的状态折成一个索引,只对状态跃迁做出反应,不会因为刷新页面补发旧通知。

跃迁横幅
出现待审批请求需要你的授权 — 「会话名」想要执行 <工具名>,请回到应用确认。
出现待回答的问题需要你的回答 — 直接显示问题正文
出现待审阅的计划等待你审阅计划 — 「会话名」提交了一份计划,等待你确认。
一轮任务从运行中变为结束,且没有待处理交互任务已完成 — 「会话名」已完成本轮任务。
一轮结束时正好又要你介入(比如收尾时向你提问)只发一条,且发「需要你…」那条

会话名只在有必要时出现:当横幅讲的就是你正盯着的那个会话时,标题不重复出现在正文里。

什么时候故意不提醒

命中任意一条就不发,这是设计,不是 bug:

  • enabled 为 false;
  • 对应通道被关掉(onInteraction / onComplete);
  • 该会话是子代理或空白会话——你看不见的内部工作不该打扰你;
  • 窗口在前台,且正显示着要通知的那个会话——输入框就在你眼前,再弹横幅是噪音。 把 onlyWhenUnfocused 设为 false 可以关掉这条规则。

另外,macOS 本身在应用处于前台时通常也不画横幅(只静默进通知中心),这是系统行为, 不是插件能决定的。

语言

默认 language: "auto",跟随应用界面语言——auto 读的是 DSH 自己的语言设置, 而不是渲染进程的 navigator.language(桌面版里后者恒为 en-US,曾经导致中文应用弹出英文通知)。

想固定成某一种语言:

window.__DSH_NOTICE__.set({ language: "en" })   // 永远英文
window.__DSH_NOTICE__.set({ language: "auto" }) // 回到跟随应用

排查语言问题时,window.__DSH_NOTICE__.state() 里的 language 是实际生效的文案语言, detectedLocale 是检测到的应用语言,两者对不上就能立刻定位。

手动安装(不想跑脚本时)

安装脚本只做两件简单的事,你完全可以自己来:

# 1. 把包链接进 profile,让它能像自带 bundle 一样被解析
ln -s /path/to/DshNotice/packages/dsh-notice \
      ~/.dsh/profiles/desktop/node_modules/dsh-notice
# 2. 在 ~/.dsh/profiles/desktop/cordis.patch.yml 末尾追加这一行
- insert:
    - id: notice
      name: 'dsh-notice'

偏好设置是每个存储分区独立的,桌面版窗口固定用同一个分区,所以配置是持久的。


常见问题

现象原因与处理
装完没有任何横幅先按 ⌘R 刷新窗口;再不行重启应用。然后在控制台跑 window.__DSH_NOTICE__.state()
控制台里没有 __DSH_NOTICE__client 模块没进页面。node plugin.mjs status 确认两行都是 yes,然后重启应用
status 显示 package linked : broken仓库被移动或删除了,软链指向了不存在的位置。在新位置重跑 node plugin.mjs install 即可修复
state() 里 attached: falseuiSession 服务没就绪,通常是插件树还没组合完,等一下或重启
state() 里 permission 不是 granted通知权限没拿到,检查系统设置的授权弹窗
delivered: true 但屏幕上没横幅系统层面拦掉了。打开「系统设置 → 通知 → DeepSeek Harness」,确认允许通知且样式不是「无」
应用在前台时不弹系统行为 + 插件的前台抑制,见上一节。把 onlyWhenUnfocused 设为 false 可以只绕过后半条
横幅文字是英文,但界面是中文用 set({ language: "zh" }) 锁定,并欢迎提 issue 附上 state() 的输出
后台会话完成时不提醒预期行为,见「什么时候故意不提醒」
卸载后还在提醒需要刷新窗口或重启应用来卸载 client 模块
改完配置想撤销window.__DSH_NOTICE__.reset() 恢复默认偏好;node plugin.mjs uninstall 彻底移除插件

想连 profile 配置一起恢复出厂状态:

ls ~/.dsh/profiles/desktop/cordis.patch.yml.dsh-notice-backup-*   # 找到备份
cp <备份> ~/.dsh/profiles/desktop/cordis.patch.yml

工作原理(简要)

插件由两半组成,加起来不到 600 行:

半边文件职责
Hostpackages/dsh-notice/lib/index.js空实现。唯一作用是让这个包成为一条 Loader 行——dsh-client-modules 只扫描 host 侧的插件行来发现带 dsh.client 的包,没有这一行,浏览器那一半不会被下发
Clientpackages/dsh-notice/lib/client.js全部功能。订阅会话状态投影,在状态跃迁时调用 Web Notifications API

两个值得一说的决定:

  • 用 Web Notifications,而不是 osascript -e 'display notification'。 桌面版窗口运行在 dsh-app://app 源上,Electron 主进程为该源安装的权限处理器对所有非媒体权限一律放行, 所以 new Notification(...) 会被交给 macOS,以 "DeepSeek Harness" 的身份发出:带应用 图标、进通知中心、遵守该应用在系统设置里的开关。而 osascript 发出的通知署名是 Script Editor、图标是通用图标、点了也无法聚焦应用。
  • 只被动读状态,不挂请求回调。 approval/request 和 user-questions/request 是 Cordis waterfall,挂上去就能接管请求。本插件完全不碰这两条链路,因此不可能吞掉、延迟或误答一个 属于你的请求。

更细的插件 API 调研笔记见 docs/DSH-PLUGIN-API-REFERENCE.md。


开发

npm test     # 40 条用例,两秒内跑完,无第三方依赖

测试分两套,都不需要浏览器或真实应用:

  • packages/dsh-notice/test/client.test.mjs(26 条) 用 node:vm 复刻了一个渲染进程式的 Realm:在里面真实执行发布态的 lib/client.js,捕获 window.__ModuleLoader__.load 的调用, 挂到一个假的 Cordis 上下文上,然后驱动会话状态投影走各种跃迁,断言"该提醒的提醒、该安静的 安静"。状态文案、子代理过滤、前台抑制、语言选择、权限缺失降级、卸载时的反订阅都在覆盖范围内。
  • test/plugin.test.mjs(14 条) 拿临时目录当 profile,端到端跑安装脚本:链接能不能被解析、 重复安装是否幂等、备份是否只在真正改动时产生、卸载后配置文件是否原样、把仓库挪走后能不能 自我修复。

改完 lib/client.js 想在应用里验证的话,重启应用(HMR 默认只监视配置文件,不监视模块文件)。

贡献流程和设计红线见 CONTRIBUTING.md。

目录结构

DshNotice/
├── README.md                           # 中文说明(默认)
├── README.en.md                        # English
├── plugin.mjs                          # 安装 / 卸载 / 状态,唯一的入口脚本
├── package.json                        # 工作区脚本:npm test、npm run plugin:install 等
├── test/plugin.test.mjs                # 14 条安装脚本用例
├── .github/workflows/ci.yml            # CI:Node 20 / 22 / 24 跑测试
├── packages/dsh-notice/
│   ├── package.json                    # 声明 dsh.client(platform / inject)与 ./client 导出
│   ├── lib/index.js                    # host 半边(空实现,只为占一行 Loader 行)
│   ├── lib/client.js                   # client 半边(全部逻辑)
│   └── test/client.test.mjs            # 26 条插件用例
├── docs/DSH-PLUGIN-API-REFERENCE.md    # DSH 插件 API 调研笔记
├── CHANGELOG.md
├── CONTRIBUTING.md
└── LICENSE

许可证

MIT