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 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
+ [![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-%7E37KB-green)
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
+ | ![DSH notification](docs/screenshots/notify-toast.png) |
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
- DSH(DeepSeek Harness)web GUI 的**桌面提醒**插件:当你切到别的软件、看不到 DSH 页面时,在"模型需要你操作"和"回复完成"两种时刻提醒你。
3
+ 简体中文 | [English](README.en.md)
4
4
 
5
- | 时刻 | 提醒 | 默认 |
6
- |---|---|---|
7
- | 🔔 需要你操作 | 审批请求 / 方案待确认(plan review)/ 提问(`ask_user_question`) | 页面可见也提醒 |
8
- | ✅ 回复完成 | 一次回复(turn)跑完;后台会话完成也提醒 | 仅页面隐藏/后台时提醒 |
5
+ [![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
9
 
10
- 提醒方式(浏览器层实现,**需要 DSH 页面保持打开**):
10
+ ---
11
11
 
12
- - **系统通知**:Windows 通知中心 Toast(点通知可切回 DSH 窗口)
13
- - **提示音**:Web Audio 合成提示音(需先在页面上点击过,浏览器才允许发声)
14
- - **标签页标题标记**:有待处理提醒时标题前出现 `🔔 需要你 · …`
12
+ **离开 DSH 页面也不错过任何动静。** 当模型停下来需要你操作(审批 / 方案待确认 / 提问)、或你在别的软件时回复正好在后台完成,dsh-notify-me 会用系统通知 + 提示音 + 标签页标题标记提醒你——开关和通知语言都直接在 **DSH 设置 → 通知提醒** 里改。
15
13
 
16
- ## 安装
14
+ ---
17
15
 
18
- ```powershell
19
- # 从 npm 安装(发布后)
20
- dsh plugin --profile web add dsh-notify-me
16
+ > 🔎 想找这个插件?可以搜:`dsh-notify-me`、**消息提醒**、**可操作提醒**、**桌面通知**、**需要你操作**、**回复完成提醒**、**后台完成提醒**(npm 关键词已含以上中英文词)。
21
17
 
22
- # 或本地开发安装(指向本项目目录)
23
- dsh plugin --profile web add "D:\chromoany\dsh-notify-me"
24
- ```
18
+ ## 效果预览
19
+
20
+ | Windows 系统通知效果 |
21
+ | --- |
22
+ | ![DSH 通知效果](docs/screenshots/notify-toast.png) |
23
+
24
+ [更新日志](CHANGELOG.md)
25
+
26
+ ## 提醒时机
27
+
28
+ | 时机 | 提醒内容 | 默认 |
29
+ | --- | --- | --- |
30
+ | 🔔 **模型需要你操作** — 审批请求 / 方案待确认(plan review)/ 提问(`ask_user_question`) | 通知 + 提示音 + `🔔 需要你 ·` 标题标记 | 页面可见也提醒 |
31
+ | ✅ **回复完成** — 一轮回复跑完;后台会话完成也会报 | 通知 + 提示音 | 仅页面隐藏/后台时提醒 |
32
+
33
+ ## 在 DSH 设置里改配置
25
34
 
26
- 然后**重启 dsh web**(新增插件需重启后进入页面清单),刷新页面即生效。
35
+ 打开 **设置 → 通知提醒**(首次安装后刷新一次页面即可看到),页面提供:
27
36
 
28
- ### 首次授权
37
+ - **启用提醒** 主开关:关闭后不再弹系统通知、不播放提示音、也不改标签页标题;
38
+ - **系统通知 / 提示音 / 音量**:Toast、WebAudio 提示音与音量滑块;
39
+ - **页面打开时也提醒「需要你」**(默认开)与 **页面打开时也提醒「回复完成」**(默认关);
40
+ - **通知语言**:跟随界面 / 简体中文 / English——决定通知文字与 `🔔 …` 标题标记使用的语言;
41
+ - **测试按钮**:用当前设置立即发一条「需要你」或「回复完成」测试提醒。
29
42
 
30
- 第一次在 DSH 页面上**点击**(或按键)时,浏览器会请求"显示通知"权限,选**允许**,否则只有提示音没有系统通知。误点拒绝后:点地址栏左侧锁 → 站点设置 → 通知 → 允许 → 刷新。
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
- 切到别的软件(让页面隐藏)后,在 F12 控制台执行:
62
+ **验证** — F12 控制台执行:
35
63
 
36
64
  ```js
37
- window.__dshNotifyMe.test("attention") // "需要你"提醒
38
- window.__dshNotifyMe.test("done") // "完成"提醒
65
+ window.__dshNotifyMe.test("done") // "完成"示例
66
+ window.__dshNotifyMe.test("attention") // "需要你"示例
39
67
  ```
40
68
 
41
- ## 设置(F12 控制台)
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
- - 当前会话 `ConversationSnapshot`:`running` true→false = 回复完成;`pending[]` 出现 `approval / plan-review / question` = 需要你操作;
69
- - 其余会话列表摘要:`pendingInteraction` 出现 / `completed` 边沿 = 后台会话需要你或已完成。
94
+ - **当前会话** `ConversationSnapshot`:`running` true→false = 回复完成;`pending[]` 新增 `approval` / `plan-review` / `question` = 模型在等你(可行时在提醒里展示提问/审批内容);
95
+ - **其它已列会话**摘要:出现新 `pendingInteraction`,或 `completed` 边沿(非选中状态下跑完)→ 后台工作提醒。
70
96
 
71
- 无 React/UI、无 settings 命名空间依赖;偏好存 `localStorage`(键 `dshNotifyMe.config`)。
97
+ 提醒核心仍然零第三方运行时依赖、完全自包含:通知文字按所选语言(跟随界面 / 中文 / English)即时解析。设置页是**可选**的 React 呈现层——当 DSH web profile 提供 `slots` / `locale` / `react` 时才注册进「设置 → 通知提醒」,缺任一能力时插件自动降级为纯提醒(无设置页),不影响功能。
72
98
 
73
- ## 发布与上架
99
+ ## 已知限制
74
100
 
75
- ```powershell
76
- # 1. 登录 npm(npmjs.com 注册账号后)
77
- npm login
78
-
79
- # 2. 打包并发布(files 白名单已限制包内容)
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
- - 浏览器层方案:页面必须开着(最小化/后台可以)。若需"浏览器全关也能收到"的 Host 层原生 Windows 通知,可另装 Host 层通知插件。
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 ? "🔔 需要你 · " + originalTitle : originalTitle;
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
- function fire(kind, title, body) {
166
- if (kind === "attention" && config.attentionHiddenOnly && !hidden()) return;
167
- if (kind === "done" && config.doneHiddenOnly && !hidden()) return;
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 label = { approval: "审批请求", "plan-review": "方案待确认", question: "提问" }[kind] || "需要操作";
183
- var d = detail || "";
184
- fire("attention", "DSH · " + label, (sessionLabel ? sessionLabel + ":" : "") + d);
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 body = sessionLabel ? sessionLabel : "回复已完成";
293
+ var d = texts();
294
+ var body = sessionLabel || d.doneBody;
188
295
  if (snippet) body = body + "\n" + snippet;
189
- fire("done", "DSH · 回复完成", body);
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 = { approval: "有审批待处理", "plan-review": "方案待确认", question: "有提问待回答" }[it.kind] || "需要你操作";
256
- var label = row.label && row.label !== "current" ? row.label : "当前对话";
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.label || "后台会话", "");
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.label && row.label !== "current" ? row.label : "当前对话", row.face ? snippetOfLastAssistant(row.faceSnap) : "");
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: 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.1.0",
476
+ version: "0.2.0",
372
477
  get config() { return readConfig(); },
373
- setConfig: function (patch) { config = writeConfig(patch); return config; },
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
- if (kind === "attention") fireAttention("question", "测试", "这是一条测试提醒:DSH 需要你的操作");
384
- else fireDone("测试提醒", "");
385
- return "sent " + kind + " (permission=" + ("Notification" in window ? Notification.permission : "n/a") + ")";
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.0.0",
4
- "description": "DSH web plugin (桌面提醒): desktop notifications and sounds when the agent needs your input (approval / question / plan review) and when a reply finishes, so you notice while working in other apps.",
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",