dsh-plugin-notify-sounds 1.9.2 → 1.9.3

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/README.md CHANGED
@@ -11,12 +11,12 @@ English | [中文](README.zh.md)
11
11
 
12
12
  **A soft chime when the agent is waiting for you — or when it is done.**
13
13
 
14
- Walk away from the window without missing anything: an OS notification in the background (**click it to jump back**), and a "Waiting for you" / "Finished" mark on the taskbar title. Once you have seen it, the plugin goes quiet — it will not nag you like an alarm clock.
14
+ Walk away from the window without missing anything: an OS notification whether the window is minimised, covered, or merely unfocused (**click it to jump back**), and a "Waiting for you" / "Finished" mark on the taskbar title. Once you have seen it, the plugin goes quiet — it will not nag you like an alarm clock.
15
15
 
16
16
  **Install**: DSH → **Plugins** in the left sidebar → Add plugin, paste the line below (keep the version — a release younger than 24 hours is resolved back to the previous one by pnpm's `minimumReleaseAge`) and press **Install**:
17
17
 
18
18
  ```
19
- dsh-plugin-notify-sounds@1.9.2
19
+ dsh-plugin-notify-sounds@1.9.3
20
20
  ```
21
21
 
22
22
  ---
@@ -31,7 +31,7 @@ Every visual detail of the settings section comes from DSH's own design system:
31
31
  |---|---|
32
32
  | **Your decision is needed** | A chime when the agent raises an approval, a plan review, or a question — separate sounds for approvals and questions |
33
33
  | **Task finished** | A chime when a session goes from running to idle |
34
- | **Aware even when you are away** | An OS notification in the background (body is the approval reason or the question itself; **click to return to the window**) plus a taskbar title mark |
34
+ | **Aware even when you are away** | An OS notification whenever the window is minimised, covered, or merely unfocused (body is the approval reason or the question itself; **click to return to the window**) plus a taskbar title mark |
35
35
  | **Missed it? It reminds again** | Unanswered decisions re-ping every 1/5/15/30 minutes — and **stop for good once you have seen them**, even if you switch away again |
36
36
  | **Only what you care about** | Sub-agents (background tasks) are completely silent by default — only your own conversation is announced |
37
37
  | **Quiet when it should be** | Quiet hours (e.g. 22:00–08:00, wrapping past midnight), notify-only-when-hidden, and a master switch |
@@ -66,7 +66,7 @@ Everything applies immediately — no restart needed.
66
66
  2. Paste the package name **with a version** (this plugin is published on [npm](https://www.npmjs.com/package/dsh-plugin-notify-sounds)):
67
67
 
68
68
  ```
69
- dsh-plugin-notify-sounds@1.9.2
69
+ dsh-plugin-notify-sounds@1.9.3
70
70
  ```
71
71
 
72
72
  Do not drop the version: pnpm's `minimumReleaseAge` policy keeps a release younger than 24 hours out of automatic resolution, so the bare name resolves to the previous one. The current version is on [npm](https://www.npmjs.com/package/dsh-plugin-notify-sounds?activeTab=versions) and in [Releases](https://github.com/BillCx330/dsh-plugin-notify-sounds/releases).
@@ -100,7 +100,7 @@ dsh plugin --profile <your profile> add github:BillCx330/dsh-plugin-notify-sound
100
100
  **Release archive** — download the `.tgz` from [Releases](https://github.com/BillCx330/dsh-plugin-notify-sounds/releases) and install it by its **path on disk**:
101
101
 
102
102
  ```sh
103
- dsh plugin --profile <your profile> add ./dsh-plugin-notify-sounds-1.9.2.tgz
103
+ dsh plugin --profile <your profile> add ./dsh-plugin-notify-sounds-1.9.3.tgz
104
104
  ```
105
105
 
106
106
  (`dsh plugin add` hands its argument straight to the package manager, so it takes a local file path — a download URL will not work.)
@@ -122,7 +122,8 @@ The command line writes `dsh.profile.bundles` and installs the dependency for yo
122
122
  - **Upgrades are installed by the package manager, not by the plugin**: 1.8.0 removed the self-installer (writing files in place never updated the profile's lockfile, so the next plugin install silently restored the old version). The update row still checks npm and reports a newer version, then points at the route that installs — the **Plugins** panel in the sidebar (uninstall, then add again), the marketplace, or `dsh plugin add` on a web profile.
123
123
  - **Versions up to 1.7.0** reach 1.8.0 through their own updater (1.8.0 keeps the same seven filenames), but that arrival is not registered with pnpm: reinstall once through the package manager as above and every later upgrade stays in place. Versions up to 1.6.9 cannot install a release that adds a file — their fixed whitelist refuses it safely; the same one-time reinstall fixes that too.
124
124
  - **Multiple windows** elect one page to speak (since 1.7.0): sounds, system notifications and repeat reminders therefore fire once for the whole GUI, not once per window. Title marks stay per-window, because each window has its own title. If the speaking page closes or crashes, another takes over within seconds.
125
- - **Click-to-return** is fully implemented and tested on Windows; macOS / Linux fall back to the system deep link and are unverified.
125
+ - **Click-to-return** is fully implemented and tested on Windows; macOS / Linux fall back to the system deep link and are unverified.
126
+ - **Third-party desktop wrappers** (e.g. DSH NEXT) register no `dsh://` protocol and expose no focus API, so clicking a notification cannot return you to the window there — the gap is on the host side, not something a plugin can close.
126
127
  - **The window is found by its title** — deliberately without inspecting other processes (that trips security software). If DSH ever changes its title format, click-to-return silently stops working; sounds and notifications are unaffected.
127
128
  - **The nav glyph** depends on the settings panel's DOM structure and silently falls back to the official gear after a DSH redesign — cosmetic only.
128
129
  - **Notification permission** is granted on the Desktop; under restrictive policies the row shows how to allow it manually, everything else keeps working.
@@ -145,6 +146,7 @@ For anyone hacking on the code (exact details live in the source comments):
145
146
 
146
147
  ## 📜 Changelog
147
148
 
149
+ - **v1.9.3** (2026-11-02) **A visible-but-unfocused window now raises a system notification.** Notifications used to fire only while the page was hidden, but a window sitting in plain sight behind another app — or on a second screen while you type elsewhere — still reports `visible`, so that whole case was silent ([#5](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/5), thanks @myzc1000). The gate is now "hidden **or** unfocused". Minimised, tray-hidden and fully covered windows behave as before (Chromium's occlusion tracking marks those hidden).Two small fixes came along: a refused write while adding a clip left its uploaded file behind (it is deleted now), and a multi-line conversation title broke the toast title across lines (it is flattened to one).
148
150
  - **v1.9.2** (2026-10-30) **System notifications are titled with the conversation name.** They used to read "Task finished / The agent has finished the current task", so with several conversations open you had to click through to learn which one. The title now carries the conversation's name (from the `title` already on DSH's session rows, falling back to the workspace folder — and treating DSH's own "Working directory: …" fallback as unnamed, so only the folder segment shows), and the body is unchanged; a decision toast prefixes its body with the event so nothing is lost. Titles are trimmed at 48 characters, an untitled session keeps the old wording, and a raw session id is never shown. These fields exist since DSH 0.2.0 — not a new API — and when they are missing the behaviour is exactly what it was. **One cross-platform fix came along**: on Windows two windows uploading the same clip at once made the second report "not audio" (415) — POSIX lets `rename` overwrite, Windows refuses — and that refusal is now treated as success. ([#4](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/4), thanks @AdySnowflake)
149
151
  - **v1.9.1** (2026-10-29) **Fixed "too quiet": the nine presets are now levelled by loudness, not by peak.** A user reported that even 100% was quiet. Two things were stacked: the presets had never been matched to each other (`coin` peaks at 0.105, `ding` at 0.315 — 9.5 dB apart), and all of them sat about 12 dB below a decoded clip (a preset played at -16 dBFS at 100%, an imported file at -4). The same slider number meant two loudnesses. The first cut levelled them by peak, which made the coin sound louder still: a square carries 3 dB more energy than a sine at the same peak (RMS/peak 1.0 vs 0.707). Levels now use a crest-corrected peak — sines and squares land on the same energy, and triangles are capped at the ceiling rather than pushed past it. The same number is about 12 dB louder, and the nine sounds agree with each other. **The saved volume is re-scaled once on upgrade**: at the new scale the same number is ~10 dB louder, so the stored value is halved (60 → 30, 100 → 50) — about 3.5 dB above where you were. Turn it up for the new headroom. ([#3](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/3), thanks @sink-er)
150
152
  - **v1.9.0** (2026-10-27) **Your own sounds become a small library: three at most, each renameable and assignable.** All three alerts used to share a single "Custom" upload, so giving "finished" its own sound while "approval needed" kept a preset was impossible. The "Custom sound" row now holds everything: press **Add audio**, and the file is listed underneath (three at most) where it can be renamed, previewed or removed; the three alert pickers list your sounds by name, so each alert plays the one you point it at. **The bytes moved out of the config**: a 512KB clip used to become 699,052 characters of base64 inside `cordis.patch.yml` — 277× the whole file, rewritten on every settings change — so audio is now stored under `profiles/<profile>/notify-sounds/sounds/<hash>.<ext>` with only the name in the config. Three gaps closed as well: **3MB and 15 seconds per sound** (a long clip turns an alert into background music and can cost tens of MB once decoded); **silent and near-zero-length files are refused at upload**; and **anything unreadable falls back to that alert's default sound** — a damaged payload used to silence all three with nothing in the UI to explain it. The list marks a file that has gone missing, and one no alert uses yet. Existing audio from an older release is moved into the library on first run; if that fails it stays where it was and keeps playing.
package/README.zh.md CHANGED
@@ -11,12 +11,12 @@
11
11
 
12
12
  **智能体在等你、或干完活时,轻轻响一声。**
13
13
 
14
- 人不在窗口前也不怕:后台弹系统通知(**点击即回到窗口**),任务栏标题亮出「待处理 / 已完成」;你回来看过一眼之后,它就闭嘴——不会像闹钟一样反复催你。
14
+ 人不在窗口前也不怕:最小化、被遮挡、或只是切到别的窗口,都会弹系统通知(**点击即回到窗口**),任务栏标题亮出「待处理 / 已完成」;你回来看过一眼之后,它就闭嘴——不会像闹钟一样反复催你。
15
15
 
16
16
  **安装**:DSH → 左侧栏「插件」→ 添加插件,粘贴下面这行(**带上版本号**,否则发布不满 24 小时的版本会被 pnpm 的 `minimumReleaseAge` 解析回上一版),点「安装」:
17
17
 
18
18
  ```
19
- dsh-plugin-notify-sounds@1.9.2
19
+ dsh-plugin-notify-sounds@1.9.3
20
20
  ```
21
21
 
22
22
  ---
@@ -31,7 +31,7 @@ dsh-plugin-notify-sounds@1.9.2
31
31
  |---|---|
32
32
  | **需要你决策** | 智能体发起审批、计划确认、提问时提示音;审批与提问可分别设音效 |
33
33
  | **任务完成** | 会话由运行转为空闲时提示音 |
34
- | **人不在窗口前也能感知** | 后台弹系统通知(正文就是审批理由或提问原文,点击回到窗口);任务栏标题显示「待处理 / 已完成」 |
34
+ | **人不在窗口前也能感知** | 最小化、被遮挡或只是没在用它时都弹系统通知(正文就是审批理由或提问原文,点击回到窗口);任务栏标题显示「待处理 / 已完成」 |
35
35
  | **没看到会再提醒** | 未处理决策按 1/5/15/30 分钟重复提醒 |
36
36
  | **只提醒你关心的** | 子智能体(后台任务)默认完全静默,只提醒主对话,可开关 |
37
37
  | **该安静时安静** | 静音时段(如 22:00–08:00,可跨午夜)、仅页面不可见时提示、总开关一键全静音 |
@@ -66,7 +66,7 @@ DSH 的状态提示只有视觉——状态点、侧栏标记。你一切到别
66
66
  2. 在输入框粘贴包名**和版本号**(本插件已发布在 [npm](https://www.npmjs.com/package/dsh-plugin-notify-sounds)):
67
67
 
68
68
  ```
69
- dsh-plugin-notify-sounds@1.9.2
69
+ dsh-plugin-notify-sounds@1.9.3
70
70
  ```
71
71
 
72
72
  版本号不要省:pnpm 的 `minimumReleaseAge` 策略会把刚发布不到 24 小时的版本挡在外面,只写包名会解析回上一个版本。想看最新版本号可到 [npm](https://www.npmjs.com/package/dsh-plugin-notify-sounds?activeTab=versions) 或 [Releases](https://github.com/BillCx330/dsh-plugin-notify-sounds/releases) 查。
@@ -100,7 +100,7 @@ dsh plugin --profile <你的 profile> add github:BillCx330/dsh-plugin-notify-sou
100
100
  **Release 压缩包** —— 从 [Releases](https://github.com/BillCx330/dsh-plugin-notify-sounds/releases) 下载 `.tgz`,用它**在磁盘上的路径**安装:
101
101
 
102
102
  ```sh
103
- dsh plugin --profile <你的 profile> add ./dsh-plugin-notify-sounds-1.9.2.tgz
103
+ dsh plugin --profile <你的 profile> add ./dsh-plugin-notify-sounds-1.9.3.tgz
104
104
  ```
105
105
 
106
106
  (`dsh plugin add` 把参数原样交给包管理器,所以要给本地文件路径,不能填下载地址。)
@@ -122,7 +122,8 @@ dsh plugin --profile <你的 profile> add ./dsh-plugin-notify-sounds
122
122
  - **升级由包管理器安装,不再由插件自己完成**:1.8.0 移除了自安装器(原地写文件从不会更新 profile 的 lockfile,于是下一次插件安装会静默把旧版本装回来)。更新行仍会检查 npm、报告新版,然后指向真正负责安装的入口——**左侧栏「插件」**(卸载后再装)、市场,或 web profile 上的 `dsh plugin add`。
123
123
  - **1.7.0 及更早版本**可以通过它们自带的自更新到达 1.8.0(1.8.0 保持同样的七个文件名),但这次到达不会被 pnpm 登记:按上面的方式用包管理器重装一次,此后所有升级都会留下。1.6.9 及更早无法安装「新增了文件」的版本——固定白名单会安全拒绝;同样的一次性重装即可解决。
124
124
  - **多窗口**:提示音、系统通知和重复提醒由同一个 GUI 的所有页面选出一个「发声页」来发(1.7.0 起),所以同一个事件只响一次;窗口标题标记仍是每个窗口各自一份,因为它们各自有标题。发声页关闭或崩溃后几秒内由其他页面接管。
125
- - **点击通知回到窗口**:Windows 完整实现并实测;macOS / Linux 走系统深链,未验证。
125
+ - **点击通知回到窗口**:Windows 完整实现并实测;macOS / Linux 走系统深链,未验证。
126
+ - **第三方封装的桌面客户端**(如 DSH NEXT):唤窗依赖官方壳的 Win32 能力与 `dsh://` 协议注册,封装版没有这两样,点击通知不会回到窗口——那是宿主侧缺的,插件无法补。
126
127
  - **唤窗按窗口标题定位主窗口**:刻意不读取其他进程信息(那会触发杀软主动防御)。若将来 DSH 改变标题格式,唤窗会静默失效——提示音与通知本身不受影响。
127
128
  - **导航小图标**:依赖设置面板的 DOM 结构,若DSH 改版后自动回落为官方齿轮,仅影响外观。
128
129
  - **通知权限**:桌面端放行;受限环境(某些浏览器策略)下被拒时,行内会引导手动允许,其余功能不受影响。
@@ -144,6 +145,7 @@ dsh plugin --profile <你的 profile> add ./dsh-plugin-notify-sounds
144
145
 
145
146
  ## 📜 更新记录
146
147
 
148
+ - **v1.9.3**(2026-11-02)**窗口可见但没焦点时也会弹系统通知。** 此前只在页面不可见时发通知;而窗口被别的程序盖住、或你切到别的窗口打字、DSH 仍完整显示在屏幕上时,页面报告的仍是 `visible`,这类「看得见但没在用」的情况完全收不到通知([#5](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/5),感谢 @myzc1000)。现在条件改为「不可见 **或** 没有焦点」。最小化、托盘、被完全遮挡(Chromium 的遮挡检测会把窗口标记为 hidden)行为不变。顺带修了两处小问题:上传音频时若写入被拒,已经存好的文件会残留(现在会一并清掉);对话标题里含换行时会把通知标题撑成两行(现在折叠成一行)。
147
149
  - **v1.9.2**(2026-10-30)**系统通知的标题改成对话名。** 此前通知固定写「任务完成 / 智能体已完成当前任务」,同时开着几个对话时,得点进去才知道是哪一个。现在标题用该对话的名字(读的是 DSH 会话列表里已有的 `title`;没有标题、或标题只是「Working directory: 路径」这类兜底串时,只显示目录名),正文不变;决策通知的正文会带上「需要你的决策:」,把原来写在标题里的事件补回来。标题超过 48 字会截断,未命名且无目录时保持原来的文案——**不会显示裸会话 id**。字段从 DSH 0.2.0 起就有,不是新 API,取不到时行为与之前完全一致。**顺带修了一个跨平台问题**:Windows 下两个窗口同时上传同一段音频时,后到的那个会被误报「不是音频」(415)——POSIX 上 `rename` 能覆盖、Windows 上会抛错;现在这种情况按成功处理。([#4](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/4),感谢 @AdySnowflake)
148
150
  - **v1.9.1**(2026-10-29)**修好「声音太小」:九个内置音效按实际响度对齐,而不是按峰值。** 有用户反馈 100% 也偏小。查下来是两件事:九个预设从未互相对齐(`coin` 的叠加峰值 0.105、`ding` 0.315,差 9.5 dB);预设整体又比自定义音频轻约 12 dB(预设 100% 时约 -16 dBFS,导入的音频约 -4 dBFS)——同一个音量数字,两种响度。第一版修复按**峰值**对齐,结果金币反而更响:方波在同等峰值下能量比正弦高 3 dB(RMS/峰值 1.0 对 0.707)。现在按**波峰因数折算后的峰值**对齐——正弦和方波落在同一能量上,三角波因为峰值已经触顶不再加推。同样的音量数字比以前响约 12 dB,九个音效之间也一致了。**升级时音量会自动折算**:新刻度下同一个数字比以前响约 10 dB,所以首次启动会把已存的音量减半(60 → 30、100 → 50),实际只比原来高约 3.5 dB;想更大声再往上调即可。([#3](https://github.com/BillCx330/dsh-plugin-notify-sounds/issues/3),感谢 @sink-er)
149
151
  - **v1.9.0**(2026-10-27)**自定义音频变成一个小库:最多 3 个,可以改名、指派。** 此前三个提醒共用同一个「自定义」上传——想给「完成」配一个、同时让「决策」用内置音,做不到。现在「自定义音效」只有一行:点「添加音频」选文件,加进去的会列在它下面(最多 3 个),每个都能改名、试听、移除;三个提醒的音效下拉里会直接列出这些音频的名字,挑哪个就响哪个。**存放位置也换了**:音频不再以 base64 塞进 `cordis.patch.yml`(一个 512KB 的音频就是 699,052 个字符,是整份配置的 277 倍,而且每次改设置都要整份重新广播),改为存到 `profiles/<profile>/notify-sounds/sounds/<哈希>.<后缀>`,配置里只留名字。顺带补了三件一直缺的:**上限 3MB、每个 15 秒**(太长的音频会变成背景音,解码后还可能占几十 MB 内存);**没有声音、或短到听不见的文件,在上传时就被拒绝**;**读不到音频时改用该项的默认音**——此前配置里的音频一旦损坏,三个提醒会全部静默,界面上还什么都不说。列表里也会标出「文件找不到了」和「还没有提醒使用它」。老用户升级时,原来的音频会自动搬进这个库;搬不进去就原样留着,照旧能响。
package/lib/client.js CHANGED
@@ -206,7 +206,7 @@ window.__ModuleLoader__.load({
206
206
  "custom.unreadable": "读不到这个文件:它可能已被移动,或没有读取权限,请重新选择",
207
207
  "custom.missing": "音频文件找不到了(可能被删除或被杀毒软件隔离),会改用默认音。请重新添加",
208
208
  "systemNotify.title": "系统通知",
209
- "systemNotify.description": "页面处于后台时弹出系统通知,标题用当前对话的名字,点击可回到窗口",
209
+ "systemNotify.description": "页面处于后台、或窗口失去焦点时弹出系统通知,标题用当前对话的名字,点击可回到窗口",
210
210
  "systemNotify.granted": "已授权系统通知",
211
211
  "systemNotify.denied": "通知权限被拒绝,请在浏览器或系统设置中允许后重试",
212
212
  "systemNotify.unsupported": "当前环境不支持系统通知",
@@ -299,7 +299,7 @@ window.__ModuleLoader__.load({
299
299
  "custom.unreadable": "Could not read this file: it may have been moved, or permission was revoked — choose it again",
300
300
  "custom.missing": "The audio file is missing (deleted, or quarantined by antivirus), so the default sound plays instead. Add it again",
301
301
  "systemNotify.title": "System notifications",
302
- "systemNotify.description": "Show an OS notification while the page is hidden — titled with the conversation name; click it to focus the window",
302
+ "systemNotify.description": "Show an OS notification while the page is hidden or the window has lost focus — titled with the conversation name; click it to focus the window",
303
303
  "systemNotify.granted": "Notification permission granted",
304
304
  "systemNotify.denied": "Denied — allow notifications in the browser or system settings and retry",
305
305
  "systemNotify.unsupported": "System notifications are unavailable in this environment",
@@ -882,6 +882,25 @@ window.__ModuleLoader__.load({
882
882
  return typeof document === "undefined" || document.visibilityState === "visible";
883
883
  }
884
884
 
885
+ /**
886
+ * Whether the user is away from this window right now.
887
+ *
888
+ * `visibilityState` alone covers minimized, tray-hidden and — through
889
+ * Chromium's occlusion tracking — a window completely covered by another
890
+ * app. What it does NOT cover is a window sitting in plain sight while
891
+ * the user types in a different app: that still reports "visible", so a
892
+ * notification gated on visibility alone never fires. That is exactly
893
+ * the case reported on issue #5 ("only fires once DSH is in the
894
+ * background"), and the reason this predicate looks at focus too.
895
+ *
896
+ * @returns whether the window is hidden or merely unfocused.
897
+ */
898
+ function pageIsAway() {
899
+ if (typeof document === "undefined") return false;
900
+ if (document.visibilityState !== "visible") return true;
901
+ return typeof document.hasFocus === "function" && document.hasFocus() === false;
902
+ }
903
+
885
904
  /**
886
905
  * Text the title alert writes in front of the window title, per state.
887
906
  *
@@ -1385,7 +1404,17 @@ window.__ModuleLoader__.load({
1385
1404
  const fallback = shown !== "" && shown !== sessionId && shown !== named ? shown : folder;
1386
1405
  const name = restatesCwd ? fallback : named;
1387
1406
  if (name === "") return "";
1388
- return name.length > NOTIFICATION_TITLE_MAX ? `${name.slice(0, NOTIFICATION_TITLE_MAX - 1)}…` : name;
1407
+ // A title taken from the first message can be several lines long (a
1408
+ // pasted prompt) or carry tabs; a toast title is one line, so the
1409
+ // whitespace is collapsed rather than shown as ragged breaks.
1410
+ const flat = name.replace(/\s+/gu, " ").trim();
1411
+ if (flat.length <= NOTIFICATION_TITLE_MAX) return flat;
1412
+ // Do not cut between a surrogate pair: half an emoji renders as a
1413
+ // replacement character in the toast.
1414
+ let cut = NOTIFICATION_TITLE_MAX - 1;
1415
+ const code = flat.charCodeAt(cut - 1);
1416
+ if (code >= 0xd800 && code <= 0xdbff) cut -= 1;
1417
+ return `${flat.slice(0, cut)}…`;
1389
1418
  }
1390
1419
 
1391
1420
  /**
@@ -2702,13 +2731,16 @@ window.__ModuleLoader__.load({
2702
2731
  const slot = CUSTOM_SLOTS.find((candidate) => soundFileOf(value, candidate) === "");
2703
2732
  if (slot === undefined) throw new Error("sound library is full");
2704
2733
  const file = await storeSound(bytes);
2705
- await persist(`${slot}File`, file);
2706
2734
  try {
2735
+ await persist(`${slot}File`, file);
2707
2736
  await persist(`${slot}Name`, name);
2708
2737
  } catch (error) {
2709
2738
  // One sound is one record: a half-written slot would show a
2710
- // recording with no name and no way to remove it.
2739
+ // recording with no name and no way to remove it. The upload
2740
+ // already landed, so it is deleted too — otherwise every
2741
+ // refused write leaves a file nothing points at.
2711
2742
  await form.set(`${slot}File`, "").catch(() => {});
2743
+ void forgetSound(file, slot);
2712
2744
  throw error;
2713
2745
  }
2714
2746
  return slot;
@@ -3031,7 +3063,7 @@ window.__ModuleLoader__.load({
3031
3063
  // One entry point for "which sound": the trigger's own file,
3032
3064
  // the legacy inline payload, or the preset its picker names.
3033
3065
  playAlert(kind, value, volume);
3034
- if (value?.systemNotify === true && typeof document !== "undefined" && document.visibilityState !== "visible") {
3066
+ if (value?.systemNotify === true && pageIsAway()) {
3035
3067
  // The name the user knows this conversation by, when the
3036
3068
  // runtime exposes one. Naming it only helps if the toast
3037
3069
  // still says what happened, so for a decision the event
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-notify-sounds",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "description": "DeepSeek Harness 原生风格提示音插件:需要决策或任务完成时播放提示音,点击系统通知可回到窗口;支持重复提醒、静音时段,以及目标模式下只在最终轮提醒。 · Native-style notification sounds for DeepSeek Harness: a chime when the agent needs your decision or finishes a task, click a system notification to jump back, plus repeat reminders, quiet hours, and goal-mode alerts that wait for the final round.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",