@mzzsfy/dsh-turn-notify 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @mzzsfy/dsh-turn-notify
2
+
3
+ DeepSeek Harness 消息通知插件:AI 回合产生六类事件(完成/出错/被中断/等待审批/AI 提问/达到上限),按你的配置送达声音、系统弹窗、页内提示、webhook、IM 五种通道,多窗口只响一次。
4
+
5
+ 设计文档见 [docs/design/dsh-turn-notify-weixin.md](../../docs/design/dsh-turn-notify-weixin.md)。
6
+
7
+ ## 快速上手
8
+
9
+ ```sh
10
+ dsh plugin --profile web add @mzzsfy/dsh-turn-notify
11
+ ```
12
+
13
+ 发布前开发安装(拷贝进 store,行为与 registry 安装一致):
14
+
15
+ ```sh
16
+ dsh plugin --profile web add file:./packages/dsh-turn-notify
17
+ ```
18
+
19
+ 安装后打开 设置 > 消息通知:默认全部事件开启,声音、页内提示、系统弹窗即装即用(系统弹窗经一次浏览器授权后生效);webhook 与 IM 填配置后启用。声音通道受浏览器自动播放策略约束,首次点击页面后才会出声。想推到微信等聊天工具,见 [IM 推送](#im-推送);想推到自建服务,见 [webhook 推送](#webhook-推送)。
20
+
21
+ ## 六类通知事件
22
+
23
+ 插件监听 AI 会话,把回合结果归入六类,每类可独立开关、独立指定声音:
24
+
25
+ | 事件 | 含义 |
26
+ |------|------|
27
+ | 任务完成 | AI 回合正常结束 |
28
+ | 任务出错 | AI 回合以错误结束,或被你手动停止 |
29
+ | 被中断 | 会话关闭时未正常收尾的回合被补发中断结束(少见) |
30
+ | 等待审批 | AI 请求工具授权,等你点击允许;回合以等待审批结束也归此类 |
31
+ | AI 提问 | AI 调用 ask_user_question 向你提问 |
32
+ | 达到上限 | 回合因输出长度上限结束 |
33
+
34
+ 过滤条件(全部可在面板调整):
35
+
36
+ - **最短回合时长**:回合结束类通知(任务完成/出错/被中断/达到上限,以及以等待审批结束归入"等待审批"的回合)短于该时长(默认 5 秒)不送达,过滤掉连续快速的小回合;AI 提问与审批请求随事件即时送达,不受此过滤。
37
+ - **子代理会话不通知**:子代理会话(前台或后台委托)自身的事件不通知,只有主会话通知。
38
+ - **子代理相关回合静默**:仅影响"任务完成"类。两类回合不通知:回合结束时仍有后台子代理在跑(发起后台委托后主回合先行结束的等待期),以及子代理收尾后唤醒父会话继续工作的回合。整条委托链路只在最终回合响一次,避免刷屏。
39
+
40
+ ## 声音通知
41
+
42
+ 声音通知是本插件的核心能力,全部在浏览器内完成,不依赖系统音效与音频文件:
43
+
44
+ - **8 种内置音色**:上行琶音、铃铛、清脆双音、警报方波、低鸣、双音提示、嘀嗒、低音下滑。由 Web Audio 实时合成,零下载、零系统依赖。浏览器自动播放策略要求先与页面交互一次(任意点击),首次交互前声音静默,其余通道不受影响。
45
+ - **自定义音效上传**:支持 wav / mp3 / ogg,可一次多选,单文件上限 2MB、总库上限 10MB。上传前可逐个试听,确认后才落盘保存。支持重命名;同一文件重复上传自动识别,不产生重复文件。
46
+ - **按事件分类指定音效**:六类事件各自映射一款音效(内置或上传均可),例如"任务完成"用上行琶音、"等待审批"用双音提示。删除音效时引用它的映射自动清空、回落内置默认;仅当映射指向不存在的音效(如手改 yaml 产生死链)时,面板才显示失效项,试听时给出归因提示。
47
+ - **按分类静音**:总开关之外,可单独让某一类事件不出声,例如只对"AI 提问""等待审批"出声。被静音分类的系统弹窗与标题闪烁随之静默,页内提示卡片与会话高亮不受影响;页内提示音有独立的分类开关,不随此处变化。
48
+ - **音量**:按 5% 步进 0-100% 可调,本机记忆。
49
+ - **映射双作用域**:音效映射默认全局共用(存 settings.yaml,所有浏览器一致);打开"当前域名独立"后,映射改动只写本浏览器(按域名隔离),公司/家里各配各的,本地优先于全局。关闭开关即恢复全局,本地已存映射休眠保留,重新开启即恢复。
50
+ - **试听**:音效库与分类映射的每一项都能即时试听,试听的就是实际生效的音效。
51
+
52
+ 声音由抢到呈现权的窗口播放,多个窗口打开同一页面时只有一个窗口出声(见 [多窗口只响一次](#多窗口只响一次))。
53
+
54
+ ## 系统弹窗与页内提示
55
+
56
+ - **系统弹窗**:浏览器 Notification 授权后,窗口失焦时弹系统级通知。HTTP 非回环地址下浏览器不提供该能力,声音开启时自动降级为标题闪烁;HTTPS 下自动恢复。Windows 下还受系统通知设置与专注助手约束。
57
+ - **页内提示**:页面角落浮出的卡片提示(dsh-toast 栈式展示,6 秒),聚焦窗口内唯一常开的提醒形态,可关。
58
+ - **页内提示音**:聚焦场景的独立声音,总开关、按事件分类独立开关与音色映射全套自成一档,独立于提示音的分类配置。开启后页内提示弹出且通知声音未播时补一声提示——聚焦窗口内通知声音被聚焦静默压制,靠它保留听觉提醒;与通知声音互斥,同一通知至多一声;音量共用,音色在音效页的「页内提示音映射」单独指定。
59
+ - **降级标题闪烁**:声音通道开启(总开关开且该类未静音)的前提下,系统弹窗开关开启但未获得授权或不可用(如 HTTP 非回环地址、曾被拒绝)且窗口未聚焦时,标签页标题以 ⏳ 前缀闪烁替代弹窗;可在偏好中关闭该提示。
60
+ - **聚焦静默**:窗口聚焦时只保留页内提示,声音、系统弹窗与标题闪烁全部静默(页内提示音开启且该分类未在页内静音时仍有补位一声);你离开键盘满 5 分钟视为不在电脑前,聚焦也全通道提醒。
61
+
62
+ ## webhook 推送
63
+
64
+ 在面板填入 webhook URL 即启用。host 直发,标签页全关也送达;payload 为 Slack 兼容 JSON(text 字段承载通知文本),另附 event/category/status/session/workspace/durationMs/ts 结构化字段;超时 10 秒,不重试。凭据只写不回显,测试按钮返回真实投递结果。
65
+
66
+ ## IM 推送
67
+
68
+ 安装 [@xmanrui/dsh-im](https://www.npmjs.com/package/@xmanrui/dsh-im) 后自动启用,可推送到微信等九种渠道。投递目标的新建与平台测试在 dsh-im 设置页完成,本插件只做选择:从已绑 bot 的目标目录勾选即保存,支持绑定多个 bot。触发逻辑与 webhook 一致,fire-and-forget 不重试;bot 离线时该次通知弃置不补发。
69
+
70
+ ## 会话行高亮
71
+
72
+ 通知实际送达时,侧边栏会话列表中对应会话行以背景脉冲闪烁强调,六类各一色(完成绿/出错红/提问蓝/中断橙/审批黄/上限紫),点击该会话行即停止。开关在面板"偏好"页首项(独立开关,本机生效,各浏览器独立),默认开启。并行多会话时用于快速定位状态刚更新的会话:运行中的会话不闪烁(与 dsh 原生状态点分工——状态点表达运行中,闪烁表达已更新待查看),会话重新进入运行状态时闪烁自动让位;聚焦时正在查看的会话不闪烁,失焦期间本会话的通知照常闪烁,返回窗口即自动消失(回来即已读,焦点与页面可见双通道)。行定位按会话标题文本匹配,标题与侧边栏行同源(session-title 投影);会话行未渲染(列表折叠/懒加载未滚动到)时该次高亮静默跳过。
73
+
74
+ ## 多窗口只响一次
75
+
76
+ 同一地址(同源)打开的多个窗口共享一次呈现:各窗口以长轮询挂起在 host 的通知队列上(事件产生或配置变更即时返回,空闲期单次挂起至多数十秒,失败按指数退避重连),经 localStorage 协调,仅一个窗口抢先呈现——发声并弹出页内提示、系统弹窗、高亮会话行,其余窗口全部跳过,不止声音。通知在产生后的下一次唤醒即送达,通常亚秒级;不同浏览器或不同地址打开的窗口各自发声互不感知。
77
+
78
+ ## 设置面板指南
79
+
80
+ 设置 > 消息通知,五个分区:
81
+
82
+ | 分区 | 内容 |
83
+ |------|------|
84
+ | 通知 | webhook 地址、最短回合时长、子代理过滤、六类事件开关;存 settings.yaml,事件开关点击即存,其余点保存后生效 |
85
+ | 偏好 | 本机浏览器记忆的设置:会话高亮(独立开关)、声音总开关与分类静音、音量、系统弹窗与授权、页内提示开关、页内提示音(总开关 + 分类独立开关)、聚焦静默、降级标题闪烁 |
86
+ | 音效 | 音效上传/试听/重命名/删除,六类事件的音效映射与双作用域开关,页内提示音映射(本机,未配置沿用通知音效) |
87
+ | IM | 绑定 dsh-im bot、勾选投递目标(仅装了 dsh-im 时显示) |
88
+ | 测试 | 声音/页内/系统/webhook/IM 逐通道点火,回执即真实结果 |
89
+
90
+ ## settings.yaml 参考
91
+
92
+ 面板与 settings.yaml 读写同一命名空间,两边改动互通、保存即生效。webhookUrl 属凭据,面板只写不回显,yaml 直改仍可。
93
+
94
+ ```yaml
95
+ turn-notify:
96
+ webhookUrl: '' # webhook 目标 URL,留空禁用
97
+ minTurnDurationMs: 5000 # 最短回合时长(毫秒),回合结束类通知短于此不送达
98
+ rootsOnly: true # 子代理会话不通知
99
+ suppressSubagentWake: true # 子代理相关回合不通知:后台委托未收尾或收尾唤醒(仅任务完成类)
100
+ enabled: # 六类事件独立开关
101
+ completed: true
102
+ error: true
103
+ interrupted: true
104
+ approval: true
105
+ ask: true
106
+ max-tokens: true
107
+ soundMapping: # 每类事件音效映射,空为内置默认,值为内置音名或上传音效 id
108
+ completed: ''
109
+ imTargets: # dsh-im 投递目标,空数组禁用
110
+ - botId: wx_xxx
111
+ targetId: owner
112
+ ```
113
+
114
+ 音量、聚焦静默、分类静音等本机偏好存浏览器 localStorage,不经 yaml。
115
+
116
+ ## 工作原理
117
+
118
+ 按数据流向分五步:
119
+
120
+ 1. **事件监听**:host 观察 `session/event`(回合结束与 ask_user_question 工具调用)与 `approval/request` 审批请求(只观察,不拦截)。回合结束原因逐一映射:正常结束归任务完成,错误与手动停止归任务出错,审批拦截归等待审批,输出触顶归达到上限,会话收尾补发的中断归被中断。
121
+ 2. **分类与过滤**:事件归入六类,经子代理会话、子代理相关静默(仅任务完成类)、最短回合时长三道过滤,通过即产生一条通知。
122
+ 3. **host 直发通道**:webhook 与 IM 由 host 进程直接投递,不依赖浏览器;同时通知写入内存队列(环形 20 条,60 秒过期,不落盘)。
123
+ 4. **浏览器通道**:各窗口以长轮询挂起在该队列上(cursor 续传,事件与配置变更即时唤醒,空闲期单次挂起至多数十秒,失败指数退避重连),localStorage 协调保证同一通知只有一个窗口呈现,再按本机偏好分发到声音/系统弹窗/页内提示。
124
+ 5. **降级链**:声音通道开启而系统弹窗不可用(HTTP 非回环、未授权)→ 标题闪烁;页内提示依赖缺失 → 干净禁用该通道;localStorage 不可用 → 该窗口直接发声(可能与多窗口重复,诚实降级)。
125
+
126
+ ## 已知取舍
127
+
128
+ - 标签页全关时,仅 webhook 与 IM 送达(声音与弹窗的浏览器前提)。
129
+ - IM 依赖 dsh-im 的 bot 在线,离线时该次通知弃置不补发;成功回执仅代表平台受理。
130
+ - 不同浏览器或不同地址打开的窗口各自发声,互不去重。
131
+ - 系统弹窗在 HTTP 非回环下永久降级,HTTPS 化后自动恢复;Windows 下受系统通知设置与专注助手约束。
132
+ - 长轮询空闲期连接由代理或服务端挂起,经中间反代时需允许数十秒的挂起响应,否则退化为更频繁的重连。
133
+
134
+ ## 安全
135
+
136
+ - 配置写入类接口(config / mapping / upload / 音效改名与删除 / 测试)带同源守卫:Origin 与 Host 不符即 403,JSON 写入另校验 content-type,阻断跨站页面 drive-by 改写配置。
137
+ - webhookUrl 标记为 secret,任何接口不回传原文。
138
+ - 音效上传双重校验扩展名与真实音频内容;音效读取接口拒绝音频扩展名以外的文件。
139
+ - 已知边界:同源守卫不防 DNS rebinding(Origin 与 Host 相等即放行)。该暴露面属 host webserver 全部 /api 路由的存量问题,应在 host 层统一解决而非逐插件补丁。
140
+
141
+ ## 开发
142
+
143
+ ```sh
144
+ cd packages/dsh-turn-notify && npm test
145
+ ```
146
+
@@ -0,0 +1,13 @@
1
+ # The turn-notify bundle patch: inserts the turn notification plugin into the
2
+ # profile root as one entry. Applied by `dsh plugin add @mzzsfy/dsh-turn-notify`;
3
+ # the row id addresses this plugin's host half, whose `session/event` observer
4
+ # feeds the projection polled by the browser half via `/api/turn-notify/*`.
5
+ # No dsh-toast placeholder row here: the shared dependency's module-table mount
6
+ # has a single authoritative consumer (session-manager). This plugin consumes
7
+ # the toast client optionally (dynamic require with graceful channel disable),
8
+ # since two placeholder rows for the same package name compose two module
9
+ # sources when their loader base URLs differ, which is fatal at boot.
10
+
11
+ - insert:
12
+ - id: turn-notify
13
+ name: '@mzzsfy/dsh-turn-notify'
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@mzzsfy/dsh-turn-notify",
3
+ "description": "消息通知:六类回合事件送达声音、系统弹窗、页内提示、webhook 与 IM;内置八种合成音色,自定义音效,按事件分类指定;多窗口只响一次",
4
+ "version": "0.7.2",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./client": "./src/client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "dsh": {
13
+ "bundle": {
14
+ "patch": "./cordis.patch.yml"
15
+ },
16
+ "client": {
17
+ "platform": "web",
18
+ "external": [
19
+ "@mzzsfy/dsh-toast/client"
20
+ ]
21
+ }
22
+ },
23
+ "scripts": {
24
+ "test": "node --test \"test/*.test.mjs\"",
25
+ "npmPublish": "npm publish --access public --provenance=false --registry=https://registry.npmjs.org"
26
+ },
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/mzzsfy/dsh-plugin.git"
31
+ },
32
+ "engines": {
33
+ "node": ">=22"
34
+ },
35
+ "dependencies": {
36
+ "@mzzsfy/dsh-toast": "^0.1.2"
37
+ },
38
+ "peerDependencies": {
39
+ "@deepseek-ai/dsh-session-title": ">=0.1.2-rc.1",
40
+ "@deepseek-ai/dsh-settings": ">=0.1.2-alpha.2",
41
+ "@deepseek-ai/schemastery": ">=3.18.0",
42
+ "react": "^18.2.0"
43
+ },
44
+ "files": [
45
+ "src",
46
+ "test",
47
+ "cordis.patch.yml",
48
+ "README.md"
49
+ ]
50
+ }