dsh-notify-ding 0.1.4 → 0.1.6

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 CHANGED
@@ -1,4 +1,4 @@
1
- # dsh·通知叮咚插件
1
+ # DSH·通知叮咚
2
2
 
3
3
  在 DSH 等待你回复或一轮对话跑完时,弹出系统通知。
4
4
 
@@ -18,6 +18,8 @@
18
18
  - 通知不设自动关闭时间,会一直留在系统通知里,直到对应会话发生操作:会话被选中、该会话开始新一轮、待回答被解决,或会话从列表移除。
19
19
  - 每个会话最多保留一条通知,新通知会顶掉同会话的旧通知;点击通知会聚焦窗口并切到该会话。
20
20
  - 同一待回答请求只响一次,两次响铃之间至少有 800 毫秒间隔,且该间隔跨标签页共享,多开标签页不会重复响。
21
+ - 一轮跑完时若该会话还有后台任务在跑,通知正文把「已空闲」换成「后台还有 N 个任务」,第二行列出任务详情的前几个,不会让人以为全都干完了。
22
+ - 会话的目标还在自动续跑时,轮与轮之间的空闲照旧叮一下,但改报「DSH 目标进行中」并带上已跑轮数,横幅不常驻、自动消失;目标停下、完成或被阻塞的那一轮才报「完成了一轮」。
21
23
 
22
24
  ## 使用
23
25
 
@@ -63,23 +65,26 @@ dsh plugin --profile web remove dsh-notify-ding
63
65
 
64
66
  ## 工作原理
65
67
 
66
- 浏览器半身订阅 `ctx.sessions.list` 与 `ctx.uiSession.sessionStatus` 两个只读快照,用边沿检测判断该不该响:运行态从真落到假算一轮跑完,出现新 key 的待回答交互算需要人工介入。首帧只建基线不发声,同一待回答请求只响一次,两次响铃之间留至少 800 毫秒并把该时刻写入 localStorage 跨标签页共享。
68
+ 浏览器半身订阅 `ctx.sessions.list` 与 `ctx.uiSession.sessionStatus` 两个只读快照,用边沿检测判断该不该响:运行态从真落到假算一轮跑完,出现新 key 的待回答交互算需要人工介入。该会话的目标还在自动续跑时,这个空闲落点改报「目标进行中」的临时提示,横幅自动消失,不堆常驻通知。首帧只建基线不发声,同一待回答请求只响一次,两次响铃之间留至少 800 毫秒并把该时刻写入 localStorage 跨标签页共享。
67
69
 
68
- 判定通过时它弹出一条浏览器系统通知。通知按会话记录且不设自动关闭,同一会话只留最新一条;会话被选中、开始新一轮、待回答被解决或从列表移除时会关掉对应通知,点击通知则聚焦窗口、切换过去并关掉该条。完整设计见 [设计说明](docs/design.md)。
70
+ 判定通过时它弹出一条浏览器系统通知。按会话各记一条,同一会话只留最新一条;「完成了一轮」与「需要你回答」这两类要等人处理,不设自动关闭,目标续跑那类临时提示只在通知中心留一小会儿。会话被选中、开始新一轮、待回答被解决或从列表移除时会关掉对应通知,点击通知则聚焦窗口、切换过去并关掉该条。
71
+
72
+ 一轮结束那条通知的正文来自可选的 `ctx.jobs`:按会话引用计数订阅 `job.list` 名册,在触发那一刻取该会话自己仍在运行的任务,有就把「已空闲」换成「后台还有 N 个任务」,第二行列出前三个任务的 id 与标签,标签取头截断。组合里没有 job-controller 时照旧报「已空闲」。完整设计见 [设计说明](docs/design.md)。
69
73
 
70
74
  ## 要求与开发
71
75
 
72
76
  - 是标准形态的 dsh client 插件,声明 `dsh.client`,导出 `./client`。
73
77
  - 同时声明了 `dsh.bundle`,因此也是一个自挂载的 bundle 层插件:用 `dsh plugin --profile <name> add` 从 GitHub 安装后会被自动识别为 profile layer 并挂载,无需手工写组合 entry。
74
78
  - 浏览器半身依赖 `uiSession` 与 `sessions`;宿主入口没有运行时行为,只为让包能被 Loader 加载。
79
+ - 后台任务名册来自可选的 `ctx.jobs`,`dsh.client.inject` 里声明了 `@deepseek-ai/dsh-api-job-controller` 以保证加载顺序;组合里没有它时插件照常工作,一轮跑完的通知照旧报「已空闲」。
75
80
  - 构建型插件:`src/` 是 TypeScript 源码,`lib/` 是构建产物且不入库,安装或发布前由 `prepare` 构建。
76
81
 
77
82
  ## 开发
78
83
 
79
84
  ```powershell
80
85
  pnpm install
81
- pnpm run typecheck # tsc --noEmit 严格类型检查
82
- pnpm run build # tsdown,产出 lib/index.js 与 lib/client.js
86
+ pnpm typecheck # tsc --noEmit 严格类型检查
87
+ pnpm build # tsdown,产出 lib/index.js 与 lib/client.js
83
88
  ```
84
89
 
85
90
  - `src/index.ts`:宿主入口,无运行时行为,只用于让包被 Loader 加载。
package/docs/design.md CHANGED
@@ -25,9 +25,17 @@
25
25
  2. **同一待回答请求只响一次**:按交互的稳定 key 记忆,消失后清理记录但不发声。
26
26
  3. **滚动最小间隔 800 毫秒**:两次响铃之间至少间隔这么久。窗口内到达的请求不丢弃,而是挂到窗口边界的定时器上补发,保证不漏报。该时刻写入 localStorage 跨标签页共享,多开标签页时同一件事只响一轮。
27
27
 
28
+ ## goal 自动续跑时的通知
29
+
30
+ 会话带一个处于 active 的目标时,DSH 会在轮与轮之间自动接着跑,每个间隙都会落一次空闲。这个空闲报「完成了一轮」会让人误以为可以回来看了,但它确实值得让人知道,于是改为一条临时提示:标题「DSH 目标进行中」,正文带会话标题与已跑轮数,并且不设 requireInteraction,横幅按系统默认时长自动消失,不在系统通知中心里长期占位。
31
+
32
+ 判定看会话摘要里的 `projectionValues.goal`:持久阶段是 active 就走这条路径,否则仍然是「完成了一轮」的常驻通知,等对应会话被操作才撤。等待回答的通知不受影响,目标模式里模型提问照样响。
33
+
34
+ goal 投影随会话控制流一起到达列表快照,因此读到的就是当下的阶段;投影值缺失或没有目标时按普通会话处理。
35
+
28
36
  ## 通知的存活与关闭
29
37
 
30
- 浏览器系统通知不再设自动关闭时间,并且带上 `requireInteraction`,会一直留在系统通知里,直到对应会话发生操作。插件按会话 id 记录当前挂着的通知,每个会话最多一条,新的先关掉旧的。
38
+ 通知分两类。要等人处理的「完成了一轮」与「需要你回答」不设自动关闭时间,并且带上 `requireInteraction`,会一直留在系统通知里,直到对应会话发生操作;目标自动续跑时的「目标进行中」是临时提示,不要求常驻,横幅按系统默认时长自动消失。插件按会话 id 记录当前挂着的通知,每个会话最多一条,新的先关掉旧的。
31
39
 
32
40
  以下任一情况出现,就算对应会话得到了一次操作,插件会关掉它的通知:
33
41
 
@@ -42,6 +50,23 @@
42
50
 
43
51
  只识别这四种状态信号,因此通知只有在 DSH 里真的对那个会话动了手才会消失;单纯回到浏览器窗口不会关掉它。
44
52
 
53
+ ## 后台任务提示
54
+
55
+ 一轮对话跑完时后台任务常常还在跑,只报「已空闲」会让人以为全都干完了,因此该会话还有运行中的任务时,完成通知的正文用数量摘要替换「已空闲」,次一级的位置列出任务详情的前几个:
56
+
57
+ ```
58
+ 测试会话 后台还有 2 个任务
59
+ bash-3 构建产物、subagent-1 调研
60
+ ```
61
+
62
+ 等待回答那条通知不带任务信息。
63
+
64
+ 名册来自可选的 `ctx.jobs`,它是 dsh-api-job-controller 的客户端半身,按会话引用计数的 `job.list` 流提供「该会话看得到的任务集合」。插件在会话出现在列表里时持有引用、会话从列表消失时释放,因此在触发那一刻读到的就是当下的名册。
65
+
66
+ 只取 owner 等于该会话的任务。无主任务会出现在每个会话的名册里,若一并列入,每条通知都会带上同一批名字。数量行报总数,详情行最多列前三个任务,每个用 id 加 producer 提供的 label,label 超过 24 字取头截断,多余的任务只在末尾报总数。
67
+
68
+ `ctx.jobs` 用 `ctx.get` 按可选服务读取,`dsh.client.inject` 只保证加载顺序。组合里没有 job-controller 时照旧报「已空闲」。
69
+
45
70
  ## 全屏静默
46
71
 
47
72
  Windows 11 的「专注」有一条自动规则「当我在全屏使用应用时」,全屏看视频或玩游戏时会自动进入专注状态,通知横幅不显示、通知音被静音,通知只落进通知中心。插件不自己发声,提示音就是系统通知自带的音效,所以这种场合没有任何声音提示。
@@ -49,6 +74,7 @@ Windows 11 的「专注」有一条自动规则「当我在全屏使用应用时
49
74
  ## 已知边界
50
75
 
51
76
  - 浏览器半身依赖 `uiSession` 与 `sessions`,缺任一则不激活。
52
- - 「一轮跑完」只覆盖正常结束与被打断这两种空闲落点,插件不区分具体结束原因。
77
+ - 后台任务摘要依赖组合里的 job-controller,缺失时通知照发,完成通知照旧报「已空闲」。
78
+ - 「一轮跑完」只覆盖正常结束与被打断这两种空闲落点,插件不区分具体结束原因。目标因达到 maxGoalRounds 上限而停下时持久阶段仍是 active,插件读不到进程本地的 activation,这一轮只会报一条「目标进行中」的临时提示,不会报「完成了一轮」;暂停、阻塞、完成这三种终态都照常报完成。
53
79
  - 系统通知被静默时横幅和声音一起消失,插件察觉不到,也没有补偿手段。
54
80
  - 页面被关掉后,浏览器通知可能仍留在系统通知中心,此时插件已不在运行,无法再收回,只能手动清掉。
package/lib/client.js CHANGED
@@ -16,23 +16,37 @@ window.__ModuleLoader__.load({
16
16
  *
17
17
  * 触发条件有两个:
18
18
  * 1. 某个会话出现了新的等待回复交互,question / plan-review / approval 都算;
19
- * 2. 某个会话从「执行中」落到「空闲」,即一轮对话跑完。
19
+ * 2. 某个会话从「执行中」落到「空闲」,即一轮对话跑完;会话的目标还在自动续跑时照样响,只是改报「目标进行中」,横幅自动消失,免得每轮堆一条常驻通知。
20
20
  *
21
21
  * 按用户选择,两种都响,且不判断页面是否聚焦、不区分是否为当前会话。
22
22
  * 重复靠两道闸门挡:同一待回答请求的 key 只响一次,以及跨标签页共享的最小间隔,避免多开标签页时同一件事响好几轮。
23
23
  *
24
24
  * 通知不设自动关闭时间,会一直挂在系统通知里,直到对应会话发生操作:会话被选中、该会话开始新一轮、待回答被解决,或会话从列表移除。每个会话最多只保留最新一条,新通知会顶掉旧的;点击通知会聚焦窗口并切到该会话。
25
25
  *
26
+ * 一轮结束的通知在会话还有后台任务在跑时,把「已空闲」换成任务数量摘要:任务常常活过一轮,只说已空闲会让人以为全都干完了。名册来自 ctx.jobs,按会话引用计数订阅;组合里没有 job-controller 时照旧报「已空闲」。
27
+ *
26
28
  * @module dsh-notify-ding/client
27
29
  */
28
30
  /** 插件名,同时也是配置项 id。 */
29
31
  const name = "dsh-notify-ding";
30
- /** 本插件需要的客户端服务。 */
32
+ /**
33
+ * 本插件需要的客户端服务。
34
+ *
35
+ * ctx.jobs 不在其中:组合里可能没有 job-controller,缺了它只是通知里少一行后台任务,不该让整个插件不加载,所以按可选服务读取。
36
+ */
31
37
  const inject = ["uiSession", "sessions"];
32
38
  /** 两次响铃之间的最小间隔,窗口内的合并成一次。 */
33
39
  const MIN_INTERVAL_MS = 800;
34
40
  /** 跨标签页共享「上次响铃时刻」的 localStorage 键。 */
35
41
  const LAST_DING_KEY = "dsh-notify-ding.lastDingAt";
42
+ /** 通知正文里任务详情最多列几个,超出只报总数。 */
43
+ const JOB_DETAIL_LIMIT = 3;
44
+ /** 任务详情里单个任务名的最大长度,超出取头截断。 */
45
+ const JOB_DETAIL_LABEL_LIMIT = 24;
46
+ /** goal 处于这个持久阶段时 DSH 会自动续跑下一轮,一轮结束不等于轮到用户。 */
47
+ const GOAL_ACTIVE_PHASE = "active";
48
+ /** 目标自动续跑时那轮空闲的通知标题,内容只在通知中心留一小会儿。 */
49
+ const GOAL_PROGRESS_TITLE = "DSH 目标进行中";
36
50
  /** 当前挂在系统通知里、按会话索引的通知,新通知会顶掉同会话的旧通知。 */
37
51
  const liveNotifications = /* @__PURE__ */ new Map();
38
52
  /** 打开会话的入口,由 apply 从 ctx.sessions 注入;缺失时点击通知只关通知。 */
@@ -43,6 +57,10 @@ window.__ModuleLoader__.load({
43
57
  let lastCurrent;
44
58
  /** 记录各会话上一帧状态,用来做边沿检测。 */
45
59
  const tracks = /* @__PURE__ */ new Map();
60
+ /** 已经持有引用的后台任务名册,键为会话 id,值为释放函数。 */
61
+ const watchedRosters = /* @__PURE__ */ new Map();
62
+ /** 客户端后台任务服务,apply 时解析;组合里没有 job-controller 时保持 undefined。 */
63
+ let jobsFace;
46
64
  /** 本页面上次响铃的时间戳,兜住同一帧内的重复触发。 */
47
65
  let lastDingAt = 0;
48
66
  /** 因节流被暂时压下的最新一条待响请求,窗口过后补发。 */
@@ -118,15 +136,16 @@ window.__ModuleLoader__.load({
118
136
  * @param sessionId 这条通知所属的会话。
119
137
  * @param title 通知标题。
120
138
  * @param body 通知正文。
139
+ * @param transient 是否临时提示;true 时不要求常驻,横幅按系统默认时长自动消失。
121
140
  */
122
- function showSystemNotification(sessionId, title, body) {
141
+ function showSystemNotification(sessionId, title, body, transient) {
123
142
  if (typeof Notification === "undefined") return;
124
143
  const present = () => {
125
144
  closeNotification(sessionId);
126
145
  try {
127
146
  const notification = new Notification(title, {
128
147
  body,
129
- requireInteraction: true
148
+ requireInteraction: !transient
130
149
  });
131
150
  notification.onclick = () => {
132
151
  openSessionInUi(sessionId);
@@ -174,24 +193,27 @@ window.__ModuleLoader__.load({
174
193
  *
175
194
  * 节流时刻写进 localStorage,多标签页共享,避免多开时同一件事响好几轮。
176
195
  * 被节流压下的请求不会直接丢弃,而是挂到窗口边界的定时器上补发,这样连续两次状态变化仍然各响一次,只是间隔被拉开,不会出现漏报。
196
+ * 正文由调用方在触发那一刻算好,补发的通知因此带的是触发时刻的任务名册。
177
197
  *
178
198
  * @param sessionId 这条通知所属的会话。
179
199
  * @param title 通知标题。
180
200
  * @param body 通知正文。
201
+ * @param transient 是否临时提示;目标续跑那种报个信就够的用 true,横幅自动消失。
181
202
  */
182
- function ding(sessionId, title, body) {
203
+ function ding(sessionId, title, body, transient = false) {
183
204
  const now = Date.now();
184
205
  const since = now - Math.max(lastDingAt, readSharedLastDing());
185
206
  if (since < 0 || since >= MIN_INTERVAL_MS) {
186
207
  lastDingAt = now;
187
208
  writeSharedLastDing(now);
188
- showSystemNotification(sessionId, title, body);
209
+ showSystemNotification(sessionId, title, body, transient);
189
210
  return;
190
211
  }
191
212
  deferred = {
192
213
  sessionId,
193
214
  title,
194
- body
215
+ body,
216
+ transient
195
217
  };
196
218
  if (deferredTimer !== 0) return;
197
219
  deferredTimer = window.setTimeout(() => {
@@ -202,7 +224,7 @@ window.__ModuleLoader__.load({
202
224
  const stamp = Date.now();
203
225
  lastDingAt = stamp;
204
226
  writeSharedLastDing(stamp);
205
- showSystemNotification(pending.sessionId, pending.title, pending.body);
227
+ showSystemNotification(pending.sessionId, pending.title, pending.body, pending.transient);
206
228
  }, MIN_INTERVAL_MS - since);
207
229
  }
208
230
  /** 会话标题,缺失时退回会话 id,保证通知里总有个能认的名字。 */
@@ -210,6 +232,110 @@ window.__ModuleLoader__.load({
210
232
  const raw = summary?.displayTitle;
211
233
  return raw && raw.length > 0 ? raw : fallbackId;
212
234
  }
235
+ /** 从客户端上下文取后台任务服务;组合里没有 job-controller 时返回 undefined。 */
236
+ function readJobsFace(ctx) {
237
+ try {
238
+ return ctx.get?.("jobs");
239
+ } catch (error) {
240
+ console.warn("[dsh-notify-ding] 读取后台任务服务失败:" + String(error));
241
+ return;
242
+ }
243
+ }
244
+ /** 开始持有某会话的后台任务名册引用;没有该服务或已经持有则什么都不做。 */
245
+ function watchRoster(sessionId) {
246
+ if (jobsFace === void 0 || watchedRosters.has(sessionId)) return;
247
+ try {
248
+ watchedRosters.set(sessionId, jobsFace.watchRows(sessionId));
249
+ } catch (error) {
250
+ console.warn("[dsh-notify-ding] 订阅后台任务名册失败:" + String(error));
251
+ }
252
+ }
253
+ /** 释放某会话的后台任务名册引用,会话从列表移除或插件卸载时调用。 */
254
+ function unwatchRoster(sessionId) {
255
+ const release = watchedRosters.get(sessionId);
256
+ if (release === void 0) return;
257
+ watchedRosters.delete(sessionId);
258
+ try {
259
+ release();
260
+ } catch (error) {
261
+ console.warn("[dsh-notify-ding] 释放后台任务名册失败:" + String(error));
262
+ }
263
+ }
264
+ /** 释放全部后台任务名册引用,插件卸载时收尾。 */
265
+ function unwatchAllRosters() {
266
+ for (const sessionId of Array.from(watchedRosters.keys())) unwatchRoster(sessionId);
267
+ }
268
+ /**
269
+ * 挑出某会话仍在跑的后台任务。
270
+ *
271
+ * 只取归属该会话的任务:无主任务会出现在每个会话的名册里,列进去会在每条通知里重复。
272
+ *
273
+ * @param sessionId 通知所属会话。
274
+ * @returns 名册里归属该会话且仍在运行的行;没有则为空数组。
275
+ */
276
+ function runningJobs(sessionId) {
277
+ const rows = jobsFace?.state.getSnapshot().rows[sessionId];
278
+ if (rows === void 0 || rows.length === 0) return [];
279
+ return rows.filter((row) => {
280
+ if (row.status !== "running" && row.status !== "stopping") return false;
281
+ return row.owner !== void 0 && String(row.owner) === sessionId;
282
+ });
283
+ }
284
+ /** 单个任务在详情行里的短名字,标签为空时退回 id,过长时取头截断。 */
285
+ function describeJob(row) {
286
+ const label = row.label.trim();
287
+ if (label.length === 0) return row.id;
288
+ const head = label.length > JOB_DETAIL_LABEL_LIMIT ? label.slice(0, JOB_DETAIL_LABEL_LIMIT) + "…" : label;
289
+ return row.id + " " + head;
290
+ }
291
+ /**
292
+ * 一轮结束时的通知正文。
293
+ *
294
+ * 还有后台任务时,「已空闲」的位置报数量,次一级的位置列出任务详情的前几个;任务常常活过一轮,只说已空闲会让人以为全都干完了。
295
+ *
296
+ * @param sessionId 通知所属会话。
297
+ * @param title 会话标题。
298
+ * @returns 通知正文,需要时带一个换行分成两行。
299
+ */
300
+ function idleBody(sessionId, title) {
301
+ let running;
302
+ try {
303
+ running = runningJobs(sessionId);
304
+ } catch (error) {
305
+ console.warn("[dsh-notify-ding] 读取后台任务名册失败:" + String(error));
306
+ return title + " 已空闲";
307
+ }
308
+ if (running.length === 0) return title + " 已空闲";
309
+ const details = running.slice(0, JOB_DETAIL_LIMIT).map(describeJob).join("、");
310
+ const rest = running.length > JOB_DETAIL_LIMIT ? " 等 " + running.length + " 个" : "";
311
+ return title + " 后台还有 " + running.length + " 个任务\n" + details + rest;
312
+ }
313
+ /**
314
+ * 该会话是否有一个还会自动续跑的目标。
315
+ *
316
+ * 目标处于 active 时 DSH 会自己接着跑下一轮,轮与轮之间都会短暂落到空闲。
317
+ * 这种空闲不报「完成了一轮」,改报目标进行中的临时提示,免得每轮堆一条常驻通知。
318
+ *
319
+ * @param summary 当前会话摘要。
320
+ * @returns 目标处于 active 时为真。
321
+ */
322
+ function hasActiveGoal(summary) {
323
+ return summary.projectionValues?.goal?.goal?.phase === GOAL_ACTIVE_PHASE;
324
+ }
325
+ /**
326
+ * 目标自动续跑时那轮空闲的通知正文。
327
+ *
328
+ * 这里报「完成了一轮」会让人以为可以回来看了,所以改报目标还在进行,并带上已经跑完的轮数。
329
+ *
330
+ * @param summary 当前会话摘要。
331
+ * @param title 会话标题。
332
+ * @returns 通知正文。
333
+ */
334
+ function goalProgressBody(summary, title) {
335
+ const rounds = summary.projectionValues?.goal?.roundsStarted;
336
+ const progress = typeof rounds === "number" && rounds > 0 ? "已跑 " + rounds + " 轮" : "正在自动续跑";
337
+ return title + " 目标进行中," + progress;
338
+ }
213
339
  /**
214
340
  * 用一帧会话列表快照做边沿检测:新出现的待回答交互响一次,运行态从真落到假响一次。
215
341
  *
@@ -231,6 +357,7 @@ window.__ModuleLoader__.load({
231
357
  const summary = state.byId[id];
232
358
  if (!summary) continue;
233
359
  seen.add(id);
360
+ watchRoster(id);
234
361
  let track = tracks.get(id);
235
362
  if (!track) {
236
363
  track = {
@@ -240,13 +367,18 @@ window.__ModuleLoader__.load({
240
367
  tracks.set(id, track);
241
368
  }
242
369
  if (!baseline) {
243
- if (track.running && !summary.running) ding(id, "DSH 完成了一轮", titleOf(summary, id) + " 已空闲");
370
+ if (track.running && !summary.running) {
371
+ const title = titleOf(summary, id);
372
+ if (hasActiveGoal(summary)) ding(id, GOAL_PROGRESS_TITLE, goalProgressBody(summary, title), true);
373
+ else ding(id, "DSH 完成了一轮", idleBody(id, title));
374
+ }
244
375
  if (!track.running && summary.running) closeNotification(id);
245
376
  }
246
377
  track.running = summary.running;
247
378
  }
248
379
  for (const id of Array.from(tracks.keys())) if (!seen.has(id)) {
249
380
  closeNotification(id);
381
+ unwatchRoster(id);
250
382
  tracks.delete(id);
251
383
  }
252
384
  }
@@ -303,6 +435,7 @@ window.__ModuleLoader__.load({
303
435
  const open = ctx.sessions.open;
304
436
  sessionOpener = typeof open === "function" ? open.bind(ctx.sessions) : void 0;
305
437
  listSourceRef = listSource;
438
+ jobsFace = readJobsFace(ctx);
306
439
  /** 是否已经用首帧快照建立过基线;首帧只记录不发声。 */
307
440
  let initialized = false;
308
441
  /** 读两个源并各扫一遍,保证两处看到的是同一帧会话列表。 */
@@ -335,8 +468,10 @@ window.__ModuleLoader__.load({
335
468
  }
336
469
  deferred = null;
337
470
  closeAllNotifications();
471
+ unwatchAllRosters();
338
472
  sessionOpener = void 0;
339
473
  listSourceRef = void 0;
474
+ jobsFace = void 0;
340
475
  lastCurrent = void 0;
341
476
  tracks.clear();
342
477
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-ding",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "「DSH·通知叮咚」:在 DSH 等待回复或一轮对话跑完时弹出系统通知。标准可安装的 dsh 客户端插件。",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -46,6 +46,7 @@
46
46
  "client": {
47
47
  "platform": "web",
48
48
  "inject": [
49
+ "@deepseek-ai/dsh-api-job-controller",
49
50
  "@deepseek-ai/dsh-client-ui-session"
50
51
  ]
51
52
  }
@@ -57,6 +58,7 @@
57
58
  },
58
59
  "peerDependencies": {
59
60
  "@deepseek-ai/dsh": "^0.2.0-rc.2",
61
+ "@deepseek-ai/dsh-api-job-controller": "^0.2.0-rc.2",
60
62
  "@deepseek-ai/dsh-client-ui-session": "^0.2.0-rc.2"
61
63
  },
62
64
  "devDependencies": {