dsh-notify-me 1.7.0 → 1.9.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,59 @@
2
2
 
3
3
  All notable changes to **dsh-notify-me** are documented here.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ### 兼容性
8
+ - 适配 `0.2.1-rc.2`(当前 npm latest):隔离 `DSH_HOME` 起 `dsh web`,启动页 70 个条目里插件排第 59、`application` 批,三个注入目标都在它前面且解析正常;`/plugins/??dsh-notify-me/client.js&rev=…` 返回 1.7.0 构建(140,153 B);无头 Chromium 载入页面后 `apply` 执行,`window.__dshNotifyMe.debug()` 报 `uiSession: bound`、来源 `sessionStatus`。宿主侧公共 store 与 `0.2.0-rc.2` 同形(`sessionStatus`、`retainedBy.mainView`、`uiWorkspace.openSession()`),**不需要改代码**。
9
+ - `package.json` 的 `dsh.compatibility.dshReleases` 补 `0.2.1-rc.2`。`0.2.1-rc.2` 的宿主已不再读取该字段(安装门控只看 `peerDependencies` 与 `engines`),它现在只是给人看的记录。
10
+ - README(中英)同步:徽章、实测版本句、代际表、两坑段落与已知限制都补上 `0.2.1-rc.2`。
11
+
12
+ ### 测试
13
+ - `npm test` 两套件在 0.2.1-rc.2 的 cordis 4.0.5 上通过(`smoke/smoke-test.cjs` 与真 cordis 腿 `smoke/cordis-host-test.mjs`)。
14
+
15
+ ## [1.9.0] — 2026-10-20
16
+
17
+ ### 新增
18
+
19
+ - **品牌标志**:`docs/brand/` 收录 24px 线稿、512→32px 应用图标、favicon 与总览图。母题是「铃铛 + 未读圆点」,设置页页眉直接用线稿版内联渲染(不依赖静态资源)。
20
+
21
+ ### 变更
22
+
23
+ - **设置页重新排版**:页眉(标志 + 一句话说明)之后按「提醒方式 → 什么时候提醒 → 试一试」三段排开,音效来源、外部推送、运行环境与诊断收进折叠的「高级设置」,高级设置右侧列出内含项。原来六张卡片平铺、每行 hint 三四行的样子不再有。
24
+ - 每一行的说明压到一到两行,只讲一件事;「音量」作为「提示音」的附属行缩进显示,「位置」作为「页面浮层提醒」的附属行缩进显示。
25
+ - README 中英双语同步重排,设置章节与截图换成 1.9.0 的实拍整页图(`docs/screenshots/settings-zh.png` / `settings-en.png`)。
26
+
27
+ ### 说明
28
+
29
+ - 插件对 DSH 侧仍然只用 `__ModuleLoader__.load`、`settings.section`、`locale` 三个既有面,1.9.0 没有引入任何新的宿主 API:**0.1.1-rc.2 → 0.2.1-rc.2 的插件条目照旧兼容**,退回旧宿主不需要改配置。
30
+ - 设置左侧导航行的图标由宿主按分区 id 硬编码(只认 account / models / agent-presets / plugins / archived-sessions),`settings.section` 没有图标字段,插件换不了,已在「已知限制」写明。
31
+
32
+ ### 测试
33
+
34
+ - smoke 增补:重排后的控件仍能按 `aria-label` 定位;「高级设置」折叠态默认收起、`settingsBoot({ advanced: true })` 展开后能继续走到音效与外部推送;浮层位置下拉仍在默认可见区。
35
+
36
+ ## [1.8.0] — 2026-10-20
37
+
38
+ ### 新增
39
+ - **页面浮层提醒**(用户建议:分屏时系统通知只出现在主屏,而主屏基本不看):Windows 的横幅固定画在主屏,系统没有设置项、Chromium 也没有 API 能把它指到别的显示器,所以新增一条不经过通知中心的通道。配置项 `overlay`(默认关):在 DSH 页面角落直接画提醒卡片,窗口在哪块屏就出现在哪块屏。
40
+ - 位置 `overlayCorner`:`top-right` / `bottom-right` / `top-left` / `bottom-left`,默认右下,改完立即生效;
41
+ - 不需要通知权限,也不依赖 Service Worker;
42
+ - 审批卡片带「同意 / 拒绝」,走审批卡片自己的 `answer('allowed-once' | 'rejected')`——桌面端(`dsh-app://`,注册不了 Service Worker)因此也能裁决,这是系统通知给不了的;
43
+ - 停留时间:「需要你」20 秒、「回复完成」8 秒,带按钮的审批卡片不自动收;最多叠 4 张;点正文切回对应对话,右上角 × 关闭;
44
+ - 与「系统通知」互不替代,可同时开(主屏 toast + 副屏浮层),也可以只留浮层。
45
+ - 设置页「系统通知」下方新增「页面浮层提醒」开关与「浮层位置」下拉(开关打开时才出现位置选项)。
46
+ - `window.__dshNotifyMe.debug()` 新增 `overlay` 块(`enabled` / `corner` / `cards`)。
47
+
48
+ ### 变更
49
+ - `quickFor()` 的门控从「`quickActions` 且 `toast`」放宽为「`quickActions` 且(`toast` 或 `overlay`)」:浮层是独立的按钮通道,关掉系统通知后仍可裁决。
50
+ - 设置页底部按钮提示按渠道如实分流:浮层开着时不再报「快捷裁决按钮暂不可用」,改为「浮层上的「同意」同样等价于「允许一次」」。
51
+ - 浮层的清理:HMR 重新注册时先撤掉上一份图层;关掉开关立即撤下所有卡片;审批在别处被裁决(页面审批卡片、通知按钮、会话切换)时对应浮层随之消失,不留可点的陈旧卡片。
52
+ - 版本号 1.7.0 → 1.8.0;`window.__dshNotifyMe.version` 与 `package.json` 同步;README 中英同步(简介、基础设置、新增「副屏与浮层提醒」一节、提醒方式、控制台配置块、已知限制),client 体积徽章 137 → 152 KiB。
53
+
54
+ ### 测试
55
+ - `smoke/smoke-test.cjs` 新增两段。① 浮层通道:默认不动 DOM、固定图层挂在 `<body>` 下、卡片类名/角色/文案、位置切换与非法值回落、4 张上限、点卡片与 × 撤下、关开关撤层、没有 `Notification` 的环境里审批按钮走完 `decidePending()` 并给出「已模拟同意」反馈、`debug().overlay` 上报。② 设置页浮层两行:开关与位置下拉的出现与持久化、文案、按钮提示分流。
56
+ - `npm test` 两套件通过。
57
+
5
58
  ## [1.7.0] — 2026-10-16
6
59
 
7
60
  ### 新增
package/README.en.md CHANGED
@@ -1,121 +1,246 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/brand/icon-app-128.png" width="76" height="76" alt="dsh-notify-me">
4
+
1
5
  # dsh-notify-me
2
6
 
7
+ **Step away from DSH and know when to come back.**
8
+
3
9
  [简体中文](README.md) | English
4
10
 
5
11
  [![npm version](https://img.shields.io/npm/v/dsh-notify-me?style=flat-square&label=npm&color=cb3837)](https://www.npmjs.com/package/dsh-notify-me)
6
- [![npm downloads](https://img.shields.io/npm/dm/dsh-notify-me?style=flat-square&label=downloads&color=1F883D)](https://www.npmjs.com/package/dsh-notify-me)
7
- ![License](https://img.shields.io/badge/license-MIT-blue)
8
- ![Platform](https://img.shields.io/badge/platform-browser-blue)
9
- ![Size](https://img.shields.io/badge/bundle-%7E79KB-green)
12
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-notify-me?style=flat-square&label=downloads%2Fmonth&color=1F883D)](https://www.npmjs.com/package/dsh-notify-me)
13
+ [![npm total downloads](https://img.shields.io/npm/dt/dsh-notify-me?style=flat-square&label=total%20downloads&color=1F883D)](https://www.npmjs.com/package/dsh-notify-me)
14
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
15
+ [![DSH Web and Desktop](https://img.shields.io/badge/DSH-Web%20%7C%20Desktop-blue?style=flat-square)](#installation)
16
+ [![Client bundle size](https://img.shields.io/badge/client-%7E156%20KiB-green?style=flat-square)](lib/client.js)
17
+ [![DSH Compatibility](https://img.shields.io/badge/DSH-tested%200.2.1--rc.2-6366f1.svg?style=flat-square)](#compatibility)
10
18
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
11
19
 
12
- ---
20
+ </div>
13
21
 
14
- **Step away from the DeepSeek Harness web UI — and still know what's going on.** When the agent stops and needs your input, or when a reply finishes in the background while you're in another app, dsh-notify-me alerts you with a desktop notification, a sound, and a tab-title marker. Approval notifications even carry Approve / Reject buttons, so you can settle the request without switching back. Toggle it and pick the notification language right from **Settings → Notify me**.
22
+ [Install](#installation) · [Preview](#ui-preview) · [Settings](#configure-it-from-dsh-settings) · [Compatibility](#compatibility) · [Changelog](CHANGELOG.md)
15
23
 
16
- ---
24
+ A notification plugin for DSH Web / Desktop, so you can keep full access off without watching the page all day.
17
25
 
18
- ## Why you need it
26
+ - **When input is needed:** approval, plan review, and questions trigger notifications, sound, and a title marker.
27
+ - **When a background reply finishes:** return to the result; click the notification to open its conversation.
28
+ - **When you leave your computer:** optionally forward alerts to Discord, Bark, Gotify, or your own webhook.
29
+ - **When you do not watch the primary display:** an optional in-page overlay follows the DSH window onto whichever screen you are looking at (system notifications only ever appear on the primary display).
30
+
31
+ > Keep the page or desktop app running; backgrounding / minimizing is fine. System notifications require permission. Approval buttons need a Service Worker capable Web environment (such as `http://127.0.0.1` or `https`); Desktop notifications have no buttons — turn on the in-page overlay to decide from there instead (see [Secondary displays and the overlay](#secondary-displays-and-the-overlay)).
32
+
33
+ > 🔎 Looking for it? Search `dsh-notify-me`, **message alerts**, **notification**, **desktop notification**, **reminder**, **approval alert**, **external notifications**, **Discord / Bark / Gotify push** or **可操作提醒 / 消息提醒 / 桌面通知 / 需要你操作** (npm keywords include both English and Chinese terms).
19
34
 
20
- Both of DSH's permission modes cost you something:
35
+ ## UI Preview
21
36
 
22
- | Mode | What it costs you | Cost type |
23
- | --- | --- | --- |
24
- | No full access (`workspace-write` + ask) | **Waiting** — the agent stops on an approval; if you aren't watching the page, you're just burning wall-clock time | Time, growing with the number of sessions |
25
- | Full access | **Irreversible risk** — you give up the ability to intercept before anything happens | Risk, independent of whether you watch |
37
+ ![Plain DSH notification shown through Edge on Windows, without approval buttons](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/notify-toast.png)
26
38
 
27
- dsh-notify-me decouples **supervision** from **sitting in front of the screen**: it pulls you back with a system notification only when a decision is genuinely needed (approval / plan review / question) or a reply finishes — **so you can afford to keep full access off** instead of trading away interception for speed. It does not lower the risk of full access itself (it is not a sandbox); it just drives the waiting cost of ask-mode down to roughly zero.
39
+ *Plain notification example · Windows / Edge · no approval buttons.* [View full size](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/notify-toast.png)
28
40
 
29
- ---
41
+ ![Three overlay cards in the bottom-right corner of the DSH page: question, approval request with Approve / Reject, and reply finished](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-en.png)
30
42
 
31
- > 🔎 Looking for it? Search `dsh-notify-me`, **message alerts**, **notification**, **desktop notification**, **reminder**, **approval alert**, **external notifications**, **Discord / Bark / Gotify push** or **可操作提醒 / 消息提醒 / 桌面通知 / 需要你操作** (npm keywords include both English and Chinese terms).
43
+ *In-page overlay (1.8.0) · the real plugin rendered on a demo page · for when system notifications cannot reach the secondary display; cards appear on whichever display the DSH window is on.* [View full size](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-en.png)
32
44
 
33
- ## UI Preview
45
+ <details>
46
+ <summary>See the settings interface</summary>
47
+
48
+ ![The Notify me settings page: header, how you get told, when to alert, try it, and the collapsed Advanced block](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/settings-en.png)
49
+
50
+ The real settings component (v1.9.0 defaults) rendered full-page in a standalone demo shell; the sidebar is kept for context, it is not the DSH shell.
51
+ </details>
52
+
53
+ ## Installation
54
+
55
+ ### DSH Web
56
+
57
+ With the DSH CLI already installed, run in a terminal:
58
+
59
+ ```powershell
60
+ # Install and auto-mount into the web profile
61
+ dsh plugin --profile web add dsh-notify-me
62
+ ```
63
+
64
+ Restart `dsh web`, then hard-refresh the page (Ctrl+Shift+R). If you use another Web profile, replace `web` with its actual name.
65
+
66
+ ### DSH Desktop
67
+
68
+ 1. Open **Plugins → Add plugin** in the desktop application.
69
+ 2. Enter the npm package name `dsh-notify-me`, follow the installation prompts, and confirm that the plugin is enabled.
70
+ 3. Quit DSH Desktop completely and relaunch it so the plugin loads.
71
+
72
+ The Electron application manages the `desktop` profile. Do not change the Web command above to `--profile desktop`: the CLI rejects that operation. Installing into the Web profile does not automatically install the plugin into Desktop.
34
73
 
35
- | System notification (screenshot from Windows) |
36
- | --- |
37
- | ![DSH notification](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/notify-toast.png) |
74
+ ## First-use check
38
75
 
39
- [Changelog](CHANGELOG.md)
76
+ 1. Click once in the DSH page and allow notifications when prompted. If you previously declined, allow them in the browser's site settings. The operating system must also allow notifications from the browser / DSH Desktop.
77
+ 2. Open **Settings → Notify me**. Confirm that the section and plugin version at its foot appear, and leave the master switch, system notifications, and sound enabled.
78
+ 3. Click **Test "reply finished"** on the settings page. A test notification should appear and sound according to your cue settings. This local test does not forward to external endpoints and ignores the "while the page is open" toggle.
79
+
80
+ Web users can also test approval buttons. On Desktop that test sends a plain notification: **no Approve / Reject buttons is the expected result**.
81
+
82
+ **Optional console check** — in environments with DevTools, you can also run:
83
+
84
+ ```js
85
+ window.__dshNotifyMe.test("done") // "reply finished" sample
86
+ window.__dshNotifyMe.test("attention") // "needs your input" sample
87
+ window.__dshNotifyMe.test("approval") // Approve / Reject with an active Web bridge; buttonless on Desktop
88
+ window.__dshNotifyMe.debug() // the bridge field reports the button channel: "active" = buttons work
89
+ ```
90
+
91
+ If no test notification appears, first check:
92
+
93
+ - the notification permission was declined — click anywhere on the page and choose **Allow** (address-bar lock → Site settings → Notifications → Allow → reload);
94
+ - the DSH process wasn't restarted after install;
95
+ - your OS/browser notification settings block the browser's toasts.
96
+
97
+ ## Why you need it
98
+
99
+ - **Without full access (`workspace-write` + ask):** keep approval before actions, but a missed request leaves the agent waiting.
100
+ - **With full access:** fewer approval interruptions, at the cost of interception before actions.
101
+
102
+ dsh-notify-me decouples **supervision** from **sitting in front of the screen**: it pulls you back with a system notification only when a decision is genuinely needed (approval / plan review / question) or a reply finishes — **so you can afford to keep full access off** instead of trading away interception for speed. It does not lower the risk of full access itself (it is not a sandbox); it reduces the time spent waiting on missed approvals in ask-mode.
40
103
 
41
104
  ## What it alerts about
42
105
 
43
- | When | What you get | Default |
44
- | --- | --- | --- |
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; 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) |
106
+ | Event | Default behavior |
107
+ | --- | --- |
108
+ | 🔔 Input needed | Alerts even in the foreground; the conversation on screen keeps only a title marker |
109
+ | ✅ Reply finished | Alerts while the page is hidden / backgrounded |
110
+
111
+ **Input needed** covers approval requests, plan review, and questions (`ask_user_question`). Supported Web environments show Approve / Reject buttons; Desktop does not.
112
+
113
+ **Reply finished** also covers background sessions. A finished but unread session keeps the `✅ Reply finished ·` title marker until opened. Title markers are independent of the "while the page is open" toggles.
47
114
 
48
115
  > **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.
49
116
 
50
117
  ## Configure it from DSH Settings
51
118
 
52
- Open **Settings → Notify me** (refresh the page once after installing):
119
+ Open **Settings → Notify me**. The page runs as how you get told, when to alert, and a try-it block; the knobs you rarely touch fold into **Advanced** at the bottom.
120
+
121
+ ### How you get told
122
+
123
+ - **Enable reminders**: master switch — off means no toasts, no sounds, and the tab title is left alone;
124
+ - **System notifications** (on by default): native toasts through the browser, landing in the Windows / macOS notification center;
125
+ - **In-page overlay** (off by default): cards drawn in the page corner, on whichever display the window sits on — system notifications only land on the primary display. No permission needed, and the Desktop shell can Approve / Reject right on the card. Runs alongside toasts; pick one of four corners when expanded. See [Secondary displays and the overlay](#secondary-displays-and-the-overlay);
126
+ - **Sound / Volume**: master cue switch and a volume slider; what it plays comes from Advanced → Alert sound;
127
+ - **Decide approvals from the toast** (on by default): approval toasts carry Approve / Reject that settle the request in place. **Desktop notifications have no buttons** (`dsh-app://` cannot register a Service Worker) — leave the overlay on instead; the page states plainly which channel can carry buttons right now.
128
+
129
+ ### Secondary displays and the overlay
130
+
131
+ Windows draws system notification banners on the primary display only. There is no setting in Windows and no API in the browser to aim one at another monitor, so with DSH parked on a second screen and the primary display unwatched, a toast is as good as not sent.
132
+
133
+ The **in-page overlay** skips the notification center and draws the card inside the DSH page instead: wherever the window is, the card is there too.
134
+
135
+ - **Position** — top right / bottom right / top left / bottom left, default bottom right; changes apply immediately;
136
+ - **No notification permission needed** — it works when permission was never granted, and on the Desktop shell where buttonless toasts are all the OS channel can do;
137
+ - **Approval cards carry Approve / Reject** — the same decision as Allow once / Reject on the approval card, and they work on the Desktop too (no Service Worker involved). Clicking the card body opens its conversation;
138
+ - **Lifetime** — "needs you" cards retire after 20s, "reply finished" after 8s; an approval card with buttons stays until it is decided or closed. At most 4 cards stack at once;
139
+ - **Relation to system notifications** — the two switches are independent. With both on, the primary display gets toasts and the second screen gets the overlay; the overlay alone is also a valid setup.
140
+
141
+ The overlay only knows which screen the window is on, not which screen you are looking at. If the window stays on the primary display while you watch another one, it stays invisible — same as a toast.
142
+
143
+ ### When to alert
144
+
145
+ - **Also alert "needs you" while the page is open** (on): turn it off to hear only from the background;
146
+ - **Stay quiet for the conversation on screen** (on): a wait belonging to the conversation you are looking at keeps only the tab marker so the approval card stays readable; backgrounding restores alerts;
147
+ - **Also alert "reply finished" while the page is open** (off): by default only background finishes alert;
148
+ - **Ignore subagent sessions** (on): skip background sessions spawned by a subagent — the parent conversation covers them; the one you have open still alerts;
149
+ - **Click a toast to open that conversation** (on): the click brings DSH forward and switches conversation; with several tabs open only one is raised, and off means a click does nothing. Desktop window-raising details live in [Runtime & diagnostics](#runtime--diagnostics).
150
+
151
+ ### Try it
152
+
153
+ Three buttons send one "needs you", one "reply finished" and one "approval buttons" test alert with the current settings — deliberately not limited by the switches above. A line underneath states which channel can carry approval buttons right now.
154
+
155
+ The plugin version sits at the bottom of the page (`dsh-notify-me v…`) — copy that line when reporting a bug or checking whether an update landed.
156
+
157
+ ### Advanced
158
+
159
+ Collapsed by default. It holds the three things you rarely touch: alert sounds, external forwarding, and runtime & diagnostics — each documented below.
160
+
161
+ ### Sound
162
+
163
+ Choose a built-in cue, custom audio file, or system notification sound independently for each alert kind.
164
+
165
+ <details>
166
+ <summary>Custom audio, previews, and fallback behavior</summary>
53
167
 
54
- - **Enable reminders** — master switch; when off, no toasts, no sounds and the tab title is never touched;
55
- - **Environment** (auto-detect by default) — identifies Web vs Desktop (DSH Desktop, `dsh-app://`) from the page protocol, with a manual override for hosts where detection comes out wrong: on the Desktop a toast click rides the `dsh://` deep link to raise the window and quick-decision buttons are unavailable; on the Web it is `window.focus()` plus the Service Worker bridge. Whenever the override disagrees with detection, the settings page says what detection saw;
56
- - **System notifications / Sound / Volume** — toast toggle, cue toggle and a volume slider;
57
168
  - **Alert sound** (picked separately for "needs you" and "reply finished") — built-in cue (default) / custom audio file / system notification sound. "Custom audio file" takes one local audio file in the settings page (1 MiB limit, stored in the browser, with preview and remove); "system notification sound" plays no plugin cue at all — the toast still appears and the operating system supplies the sound, so its timbre follows your system notification settings; in that mode "Preview" sends a test alert instead, because the sound only exists as part of a toast and that is the sound you hear. Any source that has no file, cannot be read, or does not fit falls back to the built-in cue, so an alert never goes silent;
169
+
170
+ </details>
171
+
172
+ ### External push
173
+
174
+ Off by default. Supports Discord Webhook / Bark / Gotify / custom webhooks. Alert text is sent to the addresses you enter; use trusted endpoints.
175
+
176
+ <details>
177
+ <summary>Target URLs, forwarding switches, and test messages</summary>
178
+
58
179
  - **External notifications** (default off) — forward each alert to your own push endpoints as well, so it reaches you away from the machine: **Discord Webhook** / **Bark** / **Gotify** / **custom webhook**, any number of targets, each with a type and URL (Discord takes the webhook URL; Bark `https://api.day.app/<device key>`; Gotify `https://<server>/message?token=<app token>`; a custom endpoint receives one POST of JSON: `{source, event, title, body, sessionId, time}`). "Forward 'needs you'" and "Forward 'reply finished'" switch independently; **Send test notification** sends one test message to every target (not limited by those two switches, and never pretending to be a real alert). Forwarding follows exactly the same trigger rules as local alerts ("stay quiet for the conversation on screen" and friends apply to it too); the alert text is sent as-is to the addresses you fill in, so **only use endpoints you trust**;
59
- - **"Needs you" alerts while the page is open** (default on) and **"Reply finished" alerts while the page is open** (default off);
60
- - **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;
61
- - **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;
62
- - **Click a toast to open that conversation** (default on): clicking brings DSH to the front and switches to the conversation the alert came from. With several DSH tabs open, one click raises exactly one window (a visible one first) and switches only that one — the others stay put; when off, clicking a toast does nothing. On the Desktop (DSH Desktop) the click likewise raises the window and switches conversations: page-side `window.focus()` cannot raise a native Electron window, so since 1.5.2 the plugin rides the app's own `dsh://open` deep link instead (a window hidden to the tray comes back too);
63
- - **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. **Buttons are unavailable on the Desktop** (`dsh-app://` pages cannot register a Service Worker): approval toasts fall back to their buttonless form there, and the settings page says so;
64
- - **Notification language** — follow the interface / 简体中文 / English: controls the language of the alert text and the `🔔 …` title marker;
65
- - **Test buttons** — send one "needs you", "reply finished" or "approval buttons" test alert with the current settings (**not** limited by the "while the page is open" toggles; the "needs you" test's title marker clears itself after ~6s);
66
- - **Version info** — the settings page always shows the installed plugin version at its foot (`dsh-notify-me v…`); quote that line in bug reports or to check which build you are on.
180
+
181
+ </details>
182
+
183
+ ### Runtime & diagnostics
184
+
185
+ The last block inside Advanced. The page protocol is detected automatically to tell Web from Desktop (`dsh-app://`); override it by hand if detection is wrong — Desktop clicks raise the window through the `dsh://` deep link and carry no quick-decision buttons, while Web uses `window.focus()` plus the Service Worker bridge. When the override disagrees with detection the page says so.
186
+
187
+ <details>
188
+ <summary>Alert language, Desktop window raising, reset</summary>
189
+
190
+ - **Alert language**: follow the UI / Simplified Chinese / English — governs alert copy and the `🔔 …` title marker;
191
+ - **How the Desktop window is raised**: `window.focus()` cannot lift a native Electron window, so since 1.5.2 the page drives the app's own `dsh://open` deep link — it works even when the window is parked in the tray;
192
+ - **Reset settings**: restores every switch and cue setting to the factory defaults (webhook targets and chosen audio files are kept).
193
+
194
+ </details>
195
+
196
+ > The settings sidebar icon is rendered by the DSH settings shell: it hard-codes icons per section id (account / models / agent-presets / plugins / archived-sessions) and gives every other section the default gear — `settings.section` has no icon field, so the plugin's bell can only appear inside its own page.
67
197
 
68
198
  > Notification permission is required: click once on the page → **Allow** (or address-bar lock → Site settings → Notifications → Allow → reload). On macOS, also allow the browser under System Settings → Notifications.
69
199
 
70
200
  ## How it alerts
71
201
 
72
- - **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)
202
+ - **System notification** — a toast through the browser into the Windows action center / macOS Notification Center (clicking it opens the conversation it came from; supported Web environments offer approval buttons, Desktop does not)
203
+ - **In-page overlay** (optional, default off) — an alert card drawn in the corner of the DSH page, on whatever display the window is on, optionally with Approve / Reject buttons; see [Secondary displays and the overlay](#secondary-displays-and-the-overlay)
73
204
  - **Sound** — WebAudio beeps by default (distinct patterns for "needs you" vs "done"); each kind can instead play a picked audio file, or hand the sound over to the operating system's own notification sound
74
205
  - **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
75
206
  - **External notifications** (optional, default off) — the same alert is also forwarded to the push targets you configure: Discord Webhook / Bark / Gotify / custom webhook — so the push channel on your phone picks it up while you step away from the machine
76
207
 
77
208
  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).
78
209
 
79
- ## Installation
210
+ ## Compatibility
80
211
 
81
- ```powershell
82
- # Official DSH plugin command — installs and auto-mounts into the web profile
83
- dsh plugin --profile web add dsh-notify-me
84
- ```
212
+ Per-release adaptation records live in [package.json](package.json), under `dsh.compatibility.dshReleases`. The current host (`0.2.1-rc.2`) no longer reads that field — installation gates only on `peerDependencies` and `engines` — so it is a human-readable record, not a gate.
213
+
214
+ Verified to actually activate on `0.1.2-rc.1`, `0.1.5-rc.1`, `0.1.5-rc.2` and `0.2.0-rc.2` (each booted in its own profile: the plugin entry lands in the boot page's `__DSH_BOOT__` with its inject dependencies resolved, `/plugins/??dsh-notify-me/client.js&rev=…` serves a build containing the new code, and the Settings → Notifications section registers, i.e. the plugin's `apply` really runs), and previously on `0.1.1-rc.2`.
215
+
216
+ `0.2.1-rc.2` (current latest) got its own isolated run: an isolated `DSH_HOME` booting `dsh web` (70 boot entries) placed the plugin entry 59th, in the `application` batch, with all three inject targets present and ordered before it; `/plugins/??dsh-notify-me/client.js&rev=…` served the 1.7.0 source build (140,153 B); loaded in a headless Chromium page, `apply` ran — `window.__dshNotifyMe.debug()` reported `uiSession: bound`, `uiSessionSource: sessionStatus`, `bridge: active`. Host-side shapes match `0.2.0-rc.2`; no plugin code change was needed.
85
217
 
86
- Restart `dsh web`, then hard-refresh the page (Ctrl+Shift+R).
218
+ <details>
219
+ <summary>Host generations and adaptation diagnostics</summary>
87
220
 
88
- **Compatibility** — the plugin picks its interaction source per host generation; per-release declarations live in `package.json` under `dsh.compatibility.dshReleases`:
221
+ The plugin picks its interaction source per host generation; the table below marks where each shape appeared — it is not a gate (the current host does not read `dshReleases`):
89
222
 
90
223
  | Host generation | "Needs you" interaction source | Current session | Toast-click navigation |
91
224
  | --- | --- | --- | --- |
92
225
  | `<= 0.1.2-alpha.1` | controller snapshot `pending[]` | snapshot `current` | `sessions.open()` |
93
226
  | `0.1.2-alpha.2 .. 0.1.6-alpha.1` | `uiSession.pendingInteractions` | snapshot `current` | `sessions.open()` |
94
- | `>= 0.1.6-alpha.2` (incl. `0.2.0-rc.2`) | `uiSession.sessionStatus` | `retainedBy.mainView` | `uiWorkspace.openSession()` |
227
+ | `>= 0.1.6-alpha.2` (incl. `0.2.0-rc.2`, `0.2.1-rc.2`) | `uiSession.sessionStatus` | `retainedBy.mainView` | `uiWorkspace.openSession()` |
95
228
 
96
- 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.
229
+ 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` shapes npm's peer warning — `0.2.1-rc.2` already falls inside `>=0.2.0-rc.1 <0.3.0-0`, so no exemption is needed; `dshReleases` is just an archive, real compatibility is per-release testing, and untested new versions fall back to shape-driven behaviour.
97
230
 
98
- 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.
231
+ 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. `0.2.1-rc.2` changed neither: `sessionStatus` is still a Map of `{running, pendingInteraction, completionUnread}`, `uiWorkspace.openSession(id)` is unchanged, and the session list still has no `current`.
99
232
 
100
- **Verify** — open the DevTools console and run:
101
-
102
- ```js
103
- window.__dshNotifyMe.test("done") // "reply finished" sample
104
- window.__dshNotifyMe.test("attention") // "needs your input" sample
105
- window.__dshNotifyMe.test("approval") // "approval buttons" sample: try the Approve / Reject buttons on the toast
106
- window.__dshNotifyMe.debug() // the bridge field reports the button channel: "active" = buttons work
107
- ```
233
+ </details>
108
234
 
109
- Nothing happened? 90% of the time it's one of:
110
-
111
- - the notification permission was declined — click anywhere on the page and choose **Allow** (address-bar lock → Site settings → Notifications → Allow → reload);
112
- - the DSH process wasn't restarted after install;
113
- - your OS/browser notification settings block the browser's toasts.
235
+ `1.9.0` neither widens nor narrows that range: the settings page is plain React rendering over the existing CSS variables (every colour carries a fallback) and adds no host API. The overlay, title markers and window raising still go through the 1.7 / 1.8 paths, so **plugin entries stay compatible from 0.1.1-rc.2 through 0.2.1-rc.2**, and stepping back to an older host needs no config change. The only new surface is visual: the bell in the page header is the plugin's own SVG (`docs/brand/`).
114
236
 
115
237
  ## Advanced: console configuration
116
238
 
117
239
  The Settings page is the primary entry; preferences live in `localStorage` (key `dshNotifyMe.config`) and can be scripted from the console:
118
240
 
241
+ <details>
242
+ <summary>Console API and storage reference</summary>
243
+
119
244
  ```js
120
245
  window.__dshNotifyMe.config // view current config
121
246
  window.__dshNotifyMe.setConfig({
@@ -132,6 +257,8 @@ window.__dshNotifyMe.setConfig({
132
257
  volume: 0.5, // 0..1
133
258
  autoFocus: true, // clicking a toast opens the conversation it came from
134
259
  quickActions: true, // Approve / Reject buttons on approval toasts
260
+ overlay: false, // in-page overlay alert (follows the DSH window to its display; no notification permission needed)
261
+ overlayCorner: "bottom-right", // overlay anchor: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'
135
262
  externalEnabled: false, // external-notification forwarding master switch (default off: no endpoint, nothing is sent)
136
263
  externalAttention: true, // forward "needs you" (exactly the same trigger rules as local alerts)
137
264
  externalDone: true, // forward "reply finished"
@@ -139,15 +266,24 @@ window.__dshNotifyMe.setConfig({
139
266
  })
140
267
  window.__dshNotifyMe.resetConfig() // restore defaults (also drops any picked audio files)
141
268
  window.__dshNotifyMe.decide("approval:3", "allowed-once") // programmatic decision: "allowed-once" | "rejected"
142
- window.__dshNotifyMe.testExternal() // send one test notification to every push target, resolves {sent, unverified, failed, statuses}
269
+ window.__dshNotifyMe.testExternal() // explicit outbound test to valid targets; resolves {sent, unverified, failed, statuses}
143
270
  ```
144
271
 
272
+ > **External-test switch behavior:** the settings page's "Send test notification" button requires both the plugin and external-forwarding master switches to be on, but ignores the two per-kind forwarding switches. The console `testExternal()` call is an explicit test: with valid configured targets, it sends to them even when those master switches are off; with no valid targets it sends nothing. Ordinary `test("done")` / `test("attention")` / `test("approval")` calls only test local alerts and never forward externally.
273
+
145
274
  > Picked audio files do not live in `dshNotifyMe.config`: each kind gets its own key (`dshNotifyMe.sound.done` / `dshNotifyMe.sound.attention`, holding `{name, size, data}` with a data URL), because a file inflates by ~1.37x once base64-encoded and would slow down every config read. `resetConfig()` clears both keys along with the config.
146
275
  >
147
276
  > To see which source is live, check `soundDone` / `soundAttention` and `customSoundDone` / `customSoundAttention` in `window.__dshNotifyMe.debug()`.
148
277
 
278
+ </details>
279
+
149
280
  ## How it works
150
281
 
282
+ The reminder core watches session state and pending interactions. The page handles approval decisions; Desktop window activation uses the app deep link.
283
+
284
+ <details>
285
+ <summary>Event sources, approval bridge, and Desktop window activation</summary>
286
+
151
287
  The reminder core picks its interaction source per host generation (see the table above); the current session always comes from the client `sessions` service:
152
288
 
153
289
  - the **selected session's** `SessionSnapshot`: a `running: true → false` edge means a reply finished;
@@ -169,20 +305,25 @@ DSH Desktop is a thin Electron shell and its page lives under `dsh-app://app`: D
169
305
 
170
306
  Verified on the real shell (DSH Desktop 0.2.0-rc.2, three rounds of retesting in issue #9): a toast click raises the window and switches the conversation in one step, stable across sessions and with the window in the background. Timing of the fire is what makes the chain work: the deep link must be launched from a **user-gesture handler** — the notification's `onclick` carries one, which is why the product path works — while gesture-less scripted injection (an iframe appended from a CDP evaluate, say) is silently dropped by Chromium and the external protocol never launches, which looks exactly like the channel being blocked.
171
307
 
308
+ </details>
309
+
172
310
  ## Known limitations
173
311
 
174
312
  - The page must be open for alerts to fire (background tab / minimized is fine; closing the tab stops it — that's inherent to a browser-layer plugin).
175
313
  - Notifications appear through the browser, so the browser needs "show notifications" permission in the OS notification settings, and notification-center "Do Not Disturb" must not suppress them.
176
314
  - The first sound/toast of each page load needs one user click on the page first (browser autoplay + permission policy).
177
315
  - Toasts are only produced once the notification permission is granted; declining means sound + title marker only.
316
+ - System notification banners are drawn on the primary display only: Windows has no setting and Chromium no API to aim one at another monitor, so on a multi-monitor setup the second screen never receives them. Use the in-page overlay for that — it follows the DSH window to its display.
178
317
  - Quick-decision buttons need a Service Worker: they work on `http://127.0.0.1` and `https`, not on plain `http://192.168.x.x`-style LAN addresses, and some browsers or desktop hosts never render notification buttons. **The Desktop (`dsh-app://`) is tested and cannot register a Service Worker**, so buttons never render there and approval toasts fall back to their buttonless form. The `bridge` field of `window.__dshNotifyMe.debug()` says why; everything else keeps working.
179
318
  - macOS: in the browser, alerts, sounds, the title marker and toast-click navigation carry no OS assumptions and work as on Windows. Two caveats: DSH Desktop (Electron) notifications require the app to be code-signed — unsigned builds simply emit no notification — and whether the official macOS build satisfies that is untested; and notification buttons on the macOS desktop host are likewise untested (the Windows desktop host is tested and renders none, see the line above), falling back to the buttonless form above.
180
319
  - Config lives in browser `localStorage`: switching browsers/devices or clearing site data returns to defaults (one-click restore in the Settings page).
181
- - On `>= 0.1.6` (including `0.2.0-rc.2`) the "reply finished" toast body carries only the session label, no assistant snippet: host snapshots no longer expose `nodes`. The alert itself still fires.
320
+ - On `>= 0.1.6` (including `0.2.0-rc.2`, `0.2.1-rc.2`) the "reply finished" toast body carries only the session label, no assistant snippet: host snapshots no longer expose `nodes`. The alert itself still fires.
182
321
  - The title marker is written into `document.title` alongside the host: when the host recomputes the title it can overwrite the marker until the next alert event rewrites it.
183
322
  - "Stay quiet for the conversation on screen" is judged by `document.hidden`: a window fully covered by other apps still counts as visible, so waits in the current conversation stay quiet there too.
184
323
  - External notification forwarding is subject to browser CORS rules: an endpoint that does not allow cross-origin requests falls back to a "sent but unverifiable" delivery (no-cors) and is reported honestly as such; Discord webhooks do allow cross-origin, so delivery to them is confirmed. HTTP errors are recorded as failures and never blindly retried. Forwarding runs only after the local alert has decided to fire, so anything the rules silence is never sent out.
185
324
 
325
+ - **The settings sidebar row keeps the host's default gear icon**: the settings shell hard-codes icons per section id, gives every other section the default gear, and `settings.section` exposes no icon field — the plugin cannot change it. Its own bell only appears inside the settings page and in this README.
326
+
186
327
  ## Development
187
328
 
188
329
  ```powershell