Back to home@TARS-snail

dsh-notify

Desktop notifications for DeepSeek Harness sessions, only while you are away

Stars
0
Language
TypeScript
Created
Aug 25, 2026
Updated
Aug 25, 2026
GitHub repo

Introduction

dsh-notify

一个 DeepSeek Harness(DSH)插件(bundle):当会话完成任务向用户提问(需要选择/回答)、且用户不在会话中时,发送桌面通知并播放系统默认提示音,让你把 DSH 挂到后台跑长任务时不错过关键节点。

本插件是纯事件观察者:只监听会话事件流,不拦截、不修改任何会话行为;通知失败绝不会影响会话本身。

功能

触发时机(均可单独开关)

触发时机会话事件通知示例
一轮对话正常结束(agent 回答完毕、回到空闲)turn/endreason.kind === 'completed'DSH · 回答完成 + 回答摘要
持久化目标(goal)完成goal/changeoperation === 'complete'DSH · 任务完成 + 目标内容
agent 调用 ask_user_question 向你提问tool/callname === 'ask_user_question'DSH · 需要你的回答 + 问题与选项

在场检测(presence)——只在离开时打扰

浏览器端插件(dsh.client 客户端半部)把当前选中的会话 + 页面可见性实时上报给宿主:

  • 页面是当前标签页 窗口有焦点 → 视为"在会话中" → 不弹通知
  • 切到别的标签页 / 别的应用 / 最小化 / 关闭页面 → 视为"离开" → 正常弹通知
  • 心跳 + 最后状态兜底:标签页崩溃或断连后宿主侧 45 秒(TTL)自动过期,不会永久静音
  • 无浏览器场景(headless / tui)没有上报 → 恒为"离开" → 全部通知

系统默认提示音

每次弹出桌面通知时,按顺序尝试 canberra-gtk-play -i message(系统声音主题的默认提示音)、pw-play/paplay(freedesktop message.oga),都不可用则只记录日志、保持静默。可通过 playSound 关闭。

通知后端

Linux 桌面 notify-send(自动检测,缺失时降级为控制台)、任意自定义命令(占位符 {title}/{message})、纯控制台输出。

架构

浏览器(客户端半部 lib/client.js)                     宿主(bundle 行)
┌──────────────────────────────┐        ┌──────────────────────────────────┐
│ dsh-notify/client            │  RPC   │ dsh-notify/rpc  →  ctx.presence   │
│ visibilitychange / blur /    │ ─────► │ POST /dsh-notify/presence        │
│ focus / pagehide / 心跳(15s) │        │        │                         │
│ 当前会话 + 页面可见性         │        │        ▼                         │
└──────────────────────────────┘        │ dsh-notify 主插件                │
                                        │ session/event → 抑制检查 → 通知  │
                                        └──────────────────────────────────┘

bundle 声明了两个宿主行:notifyinject: ['sessions'],提供 presence 服务,headless 可用)和 notify-rpcinject: ['connection', 'presence'],仅在 Web 宿主注册 RPC 通道,headless 下自动挂起)。客户端半部通过 dsh.client 清单随 Web 装配加载,由构建脚本打包成 window.__ModuleLoader__ 惰性工厂格式。

安装

本包是一个符合 DSH 规范的 bundlepackage.json 中声明了 dsh.bundle.patch,指向随包发布的 cordis.patch.yml 配置层。

从本地目录安装(开发/自用)

# 1. 构建(lib/ 产物;link 方式安装不会自动跑 prepare)
npm install
npm run build

# 2. 安装进某个 profile(首次使用该 profile 会自动初始化)
dsh plugin --profile web add /path/to/DSH_remind

dsh plugin 检测到包声明了 dsh.bundle 后,会自动把它追加进该 profile 的 dsh.profile.bundles 层列表。重启该 profile(如 dsh web)后生效。

从 GitHub 安装

发布到仓库后:

dsh plugin --profile web add github:you/dsh-notify#<commit-sha>

git 依赖安装时 pnpm 会执行 prepare 脚本(即 npm run build)自动构建 lib/;pnpm ≥ 10 需要你在 profile 目录的 pnpm-workspace.yaml 里允许执行构建脚本(首次 add 失败时 dsh 会打印确切的键):

allowBuilds:
  dsh-notify: true

验证配置层(不启动)

dsh --profile web --patch /path/to/DSH_remind/cordis.patch.yml --dump-config
# 应能看到 "# == .../cordis.patch.yml" 下的 notify 行

配置

默认零配置可用。在 profile 的 cordis.patch.yml(用户层)中按 id 覆盖该行:

- id: notify
  name: dsh-notify
  config:
    backend: notify-send
    urgency: normal
    expireMs: 10000
    onTurnComplete: true
    onGoalComplete: true
    onUserQuestion: true
    onlyQuestionsWithChoices: false
    onlyWhenAway: true
    presenceTtlMs: 45000
    playSound: true
    titlePrefix: DSH
    previewMaxChars: 120
    debounceMs: 1000
字段类型默认说明
backendauto | notify-send | console | commandautoauto:有 notify-send 用桌面通知,否则降级控制台
commandstring''command 后端的命令模板,经 /bin/sh -c 执行;{title}{message} 被替换,引号转义由命令作者负责
urgencylow | normal | criticalnormalnotify-send 的紧急度
expireMsnumber10000notify-send 展示时长(毫秒,0 = 常驻直到关闭)
titlePrefixstringDSH通知标题前缀
onTurnCompletebooleantrue一轮回答完成时通知
onGoalCompletebooleantruegoal(长期任务)完成时通知
onUserQuestionbooleantrueagent 提问时通知
onlyQuestionsWithChoicesbooleanfalsetrue 时只通知带选项(需选择)的问题
onlyWhenAwaybooleantruetrue 时,正在浏览该会话(当前标签页且窗口有焦点)就不弹通知;离开才弹
presenceTtlMsnumber45000浏览器上报的在场状态有效期(毫秒);标签页停止上报(崩溃/断连)后超过该时长即视为离开
playSoundbooleantrue弹通知时播放系统默认提示音(canberra-gtk-play -i messagepw-play/paplay 兜底)
previewMaxCharsnumber120通知正文中回答摘要/目标内容的截断长度
debounceMsnumber0 及以上同类通知的最小间隔(毫秒),避免同一时刻连发多条

自定义命令示例(macOS):

config:
  backend: command
  command: 'osascript -e "display notification \"{message}\" with title \"{title}\""'

工作原理

浏览器(页面)                           宿主(本 bundle)
 会话选中变化 ─┐
 visibility/focus/blur ─┼─► RPC /dsh-notify/presence ─► ctx.presence(TTL 状态表)
 心跳 15s ─────┘                                        │
                                                        ▼
DSH 会话事件流 (session/event) ─────────────────► 离开检查(isAttended)─► 防抖 ─► 通知 + 提示音
  • 主插件声明 inject: ['sessions']:等待 dsh-session 服务就绪后加载(每个 profile 的 dsh-base 都提供),并提供 presence 服务。
  • 通过 ctx.on('session/event', …) 订阅事件流;session/disposed 时清理会话状态。
  • 监听器整体 try/catch 包裹:session/event 处于会话热路径上,任何通知故障都会被吞掉并只写入日志,绝不冒泡。
  • 通知与提示音进程以 detached 方式派生并 unref(),不阻塞、不等待;退出码非零或 stderr 有输出会记录到 DSH 日志。
  • 所有注册(监听器、effect、RPC 通道)都由 Cordis 随插件卸载自动回收,支持 HMR。
  • 客户端半部保持零 import(构建期纯度门禁),由 scripts/build-client.mjs 打包成 Web 装配所需的惰性工厂格式。

开发

npm install          # 依赖(沙箱环境可加 --cache ./.npm-cache)
npm run build        # tsc → lib/(宿主)+ lib/client.js(客户端 bundle)
npm test             # 构建 + 29 个测试(宿主事件/在场抑制/RPC/客户端 DOM 模拟,无需 LLM/网络)

测试直接加载构建产物:在裸 Cordis 根上下文上提供假 sessions/connection 服务,注入合成会话事件与在场上报并断言通知输出;客户端半部在 mock 的 DOM 与 ModuleLoader 上运行。

许可证

MIT