Turn event notifications: six event classes (completed, error, interrupted, approval requested, AI question, limit reached) delivered via sound, system notification, in-page banner, webhook, IM and host desktop-notification channels; when several browser windows are open, only one rings.
Install
# from npm (prebuilt)
dsh plugin --profile web add @mzzsfy/dsh-turn-notify
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:mzzsfy/dsh-plugin#path:/packages/dsh-turn-notify
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
DeepSeek Harness 消息通知插件:AI 回合产生六类事件(完成/出错/被中断/等待审批/AI 提问/达到上限),按你的配置送达声音、系统弹窗、页内提示、webhook、IM、宿主桌面通知六种通道,多窗口只响一次。
快速上手
dsh plugin --profile web add @mzzsfy/dsh-turn-notify
安装后打开 设置 > 消息通知:默认全部事件开启,声音与系统弹窗即装即用(系统弹窗经一次浏览器授权后生效);webhook、IM 与宿主桌面通知填配置后启用。页内提示经公共依赖 @mzzsfy/dsh-toast 提供,由 session-manager 插件代挂装载——未安装 session-manager 时该通道自动禁用,其余通道不受影响。声音通道受浏览器自动播放策略约束,首次点击页面后才会出声。想推到微信等聊天工具,见 IM 推送;想推到自建服务,见 webhook 推送;想在宿主机桌面弹系统通知(浏览器全关也送达),见 宿主桌面通知。
六类通知事件
插件监听 AI 会话,把回合结果归入六类,每类可独立开关、独立指定声音:
| 事件 | 含义 |
|---|---|
| 任务完成 | AI 回合正常结束 |
| 任务出错 | AI 回合以错误结束,或被你手动停止 |
| 被中断 | 会话关闭时未正常收尾的回合被补发中断结束(少见) |
| 等待审批 | AI 请求工具授权,等你点击允许;回合以等待审批结束也归此类 |
| AI 提问 | AI 调用 ask_user_question 向你提问 |
| 达到上限 | 回合因输出长度上限结束 |
过滤条件(全部可在面板调整):
- 最短回合时长:回合结束类通知(任务完成/出错/被中断/达到上限,以及以等待审批结束归入"等待审批"的回合)短于该时长(默认 5 秒)不送达,过滤掉连续快速的小回合;AI 提问与审批请求随事件即时送达,不受此过滤。
- 子代理会话不通知:子代理会话(前台或后台委托)自身的事件不通知,只有主会话通知。
- 子代理相关回合静默:仅影响"任务完成"类。两类回合不通知:回合结束时仍有后台子代理在跑(发起后台委托后主回合先行结束的等待期),以及子代理收尾后唤醒父会话继续工作的回合。整条委托链路只在最终回合响一次,避免刷屏。
声音通知
声音通知是本插件的核心能力,全部在浏览器内完成,不依赖系统音效与音频文件:
- 8 种内置音色:上行琶音、铃铛、清脆双音、警报方波、低鸣、双音提示、嘀嗒、低音下滑。由 Web Audio 实时合成,零下载、零系统依赖。浏览器自动播放策略要求先与页面交互一次(任意点击),首次交互前声音静默,其余通道不受影响。
- 自定义音效上传:支持 wav / mp3 / ogg,可一次多选,单文件上限 2MB、总库上限 10MB。上传前可逐个试听,确认后才落盘保存。支持重命名;同一文件重复上传自动识别,不产生重复文件。
- 按事件分类指定音效:六类事件各自映射一款音效(内置或上传均可),例如"任务完成"用上行琶音、"等待审批"用双音提示。删除音效时引用它的映射自动清空、回落内置默认;仅当映射指向不存在的音效(如手改 yaml 产生死链)时,面板才显示失效项,试听时给出归因提示。
- 按分类静音:总开关之外,可单独让某一类事件不出声,例如只对"AI 提问""等待审批"出声。静音只关闭声音通道本身,系统弹窗与标题闪烁照常;页内提示卡片与会话高亮不受影响;页内提示音有独立的分类开关,不随此处变化。
- 音量:按 5% 步进 0-100% 可调,本机记忆。
- 映射双作用域:音效映射默认全局共用(存 settings.yaml,所有浏览器一致);打开"当前域名独立"后,映射改动只写本浏览器(按域名隔离),公司/家里各配各的,本地优先于全局。关闭开关即恢复全局,本地已存映射休眠保留,重新开启即恢复。
- 试听:音效库与分类映射的每一项都能即时试听,试听的就是实际生效的音效。
声音由抢到呈现权的窗口播放,多个窗口打开同一页面时只有一个窗口出声(见 多窗口只响一次)。
系统弹窗与页内提示
- 系统弹窗:浏览器 Notification 授权后,窗口失焦时弹系统级通知;标题为事件文本,通知小结(见下节)作正文。点击弹窗聚焦窗口并直接切换到对应会话(点击直达,下节)。HTTP 非回环地址下浏览器不提供该能力,声音开启时自动降级为标题闪烁;HTTPS 下自动恢复。Windows 下还受系统通知设置与专注助手约束。
- 页内提示:页面角落浮出的卡片提示(dsh-toast 栈式展示,6 秒),聚焦窗口内唯一常开的提醒形态,可关;通知带小结时以第二行呈现,点击卡片直达对应会话。
- 页内提示音:聚焦场景的独立声音,总开关、按事件分类独立开关与音色映射全套自成一档,独立于提示音的分类配置。开启后页内提示弹出且通知声音未播时补一声提示——聚焦窗口内通知声音被聚焦静默压制,靠它保留听觉提醒;与通知声音互斥,同一通知至多一声;音量共用,音色在音效页的「页内提示音映射」单独指定。
- 降级标题闪烁:系统弹窗开关开启但未获得授权或不可用(如 HTTP 非回环地址、曾被拒绝)且窗口未聚焦时,标签页标题以 ⏳ 前缀闪烁替代弹窗;单条闪烁 6 秒自停,连环通知逐条续命但总时长硬顶 30 秒必停,返回窗口立即停止;可在偏好中关闭该提示。
- 聚焦静默:窗口可见且有焦点时只保留页内提示,声音、系统弹窗与标题闪烁全部静默(页内提示音开启且该分类未在页内静音时仍有补位一声);可见但无焦点(多屏正看别的窗口)不静默,照常弹窗出声;你离开键盘满 5 分钟视为不在电脑前,聚焦也全通道提醒。
点击直达会话
等待你动作的审批与提问、以及回合结束类通知,呈现时都携带直达动作:点击页内提示卡片或系统弹窗,窗口聚焦并自动切换到对应会话(模拟点击侧边栏会话行,与手工点击完全同路),高亮随之清除(查看即已读)。会话行未渲染(列表折叠/懒加载未滚动到)时仅聚焦窗口;通知无会话标题时同样只聚焦。「知道了」类显式关闭不触发直达。
等待动作二次提醒
审批请求与 AI 提问送达后 10 分钟仍未处理时,自动补发一轮提醒(页内提示 + 声音 + 标题闪烁,独立于聚焦静默与通道路由——它就是"上一轮没看到"的兜底),每条通知至多补发一次,不连环催。"已处理"的判定:你已切到该会话,或已点掉会话行高亮;通知无会话标题时无法感知处理,按未处理补发。通知被其他窗口处理或已移出投影队列时,补发登记自动撤销。
通知小结
回合结束类通知附一句话小结:耗时(回合时长)+ tokens(本回合各步用量四桶合计)+ 最终回答前缀(末条含文本回答的首行,按码点截断),拼装为「耗时 12 秒 · 1.5k tokens · 已修复登录」形态;缺哪项省哪项,全缺省无小结。系统弹窗作正文、页内卡片作第二行,webhook payload 增补 summary 与 tokens 结构化字段。统计来自 host 侧会话事件流内的 assistant 消息,回合结束即消费清账,无额外请求。
事件→通道路由
六类事件各自可指定放行通道名单(kindRoutes,存 settings.yaml),名单外的通道对该类事件一律不送达;未配置的分类全通道放行,名单为空数组即全禁。通道名:sound(提示音)/ system(系统弹窗)/ toast(页内提示,页内提示音随行)/ blink(降级标题闪烁)/ webhook / im / host(宿主桌面通知)。典型用法:任务完成只推 webhook,重要事件(审批/提问)走 IM:
turn-notify:
kindRoutes:
completed: [webhook] # 完成只推 webhook,浏览器不响
approval: [im, sound, system] # 审批走 IM 且本机弹窗出声
路由是全局配置,作用于所有浏览器;本机呈现偏好(声音/弹窗开关、聚焦静默)在其上叠加,两层都放行才送达。面板"通知"分区不展示该配置,yaml 直改保存即生效。
webhook 推送
在面板填入 webhook URL 即启用。host 直发,标签页全关也送达;payload 为 Slack 兼容 JSON(text 字段承载通知文本,summary 字段承载通知小结),另附 event/category/status/session/workspace/durationMs/tokens/ts 结构化字段;超时 10 秒,不重试。URL 原文随配置响应回显、保存即提交输入框内容(清空并保存即禁用),测试按钮返回真实投递结果。
宿主桌面通知
开启面板"通知"页的宿主通知开关后,通知触发时由宿主进程直接弹操作系统级桌面通知,零依赖:macOS 走 osascript,Linux 走 notify-send,Windows 走 PowerShell 调 WinRT toast;超时 10 秒,失败即弃不重试,不影响其余通道。去重方向为浏览器优先:浏览器在场(2 秒内有在途长轮询)时由浏览器呈现,宿主让位,正常路径零重复;浏览器离场(标签页全关)时宿主补位。重复只发生在在场判定的边界情况(认可窗口边界与网络抖动),宁可重复不可漏。两个开关判定一致,任一开启即生效:
- 宿主机弹桌面通知:启用宿主通知通道(浏览器优先去重内建)。
- 仅当无浏览器接收时补位:与总开关等效,作为显式的补位语义表达保留。
"浏览器在场"按投影长轮询记账:任意来源 + 续传 cursor + 同源形态的 GET 在服务端挂起期间计为在途,断连(标签页关闭/请求中止)经连接关闭事件即时出账,最近活动在 2 秒认可窗口内仍算在场——优雅断连的盲区不超过 2 秒;异常断连(睡眠、强杀、断网)服务端无感知,受长轮询挂起上限约束,最坏约 25 秒后自愈出账。来源不限地址:浏览器可在任意机器访问 dsh(局域网部署),在场信号看的是"有无 dsh 页面在轮询",与浏览器同不同机无关。浏览器在场但在场浏览器的呈现偏好全关(声音/弹窗/页内全关或系统通知未授权)时,宿主同样让位,该事件在浏览器侧无 OS 级通知(仅剩会话行高亮)。宿主在无桌面的服务器上时 spawn 会静默失败,无害;测试页"测试宿主通知"返回真实结果,可据此验证宿主机桌面环境是否可用。聚焦时宿主通知照弹(宿主无法感知窗口焦点,聚焦静默是浏览器侧偏好)。
IM 推送
安装 @xmanrui/dsh-im 后自动启用,可推送到微信等九种渠道。投递目标的新建与平台测试在 dsh-im 设置页完成,本插件只做选择:从已绑 bot 的目标目录勾选即保存,支持绑定多个 bot。触发逻辑与 webhook 一致,fire-and-forget 不重试;bot 离线时该次通知弃置不补发。
会话行高亮
通知实际送达时,侧边栏会话列表中对应会话行以背景脉冲闪烁强调,六类各一色(完成绿/出错红/提问蓝/中断橙/审批黄/上限紫),点击该会话行即停止。开关在面板"偏好"页首项(独立开关,本机生效,各浏览器独立),默认开启。并行多会话时用于快速定位状态刚更新的会话:运行中的会话不闪烁(与 dsh 原生状态点分工——状态点表达运行中,闪烁表达已更新待查看),会话重新进入运行状态时闪烁自动让位;聚焦时正在查看的会话不闪烁,失焦期间本会话的通知照常闪烁,返回窗口即自动消失(回来即已读,焦点与页面可见双通道)。行定位按会话标题文本匹配,标题默认与侧边栏行同源(session-title 投影);该服务缺失或不可用(如伪会话无事件日志)时回落会话事件内存储的标题,可能与侧边栏不同步。会话行未渲染(列表折叠/懒加载未滚动到)时该次高亮静默跳过。重开页面的冷启动静默对齐同样挂高亮(见"多窗口只响一次"),重开即见哪些会话有动静。
工作区文件夹运行标记
侧边栏会话列表按工作区分组(官方 WorkspaceBrowser 分组模式)时,有运行中会话的工作区文件夹组头显示脉冲运行点并给文件夹图标着色,游离(未归属任何工作区)的可见运行中会话使末位「未分组」组头同样标记——折叠与展开同样生效。归属与运行态一律取服务快照投影,DOM 仅按下标对位官方组容器;组容器数漂移(单列表模式/搜索态/结构变更)即清除标记不误标。图标着色让位官方 folderActive(展开且含当前会话)态。开关在面板"偏好"页"会话列表"卡(存宿主配置,各浏览器共用),默认开启,变更刷新页面生效。
多窗口只响一次
同一地址(同源)打开的多个窗口共享一次呈现:各窗口以长轮询挂起在 host 的通知队列上(事件产生或配置变更即时返回,空闲期单次挂起至多数十秒,失败按指数退避重连),经 localStorage 协调,仅一个窗口抢先呈现——发声并弹出页内提示、系统弹窗、高亮会话行,其余窗口全部跳过,不止声音。通知在产生后的下一次唤醒即送达,通常亚秒级;不同浏览器或不同地址打开的窗口各自发声互不感知。窗口关闭/刷新期间错过的通知不做重开回放:启动首轮静默对齐游标与完成标记,不逐条补发提示与声音(等待用户动作的审批与提问例外,照常呈现);未读会话行高亮不在静默范围,照常挂上。
设置面板指南
设置 > 消息通知,五个分区:
| 分区 | 内容 |
|---|---|
| 通知 | webhook 地址、最短回合时长、子代理过滤、宿主通知(总开关与回退)、六类事件开关;存 settings.yaml,事件开关点击即存,其余点保存后生效。事件→通道路由(kindRoutes)亦存此命名空间,经 yaml 配置 |
| 偏好 | 本机浏览器记忆的设置:会话高亮(独立开关)、声音总开关与分类静音、音量、系统弹窗与授权、页内提示开关、页内提示音(总开关 + 分类独立开关)、聚焦静默、降级标题闪烁 |
| 音效 | 音效上传/试听/重命名/删除,六类事件的音效映射与双作用域开关,页内提示音映射(本机,未配置沿用通知音效) |
| IM | 绑定 dsh-im bot、勾选投递目标(仅装了 dsh-im 时显示) |
| 测试 | 声音/页内/系统/宿主/webhook/IM 逐通道点火,回执即真实结果 |
settings.yaml 参考
面板与 settings.yaml 读写同一命名空间,两边改动互通、保存即生效。webhookUrl 随面板配置响应回显,yaml 直改同样可见。
turn-notify:
webhookUrl: '' # webhook 目标 URL,留空禁用
minTurnDurationMs: 5000 # 最短回合时长(毫秒),回合结束类通知短于此不送达
rootsOnly: true # 子代理会话不通知
suppressSubagentWake: true # 子代理相关回合不通知:后台委托未收尾或收尾唤醒(仅任务完成类)
hostNotify: false # 宿主机桌面通知总开关;浏览器优先,浏览器在场(2 秒内有在途长轮询)时让位
hostNotifyFallback: false # 宿主通知补位:浏览器离场(2 秒内无在途长轮询)时弹宿主桌面通知,与总开关判定一致
enabled: # 六类事件独立开关
completed: true
error: true
interrupted: true
approval: true
ask: true
max-tokens: true
soundMapping: # 每类事件音效映射,空为内置默认,值为内置音名或上传音效 id
completed: ''
kindRoutes: # 事件→通道路由:分类到放行通道名单,未配置的分类全通道放行
completed: [webhook]
imTargets: # dsh-im 投递目标,空数组禁用
- botId: wx_xxx
targetId: owner
音量、聚焦静默、分类静音等本机偏好存浏览器 localStorage,不经 yaml。
工作原理
按数据流向分五步:
- 事件监听:host 观察
session/event(回合结束、ask_user_question 工具调用与 assistant 消息)与approval/request审批请求(只观察,不拦截)。回合结束原因逐一映射:正常结束归任务完成,错误与手动停止归任务出错,审批拦截归等待审批,输出触顶归达到上限,会话收尾补发的中断归被中断;assistant 消息流经途累积回合小结(tokens 与回答前缀),回合结束随通知消费。 - 分类与过滤:事件归入六类,经子代理会话、子代理相关静默(仅任务完成类)、最短回合时长三道过滤,通过即产生一条通知,并按 kindRoutes 裁定放行通道。
- host 直发通道:webhook、IM 与宿主桌面通知由 host 进程直接投递(经路由名单过滤),不依赖浏览器;宿主通知按浏览器在场(在途长轮询,来源不限本机)浏览器优先去重,离场补位;同时通知写入内存队列(环形 20 条,60 秒过期,不落盘),路由名单随单元下发。
- 浏览器通道:各窗口以长轮询挂起在该队列上(cursor 续传,事件与配置变更即时唤醒,空闲期单次挂起至多数十秒,失败指数退避重连),localStorage 协调保证同一通知只有一个窗口呈现,再按本机偏好与路由名单分发到声音/系统弹窗/页内提示;等待用户动作的审批与提问超时未处理时补发一轮提醒。
- 降级链:声音通道开启而系统弹窗不可用(HTTP 非回环、未授权)→ 标题闪烁;页内提示依赖缺失 → 干净禁用该通道;localStorage 不可用 → 该窗口直接发声(可能与多窗口重复,诚实降级)。
已知取舍
- 标签页全关时,仅 webhook、IM 与宿主桌面通知(如开启)送达(声音与弹窗的浏览器前提)。
- 宿主桌面通知为浏览器优先去重:浏览器在场(2 秒内有在途长轮询)时宿主让位,边界情况(认可窗口边界、网络抖动)宁可不弹不误弹,优雅断连的离场盲区不超过 2 秒,异常断连受长轮询挂起上限约束(最坏约 25 秒);聚焦时宿主通知照弹(宿主无法感知窗口焦点);无桌面的宿主上 spawn 静默失败,无害。在场记账的形态校验(续传 cursor + 同源 fetch 形态)为启发式,防无意探活误压制与网页伪造在场(DNS rebinding 将域解析至 dsh 的页面同源可过校验,但该页面同时已握有全部写配置 API,通知压制非最大损失面),不构成对抗本机进程的安全边界。
- IM 依赖 dsh-im 的 bot 在线,离线时该次通知弃置不补发;成功回执仅代表平台受理。
- 不同浏览器或不同地址打开的窗口各自发声,互不去重。
- 系统弹窗在 HTTP 非回环下永久降级,HTTPS 化后自动恢复;Windows 下受系统通知设置与专注助手约束。
- 长轮询空闲期连接由代理或服务端挂起,经中间反代时需允许数十秒的挂起响应,否则退化为更频繁的重连;反代还需透传 Host 头(如 nginx
proxy_set_header Host $host),否则同源对账失败、真实浏览器不入账,宿主通知每次重复弹(失败方向与"宁可重复不可漏"一致)。 - 等待动作二次提醒的"已处理"判定以本窗口可见信号(会话切换/高亮清除)近似,无会话标题的通知无法感知处理,宁可多响一次。
- 点击直达依赖侧边栏会话行可被标题匹配命中,行未渲染或标题不同步时仅聚焦不切换。
安全
- 配置写入类接口(config / mapping / upload / 音效改名与删除 / 测试)带同源守卫:Origin 与 Host 不符即 403,JSON 写入另校验 content-type,阻断跨站页面 drive-by 改写配置。
- webhookUrl 在 settings 层标记为 secret(导出与日志脱敏);面板配置接口原文回显,本机同源可读写。
- 音效上传双重校验扩展名与真实音频内容;音效读取接口拒绝音频扩展名以外的文件。
- 已知边界:同源守卫不防 DNS rebinding(Origin 与 Host 相等即放行)。该暴露面属 host webserver 全部 /api 路由的存量问题,应在 host 层统一解决而非逐插件补丁。
开发
cd packages/dsh-turn-notify && npm test
开发安装(仓库维护者)
node scripts/dev-link.mjs dsh-turn-notify # 仓库根执行:归一 profile 依赖行 + 挂 junction
工作副本以 junction 挂进 profile,host 半区改动保存约 1 秒热重载,client 半区改动刷新页面即生效,无需发版;规约与全仓归一见 node scripts/dev-link.mjs all。
dsh 版本兼容
三版本全部通过:通知开关与播报形态、激活 live。0.1.6 控件数 13→19 属宿主演进,不构成不兼容。
Links
More in this category
xmanrui/dsh-im★ 1382
Connect IM bots to DeepSeek Harness via QR codes or bot credentials (9 channels: Feishu, WeChat, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp).
alvinunreal/openpets#dsh★ 1208
Bridges DeepSeek Harness lifecycle status, errors, and approval requests to a locally running OpenPets desktop companion.
shaobeichen/dsh-pocket★ 1199
Remote phone access to the DSH Web UI: scan a QR code for LAN or public (cloudflared tunnel) access with real-time sync, a mobile-adaptive layout, and a settings tab.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 555
Connects DeepSeek Harness to Avernet's Bot Collaboration Network over WebSocket V2, with automatic onboarding, isolated agent sessions, tool-call events, and multi-bot routing tools.
omdsh-dev/dsh-notification★ 83
Desktop notifications for turn completions, with per-outcome controls and keyword rules.
whyihaveyou/dsh-suite#plugin-notify★ 55
IM webhook and local notifications on turn completion, errors, or approval (Feishu/WeCom/DingTalk/Slack/Discord/custom).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.