@telosmaylx/dsh-session-notify 0.1.3 → 0.1.4
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 +463 -120
- package/lib/client.js +32 -4
- package/lib/index.js +19 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,78 +1,150 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# dsh-session-notify
|
|
2
4
|
|
|
5
|
+
**DSH(DeepSeek Harness)会话完成提醒插件 —— 每一轮结束,让完成状态主动找你,而不是你盯着屏幕等。**
|
|
6
|
+
|
|
3
7
|
[](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
|
|
4
8
|
[](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
|
|
5
|
-
[](./LICENSE)
|
|
10
|
+
[](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
|
|
11
|
+
[](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
|
|
12
|
+
[](https://github.com/TelosmaYLX/dsh-session-notify/pulls)
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
每轮对话结束时,把「已完成 / 出错 / 被阻塞 / 达到上限」连同用时、token 消耗写入会话日志,并推送浏览器系统通知与页内 toast。内置 5 种语言、可视化文案模板编辑器、自定义预设库,缓存命中率与生成速度取自官方投影,与状态栏同口径。
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 目录
|
|
21
|
+
|
|
22
|
+
- [功能特性](#功能特性)
|
|
23
|
+
- [环境要求](#环境要求)
|
|
24
|
+
- [安装](#安装)
|
|
25
|
+
- [卸载](#卸载)
|
|
26
|
+
- [快速开始](#快速开始)
|
|
27
|
+
- [通知行为](#通知行为)
|
|
28
|
+
- [触发条件](#触发条件)
|
|
29
|
+
- [推送正文从哪来](#推送正文从哪来)
|
|
30
|
+
- [通知示例](#通知示例)
|
|
31
|
+
- [通知权限](#通知权限)
|
|
32
|
+
- [配置](#配置)
|
|
33
|
+
- [设置面板](#设置面板)
|
|
34
|
+
- [文案模板与占位符](#文案模板与占位符)
|
|
35
|
+
- [预设系统](#预设系统)
|
|
36
|
+
- [宿主配置项](#宿主配置项)
|
|
37
|
+
- [工作原理](#工作原理)
|
|
38
|
+
- [项目结构](#项目结构)
|
|
39
|
+
- [开发与调试](#开发与调试)
|
|
40
|
+
- [常见问题](#常见问题)
|
|
41
|
+
- [更新日志](#更新日志)
|
|
42
|
+
- [贡献](#贡献)
|
|
43
|
+
- [相关链接](#相关链接)
|
|
44
|
+
- [许可证](#许可证)
|
|
45
|
+
|
|
46
|
+
---
|
|
12
47
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
48
|
+
## 功能特性
|
|
49
|
+
|
|
50
|
+
### 三通道提醒,一条不漏
|
|
51
|
+
|
|
52
|
+
| 通道 | 形式 | 说明 |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| 会话内系统消息 | 可折叠提示行 | 每轮结束把结束原因与用时、消耗作为插件来源的系统消息追加进会话日志,随 JSONL 落盘,恢复或回放会话后依然可见。 |
|
|
55
|
+
| 浏览器系统通知 | Web Notification | 原生弹窗。每次完成事件使用独立 `tag`(`dsh-session-notify:<timestamp>`),不与前一次互相替换,也不被折叠成一个分组条目;点击通知聚焦回窗口。 |
|
|
56
|
+
| 页内 toast | 右下角浮动弹窗 | 永远展示的保底通道:系统通知被平台静默、权限拒绝或环境不支持时仍有可见反馈。同屏最多 3 条(超出移除最旧),10 秒自动消失,点击关闭。 |
|
|
57
|
+
|
|
58
|
+
### 后台会话全覆盖
|
|
59
|
+
|
|
60
|
+
- 宿主为所有会话(含后台、未打开窗口的)维护「最近一条通知正文」的会话投影单元(key = `session-complete-notify`),推送正文跨会话一致,不依赖你恰好开着那个窗口。
|
|
61
|
+
- 客户端从会话列表快照观测所有会话的 `running` 位,`true → false` 边沿即触发推送,与官方 sidebar 提醒同策略(首次观测只记录基线,已在 idle 的会话不补发)。
|
|
18
62
|
|
|
19
|
-
|
|
63
|
+
### 可定制到每一句话
|
|
20
64
|
|
|
21
|
-
|
|
65
|
+
- **5 种语言**:简体中文、繁體中文、English、日本語、한국어 —— 通知文案、时长与用量措辞、设置面板界面全部随语言切换(切换即时重渲染)。
|
|
66
|
+
- **可视化模板编辑器**(Chip 胶囊编辑器):动态信息渲染为内联胶囊(占位符代码不露出),「+ 插入信息」在光标处插入(可插到文字中间),点击胶囊移除,每栏带实时预览(信息以示例值流入正文)。
|
|
67
|
+
- **预设系统**:内置「默认」预设作为基线;当前配置可另存为自定义预设(`localStorage` 持久化),支持自动编号的未命名预设(`未命名`、`未命名 2`…)、「来自:xxx · 已修改」来源指示、删除预设。
|
|
68
|
+
- **推送标题模板**:`{title}` 引用会话标题,留空则直接使用会话标题。
|
|
22
69
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
70
|
+
### 与官方口径同源
|
|
71
|
+
|
|
72
|
+
- **缓存命中率**取自官方 `tokenUsage` 投影:缓存读 /(未缓存输入 + 缓存读 + 缓存写)。
|
|
73
|
+
- **生成速度**取自官方 `sessionStats` 投影:输出 token ÷ 解码耗时。
|
|
74
|
+
- 两者与 dsh-web-ui 状态栏完全同口径,不含排队、准备、工具时间;投影不可用或数据未就绪时自动退回本地用量聚合估算。
|
|
75
|
+
|
|
76
|
+
> [!NOTE]
|
|
77
|
+
> 缓存命中率与速度只在自定义模板中通过 `{cache}`、`{tps}` 占位符插入时才显示。使用内置默认文案时,正文只含用时与消耗。
|
|
78
|
+
|
|
79
|
+
### 工程质量
|
|
80
|
+
|
|
81
|
+
- **只响应实时事件**:resume、replay 不重放旧通知,加载会话不刷屏。
|
|
82
|
+
- **自免疫循环**:插件追加的消息类型(`user/message`)与自身监听目标(`turn/*`)不相交。
|
|
83
|
+
- **零外部依赖**:宿主平面零裸 import,UserMessage 按 `dsh-llm` 的 `createUserMessage` 契约手工构造;纯逻辑层(`lib/core.js`)零依赖,可独立测试。
|
|
84
|
+
- **Cordis effect 纪律**:重试定时器包装在 `ctx.effect()` 中并返回 `clearTimeout` disposer,注册随 fiber 卸载自动撤销,HMR 热重载安全。
|
|
85
|
+
- **安装即挂载**:声明官方 `dsh.bundle` manifest,`dsh plugin add` 一条命令装完即用,无需手写 patch。
|
|
28
86
|
|
|
29
87
|
---
|
|
30
88
|
|
|
31
|
-
##
|
|
89
|
+
## 环境要求
|
|
32
90
|
|
|
33
|
-
|
|
|
91
|
+
| 依赖 | 要求 |
|
|
34
92
|
| --- | --- |
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
| 会话标题 | `{title}` 标签:推送标题与消息正文都可插入会话标题(官方 `title` 投影) |
|
|
40
|
-
| 设置面板 | 预设库(默认 + 自定义预设自动管理 / 命名预设)、标题模板、5 字段消息模板、5 语言、标签插入(光标处)、实时预览 |
|
|
41
|
-
| 多语言 | 消息文案(标签/时长/用量/默认文案)+ 面板 UI 全量 i18n:`zh` `zh-tw` `en` `ja` `ko` |
|
|
42
|
-
| 子代理过滤 | 默认跳过子代理会话(子代理由父会话编排) |
|
|
43
|
-
| 投影同步 | 注册 `session-complete-notify` 投影单元:每个会话的最新通知全文推送到客户端,**后台会话同样有完整正文** |
|
|
93
|
+
| DSH(DeepSeek Harness) | Web profile 部署。官方 base bundle 默认包含 `@deepseek-ai/dsh-settings`(设置命名空间)与会话投影,无需额外配置 |
|
|
94
|
+
| cordis | `>=4.0.0-rc <5`(peer dependency,由宿主提供) |
|
|
95
|
+
| Node.js | `>=22`(宿主侧) |
|
|
96
|
+
| 浏览器 | 支持 Web Notification 则有系统通知;不支持、权限拒绝或被静默时由 toast 兜底 |
|
|
44
97
|
|
|
45
98
|
---
|
|
46
99
|
|
|
47
100
|
## 安装
|
|
48
101
|
|
|
49
|
-
|
|
102
|
+
> [!WARNING]
|
|
103
|
+
> 裸 `npm install` 只会把包装进依赖树,**不会注册插件** —— 这是 DSH 官方设计(`npm install only adds the dependency; it does not register the plugin`)。自动挂载的唯一官方途径是 `dsh plugin add`:它读取包内 `dsh.bundle` manifest(本插件自 0.1.3 起声明,指向仓库根 `cordis.patch.yml`)并自动应用。
|
|
104
|
+
|
|
105
|
+
### 方式一:dsh plugin add(推荐)
|
|
106
|
+
|
|
107
|
+
安装包的同时自动应用 `cordis.patch.yml`,把插件挂载进 profile 装配(host 事件订阅 + client 启动图注入)。
|
|
50
108
|
|
|
51
109
|
```bash
|
|
52
110
|
dsh plugin --profile web add @telosmaylx/dsh-session-notify
|
|
53
111
|
```
|
|
54
112
|
|
|
55
|
-
|
|
113
|
+
### 方式二:从 GitHub 仓库安装
|
|
56
114
|
|
|
57
|
-
|
|
115
|
+
```bash
|
|
116
|
+
dsh plugin add github:TelosmaYLX/dsh-session-notify
|
|
117
|
+
```
|
|
58
118
|
|
|
59
|
-
|
|
119
|
+
也可以在 DSH Web GUI 会话内执行:
|
|
60
120
|
|
|
61
121
|
```bash
|
|
62
|
-
# 在 Web GUI 会话中执行
|
|
63
122
|
dev_install_package github=TelosmaYLX/dsh-session-notify
|
|
64
|
-
# 或
|
|
65
|
-
dsh plugin --profile web add git+https://github.com/TelosmaYLX/dsh-session-notify.git
|
|
66
123
|
```
|
|
67
124
|
|
|
68
|
-
###
|
|
125
|
+
### 方式三:本地目录热装配(开发用)
|
|
126
|
+
|
|
127
|
+
把路径换成你的克隆目录,在 DSH Web GUI 会话内执行:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
dev_install_package dir=/你的/克隆目录/dsh-session-notify
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 方式四:npm 包手动安装
|
|
134
|
+
|
|
135
|
+
先打包:
|
|
69
136
|
|
|
70
137
|
```bash
|
|
71
|
-
|
|
72
|
-
dev_install_package dir=</你的插件目录>
|
|
138
|
+
npm pack @telosmaylx/dsh-session-notify
|
|
73
139
|
```
|
|
74
140
|
|
|
75
|
-
|
|
141
|
+
解压后指定目录安装(在 DSH Web GUI 会话内执行):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
dev_install_package dir=/解压/目录/package
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 方式五:手动 cordis patch(不依赖安装器)
|
|
76
148
|
|
|
77
149
|
在 `~/.dsh/profiles/web/cordis.patch.yml` 追加:
|
|
78
150
|
|
|
@@ -83,120 +155,391 @@ dev_install_package dir=</你的插件目录>
|
|
|
83
155
|
config: {}
|
|
84
156
|
```
|
|
85
157
|
|
|
86
|
-
>
|
|
158
|
+
> [!IMPORTANT]
|
|
159
|
+
> 无论用哪种方式,装完都需要**刷新一次浏览器页面** —— 客户端 bundle 通过 `__DSH_BOOT__` 启动图注入。
|
|
87
160
|
|
|
88
|
-
|
|
161
|
+
## 卸载
|
|
89
162
|
|
|
90
|
-
|
|
163
|
+
一条命令移除插件及其挂载(自动从 `cordis.patch.yml` 移除 insert 条目):
|
|
91
164
|
|
|
92
|
-
|
|
165
|
+
```bash
|
|
166
|
+
dsh plugin --profile web remove @telosmaylx/dsh-session-notify
|
|
167
|
+
```
|
|
93
168
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
3. 任一会话(顶部会话)完成时:
|
|
97
|
-
- 会话尾部出现系统消息(文案 = 你的模板)
|
|
98
|
-
- 右下角 toast 弹出(标题 = 你的标题模板 + 会话标题)
|
|
99
|
-
- 授权后系统通知同步弹出
|
|
169
|
+
> [!NOTE]
|
|
170
|
+
> 手动安装(方式四/五)的用户,需同步从 `~/.dsh/profiles/web/cordis.patch.yml` 删除对应 insert 条目,再刷新页面。
|
|
100
171
|
|
|
101
|
-
###
|
|
172
|
+
### 卸载时自动清理的内容
|
|
102
173
|
|
|
103
|
-
|
|
174
|
+
插件实现了完整的生命周期收尾(Cordis effect 纪律),卸载/禁用/HMR 热重载时:
|
|
175
|
+
|
|
176
|
+
| 平面 | 自动释放的资源 |
|
|
104
177
|
| --- | --- |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
| **修改 / 删除** | 选中自定义预设时出现:修改 = 当前表单写回该预设;删除 = 移除 |
|
|
108
|
-
| **语言** | 简体中文 / 繁體中文 / English / 日本語 / 한국어 —— 面板与消息文案即时切换 |
|
|
109
|
-
| **推送标题** | 占位符提示「留空则使用会话标题」;`{title}` 标签经「+ 会话标题」插入,如 `【完成】{title}` |
|
|
110
|
-
| **模板 × 5** | 完成 / 出错 / 中止 / 阻塞 / 输出上限:文字 + 内联标签胶囊(点击 × 删除、+ 光标处插入、全选可删);空态直接展示默认文案(始终可编辑) |
|
|
111
|
-
| 预览 | 纯文本最终效果(示例值直出,无标签样式) |
|
|
178
|
+
| host | `session/event` 事件订阅、settings 命名空间、会话投影单元、设置注册重试定时器(`ctx.effect` 包装);置卸载标志抑制已调度的微任务追加 |
|
|
179
|
+
| client | 会话列表订阅、完成推送正文的轮询定时器、`window.__dsch_notify_debug` 调试钩子(按引用删除,防闭包泄漏)、页内 toast 容器 DOM |
|
|
112
180
|
|
|
113
|
-
###
|
|
181
|
+
### 卸载后保留的数据
|
|
114
182
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
| `{duration}` | 用时 | 本轮耗时(语言化格式) | 1 分 23 秒 / 1m23s |
|
|
119
|
-
| `{usage}` | 消耗 | 输入 / 输出 token | 1,600 输入 / 3,560 输出 |
|
|
120
|
-
| `{error}` | 错误 | 错误详情;无错误显示 `none` | 连接超时 |
|
|
121
|
-
| `{cache}` | 缓存命中 | 缓存命中率(官方 tokenUsage 四桶口径) | 96.5% |
|
|
122
|
-
| `{tps}` | 速度 TPS | 输出 token ÷ 解码耗时(官方 sessionStats 口径) | 115 tok/s |
|
|
123
|
-
|
|
124
|
-
### 预设模型(自动生命周期)
|
|
125
|
-
|
|
126
|
-
- **默认预设**:空模板 + 空标题(= 内置默认文案)
|
|
127
|
-
- **保存 = 自动落入一个预设**:
|
|
128
|
-
- 来自自定义预设 → 更新它(修改即自动,无需手动按钮)
|
|
129
|
-
- 来自默认/空白 → 新建 **未命名预设**;已存在则递增编号(未命名预设 2 / 3 …),从不覆盖旧的
|
|
130
|
-
- 从未命名预设本身载入再改 → 更新该未命名预设
|
|
131
|
-
- 预设库持久化在浏览器 localStorage(`dsh-scn-custom-presets`)
|
|
183
|
+
- **设置配置**(语言、文案模板)留在 settings 文档,重装后自动恢复;
|
|
184
|
+
- **自定义预设**存于浏览器 `localStorage`(`dsh-scn-custom-presets`),重装后仍在;
|
|
185
|
+
- 历史会话中已追加的系统消息与 JSONL 日志**不会**被回滚(它们是会话数据的一部分,与官方侧边栏提示同语义)。
|
|
132
186
|
|
|
133
187
|
---
|
|
134
188
|
|
|
135
|
-
##
|
|
189
|
+
## 快速开始
|
|
190
|
+
|
|
191
|
+
1. 按上面任一方式安装并刷新页面。
|
|
192
|
+
2. 发起任意一轮对话,等它结束 —— 右下角弹出 toast、浏览器弹系统通知、会话日志里出现可折叠的系统提示行。
|
|
193
|
+
3. 首次收到完成事件时,浏览器会请求通知权限(每页只问一次),允许后后续完成都有系统通知。
|
|
194
|
+
4. 打开 **设置 → 插件 → 会话完成提醒**,切换语言、编辑文案模板、另存预设。保存后点「点击刷新」让宿主与客户端两侧重新读取,新配置即生效。
|
|
136
195
|
|
|
137
|
-
|
|
196
|
+
刚装好时,会话日志里会出现这样一行可折叠提示:
|
|
138
197
|
|
|
198
|
+
```text
|
|
199
|
+
会话已完成(用时 1 分 12 秒,消耗 1,240 输入 / 3,560 输出)。
|
|
139
200
|
```
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 通知行为
|
|
205
|
+
|
|
206
|
+
### 触发条件
|
|
207
|
+
|
|
208
|
+
每轮对话结束(`turn/end`)时按结束原因判断,命中白名单即提醒:
|
|
209
|
+
|
|
210
|
+
| 结束原因 | 含义 | 默认 |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `completed` | 会话正常完成 | 提醒 |
|
|
213
|
+
| `aborted` | 会话中止 | 提醒 |
|
|
214
|
+
| `blocked` | 会话被阻塞 | 提醒 |
|
|
215
|
+
| `error` | 会话出错(附错误详情,超长截断) | 提醒 |
|
|
216
|
+
| `max-tokens` | 达到输出 token 上限 | 提醒 |
|
|
217
|
+
| `interrupted` | 中断(崩溃恢复后由持久化后端补写的孤儿轮次关闭标记) | 不提醒(可配置加入) |
|
|
218
|
+
|
|
219
|
+
**子代理会话默认跳过**(`header.origin === 'subagent'` 或 `delegationDepth > 0`)—— 子代理由父会话编排,逐轮提醒是噪音;可在宿主配置关闭跳过。
|
|
220
|
+
|
|
221
|
+
### 推送正文从哪来
|
|
222
|
+
|
|
223
|
+
客户端在会话列表观测到 `running: true → false` 边沿时推送,正文按以下优先级获取(最长轮询 6 秒,400ms 间隔):
|
|
224
|
+
|
|
225
|
+
1. **宿主投影**(key = `session-complete-notify`)—— 每个会话都有,后台会话同样拿到全文;
|
|
226
|
+
2. **会话事件窗口里的 notice 节点**(`kind=context` + `form=notice`)—— 正在查看的会话,落盘后立即可用;
|
|
227
|
+
3. **降级** —— 「详情见会话内系统消息」+ 工作区信息(`cwd` 最后一段)。
|
|
228
|
+
|
|
229
|
+
### 通知示例
|
|
230
|
+
|
|
231
|
+
以下均由 `lib/core.js` 的 `buildNotice` 实际生成。
|
|
232
|
+
|
|
233
|
+
简体中文默认文案:
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
会话已完成(用时 3 分 25 秒,消耗 12,400 输入 / 35,600 输出)。
|
|
151
237
|
```
|
|
152
238
|
|
|
153
|
-
|
|
239
|
+
出错时附带错误详情(单行化,超过 40 字符截断):
|
|
154
240
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
| 双通道解耦 | 系统消息(宿主,持久)与 push(客户端,即时)各自独立;正文以投影通知全文为准保证跨会话一致 |
|
|
161
|
-
| 标签即开关 | 模板里插了标签就显示、不插就不显示(无独立开关) |
|
|
162
|
-
| 注册竞态兜底 | 设置命名空间注册遇 duplicate(热重载下的旧 fiber 注销竞态)自动退避重试 8 次并落盘日志 |
|
|
241
|
+
```text
|
|
242
|
+
会话出错:connection timeout(用时 12 秒)。
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
English 默认文案:
|
|
163
246
|
|
|
164
|
-
|
|
247
|
+
```text
|
|
248
|
+
Session completed (took 3m25s, used 12,400 in / 35,600 out).
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
自定义模板(在设置面板编辑,本例用到全部信息位):
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
{title} 干完了!用时 {duration},消耗 {usage},缓存命中 {cache},速度 {tps}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
渲染结果:
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
重构登录模块 干完了!用时 3 分 25 秒,消耗 103,600 输入 / 35,600 输出,缓存命中 96.5%,速度 92 tok/s
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
五种语言的同一事件:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
会话已完成(用时 3 分 25 秒,消耗 1,240 输入 / 3,560 输出)。
|
|
267
|
+
會話已完成(用時 3 分 25 秒,消耗 1,240 輸入 / 3,560 輸出)。
|
|
268
|
+
Session completed (took 3m25s, used 1,240 in / 3,560 out).
|
|
269
|
+
セッション完了(所要 3 分 25 秒、消費 1,240 入力 / 3,560 出力)。
|
|
270
|
+
세션 완료(소요 3분 25초, 소모 1,240 입력 / 3,560 출력)。
|
|
271
|
+
```
|
|
165
272
|
|
|
166
|
-
|
|
167
|
-
|
|
273
|
+
### 通知权限
|
|
274
|
+
|
|
275
|
+
| 权限状态 | 行为 |
|
|
276
|
+
| --- | --- |
|
|
277
|
+
| `default`(未决定) | 每页加载只发起一次授权请求,后续完成事件不再触发询问 |
|
|
278
|
+
| `granted` | 每次完成都发系统通知(独立 tag,互不覆盖) |
|
|
279
|
+
| `denied` 或环境不支持 | 仅 toast(永远展示,保底可见) |
|
|
168
280
|
|
|
169
281
|
---
|
|
170
282
|
|
|
171
|
-
##
|
|
283
|
+
## 配置
|
|
284
|
+
|
|
285
|
+
绝大多数配置在 **DSH Web UI → 设置 → 插件 → 会话完成提醒** 面板完成(保存后点「点击刷新」生效)。仅「触发原因白名单」与「跳过子代理」两项在宿主 `cordis.patch.yml` 的 `config` 中配置。
|
|
286
|
+
|
|
287
|
+
### 设置面板
|
|
288
|
+
|
|
289
|
+
面板在官方「设置 → 插件」面板中注册(`settings.plugin.item` keyed slot,key = `session-complete-notify`),样式逐值复刻原生插件卡片(12px 圆角、展开收起、旋转 chevron、footer 状态位 + 弃置 ghost + 主色保存按钮):
|
|
172
290
|
|
|
173
|
-
|
|
|
291
|
+
| 区域 | 内容 |
|
|
174
292
|
| --- | --- |
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
293
|
+
| 预设 | 下拉选择内置或自定义预设;「新增」把当前配置另存为自定义预设;当前预设可「删除」 |
|
|
294
|
+
| 语言 | 5 种语言单选,切换即时重渲染整个面板 |
|
|
295
|
+
| 推送标题 | `{title}` 引用会话标题,留空则直接用会话标题 |
|
|
296
|
+
| 模板 × 5 | 每条结束原因(完成、出错、中止、阻塞、输出上限)独立一个 Chip 编辑器:文字 + 内联信息胶囊,光标处插入、点击移除、实时预览 |
|
|
297
|
+
| 跳过子代理会话 | 复选框(保存时一并写入设置文档) |
|
|
298
|
+
| 保存 | 写入宿主设置文档(`language` / `templates` / `titleTemplate`);保存后显示「点击刷新」链接 |
|
|
299
|
+
|
|
300
|
+
> [!NOTE]
|
|
301
|
+
> 面板中「跳过子代理会话」保存的是设置文档里的布尔值;宿主 `cordis.patch.yml` 的 `config.skipSubagents` 是其启动默认值,两者任一为真即跳过。
|
|
302
|
+
|
|
303
|
+
### 文案模板与占位符
|
|
304
|
+
|
|
305
|
+
每条结束原因独立一个模板输入框,**标签即开关** —— 在模板里插入对应信息标签,该项数据才会显示:
|
|
306
|
+
|
|
307
|
+
| 占位符 | 含义 | 示例值 |
|
|
308
|
+
| --- | --- | --- |
|
|
309
|
+
| `{title}` | 会话标题(推送标题模板也可用) | `重构登录模块` |
|
|
310
|
+
| `{duration}` | 本轮用时(`turn/start` 起表 → `turn/end` 结束) | `3 分 25 秒` / `3m25s` |
|
|
311
|
+
| `{usage}` | token 消耗(输入 = 未缓存 + 缓存读 + 缓存写) | `1,240 输入 / 3,560 输出` |
|
|
312
|
+
| `{error}` | 错误信息(无错误时显示 `none`;单行化,80 字符截断) | `connection timeout` |
|
|
313
|
+
| `{cache}` | 缓存命中率(官方投影口径,无数据为空) | `96.5%` |
|
|
314
|
+
| `{tps}` | 生成速度(官方投影口径,无数据为空) | `92 tok/s` |
|
|
315
|
+
| `{label}` | 已废弃 —— 渲染时自动剥除,旧模板仍兼容(插入菜单已移除该选项) | — |
|
|
316
|
+
|
|
317
|
+
模板留空即使用内置默认文案(自动带用时与消耗)。折叠行 `summary` 与正文同源(渲染结果截断至 120 字符)—— 只看折叠行的用户也能看到真实标题与用时、消耗。
|
|
318
|
+
|
|
319
|
+
### 预设系统
|
|
320
|
+
|
|
321
|
+
- **内置预设**:仅「默认」,作为基线。
|
|
322
|
+
- **自定义预设**:保存在 `localStorage`(key = `dsh-scn-custom-presets`):
|
|
323
|
+
- 「新增」命名后保存为自定义预设;保存后可「修改」自动同步、「删除」移除;
|
|
324
|
+
- **自动编号的未命名预设**:从「默认 / 空白」直接保存时,自动生成 `未命名`、`未命名 2`、`未命名 3`…(编号取当前最大值 + 1);
|
|
325
|
+
- 表单显示「来自:xxx · 已修改」来源指示(来自预设但内容已改动时)。
|
|
326
|
+
- **保存即同步**:保存时若表单来源是自定义预设则更新该预设,否则新建或继续编号未命名预设。
|
|
327
|
+
|
|
328
|
+
### 宿主配置项
|
|
329
|
+
|
|
330
|
+
```yaml
|
|
331
|
+
- insert:
|
|
332
|
+
- id: dsh-session-notify
|
|
333
|
+
name: '@telosmaylx/dsh-session-notify'
|
|
334
|
+
config:
|
|
335
|
+
reasons: [completed, aborted, blocked, error, max-tokens]
|
|
336
|
+
skipSubagents: true
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
340
|
+
| --- | --- | --- | --- |
|
|
341
|
+
| `reasons` | `string[]` | `[completed, aborted, blocked, error, max-tokens]` | 触发提醒的 `turn/end` 原因白名单 |
|
|
342
|
+
| `skipSubagents` | `boolean` | `true` | 跳过子代理会话(`origin=subagent` 或 `delegationDepth>0`) |
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 工作原理
|
|
347
|
+
|
|
348
|
+
插件分**宿主平面**(Node)与**客户端平面**(浏览器),中间靠会话日志(JSONL)与官方会话投影衔接:
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
┌─────────────────── 宿主平面(lib/index.js,Node)──────────────────┐
|
|
352
|
+
│ │
|
|
353
|
+
│ session/event 火线 │
|
|
354
|
+
│ ├─ turn/start → tracker 起表(key: sessionId:turn) │
|
|
355
|
+
│ ├─ assistant/message → 累加该轮 token 用量 │
|
|
356
|
+
│ └─ turn/end → reason.kind ∈ reasons ? │
|
|
357
|
+
│ ├─ 子代理会话?跳过 │
|
|
358
|
+
│ ├─ 读官方投影:cache / tps / title │
|
|
359
|
+
│ ├─ 按语言+模板构建通知(summary ≤120 字) │
|
|
360
|
+
│ └─ queueMicrotask 追加系统消息 │
|
|
361
|
+
│ (避开 append 重入窗口) │
|
|
362
|
+
│ │
|
|
363
|
+
│ settings.register → 官方「设置 → 插件」命名空间(失败退避重试) │
|
|
364
|
+
│ sessionProjections → 注册投影单元(key=session-complete-notify) │
|
|
365
|
+
└──────────────────────────────┬──────────────────────────────────────┘
|
|
366
|
+
│ user/message (source: plugin, form: notice)
|
|
367
|
+
▼ JSONL 持久化 + 投影推送
|
|
368
|
+
┌─────────────────── 客户端平面(lib/client.js,浏览器)──────────────┐
|
|
369
|
+
│ │
|
|
370
|
+
│ 会话列表订阅:running true → false 边沿 → pushCompletion │
|
|
371
|
+
│ ├─ 取正文:投影 → 事件窗口 notice → 降级(轮询 ≤6s) │
|
|
372
|
+
│ ├─ Web Notification(独立 tag,点击聚焦) │
|
|
373
|
+
│ └─ 页内 toast(永远展示,≤3 条,10s 自动消失) │
|
|
374
|
+
│ │
|
|
375
|
+
│ slots.inject('settings.plugin.item') → 设置卡片(预设/语言/模板) │
|
|
376
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 关键设计决策
|
|
380
|
+
|
|
381
|
+
- **不重放**:只处理实时事件,resume、replay 不会补发历史通知。
|
|
382
|
+
- **无自我循环**:插件追加 `user/message`,自身只监听 `turn/*`,事件类型不相交。
|
|
383
|
+
- **零外部 import**:插件从仓库目录以 realpath 加载,`@deepseek-ai/*` 无法裸解析 —— 宿主平面用 `createRequire` 锚定 profile 共享依赖枢纽(`.dsh/profiles/node_modules`)取 `schemastery`(设置 schema)与 `zod`(投影 schema);UserMessage 按 `dsh-llm` 契约手工构造(`id = crypto.randomUUID()`,deep-freeze 由 `session.append` 的 adopt 快照阶段完成)。
|
|
384
|
+
- **append 重入规避**:`session/event` 观察者回调运行在 `turn/end` 那次 append 的发布边界之内(dsh-session 在 dispatch 前置 `entry.appending`、`finally` 复位),同步 append 会被拒绝 —— 因此推迟到 `queueMicrotask`(微任务在本次同步栈含 `finally` 复位之后才执行)。
|
|
385
|
+
- **effect 纪律**:设置注册的退避重试定时器包装在 `ctx.effect()` 中并返回 `clearTimeout` disposer —— 插件在重试窗口内被卸载或热重载时定时器随 fiber 拆除,不会对已释放的 ctx 触发注册(极老环境无 `ctx.effect` API 时退化为裸定时器 + ctx 已拆除兜底捕获)。
|
|
386
|
+
- **HMR 安全**:`core.js` 导入带 `?v=1` 缓存破坏(HMR 重载按 URL 键控);设置注册遇到热重载竞态(duplicate)时自动退避重试(最多 8 次,间隔 `400ms × attempts`)。
|
|
387
|
+
- **投影注册双轨**:优先 `ctx.root.get('sessionProjections')`(最靠近宿主根的一份),拿不到时回退注入实例;只注册进注入实例时客户端可能读不到投影单元,推送正文走降级路径 —— 属尽力而为,不影响会话内系统消息。
|
|
180
388
|
|
|
181
389
|
---
|
|
182
390
|
|
|
183
391
|
## 项目结构
|
|
184
392
|
|
|
393
|
+
```text
|
|
394
|
+
dsh-session-notify/
|
|
395
|
+
├── lib/
|
|
396
|
+
│ ├── index.js # 宿主平面(Node):session/event 订阅 → 系统消息落盘;
|
|
397
|
+
│ │ # settings 命名空间注册(schemastery schema,退避重试);
|
|
398
|
+
│ │ # sessionProjections 投影单元(后台会话推送正文)
|
|
399
|
+
│ ├── core.js # 纯逻辑层(零依赖,可独立测试):轮次计时与用量聚合、
|
|
400
|
+
│ │ # 5 语言文案表、时长/用量/缓存/速度格式化、
|
|
401
|
+
│ │ # 模板渲染({title}{duration}{usage}{error}{cache}{tps})
|
|
402
|
+
│ └── client.js # 浏览器平面:完成推送(系统通知 + toast)、
|
|
403
|
+
│ # 设置卡片(Chip 模板编辑器 + 预设系统 + 实时预览)
|
|
404
|
+
├── scripts/
|
|
405
|
+
│ ├── build.sh # 零构建:仅 node --check 语法校验
|
|
406
|
+
│ ├── verify-notice.mjs # 校验会话日志落盘证据(zstd 多帧逐帧解压)
|
|
407
|
+
│ ├── probe-client.mjs # 探针:客户端装配
|
|
408
|
+
│ ├── probe-client-e2e.mjs # 探针:客户端端到端
|
|
409
|
+
│ ├── probe-card-render.mjs # 探针:设置卡片渲染
|
|
410
|
+
│ ├── probe-settings-card.mjs # 探针:设置面板卡片
|
|
411
|
+
│ ├── probe-settings-check.mjs# 探针:设置面板检查
|
|
412
|
+
│ └── probe-diag-settings.mjs # 探针:settings 诊断
|
|
413
|
+
├── cordis.patch.yml # dsh.bundle manifest —— dsh plugin add 自动挂载的凭证
|
|
414
|
+
├── package.json # dsh.bundle(patch)+ dsh.client(web 注入)双 manifest;
|
|
415
|
+
│ # exports: "." / "./client" / "./core"
|
|
416
|
+
├── LICENSE # MIT
|
|
417
|
+
└── README.md # 本文档
|
|
185
418
|
```
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 开发与调试
|
|
423
|
+
|
|
424
|
+
语法校验(零构建,`prepublishOnly` 同款检查):
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
npm run build
|
|
191
428
|
```
|
|
192
429
|
|
|
193
|
-
|
|
430
|
+
发布(发布前自动执行 `prepublishOnly` 语法校验):
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
npm publish --registry=https://registry.npmjs.org --access public
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
离线校验:解出会话日志中所有 plugin-source 事件与 `turn/end` 尾部序列(不传路径则自动选 `~/.dsh/sessions` 下最新会话):
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
node scripts/verify-notice.mjs <session.jsonl.zstd>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 调试入口
|
|
443
|
+
|
|
444
|
+
| 入口 | 内容 |
|
|
445
|
+
| --- | --- |
|
|
446
|
+
| `~/.dsh/session-complete-notify.log` | 宿主诊断日志:设置注册、重试与失败、投影注册、追加失败堆栈 |
|
|
447
|
+
| 浏览器 console `[dsh-session-notify-client]` | 客户端日志:权限状态、通知展示、设置保存 |
|
|
448
|
+
| `window.__dsch_notify_debug.readNotice(id)` | 手动读取指定会话的最新通知正文 |
|
|
449
|
+
| `window.__dsch_notify_debug.snapshotDebug(id)` | 会话尾部节点类型 + notice 数量 + 最近正文(前 200 字) |
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 常见问题
|
|
454
|
+
|
|
455
|
+
<details>
|
|
456
|
+
<summary><b>npm install 之后为什么不自动挂载?</b></summary>
|
|
457
|
+
|
|
458
|
+
这是 DSH 官方设计:`npm install` 只把包装进依赖树,不注册插件。自动挂载的唯一途径是 `dsh plugin add` —— 它读取包内 `dsh.bundle` manifest(本插件自 0.1.3 起声明)并自动应用 `cordis.patch.yml`。参见[安装](#安装)。
|
|
459
|
+
|
|
460
|
+
</details>
|
|
461
|
+
|
|
462
|
+
<details>
|
|
463
|
+
<summary><b>为什么「中断」(interrupted)不提醒?</b></summary>
|
|
464
|
+
|
|
465
|
+
`interrupted` 是崩溃恢复后由持久化后端补写的孤儿轮次关闭标记,用户视角的「完成」不包含它(否则恢复会话会刷一屏误报)。确有需要可在宿主配置的 `reasons` 中加入。
|
|
466
|
+
|
|
467
|
+
</details>
|
|
468
|
+
|
|
469
|
+
<details>
|
|
470
|
+
<summary><b>后台会话(没打开窗口的)也会推送吗?</b></summary>
|
|
471
|
+
|
|
472
|
+
会。客户端从会话列表快照观测所有会话的 `running` 边沿;正文优先取宿主投影 —— 宿主为所有会话(含后台)维护投影单元,因此推送正文跨会话一致。投影不可用时降级为事件窗口或工作区信息。
|
|
473
|
+
|
|
474
|
+
</details>
|
|
475
|
+
|
|
476
|
+
<details>
|
|
477
|
+
<summary><b>保存设置后为什么提示刷新页面?</b></summary>
|
|
478
|
+
|
|
479
|
+
宿主在注册命名空间时读取一次设置,客户端 bundle 在页面加载时装配。保存后点「点击刷新」让两侧重新读取,新语言、模板即生效。
|
|
480
|
+
|
|
481
|
+
</details>
|
|
482
|
+
|
|
483
|
+
<details>
|
|
484
|
+
<summary><b>缓存命中率、速度数据从哪来?为什么有时是空的?</b></summary>
|
|
485
|
+
|
|
486
|
+
来自官方 `sessionProjections`(`tokenUsage`、`sessionStats`),与 dsh-web-ui 状态栏同口径。宿主读取投影快照失败或数据尚未就绪时,退回本地用量聚合估算,仍无数据则该项留空(标签插了也不显示)。另外,这两项只在自定义模板中通过 `{cache}`、`{tps}` 插入时才出现,默认文案不含。
|
|
487
|
+
|
|
488
|
+
</details>
|
|
489
|
+
|
|
490
|
+
<details>
|
|
491
|
+
<summary><b>通知正文里的错误信息太长、有换行怎么办?</b></summary>
|
|
492
|
+
|
|
493
|
+
摘要行(折叠行)与错误详情都会单行化并截断:摘要 120 字符、模板 `{error}` 80 字符、默认文案的错误详情 40 字符,超长以省略号结尾。
|
|
494
|
+
|
|
495
|
+
</details>
|
|
194
496
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
497
|
+
<details>
|
|
498
|
+
<summary><b>可以自定义系统通知的图标或声音吗?</b></summary>
|
|
499
|
+
|
|
500
|
+
当前版本使用浏览器默认通知样式,不注入自定义图标或声音,toast 为固定深色卡片。如需这些能力欢迎提 Issue 或 PR。
|
|
501
|
+
|
|
502
|
+
</details>
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## 更新日志
|
|
507
|
+
|
|
508
|
+
| 版本 | 日期 | 变更 |
|
|
509
|
+
| --- | --- | --- |
|
|
510
|
+
| **0.1.4** | 2026-08-28 | 补充完整卸载支持:`dispose` 生命周期收尾(host 置卸载标志抑制待追加微任务;client 清理正文轮询定时器、`__dsch_notify_debug` 钩子、toast 容器);卸载文档与 FAQ 同步 |
|
|
511
|
+
| **0.1.3** | 2026-08-28 | 声明官方 `dsh.bundle` manifest(`dsh plugin add` 一条命令自动挂载);settings 重试定时器改为 `ctx.effect()` 包装(Cordis effect 纪律);安装文档重排 |
|
|
512
|
+
| 0.1.2 | 2026-08-27 | 包更名至 `@telosmaylx` scope(npm 用户名作用域) |
|
|
513
|
+
| 0.1.1 | 2026-08-27 | GitHub、npm 安装方式文档化 |
|
|
514
|
+
| 0.1.0 | 2026-08-26 | 初始版本:会话内系统消息 + 浏览器推送 + 官方设置面板 |
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## 贡献
|
|
519
|
+
|
|
520
|
+
欢迎 Issue 与 PR:
|
|
521
|
+
|
|
522
|
+
1. Fork 仓库并新建分支(`feat/xxx`)
|
|
523
|
+
2. 改动后运行 `npm run build` 做语法校验
|
|
524
|
+
3. 提交 PR,说明动机与验证方式
|
|
525
|
+
|
|
526
|
+
提交前请遵守 [Cordis 开发教程](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial) 纪律:
|
|
527
|
+
|
|
528
|
+
- Cordis 之外的资源(定时器、订阅、watcher)必须包装在 `ctx.effect()` 中并返回 disposer;
|
|
529
|
+
- 配置项显式 `id` 防止编辑漂移;
|
|
530
|
+
- 插件须声明 `dsh.bundle` manifest 才能被 `dsh plugin add` 识别安装。
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## 相关链接
|
|
535
|
+
|
|
536
|
+
- [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) —— DSH 插件精选列表(投稿规范:`dsh.bundle` 是安装唯一凭证)
|
|
537
|
+
- [Cordis 开发教程](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial) —— 插件开发全流程(01-07 章)
|
|
538
|
+
- [npm 包主页](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
|
|
539
|
+
- [GitHub 仓库](https://github.com/TelosmaYLX/dsh-session-notify)
|
|
540
|
+
|
|
541
|
+
---
|
|
199
542
|
|
|
200
|
-
##
|
|
543
|
+
## 许可证
|
|
201
544
|
|
|
202
|
-
MIT
|
|
545
|
+
[MIT](./LICENSE) © dsh-session-notify contributors
|
package/lib/client.js
CHANGED
|
@@ -142,6 +142,8 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
try { notifyScope = ctx.settingsScope.bind({ namespace: SETTINGS_NS }) } catch (e) { /* 无设置作用域时用默认 */ }
|
|
143
143
|
var prevRunning = new Map() // sessionId -> 上次观测的 running 位
|
|
144
144
|
var primed = false // 首次观测只记录基线(已在 idle 的会话不补发),与官方 sidebar 提醒同策略
|
|
145
|
+
var retryTimer = null // 完成推送正文轮询定时器(卸载时清除:不在已拆 fiber 上继续弹通知)
|
|
146
|
+
var debugHook = null // window.__dsch_notify_debug 引用(卸载时按引用删除,防闭包泄漏)
|
|
145
147
|
|
|
146
148
|
try {
|
|
147
149
|
console.log('[' + PLUGIN_TAG + '-client] active, watching session activity for completion pushes')
|
|
@@ -180,10 +182,11 @@ window.__ModuleLoader__.load({
|
|
|
180
182
|
function attempt() {
|
|
181
183
|
var notice = readNoticeAny(id)
|
|
182
184
|
if (notice || Date.now() - start > 6000) {
|
|
185
|
+
retryTimer = null
|
|
183
186
|
notifyUser(pushTitle(name, notice ? '' : ' 已完成'), notice || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息')
|
|
184
187
|
return
|
|
185
188
|
}
|
|
186
|
-
setTimeout(attempt, 400)
|
|
189
|
+
retryTimer = setTimeout(attempt, 400)
|
|
187
190
|
}
|
|
188
191
|
attempt()
|
|
189
192
|
}
|
|
@@ -263,7 +266,7 @@ window.__ModuleLoader__.load({
|
|
|
263
266
|
// 调试钩子(仅探针/排障用):window.__dsch_notify_debug
|
|
264
267
|
try {
|
|
265
268
|
if (typeof window !== 'undefined') {
|
|
266
|
-
|
|
269
|
+
debugHook = {
|
|
267
270
|
readNotice: readNotice,
|
|
268
271
|
snapshotDebug: function (id) {
|
|
269
272
|
try {
|
|
@@ -280,17 +283,42 @@ window.__ModuleLoader__.load({
|
|
|
280
283
|
}
|
|
281
284
|
},
|
|
282
285
|
}
|
|
286
|
+
window.__dsch_notify_debug = debugHook
|
|
283
287
|
}
|
|
284
288
|
} catch (e) { /* noop */ }
|
|
285
289
|
|
|
290
|
+
// 卸载清理(Cordis 教程第 2 章生命周期纪律):取消会话订阅、清除正文轮询
|
|
291
|
+
// 定时器(不在已拆 fiber 上继续弹通知)、按引用删除调试钩子(防闭包持有
|
|
292
|
+
// ctx/list 泄漏)、移除页内 toast 容器。settings 卡片为 slot 托管 effect,
|
|
293
|
+
// 自定义预设存于 localStorage(用户数据),两者卸载时均保留。
|
|
294
|
+
function cleanup() {
|
|
295
|
+
try { dispose() } catch (e) { /* 订阅已取消则忽略 */ }
|
|
296
|
+
if (retryTimer !== null) {
|
|
297
|
+
clearTimeout(retryTimer)
|
|
298
|
+
retryTimer = null
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
if (typeof window !== 'undefined' && window.__dsch_notify_debug === debugHook) {
|
|
302
|
+
delete window.__dsch_notify_debug
|
|
303
|
+
}
|
|
304
|
+
} catch (e) { /* noop */ }
|
|
305
|
+
try {
|
|
306
|
+
if (typeof document !== 'undefined') {
|
|
307
|
+
var toastRoot = document.querySelector('[data-dsh-notify-root]')
|
|
308
|
+
if (toastRoot) toastRoot.remove()
|
|
309
|
+
}
|
|
310
|
+
} catch (e) { /* noop */ }
|
|
311
|
+
log('unloaded: subscription, retry timer, debug hook, toast root released')
|
|
312
|
+
}
|
|
313
|
+
|
|
286
314
|
// 与宿主插件生命周期一致:ctx.effect 登记清理,fiber 卸载时释放。
|
|
287
315
|
if (ctx.effect && typeof ctx.effect === 'function') {
|
|
288
316
|
ctx.effect(function () {
|
|
289
|
-
return
|
|
317
|
+
return cleanup
|
|
290
318
|
})
|
|
291
319
|
} else {
|
|
292
320
|
try {
|
|
293
|
-
var oc = ((ctx.onDispose && ctx.onDispose(
|
|
321
|
+
var oc = ((ctx.onDispose && ctx.onDispose(cleanup)) || undefined)
|
|
294
322
|
void oc
|
|
295
323
|
} catch (e) {
|
|
296
324
|
/* 老接口缺失时仅内存泄漏,不影响功能 */
|
package/lib/index.js
CHANGED
|
@@ -110,6 +110,7 @@ export function apply(ctx, config = {}) {
|
|
|
110
110
|
const tracker = createTurnTracker()
|
|
111
111
|
let settings = DEFAULT_SETTINGS
|
|
112
112
|
let projRegistry = null // sessionProjections 服务(非注入可选依赖,经 ctx.inject 捕获)
|
|
113
|
+
let disposed = false // 卸载/HMR 拆除标记:置位后不再追加通知
|
|
113
114
|
|
|
114
115
|
// 官方设置命名空间:设置面板(设置 → 插件)可编辑;user 层持久化在 settings 文档。
|
|
115
116
|
// 重试兜底:热重载时旧 fiber 注销与新 fiber 注册存在竞态,register 可能因
|
|
@@ -258,13 +259,30 @@ export function apply(ctx, config = {}) {
|
|
|
258
259
|
// finally 中复位),此时同步 append 会被拒绝:
|
|
259
260
|
// "session append cannot reenter while another append is being published"
|
|
260
261
|
// 推迟到微任务——微任务队列在本次同步栈(含 finally 复位)之后才跑。
|
|
261
|
-
queueMicrotask(() =>
|
|
262
|
+
queueMicrotask(() => {
|
|
263
|
+
if (disposed) return // 卸载窗口内已调度的微任务:fiber 已拆除,跳过追加
|
|
264
|
+
appendNotice(ctx, session, notice)
|
|
265
|
+
})
|
|
262
266
|
return
|
|
263
267
|
}
|
|
264
268
|
default:
|
|
265
269
|
return
|
|
266
270
|
}
|
|
267
271
|
})
|
|
272
|
+
|
|
273
|
+
// 卸载清理(Cordis 教程第 2 章生命周期纪律):事件订阅 / settings scope /
|
|
274
|
+
// 投影注入均为 Cordis 托管 effect,卸载时自动释放;这里只做插件自有状态的
|
|
275
|
+
// 收尾——置 disposed 标志(抑制已调度的微任务追加)并落一条卸载日志。
|
|
276
|
+
try {
|
|
277
|
+
if (typeof ctx.on === 'function') {
|
|
278
|
+
ctx.on('dispose', () => {
|
|
279
|
+
disposed = true
|
|
280
|
+
fileLog('plugin unloaded (host): pending appends suppressed')
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
/* 极老环境无 dispose 事件:由 fiber 自然回收 */
|
|
285
|
+
}
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
/** 把系统消息追加进会话日志(失败只记日志,绝不抛出破坏 event 火线)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telosmaylx/dsh-session-notify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "DSH session completion notifier: appends a plugin system message to the session log on turn/end and pushes browser notifications (Web Notification + toast), with a fully customizable official settings panel (presets, 5-language templates, cache-hit rate & tok/s from official projections).",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|