dsh-notify-me 1.5.1 → 1.5.2

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,25 @@
2
2
 
3
3
  All notable changes to **dsh-notify-me** are documented here.
4
4
 
5
+ ## [1.5.2] — 2026-10-04
6
+
7
+ ### 新增
8
+ - **设置页新增「运行环境」**(默认自动识别):按页面协议(`dsh-app://` = 桌面端 DSH Desktop)识别 Web 端 / 桌面端,识别不准时可手动指定「桌面端」或「Web 端」。手动覆盖同时决定两件事:点通知的抬窗通道(桌面端 `dsh://` 深链 / Web 端 `window.focus()`)与快捷裁决按钮的可用性标注;覆盖与自动识别不一致时设置页明示检测结果,强选「桌面端」但页面并非 `dsh-app://` 时另提示可能弹「打开应用」确认框。
9
+
10
+ ### 修复
11
+ - **桌面端「点击通知回到对应对话」真正把窗口带到前台**(issue #9):DSH Desktop 的页面跑在 `dsh-app://app`(Electron),DOM `window.focus()` 碰不到原生窗口,此前点通知只在后台切好对话、窗口不上前台。现在点通知正文时页面发一条隐藏 iframe 导航到 `dsh://open` —— 子框架导航不经过壳的 `will-navigate` / window-open 拦截,URL 交给系统协议注册,应用单实例锁的 `second-instance` 处理走壳自己的 `focusPrimaryWindow()`(restore + show + focus)。窗口隐藏在托盘、被别的窗口压住、最小化都能抬回来;切对话行为不变,浏览器端不受影响。
12
+
13
+ ### 变更
14
+ - **设置页在桌面端明示快捷裁决按钮不可用**:`dsh-app://` 页面无法注册 Service Worker(实测),审批通知的「同意 / 拒绝」按钮渲染不了、自动退回无按钮样式;此前开关本身毫无标注(只有测试按钮的反馈里带一行原始 `bridge` 状态),容易被当成开关坏了。现在设置页在桌面端直接说明这是宿主限制;「测试『审批按钮』」在无桥环境发普通测试通知时也明说按钮渲染不了。
15
+ - **测试按钮的反馈文字移到按钮正下方**:此前落在页面最底部(常在视口外),点了测试看不到任何反馈,像「没反应」;无桥 / 开关关闭时的提示也一并放在同一位置。
16
+ - `window.__dshNotifyMe.debug()` 新增 `desktop`(生效环境是否桌面端)、`raiseChannel`(`dsh-deeplink` / `window-focus`)、`hostEnv`(设置值)与 `hostEnvDetected`(自动识别结果)。
17
+ - 版本号 1.5.1 → 1.5.2;`window.__dshNotifyMe.version` 同步。
18
+ - README(中英)同步:设置清单加「运行环境」,新增「桌面端怎么抬窗的 / How the Desktop window gets raised」一节,「点击通知回到对应对话」「审批通知上直接裁决」两条口径与已知限制(桌面端按钮不可用由「未实测」改为实测结论)。
19
+
20
+ ### 测试
21
+ - 抬窗通道实测:最小 Electron 44 复刻(`setWindowOpenHandler` / `will-navigate` 与桌面壳同款拦截、不装 `setPermissionRequestHandler`)上,非沙箱 iframe 导航 `dsh://open` 抬起真实 DSH Desktop 窗口(restore + 前台约 0.8s),沙箱 iframe 被拒(Electron GHSA-p2rr-rvmm-c5fp 修复生效),`window.focus()` 不抬(与 issue #9 结论一致);`dsh://open` 经系统协议回流四轮实测均可靠抬窗。
22
+ - `smoke/smoke-test.cjs` 新增桌面抬窗通道用例段:桌面端点通知正文发 `dsh://open` 深链且仍切对话、浏览器端绝不发深链、`hostEnv` 手动覆盖双向生效(强 Web 桌面页不发链、强桌面 Web 页发链)。
23
+
5
24
  ## [1.5.1] — 2026-10-04
6
25
 
7
26
  ### 修复
package/README.en.md CHANGED
@@ -52,12 +52,13 @@ dsh-notify-me decouples **supervision** from **sitting in front of the screen**:
52
52
  Open **Settings → Notify me** (refresh the page once after installing):
53
53
 
54
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;
55
56
  - **System notifications / Sound / Volume** — toast toggle, WebAudio beep toggle and a volume slider;
56
57
  - **"Needs you" alerts while the page is open** (default on) and **"Reply finished" alerts while the page is open** (default off);
57
58
  - **Stay quiet for the conversation on screen** (default on): a wait inside the conversation you are looking at keeps only the tab marker — no toast or sound landing on the approval card. Backgrounding the page puts that alert back at once, unless you already handled it. Waits in other background sessions are unaffected;
58
59
  - **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;
59
- - **Click a toast to open that conversation** (default on): clicking brings DSH to the front and switches to the conversation the alert came from. 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;
60
- - **Decide approvals from the toast** (default on): approval notifications carry Approve / Reject buttons, and one click settles the request exactly like the Allow once / Reject buttons on the approval card. Clicking the toast body still takes you back to the conversation;
60
+ - **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);
61
+ - **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;
61
62
  - **Notification language** — follow the interface / 简体中文 / English: controls the language of the alert text and the `🔔 …` title marker;
62
63
  - **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).
63
64
 
@@ -147,14 +148,18 @@ Action buttons (`actions`) belong to persistent notifications shown through `Ser
147
148
 
148
149
  Every button toast is booked against its interaction key, so it can only decide the request it was raised for. Once the page answers that request or a newer one replaces it, clicking the leftover buttons decides nothing and just closes the notification. Button toasts also skip the auto-dismiss timer: an undecided approval stays in the notification center until you settle it.
149
150
 
151
+ ### How the Desktop window gets raised
152
+
153
+ DSH Desktop is a thin Electron shell and its page lives under `dsh-app://app`: DOM `window.focus()` only touches the browsing-context window, never the native one, and the shell's `will-navigate` / window-open handlers block every ordinary way for the page to fire a `dsh://` URL — so through 1.5.1 a toast click switched the conversation in the background but never brought the window forward. Since 1.5.2 a body click navigates a hidden iframe to `dsh://open`: subframe navigations pass the shell's `will-navigate` interception, the URL reaches the OS protocol registration, and the app's single-instance owner runs its own `focusPrimaryWindow()` (restore + show + focus) — the same path the login completion page uses to bring the client forward. A window hidden to the tray, buried under others, or minimized all come back. Browsers are unaffected and still use `window.focus()` / the worker's `client.focus()`.
154
+
150
155
  ## Known limitations
151
156
 
152
157
  - 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).
153
158
  - 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.
154
159
  - The first sound/toast of each page load needs one user click on the page first (browser autoplay + permission policy).
155
160
  - Toasts are only produced once the notification permission is granted; declining means sound + title marker only.
156
- - 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 `bridge` field of `window.__dshNotifyMe.debug()` says why; toasts then fall back to their buttonless form and everything else keeps working.
157
- - 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 macOS / desktop hosts are likewise unrendered in some cases, falling back to the buttonless form above.
161
+ - 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.
162
+ - 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.
158
163
  - Config lives in browser `localStorage`: switching browsers/devices or clearing site data returns to defaults (one-click restore in the Settings page).
159
164
  - 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.
160
165
  - 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.
package/README.md CHANGED
@@ -52,12 +52,13 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
52
52
  打开 **设置 → 通知提醒**(首次安装后刷新一次页面即可看到),页面提供:
53
53
 
54
54
  - **启用提醒** 主开关:关闭后不再弹系统通知、不播放提示音、也不改标签页标题;
55
+ - **运行环境**(默认自动识别):按页面协议识别 Web 端 / 桌面端(DSH Desktop,`dsh-app://`),识别不准时可手动指定——桌面端点通知走 `dsh://` 深链抬窗、快捷裁决按钮不可用;Web 端走 `window.focus()` 与 Service Worker 桥。手动覆盖与自动识别不一致时,设置页会明示检测结果;
55
56
  - **系统通知 / 提示音 / 音量**:Toast、WebAudio 提示音与音量滑块;
56
57
  - **页面打开时也提醒「需要你」**(默认开)与 **页面打开时也提醒「回复完成」**(默认关);
57
58
  - **当前对话不弹通知**(默认开):等待就发生在你正看着的对话里时只留标题标记,不弹通知也不响音(免得盖住审批卡片);页面切到后台立刻补发,若你已经处理掉就作废。其它后台会话不受影响;
58
59
  - **子代理会话不提醒**(默认开):后台子代理(subagent)子会话的「需要你」与「回复完成」都不提醒——它们是主对话那一轮的步骤,完成由主对话自己的提醒覆盖;你当前打开的那个子代理会话照常提醒;关掉后恢复逐会话提醒;
59
- - **点击通知回到对应对话**(默认开):点通知把 DSH 带到前台并切到提醒所属的对话;多个 DSH 标签页同时开着时,一次点击只调起一个窗口(优先当前可见的那个)并只切它的对话,其余标签页不动;关掉后点通知不做任何事;
60
- - **审批通知上直接裁决**(默认开):权限审批的通知上带「同意 / 拒绝」按钮,点一下就按审批卡片的「允许一次 / 拒绝」直接裁决,不用切回页面;点通知正文仍是原来的「切回对应对话」;
60
+ - **点击通知回到对应对话**(默认开):点通知把 DSH 带到前台并切到提醒所属的对话;多个 DSH 标签页同时开着时,一次点击只调起一个窗口(优先当前可见的那个)并只切它的对话,其余标签页不动;关掉后点通知不做任何事。桌面端(DSH Desktop)同样会把窗口带到前台并切对话:页面侧的 `window.focus()` 在 Electron 里抬不动原生窗口,1.5.2 起改走应用自己的 `dsh://open` 深链调窗(窗口隐藏在托盘里也能被抬回来);
61
+ - **审批通知上直接裁决**(默认开):权限审批的通知上带「同意 / 拒绝」按钮,点一下就按审批卡片的「允许一次 / 拒绝」直接裁决,不用切回页面;点通知正文仍是原来的「切回对应对话」。**桌面端暂不支持按钮**(`dsh-app://` 页面无法注册 Service Worker),审批通知自动退回无按钮样式,设置页会明示这一限制;
61
62
  - **通知语言**:跟随界面 / 简体中文 / English——决定通知文字与 `🔔 …` 标题标记使用的语言;
62
63
  - **测试按钮**:用当前设置立即发一条「需要你」「回复完成」或「审批按钮」测试提醒(**不受**上面「页面打开时也提醒」开关限制;「需要你」测试的标题标记约 6 秒后自动消失)。
63
64
 
@@ -143,14 +144,18 @@ window.__dshNotifyMe.decide("approval:3", "allowed-once") // 程序化裁决:
143
144
 
144
145
  每条按钮通知按交互 key 记账,一次只认领一条等待;请求被页面里答掉、或被新请求顶替之后,残留通知上的按钮再点也不会误裁,只是安静地关掉。带按钮的通知不设自动消失——审批没裁决就一直留在通知中心里等你。
145
146
 
147
+ ### 桌面端怎么抬窗的
148
+
149
+ DSH Desktop 是一层 Electron 壳,页面跑在 `dsh-app://app` 下:DOM 的 `window.focus()` 只动浏览器窗口对象、碰不到原生窗口,壳的 `will-navigate` / `window-open` 又把页面里发 `dsh://` 的常规出口全堵上了——所以 1.5.1 及以前点通知只在后台切好对话,窗口不上前台。1.5.2 起点通知正文时页面发一条隐藏 iframe 导航到 `dsh://open`:子框架导航不经过壳的 `will-navigate` 拦截,URL 交给系统协议注册,应用的单实例锁收到第二次启动后走 `focusPrimaryWindow()`(restore + show + focus)——与登录完成页把客户端置前是同一条路。窗口隐藏在托盘、被别的窗口压住、最小化,都能被抬回来。浏览器端不受影响,仍走 `window.focus()` / worker `client.focus()`。
150
+
146
151
  ## 已知限制
147
152
 
148
153
  - 页面必须开着才会提醒(后台标签/最小化可以;关标签页即失效——浏览器层方案固有限制)。
149
154
  - 通知经由浏览器弹出,需允许浏览器通知权限,且勿扰模式不能屏蔽它。
150
155
  - 每次页面加载后第一次出声/弹通知前,需在页面上点击过一次(浏览器自动播放与权限策略)。
151
156
  - 未授权通知权限时只有提示音与标题标记。
152
- - 快捷裁决按钮要 Service Worker:`http://127.0.0.1`、`https` 可用,`http://192.168.x.x` 这类局域网地址不行;浏览器或桌面宿主不渲染通知按钮时,`window.__dshNotifyMe.debug()` 的 `bridge` 字段会说明原因,通知自动退回无按钮样式,其余功能不受影响。
153
- - macOS:浏览器里提醒、提示音、标题标记、点通知跳转都没有平台假设,照常工作;但 DSH Desktop(Electron)的系统通知要求应用已签名,未签名的通知直接发不出去,官方 macOS 版是否满足这一前提未实测;审批按钮在 macOS 与桌面宿主上的渲染同样未实测,不渲染时自动退回无按钮通知。
157
+ - 快捷裁决按钮要 Service Worker:`http://127.0.0.1`、`https` 可用,`http://192.168.x.x` 这类局域网地址不行;**桌面端(`dsh-app://`)已实测无法注册 Service Worker**,按钮渲染不了,审批通知退回无按钮样式。浏览器或桌面宿主不渲染通知按钮时,`window.__dshNotifyMe.debug()` 的 `bridge` 字段会说明原因,其余功能不受影响。
158
+ - macOS:浏览器里提醒、提示音、标题标记、点通知跳转都没有平台假设,照常工作;但 DSH Desktop(Electron)的系统通知要求应用已签名,未签名的通知直接发不出去,官方 macOS 版是否满足这一前提未实测;审批按钮在 macOS 桌面宿主上的渲染同样未实测(Windows 桌面端已实测不渲染,见上条),不渲染时自动退回无按钮通知。
154
159
  - 配置存在浏览器 `localStorage`:换浏览器/设备或清除站点数据后会回到默认值(设置页可一键恢复默认)。
155
160
  - `≥ 0.1.6`(含 `0.2.0-rc.2`)的「回复完成」提醒正文只有会话名,不再附带回复摘要——宿主快照已不再提供 `nodes`;提醒本身照常触发。
156
161
  - 标题标记由宿主与插件共同写 `document.title`:宿主重算标题时标记可能被覆盖,直到下一次提醒事件重新写成。
package/lib/client.js CHANGED
@@ -216,6 +216,12 @@
216
216
  // language of the notification copy: 'auto' follows the DSH UI locale,
217
217
  // 'zh' and 'en' pin Simplified Chinese / English.
218
218
  language: "auto",
219
+ // Which host environment this page runs in: 'auto' detects from the page
220
+ // protocol (dsh-app:// = the packaged DSH Desktop shell), 'desktop' and
221
+ // 'web' force it. The desktop rides the dsh:// deep-link raise path and
222
+ // cannot carry quick-decision buttons at all; the browser uses
223
+ // window.focus() and the Service Worker bridge.
224
+ hostEnv: "auto",
219
225
  // toast + sound are only useful while the user is elsewhere; the tab
220
226
  // being hidden is the reliable "away" signal.
221
227
  attentionHiddenOnly: false, // needs-input alerts even while the page is visible
@@ -360,6 +366,44 @@
360
366
  return "Notification" in window && Notification.permission === "granted";
361
367
  } catch (e) { return false; } // a policy-disabled Notification must not break delivery
362
368
  }
369
+ // True on the packaged desktop shell, where the app page lives under
370
+ // dsh-app://app inside Electron. The desktop needs its own raise path
371
+ // (raiseWindow below) and cannot carry quick-decision buttons at all.
372
+ // Detection is a page-protocol read; the hostEnv setting can override it
373
+ // for hosts where detection comes out wrong.
374
+ function detectedDesktop() {
375
+ try { return location.protocol === "dsh-app:"; } catch (e) { return false; }
376
+ }
377
+ function isDesktopShell() {
378
+ if (config.hostEnv === "desktop") return true;
379
+ if (config.hostEnv === "web") return false;
380
+ return detectedDesktop();
381
+ }
382
+ // Bring the OS window forward. A browser tab answers to window.focus();
383
+ // the Electron shell does not — DOM focus() never touches the native
384
+ // window, so a toast click there left the session switched but the window
385
+ // buried. The desktop rides the app's own dsh://open deep link instead:
386
+ // the URL is fired through a hidden iframe (the shell's will-navigate and
387
+ // window-open handlers intercept top-level navigations and window.open()
388
+ // to non-http(s) URLs, but not subframe navigations), the OS hands it to
389
+ // the protocol registration, and the single-instance owner restores,
390
+ // shows and focuses the window — the same path the login completion page
391
+ // uses to bring DSH forward.
392
+ function raiseWindow() {
393
+ try { window.focus(); } catch (e) {}
394
+ if (!isDesktopShell()) return;
395
+ try {
396
+ var f = document.createElement("iframe");
397
+ f.setAttribute("aria-hidden", "true");
398
+ f.setAttribute("tabindex", "-1");
399
+ f.style.cssText = "position:fixed;left:-10px;top:-10px;width:0;height:0;border:0;visibility:hidden";
400
+ f.src = "dsh://open";
401
+ (document.body || document.documentElement).appendChild(f);
402
+ setTimeout(function () {
403
+ try { if (f.parentNode) f.parentNode.removeChild(f); } catch (e) {}
404
+ }, 4000);
405
+ } catch (e) {}
406
+ }
363
407
  // Bring the window forward and make the alerted session the current one,
364
408
  // so a click on the toast lands on the conversation that needs the user
365
409
  // instead of just the app. Navigation moved with the host API: `sessions`
@@ -369,7 +413,7 @@
369
413
  // cordis service that cannot be resolved must still leave a usable window
370
414
  // in front.
371
415
  function focusSession(sessionId) {
372
- try { window.focus(); } catch (e) {}
416
+ raiseWindow();
373
417
  if (!sessionId) return;
374
418
  try {
375
419
  var st = listStore && listStore.getSnapshot();
@@ -1435,7 +1479,7 @@
1435
1479
  });
1436
1480
  } catch (e) {}
1437
1481
  // keep in sync with package.json "version" on every release
1438
- api.version = "1.5.1";
1482
+ api.version = "1.5.2";
1439
1483
  api.setConfig = function (patch) { return setConfigLocal(patch); };
1440
1484
  api.resetConfig = function () {
1441
1485
  try { localStorage.removeItem(LS_CONFIG); } catch (e) {}
@@ -1475,6 +1519,11 @@
1475
1519
  // quick-decision bridge: 'active' is the only state in which approval
1476
1520
  // toasts carry working 同意/拒绝 buttons.
1477
1521
  bridge: swState,
1522
+ // how a toast click gets the OS window forward on this host
1523
+ hostEnv: config.hostEnv,
1524
+ hostEnvDetected: detectedDesktop() ? "desktop" : "web",
1525
+ desktop: isDesktopShell(),
1526
+ raiseChannel: isDesktopShell() ? "dsh-deeplink" : "window-focus",
1478
1527
  actionKeys: Object.keys(pendingActions),
1479
1528
  permission: ("Notification" in window) ? Notification.permission : "n/a",
1480
1529
  visibility: hidden() ? "hidden" : "visible",
@@ -1502,6 +1551,14 @@
1502
1551
  blurb: "当模型停下来需要你操作(审批 / 方案待确认 / 提问),或回复在后台完成时,用系统通知、提示音和标签页标题提醒你。",
1503
1552
  enable: "启用提醒",
1504
1553
  enableHint: "总开关:关闭后不再弹系统通知、不播放提示音,也不再改动标签页标题。",
1554
+ hostEnv: "运行环境",
1555
+ hostEnvHint: "默认自动识别:页面协议是 dsh-app:// 即桌面端(DSH Desktop),其余为 Web 端。识别不准时手动指定:桌面端点通知借 dsh:// 深链把窗口带到前台、快捷裁决按钮不可用;Web 端走 window.focus() 与 Service Worker 桥。",
1556
+ hostEnvAuto: "自动识别(推荐)",
1557
+ hostEnvDesktop: "桌面端(DSH Desktop)",
1558
+ hostEnvWeb: "Web 端(浏览器)",
1559
+ hostEnvMismatch: "(自动识别结果是「",
1560
+ hostEnvMismatchTail: "」,手动选择会覆盖它)",
1561
+ hostEnvDesktopForcedNote: "(当前页面不是 dsh-app://:点通知会尝试唤起 dsh:// 协议,浏览器可能弹「打开应用」确认框)",
1505
1562
  toast: "系统通知",
1506
1563
  toastHint: "经浏览器弹出系统 Toast(Windows 通知中心);需要已授予通知权限。",
1507
1564
  sound: "提示音",
@@ -1521,6 +1578,9 @@
1521
1578
  quickApprovalNote: "「同意」等价于审批卡片上的「允许一次」,只放行这一次。",
1522
1579
  bridgeOff: "快捷裁决按钮暂不可用(",
1523
1580
  bridgeOffTail: "):审批通知会退回不带按钮的普通样式。",
1581
+ bridgeOffDesktop: "桌面端暂不支持快捷裁决按钮:dsh-app:// 页面无法注册 Service Worker,审批通知只能是不带按钮的普通样式,测试按钮同样只发普通通知。这是宿主限制,不是设置坏了。",
1582
+ testApprovalNoBridge: "已发普通测试通知(快捷裁决按钮需要 Service Worker,当前环境没有,按钮渲染不了)。",
1583
+ testNeedsQuick: "先打开上面的「审批通知上直接裁决」才能测按钮。",
1524
1584
  volume: "音量",
1525
1585
  language: "通知语言",
1526
1586
  langAuto: "跟随界面",
@@ -1544,6 +1604,14 @@
1544
1604
  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.",
1545
1605
  enable: "Enable reminders",
1546
1606
  enableHint: "Master switch: when off, no toasts, no sounds, and the tab title is never modified.",
1607
+ hostEnv: "Environment",
1608
+ hostEnvHint: "Auto-detect by default: a dsh-app:// page is the Desktop (DSH Desktop) shell, anything else is the Web. Override it if detection comes out wrong: on the Desktop a toast click raises the window through the dsh:// deep link and quick-decision buttons are unavailable; on the Web it is window.focus() plus the Service Worker bridge.",
1609
+ hostEnvAuto: "Auto-detect (recommended)",
1610
+ hostEnvDesktop: "Desktop (DSH Desktop)",
1611
+ hostEnvWeb: "Web (browser)",
1612
+ hostEnvMismatch: " (auto-detection sees \"",
1613
+ hostEnvMismatchTail: "\"; the manual choice overrides it)",
1614
+ hostEnvDesktopForcedNote: " (this page is not dsh-app://: a toast click will try to launch the dsh:// protocol and the browser may show an \"open app?\" prompt)",
1547
1615
  toast: "System notifications",
1548
1616
  toastHint: "Native toasts via the browser (Windows notification center); requires notification permission.",
1549
1617
  sound: "Sound",
@@ -1563,6 +1631,9 @@
1563
1631
  quickApprovalNote: "\"Approve\" equals \"Allow once\" on the approval card — it allows that one call only.",
1564
1632
  bridgeOff: "Quick-decision buttons are unavailable right now (",
1565
1633
  bridgeOffTail: "); approval toasts fall back to their plain form.",
1634
+ bridgeOffDesktop: "Quick-decision buttons are unavailable on Desktop: dsh-app:// pages cannot register a Service Worker, so approval toasts fall back to their plain, button-less form and the test button only sends a plain notification. This is a host limitation, not a broken setting.",
1635
+ testApprovalNoBridge: "Plain test notification sent (quick-decision buttons need a Service Worker, which this environment lacks, so they cannot render).",
1636
+ testNeedsQuick: "Turn on \"Decide approvals from the toast\" above to test the buttons.",
1566
1637
  volume: "Volume",
1567
1638
  language: "Notification language",
1568
1639
  langAuto: "Follow the interface",
@@ -1663,19 +1734,45 @@
1663
1734
  testAlert(kind);
1664
1735
  var langName = effectiveLang() === "en" ? t("langEn") : t("langZh");
1665
1736
  var msg = t("sent") + langName;
1666
- if (kind === "approval" && !bridgeActive()) msg += " · " + t("bridgeOff") + swState + t("bridgeOffTail");
1737
+ if (kind === "approval" && !bridgeActive()) {
1738
+ msg += " · " + (isDesktopShell() ? t("testApprovalNoBridge") : t("bridgeOff") + swState + t("bridgeOffTail"));
1739
+ }
1667
1740
  setFeedback(msg);
1668
1741
  }
1669
1742
  var permOK = ("Notification" in window) ? Notification.permission === "granted" : false;
1670
1743
  var resolvedLang = effectiveLang();
1671
1744
  var resolvedLangName = resolvedLang === "en" ? t("langEn") : t("langZh");
1745
+ // The environment row states what detection saw whenever the manual
1746
+ // choice disagrees with it — the override exists for hosts where the
1747
+ // protocol read comes out wrong, so the divergence must be visible.
1748
+ var envHint = t("hostEnvHint");
1749
+ if (cfg.hostEnv === "desktop" && !detectedDesktop()) envHint += t("hostEnvDesktopForcedNote");
1750
+ else if (cfg.hostEnv !== "auto" && isDesktopShell() !== detectedDesktop()) {
1751
+ envHint += t("hostEnvMismatch") + (detectedDesktop() ? t("hostEnvDesktop") : t("hostEnvWeb")) + t("hostEnvMismatchTail");
1752
+ }
1672
1753
  return h("div", { className: "dnm-wrap" }, [
1673
1754
  h("p", { className: "dnm-blurb" }, t("blurb")),
1674
1755
  card([
1675
1756
  rowSwitch("enabled", t("enable"), t("enableHint"), cfg.enabled, function (v) {
1676
1757
  patch({ enabled: v });
1677
1758
  if (!v && attentionActive > 0) { attentionActive = 0; applyTitle(); }
1678
- })
1759
+ }),
1760
+ h("div", { className: "dnm-row" }, [
1761
+ h("div", { className: "dnm-rowText" }, [
1762
+ h("div", { className: "dnm-rowLabel" }, t("hostEnv")),
1763
+ h("div", { className: "dnm-rowHint" }, envHint)
1764
+ ]),
1765
+ h("select", {
1766
+ className: "dnm-select",
1767
+ value: cfg.hostEnv,
1768
+ "aria-label": t("hostEnv"),
1769
+ onChange: function (e) { patch({ hostEnv: e.currentTarget.value }); }
1770
+ }, [
1771
+ h("option", { value: "auto" }, t("hostEnvAuto")),
1772
+ h("option", { value: "desktop" }, t("hostEnvDesktop")),
1773
+ h("option", { value: "web" }, t("hostEnvWeb"))
1774
+ ])
1775
+ ])
1679
1776
  ]),
1680
1777
  card([
1681
1778
  rowSwitch("toast", t("toast"), t("toastHint"), cfg.toast, function (v) { patch({ toast: v }); }),
@@ -1733,9 +1830,12 @@
1733
1830
  ])
1734
1831
  ]),
1735
1832
  cfg.quickActions && cfg.toast && !bridgeActive() ? h("div", { className: "dnm-row" }, [
1736
- h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, t("bridgeOff") + swState + t("bridgeOffTail"))])
1833
+ h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, isDesktopShell() ? t("bridgeOffDesktop") : t("bridgeOff") + swState + t("bridgeOffTail"))])
1737
1834
  ]) : h("div", { className: "dnm-row" }, [
1738
- h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, t("quickApprovalNote"))])
1835
+ h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, cfg.quickActions ? t("quickApprovalNote") : t("testNeedsQuick"))])
1836
+ ]),
1837
+ h("div", { className: "dnm-row" }, [
1838
+ h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-feedback" }, feedback)])
1739
1839
  ]),
1740
1840
  h("div", { className: "dnm-row" }, [
1741
1841
  h("div", { className: "dnm-rowText" }, [
@@ -1753,8 +1853,7 @@
1753
1853
  !permOK ? h("div", { className: "dnm-row" }, [
1754
1854
  h("div", { className: "dnm-rowText" }, [h("div", { className: "dnm-rowHint" }, t("permHint"))])
1755
1855
  ]) : null
1756
- ]),
1757
- h("p", { className: "dnm-feedback" }, feedback)
1856
+ ])
1758
1857
  ]);
1759
1858
  }
1760
1859
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "DSH 桌面消息提醒插件:模型需要你操作(审批/方案确认/提问的可操作提醒)或回复在后台完成时,系统通知+提示音+标签页标题提醒你;权限审批的通知上可直接点「同意/拒绝」完成裁决;可在设置页开关并切换通知语言。Desktop notification & message alerts for DeepSeek Harness web — toasts, sounds and a tab-title marker when the agent needs your input (approval / plan review / question) or a reply finishes in the background, Approve/Reject buttons on approval toasts to decide right from the notification; toggleable from Settings with zh/en notification language.",
5
5
  "license": "MIT",
6
6
  "type": "module",