dsh-notify 0.1.5 → 0.1.7

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,27 @@
1
+ # Changelog
2
+
3
+ ## [0.1.7] - 2026-09-10
4
+
5
+ - 安全:spawn 子进程环境变量清洗——剔除变量名含 KEY / SECRET / TOKEN / PASSWORD 的环境变量,防止宿主凭据经子进程泄漏;
6
+ - 修复:卸载时托盘进程 kill 后等待其退出再结束,无残留进程;
7
+ - 文档:功能节补充安全加固说明;相关插件表格修复断行。
8
+
9
+ ## [0.1.6] - 2026-08-29
10
+
11
+ - 文档:删除与 FAQ 重复的「排障/触发时机」节;新增兼容性行(Windows 10/11 · Node ≥ 22.5 · DSH 0.1.1-rc.2 实测);
12
+ - 元数据:npm description 重写(唯一托盘 + workspace·session 标注),keywords 10 → 15;
13
+ - 新增英文版 README.en.md 与双语切换行;CHANGELOG.md 纳入 npm files。
14
+ ## [0.1.5] - 2026-08-25
15
+
16
+ - 相关插件段新增 dsh-plugin-windows-guard(Windows 环境防坑守则 skill 插件,互相引流)。
17
+
18
+
19
+ ## [0.1.4] - 2026-08-21
20
+
21
+ - README 新增「相关插件」互相引用段(列出同系列已发布插件,npm / GitHub 链接 + 一句话说明),互相引流。
22
+
23
+ ## [0.1.3] - 2026-08-19
24
+
25
+ - Windows 原生通知 + 系统托盘常驻图标(dsh 生态唯一带托盘)。
26
+ - 触发时机:完成 / 停止 / 出错 / 等你选择 / 会话关闭。
27
+ - 发布 npm、GitHub topics、dsh-market 收录(issue #37/#41)。
package/README.en.md ADDED
@@ -0,0 +1,108 @@
1
+ <p align="center">
2
+ <img src="docs/banner.svg" alt="dsh-notify banner" width="100%">
3
+ </p>
4
+
5
+ # dsh-notify
6
+
7
+ ![npm version](https://img.shields.io/npm/v/dsh-notify)
8
+ ![npm downloads](https://img.shields.io/npm/dm/dsh-notify)
9
+ ![License](https://img.shields.io/github/license/Pasumao/dsh-plugin-notify)
10
+ ![Stars](https://img.shields.io/github/stars/Pasumao/dsh-plugin-notify?style=social)
11
+ ![AI Assisted](https://img.shields.io/badge/AI-Assisted-8A2BE2)
12
+
13
+ [中文](./README.md) | **English**
14
+
15
+ **The only dsh plugin with a system tray icon**: when the agent stops running (finished / aborted / error / waiting for your choice / session closed), a native Windows Toast pops up automatically with the body labeled "workspace · session", and a whale icon stays resident in the taskbar tray — switch away from the window during long tasks and one glance at the tray tells you whether it's done.
16
+
17
+ Zero runtime dependencies, zero build; one `dsh plugin add` command and it's ready to use.
18
+
19
+ ## Screenshot
20
+
21
+ ![dsh-notify in action: a native Windows Toast notification](docs/notify-toast.png)
22
+
23
+ > Real Windows Toast capture: the body is labeled "workspace · session", and the whale icon stays
24
+ > resident in the taskbar tray.
25
+
26
+ ## Features
27
+
28
+ - **Native Windows Toast notifications**: when the agent stops running (finished / aborted / error / output limit reached / waiting for your choice / session closed), a system notification pops up automatically with the body labeled "workspace · session" — tell at a glance which session finished;
29
+ - **Permanent system tray icon**: a whale icon stays resident in the taskbar tray; the right-click menu opens the dsh web page / quits the background process (competitors explicitly ship no tray — this plugin is the only one in the dsh ecosystem);
30
+ - **Anti-spam**: `rootsOnly` defaults to notifying only root sessions, so subagents won't flood you; `cooldownMs` sets the minimum interval between two notifications of the same type in the same session;
31
+ - **Security hardening**: the notification child process runs with a sanitized environment (variables whose names contain KEY / SECRET / TOKEN / PASSWORD are stripped, preventing credential leaks); on uninstall the tray process is killed and awaited before exit — no leftover processes;
32
+ - **Zero runtime dependencies, zero build**: one `dsh plugin add` command and it's ready to use.
33
+
34
+ ## Use cases
35
+
36
+ - **Switch windows during long tasks**: while a long LLM generation / batch job runs, switch to another app; when it ends, a tray notification pops up and one glance tells you the result;
37
+ - **Unattended batch jobs**: run multiple rounds overnight and get a Toast when everything is done — no need to keep watching the page;
38
+ - **Multiple sessions at once**: run several workspace sessions simultaneously; each notification body carries the "workspace · session" label, so nothing gets mixed up;
39
+ - **Watching subagents**: keep the default `rootsOnly: true` when you only care about root-session results, and turn it off when you need to watch subagents.
40
+
41
+ ## Installation
42
+
43
+ ```powershell
44
+ dsh plugin --profile web add dsh-notify
45
+ ```
46
+
47
+ Install from GitHub: `dsh plugin --profile web add github:Pasumao/dsh-plugin-notify`
48
+
49
+ Install from source (local development / debugging):
50
+
51
+ ```bash
52
+ git clone https://github.com/Pasumao/dsh-plugin-notify.git
53
+ cd dsh-plugin-notify
54
+ npm install
55
+ # Mount into the profile as a link: dependency (package name: dsh-notify)
56
+ ```
57
+
58
+ After installing, restart `dsh web`; the plugin is active once the whale icon appears in the taskbar.
59
+
60
+ > Compatibility: Windows 10/11 · Node ≥ 22.5 · tested on DSH `0.1.1-rc.2`.
61
+
62
+ ## Configuration
63
+
64
+ Override the config in the profile's `cordis.patch.yml` under the id `dsh-plugin-notify`. Common options:
65
+
66
+ | key | Default | Description |
67
+ |---|---|---|
68
+ | `cooldownMs` | `10000` | Minimum interval in milliseconds between two notifications of the same type in the same session |
69
+ | `rootsOnly` | `true` | Notify only root sessions; subagents won't flood you |
70
+ | `tray` | `true` | Enable/disable the tray icon |
71
+ | `titlePrefix` | `'dsh'` | Notification title prefix |
72
+
73
+ ## Testing
74
+
75
+ ```powershell
76
+ node scripts/test-harness.mjs # Fires three real Toast notifications as a self-test
77
+ ```
78
+
79
+ ## FAQ
80
+
81
+ - **Not receiving notifications?** Check that Windows "Settings → System → Notifications" allows PowerShell to show notifications, and that you are not in Focus Assist / Do Not Disturb mode;
82
+ - **Only root-session notifications?** Keep the default `rootsOnly: true`; change it to `false` when you need to watch subagents;
83
+ - **Notifications too frequent?** Just increase `cooldownMs` (default 10000ms);
84
+ - **Tray icon missing?** Restart dsh web; if it is still gone, check whether the `tray: true` config key was overridden.
85
+
86
+ ## Related plugins
87
+
88
+ This plugin is part of the **Pasumao dsh plugin ecosystem**; the published plugins in the series work well together:
89
+
90
+ | Plugin (npm) | GitHub | Description |
91
+ |---|---|---|
92
+ | [dsh-plugin-choice-refresh](https://www.npmjs.com/package/dsh-plugin-choice-refresh) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-choice-refresh) | Choice enhancements: regenerate options / more options |
93
+ | [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-dev-kb) | Plugin development knowledge base (full mirror of official docs + skills) |
94
+ | [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-image-tools) | Image choice cards + inline images in replies + image pickup for blind models |
95
+ | [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-table-zoom) | Floating window for long chat tables + one-click Markdown copy |
96
+ | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows environment guard: rule skills + mojibake detection / dangerous-write interception / encoding diagnosis & repair |
97
+ | [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code-style file explorer + editable preview |
98
+ | [dsh-plugin-context-trim](https://www.npmjs.com/package/dsh-plugin-context-trim) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-context-trim) | Per-session injection gate: trim skills / tools / prompt sections per session |
99
+
100
+ > For the other plugins in the series, see [Pasumao · dsh plugins](https://github.com/Pasumao); if you find them useful, a ⭐ on GitHub is appreciated.
101
+
102
+ ## AI-generated disclosure
103
+
104
+ The code and documentation were generated with AI assistance (DeepSeek Harness), all human-reviewed and verified on a live machine (`scripts/test-harness.mjs` fires real Toast notifications as a self-test).
105
+
106
+ ## License
107
+
108
+ [MIT](./LICENSE)
package/README.md CHANGED
@@ -1,18 +1,33 @@
1
+ <p align="center">
2
+ <img src="docs/banner.svg" alt="dsh-notify banner" width="100%">
3
+ </p>
4
+
1
5
  # dsh-notify
2
6
 
3
7
  ![npm version](https://img.shields.io/npm/v/dsh-notify)
8
+ ![npm downloads](https://img.shields.io/npm/dm/dsh-notify)
4
9
  ![License](https://img.shields.io/github/license/Pasumao/dsh-plugin-notify)
10
+ ![Stars](https://img.shields.io/github/stars/Pasumao/dsh-plugin-notify?style=social)
5
11
  ![AI Assisted](https://img.shields.io/badge/AI-Assisted-8A2BE2)
6
12
 
13
+ [**中文**](./README.md) | [English](./README.en.md)
14
+
7
15
  **唯一带系统托盘的 Windows 原生通知插件**:agent 不再运行时(完成 / 停止 / 出错 / 等你选择 / 会话关闭)自动弹原生 Toast,正文标注「工作区 · 会话」,任务栏托盘常驻鲸鱼图标——跑长任务时切走窗口,瞄一眼托盘就知道跑完了没。
8
16
 
9
17
  零运行时依赖、零构建,`dsh plugin add` 一条命令装完即用。
10
18
 
19
+ ## 效果图
20
+
21
+ ![dsh-notify 实机截图:Windows 原生 Toast 通知](docs/notify-toast.png)
22
+
23
+ > 真实 Windows Toast 实拍:正文标注「工作区 · 会话」,任务栏托盘鲸鱼图标常驻。
24
+
11
25
  ## 功能
12
26
 
13
27
  - **Windows 原生 Toast 通知**:agent 不再运行时(完成 / 停止 / 出错 / 达到输出上限 / 等你选择 / 会话关闭)自动弹系统通知,正文标注「工作区 · 会话」,一眼知道哪个会话跑完了;
14
28
  - **系统托盘常驻图标**:任务栏托盘鲸鱼图标常驻,右键菜单可唤起 dsh web 页面 / 退出后台(竞品均明确不做托盘,本插件是 dsh 生态唯一);
15
29
  - **防刷屏**:`rootsOnly` 默认仅根会话通知,子代理不刷屏;`cooldownMs` 同会话同类型两次通知最小间隔;
30
+ - **安全加固**:弹通知的子进程环境变量经清洗(剔除含 KEY / SECRET / TOKEN / PASSWORD 的变量),托盘进程退出前不残留凭据;卸载时托盘进程 kill 后等待退出再结束,无残留进程;
16
31
  - **零运行时依赖、零构建**,`dsh plugin add` 一条命令装完即用。
17
32
 
18
33
  ## 使用场景
@@ -41,9 +56,7 @@ npm install
41
56
 
42
57
  装完重启 `dsh web`,任务栏出现鲸鱼图标即生效。
43
58
 
44
- ## 触发时机
45
-
46
- 任务完成、被中止、执行出错、达到输出上限、停下来等你选择、运行中的会话被关闭。
59
+ > 兼容性:Windows 10/11 · Node ≥ 22.5 · 实测于 DSH `0.1.1-rc.2`。
47
60
 
48
61
  ## 配置
49
62
 
@@ -70,10 +83,6 @@ node scripts/test-harness.mjs # 弹三条真实 Toast 自测
70
83
  - **通知太频繁?** 调大 `cooldownMs`(默认 10000ms)即可;
71
84
  - **托盘图标不见了?** 重启 dsh web;仍无则检查 `tray: true` 配置项是否被覆盖。
72
85
 
73
- ## 排障
74
-
75
- Toast 不出现:检查 Windows「通知与操作」是否允许 PowerShell 显示通知。
76
-
77
86
  ## 相关插件
78
87
 
79
88
  本插件属于 **Pasumao 的 dsh 插件生态**,同系列已发布插件可搭配使用:
@@ -84,9 +93,9 @@ Toast 不出现:检查 Windows「通知与操作」是否允许 PowerShell 显
84
93
  | [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-dev-kb) | 插件开发知识库(官方文档完整镜像 + 技能) |
85
94
  | [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-image-tools) | 图片选择卡 + 回复内嵌图片 + 盲模型收图 |
86
95
  | [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-table-zoom) | 聊天长表格浮窗查看 + 一键复制 Markdown |
87
- | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑守则 skill(编码/转义/路径/进程/乱码预防) |
96
+ | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑:守则技能 + 乱码检测 / 危险写拦截 / 编码诊断修复 |
88
97
  | [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code 风格文件浏览器 + 可编辑预览 |
89
-
98
+ | [dsh-plugin-context-trim](https://www.npmjs.com/package/dsh-plugin-context-trim) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-context-trim) | 会话注入门控:skill / tool / 提示词段落按会话裁剪 |
90
99
  > 本系列其余插件见 [Pasumao · dsh 插件](https://github.com/Pasumao);觉得好用欢迎到 GitHub 点 ⭐。
91
100
 
92
101
  ## AI 生成声明
@@ -0,0 +1,25 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="260" viewBox="0 0 1200 260" role="img" aria-label="dsh-notify banner">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#0a1830"/>
5
+ <stop offset="1" stop-color="#102a4c"/>
6
+ </linearGradient>
7
+ <radialGradient id="glow" cx="0.85" cy="0.1" r="0.9">
8
+ <stop offset="0" stop-color="#4ADE80" stop-opacity="0.22"/>
9
+ <stop offset="1" stop-color="#4ADE80" stop-opacity="0"/>
10
+ </radialGradient>
11
+ <linearGradient id="whale" x1="0" y1="0" x2="0" y2="1">
12
+ <stop offset="0" stop-color="#ffffff"/>
13
+ <stop offset="1" stop-color="#cfe3ff"/>
14
+ </linearGradient>
15
+ </defs>
16
+ <rect width="1200" height="260" rx="18" fill="url(#bg)"/>
17
+ <rect width="1200" height="260" rx="18" fill="url(#glow)"/>
18
+ <rect x="0" y="0" width="1200" height="6" rx="3" fill="#4ADE80" opacity="0.85"/>
19
+ <g transform="translate(64,80) scale(2.0)">
20
+ <path d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z" fill="url(#whale)"/>
21
+ </g>
22
+ <text x="212" y="86" font-family="'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="17" font-weight="600" letter-spacing="5" fill="#4ADE80">DEEPSEEK HARNESS · DSH PLUGIN</text>
23
+ <text x="212" y="152" font-family="ui-monospace,'Cascadia Code',Consolas,monospace" font-size="46" font-weight="700" fill="#f4f8ff">dsh-notify</text>
24
+ <text x="212" y="204" font-family="'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="22" fill="#9fb3c8">Windows 原生 Toast 通知 · 系统托盘常驻 · agent 停止即知</text>
25
+ </svg>
Binary file
package/lib/index.js CHANGED
@@ -88,6 +88,20 @@ function defaultTrayIconPath() {
88
88
  /** 当前托盘图标的子进程(模块级:进程内同一时刻至多一个托盘图标)。 */
89
89
  let trayChild = null
90
90
 
91
+ /**
92
+ * 清洗后的子进程环境:拷贝 process.env 并剔除疑似密钥项
93
+ * (key 名匹配 /KEY|SECRET|TOKEN|PASSWORD/i,大小写不敏感)——
94
+ * toast/tray 脚本只需要系统与通知变量,不该继承 dsh 进程里的凭据。
95
+ */
96
+ function sanitizedEnv(extra = {}) {
97
+ const env = {}
98
+ for (const [key, value] of Object.entries(process.env)) {
99
+ if (/KEY|SECRET|TOKEN|PASSWORD/i.test(key)) continue
100
+ env[key] = value
101
+ }
102
+ return { ...env, ...extra }
103
+ }
104
+
91
105
  /**
92
106
  * 弹 Toast 的 PowerShell 脚本。全部参数从环境变量读取:
93
107
  * DSH_NOTICE_TITLE / DSH_NOTICE_BODY / DSH_NOTICE_LOCATION(「工作区 · 会话」行) /
@@ -131,14 +145,13 @@ function fireToast(ctx, cfg, { title, body, location = '' }) {
131
145
  ps,
132
146
  ['-NoProfile', '-STA', '-WindowStyle', 'Hidden', '-EncodedCommand', encoded],
133
147
  {
134
- env: {
135
- ...process.env,
148
+ env: sanitizedEnv({
136
149
  DSH_NOTICE_TITLE: title,
137
150
  DSH_NOTICE_BODY: body,
138
151
  DSH_NOTICE_LOCATION: location,
139
152
  DSH_NOTICE_ICON_PATH: existsSync(iconPath) ? iconPath : '',
140
153
  DSH_NOTICE_AUMID: cfg.aumid,
141
- },
154
+ }),
142
155
  windowsHide: true,
143
156
  stdio: 'ignore',
144
157
  },
@@ -297,12 +310,11 @@ export function apply(ctx, config = {}) {
297
310
  defaultPowershellPath(),
298
311
  ['-NoProfile', '-STA', '-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', trayPath, '-ParentPid', String(process.pid), '-IconPath', defaultTrayIconPath(), '-WebUrl', cfg.webUrl],
299
312
  {
300
- env: {
301
- ...process.env,
313
+ env: sanitizedEnv({
302
314
  DSH_TRAY_TOOLTIP: 'dsh 后台运行中',
303
315
  DSH_TRAY_OPEN: '打开 dsh',
304
316
  DSH_TRAY_EXIT: '关闭进程',
305
- },
317
+ }),
306
318
  windowsHide: true,
307
319
  stdio: 'ignore',
308
320
  },
@@ -471,14 +483,27 @@ export function apply(ctx, config = {}) {
471
483
  const offSession = ctx.on('session/event', onSessionEvent)
472
484
  const offDisposed = ctx.on('agent/disposed', onDisposed)
473
485
 
474
- ctx.effect(() => () => {
486
+ ctx.effect(() => async () => {
475
487
  offStatus()
476
488
  offSession()
477
489
  offDisposed()
478
490
  states.clear()
479
491
  if (trayChild) {
480
- try { trayChild.kill() } catch {}
492
+ const child = trayChild
481
493
  trayChild = null
494
+ // kill 后等待托盘进程真正退出(上限 2s 兜底),再结束 dispose。
495
+ await new Promise((resolve) => {
496
+ let done = false
497
+ const finish = () => {
498
+ if (!done) {
499
+ done = true
500
+ resolve()
501
+ }
502
+ }
503
+ try { child.once('exit', finish) } catch { finish() }
504
+ try { child.kill() } catch { finish() }
505
+ setTimeout(finish, 2000)
506
+ })
482
507
  }
483
508
  })
484
509
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-notify",
3
- "description": "DeepSeek Harness (dsh) Windows notification plugin: a native toast when the agent stops running (finished / aborted / error / waiting for your choice / session closed), plus a system-tray icon. Pure host-side, zero dependencies, zero build.",
4
- "version": "0.1.5",
3
+ "description": "Native Windows toast + system tray for DeepSeek Harness (dsh): the only dsh plugin with a tray icon. Pops the moment the agent stops running finished / aborted / error / output limit / waiting for your choice / session closed — body labeled with workspace · session, so you can switch windows during long tasks and glance at the tray instead of watching a spinner. Pure host-side, zero dependencies, zero build.",
4
+ "version": "0.1.7",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -12,9 +12,11 @@
12
12
  "lib",
13
13
  "assets",
14
14
  "scripts",
15
+ "docs",
15
16
  "cordis.patch.yml",
16
17
  "README.md",
17
- "设计文稿.md",
18
+ "README.en.md",
19
+ "CHANGELOG.md",
18
20
  "LICENSE"
19
21
  ],
20
22
  "scripts": {
@@ -37,7 +39,12 @@
37
39
  "notify",
38
40
  "notification",
39
41
  "toast",
40
- "windows"
42
+ "windows-toast",
43
+ "tray",
44
+ "system-tray",
45
+ "windows",
46
+ "long-running",
47
+ "agent-monitor"
41
48
  ],
42
49
  "repository": {
43
50
  "type": "git",
@@ -1,119 +0,0 @@
1
- # dsh-plugin-notify 设计文稿
2
-
3
- > 需求:设计一个 dsh 插件——只要 agent「没在运行了」(完成了绘画/生成等任务、被停止、出错、或停下来让我选择),就在 Windows 上弹系统提示提醒我。
4
-
5
- ## 1. 目标与范围
6
-
7
- | 项 | 内容 |
8
- |---|---|
9
- | 目标 | 用户用 DSH Web GUI 发起长任务后离开电脑;当 agent 不再运行时弹 Windows 通知,叫用户回来看结果。通知正文标注「工作区 · 会话」位置行,用户能直接知道是哪个工作区的哪个会话完成了。 |
10
- | 形态 | 纯 **host 端** Cordis 插件(`lib/index.js`,普通 ESM),零运行时依赖、零构建步骤。 |
11
- | 通知形式 | Windows 原生 Toast(`ToastNotificationManager`),`scenario="reminder"` 常驻直到手动关闭,带 dsh 鲸鱼 logo,点击打开 dsh 页面。 |
12
- | 后台指示 | 系统托盘(任务栏右下角)常驻 dsh 鲸鱼图标(`NotifyIcon`),双击/右键打开 dsh、右键关闭 dsh 后台,dsh 退出后自动消失。 |
13
- | 非目标 | 点击通知精确跳转到某个会话(web 端无 URL 深链,且借用 AUMID 的 toast 点击激活在本机不可靠,已放弃)。 |
14
-
15
- ## 2. 关键结论(对 dsh 源码的调研)
16
-
17
- 1. **插件注册(标准 bundle)**:依赖的 `package.json` 声明 `dsh.bundle.patch`(指向包内 `cordis.patch.yml`),`dsh plugin --profile <name> add <pkg>` 安装后自动把该包加入 `dsh.profile.bundles` 层(校准逻辑见 `dsh` 的 `plugin` 子命令:按已装依赖里声明了 `dsh.bundle` 的包追加)。包内补丁用 `- insert: [{ id, name, config? }]` 注册自身,用户层 `cordis.patch.yml` 后应用、按 id 覆盖 config。本插件发布名 `dsh-notify`,补丁 `name` 与 `package.json.name` 一致。
18
- 2. **"不再运行"信号**:`@deepseek-ai/dsh-agent` 的 `agent/status`(`idle ⇄ running`),`running → idle` 即"不再运行";scope 过滤下未打标签的根 ctx 监听者能收到所有 agent 事件。
19
- 3. **结束原因**:`session/event` 的 `turn/end` 带 `TurnEndReason`:`completed / aborted / blocked / error / max-tokens / interrupted`。
20
- 4. **"等你选择"信号**:`ask_user_question` 执行时阻塞、agent 保持 `running`,用日志里未配对的 `tool/call`(`name:'ask_user_question'`)检测,`tool/result.message.callId` 配对即结束。
21
- 5. **配置传递**:cordis loader 调 `apply(ctx, config)`;patch 条目可带 `config:`。
22
- 6. **Windows 通知**:`powershell.exe`(5.1) 恒在 `%WINDIR%\System32\WindowsPowerShell\v1.0\`;用 `ToastNotificationManager` 发原生 Toast,AUMID 借用 PowerShell 已注册的 `{1AC14E77-...}\WindowsPowerShell\v1.0\powershell.exe`;脚本经 `-EncodedCommand`(UTF-16LE base64)传入、数据经 `env`(UTF-16)传入,规避引号/代码页问题。
23
- 7. **位置标注数据源**:`ctx.workspaceRegistry`(`dsh-web-app` 提供)`list()` 返回带 `title`/`path`/`sessionIds` 的工作区,按 `sessionIds.includes(id)` 同步匹配归属;`ctx.sessionTitle`(`dsh-base` 提供)`get(session)` 返回 sidebar 显示的会话标题。两者都是进程内同步读,通知时现取即可,异常/缺失时优雅降级。
24
-
25
- ## 3. 架构
26
-
27
- ```
28
- D:\dsh\plugins\dsh-plugin-notify\
29
- ├── package.json # dsh-notify;main: lib/index.js;dsh.bundle.patch: ./cordis.patch.yml
30
- ├── cordis.patch.yml # bundle 补丁:- insert 注册 dsh-plugin-notify 行
31
- ├── lib/index.js # host 半区:事件订阅 + 状态机 + Toast 派发 + 托盘图标
32
- ├── assets/
33
- │ ├── dsh.png # Toast 图标(黑色鲸鱼 logo,256×256)
34
- │ └── dsh.ico # 托盘图标(黑色鲸鱼 logo,32×32)
35
- ├── 设计文稿.md # 本文档
36
- ├── README.md # 安装/配置/排障
37
- └── scripts/
38
- ├── tray.ps1 # 系统托盘图标(常驻,父进程退出后自动消失)
39
- ├── smoke-notice.ps1 # 独立冒烟:直接弹一条 Toast
40
- └── test-harness.mjs # 端到端自测:驱动处理器弹三条真实 Toast
41
- ```
42
-
43
- 数据流:
44
-
45
- ```
46
- agent 循环 ──agent/status──▶ 状态机(每会话) ──running→idle──▶ 组装文案 ──▶ spawn powershell ──▶ Toast(点击打开 dsh 页面)
47
- ──session/event─▶ 状态机(记录 turn/reason/摘要/等待) ──等待/完成──▶ 同上
48
- ──agent/disposed▶ 状态机(兜底"已关闭")
49
- 宿主启动 ──▶ spawn tray.ps1 ──▶ 系统托盘常驻图标(父进程退出 → 图标消失)
50
- ```
51
-
52
- ## 4. 事件订阅与状态机
53
-
54
- 每个被跟踪的会话维护一条状态记录(`Map<agentId, State>`):`running`、`turnStarted`(区分空转与真实工作)、`lastReason`、`lastPrompt`、`waiting/waitingCallId/waitingText`、`session`(最近可见会话对象引用)、`workspaceLabel/sessionLabel`(「工作区 · 会话」标签缓存)、`lastToastAt[kind]`(按类型冷却)。
55
-
56
- | 事件 | 动作 |
57
- |---|---|
58
- | `agent/status` running | `running=true` |
59
- | `agent/status` idle | 若 `running=true` 且 `turnStarted||waiting` → 按 `lastReason` 选文案通知;空转不通知 |
60
- | `session/event` `turn/start` | `turnStarted=true` |
61
- | `session/event` `turn/end` | 记录 `lastReason` |
62
- | `session/event` `user/message` | 记录 `lastPrompt`(首个文本块摘要) |
63
- | `session/event` `tool/call`(`ask_user_question`) | 置 `waiting`,立即通知"等你选择" |
64
- | `session/event` `tool/result`(配对) | 清除 `waiting` |
65
- | `agent/disposed` | 若曾 running → 通知"会话已关闭";删状态 |
66
-
67
- 结束原因 → 标题:`completed`→任务完成、`aborted`→任务已停止、`error/interrupted`→任务出错、`max-tokens`→达到输出上限、`blocked`→回合被阻断。
68
-
69
- ## 5. 关键实现细节
70
-
71
- ### 5.1 Toast 派发(fire-and-forget)
72
-
73
- `TOAST_SCRIPT` 用 `ToastNotificationManager` 发 `<toast scenario="reminder" activationType="protocol" launch="<webUrl>">`(整条常驻且点击打开 dsh 页面),`<image placement="appLogoOverride" src="file:///…dsh.png">` 显示鲸鱼 logo。脚本经 `-EncodedCommand` 传入、数据经 `env` 传入;进程失败只 `ctx.logger.warn`,不影响 dsh。
74
-
75
- 正文由三个 `<text>` 组成:标题、任务摘要/等待文案、位置行(`工作区「…」 · 会话「…」`,经 `DSH_NOTICE_LOCATION` 环境变量传入,为空则省略该元素)。位置行在 `notify()` 时现取:`resolveWorkspaceLabel` 先按 `workspaceRegistry.list()` 的 `sessionIds` 匹配归属标题、兜底 cwd 目录名;`resolveSessionLabel` 优先 `sessionTitle.get(session).title`,再直接折会话日志 `session/title` 事件(与 `foldSessionTitle` 同款,插件 ctx 上服务不可见时仍能取到标题)、兜底会话 id 短号;两者均含长度截断,服务缺失时自动降级(无服务 → 无位置行)。
76
-
77
- ### 5.2 系统托盘图标
78
-
79
- host 半区 `apply` 时 `spawn(powershell.exe -STA -File scripts/tray.ps1 -ParentPid <pid> …)`,`tray.ps1` 用 `NotifyIcon` + `Application.Run()` 常驻;内置 `Timer` 每 3s 检查父进程 PID,父进程消失即退出;模块级 `trayChild` 保证进程内至多一个。双击/右键「打开 dsh」`Start-Process <webUrl>`,右键「关闭进程」`Stop-Process <父 PID>`。
80
-
81
- ### 5.3 防抖与空转过滤
82
-
83
- 激活时把已 `running` 的 agent 记为"有活动"(完成时照常通知),空闲 agent 不建状态(避免历史会话刷屏);`idle→running→idle` 且无 `turn/start` 不通知;冷却按类型记录,`cooldownMs` 内同类型不重复。
84
-
85
- ## 6. 配置文件
86
-
87
- patch 条目 `config`(全部有默认值,见 `README.md` 表格)。核心字段:`enabled`、`tray`、`rootsOnly`、`notifyFinished/Aborted/Error/Waiting/Disposed`、`cooldownMs`、`titlePrefix`(默认 `'dsh'`)、`iconPath`(默认 `assets/dsh.png`)、`webUrl`、`aumid`、`summaryMaxChars`、`powershellPath`。
88
-
89
- ## 7. 安装步骤
90
-
91
- 本项目是标准 DSH bundle:`package.json` 声明 `dsh.bundle.patch: ./cordis.patch.yml`,包内自带 `cordis.patch.yml`(`- insert: [{ id: dsh-plugin-notify, name: dsh-notify }]`)。`dsh plugin` 命令是 pnpm 转发器,安装成功后按「已装依赖中声明了 `dsh.bundle` 的包」自动校准 `dsh.profile.bundles`,无需手动写补丁。
92
-
93
- 安装方式(任选其一):
94
-
95
- 1. **npm**(推荐):`dsh plugin --profile web add dsh-notify`。
96
- 2. **GitHub**:`dsh plugin --profile web add github:Pasumao/dsh-plugin-notify`(零构建,一般无需 allowBuilds)。
97
- 3. **本地路径**:`dsh plugin --profile web add D:/dsh/plugins/dsh-plugin-notify`。
98
- 4. **手动 link**(旧版流程):dependencies 加 `"dsh-notify": "link:D:/dsh/plugins/dsh-plugin-notify"` 后 `pnpm install`。
99
-
100
- 装完重启 `dsh web`(托盘图标出现即加载成功)。调整行为时在用户层 `cordis.patch.yml` 按 id 覆盖 config。
101
-
102
- ## 8. 边界与备选
103
-
104
- | 情况 | 处理 |
105
- |---|---|
106
- | 重载时已空闲的历史会话 | 不建状态、不通知 |
107
- | 唤醒后被清空的空转 | 无 `turn/start`,不通知 |
108
- | 子代理大量完成 | `rootsOnly` 默认过滤(`delegationDepth`) |
109
- | 中文乱码 | `env`(UTF-16) + `-EncodedCommand`(UTF-16LE base64) |
110
- | PowerShell 缺失/失败 | 仅 warn,不崩溃 |
111
- | HMR 重复加载 | `ctx.effect` 清理监听/状态/托盘 |
112
-
113
- 备选(记录):点击精确跳会话需注册 dsh 自有 AUMID(`SHGetPropertyStoreFromParsingName` 在本机返回 `0x80070057` 未成功),或用 BurntToast 模块(`Install-Module BurntToast`)实现可点击 toast。
114
-
115
- ## 9. 测试与验收
116
-
117
- 1. `scripts/smoke-notice.ps1` 独立弹 Toast;`node scripts/test-harness.mjs` 驱动处理器弹三条真实 Toast(等待/完成/停止)。
118
- 2. 端到端(重启后):正常任务完成弹 Toast、点停止弹"任务已停止"、`ask_user_question` 等待期弹"在等你选择"、重载旧会话不弹、子代理不刷屏、关闭 running 会话弹"已关闭";托盘图标常驻、父进程退出自动消失、点击打开 dsh。
119
- 3. 日志:`ctx.logger` 记录每次通知的 kind/session/「工作区 · 会话」位置行与 spawn 错误。