dsh-notify-me 1.0.0 → 1.1.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 +36 -0
- package/README.en.md +123 -0
- package/README.md +72 -64
- package/lib/client.js +387 -25
- package/package.json +24 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **dsh-notify-me** are documented here.
|
|
4
|
+
|
|
5
|
+
## [1.1.0] — 2026-09-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **设置入口**:DSH web 的 **设置 → 通知提醒** 新增独立设置页(`settings.section` 槽位,浏览器半身可选 React 呈现,缺失能力时自动降级为纯提醒):主开关、系统通知/提示音/音量、两种场景的可见时提醒开关、点击通知聚焦开关,以及 **通知语言**(跟随界面 / 简体中文 / English)与一键测试、恢复默认。
|
|
9
|
+
- **通知语言**:提醒文案(通知标题/正文/`🔔 …` 标题标记)不再硬编码中文——`language` 配置支持 `auto`(跟随 DSH 界面语言)/ `zh` / `en`;页面重新加载后按需即时解析。
|
|
10
|
+
- **总开关**:`enabled` 配置(默认开),关闭后不发通知、不响铃、不改标签页标题。
|
|
11
|
+
- **可发现性**:`package.json` 新增中英关键词(消息提醒 / 可操作提醒 / 桌面通知 / 需要你操作 / 回复完成提醒 / 后台完成提醒 / desktop-notification / message-alert / approval-alert 等),description 与 README 首页同步补齐搜索词。
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `lib/client.js` 重写:提醒核心保持零第三方依赖与自包含;新增语言解析/多语言文案表与可选 Settings 页面(依赖 `slots`/`locale`/`react`,注册前做能力守卫)。
|
|
15
|
+
- 冒烟测试扩展:覆盖主开关、`language` 中英切换后的通知文案与标题标记、`resetConfig` 恢复新增默认项。
|
|
16
|
+
|
|
17
|
+
## [1.0.1] — 2026-09-08
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- README is now **简体中文-primary** homepage with a `简体中文` homepage plus an English version (`README.en.md`), plus this changelog, mirroring the ecosystem's documentation conventions.
|
|
21
|
+
- `package.json`: added `publishConfig.access: public`, `repository`/`homepage`/`bugs` links, and added `README.en.md` + `CHANGELOG.md` to the published `files` set.
|
|
22
|
+
|
|
23
|
+
## [1.0.0] — 2026-09-08
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- First release. Browser-layer desktop reminder plugin for the DeepSeek Harness web UI.
|
|
27
|
+
- 🔔 **Needs-your-input alerts**: fires on new pending interactions — sandbox `approval`, `plan-review`, and `question` (`ask_user_question`) — with toast + sound + `🔔 需要你 ·` tab-title marker (alerts even while the page is visible).
|
|
28
|
+
- ✅ **Reply-finished alerts**: detects the selected session's `running: true → false` edge (and background sessions' completion) — toast + sound, by default only while the page is hidden/backgrounded.
|
|
29
|
+
- Clicking a toast focuses the DSH window.
|
|
30
|
+
- Zero-UI, zero-server-logic: pure client-side bundle subscribing to the `sessions` service; preferences in `localStorage`; auditable, self-contained code (`<20 KB`).
|
|
31
|
+
- Offline smoke test (`smoke/smoke-test.cjs`) covering the alert state machine.\n- Screenshot + `screenshots.json` for the plugin-market page; UI preview shown in the README.
|
|
32
|
+
|
|
33
|
+
<!-- versions -->
|
|
34
|
+
[1.1.0]: https://github.com/chromoany/dsh-notify-me/releases/tag/v1.1.0
|
|
35
|
+
[1.0.1]: https://github.com/chromoany/dsh-notify-me/compare/v1.0.0...v1.0.1
|
|
36
|
+
[1.0.0]: https://github.com/chromoany/dsh-notify-me/releases/tag/v1.0.0
|
package/README.en.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# dsh-notify-me
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
**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 Windows desktop notification, a sound, and a tab-title marker. Toggle it and pick the notification language right from **Settings → Notify me**.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
> 🔎 Looking for it? Search `dsh-notify-me`, **message alerts**, **notification**, **desktop notification**, **reminder**, **approval alert** or **可操作提醒 / 消息提醒 / 桌面通知 / 需要你操作** (npm keywords include both English and Chinese terms).
|
|
18
|
+
|
|
19
|
+
## UI Preview
|
|
20
|
+
|
|
21
|
+
| Windows system notification |
|
|
22
|
+
| --- |
|
|
23
|
+
|  |
|
|
24
|
+
|
|
25
|
+
[Changelog](CHANGELOG.md)
|
|
26
|
+
|
|
27
|
+
## What it alerts about
|
|
28
|
+
|
|
29
|
+
| When | What you get | Default |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| 🔔 **The agent needs your input** — sandbox approval / plan review / a question (`ask_user_question`) | Toast + sound + `🔔 Action needed ·` title marker | Alerts even while the page is visible |
|
|
32
|
+
| ✅ **A reply finishes** — a turn completes; background sessions finishing are also reported | Toast + sound | Alerts only while the page is hidden / backgrounded |
|
|
33
|
+
|
|
34
|
+
## Configure it from DSH Settings
|
|
35
|
+
|
|
36
|
+
Open **Settings → Notify me** (refresh the page once after installing):
|
|
37
|
+
|
|
38
|
+
- **Enable reminders** — master switch; when off, no toasts, no sounds and the tab title is never touched;
|
|
39
|
+
- **System notifications / Sound / Volume** — toast toggle, WebAudio beep toggle and a volume slider;
|
|
40
|
+
- **"Needs you" alerts while the page is open** (default on) and **"Reply finished" alerts while the page is open** (default off);
|
|
41
|
+
- **Notification language** — follow the interface / 简体中文 / English: controls the language of the alert text and the `🔔 …` title marker;
|
|
42
|
+
- **Test buttons** — send one "needs you" or "reply finished" test alert with the current settings.
|
|
43
|
+
|
|
44
|
+
> Notification permission is required: click once on the page → **Allow** (or address-bar lock → Site settings → Notifications → Allow → reload).
|
|
45
|
+
|
|
46
|
+
## How it alerts
|
|
47
|
+
|
|
48
|
+
- **System notification** — native notification-center toast (clicking it brings the DSH window back to front)
|
|
49
|
+
- **Sound** — WebAudio beeps (distinct patterns for "needs you" vs "done")
|
|
50
|
+
- **Tab title marker** — while something is waiting on you, the tab title is prefixed with `🔔 Action needed · …` / `🔔 需要你 · …`
|
|
51
|
+
|
|
52
|
+
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).
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
# Official DSH plugin command — installs and auto-mounts into the web profile
|
|
58
|
+
dsh plugin --profile web add dsh-notify-me
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Restart `dsh web`, then hard-refresh the page (Ctrl+Shift+R).
|
|
62
|
+
|
|
63
|
+
**Verify** — open the DevTools console and run:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
window.__dshNotifyMe.test("done") // "reply finished" sample
|
|
67
|
+
window.__dshNotifyMe.test("attention") // "needs your input" sample
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Nothing happened? 90% of the time it's one of:
|
|
71
|
+
|
|
72
|
+
- the notification permission was declined — click anywhere on the page and choose **Allow** (address-bar lock → Site settings → Notifications → Allow → reload);
|
|
73
|
+
- the DSH process wasn't restarted after install;
|
|
74
|
+
- your OS/browser notification settings block the browser's toasts.
|
|
75
|
+
|
|
76
|
+
## Advanced: console configuration
|
|
77
|
+
|
|
78
|
+
The Settings page is the primary entry; preferences live in `localStorage` (key `dshNotifyMe.config`) and can be scripted from the console:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
window.__dshNotifyMe.config // view current config
|
|
82
|
+
window.__dshNotifyMe.setConfig({
|
|
83
|
+
enabled: true, // false = disable all reminders (master switch)
|
|
84
|
+
language: "auto", // 'auto' follow the UI | 'zh' | 'en'
|
|
85
|
+
attentionHiddenOnly: false, // true = don't alert "needs you" while the page is visible
|
|
86
|
+
doneHiddenOnly: true, // false = also alert "reply finished" while visible
|
|
87
|
+
toast: true, // system-notification toggle
|
|
88
|
+
sound: true, // sound toggle
|
|
89
|
+
volume: 0.5, // 0..1
|
|
90
|
+
autoFocus: true // clicking the toast focuses the DSH window
|
|
91
|
+
})
|
|
92
|
+
window.__dshNotifyMe.resetConfig() // restore defaults
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## How it works
|
|
96
|
+
|
|
97
|
+
The browser half subscribes to the client `sessions` service — the same source the UI itself renders from:
|
|
98
|
+
|
|
99
|
+
- the **selected session's** `ConversationSnapshot`: a `running: true → false` edge means a reply finished; a new entry in `pending[]` of kind `approval` / `plan-review` / `question` means the agent is waiting on you (payload text is shown in the alert when available);
|
|
100
|
+
- **every other listed session's** summary: a new `pendingInteraction`, or the `completed` edge (finished while not selected), alerts you about background work.
|
|
101
|
+
|
|
102
|
+
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).
|
|
103
|
+
|
|
104
|
+
## Known limitations
|
|
105
|
+
|
|
106
|
+
- 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).
|
|
107
|
+
- Notifications appear through the browser, so the browser needs "show notifications" permission in Windows settings, and notification-center "Do Not Disturb" must not suppress them.
|
|
108
|
+
- The first sound/toast of each page load needs one user click on the page first (browser autoplay + permission policy).
|
|
109
|
+
- Toasts are only produced once the notification permission is granted; declining means sound + title marker only.
|
|
110
|
+
- Config lives in browser `localStorage`: switching browsers/devices or clearing site data returns to defaults (one-click restore in the Settings page).
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
node --check lib\client.js
|
|
116
|
+
node --check lib\index.js
|
|
117
|
+
node smoke\smoke-test.cjs # offline state-machine smoke test (master switch + zh/en language cases)
|
|
118
|
+
npm pack --dry-run # preview the published tarball
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT — see [LICENSE](LICENSE).
|
package/README.md
CHANGED
|
@@ -1,48 +1,82 @@
|
|
|
1
1
|
# dsh-notify-me
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
简体中文 | [English](README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
7
|
+

|
|
8
|
+

|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
- **提示音**:Web Audio 合成提示音(需先在页面上点击过,浏览器才允许发声)
|
|
14
|
-
- **标签页标题标记**:有待处理提醒时标题前出现 `🔔 需要你 · …`
|
|
12
|
+
**离开 DSH 页面也不错过任何动静。** 当模型停下来需要你操作(审批 / 方案待确认 / 提问)、或你在别的软件时回复正好在后台完成,dsh-notify-me 会用系统通知 + 提示音 + 标签页标题标记提醒你——开关和通知语言都直接在 **DSH 设置 → 通知提醒** 里改。
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
---
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
# 从 npm 安装(发布后)
|
|
20
|
-
dsh plugin --profile web add dsh-notify-me
|
|
16
|
+
> 🔎 想找这个插件?可以搜:`dsh-notify-me`、**消息提醒**、**可操作提醒**、**桌面通知**、**需要你操作**、**回复完成提醒**、**后台完成提醒**(npm 关键词已含以上中英文词)。
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
## 效果预览
|
|
19
|
+
|
|
20
|
+
| Windows 系统通知效果 |
|
|
21
|
+
| --- |
|
|
22
|
+
|  |
|
|
23
|
+
|
|
24
|
+
[更新日志](CHANGELOG.md)
|
|
25
|
+
|
|
26
|
+
## 提醒时机
|
|
27
|
+
|
|
28
|
+
| 时机 | 提醒内容 | 默认 |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| 🔔 **模型需要你操作** — 审批请求 / 方案待确认(plan review)/ 提问(`ask_user_question`) | 通知 + 提示音 + `🔔 需要你 ·` 标题标记 | 页面可见也提醒 |
|
|
31
|
+
| ✅ **回复完成** — 一轮回复跑完;后台会话完成也会报 | 通知 + 提示音 | 仅页面隐藏/后台时提醒 |
|
|
32
|
+
|
|
33
|
+
## 在 DSH 设置里改配置
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
打开 **设置 → 通知提醒**(首次安装后刷新一次页面即可看到),页面提供:
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
- **启用提醒** 主开关:关闭后不再弹系统通知、不播放提示音、也不改标签页标题;
|
|
38
|
+
- **系统通知 / 提示音 / 音量**:Toast、WebAudio 提示音与音量滑块;
|
|
39
|
+
- **页面打开时也提醒「需要你」**(默认开)与 **页面打开时也提醒「回复完成」**(默认关);
|
|
40
|
+
- **通知语言**:跟随界面 / 简体中文 / English——决定通知文字与 `🔔 …` 标题标记使用的语言;
|
|
41
|
+
- **测试按钮**:用当前设置立即发一条「需要你」或「回复完成」测试提醒。
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
> 需要浏览器通知权限:页面上点一下 → 允许;或地址栏锁 → 站点设置 → 通知 → 允许 → 刷新。
|
|
44
|
+
|
|
45
|
+
## 提醒方式
|
|
46
|
+
|
|
47
|
+
- **系统通知**:Windows 通知中心 Toast(点击可把 DSH 窗口切回前台)
|
|
48
|
+
- **提示音**:WebAudio 合成音("需要你"与"完成"使用不同音型)
|
|
49
|
+
- **标签页标题标记**:有待处理事项时,标题前出现 `🔔 需要你 · …` / `🔔 Action needed · …`
|
|
50
|
+
|
|
51
|
+
本插件是**浏览器层**实现:DSH 页面需保持打开(最小化/后台即可——那正是它监听的"离开"状态)。
|
|
52
|
+
|
|
53
|
+
## 安装
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
# DSH 官方插件命令:安装并自动挂载进 web profile
|
|
57
|
+
dsh plugin --profile web add dsh-notify-me
|
|
58
|
+
```
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
重启 `dsh web` 后硬刷新页面(Ctrl+Shift+R)。
|
|
33
61
|
|
|
34
|
-
|
|
62
|
+
**验证** — F12 控制台执行:
|
|
35
63
|
|
|
36
64
|
```js
|
|
37
|
-
window.__dshNotifyMe.test("
|
|
38
|
-
window.__dshNotifyMe.test("
|
|
65
|
+
window.__dshNotifyMe.test("done") // "完成"示例
|
|
66
|
+
window.__dshNotifyMe.test("attention") // "需要你"示例
|
|
39
67
|
```
|
|
40
68
|
|
|
41
|
-
|
|
69
|
+
没反应?九成是:通知权限被拒绝(在页面上点一下 → 选"允许";或地址栏锁 → 站点设置 → 通知 → 允许 → 刷新)、装完没重启 DSH、系统设置里浏览器通知被关。
|
|
70
|
+
|
|
71
|
+
## 控制台高级配置
|
|
72
|
+
|
|
73
|
+
设置页是第一入口;想脚本化/批量改或恢复默认时,偏好存 `localStorage`(`dshNotifyMe.config`),可用控制台实时调整:
|
|
42
74
|
|
|
43
75
|
```js
|
|
44
76
|
window.__dshNotifyMe.config // 查看
|
|
45
77
|
window.__dshNotifyMe.setConfig({
|
|
78
|
+
enabled: true, // false = 关闭所有提醒(主开关)
|
|
79
|
+
language: "auto", // 'auto' 跟随界面 | 'zh' 简体中文 | 'en' English
|
|
46
80
|
attentionHiddenOnly: false, // true = 页面可见时"需要你"不提醒
|
|
47
81
|
doneHiddenOnly: true, // false = 页面可见时"完成"也提醒
|
|
48
82
|
toast: true, // 系统通知开关
|
|
@@ -53,58 +87,32 @@ window.__dshNotifyMe.setConfig({
|
|
|
53
87
|
window.__dshNotifyMe.resetConfig() // 恢复默认
|
|
54
88
|
```
|
|
55
89
|
|
|
56
|
-
## 卸载
|
|
57
|
-
|
|
58
|
-
```powershell
|
|
59
|
-
dsh plugin --profile web remove dsh-notify-me
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
然后重启。
|
|
63
|
-
|
|
64
90
|
## 工作原理
|
|
65
91
|
|
|
66
|
-
浏览器半身订阅客户端 `sessions`
|
|
92
|
+
浏览器半身订阅客户端 `sessions` 服务(与 UI 同一数据源):
|
|
67
93
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
94
|
+
- **当前会话** `ConversationSnapshot`:`running` true→false = 回复完成;`pending[]` 新增 `approval` / `plan-review` / `question` = 模型在等你(可行时在提醒里展示提问/审批内容);
|
|
95
|
+
- **其它已列会话**摘要:出现新 `pendingInteraction`,或 `completed` 边沿(非选中状态下跑完)→ 后台工作提醒。
|
|
70
96
|
|
|
71
|
-
|
|
97
|
+
提醒核心仍然零第三方运行时依赖、完全自包含:通知文字按所选语言(跟随界面 / 中文 / English)即时解析。设置页是**可选**的 React 呈现层——当 DSH web profile 提供 `slots` / `locale` / `react` 时才注册进「设置 → 通知提醒」,缺任一能力时插件自动降级为纯提醒(无设置页),不影响功能。
|
|
72
98
|
|
|
73
|
-
##
|
|
99
|
+
## 已知限制
|
|
74
100
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
npm pack --dry-run # 先看包里有什么
|
|
81
|
-
npm publish
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
发布后即可:`dsh plugin --profile web add dsh-notify-me`。
|
|
85
|
-
|
|
86
|
-
**上架 dshmarket(插件市场)**:市场数据源为 [awesome-dsh-plugin.com](https://awesome-dsh-plugin.com) 的 `plugins.json`(GitHub 仓库维护),收录后可在 设置 → 插件市场 被搜索到("notify" 类目)。提交入口与要求见仓库 CONTRIBUTING;通常需要:npm 已发布 + GitHub 仓库 + 在 plugins.json 里按字段新增一条记录(类别 `notify`)。
|
|
87
|
-
|
|
88
|
-
## 目录结构
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
package.json 插件声明(dsh.client / exports ./client / bundle patch / files 白名单)
|
|
92
|
-
cordis.patch.yml loader 条目(id: notify-me, name: dsh-notify-me)
|
|
93
|
-
lib/index.js 服务端半身(空壳,仅为 loader 条目存在)
|
|
94
|
-
lib/client.js 浏览器半身:通知 / 声音 / 标题标记 + sessions 订阅
|
|
95
|
-
smoke/ 离线冒烟测试(node smoke/smoke-test.cjs)
|
|
96
|
-
LICENSE MIT
|
|
97
|
-
```
|
|
101
|
+
- 页面必须开着才会提醒(后台标签/最小化可以;关标签页即失效——浏览器层方案固有限制)。
|
|
102
|
+
- 通知经由浏览器弹出,需允许浏览器通知权限,且勿扰模式不能屏蔽它。
|
|
103
|
+
- 每次页面加载后第一次出声/弹通知前,需在页面上点击过一次(浏览器自动播放与权限策略)。
|
|
104
|
+
- 未授权通知权限时只有提示音与标题标记。
|
|
105
|
+
- 配置存在浏览器 `localStorage`:换浏览器/设备或清除站点数据后会回到默认值(设置页可一键恢复默认)。
|
|
98
106
|
|
|
99
107
|
## 开发自检
|
|
100
108
|
|
|
101
109
|
```powershell
|
|
102
110
|
node --check lib\client.js
|
|
103
111
|
node --check lib\index.js
|
|
104
|
-
node smoke\smoke-test.cjs
|
|
112
|
+
node smoke\smoke-test.cjs # 离线状态机冒烟测试(含主开关与中英语言用例)
|
|
113
|
+
npm pack --dry-run # 预览发布包
|
|
105
114
|
```
|
|
106
115
|
|
|
107
|
-
##
|
|
116
|
+
## License
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
- 本插件代码完全自包含、可审计(无第三方运行时依赖,未执行任何本机命令)。
|
|
118
|
+
MIT — 见 [LICENSE](LICENSE)。
|
package/lib/client.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// executed through the vendored cordis Loader's lazy-CJS module table
|
|
5
5
|
// (window.__ModuleLoader__.load). The factory body is plain CJS with
|
|
6
6
|
// require() resolved against the shell's module table — the same shape the
|
|
7
|
-
// shipped ui-* packages' tsdown bundles emit.
|
|
7
|
+
// shipped ui-* packages' tsdown bundles emit. react is part of the platform
|
|
8
|
+
// base, so require("react") resolves to the shell-seeded instance (same as
|
|
9
|
+
// every shipped ui-* bundle); the settings page is skipped when react or the
|
|
10
|
+
// slots/locale services are absent, keeping the reminder core self-contained.
|
|
8
11
|
//
|
|
9
12
|
// What this plugin does (all client-side, no settings transport needed):
|
|
10
13
|
// - watches the "sessions" service (SessionRuntime): the selected
|
|
@@ -13,6 +16,10 @@
|
|
|
13
16
|
// - fires a Windows desktop Notification (+ optional sound) when the agent
|
|
14
17
|
// needs the user's input — pending interaction kinds 'approval',
|
|
15
18
|
// 'plan-review' or 'question' — and when a reply/turn finishes;
|
|
19
|
+
// - registers a "通知提醒 / Notify me" page into DSH Settings
|
|
20
|
+
// (settings.section slot) exposing an on/off master switch, per-channel
|
|
21
|
+
// toggles, volume and the notification language (follow-interface /
|
|
22
|
+
// Simplified Chinese / English);
|
|
16
23
|
// - preferences persist in localStorage (origin-scoped, like other
|
|
17
24
|
// third-party client plugins) and are tweakable on window.__dshNotifyMe.
|
|
18
25
|
|
|
@@ -23,9 +30,18 @@ window.__ModuleLoader__.load({
|
|
|
23
30
|
var exports = module.exports;
|
|
24
31
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
25
32
|
|
|
33
|
+
// react is externalized into the loader module table; never required by
|
|
34
|
+
// the reminder core, only by the optional Settings page.
|
|
35
|
+
var React = null;
|
|
36
|
+
try { React = require("react") || null; } catch (e) { React = null; }
|
|
37
|
+
|
|
26
38
|
// ───────────────────────── configuration ─────────────────────────
|
|
27
39
|
var LS_CONFIG = "dshNotifyMe.config";
|
|
28
40
|
var DEFAULTS = {
|
|
41
|
+
enabled: true, // master switch: off = no toast/sound/title marker at all
|
|
42
|
+
// language of the notification copy: 'auto' follows the DSH UI locale,
|
|
43
|
+
// 'zh' and 'en' pin Simplified Chinese / English.
|
|
44
|
+
language: "auto",
|
|
29
45
|
// toast + sound are only useful while the user is elsewhere; the tab
|
|
30
46
|
// being hidden is the reliable "away" signal.
|
|
31
47
|
attentionHiddenOnly: false, // needs-input alerts even while the page is visible
|
|
@@ -51,6 +67,78 @@ window.__ModuleLoader__.load({
|
|
|
51
67
|
return cfg;
|
|
52
68
|
}
|
|
53
69
|
var config = readConfig();
|
|
70
|
+
// Persist a patch, swap the live module config and re-apply any active
|
|
71
|
+
// title marker (its prefix follows the notification language).
|
|
72
|
+
function setConfigLocal(patch) {
|
|
73
|
+
config = writeConfig(patch);
|
|
74
|
+
applyTitle();
|
|
75
|
+
return config;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ───────────────────────── copy tables ─────────────────────────
|
|
79
|
+
// Notification copy, keyed by language id. Selected through
|
|
80
|
+
// effectiveLang() so a pinned language stays pinned even while the DSH UI
|
|
81
|
+
// speaks the other one.
|
|
82
|
+
var TXT = {
|
|
83
|
+
zh: {
|
|
84
|
+
kind: { approval: "审批请求", "plan-review": "方案待确认", question: "提问" },
|
|
85
|
+
kindDefault: "需要操作",
|
|
86
|
+
bodyKind: { approval: "有审批待处理", "plan-review": "方案待确认", question: "有提问待回答" },
|
|
87
|
+
bodyDefault: "需要你操作",
|
|
88
|
+
current: "当前对话",
|
|
89
|
+
doneTitle: "DSH · 回复完成",
|
|
90
|
+
doneBody: "回复已完成",
|
|
91
|
+
background: "后台会话",
|
|
92
|
+
marker: "🔔 需要你 · ",
|
|
93
|
+
sep: ":",
|
|
94
|
+
testAttention: "这是一条测试提醒:DSH 需要你的操作",
|
|
95
|
+
testDone: "测试提醒 · 回复完成示例"
|
|
96
|
+
},
|
|
97
|
+
en: {
|
|
98
|
+
kind: { approval: "Approval request", "plan-review": "Plan review", question: "Question" },
|
|
99
|
+
kindDefault: "Action needed",
|
|
100
|
+
bodyKind: {
|
|
101
|
+
approval: "An approval is waiting for you",
|
|
102
|
+
"plan-review": "A plan awaits your review",
|
|
103
|
+
question: "A question is waiting for you"
|
|
104
|
+
},
|
|
105
|
+
bodyDefault: "Your input is needed",
|
|
106
|
+
current: "Current conversation",
|
|
107
|
+
doneTitle: "DSH · Reply finished",
|
|
108
|
+
doneBody: "Reply finished",
|
|
109
|
+
background: "Background session",
|
|
110
|
+
marker: "🔔 Action needed · ",
|
|
111
|
+
sep: ": ",
|
|
112
|
+
testAttention: "This is a test alert: DSH is waiting for your input",
|
|
113
|
+
testDone: "Test alert · reply-finished sample"
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
// Locale service handle (registered on the client context by dsh-client-locale).
|
|
117
|
+
var localeSvc = null;
|
|
118
|
+
// Current DSH UI language: 'zh' | 'en' | null (unknown). Used by the
|
|
119
|
+
// 'auto' notification-language mode.
|
|
120
|
+
function currentUILang() {
|
|
121
|
+
try {
|
|
122
|
+
if (localeSvc && typeof localeSvc.getLocale === "function") {
|
|
123
|
+
var a = localeSvc.getLocale().active;
|
|
124
|
+
if (typeof a === "string" && a) {
|
|
125
|
+
if (a.indexOf("zh") === 0) return "zh";
|
|
126
|
+
if (a.indexOf("en") === 0) return "en";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {}
|
|
130
|
+
try {
|
|
131
|
+
var dl = (document.documentElement && document.documentElement.lang) || "";
|
|
132
|
+
if (dl.indexOf("zh") === 0) return "zh";
|
|
133
|
+
if (dl.indexOf("en") === 0) return "en";
|
|
134
|
+
} catch (e) {}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function effectiveLang() {
|
|
138
|
+
if (!config.language || config.language === "auto") return currentUILang() || "zh";
|
|
139
|
+
return config.language === "en" ? "en" : "zh";
|
|
140
|
+
}
|
|
141
|
+
function texts() { return TXT[effectiveLang()] || TXT.zh; }
|
|
54
142
|
|
|
55
143
|
// ─────────────────────── notification plumbing ───────────────────
|
|
56
144
|
var permAsked = false;
|
|
@@ -144,7 +232,9 @@ window.__ModuleLoader__.load({
|
|
|
144
232
|
function applyTitle() {
|
|
145
233
|
try {
|
|
146
234
|
if (originalTitle === null) originalTitle = document.title;
|
|
147
|
-
document.title = attentionActive > 0
|
|
235
|
+
document.title = (config.enabled && attentionActive > 0)
|
|
236
|
+
? texts().marker + originalTitle
|
|
237
|
+
: originalTitle;
|
|
148
238
|
} catch (e) {}
|
|
149
239
|
}
|
|
150
240
|
function acquireTitleMark() {
|
|
@@ -162,9 +252,10 @@ window.__ModuleLoader__.load({
|
|
|
162
252
|
|
|
163
253
|
function hidden() { return document.hidden || document.visibilityState === "hidden"; }
|
|
164
254
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
255
|
+
// Deliver one alert regardless of visibility rules (used by the test
|
|
256
|
+
// buttons); respects the master switch and permission state.
|
|
257
|
+
function deliver(kind, title, body) {
|
|
258
|
+
if (!config.enabled) return;
|
|
168
259
|
showToast(kind, title, body);
|
|
169
260
|
if (kind === "attention") {
|
|
170
261
|
playAttentionSound();
|
|
@@ -178,15 +269,31 @@ window.__ModuleLoader__.load({
|
|
|
178
269
|
}
|
|
179
270
|
} catch (e) {}
|
|
180
271
|
}
|
|
272
|
+
function fire(kind, title, body) {
|
|
273
|
+
if (!config.enabled) return;
|
|
274
|
+
if (kind === "attention" && config.attentionHiddenOnly && !hidden()) return;
|
|
275
|
+
if (kind === "done" && config.doneHiddenOnly && !hidden()) return;
|
|
276
|
+
deliver(kind, title, body);
|
|
277
|
+
}
|
|
278
|
+
// A display label for a session row ("current" maps to the localized
|
|
279
|
+
// "当前对话 / Current conversation" placeholder).
|
|
280
|
+
function sessionLabelText(row) {
|
|
281
|
+
var raw = row && row.label;
|
|
282
|
+
if (!raw || raw === "current") return texts().current;
|
|
283
|
+
return raw;
|
|
284
|
+
}
|
|
181
285
|
function fireAttention(kind, sessionLabel, detail) {
|
|
182
|
-
var
|
|
183
|
-
var
|
|
184
|
-
|
|
286
|
+
var d = texts();
|
|
287
|
+
var label = d.kind[kind] || d.kindDefault;
|
|
288
|
+
var content = detail || d.bodyKind[kind] || d.bodyDefault;
|
|
289
|
+
var who = sessionLabel || "";
|
|
290
|
+
fire("attention", "DSH · " + label, (who ? who + d.sep : "") + content);
|
|
185
291
|
}
|
|
186
292
|
function fireDone(sessionLabel, snippet) {
|
|
187
|
-
var
|
|
293
|
+
var d = texts();
|
|
294
|
+
var body = sessionLabel || d.doneBody;
|
|
188
295
|
if (snippet) body = body + "\n" + snippet;
|
|
189
|
-
fire("done",
|
|
296
|
+
fire("done", d.doneTitle, body);
|
|
190
297
|
}
|
|
191
298
|
|
|
192
299
|
// ───────────────────────── state machine ─────────────────────────
|
|
@@ -252,16 +359,15 @@ window.__ModuleLoader__.load({
|
|
|
252
359
|
if (newKeys.length > 0) {
|
|
253
360
|
var it = newKeys[0];
|
|
254
361
|
var detail = textFromPayload(it.payload);
|
|
255
|
-
if (!detail) detail =
|
|
256
|
-
|
|
257
|
-
fireAttention(it.kind, label, detail);
|
|
362
|
+
if (!detail) detail = "";
|
|
363
|
+
fireAttention(it.kind, sessionLabelText(row), detail);
|
|
258
364
|
lastAttentionAt = Date.now();
|
|
259
365
|
}
|
|
260
366
|
|
|
261
367
|
// 2) a non-selected session finished while we were away (sidebar "done")
|
|
262
368
|
var notified = false;
|
|
263
369
|
if (!row.face && row.completed && prev && !prev.completed && prev.seeded) {
|
|
264
|
-
fireDone(row
|
|
370
|
+
fireDone(sessionLabelText(row), "");
|
|
265
371
|
notified = true;
|
|
266
372
|
}
|
|
267
373
|
|
|
@@ -271,7 +377,7 @@ window.__ModuleLoader__.load({
|
|
|
271
377
|
if (Date.now() - lastAttentionAt < 300) {
|
|
272
378
|
/* answered by an attention alert above */
|
|
273
379
|
} else {
|
|
274
|
-
fireDone(row
|
|
380
|
+
fireDone(sessionLabelText(row), row.face ? snippetOfLastAssistant(row.faceSnap) : "");
|
|
275
381
|
}
|
|
276
382
|
}
|
|
277
383
|
|
|
@@ -295,7 +401,6 @@ window.__ModuleLoader__.load({
|
|
|
295
401
|
var pend = (snap.pending || []).map(function (p) {
|
|
296
402
|
return { key: p.key || (p.kind + ":" + sessionId), kind: p.kind, payload: p.payload || null };
|
|
297
403
|
});
|
|
298
|
-
var label = "current";
|
|
299
404
|
evaluateRow({
|
|
300
405
|
id: sessionId,
|
|
301
406
|
running: !!snap.running,
|
|
@@ -303,7 +408,7 @@ window.__ModuleLoader__.load({
|
|
|
303
408
|
completed: false,
|
|
304
409
|
face: true,
|
|
305
410
|
faceSnap: snap,
|
|
306
|
-
label:
|
|
411
|
+
label: "current"
|
|
307
412
|
});
|
|
308
413
|
}
|
|
309
414
|
// list row -> adapter (every other listed session)
|
|
@@ -368,9 +473,9 @@ window.__ModuleLoader__.load({
|
|
|
368
473
|
|
|
369
474
|
// ──────────────────────── global control API ─────────────────────
|
|
370
475
|
var api = {
|
|
371
|
-
version: "0.
|
|
476
|
+
version: "0.2.0",
|
|
372
477
|
get config() { return readConfig(); },
|
|
373
|
-
setConfig: function (patch) {
|
|
478
|
+
setConfig: function (patch) { return setConfigLocal(patch); },
|
|
374
479
|
resetConfig: function () {
|
|
375
480
|
try { localStorage.removeItem(LS_CONFIG); } catch (e) {}
|
|
376
481
|
config = readConfig();
|
|
@@ -380,23 +485,280 @@ window.__ModuleLoader__.load({
|
|
|
380
485
|
test: function (kind) {
|
|
381
486
|
kind = kind === "attention" ? "attention" : "done";
|
|
382
487
|
unlockOnce();
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
488
|
+
var d = texts();
|
|
489
|
+
if (kind === "attention") fireAttention("question", "", d.testAttention);
|
|
490
|
+
else fireDone("", d.testDone);
|
|
491
|
+
return "sent " + kind + " (lang=" + effectiveLang() + ", permission=" + ("Notification" in window ? Notification.permission : "n/a") + ")";
|
|
386
492
|
}
|
|
387
493
|
};
|
|
388
494
|
window.__dshNotifyMe = api;
|
|
389
495
|
|
|
496
|
+
// ────────────────── Settings page (react only, optional) ─────────
|
|
497
|
+
// Registered when the web profile provides react + slots + locale; the
|
|
498
|
+
// reminder core above never depends on any of it.
|
|
499
|
+
var NS = "dsh-notify-me";
|
|
500
|
+
var UI_zh = {
|
|
501
|
+
nav: "通知提醒",
|
|
502
|
+
blurb: "当模型停下来需要你操作(审批 / 方案待确认 / 提问),或回复在后台完成时,用系统通知、提示音和标签页标题提醒你。",
|
|
503
|
+
enable: "启用提醒",
|
|
504
|
+
enableHint: "总开关:关闭后不再弹系统通知、不播放提示音,也不再改动标签页标题。",
|
|
505
|
+
toast: "系统通知",
|
|
506
|
+
toastHint: "经浏览器弹出系统 Toast(Windows 通知中心);需要已授予通知权限。",
|
|
507
|
+
sound: "提示音",
|
|
508
|
+
soundHint: "WebAudio 合成音,「需要你」与「回复完成」使用不同音型。",
|
|
509
|
+
attentionVisible: "页面打开时也提醒「需要你」",
|
|
510
|
+
attentionVisibleHint: "默认开启:模型在等你审批 / 确认方案 / 回答问题(含提问弹窗)时,即使页面在最前也会提醒。",
|
|
511
|
+
doneVisible: "页面打开时也提醒「回复完成」",
|
|
512
|
+
doneVisibleHint: "默认关闭:「完成」只在页面隐藏或切到后台时才提醒,避免打扰。",
|
|
513
|
+
autoFocus: "点击通知把 DSH 切回前台",
|
|
514
|
+
volume: "音量",
|
|
515
|
+
language: "通知语言",
|
|
516
|
+
langAuto: "跟随界面",
|
|
517
|
+
langZh: "简体中文",
|
|
518
|
+
langEn: "English",
|
|
519
|
+
langAutoNote: "(跟随界面)",
|
|
520
|
+
current: "界面当前语言",
|
|
521
|
+
testTitle: "快速测试",
|
|
522
|
+
testHint: "用当前设置各发一条测试提醒。",
|
|
523
|
+
testAttention: "测试「需要你」",
|
|
524
|
+
testDone: "测试「回复完成」",
|
|
525
|
+
reset: "恢复默认设置",
|
|
526
|
+
resetDone: "已恢复默认设置。",
|
|
527
|
+
sent: "已发送测试提醒 · 通知语言:",
|
|
528
|
+
permHint: "通知权限尚未授予:先在页面上点击一次并允许通知,系统通知才能弹出(提示音不受影响)。",
|
|
529
|
+
offHint: "已关闭提醒:先打开「启用提醒」再测试。"
|
|
530
|
+
};
|
|
531
|
+
var UI_en = {
|
|
532
|
+
nav: "Notify me",
|
|
533
|
+
blurb: "When the agent stops and needs your input (approval / plan review / question) or a reply finishes in the background, get a system notification, a sound, and a tab-title marker.",
|
|
534
|
+
enable: "Enable reminders",
|
|
535
|
+
enableHint: "Master switch: when off, no toasts, no sounds, and the tab title is never modified.",
|
|
536
|
+
toast: "System notifications",
|
|
537
|
+
toastHint: "Native toasts via the browser (Windows notification center); requires notification permission.",
|
|
538
|
+
sound: "Sound",
|
|
539
|
+
soundHint: "WebAudio beeps — \"needs you\" and \"reply finished\" use different patterns.",
|
|
540
|
+
attentionVisible: "Also alert \"needs you\" while the page is open",
|
|
541
|
+
attentionVisibleHint: "On by default: alert even in the foreground when the model waits for approval / plan review / a question.",
|
|
542
|
+
doneVisible: "Also alert \"reply finished\" while the page is open",
|
|
543
|
+
doneVisibleHint: "Off by default: \"done\" only alerts while the page is hidden or backgrounded, to avoid noise.",
|
|
544
|
+
autoFocus: "Click a toast to focus the DSH window",
|
|
545
|
+
volume: "Volume",
|
|
546
|
+
language: "Notification language",
|
|
547
|
+
langAuto: "Follow the interface",
|
|
548
|
+
langZh: "简体中文",
|
|
549
|
+
langEn: "English",
|
|
550
|
+
langAutoNote: " (follows the interface)",
|
|
551
|
+
current: "Current interface language",
|
|
552
|
+
testTitle: "Quick test",
|
|
553
|
+
testHint: "Send one test alert with the current settings.",
|
|
554
|
+
testAttention: "Test \"needs you\"",
|
|
555
|
+
testDone: "Test \"reply finished\"",
|
|
556
|
+
reset: "Restore defaults",
|
|
557
|
+
resetDone: "Defaults restored.",
|
|
558
|
+
sent: "Test alert sent · notification language: ",
|
|
559
|
+
permHint: "Notification permission is not granted yet — click once on the page and allow notifications so toasts can appear (sound is unaffected).",
|
|
560
|
+
offHint: "Reminders are off: enable them first, then test."
|
|
561
|
+
};
|
|
562
|
+
// Small element helper to keep the hand-written page readable.
|
|
563
|
+
function h(tag, props) {
|
|
564
|
+
var args = [tag, props || null];
|
|
565
|
+
for (var i = 2; i < arguments.length; i++) args.push(arguments[i]);
|
|
566
|
+
return React.createElement.apply(React, args);
|
|
567
|
+
}
|
|
568
|
+
// css classes (namespaced to avoid collisions)
|
|
569
|
+
var UI_CSS = "" +
|
|
570
|
+
".dnm-wrap{width:100%;max-width:720px;display:flex;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary,rgba(20,20,20,.9))}" +
|
|
571
|
+
".dnm-blurb{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
|
|
572
|
+
".dnm-card{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.35));background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.05));border-radius:10px;overflow:hidden}" +
|
|
573
|
+
".dnm-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 14px;min-height:46px;box-sizing:border-box}" +
|
|
574
|
+
".dnm-row+.dnm-row{border-top:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.35))}" +
|
|
575
|
+
".dnm-rowText{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}" +
|
|
576
|
+
".dnm-rowLabel{font-size:13px;font-weight:600;line-height:20px;color:var(--dsw-alias-label-primary,rgba(20,20,20,.9))}" +
|
|
577
|
+
".dnm-rowHint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
|
|
578
|
+
".dnm-switch{flex:none;position:relative;width:34px;height:20px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.45));background:var(--dsw-alias-bg-layer-1,rgba(128,128,128,.2));cursor:pointer;padding:0;transition:background .15s ease}" +
|
|
579
|
+
".dnm-switch.on{background:var(--dsw-alias-state-business-primary,#3b82f6);border-color:transparent}" +
|
|
580
|
+
".dnm-knob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:left .15s ease}" +
|
|
581
|
+
".dnm-switch.on .dnm-knob{left:16px}" +
|
|
582
|
+
".dnm-range{width:150px;accent-color:var(--dsw-alias-state-business-primary,#3b82f6)}" +
|
|
583
|
+
".dnm-select{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.4));background:var(--dsw-alias-bg-layer-1,rgba(255,255,255,.03));color:var(--dsw-alias-label-primary,rgba(20,20,20,.9));border-radius:8px;padding:5px 8px;font-size:13px;font-family:inherit}" +
|
|
584
|
+
".dnm-btn{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.4));background:transparent;color:var(--dsw-alias-label-primary,rgba(20,20,20,.9));border-radius:8px;padding:5px 12px;font-size:13px;cursor:pointer;font-family:inherit}" +
|
|
585
|
+
".dnm-btn:hover{background:var(--dsw-alias-bg-layer-1,rgba(128,128,128,.1))}" +
|
|
586
|
+
".dnm-btn[disabled]{opacity:.45;cursor:default}" +
|
|
587
|
+
".dnm-btnRow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}" +
|
|
588
|
+
".dnm-hint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
|
|
589
|
+
".dnm-feedback{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9));min-height:18px}";
|
|
590
|
+
(function injectStyle() {
|
|
591
|
+
try {
|
|
592
|
+
if (typeof document === "undefined" || typeof document.createElement !== "function") return;
|
|
593
|
+
var tagId = "dsh-notify-me/settings";
|
|
594
|
+
if (document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]")) return;
|
|
595
|
+
var s = document.createElement("style");
|
|
596
|
+
s.dataset.plugin = "dsh-notify-me";
|
|
597
|
+
s.dataset.pluginCss = tagId;
|
|
598
|
+
s.textContent = UI_CSS;
|
|
599
|
+
(document.head || document.documentElement).appendChild(s);
|
|
600
|
+
} catch (e) {}
|
|
601
|
+
})();
|
|
602
|
+
|
|
603
|
+
function rowSwitch(key, labelText, hintText, checked, onChange) {
|
|
604
|
+
return h("div", { className: "dnm-row" }, [
|
|
605
|
+
h("div", { className: "dnm-rowText" }, [
|
|
606
|
+
h("div", { className: "dnm-rowLabel" }, labelText),
|
|
607
|
+
hintText ? h("div", { className: "dnm-rowHint" }, hintText) : null
|
|
608
|
+
]),
|
|
609
|
+
h("button", {
|
|
610
|
+
type: "button",
|
|
611
|
+
role: "switch",
|
|
612
|
+
"aria-checked": checked ? "true" : "false",
|
|
613
|
+
className: "dnm-switch" + (checked ? " on" : ""),
|
|
614
|
+
onClick: function () { onChange(!checked); }
|
|
615
|
+
}, h("span", { className: "dnm-knob" }))
|
|
616
|
+
]);
|
|
617
|
+
}
|
|
618
|
+
function card(children) {
|
|
619
|
+
return h("div", { className: "dnm-card" }, children);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function NotifySettingsView(props) {
|
|
623
|
+
var ctx = props.ctx;
|
|
624
|
+
var t = props.t;
|
|
625
|
+
// Keep the page copy in sync with the active DSH UI language.
|
|
626
|
+
React.useSyncExternalStore(function (cb) { return ctx.locale.subscribe(cb); }, function () { return ctx.locale.getSnapshot(); });
|
|
627
|
+
var cfgState = React.useState(function () { return readConfig(); });
|
|
628
|
+
var cfg = cfgState[0];
|
|
629
|
+
var setCfg = cfgState[1];
|
|
630
|
+
var feedbackState = React.useState("");
|
|
631
|
+
var feedback = feedbackState[0];
|
|
632
|
+
var setFeedback = feedbackState[1];
|
|
633
|
+
function patch(p) {
|
|
634
|
+
setCfg(setConfigLocal(p));
|
|
635
|
+
}
|
|
636
|
+
function runTest(kind) {
|
|
637
|
+
if (!config.enabled) { setFeedback(t("offHint")); return; }
|
|
638
|
+
unlockOnce();
|
|
639
|
+
if (kind === "attention") fireAttention("question", "", texts().testAttention);
|
|
640
|
+
else fireDone("", texts().testDone);
|
|
641
|
+
var langName = effectiveLang() === "en" ? t("langEn") : t("langZh");
|
|
642
|
+
setFeedback(t("sent") + langName);
|
|
643
|
+
}
|
|
644
|
+
var permOK = ("Notification" in window) ? Notification.permission === "granted" : false;
|
|
645
|
+
var resolvedLang = effectiveLang();
|
|
646
|
+
var resolvedLangName = resolvedLang === "en" ? t("langEn") : t("langZh");
|
|
647
|
+
return h("div", { className: "dnm-wrap" }, [
|
|
648
|
+
h("p", { className: "dnm-blurb" }, t("blurb")),
|
|
649
|
+
card([
|
|
650
|
+
rowSwitch("enabled", t("enable"), t("enableHint"), cfg.enabled, function (v) {
|
|
651
|
+
patch({ enabled: v });
|
|
652
|
+
if (!v && attentionActive > 0) { attentionActive = 0; applyTitle(); }
|
|
653
|
+
})
|
|
654
|
+
]),
|
|
655
|
+
card([
|
|
656
|
+
rowSwitch("toast", t("toast"), t("toastHint"), cfg.toast, function (v) { patch({ toast: v }); }),
|
|
657
|
+
rowSwitch("sound", t("sound"), t("soundHint"), cfg.sound, function (v) { patch({ sound: v }); }),
|
|
658
|
+
rowSwitch("attentionVisible", t("attentionVisible"), t("attentionVisibleHint"), !cfg.attentionHiddenOnly, function (v) { patch({ attentionHiddenOnly: !v }); }),
|
|
659
|
+
rowSwitch("doneVisible", t("doneVisible"), t("doneVisibleHint"), !cfg.doneHiddenOnly, function (v) { patch({ doneHiddenOnly: !v }); }),
|
|
660
|
+
rowSwitch("autoFocus", t("autoFocus"), null, cfg.autoFocus, function (v) { patch({ autoFocus: v }); }),
|
|
661
|
+
h("div", { className: "dnm-row" }, [
|
|
662
|
+
h("div", { className: "dnm-rowText" }, [
|
|
663
|
+
h("div", { className: "dnm-rowLabel" }, t("volume")),
|
|
664
|
+
h("div", { className: "dnm-rowHint" }, String(Math.round(cfg.volume * 100)) + "%")
|
|
665
|
+
]),
|
|
666
|
+
h("input", {
|
|
667
|
+
type: "range",
|
|
668
|
+
className: "dnm-range",
|
|
669
|
+
min: 0, max: 100, step: 5,
|
|
670
|
+
value: Math.round(cfg.volume * 100),
|
|
671
|
+
"aria-label": t("volume"),
|
|
672
|
+
onChange: function (e) { patch({ volume: Number(e.currentTarget.value) / 100 }); }
|
|
673
|
+
})
|
|
674
|
+
])
|
|
675
|
+
]),
|
|
676
|
+
card([
|
|
677
|
+
h("div", { className: "dnm-row" }, [
|
|
678
|
+
h("div", { className: "dnm-rowText" }, [
|
|
679
|
+
h("div", { className: "dnm-rowLabel" }, t("language")),
|
|
680
|
+
h("div", { className: "dnm-rowHint" }, resolvedLangName + (cfg.language === "auto" ? t("langAutoNote") : ""))
|
|
681
|
+
]),
|
|
682
|
+
h("select", {
|
|
683
|
+
className: "dnm-select",
|
|
684
|
+
value: cfg.language,
|
|
685
|
+
"aria-label": t("language"),
|
|
686
|
+
onChange: function (e) { patch({ language: e.currentTarget.value }); }
|
|
687
|
+
}, [
|
|
688
|
+
h("option", { value: "auto" }, t("langAuto")),
|
|
689
|
+
h("option", { value: "zh" }, t("langZh")),
|
|
690
|
+
h("option", { value: "en" }, t("langEn"))
|
|
691
|
+
])
|
|
692
|
+
]),
|
|
693
|
+
h("div", { className: "dnm-row" }, [
|
|
694
|
+
h("div", { className: "dnm-rowText" }, [
|
|
695
|
+
h("div", { className: "dnm-rowLabel" }, t("testTitle")),
|
|
696
|
+
h("div", { className: "dnm-rowHint" }, t("testHint"))
|
|
697
|
+
]),
|
|
698
|
+
h("div", { className: "dnm-btnRow" }, [
|
|
699
|
+
h("button", { type: "button", className: "dnm-btn", disabled: !cfg.enabled, onClick: function () { runTest("attention"); } }, t("testAttention")),
|
|
700
|
+
h("button", { type: "button", className: "dnm-btn", disabled: !cfg.enabled, onClick: function () { runTest("done"); } }, t("testDone"))
|
|
701
|
+
])
|
|
702
|
+
]),
|
|
703
|
+
h("div", { className: "dnm-row" }, [
|
|
704
|
+
h("div", { className: "dnm-rowText" }, [
|
|
705
|
+
h("div", { className: "dnm-rowLabel" }, t("reset")),
|
|
706
|
+
h("div", { className: "dnm-rowHint" }, " ")
|
|
707
|
+
]),
|
|
708
|
+
h("button", { type: "button", className: "dnm-btn", onClick: function () {
|
|
709
|
+
try { localStorage.removeItem(LS_CONFIG); } catch (e) {}
|
|
710
|
+
config = readConfig();
|
|
711
|
+
applyTitle();
|
|
712
|
+
setCfg(config);
|
|
713
|
+
setFeedback(t("resetDone"));
|
|
714
|
+
} }, t("reset"))
|
|
715
|
+
]),
|
|
716
|
+
!permOK ? h("div", { className: "dnm-row" }, [
|
|
717
|
+
h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, t("permHint"))])
|
|
718
|
+
]) : null
|
|
719
|
+
]),
|
|
720
|
+
h("p", { className: "dnm-feedback" }, feedback)
|
|
721
|
+
]);
|
|
722
|
+
}
|
|
723
|
+
|
|
390
724
|
// ────────────────────────── plugin body ──────────────────────────
|
|
391
|
-
var inject = ["sessions"];
|
|
725
|
+
var inject = ["sessions", "locale", "slots"];
|
|
392
726
|
|
|
393
727
|
/**
|
|
394
728
|
* Client plugin body: watch the sessions service and raise desktop
|
|
395
|
-
* notifications + sounds when the agent needs input or a reply finishes
|
|
729
|
+
* notifications + sounds when the agent needs input or a reply finishes;
|
|
730
|
+
* also contributes a Settings page (settings.section slot).
|
|
396
731
|
* @param ctx - client root context.
|
|
397
732
|
*/
|
|
398
733
|
function apply(ctx) {
|
|
399
734
|
rootCtx = ctx;
|
|
735
|
+
try {
|
|
736
|
+
if (ctx.locale && typeof ctx.locale.getLocale === "function") localeSvc = ctx.locale;
|
|
737
|
+
} catch (e) {}
|
|
738
|
+
// Settings UI — only when the full web profile (react + slots + locale)
|
|
739
|
+
// is present. The reminder core above never depends on this.
|
|
740
|
+
try {
|
|
741
|
+
if (React && React.createElement && ctx.slots && ctx.locale && typeof ctx.locale.register === "function" && typeof ctx.slots.inject === "function") {
|
|
742
|
+
ctx.effect(function () {
|
|
743
|
+
ctx.locale.register(NS, { zh: UI_zh, en: UI_en });
|
|
744
|
+
}, "dsh-notify-me: settings dictionaries");
|
|
745
|
+
var t = ctx.locale.bind(NS);
|
|
746
|
+
ctx.slots.inject("settings.section", function () {
|
|
747
|
+
return ctx.slots.register({
|
|
748
|
+
name: "settings.section",
|
|
749
|
+
id: "dsh-notify-me",
|
|
750
|
+
order: 45,
|
|
751
|
+
label: function () { return t("nav"); },
|
|
752
|
+
locale: NS,
|
|
753
|
+
inject: function () { return { t: t }; }
|
|
754
|
+
}, function (ownerProps) {
|
|
755
|
+
return React.createElement(NotifySettingsView, { ctx: ctx, t: t });
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
} catch (e) {
|
|
760
|
+
try { console.error("[dsh-notify-me] settings page unavailable", e); } catch (e2) {}
|
|
761
|
+
}
|
|
400
762
|
ctx.effect(function () {
|
|
401
763
|
var disposed = false;
|
|
402
764
|
var unsubs = [];
|
|
@@ -416,7 +778,7 @@ window.__ModuleLoader__.load({
|
|
|
416
778
|
if (disposed) return;
|
|
417
779
|
try { bindCurrentFace(); } catch (e) {}
|
|
418
780
|
}, 1500);
|
|
419
|
-
console.log("[dsh-notify-me] active — window.__dshNotifyMe 可配置提醒 (toast=" + config.toast + ", sound=" + config.sound + ")");
|
|
781
|
+
try { console.log("[dsh-notify-me] active — window.__dshNotifyMe 可配置提醒 (enabled=" + config.enabled + ", lang=" + effectiveLang() + ", toast=" + config.toast + ", sound=" + config.sound + ")"); } catch (e) {}
|
|
420
782
|
} catch (e) {
|
|
421
783
|
try { console.error("[dsh-notify-me] init failed", e); } catch (e2) {}
|
|
422
784
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-notify-me",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "1.1.0",
|
|
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; toggleable from Settings with zh/en notification language.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -15,15 +15,33 @@
|
|
|
15
15
|
"lib/client.js",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"README.md",
|
|
18
|
+
"README.en.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
18
20
|
"LICENSE"
|
|
19
21
|
],
|
|
20
22
|
"keywords": [
|
|
21
23
|
"deepseek-harness",
|
|
22
24
|
"dsh",
|
|
23
25
|
"dsh-plugin",
|
|
26
|
+
"dsh-web",
|
|
24
27
|
"notification",
|
|
25
|
-
"desktop",
|
|
28
|
+
"desktop-notification",
|
|
26
29
|
"notify",
|
|
30
|
+
"notifications",
|
|
31
|
+
"alerts",
|
|
32
|
+
"reminder",
|
|
33
|
+
"reminders",
|
|
34
|
+
"message-alert",
|
|
35
|
+
"needs-your-input",
|
|
36
|
+
"approval-alert",
|
|
37
|
+
"background-alert",
|
|
38
|
+
"桌面通知",
|
|
39
|
+
"消息提醒",
|
|
40
|
+
"可操作提醒",
|
|
41
|
+
"提醒插件",
|
|
42
|
+
"需要你操作",
|
|
43
|
+
"回复完成提醒",
|
|
44
|
+
"后台完成提醒",
|
|
27
45
|
"提醒"
|
|
28
46
|
],
|
|
29
47
|
"engines": {
|
|
@@ -33,6 +51,9 @@
|
|
|
33
51
|
"test": "node smoke/smoke-test.cjs",
|
|
34
52
|
"pack": "npm pack --dry-run"
|
|
35
53
|
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
36
57
|
"author": "chromoany",
|
|
37
58
|
"repository": {
|
|
38
59
|
"type": "git",
|