dsh-notify-me 1.3.1 → 1.5.0

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,37 @@
2
2
 
3
3
  All notable changes to **dsh-notify-me** are documented here.
4
4
 
5
+ ## [1.5.0] — 2026-10-03
6
+
7
+ ### 新增
8
+ - **「完成未读」标题标记**:后台会话的回复跑完、你还没打开看的期间,标签页标题前出现 `✅ 回复完成 · …` / `✅ Reply finished · …`,打开该会话(宿主此时清掉未读)或它再次开跑即消失——离开屏幕再回来,一眼看到哪些跑完了。与「需要你」标记可同时出现(「需要你」在前),宿主改写标题后标记基于新标题重建。未读事实取自宿主:`≥ 0.1.6` 用 `sessionStatus` 行的 `completionUnread`(后台会话完成即点亮,打开 / 再次开跑 / 会话消失即清零),旧宿主用列表行的 `completed` 提醒位(选中会话即清零)。静音中的后台子代理会话照旧不标记;「页面打开时也提醒『回复完成』」开关只管通知,不影响标记。
9
+ - **「回复完成」通道接上宿主的完成未读标志**:此前只靠 `running` 快照边沿,页面没看见的完成(插件热重载中途、快照把 true→false 合并成一跳、完成发生在绑定之前)就永远漏掉。现在 `sessionStatus` 的 `completionUnread` 级别标志(0.1.6+)负责补漏——每条未读实例只报一次,绑定时已存在的未读也补报一声;边沿与级别标志是同一次完成的两个视角、同刻到达时按短时间窗合并成一声。边沿路径保持不变:当前会话的正文仍带回复摘要,旧宿主照旧。
10
+ - 「等你操作」不算完成:一轮带着 plan-review / 提问结束时只走「需要你」提醒,答完也不补「回复完成」马后炮;宿主仍标未读时,标题标记照常挂到你打开会话为止。后台子代理会话的未读照旧静音,解除静音后补报一次。
11
+ - `window.__dshNotifyMe.debug()` 新增 `doneUnreadMarked`(当前挂着「完成未读」标记的会话列表)。
12
+
13
+ ### 变更
14
+ - 版本号 1.4.0 → 1.5.0;`window.__dshNotifyMe.version` 同步。
15
+ - README(中英)同步:「提醒时机」表、「提醒方式」的标题标记说明、「工作原理」的完成未读级别标志两代取源与合并口径。
16
+
17
+ ### 测试
18
+ - `smoke/smoke-test.cjs` 新增 completionUnread 用例段:漏报补发、同刻双视角只响一声、绑定前已有的未读只报一次、等输入的停顿不算完成(答完不补马后炮、标记照旧)、当前会话不走未读通道、后台子代理静音与解除静音、页面可见时只留标记不弹通知、双标记叠加与宿主改标题后的重建;legacy 用例补 `completed` 提醒位的标记出现与「选中会话即撤」。
19
+ - `smoke/cordis-host-test.mjs` 在真 cordis `sessionStatus` 宿主上补一条走真 store 的完成未读用例:报告一次、标记、不重复、打开即撤。
20
+
21
+ ## [1.4.0] — 2026-10-03
22
+
23
+ ### 新增
24
+ - **子代理会话不提醒**(设置页新开关「子代理会话不提醒」,默认开启):DSH 把每个子代理(subagent)子会话列成独立会话行(`origin: 'subagent'` 加 `parentId`),于是这些子会话的「需要你」与「回复完成」都会照常弹通知——可它们是所属主对话那一轮里的步骤,提醒只是噪音。现在默认静音**后台**子代理会话:列表行评估与 `sessionStatus` 等待通道都跳过「带 `origin: 'subagent'` 且不是当前会话」的行;你正打开着的那个子代理会话不算静音对象,照常提醒(走当前对话的常规规则:页面可见时默认只留标题标记、转后台补发)。主对话自己的提醒不受影响。
25
+ - 判定认 `origin` 加「不是当前会话」两条:只有 `parentId`、没有 `origin` 的分支(fork)会话仍是你自己的对话,照常提醒;静音随你打开的会话实时变化——切进某个后台子代理会话,它积压的等待按当前对话的规则补上,切走后重新回到静音范围(标题标记与排队中的通知一并撤下)。静音开关读的是实时配置,切换后下一条事件即生效,不需要重载页面。
26
+ - `window.__dshNotifyMe` 配置新增 `ignoreSubagent`(默认 `true`,可用 `setConfig` / `resetConfig` 调整),`debug()` 新增同名字段便于核对。
27
+
28
+ ### 变更
29
+ - 版本号 1.3.1 → 1.4.0;`window.__dshNotifyMe.version` 同步。
30
+ - README(中英)补上设置页新开关、提醒时机说明、控制台配置示例与工作原理里的子代理口径。
31
+ - README(中英)新增「致谢与贡献须知 / Credits & how contributions land」:外部 PR 的署名口径与贡献者名单;「只静音后台子代理、当前打开的照常提醒」这一口径来自 [@d0ublecl1ck](https://github.com/d0ublecl1ck) 在 [#8](https://github.com/chromoany/dsh-notify-me/pull/8) 讨论里的建议(收编进 main 的原版是整条静音),本次按该口径改准并由维护者落地。
32
+
33
+ ### 测试
34
+ - `smoke/smoke-test.cjs` 新增子代理用例:`ignoreSubagent` 默认值与 `debug()` 字段、后台子代理完成不提醒而普通后台会话照常、后台子代理等待不提醒也不打标题标记、分支(fork)会话仍提醒、关掉开关后子代理的等待与完成重新提醒,以及「当前打开的子代理会话照常提醒」——等待与完成都提醒;后台积压的等待在切进去时按当前对话的规则排队补发,切走则连标题标记与排队通知一起撤下。
35
+
5
36
  ## [1.3.1] — 2026-10-03
6
37
 
7
38
  ### 文档
package/README.en.md CHANGED
@@ -43,7 +43,9 @@ dsh-notify-me decouples **supervision** from **sitting in front of the screen**:
43
43
  | When | What you get | Default |
44
44
  | --- | --- | --- |
45
45
  | 🔔 **The agent needs your input** — sandbox approval / plan review / a question (`ask_user_question`) | Toast + sound + `🔔 Action needed ·` title marker; approval toasts also carry Approve / Reject buttons | Alerts even while the page is visible (the conversation on screen is the exception: tab marker only by default) |
46
- | ✅ **A reply finishes** — a turn completes; background sessions finishing are also reported | Toast + sound | Alerts only while the page is hidden / backgrounded |
46
+ | ✅ **A reply finishes** — a turn completes; background sessions finishing are also reported | Toast + sound; while a background session is **finished but unread** the tab title carries a `✅ Reply finished ·` marker, gone once you open that session | Alerts only while the page is hidden / backgrounded (the title marker is not gated by this) |
47
+
48
+ > **Background** subagent child sessions are **not** alerted about by default: DSH lists each of them as its own session, but they are steps inside the parent conversation's turn, so an alert for them is only noise. The subagent session you have open is not part of this and alerts as usual. Settings → "Ignore subagent sessions" turns the mute off.
47
49
 
48
50
  ## Configure it from DSH Settings
49
51
 
@@ -53,6 +55,7 @@ Open **Settings → Notify me** (refresh the page once after installing):
53
55
  - **System notifications / Sound / Volume** — toast toggle, WebAudio beep toggle and a volume slider;
54
56
  - **"Needs you" alerts while the page is open** (default on) and **"Reply finished" alerts while the page is open** (default off);
55
57
  - **Stay quiet for the conversation on screen** (default on): a wait inside the conversation you are looking at keeps only the tab marker — no toast or sound landing on the approval card. Backgrounding the page puts that alert back at once, unless you already handled it. Waits in other background sessions are unaffected;
58
+ - **Ignore subagent sessions** (default on): background subagent child sessions raise neither "needs you" nor "reply finished" — those are steps of the parent conversation's turn, and the parent's own alert already covers them. The subagent session you have open alerts as usual. Turn it off to alert for every session;
56
59
  - **Click a toast to open that conversation** (default on): clicking brings DSH to the front and switches to the conversation the alert came from; when off, clicking a toast does nothing;
57
60
  - **Decide approvals from the toast** (default on): approval notifications carry Approve / Reject buttons, and one click settles the request exactly like the Allow once / Reject buttons on the approval card. Clicking the toast body still takes you back to the conversation;
58
61
  - **Notification language** — follow the interface / 简体中文 / English: controls the language of the alert text and the `🔔 …` title marker;
@@ -64,7 +67,7 @@ Open **Settings → Notify me** (refresh the page once after installing):
64
67
 
65
68
  - **System notification** — a toast through the browser into the Windows action center / macOS Notification Center (clicking it opens the conversation it came from; approval toasts can be decided straight from the buttons)
66
69
  - **Sound** — WebAudio beeps (distinct patterns for "needs you" vs "done")
67
- - **Tab title marker** — while something is waiting on you, the tab title is prefixed with `🔔 Action needed · …` / `🔔 需要你 · …`
70
+ - **Tab title marker** — while something is waiting on you, the tab title is prefixed with `🔔 Action needed · …` / `🔔 需要你 · …`; while a background session is **finished but unread**, with `✅ Reply finished · …` / `✅ 回复完成 · …`, gone once you open that session (or it starts running again). Both kinds can be up together, "needs you" first; a host title change is rebased under the markers
68
71
 
69
72
  This is a **browser-layer** plugin: the DSH page must stay open (minimized or backgrounded is fine — that's exactly the "away" state it watches for).
70
73
 
@@ -85,7 +88,7 @@ Restart `dsh web`, then hard-refresh the page (Ctrl+Shift+R).
85
88
  | `0.1.2-alpha.2 .. 0.1.6-alpha.1` | `uiSession.pendingInteractions` | snapshot `current` | `sessions.open()` |
86
89
  | `>= 0.1.6-alpha.2` (incl. `0.2.0-rc.2`) | `uiSession.sessionStatus` | `retainedBy.mainView` | `uiWorkspace.openSession()` |
87
90
 
88
- The ranges are illustrative: the plugin picks its source by store shape (whenever `sessionStatus` exists it takes the new path); the version numbers only mark where each shape appeared.
91
+ The ranges are illustrative: the plugin picks its source by store shape (whenever `sessionStatus` exists it takes the new path); the version numbers only mark where each shape appeared. The `peerDependencies` range in `package.json` likewise only shapes npm's install warning — compatibility is declared per release in `dshReleases` after real testing, and untested new versions fall back to shape-driven behaviour.
89
92
 
90
93
  Verified to actually activate on `0.1.2-rc.1`, `0.1.5-rc.2` and `0.2.0-rc.2` (each booted in its own profile: the Settings → Notifications section registers, i.e. the plugin's `apply` really runs), and previously on `0.1.1-rc.2`. Two traps are worth remembering: a package listed in `dsh.client.inject` that a newer runtime no longer ships parks the client entry at `pending (waiting for services: …)` forever — which is what broke 1.1.3 on `0.1.2-rc.1` and later; and when the host renames a store or a field nothing throws, alerts just go **silent** — `0.2.0-rc.2` moved `pendingInteractions`→`sessionStatus`, `list.current`→`retainedBy.mainView` and `sessions.open()`→`uiWorkspace.openSession()`. So every adaptation must confirm `window.__dshNotifyMe.debug()` reports `bound` and which source answered.
91
94
 
@@ -116,6 +119,7 @@ window.__dshNotifyMe.setConfig({
116
119
  attentionHiddenOnly: false, // true = don't alert "needs you" while the page is visible
117
120
  currentHiddenOnly: true, // true = waits in the conversation on screen keep only the tab marker (replayed once backgrounded)
118
121
  doneHiddenOnly: true, // false = also alert "reply finished" while visible
122
+ ignoreSubagent: true, // true = never alert for background subagent sessions (the open one is exempt)
119
123
  toast: true, // system-notification toggle
120
124
  sound: true, // sound toggle
121
125
  volume: 0.5, // 0..1
@@ -132,7 +136,8 @@ The reminder core picks its interaction source per host generation (see the tabl
132
136
 
133
137
  - the **selected session's** `SessionSnapshot`: a `running: true → false` edge means a reply finished;
134
138
  - **pending interactions**: a new key in `uiSession.sessionStatus` (`>= 0.1.6`, per-session `pendingInteraction`) or in `pendingInteractions` (`0.1.2 .. 0.1.5`) means the agent is waiting on you — approval requests carry the tool name and reason, questions their text; older hosts fall back to the controller snapshot's `pending[]`;
135
- - **every other listed session's** summary: a `running: true → false` edge (or the legacy `completed` flag) alerts you about background work.
139
+ - **every other listed session's** summary: a `running: true → false` edge (or the legacy `completed` flag) alerts you about background work. The host's completion-unread level flag backs that edge up and owns the title marker: `sessionStatus` rows' `completionUnread` on `>= 0.1.6` (lit when a background session finishes, cleared when it is opened / re-runs / disappears), the list row's `completed` reminder bit on older hosts (cleared on select). It re-reports a completion whose edge this page never saw (a plugin remount mid-turn, coalesced snapshots) exactly once, and when both views of one completion arrive together a short window collapses them into a single alert.
140
+ - **subagent sessions**: **background** rows carrying `origin: 'subagent'` (and a `parentId`) stay muted by default. The subagent session you have open is exempt — its alerts follow the normal current-conversation rules (tab marker only while the page is visible, delivered on background), its outstanding waits are picked up when you switch into it, and switching away mutes them again. A fork has a `parentId` but no `origin`, is your own conversation, and keeps alerting throughout.
136
141
 
137
142
  The reminder core stays dependency-free and self-contained: notification copy resolves at alert time from the chosen language (follow-interface / Simplified Chinese / English). The Settings page is an **optional** React surface — it only registers into Settings when the web profile provides the `slots` / `locale` services and `react`; without them the plugin degrades gracefully to alerts-only (no Settings page).
138
143
 
@@ -167,6 +172,15 @@ npm pack --dry-run # preview the published tarball
167
172
 
168
173
  `cordis-host-test.mjs` borrows `@deepseek-ai/cordis` from a local DSH installation (it probes the global npm directory, `~/.dsh/profiles/web/node_modules` and the flat fallback `~/.dsh/profiles/node_modules` in turn); in a source checkout with no profile dependencies, point it there with `DSH_CORDIS_PATH=<checkout>/vendor/cordis/lib/index.js`.
169
174
 
175
+ ## Credits & how contributions land
176
+
177
+ Contributors (by handle; the linked PR is their change):
178
+
179
+ - [@AgMahone](https://github.com/AgMahone) — [#7](https://github.com/chromoany/dsh-notify-me/pull/7): the 0.1.6+ / 0.2.0 host-generation support, plus the double-chime and cordis-lookup reports and retesting;
180
+ - [@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.
181
+
182
+ 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.
183
+
170
184
  ## License
171
185
 
172
186
  MIT — see [LICENSE](LICENSE).
package/README.md CHANGED
@@ -43,7 +43,9 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
43
43
  | 时机 | 提醒内容 | 默认 |
44
44
  | --- | --- | --- |
45
45
  | 🔔 **模型需要你操作** — 审批请求 / 方案待确认(plan review)/ 提问(`ask_user_question`) | 通知 + 提示音 + `🔔 需要你 ·` 标题标记;审批的通知上直接带「同意 / 拒绝」按钮 | 页面可见也提醒(当前对话除外:默认只留标题标记) |
46
- | ✅ **回复完成** — 一轮回复跑完;后台会话完成也会报 | 通知 + 提示音 | 仅页面隐藏/后台时提醒 |
46
+ | ✅ **回复完成** — 一轮回复跑完;后台会话完成也会报 | 通知 + 提示音;后台会话**完成但还没看**的期间标题带 `✅ 回复完成 ·` 标记,打开该会话即撤 | 仅页面隐藏/后台时提醒(标题标记不受此限) |
47
+
48
+ > **后台**子代理(subagent)子会话的等待与完成默认**不提醒**:DSH 把每个子代理子会话列成独立会话,但它们是所属主对话那一轮里的步骤,提醒只会是噪音。你当前打开的那个子代理会话不算在内,照常提醒。设置页「子代理会话不提醒」可关掉这个静音。
47
49
 
48
50
  ## 在 DSH 设置里改配置
49
51
 
@@ -53,6 +55,7 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
53
55
  - **系统通知 / 提示音 / 音量**:Toast、WebAudio 提示音与音量滑块;
54
56
  - **页面打开时也提醒「需要你」**(默认开)与 **页面打开时也提醒「回复完成」**(默认关);
55
57
  - **当前对话不弹通知**(默认开):等待就发生在你正看着的对话里时只留标题标记,不弹通知也不响音(免得盖住审批卡片);页面切到后台立刻补发,若你已经处理掉就作废。其它后台会话不受影响;
58
+ - **子代理会话不提醒**(默认开):后台子代理(subagent)子会话的「需要你」与「回复完成」都不提醒——它们是主对话那一轮的步骤,完成由主对话自己的提醒覆盖;你当前打开的那个子代理会话照常提醒;关掉后恢复逐会话提醒;
56
59
  - **点击通知回到对应对话**(默认开):点通知把 DSH 带到前台并切到提醒所属的对话;关掉后点通知不做任何事;
57
60
  - **审批通知上直接裁决**(默认开):权限审批的通知上带「同意 / 拒绝」按钮,点一下就按审批卡片的「允许一次 / 拒绝」直接裁决,不用切回页面;点通知正文仍是原来的「切回对应对话」;
58
61
  - **通知语言**:跟随界面 / 简体中文 / English——决定通知文字与 `🔔 …` 标题标记使用的语言;
@@ -64,7 +67,7 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
64
67
 
65
68
  - **系统通知**:经浏览器弹出的系统通知,进 Windows 通知中心或 macOS 通知中心(点击回到提醒所属的对话;审批的通知点按钮直接裁决)
66
69
  - **提示音**:WebAudio 合成音(「需要你」与「完成」使用不同音型)
67
- - **标签页标题标记**:有待处理事项时,标题前出现 `🔔 需要你 · …` / `🔔 Action needed · …`
70
+ - **标签页标题标记**:有待处理事项时,标题前出现 `🔔 需要你 · …` / `🔔 Action needed · …`;后台会话**完成但还没看**时出现 `✅ 回复完成 · …` / `✅ Reply finished · …`,打开对应会话(或它再次开跑)即消失。两种标记可同时出现,「需要你」在前;宿主改写标题后标记基于新标题重建
68
71
 
69
72
  本插件是**浏览器层**实现:DSH 页面需保持打开(最小化/后台即可——那正是它监听的「离开」状态)。
70
73
 
@@ -85,7 +88,7 @@ dsh plugin --profile web add dsh-notify-me
85
88
  | `0.1.2-alpha.2 .. 0.1.6-alpha.1` | `uiSession.pendingInteractions` | 快照 `current` | `sessions.open()` |
86
89
  | `≥ 0.1.6-alpha.2`(含 `0.2.0-rc.2`) | `uiSession.sessionStatus` | `retainedBy.mainView` | `uiWorkspace.openSession()` |
87
90
 
88
- 区间仅作示意:插件按 store 形状自动选源(`sessionStatus` 存在即走新路),版本号只是各形状的出现边界。
91
+ 区间仅作示意:插件按 store 形状自动选源(`sessionStatus` 存在即走新路),版本号只是各形状的出现边界。`package.json` 的 `peerDependencies` 范围同理只影响 npm install 的提示文案——兼容与否以 `dshReleases` 的逐版实测声明为准,未实测的新版本由形状驱动兜底。
89
92
 
90
93
  实测**真正激活**(各自独立 profile 启动:设置里出现「通知提醒」分区,即插件的 `apply` 确实执行)于 `0.1.2-rc.1`、`0.1.5-rc.2` 与 `0.2.0-rc.2`;更早的 `0.1.1-rc.2` 亦验证过。两个坑值得记住:一是 `dsh.client.inject` 里列了新版运行时已不再提供的包,客户端条目会停在 `pending (waiting for services: …)` 而永不执行——1.1.3 在 `0.1.2-rc.1` 及以后正是这样失效的;二是宿主换存储/字段名时**不会报错**,提醒只会静默失效——`0.2.0-rc.2` 上 `pendingInteractions`→`sessionStatus`、`list.current`→`retainedBy.mainView`、`sessions.open()`→`uiWorkspace.openSession()` 都是这一类,所以每次适配都要用 `window.__dshNotifyMe.debug()` 确认真的是 `bound`、以及用的是哪个来源。
91
94
 
@@ -112,6 +115,7 @@ window.__dshNotifyMe.setConfig({
112
115
  attentionHiddenOnly: false, // true = 页面可见时「需要你」不提醒
113
116
  currentHiddenOnly: true, // true = 当前对话的等待只留标题标记(转后台补发)
114
117
  doneHiddenOnly: true, // false = 页面可见时「完成」也提醒
118
+ ignoreSubagent: true, // true = 后台子代理(subagent)会话不提醒(当前打开的照常)
115
119
  toast: true, // 系统通知开关
116
120
  sound: true, // 提示音开关
117
121
  volume: 0.5, // 音量 0~1
@@ -128,7 +132,8 @@ window.__dshNotifyMe.decide("approval:3", "allowed-once") // 程序化裁决:
128
132
 
129
133
  - **当前会话** `SessionSnapshot`:`running` true→false = 回复完成;
130
134
  - **待办交互**:`uiSession` 的 `sessionStatus`(`≥ 0.1.6`,逐会话 `pendingInteraction`)或 `pendingInteractions`(`0.1.2 .. 0.1.5`)出现新 key = 模型在等你,容器里带上审批工具名 / 理由、提问文本;旧宿主回落到控制器快照的 `pending[]`;
131
- - **其它已列会话**摘要:`running` true→false(或旧宿主的 `completed` 边沿)= 后台工作提醒。
135
+ - **其它已列会话**摘要:`running` true→false(或旧宿主的 `completed` 边沿)= 后台工作提醒。宿主的「完成未读」级别标志负责补漏与标题标记:`≥ 0.1.6` 用 `sessionStatus` 行的 `completionUnread`(后台会话完成即点亮、打开该会话/再次开跑/会话消失即清零),旧宿主用列表行的 `completed` 提醒位(选中会话即清零);页面没看见的完成边沿(插件热重载中途、快照合并跳变)由此补报一次,边沿与级别标志同来时按短时间窗合并,只响一声。
136
+ - **子代理会话**:列表行带 `origin: 'subagent'`(并有 `parentId`)的**后台**会话默认静音;你正打开着的那个子代理会话不算静音对象,它的提醒走当前对话的常规规则(页面可见时默认只留标题标记、转后台补发),切进来时积压的等待同样补上,切走后重新静音。只有 `parentId`、没有 `origin` 的分支(fork)会话属于你自己的对话,始终照常提醒。
132
137
 
133
138
  提醒核心仍然零第三方运行时依赖、完全自包含:通知文字按所选语言(跟随界面 / 中文 / English)即时解析。设置页是**可选**的 React 呈现层——当 DSH web profile 提供 `slots` / `locale` / `react` 时才注册进「设置 → 通知提醒」,缺任一能力时插件自动降级为纯提醒(无设置页),不影响功能。
134
139
 
@@ -163,6 +168,15 @@ npm pack --dry-run # 预览发布包
163
168
 
164
169
  `cordis-host-test.mjs` 会从本机 DSH 安装借 `@deepseek-ai/cordis`(依次探测 npm 全局目录、`~/.dsh/profiles/web/node_modules` 与 `~/.dsh/profiles/node_modules` 平铺回退目录);源码 checkout 里没有 profile 依赖时,用 `DSH_CORDIS_PATH=<checkout>/vendor/cordis/lib/index.js` 指定即可。
165
170
 
171
+ ## 致谢与贡献须知
172
+
173
+ 贡献者(按 handle 列出,对应 PR 就是他们提的改动):
174
+
175
+ - [@AgMahone](https://github.com/AgMahone) — [#7](https://github.com/chromoany/dsh-notify-me/pull/7):0.1.6+ / 0.2.0 宿主代际支持,以及通知双响、cordis 查找路径的报障与复测;
176
+ - [@d0ublecl1ck](https://github.com/d0ublecl1ck) — [#8](https://github.com/chromoany/dsh-notify-me/pull/8):子代理会话静音(`ignoreSubagent`)与配套测试、文档;1.4.0 里「只静音后台子代理、当前打开的照常提醒」的口径同样出自他在 #8 讨论里的建议,落地由维护者完成。
177
+
178
+ 署名口径:本仓库收编外部 PR 时以维护者提交落地——不沿用原 commit、不挂 Co-authored-by,GitHub 的 contributors 图只显示维护者,贡献者以上面的致谢署名。
179
+
166
180
  ## License
167
181
 
168
182
  MIT — 见 [LICENSE](LICENSE)。
package/lib/client.js CHANGED
@@ -28,10 +28,23 @@
28
28
  // * "sessions" -> the selected session's ConversationSnapshot (running)
29
29
  // and the session-list summaries of every other session (running /
30
30
  // completed), which carry the "reply finished" edge on every version.
31
+ // The host's completion-unread level flag backs that edge up and owns
32
+ // the "完成未读" title marker: `sessionStatus.completionUnread` on
33
+ // 0.1.6+ (armed when a background session finishes, cleared when it is
34
+ // opened / re-runs / disappears), the list row's `completed` reminder
35
+ // bit on older hosts. The edge path stays the reporter for the on-screen
36
+ // session (its snippet comes from the face snapshot); the two views of
37
+ // one completion are collapsed by reportDone's short dedupe window.
31
38
  // The selected ("current") session is read from the list snapshot's
32
39
  // `current` field where it exists (<= 0.1.5-rc.3) and otherwise derived
33
40
  // from the mainView retention count (0.1.6+, the way the shipped
34
41
  // DocumentTitle / ui-session pick it);
42
+ // - mutes background subagent child sessions by default
43
+ // (config.ignoreSubagent): DSH lists them as ordinary rows carrying
44
+ // `origin: 'subagent'`, but their waits and finish edges are steps of the
45
+ // parent conversation's turn. The subagent session the user has open is not
46
+ // muted — it is on screen like any other conversation and alerts by the
47
+ // normal rules;
35
48
  // - fires a Windows desktop Notification (+ optional sound) when the agent
36
49
  // needs the user's input — pending interaction kinds 'approval',
37
50
  // 'plan-review' or 'question' — and when a reply/turn finishes;
@@ -140,6 +153,13 @@
140
153
  // the page brings that alert back (see flushQuieted).
141
154
  currentHiddenOnly: true, // the visible session's own waits stay silent
142
155
  doneHiddenOnly: true, // reply-finished alerts only when the page is hidden
156
+ // Background subagent child sessions are steps of another conversation's
157
+ // turn: DSH lists each of them as its own row (origin: 'subagent'), so
158
+ // their waits and finish edges would toast noise the user cannot act on.
159
+ // Muted by default; the list row's own origin field is the only thing this
160
+ // keys on, plus "not the session on screen" — an open subagent session is
161
+ // just another conversation to alert for.
162
+ ignoreSubagent: true,
143
163
  toast: true,
144
164
  sound: true,
145
165
  volume: 0.5,
@@ -190,6 +210,7 @@
190
210
  doneBody: "回复已完成",
191
211
  background: "后台会话",
192
212
  marker: "🔔 需要你 · ",
213
+ doneMarker: "✅ 回复完成 · ",
193
214
  sep: ":",
194
215
  approve: "同意",
195
216
  reject: "拒绝",
@@ -213,6 +234,7 @@
213
234
  doneBody: "Reply finished",
214
235
  background: "Background session",
215
236
  marker: "🔔 Action needed · ",
237
+ doneMarker: "✅ Reply finished · ",
216
238
  sep: ": ",
217
239
  approve: "Approve",
218
240
  reject: "Reject",
@@ -586,16 +608,25 @@
586
608
  // title over (or an earlier load leaving a marker behind) can never make us
587
609
  // mistake our own marker for the base title.
588
610
  var attentionActive = 0;
611
+ var doneActive = 0; // "完成未读" marks, one per finished-but-unread session
589
612
  var hostBase = null; // document.title as the host last left it
590
613
  var writtenTitle = null; // exact string this plugin last put into document.title
591
- // Our own marker prefix never belongs to the base title — strip a leftover
592
- // one (an earlier load that was closed while a wait was pending, or a title
593
- // the host tracked while a marker was up) before storing the base.
614
+ // Our own marker prefixes never belong to the base title — strip leftovers
615
+ // (an earlier load that was closed while a mark was up, or a title the host
616
+ // tracked while a marker was up) before storing the base. Both marker kinds
617
+ // can be up at once and either may lead, so strip repeatedly.
594
618
  function cleanBase(t) {
595
619
  var d = texts();
596
- var prefixes = [d.marker, TXT.zh.marker, TXT.en.marker];
597
- for (var i = 0; i < prefixes.length; i++) {
598
- while (t.indexOf(prefixes[i]) === 0) t = t.slice(prefixes[i].length);
620
+ var prefixes = [d.marker, TXT.zh.marker, TXT.en.marker, d.doneMarker, TXT.zh.doneMarker, TXT.en.doneMarker];
621
+ var stripped = true;
622
+ while (stripped) {
623
+ stripped = false;
624
+ for (var i = 0; i < prefixes.length; i++) {
625
+ if (prefixes[i] && t.indexOf(prefixes[i]) === 0) {
626
+ t = t.slice(prefixes[i].length);
627
+ stripped = true;
628
+ }
629
+ }
599
630
  }
600
631
  return t;
601
632
  }
@@ -603,7 +634,15 @@
603
634
  try {
604
635
  var current = String(document.title || "");
605
636
  if (current !== writtenTitle) hostBase = cleanBase(current);
606
- var next = (config.enabled && attentionActive > 0) ? texts().marker + hostBase : hostBase;
637
+ var d = texts();
638
+ var prefix = "";
639
+ if (config.enabled) {
640
+ // "needs you" leads when both kinds are up: it is the one that wants
641
+ // action, the unread mark only says something finished.
642
+ if (attentionActive > 0) prefix += d.marker;
643
+ if (doneActive > 0) prefix += d.doneMarker;
644
+ }
645
+ var next = prefix + hostBase;
607
646
  writtenTitle = next;
608
647
  if (current !== next) document.title = next;
609
648
  } catch (e) {}
@@ -616,6 +655,24 @@
616
655
  if (attentionActive > 0) attentionActive -= 1;
617
656
  applyTitle();
618
657
  }
658
+ // Unread-reply marker: one per session id, held exactly while the host
659
+ // still calls the completion unread (sessionStatus.completionUnread on
660
+ // 0.1.6+, the list row's `completed` reminder bit on older hosts) and
661
+ // dropped the moment the session is opened — the tab then reads "something
662
+ // finished, waiting for you" until it is read.
663
+ var doneUnreadMarks = Object.create(null); // sid -> true
664
+ function markDoneUnread(sid) {
665
+ if (!sid || doneUnreadMarks[sid]) return;
666
+ doneUnreadMarks[sid] = true;
667
+ doneActive += 1;
668
+ applyTitle();
669
+ }
670
+ function unmarkDoneUnread(sid) {
671
+ if (!sid || !doneUnreadMarks[sid]) return;
672
+ delete doneUnreadMarks[sid];
673
+ if (doneActive > 0) doneActive -= 1;
674
+ applyTitle();
675
+ }
619
676
  // Title marker ownership: on hosts that publish pending interactions
620
677
  // (uiSession) that source owns the marker, but whichever source saw the wait
621
678
  // first must still mark it (the other one may never report that key at all),
@@ -651,6 +708,14 @@
651
708
  // release path deletes it) and is re-delivered once the page is backgrounded.
652
709
  var quietedAttention = Object.create(null);
653
710
  var lastAttentionAt = 0;
711
+ // One completion can reach this plugin twice: the running edge from the
712
+ // controller snapshots and the completionUnread level flag from
713
+ // sessionStatus are two views of the same host event and land in the same
714
+ // tick (the host publishes both stores from one manager update). A short
715
+ // per-session window collapses them; two genuine completions of one session
716
+ // are never that close — each needs a whole turn in between.
717
+ var doneFiredAt = Object.create(null); // sid -> last done report timestamp
718
+ var DONE_DEDUPE_MS = 250;
654
719
 
655
720
  function hidden() { return document.hidden || document.visibilityState === "hidden"; }
656
721
 
@@ -748,6 +813,28 @@
748
813
  } catch (e) {}
749
814
  return sid;
750
815
  }
816
+ // Should this session's alerts stay muted as a subagent? Every child session
817
+ // a subagent run created is listed as a row carrying `origin: 'subagent'`
818
+ // (plus a parentId), including the rows the host synthesizes from its
819
+ // subagentCatalog. Those alerts describe a step of another conversation's
820
+ // turn, so the ones running in the background are muted by default
821
+ // (config.ignoreSubagent); a forked session has a parentId but no origin and
822
+ // is the user's own conversation, so it keeps alerting. The subagent session
823
+ // the user has open is not background at all: it is on screen like any other
824
+ // conversation, so it alerts by the normal rules (including the
825
+ // current-conversation quiet rule) and is never a mute candidate. Read live
826
+ // rather than cached, so the settings switch and session switches both take
827
+ // effect on the next event without rebinding anything.
828
+ function isSubagentSession(sid) {
829
+ if (!config.ignoreSubagent || !sid) return false;
830
+ if (currentSessionId() === sid) return false;
831
+ try {
832
+ var st = listStore && listStore.getSnapshot();
833
+ var sum = st && st.byId && st.byId[sid];
834
+ if (sum && sum.origin === "subagent") return true;
835
+ } catch (e) {}
836
+ return false;
837
+ }
751
838
  // Returns null once the alert went out, or the copy it would have shown
752
839
  // when only the visible-session rule held it back — the caller queues that
753
840
  // copy by interaction key so it can still fire from the background (the
@@ -769,6 +856,18 @@
769
856
  if (snippet) body = body + "\n" + snippet;
770
857
  fire("done", d.doneTitle, body, false, sessionId);
771
858
  }
859
+ // Every "reply finished" report funnels through here, so the edge path and
860
+ // the completionUnread level flag cannot double-alert one completion. The
861
+ // timestamp is recorded even when the visibility rules suppress the toast:
862
+ // the report was handled (the unread marker is what persists), not queued.
863
+ function reportDone(sessionLabel, snippet, sessionId) {
864
+ if (sessionId) {
865
+ var now = Date.now();
866
+ if (doneFiredAt[sessionId] && now - doneFiredAt[sessionId] < DONE_DEDUPE_MS) return;
867
+ doneFiredAt[sessionId] = now;
868
+ }
869
+ fireDone(sessionLabel, snippet, sessionId);
870
+ }
772
871
  // Test alerts deliberately ignore the visibility rules (attentionHiddenOnly
773
872
  // / doneHiddenOnly): the whole point of a test button is to prove the
774
873
  // channel works while you are looking at the page. Only the master switch
@@ -888,12 +987,20 @@
888
987
  var pendingNow = row.pending || [];
889
988
  var prevPendingKeys = prev ? prev.pendingKeys : [];
890
989
  var wasRunning = prev ? prev.running : false;
990
+ var muted = isSubagentSession(row.id);
891
991
  var newKeys = [];
892
- for (var i = 0; i < pendingNow.length; i++) {
893
- var pk = pendingNow[i].key;
894
- if (prevPendingKeys.indexOf(pk) === -1 && !attentionKeysFired[pk]) {
895
- newKeys.push(pendingNow[i]);
896
- attentionKeysFired[pk] = true;
992
+ // A muted row still feeds the baseline below, so lifting the mute
993
+ // (turning the switch off, or opening that subagent session) resumes
994
+ // alerting from the next edge on instead of replaying old ones.
995
+ // Its keys are left out of attentionKeysFired on purpose: a wait that was
996
+ // never announced must stay announceable once the mute is lifted.
997
+ if (!muted) {
998
+ for (var i = 0; i < pendingNow.length; i++) {
999
+ var pk = pendingNow[i].key;
1000
+ if (prevPendingKeys.indexOf(pk) === -1 && !attentionKeysFired[pk]) {
1001
+ newKeys.push(pendingNow[i]);
1002
+ attentionKeysFired[pk] = true;
1003
+ }
897
1004
  }
898
1005
  }
899
1006
  var hasPendingNow = pendingNow.length > 0;
@@ -905,7 +1012,7 @@
905
1012
  // On hosts that expose uiSession this is the fallback path only: the
906
1013
  // interaction snapshot there is the authoritative source, and it drives
907
1014
  // the title marker itself (see the uiSession watcher below).
908
- if (newKeys.length > 0) {
1015
+ if (!muted && newKeys.length > 0) {
909
1016
  var it = newKeys[0];
910
1017
  var quick = quickFor(it.key, it, row.id);
911
1018
  var copy = fireAttention(it.kind, sessionLabelText(row), textFromPayload(it.payload), !!row.face, row.id, quick);
@@ -916,18 +1023,18 @@
916
1023
 
917
1024
  // 2) a non-selected session finished while we were away (sidebar "done")
918
1025
  var notified = false;
919
- if (!row.face && row.completed && prev && !prev.completed && prev.seeded) {
920
- fireDone(sessionLabelText(row), "", row.id);
1026
+ if (!muted && !row.face && row.completed && prev && !prev.completed && prev.seeded) {
1027
+ reportDone(sessionLabelText(row), "", row.id);
921
1028
  notified = true;
922
1029
  }
923
1030
 
924
1031
  // 3) running true -> false on a session we were watching: a reply finished
925
- if (!notified && prev && prev.seeded && wasRunning && !runningNow && !hasPendingNow) {
1032
+ if (!muted && !notified && prev && prev.seeded && wasRunning && !runningNow && !hasPendingNow) {
926
1033
  // ignore the edge if it happened in the same tick as an attention alert
927
1034
  if (Date.now() - lastAttentionAt < 300) {
928
1035
  /* answered by an attention alert above */
929
1036
  } else {
930
- fireDone(sessionLabelText(row), row.face ? snippetOfLastAssistant(row.faceSnap) : "", row.id);
1037
+ reportDone(sessionLabelText(row), row.face ? snippetOfLastAssistant(row.faceSnap) : "", row.id);
931
1038
  }
932
1039
  }
933
1040
 
@@ -943,6 +1050,16 @@
943
1050
  if (!uiPending && prev && prevPendingKeys.length > 0 && pendingNow.length === 0) {
944
1051
  for (var r = 0; r < prevPendingKeys.length; r++) unmarkAttention(prevPendingKeys[r]);
945
1052
  }
1053
+
1054
+ // Unread-reply marker on hosts whose reminder bit lives in the list row
1055
+ // (`completed`: armed when a non-selected session finishes, cleared on
1056
+ // select — the manager's completedNotifications). Hosts exposing
1057
+ // sessionStatus get their marker from syncDoneFromStatus instead; the
1058
+ // two never share a row, so the mark counter stays balanced.
1059
+ if (!statusDoneSource()) {
1060
+ if (row.completed && !muted) markDoneUnread(row.id);
1061
+ else unmarkDoneUnread(row.id);
1062
+ }
946
1063
  }
947
1064
 
948
1065
  // face -> row adapter (selected session, rich data)
@@ -1010,7 +1127,19 @@
1010
1127
  var cur = currentSessionId();
1011
1128
  bindCurrentFace();
1012
1129
  bindUiSession();
1130
+ // The subagent mute keys on "not the session on screen", and switching
1131
+ // sessions only emits on the list store — re-run the status pass so
1132
+ // the decision tracks the switch: opening a muted subagent session
1133
+ // announces its outstanding wait (under the current-conversation rules),
1134
+ // leaving it puts the next edge back in the muted bucket.
1135
+ evalUiStatus();
1013
1136
  var byId = (st && st.byId) || {};
1137
+ // A vanished row drops its unread-reply mark with it: the host forgets
1138
+ // the reminder bit together with the row, and a gone row reports
1139
+ // nothing. (Hosts with sessionStatus sweep this in syncDoneFromStatus.)
1140
+ if (!statusDoneSource()) {
1141
+ for (var mk in doneUnreadMarks) if (!byId[mk]) unmarkDoneUnread(mk);
1142
+ }
1014
1143
  for (var sid in byId) {
1015
1144
  if (sid === cur) continue; // current handled by the face watcher
1016
1145
  var sum = byId[sid];
@@ -1113,22 +1242,35 @@
1113
1242
  uiStore = store;
1114
1243
  uiSource = found.source;
1115
1244
  uiPending = true; // this watcher now owns the title marker
1116
- uiSub = store.subscribe(function () { try { evalUiPending(); } catch (e) {} });
1245
+ uiSub = store.subscribe(function () { try { evalUiStatus(); } catch (e) {} });
1117
1246
  noteUiBind("bound");
1118
- evalUiPending(); // seed: a wait that predates this plugin still alerts once
1247
+ evalUiStatus(); // seed: a wait that predates this plugin still alerts once
1119
1248
  }
1120
- function evalUiPending() {
1249
+ // True once the sessionStatus store is the live uiSession source (0.1.6+):
1250
+ // its rows then also own the completion-unread level flag and its marker.
1251
+ function statusDoneSource() {
1252
+ return !!(uiPending && uiSource === "sessionStatus");
1253
+ }
1254
+ function evalUiStatus() {
1121
1255
  var svc = lookupService("uiSession");
1122
1256
  var found = interactionStore(svc);
1123
1257
  if (!found || typeof found.store.getSnapshot !== "function") return;
1124
1258
  var snap = found.store.getSnapshot(); // Map<sessionId, interaction|status>
1125
1259
  if (!snap || typeof snap.forEach !== "function") return;
1260
+ if (found.source === "sessionStatus") syncDoneFromStatus(snap);
1126
1261
  var seen = Object.create(null);
1127
1262
  snap.forEach(function (value, sid) {
1128
1263
  var it = pendingOf(value, found.source);
1129
1264
  // A session with no pending interaction is not "seen": leaving it out
1130
1265
  // lets the teardown pass below release any mark its answer replaced.
1131
1266
  if (!it) return;
1267
+ // A background subagent wait is muted and deliberately leaves no
1268
+ // baseline: the row is what isSubagentSession reads, and skipping the
1269
+ // record here also lets the teardown pass drop a mark whose wait was
1270
+ // announced before the mute was switched on (or before the user
1271
+ // switched away from that subagent session). Opening the session later
1272
+ // announces the outstanding wait then, like any first sighting.
1273
+ if (isSubagentSession(sid)) return;
1132
1274
  seen[sid] = true;
1133
1275
  var key = it.key || (it.kind + ":" + sid);
1134
1276
  var prev = uiBaseline[sid];
@@ -1154,6 +1296,42 @@
1154
1296
  delete uiBaseline[sid2];
1155
1297
  }
1156
1298
  }
1299
+ // The sessionStatus rows also carry the reply-finished channel's level
1300
+ // flag (0.1.6-alpha.2+): `completionUnread` lights when a session that is
1301
+ // not on screen finishes and clears when the session is opened, starts
1302
+ // running again, or disappears. Two jobs live here:
1303
+ // * the "完成未读" title marker, held exactly while a background session
1304
+ // is finished-but-unread — open the session and the tab goes quiet;
1305
+ // * one done alert for completions whose running edge this page never saw
1306
+ // (coalesced list snapshots, a plugin remount mid-turn, a completion
1307
+ // that predates this bind). The latch reports each unread instance once;
1308
+ // reportDone's window collapses the double view when the edge did fire.
1309
+ // The edge path stays the reporter for the on-screen session (its snippet
1310
+ // comes from the face snapshot) and for hosts without this store.
1311
+ var uiDoneBaseline = Object.create(null); // sid -> true once this unread reported
1312
+ function syncDoneFromStatus(snap) {
1313
+ var want = Object.create(null);
1314
+ try {
1315
+ snap.forEach(function (value, sid) {
1316
+ if (!value) return;
1317
+ // A stop that awaits input (plan-review / question end the turn with
1318
+ // a pending interaction) is a wait, not a completion — the same rule
1319
+ // as the edge path's !hasPendingNow guard. The on-screen session is
1320
+ // never "unread" (the host clears the flag for it; its completion is
1321
+ // the face edge's job), and a background subagent step is muted.
1322
+ if (!value.completionUnread || value.pendingInteraction || isSubagentSession(sid) || currentSessionId() === sid) return;
1323
+ want[sid] = true;
1324
+ markDoneUnread(sid);
1325
+ if (uiDoneBaseline[sid]) return; // this unread instance already reported
1326
+ uiDoneBaseline[sid] = true;
1327
+ if (Date.now() - lastAttentionAt < 300) return; // same-tick attention owns the moment
1328
+ reportDone(labelForSessionId(sid), "", sid);
1329
+ });
1330
+ } catch (e) {}
1331
+ // answered / re-run / gone / muted: latch and mark go with the flag
1332
+ for (var s in uiDoneBaseline) if (!want[s]) delete uiDoneBaseline[s];
1333
+ for (var m in doneUnreadMarks) if (!want[m]) unmarkDoneUnread(m);
1334
+ }
1157
1335
  function detachUiSession() {
1158
1336
  if (uiSub) { try { uiSub(); } catch (e) {} uiSub = null; }
1159
1337
  uiStore = null;
@@ -1162,6 +1340,8 @@
1162
1340
  // go back to the controller marker path
1163
1341
  for (var s in uiBaseline) unmarkAttention(uiBaseline[s].key);
1164
1342
  uiBaseline = Object.create(null);
1343
+ for (var dm in doneUnreadMarks) unmarkDoneUnread(dm);
1344
+ uiDoneBaseline = Object.create(null);
1165
1345
  uiPending = false;
1166
1346
  }
1167
1347
 
@@ -1179,7 +1359,7 @@
1179
1359
  });
1180
1360
  } catch (e) {}
1181
1361
  // keep in sync with package.json "version" on every release
1182
- api.version = "1.3.1";
1362
+ api.version = "1.5.0";
1183
1363
  api.setConfig = function (patch) { return setConfigLocal(patch); };
1184
1364
  api.resetConfig = function () {
1185
1365
  try { localStorage.removeItem(LS_CONFIG); } catch (e) {}
@@ -1212,6 +1392,7 @@
1212
1392
  attentionHiddenOnly: config.attentionHiddenOnly,
1213
1393
  currentHiddenOnly: config.currentHiddenOnly,
1214
1394
  doneHiddenOnly: config.doneHiddenOnly,
1395
+ ignoreSubagent: config.ignoreSubagent,
1215
1396
  toast: config.toast,
1216
1397
  sound: config.sound,
1217
1398
  quickActions: config.quickActions,
@@ -1229,6 +1410,7 @@
1229
1410
  pendingBaselines: Object.keys(uiBaseline).length,
1230
1411
  firedKeys: Object.keys(attentionKeysFired),
1231
1412
  markedKeys: Object.keys(attentionKeysMarked),
1413
+ doneUnreadMarked: Object.keys(doneUnreadMarks),
1232
1414
  quietedKeys: Object.keys(quietedAttention),
1233
1415
  lastAttentionAt: lastAttentionAt ? new Date(lastAttentionAt).toISOString() : null
1234
1416
  };
@@ -1254,6 +1436,8 @@
1254
1436
  currentQuietHint: "默认开启:等待就发生在你正看着的这个对话里时,只留标签页标记,不弹系统通知、不响提示音,免得挡住审批卡片;页面切到后台后照常提醒。",
1255
1437
  doneVisible: "页面打开时也提醒「回复完成」",
1256
1438
  doneVisibleHint: "默认关闭:「完成」只在页面隐藏或切到后台时才提醒,避免打扰。",
1439
+ subagentQuiet: "子代理会话不提醒",
1440
+ subagentQuietHint: "默认开启:后台子代理(subagent)会话的「需要你」与「回复完成」都不提醒——它们是另一个对话的步骤,完成由所属主对话自己的提醒覆盖;你当前打开的那个子代理会话照常提醒。关掉后恢复逐会话提醒。",
1257
1441
  autoFocus: "点击通知回到对应对话",
1258
1442
  autoFocusHint: "默认开启:点系统通知会把 DSH 带到前台,并直接切到这条提醒所属的对话;关掉后点击通知不做任何事。",
1259
1443
  quickActions: "审批通知上直接裁决",
@@ -1294,6 +1478,8 @@
1294
1478
  currentQuietHint: "On by default: a wait inside the conversation you are looking at keeps only the tab marker — no toast or sound landing on the approval card. Backgrounding the page puts the alert back.",
1295
1479
  doneVisible: "Also alert \"reply finished\" while the page is open",
1296
1480
  doneVisibleHint: "Off by default: \"done\" only alerts while the page is hidden or backgrounded, to avoid noise.",
1481
+ subagentQuiet: "Ignore subagent sessions",
1482
+ subagentQuietHint: "On by default: background sessions spawned by a subagent raise neither \"needs you\" nor \"reply finished\" alerts — they are steps of another conversation, already covered by that conversation's own alert. The subagent session you have open alerts as usual. Turn off to alert for every session.",
1297
1483
  autoFocus: "Click a toast to open that conversation",
1298
1484
  autoFocusHint: "On by default: clicking a toast brings DSH forward and switches to the conversation the alert came from. When off, clicking a toast does nothing.",
1299
1485
  quickActions: "Decide approvals from the toast",
@@ -1421,6 +1607,7 @@
1421
1607
  rowSwitch("attentionVisible", t("attentionVisible"), t("attentionVisibleHint"), !cfg.attentionHiddenOnly, function (v) { patch({ attentionHiddenOnly: !v }); }),
1422
1608
  rowSwitch("currentQuiet", t("currentQuiet"), t("currentQuietHint"), cfg.currentHiddenOnly, function (v) { patch({ currentHiddenOnly: v }); }),
1423
1609
  rowSwitch("doneVisible", t("doneVisible"), t("doneVisibleHint"), !cfg.doneHiddenOnly, function (v) { patch({ doneHiddenOnly: !v }); }),
1610
+ rowSwitch("subagentQuiet", t("subagentQuiet"), t("subagentQuietHint"), cfg.ignoreSubagent, function (v) { patch({ ignoreSubagent: v }); }),
1424
1611
  rowSwitch("autoFocus", t("autoFocus"), t("autoFocusHint"), cfg.autoFocus, function (v) { patch({ autoFocus: v }); }),
1425
1612
  rowSwitch("quickActions", t("quickActions"), t("quickActionsHint"), cfg.quickActions, function (v) {
1426
1613
  patch({ quickActions: v });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
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",