Pular para o conteúdo principal
V

dsh-macos-notifier

veritaswiki/dsh-macos-notifier

Posts a macOS notification titled with the session name when that session finishes a turn, and clicking it returns to the session. An in-app notice is always shown alongside it (9 s in the foreground, 60 s in the background); the system banner needs notifications allowed for DeepSeek Harness in macOS settings, otherwise only the in-app notice appears.

Instalar

dsh plugin --profile web add github:veritaswiki/dsh-macos-notifier

README

dsh-macos-notifier — 会话结束时的 macOS 通知

License: MIT Node DSH

会话干完这一轮时给你一条标题为会话名的 macOS 通知,点一下回到那个会话。通知由 DSH 自己的渲染层发出:不起子进程、不调用 osascript、不写状态文件。

它做什么

  • 会话从「运行中」变成「已停止」时发一条 macOS 系统通知——onStatus() 只放行 prev === true && isRunning === false 这一条边沿,运行中途的状态抖动不弹。
  • 通知标题是会话名:取 sessions 快照里的 byId[id].title,读不到时回退 label,再读不到用 DSH。
  • 点通知回到那个会话:uiWorkspace.openSession(sessionId)。
  • 同一会话的通知互相替换,不堆成一列:tag 固定为 dsh-session-<sessionId>;同一会话 2 秒内不重复发(THROTTLE_MS = 2000)。
  • 系统通知之外必定再发一张应用内提示(同一个事件两路都发):前台 9 秒、后台 60 秒后自动消失,同屏最多 3 张。
  • 会话头部工具区有一个小铃铛:点一下发一条测试通知,用来确认通道通不通。
  • 子代理会话、带父会话的会话、空白会话不发通知(isNotifiable() 过滤)。

它怎么做

全部逻辑在 client.js,跑在 DSH 的渲染层里;index.js 是一个空的 no-op 插件。

环节实现(client.js)
触发ctx.get('remote').$on('api-session/status', (sessionId, isRunning) => …),交给 onStatus() 判断边沿
兜底轮询remote.$on 不可用时退化为 setInterval(…, 800),比对快照里的 running 字段
会话过滤isNotifiable() 跳过 origin === 'subagent'、带 parentSessionId、blank === true 的会话
标题ctx.get('sessions') 快照的 byId[sessionId].title → 回退 label → 回退 'DSH'
系统通知new Notification(title, { body, tag: 'dsh-session-' + sessionId, silent: false })
节流THROTTLE_MS = 2000,按 sessionId 记时,窗口内直接返回 false
点击(主路径)n.onclick → ctx.get('uiWorkspace').openSession(sessionId)
点击(兜底)发通知时把 sessionId 写进 localStorage['dsh.macos-notifier.pending'](PENDING_TTL_MS = 90000),window 的 focus 事件里 takePending() 补跳一次
应用内提示命令式 DOM:document.body 上挂 [data-mn-layer] 容器,不经过 React 插槽,同屏最多 3 张
小铃铛slots.inject('conversation.session.header.utilities', …),id: 'macos-notifier-action',order: 90
样式宿主slots.inject('shell.overlay', …) 只注册一段 <style>,id: 'macos-notifier-style',order: 200,随插件卸载移除
宿主侧index.js 的 apply() 是空函数,只提供一行可启停的 Loader entry

因为通知和跳转在同一个渲染层里,点击时手里直接握着 sessionId:

  • 不起子进程:index.js 无 import,client.js 只用宿主提供的 require('react') 和浏览器的 Notification / DOM API;
  • 不调用 osascript,也就不会把通知归属到别的 App;
  • 不写状态文件,也不需要自定义 URL 协议。

安装

本插件是 DSH(Cordis)bundle,三种装法。

方式 A:让 Agent 用 plugin_manager 安装

在 DSH 里对 Agent 说「装 dsh-macos-notifier」,它会调 plugin_manager:

  • action: "install_bundle",target 填安装 spec:
    • 已 clone 到本地:file:/abs/path/to/dsh-macos-notifier
    • 或仓库:github:veritaswiki/dsh-macos-notifier
  • 装完 action: "set_bundle" 启用。

方式 B:手动挂到 profile

  1. 把仓库放在任意目录(下面记作 /abs/path/to/dsh-macos-notifier)。
  2. 编辑 profile 的 package.json(~/.dsh/profiles/<profile>/package.json):
    • dependencies 里加 "dsh-macos-notifier": "link:/abs/path/to/dsh-macos-notifier"
    • dsh.profile.bundles 数组里加 "dsh-macos-notifier"
  3. 在 profile 目录安装依赖(DSH 下次启动时也会自行安装),然后重启 DSH。

方式 C:从源码

  1. git clone https://github.com/veritaswiki/dsh-macos-notifier.git 到任意目录。
  2. 同方式 B,只把依赖写成 "dsh-macos-notifier": "file:/abs/path/to/dsh-macos-notifier"。
  3. 重启 DSH。

依赖说明

client.js 只 require('react')(宿主提供),index.js 没有任何 import。因此本包没有需要单独安装的依赖,也不需要任何 @deepseek-ai/* 包;仓库不带 node_modules。

装完先开系统通知

系统设置 → 通知 → DeepSeek Harness → 打开「允许通知」,样式选「横幅」或「提醒」。没开之前系统横幅不会出现,此时只有应用内提示可见。

配置

本插件没有设置页:index.js 的 apply() 是空函数,没有 Config schema,所以没有任何能在 UI 里改的配置项。可调的东西只有下面这些代码常量与系统权限:

键位置类型默认值说明
THROTTLE_MSclient.jsnumber2000同一会话两条系统通知的最小间隔(毫秒)
PENDING_TTL_MSclient.jsnumber90000待跳转 sessionId 在 localStorage 里的有效期(毫秒)
PENDING_KEYclient.jsstring'dsh.macos-notifier.pending'待跳转目标的 localStorage 键名
提示卡存活时间client.jsnumber9000(前台)/ 60000(后台)应用内提示自动消失的毫秒数
同屏提示卡上限client.jsnumber3超过就移除最早的一张
dsh.client.immediatelypackage.jsonbooleantrueclient 模块随宿主立即加载
dsh.bundle.patchpackage.jsonstring./cordis.patch.ymlbundle 补丁入口
系统通知权限系统设置 → 通知 → DeepSeek Harness枚举未授权未授权时 systemNotify() 直接返回 false,不发横幅

已知限制

  • 仅 macOS 有意义:用的是渲染层的 Web Notification。在普通浏览器里打开同一个 Web GUI 时,通知由浏览器自己发、权限另算,插件不接管。
  • 系统通知未授权时没有横幅:systemNotify() 在 Notification.permission !== 'granted' 时直接返回 false,此时只剩应用内提示。
  • 首次授权要用户动手:权限只在点击小铃铛的手势里请求(页面加载时自动请求会被系统拒绝)。denied / default 时点铃铛打开的是系统通知设置,不是权限对话框。
  • 依赖宿主两个契约:事件名 api-session/status,以及 sessions 快照字段(byId[].title / label / running / origin / parentSessionId / blank)。宿主改事件名则退化为每 800 ms 轮询;改快照结构则不再触发。全程 try/catch,不抛错、不挡界面。
  • 补跳只有 90 秒窗口:点横幅若只把窗口拉到前台、没触发 onclick,靠 window 的 focus 事件补跳;超过 PENDING_TTL_MS 不再跳。
  • 不是通知中心:不保存历史、不分组、UI 里不可调;应用内提示固定盖在页面右下角(z-index: 2147483000)。
  • 节流写死:THROTTLE_MS = 2000 硬编码在 client.js,同一会话 2 秒内的第二次结束不会重复弹。

文件

文件作用
package.jsonbundle 清单:dsh.bundle.patch + dsh.client(web,立即加载)
cordis.patch.yml插入 macos-notifier Loader 行(insert.name 必须等于包名)
index.jsHost 侧:空插件,只提供一行可启停的 Loader entry
client.js客户端全部逻辑:状态边沿、系统通知、应用内提示、小铃铛
locale/zh.json、locale/en.json插件管理页标题与描述
icon.svg插件图标(DSH 内部用)
assets/icon-180.png180×180 位图图标

开发

node --check index.js
node --check client.js
node scripts/sanitize-check.mjs

本仓库没有自动化测试(没有 test/ 目录,package.json 里也没有 test 脚本);CI 只跑语法检查与脱敏门禁两步。

scripts/sanitize-check.mjs 是脱敏门禁:本机绝对路径、私有包前缀、回环地址、私钥材料、内网地址段、内部主机名、token 前缀一律不许出现,命中即退出码 1。

License

MIT © 2026 veritaswiki

Plugins relacionados