dsh-notify-me 1.6.0 → 1.6.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to **dsh-notify-me** are documented here.
4
4
 
5
+ ## [1.6.1] — 2026-10-16
6
+
7
+ ### 修复
8
+ - **列表变化里「当前会话」只推导一次,消掉 O(行数 × 会话数)**(性能,issue #13):`isSubagentSession()` 现在接收调用方已经推导好的当前会话 id。此前它对**每一行**都要重新调 `currentSessionId()`,而 `0.1.6+`(含 `0.2.0-rc.2`)的列表快照已无 `current`,只能 `for (var id in byId)` 扫完整份会话表——一次列表变化因此是「行数 × 会话数」。实测(DSH `0.2.0-rc.2`、**482 个会话**、页面空闲时用 CDP 采 25s 与 38s 两轮 CPU profile):`currentSessionId()` 自身耗时占 **5.9%~6.5%**,是该页面最热的 JS 函数;修复后同轮 profile 里它退到噪声水平。第二窗口/后台窗口下所有行 `mainView` 为 0 时,旧实现每次都要扫满全表再返回 `null`,这一档同样消失。
9
+ - `evalUiStatus()` / `syncDoneFromStatus()` 两个「每个会话一条」的循环同样改为每次 pass 只推导一次。
10
+
11
+ ### 测试
12
+ - `smoke/smoke-test.cjs` 新增断言「一次列表变化的 `byId` 枚举次数不随行数增长」(40 行与 4 行必须相等,不写死魔数)。本版基线上改动前该断言报 `9 vs 45`(PR 在 1.5.3 基线测得 `7 vs 43`),修复后为 `5 vs 5`(先写失败断言、先红后绿)。
13
+
14
+ ### 变更
15
+ - 版本号 1.6.0 → 1.6.1;`window.__dshNotifyMe.version` 同步。
16
+ - README(中英)致谢名单新增 [@yanzhaohui1999](https://github.com/yanzhaohui1999)(issue #13 的性能报告与 PR #14 的修复及回归断言)。
17
+
5
18
  ## [1.6.0] — 2026-10-15
6
19
 
7
20
  ### 新增
package/README.en.md CHANGED
@@ -195,6 +195,7 @@ Contributors (by handle; the link points at the PR / issue they worked on):
195
195
  - [@d0ublecl1ck](https://github.com/d0ublecl1ck) — [#8](https://github.com/chromoany/dsh-notify-me/pull/8): muting subagent sessions (`ignoreSubagent`) with its tests and docs; the 1.4.0 refinement — mute only background subagents, alert as usual for the one you have open — is his suggestion from the #8 discussion, landed by the maintainer;
196
196
  - [@EliteOtaku](https://github.com/EliteOtaku) — [#9](https://github.com/chromoany/dsh-notify-me/issues/9): the layer-by-layer investigation and three rounds of real-shell retesting of the desktop raise path (1.5.2) — confirming that a toast click raises the window and switches conversations reliably, and pinning down the reproduction difference ("gesture-less scripted injection is silently dropped by Chromium"), which corrected the initial "the sandbox blocks it" reading;
197
197
  - [@Wh1stle05](https://github.com/Wh1stle05) — [#10](https://github.com/chromoany/dsh-notify-me/pull/10): the per-kind alert sound source (built-in synth / picked audio file / system notification sound, chosen independently for "needs you" and "reply done"), with its settings UI, fallback chain, tests and docs; the two refinements — a "system" preview sending a real test notification instead of doing nothing, and the fake React holding hook state by index across renders — come from the same PR.
198
+ - [@yanzhaohui1999](https://github.com/yanzhaohui1999) — [#13](https://github.com/chromoany/dsh-notify-me/issues/13) / [#14](https://github.com/chromoany/dsh-notify-me/pull/14): the 482-session idle-page CPU report (pinning `currentSessionId()` rescanning the whole table per row, 5.9%~6.5% of CPU) and the fix (derive the on-screen session once per list change), with a red-then-green regression assertion — a 4-row and a 40-row list change must cost the same (upstream reported 7 vs 43, 5 vs 5 after).
198
199
 
199
200
  Attribution policy: external PRs are landed as maintainer commits — the original commit is not reused and no Co-authored-by trailer is added, so GitHub's contributors graph lists the maintainer only and contributors are credited above.
200
201
 
package/README.md CHANGED
@@ -191,6 +191,7 @@ npm pack --dry-run # 预览发布包
191
191
  - [@d0ublecl1ck](https://github.com/d0ublecl1ck) — [#8](https://github.com/chromoany/dsh-notify-me/pull/8):子代理会话静音(`ignoreSubagent`)与配套测试、文档;1.4.0 里「只静音后台子代理、当前打开的照常提醒」的口径同样出自他在 #8 讨论里的建议,落地由维护者完成;
192
192
  - [@EliteOtaku](https://github.com/EliteOtaku) — [#9](https://github.com/chromoany/dsh-notify-me/issues/9):桌面端抬窗链路(1.5.2)的逐层排查与真实壳三轮复测——确认点通知抬窗 + 切会话稳定生效,并定位出「无用户手势的脚本化注入被 Chromium 静默丢弃」这一复现差异,纠正了最初「沙箱拦死」的判断;
193
193
  - [@Wh1stle05](https://github.com/Wh1stle05) — [#10](https://github.com/chromoany/dsh-notify-me/pull/10):「音效来源」功能(内置合成音 / 自定义音频文件 / 系统通知音,「需要你」与「回复完成」各自独立选),含设置页交互、回落链与测试、文档;「系统通知音」试听改发测试通知、假 React 按索引跨渲染保持 hook 状态这两处修正同样出自该 PR。
194
+ - [@yanzhaohui1999](https://github.com/yanzhaohui1999) — [#13](https://github.com/chromoany/dsh-notify-me/issues/13) / [#14](https://github.com/chromoany/dsh-notify-me/pull/14):482 个会话下空闲页面烧 CPU 的性能报告(定位到 `currentSessionId()` 每行重扫全表,实测占 CPU 5.9%~6.5%)与修复(一次列表变化只推导一次「当前会话」),并带先红后绿的回归断言——4 行与 40 行的列表变化代价必须相等(上游报 7 vs 43,修后 5 vs 5)。
194
195
 
195
196
  署名口径:本仓库收编外部 PR 时以维护者提交落地——不沿用原 commit、不挂 Co-authored-by,GitHub 的 contributors 图只显示维护者,贡献者以上面的致谢署名。
196
197
 
package/lib/client.js CHANGED
@@ -1033,9 +1033,14 @@
1033
1033
  // current-conversation quiet rule) and is never a mute candidate. Read live
1034
1034
  // rather than cached, so the settings switch and session switches both take
1035
1035
  // effect on the next event without rebinding anything.
1036
- function isSubagentSession(sid) {
1036
+ function isSubagentSession(sid, currentId) {
1037
1037
  if (!config.ignoreSubagent || !sid) return false;
1038
- if (currentSessionId() === sid) return false;
1038
+ // Reuse the caller's derivation when it has one. Every row walk already
1039
+ // knows the on-screen session (onListChanged derives it once and then
1040
+ // calls this per row), and deriving it again inside the row rescans the
1041
+ // whole list: O(rows x sessions) on 0.1.6+ hosts, where the list snapshot
1042
+ // has no `current` and the on-screen row has to be found by walking byId.
1043
+ if ((currentId === undefined ? currentSessionId() : currentId) === sid) return false;
1039
1044
  try {
1040
1045
  var st = listStore && listStore.getSnapshot();
1041
1046
  var sum = st && st.byId && st.byId[sid];
@@ -1188,14 +1193,14 @@
1188
1193
  } catch (e) {}
1189
1194
  return "";
1190
1195
  }
1191
- // Evaluate one row/face observation. row: {id, running, pending:[{key,kind,payload}], completed, label, face:bool}
1196
+ // Evaluate one row/face observation. row: {id, running, pending:[{key,kind,payload}], completed, label, face:bool, currentId?}
1192
1197
  function evaluateRow(row) {
1193
1198
  var prev = prevById[row.id];
1194
1199
  var runningNow = !!row.running;
1195
1200
  var pendingNow = row.pending || [];
1196
1201
  var prevPendingKeys = prev ? prev.pendingKeys : [];
1197
1202
  var wasRunning = prev ? prev.running : false;
1198
- var muted = isSubagentSession(row.id);
1203
+ var muted = isSubagentSession(row.id, row.currentId);
1199
1204
  var newKeys = [];
1200
1205
  // A muted row still feeds the baseline below, so lifting the mute
1201
1206
  // (turning the switch off, or opening that subagent session) resumes
@@ -1283,6 +1288,7 @@
1283
1288
  completed: false,
1284
1289
  face: true,
1285
1290
  faceSnap: snap,
1291
+ currentId: sessionId,
1286
1292
  label: "current"
1287
1293
  });
1288
1294
  }
@@ -1294,6 +1300,7 @@
1294
1300
  pending: sum.pendingInteraction ? [{ key: "list:" + sid + ":" + sum.pendingInteraction, kind: sum.pendingInteraction, payload: null }] : [],
1295
1301
  completed: !!sum.completed,
1296
1302
  face: false,
1303
+ currentId: currentId,
1297
1304
  label: sum.displayTitle || sum.title || sid
1298
1305
  });
1299
1306
  }
@@ -1467,6 +1474,7 @@
1467
1474
  if (!snap || typeof snap.forEach !== "function") return;
1468
1475
  if (found.source === "sessionStatus") syncDoneFromStatus(snap);
1469
1476
  var seen = Object.create(null);
1477
+ var cur = currentSessionId();
1470
1478
  snap.forEach(function (value, sid) {
1471
1479
  var it = pendingOf(value, found.source);
1472
1480
  // A session with no pending interaction is not "seen": leaving it out
@@ -1478,7 +1486,7 @@
1478
1486
  // announced before the mute was switched on (or before the user
1479
1487
  // switched away from that subagent session). Opening the session later
1480
1488
  // announces the outstanding wait then, like any first sighting.
1481
- if (isSubagentSession(sid)) return;
1489
+ if (isSubagentSession(sid, cur)) return;
1482
1490
  seen[sid] = true;
1483
1491
  var key = it.key || (it.kind + ":" + sid);
1484
1492
  var prev = uiBaseline[sid];
@@ -1520,6 +1528,7 @@
1520
1528
  function syncDoneFromStatus(snap) {
1521
1529
  var want = Object.create(null);
1522
1530
  try {
1531
+ var cur = currentSessionId();
1523
1532
  snap.forEach(function (value, sid) {
1524
1533
  if (!value) return;
1525
1534
  // A stop that awaits input (plan-review / question end the turn with
@@ -1527,7 +1536,7 @@
1527
1536
  // as the edge path's !hasPendingNow guard. The on-screen session is
1528
1537
  // never "unread" (the host clears the flag for it; its completion is
1529
1538
  // the face edge's job), and a background subagent step is muted.
1530
- if (!value.completionUnread || value.pendingInteraction || isSubagentSession(sid) || currentSessionId() === sid) return;
1539
+ if (!value.completionUnread || value.pendingInteraction || isSubagentSession(sid, cur) || cur === sid) return;
1531
1540
  want[sid] = true;
1532
1541
  markDoneUnread(sid);
1533
1542
  if (uiDoneBaseline[sid]) return; // this unread instance already reported
@@ -1567,7 +1576,7 @@
1567
1576
  });
1568
1577
  } catch (e) {}
1569
1578
  // keep in sync with package.json "version" on every release
1570
- api.version = "1.6.0";
1579
+ api.version = "1.6.1";
1571
1580
  api.setConfig = function (patch) { return setConfigLocal(patch); };
1572
1581
  api.resetConfig = function () {
1573
1582
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "DSH 桌面消息提醒插件:模型需要你操作(审批/方案确认/提问的可操作提醒)或回复在后台完成时,系统通知+提示音+标签页标题提醒你;权限审批的通知上可直接点「同意/拒绝」完成裁决;可在设置页开关并切换通知语言。Desktop notification & message alerts for DeepSeek Harness web — toasts, sounds and a tab-title marker when the agent needs your input (approval / plan review / question) or a reply finishes in the background, Approve/Reject buttons on approval toasts to decide right from the notification; toggleable from Settings with zh/en notification language.",
5
5
  "license": "MIT",
6
6
  "type": "module",