renpengfei1027/dsh-web-notify ↗★ 0
dsh-web-notify
Approval attention plugin for the DSH Web GUI — audible + visual alerts (chime, tab badge, OS notification, dock) for pending approvals, session completions, job failures, and connection loss.
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:renpengfei1027/dsh-web-notify说明文档
阅读完整 README ↗配置参数
设置卡片注册进 DSH Web 设置页的「插件配置」→「通知」(官方 settings.plugin.item 槽位),改完即生效(120 ms debounce 热重配,无需重启)。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
sound | boolean | true | 提示音主开关 |
volume | number 0–1 | 0.15 | 提示音音量 |
badge | boolean | true | 标签页标题徽标 + Favicon 徽章 + PWA 任务栏徽标(同一开关) |
toast | boolean | true | 一次性事件卡片(完成 / 失败 / 断线) |
notify | boolean | true | OS 通知主开关(首次触发在下一个用户手势请求 Notification 权限) |
dock | boolean | true | 通知中心 Dock(右下角 FAB + 展开面板) |
completion | boolean | true | ① 会话 / 子代理完成提醒 |
completionSound | boolean | true | 完成时播放轻单音 |
completionNotify | boolean | true | 完成也走 OS 通知 |
connection | boolean | true | ② 掉线 / 重连提醒 |
connectionAlertAfterMs | number ≥1000 | 10000 | 断线持续超过该毫秒数才提醒 |
jobFailure | boolean | true | ③ 后台任务失败提醒 |
failureNotify | boolean | false | ③ 任务失败 + ④ 任务异常共用一个 OS 通知开关 |
agentError | boolean | true | ④ 模型 / 工具运行异常(429 配额、输出上限、中断、工具失败) |
cooldownMs | number ≥0 | 5000 | 同会话同 kind 去重冷却 |
alertKinds | string[] | ["approval","plan-review","question"] | 触发待处理提醒的 kind 白名单 |
quiet | object | {enabled:false, start:"23:00", end:"08:00"} | 免打扰时段(仅静音,视觉通道照常) |
soundResolved | boolean | false | 审批解决时播放下行柔和音 |
diagnostics | boolean | true | on-device 观测仪(采样最近 60 次会话快照,含 job 状态;状态集合始终自动收集) |
几个常用调法示例:
- 只要审批不要失败/断线:
completion=false、connection=false、jobFailure=false、agentError=false - 只想听响,不喜欢卡片弹:
toast=false、notify=false,保留sound + badge + dock - 夜间开发免打扰:
quiet.enabled=true、quiet.start=22:00、quiet.end=09:00,提示音全关、视觉照常 - 只接 PWA / 任务栏,系统通知弹了嫌吵:
notify=false、badge=true、dock=true
4. 重启 dsh web,设置页「插件配置」下即出现「通知」卡片
dsh web
### 方式二:npm 一键挂载
```sh
dsh plugin --profile web add dsh-web-notify
AI 编码工具 / 沙箱环境注意事项
在 TRAE、Cursor 等 AI 编码工具中安装本插件时,需注意:
- 沙箱写限制:AI 工具的沙箱通常阻止写入
~/.dsh/目录,而dsh plugin和dsh web都需要写 profile 文件。必须在 AI 工具外部的普通终端中执行这些命令。 - 切勿手动
npm install:手动把包塞进~/.dsh/profiles/web/node_modules/会绕过dsh plugin的依赖链接逻辑,导致插件的@deepseek-ai/*peer 依赖与 DSH host 的模块树脱节,settings服务不可达,命名空间注册静默失败(卡片永远只读)。 - 始终用
dsh plugin --profile web add:这是唯一正确的安装方式,它会通过 pnpm 正确链接依赖、更新package.json和cordis.patch.yml。
安装后校验
安装完成并重启 dsh web 后,检查以下文件和指标:
| 校验项 | 位置 / 命令 | 预期 |
|---|---|---|
| profile dependencies | ~/.dsh/profiles/web/package.json | dependencies 含 dsh-web-notify |
| profile patch | ~/.dsh/profiles/web/cordis.patch.yml | 含 - id: notifications 插入行 |
| 包已安装 | ~/.dsh/profiles/web/node_modules/dsh-web-notify/ | 目录存在,含 lib/、cordis.patch.yml |
| 命名空间已注册 | DevTools Console: __NOTIFICATIONS__.scopeStatus | "ready"(非 "unavailable") |
| 卡片可编辑 | 设置页 → 插件配置 → 通知 | 字段可编辑(非只读) |
放行设置命名空间(可选,但推荐)
DSH 官方 apiproxy 的 WEB_SETTINGS_NAMESPACES 是硬编码白名单,第三方命名空间默认只读。运行一次本仓库的 patch 脚本把 notifications 注入白名单:
node scripts/patch-apiproxy.mjs
之后设置卡片可读可写;不放行则卡片只读,DEFAULTS 生效。dsh 升级后需重跑此脚本(脚本幂等,从旧的 approval-alerter 命名空间升级也支持原地替换)。
Diagnostics 观察调试
插件加载后,在 DSH Web 页面打开 DevTools Console:
// 插件是否完整挂载
> __NOTIFICATIONS__.applied, __NOTIFICATIONS__.cardRegistered
true, true
// 绑定到了哪个 settings provider,sessions / connection 服务是否可用
> __NOTIFICATIONS__.binder, __NOTIFICATIONS__.sessions, __NOTIFICATIONS__.connAvailable
"settingsScope", true, true
// 宿主事件通道健康度(~30s 一次心跳;lastHeartbeatAt 不变表示 host feed 断了)
> __NOTIFICATIONS__.feedCounters, __NOTIFICATIONS__.lastHeartbeatAt
{ heartbeat: 4, "agent-error": 1, … }, 1756789012345
// 宿主投递的 job 状态全量词汇(可对照 sentinel / lifecycle 对哪些终态做判断)
> __NOTIFICATIONS__.hostStatuses, __NOTIFICATIONS__.hostStatusCounts
["failed","killed","completed","running",…], { completed: 8, failed: 2, … }
// 浏览器侧采样(diagnostics=true 时开启,最近 60 帧)
> __NOTIFICATIONS__.jobSamples[0]
{ ts, sessionId, sessionTitle, jobs: [{ jobId, status }], alerts: [] }
// 一键 demo 卡片 / demo 提示音(排查 UI 与音频是否能响)
> __NOTIFICATIONS__.demo("error") // 弹 error 变体卡片
> __NOTIFICATIONS__.demoSound(0.3) // 以指定音量播放完成单音
生效
插件集合变更必须重启 dsh web——仅刷新页面不会注册新包(官方 client-modules 文档明确:包元数据按名缓存且永不过期)。白名单 patch 之后也要重启。
验证
- 设置页「插件配置」下出现独立的「通知」卡片,字段可编辑
- 触发一个待审批:标签页标题出现
⚠ 1 待审批 —,Favicon 显示红底数字 1,右下角 Dock 出现 FAB 与列表,播放三连音,OS 通知弹出(首次需授权) - 点击 OS 通知或 Dock 行的「去处理」→ 窗口聚焦并打开对应会话
- 完成一个会话:右上角弹完成 toast + 完成单音
- DevTools 控制台可见
[notifications]前缀的日志;window.__NOTIFICATIONS__暴露 apply 分步记录与jobSamples采样环
卸载
dsh plugin --profile web remove dsh-web-notify
或删除本地 profile 的 cordis.patch.yml 插入行与 node_modules junction。
限制
- 提醒粒度是会话级(列表行只有 kind 状态);任务失败能到 job 级(含命令 label 与 exit detail),模型 / 工具异常走事件流原文(截 240 字符)
- 子代理可达性:子代理行位于检测管道内(与会话同一张 lineage 表),但被委派子代理的审批策略固定为
'never'、提问被拒,实际不会产生待审批/计划审批/提问条目——只可能出现父会话的审批;完成 / 失败 / 异常提醒照常覆盖子代理(见上文「子代理通知可达性」) - 提示音需要页面有过用户手势(浏览器音频策略);无手势时静默降级为视觉通道
- OS 通知权限在首次提醒后的下一次点击时请求;若 Windows 不弹,检查浏览器站点设置(127.0.0.1 通知权限)与 Windows「专注助手」
- 设置卡片走 settings scope;若宿主 apiproxy 未放行第三方命名空间,卡片只读,
DEFAULTS生效
项目结构
dsh-web-notify/
├── package.json # dsh.client.platform=web + inject + dsh.bundle.patch
├── cordis.patch.yml # 插件行 insert
├── src/
│ ├── index.ts # host 半:settings 命名空间 + systemPrompt 通告 + 事件流转发
│ └── client/ # 浏览器半(零 @deepseek-ai 运行时依赖)
│ ├── index.ts # 入口:apply/inject/mount + settings scope 热重配 + 卡片注册
│ ├── types.ts # 本地结构类型 + DEFAULTS
│ ├── locales.ts # zh/en 词典 + t()
│ ├── channels.ts # WebAudio 提示音 / 免打扰 / OS 通知 / jumpToSession
│ ├── badge.ts # 标题徽标 + Favicon 徽章(canvas)+ PWA 徽标
│ ├── stores.ts # toast / dock 两个 uSES store
│ ├── toast-ui.tsx # toast 卡片 + 堆栈
│ ├── toast-mount.tsx
│ ├── sentinel.ts # 待处理边沿哨兵(Dock 承担视觉,仅打脉冲)
│ ├── lifecycle.ts # ① 完成 + ③ 任务失败 + ② 连接监视
│ ├── dock.ts # Dock FAB + 面板 + mount + startDock
│ └── settings-card.tsx # 设置卡片
└── scripts/
├── build.mjs # esbuild 构建 → lib/{index.js,client.js}(loader 包装)
├── smoke.mjs # 运行时冒烟(9 场景,对生成产物跑)
├── patch-apiproxy.mjs # 把 notifications 注入 apiproxy 白名单
└── release.mjs # 发布流水线:build → smoke → pack → publish
License
MIT
Configuration
The settings card registers under Plugin settings → Notifications in the DSH Web Settings page (official settings.plugin.item slot). Changes take effect hot (120 ms debounce, no restart).
| Field | Type | Default | Description |
|---|---|---|---|
sound | boolean | true | Chime master switch |
volume | number 0–1 | 0.15 | Chime loudness |
badge | boolean | true | Tab-title badge + Favicon badge + PWA taskbar badge (one shared switch) |
toast | boolean | true | One-shot corner toasts (completion / failure / disconnect) |
notify | boolean | true | OS notify master switch; Notification permission is requested on the next user gesture after the first trigger |
dock | boolean | true | Notifications dock: corner FAB + expandable panel |
completion | boolean | true | ① Session / subagent completion alerts |
completionSound | boolean | true | Play a soft chime on completion |
completionNotify | boolean | true | Also send an OS notify on completion |
connection | boolean | true | ② Disconnect / reconnect alerts |
connectionAlertAfterMs | number ≥1000 | 10000 | Milliseconds of outage before alerting |
jobFailure | boolean | true | ③ Background job failure alerts |
failureNotify | boolean | false | Shared OS-notify switch for ③ failures + ④ runtime errors |
agentError | boolean | true | ④ Model / tool runtime errors: 429 quota, output limit, interruption, tool failure |
cooldownMs | number ≥0 | 5000 | Per-session per-kind dedupe window |
alertKinds | string[] | ["approval","plan-review","question"] | Kind whitelist for pending alerts |
quiet | object | {enabled:false, start:"23:00", end:"08:00"} | Quiet hours (only mutes; visual surfaces stay) |
soundResolved | boolean | false | Soft downstream chime when a pending approval resolves |
diagnostics | boolean | true | On-device observer (last 60 session snapshots incl. job status; the status set is always auto-collected) |
A few common recipes:
- Approvals only, no failures / disconnects:
completion=false,connection=false,jobFailure=false,agentError=false - Just the chime, hate popping cards:
toast=false,notify=false, keepsound + badge + dock - Late-night silence:
quiet.enabled=true,quiet.start=22:00,quiet.end=09:00— chimes fully off, visuals remain - Only PWA / taskbar, OS notify is too loud:
notify=false,badge=true,dock=true