dsh-notify-windows 0.4.0 → 0.6.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 +41 -0
- package/LICENSE +21 -21
- package/README.en.md +110 -75
- package/README.md +110 -93
- package/cordis.patch.yml +14 -0
- package/lib/index.js +273 -211
- package/lib/notify.ps1 +35 -35
- package/package.json +18 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本项目遵循 [Semantic Versioning](https://semver.org/)。
|
|
4
|
+
|
|
5
|
+
## [0.6.0] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### 变更(dsh.bundle 安装 + 对齐 dsh 0.1.0-rc.8)
|
|
8
|
+
|
|
9
|
+
- **改为 `dsh.bundle` 安装**:新增 `cordis.patch.yml`(包内自带 `insert:` 条目)并在
|
|
10
|
+
package.json 声明 `dsh.bundle.patch`。安装方式从「手动打 insert + 复制到
|
|
11
|
+
node_modules」改为 `dsh plugin --profile <name> add dsh-notify-windows`,
|
|
12
|
+
pnpm 安装后 `dsh` 自动把包加入 `dsh.profile.bundles` 层栈并挂载入口。
|
|
13
|
+
- **删除 manual-insert 部署**:移除 `scripts/install-profile.ps1`(旧的复制 lib 到
|
|
14
|
+
version-suffixed node_modules 目录并正则改写 profile `cordis.patch.yml` 的做法)。
|
|
15
|
+
- **rc.8 兼容**:
|
|
16
|
+
- 子代理过滤改用 rc.8 的 `session.header.delegationDepth`(旧字段 `depth` 已移除);
|
|
17
|
+
- 导出风格改为 rc.8 推荐的 `export const name` + `export const apply`(旧 default
|
|
18
|
+
export 会让 loader 丢弃 `name` 命名导出,fiber 名显示为 `dshNotify`)。
|
|
19
|
+
- **配置文件覆盖方式**:不再需要手动 `- insert:`;在 profile 的 `cordis.patch.yml`
|
|
20
|
+
中按 id(`dsh-notify`)覆盖 `config` 即可,与 `dsh-yolo-mode` 等 bundle 插件一致。
|
|
21
|
+
|
|
22
|
+
### 其它
|
|
23
|
+
|
|
24
|
+
- 冒烟测试 `scripts/smoke-test.mjs` 路径自发现(`DSH_HARNESS_DIR` 可覆盖),
|
|
25
|
+
适配新的 `apply` 导出与 `delegationDepth` 字段。
|
|
26
|
+
- README 更新:安装 / 更新 / 卸载命令与配置覆盖示例。
|
|
27
|
+
|
|
28
|
+
## [0.5.0] - 2026-08-13
|
|
29
|
+
|
|
30
|
+
### 新增
|
|
31
|
+
|
|
32
|
+
- /goal 自动推进回合默认静默提醒,仅目标完成 / 阻塞的最终回合弹通知(可配置)。
|
|
33
|
+
- 任务完成通知自动附带 Agent 最后回复的摘要(可裁剪,`excerptMaxChars`)。
|
|
34
|
+
|
|
35
|
+
## [0.4.0] - 2026-08-13
|
|
36
|
+
|
|
37
|
+
### 新增
|
|
38
|
+
|
|
39
|
+
- 增加审批(`approval/asked`)与提问(`ask_user_question` / `run_code` 内联调用)
|
|
40
|
+
提醒。
|
|
41
|
+
- Windows 桌面 Toast 通知,通过 PowerShell 5.1 WinRT API 发送,零运行时依赖。
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 SeverusZh
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SeverusZh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
CHANGED
|
@@ -1,75 +1,110 @@
|
|
|
1
|
-
# dsh-notify
|
|
2
|
-
|
|
3
|
-
A DeepSeek Harness (DSH) plugin that sends Windows toast notifications whenever
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
on
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
# dsh-notify-windows
|
|
2
|
+
|
|
3
|
+
> A DeepSeek Harness (DSH) plugin that sends Windows toast notifications whenever the agent needs your attention.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/npm/v/dsh-notify-windows" alt="npm">
|
|
7
|
+
<img src="https://img.shields.io/github/license/SeverusZh/dsh-notify-windows" alt="license">
|
|
8
|
+
<img src="https://img.shields.io/badge/platform-Windows%2010%2F11-blue" alt="platform">
|
|
9
|
+
<img src="https://img.shields.io/github/stars/SeverusZh/dsh-notify-windows?style=social" alt="stars">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
**Task finished** ✅ | **Approval pending** 🔐 | **Question pending** ❓ — never miss anything that needs you, even away from the screen.
|
|
13
|
+
|
|
14
|
+
## ✨ Features
|
|
15
|
+
|
|
16
|
+
- **Task completion** — listens to `turn/end` session events; toasts on completed / errored / max-tokens turns, with the session title and reason;
|
|
17
|
+
- **Approval pending** — listens to `approval/asked`; toasts the moment a permission decision is waiting, and skips sessions whose approval policy is `never` (nothing is waiting there);
|
|
18
|
+
- **Question pending** — toasts when the agent calls `ask_user_question`; in this deployment every tool call goes through `run_code`, so the plugin also scans `run_code` source for `tools.ask_user_question(` calls and extracts the question text;
|
|
19
|
+
- **Quiet by default** — subagent sessions are ignored unless `includeSubagents` is set;
|
|
20
|
+
- **Zero dependencies** — toasts are sent through Windows PowerShell 5.1's WinRT toast API, with the AppUserModelId registered under HKCU on first use (no admin required);
|
|
21
|
+
- **Diagnosable** — optional log (`%TEMP%\dsh-notify\notify.log`) and a debug event log.
|
|
22
|
+
|
|
23
|
+
## 🚀 Install
|
|
24
|
+
|
|
25
|
+
The plugin installs through the **`dsh.bundle`** mechanism: the npm package ships its own
|
|
26
|
+
`cordis.patch.yml`, and after `dsh plugin add` it auto-mounts the `dsh-notify` entry — no
|
|
27
|
+
manual `- insert:` needed.
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
dsh plugin --profile web add dsh-notify-windows
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Restart DSH and refresh the browser:
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
dsh --profile web
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> Note: don't hand-add `- insert:` with `dsh-notify` anymore — DSH would fail to boot with
|
|
40
|
+
> `duplicate loader entry id: dsh-notify`. To tweak settings, override the entry by id in the
|
|
41
|
+
> profile's `cordis.patch.yml` (see below).
|
|
42
|
+
|
|
43
|
+
### Update / Remove
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
dsh plugin --profile web update dsh-notify-windows
|
|
47
|
+
dsh plugin --profile web remove dsh-notify-windows
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Restart the DSH host after updating (a newly added bundle layer needs a boot).
|
|
51
|
+
|
|
52
|
+
## ⚙️ Configuration
|
|
53
|
+
|
|
54
|
+
All `config` keys are optional and default in the plugin. To override, target the entry by id
|
|
55
|
+
in `$DSH_HOME/profiles/web/cordis.patch.yml` (hot-applied by the running host — no restart):
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- id: dsh-notify
|
|
59
|
+
name: dsh-notify-windows
|
|
60
|
+
config:
|
|
61
|
+
enabled: true
|
|
62
|
+
reasons: [completed, error, max-tokens]
|
|
63
|
+
notifyOnStart: true
|
|
64
|
+
notifyOnApproval: true
|
|
65
|
+
log: true
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Key | Default | Meaning |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `enabled` | `true` | master switch |
|
|
71
|
+
| `reasons` | `[completed, error, max-tokens]` | turn-end reasons to notify (`aborted` / `interrupted` / `blocked` also available) |
|
|
72
|
+
| `includeSubagents` | `false` | also notify subagent sessions |
|
|
73
|
+
| `notifyOnStart` | `false` | toast when the plugin loads |
|
|
74
|
+
| `notifyOnApproval` | `true` | toast on `approval/asked` (skipped under policy `never`) |
|
|
75
|
+
| `notifyOnAskUser` | `true` | toast when the agent asks you a question |
|
|
76
|
+
| `notifyOnGoalRounds` | `false` | stay quiet on /goal auto-continuation rounds (except the final complete/block round) |
|
|
77
|
+
| `excerpt` | `true` | append an excerpt of the agent's final reply to the toast |
|
|
78
|
+
| `excerptMaxChars` | `80` | max characters of the excerpt |
|
|
79
|
+
| `appName` | `DeepSeek Harness` | toast source display name and fallback title |
|
|
80
|
+
| `aumid` | `DeepSeekHarness.Notify` | toast AppUserModelId |
|
|
81
|
+
| `log` | `true` | write `%TEMP%\dsh-notify\notify.log` |
|
|
82
|
+
| `debug` | `false` | log every session event (diagnostics, heavy) |
|
|
83
|
+
|
|
84
|
+
## 🔔 Triggers
|
|
85
|
+
|
|
86
|
+
| Scenario | Session event | Sample toast |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| Task finished | `turn/end` | Task completed (turn N) |
|
|
89
|
+
| Task errored / capped | `turn/end` | Task errored / Output hit token cap |
|
|
90
|
+
| Approval waiting | `approval/asked` | DeepSeek Harness · Needs approval / tool pwsh: … |
|
|
91
|
+
| Question waiting | `tool/call` (incl. run_code detection) | DeepSeek Harness · Needs your answer / Continue? |
|
|
92
|
+
|
|
93
|
+
## 🧪 Verify
|
|
94
|
+
|
|
95
|
+
```powershell
|
|
96
|
+
node scripts\smoke-test.mjs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Runs the plugin on a bare cordis Context with synthetic events: three test toasts fire (completion, approval, question) and the log assertions verify the filtering (subagent, `never`-policy, unrelated calls).
|
|
100
|
+
|
|
101
|
+
## ❓ FAQ
|
|
102
|
+
|
|
103
|
+
- **No toast?** Check Windows notification settings for this app and Focus Assist; the AUMID registers itself on first use.
|
|
104
|
+
- **Do /goal rounds toast?** Not by default: auto-continuation rounds stay quiet and only the final round that completes (or blocks) the goal toasts. Set `notifyOnGoalRounds` to `true` to hear every round.
|
|
105
|
+
- **Approval toasts missing?** Sessions whose approval policy is `never` auto-reject — nothing waits, so the plugin stays quiet. Only `ask`-policy sessions notify.
|
|
106
|
+
- **Hot-update after code changes?** Run `dsh plugin --profile web update dsh-notify-windows`, then restart the DSH host.
|
|
107
|
+
|
|
108
|
+
## 📄 License
|
|
109
|
+
|
|
110
|
+
[MIT](./LICENSE) © 2026 SeverusZh
|
package/README.md
CHANGED
|
@@ -1,93 +1,110 @@
|
|
|
1
|
-
# dsh-notify
|
|
2
|
-
|
|
3
|
-
DeepSeek Harness(DSH
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
1
|
+
# dsh-notify-windows
|
|
2
|
+
|
|
3
|
+
> DeepSeek Harness(DSH)插件:当 Agent 需要你关注时,向 Windows 发送系统桌面通知。
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/npm/v/dsh-notify-windows" alt="npm">
|
|
7
|
+
<img src="https://img.shields.io/github/license/SeverusZh/dsh-notify-windows" alt="license">
|
|
8
|
+
<img src="https://img.shields.io/badge/platform-Windows%2010%2F11-blue" alt="platform">
|
|
9
|
+
<img src="https://img.shields.io/github/stars/SeverusZh/dsh-notify-windows?style=social" alt="stars">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
**任务完成** ✅ | **等待审批** 🔐 | **等待回答** ❓ —— 离开屏幕也不错过任何需要你处理的事。
|
|
13
|
+
|
|
14
|
+
## ✨ 功能
|
|
15
|
+
|
|
16
|
+
- **任务完成提醒**:监听会话 `turn/end` 事件,任务完成 / 出错 / 输出超限时立即弹窗,标题自动使用会话标题,正文显示原因与轮次;
|
|
17
|
+
- **权限审批提醒**:监听 `approval/asked` 事件,有操作等待你的审批时立即提醒;会话审批策略为 `never` 时自动跳过(此时没有东西在等你);
|
|
18
|
+
- **提问确认提醒**:Agent 调用 `ask_user_question` 向你提问时提醒;本部署下所有工具都经 `run_code` 调用,插件会扫描 `run_code` 程序源码中的 `tools.ask_user_question(` 调用并提取问题文本;
|
|
19
|
+
- **防打扰**:默认忽略子代理(subagent)会话,只提醒主会话;
|
|
20
|
+
- **零依赖**:通知通过 Windows PowerShell 5.1 的 WinRT Toast API 发送,自动注册 HKCU 的 AppUserModelId(无需管理员权限);
|
|
21
|
+
- **可诊断**:可选日志(`%TEMP%\dsh-notify\notify.log`)与 debug 事件日志。
|
|
22
|
+
|
|
23
|
+
## 🚀 安装
|
|
24
|
+
|
|
25
|
+
项目通过 **`dsh.bundle`** 机制安装:npm 包自带的 `cordis.patch.yml` 会在
|
|
26
|
+
`dsh plugin add` 后自动挂载 `dsh-notify` 入口,**不需要**再手动 `- insert:`。
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
dsh plugin --profile web add dsh-notify-windows
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
重启 DSH 并刷新浏览器后生效:
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
dsh --profile web
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> 注意:不要再用 `- insert:` 手动添加 `dsh-notify`,否则启动会报
|
|
39
|
+
> `duplicate loader entry id: dsh-notify`。想调整配置,在 profile 的
|
|
40
|
+
> `cordis.patch.yml` 里按 id 覆盖即可(见下节)。
|
|
41
|
+
|
|
42
|
+
### 更新 / 卸载
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
dsh plugin --profile web update dsh-notify-windows
|
|
46
|
+
dsh plugin --profile web remove dsh-notify-windows
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
更新后需重启 DSH 宿主(新增 bundle 层需要重新启动)。
|
|
50
|
+
|
|
51
|
+
## ⚙️ 配置项
|
|
52
|
+
|
|
53
|
+
插件行 `config` 全字段可选,未填按默认值。需要调整时,在
|
|
54
|
+
`$DSH_HOME/profiles/web/cordis.patch.yml` 里按 id 覆盖主条目即可
|
|
55
|
+
(该文件被运行中的 DSH 热监视,改动立即生效,无需重启):
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- id: dsh-notify
|
|
59
|
+
name: dsh-notify-windows
|
|
60
|
+
config:
|
|
61
|
+
enabled: true
|
|
62
|
+
reasons: [completed, error, max-tokens]
|
|
63
|
+
notifyOnStart: true
|
|
64
|
+
notifyOnApproval: true
|
|
65
|
+
log: true
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| 配置 | 默认值 | 说明 |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `enabled` | `true` | 总开关 |
|
|
71
|
+
| `reasons` | `[completed, error, max-tokens]` | 需要提醒的回合结束原因(可选 `aborted` / `interrupted` / `blocked`) |
|
|
72
|
+
| `includeSubagents` | `false` | 是否也提醒子代理会话 |
|
|
73
|
+
| `notifyOnStart` | `false` | 插件加载时发一条「已激活」通知 |
|
|
74
|
+
| `notifyOnApproval` | `true` | 审批请求提醒(策略 `never` 时自动跳过) |
|
|
75
|
+
| `notifyOnAskUser` | `true` | Agent 提问等待回答时提醒 |
|
|
76
|
+
| `notifyOnGoalRounds` | `false` | /goal 自动推进回合不提醒(目标完成/阻塞的最终回合除外) |
|
|
77
|
+
| `excerpt` | `true` | 通知正文附带 Agent 最后回复的摘要 |
|
|
78
|
+
| `excerptMaxChars` | `80` | 摘要最大字符数 |
|
|
79
|
+
| `appName` | `DeepSeek Harness` | 通知来源显示名与兜底标题 |
|
|
80
|
+
| `aumid` | `DeepSeekHarness.Notify` | 通知 AppUserModelId |
|
|
81
|
+
| `log` | `true` | 写日志到 `%TEMP%\dsh-notify\notify.log` |
|
|
82
|
+
| `debug` | `false` | 把所有会话事件写入日志(排查用,量大) |
|
|
83
|
+
|
|
84
|
+
## 🔔 触发场景
|
|
85
|
+
|
|
86
|
+
| 场景 | 会话事件 | 通知示例 |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| 任务完成 | `turn/end` | 「任务已完成(第 N 轮)」 |
|
|
89
|
+
| 任务出错 / 超限 | `turn/end` | 「任务出错」/「输出达到 token 上限」 |
|
|
90
|
+
| 等待审批 | `approval/asked` | 「DeepSeek Harness · 需要审批 / 工具 pwsh:…」 |
|
|
91
|
+
| 等待回答 | `tool/call`(含 run_code 检测) | 「DeepSeek Harness · 需要回答 / 是否继续?」 |
|
|
92
|
+
|
|
93
|
+
## 🧪 验证
|
|
94
|
+
|
|
95
|
+
```powershell
|
|
96
|
+
node scripts\smoke-test.mjs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
独立进程内用 cordis Context 模拟会话事件:应弹出任务完成 / 审批 / 提问三条测试通知,并断言日志记录与过滤行为(子代理、`never` 策略、无关调用均被过滤)。
|
|
100
|
+
|
|
101
|
+
## ❓ 常见问题
|
|
102
|
+
|
|
103
|
+
- **收不到通知?** 检查 Windows「通知与操作」设置是否允许该应用显示通知,以及「专注助手」是否开启;首次发送会自动注册 AUMID。
|
|
104
|
+
- **/goal 模式会提醒吗?** 默认不会:自动推进的中间回合保持静默,只有目标完成(或阻塞)的最终回合才提醒;如需每个回合都提醒,把 `notifyOnGoalRounds` 设为 `true`。
|
|
105
|
+
- **为什么审批提醒有时不弹?** 会话审批策略为 `never` 时审批会被自动拒绝、不会等待,插件会跳过提醒;策略为 `ask` 时才提醒。
|
|
106
|
+
- **更新插件代码后如何生效?** 执行 `dsh plugin --profile web update dsh-notify-windows` 后重启 DSH 宿主。
|
|
107
|
+
|
|
108
|
+
## 📄 许可证
|
|
109
|
+
|
|
110
|
+
[MIT](./LICENSE) © 2026 SeverusZh
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# dsh-notify-windows bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# Declares the plugin's single loader entry. The `insert:` rows live in the
|
|
4
|
+
# package's own patch file, so `dsh plugin add dsh-notify-windows` auto-mounts
|
|
5
|
+
# the entry — no manual `- insert:` in the profile's cordis.patch.yml.
|
|
6
|
+
# The `name` (a bare package specifier) resolves to this package's
|
|
7
|
+
# `main`/`exports["."]` (lib/index.js) via the loader's two-anchor resolution
|
|
8
|
+
# (dsh install first, then the profile's node_modules).
|
|
9
|
+
#
|
|
10
|
+
# Install: dsh plugin --profile <name> add dsh-notify-windows
|
|
11
|
+
# Restart `dsh --profile <name>` after installing.
|
|
12
|
+
- insert:
|
|
13
|
+
- id: dsh-notify
|
|
14
|
+
name: dsh-notify-windows
|
package/lib/index.js
CHANGED
|
@@ -1,211 +1,273 @@
|
|
|
1
|
-
// dsh-notify — DeepSeek Harness plugin: send a Windows toast notification
|
|
2
|
-
// whenever the agent needs the user's attention:
|
|
3
|
-
// - turn/end — a task completed, failed, or was cut off
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
1
|
+
// dsh-notify-windows — DeepSeek Harness plugin: send a Windows toast notification
|
|
2
|
+
// whenever the agent needs the user's attention:
|
|
3
|
+
// - turn/end — a task completed, failed, or was cut off (with an
|
|
4
|
+
// optional excerpt of the agent's final reply);
|
|
5
|
+
// - approval/asked — a permission approval is waiting (only when the
|
|
6
|
+
// session's effective approval policy is "ask");
|
|
7
|
+
// - tool/call — the agent called the ask_user_question tool (directly
|
|
8
|
+
// or inside a run_code program) and is waiting.
|
|
9
|
+
//
|
|
10
|
+
// Goal rounds (/goal auto-continuation) are quiet by default: only the final
|
|
11
|
+
// round that completes or blocks the goal produces a toast.
|
|
12
|
+
//
|
|
13
|
+
// Zero runtime dependencies: notifications go through the toast sender
|
|
14
|
+
// script (./notify.ps1) executed by Windows PowerShell 5.1, whose .NET
|
|
15
|
+
// Framework runtime supports the WinRT toast API.
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const NOTIFY_SCRIPT = fileURLToPath(new URL("./notify.ps1", import.meta.url));
|
|
23
|
+
const VERSION = "0.6.0";
|
|
24
|
+
|
|
25
|
+
const REASON_TEXT = {
|
|
26
|
+
completed: "任务已完成",
|
|
27
|
+
error: "任务出错",
|
|
28
|
+
"max-tokens": "输出达到 token 上限",
|
|
29
|
+
aborted: "任务已取消",
|
|
30
|
+
interrupted: "任务中断",
|
|
31
|
+
blocked: "任务未开始",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const name = "dsh-notify-windows";
|
|
35
|
+
|
|
36
|
+
function truncate(text, max) {
|
|
37
|
+
if (typeof text !== "string") return "";
|
|
38
|
+
return text.length > max ? text.slice(0, max) + "…" : text;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** First question text of an ask_user_question tool call, if parseable. */
|
|
42
|
+
function extractAskQuestion(argumentsJson) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(argumentsJson);
|
|
45
|
+
const first = Array.isArray(parsed?.questions) ? parsed.questions[0] : undefined;
|
|
46
|
+
return typeof first?.question === "string" ? first.question : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* This deployment invokes every tool through run_code programs, so a real
|
|
54
|
+
* question never appears as a model-level ask_user_question tool call.
|
|
55
|
+
* Detect the nested call inside a run_code program's code and pull out the
|
|
56
|
+
* first question text.
|
|
57
|
+
*/
|
|
58
|
+
function extractQuestionFromCode(argumentsJson) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(argumentsJson);
|
|
61
|
+
const code = typeof parsed?.code === "string" ? parsed.code : "";
|
|
62
|
+
const callIndex = code.search(/tools\.ask_user_question\s*\(/);
|
|
63
|
+
if (callIndex < 0) return undefined;
|
|
64
|
+
const region = code.slice(callIndex, callIndex + 8000);
|
|
65
|
+
const matches = [...region.matchAll(/question\s*:\s*["']([^"']{2,200})["']/g)];
|
|
66
|
+
return matches.length > 0 ? matches[0][1] : undefined;
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** First non-empty text block of a message, trimmed. */
|
|
73
|
+
function textOfMessage(message) {
|
|
74
|
+
const blocks = message?.content;
|
|
75
|
+
if (!Array.isArray(blocks)) return undefined;
|
|
76
|
+
for (const block of blocks) {
|
|
77
|
+
if (block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) return block.text.trim();
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Scan the session log from the turn/end event back to its turn/start and
|
|
84
|
+
* collect what the notification needs: the driving user message's source,
|
|
85
|
+
* every goal/change operation in the window, and the last assistant reply
|
|
86
|
+
* text (for the excerpt).
|
|
87
|
+
*/
|
|
88
|
+
function turnWindow(session, turnEnd) {
|
|
89
|
+
const events = session?.events ?? [];
|
|
90
|
+
const turn = turnEnd?.data?.turn;
|
|
91
|
+
let endIndex = events.length - 1;
|
|
92
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
93
|
+
if (events[i]?.seq === turnEnd?.seq) { endIndex = i; break; }
|
|
94
|
+
}
|
|
95
|
+
let userSource;
|
|
96
|
+
const goalOps = [];
|
|
97
|
+
let lastAssistantText;
|
|
98
|
+
for (let i = endIndex; i >= 0; i -= 1) {
|
|
99
|
+
const event = events[i];
|
|
100
|
+
if (!event) continue;
|
|
101
|
+
if (event.type === "turn/start" && event.data?.turn === turn) break;
|
|
102
|
+
// Every user/message in the window overwrites: walking backwards, the
|
|
103
|
+
// final value is the FIRST message of the turn — the driving input.
|
|
104
|
+
if (event.type === "user/message" && event.data?.source) userSource = event.data.source;
|
|
105
|
+
if (event.type === "goal/change" && event.data?.operation) goalOps.push(event.data.operation);
|
|
106
|
+
if (event.type === "assistant/message" && event.data?.turn === turn && lastAssistantText === undefined) {
|
|
107
|
+
const text = textOfMessage(event.data.message);
|
|
108
|
+
if (text) lastAssistantText = text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { userSource, goalOps, lastAssistantText };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const apply = (ctx, config = {}) => {
|
|
115
|
+
const cfg = {
|
|
116
|
+
enabled: true,
|
|
117
|
+
reasons: ["completed", "error", "max-tokens"],
|
|
118
|
+
includeSubagents: false,
|
|
119
|
+
notifyOnStart: false,
|
|
120
|
+
notifyOnApproval: true,
|
|
121
|
+
notifyOnAskUser: true,
|
|
122
|
+
notifyOnGoalRounds: false,
|
|
123
|
+
excerpt: true,
|
|
124
|
+
excerptMaxChars: 80,
|
|
125
|
+
appName: "DeepSeek Harness",
|
|
126
|
+
aumid: "DeepSeekHarness.Notify",
|
|
127
|
+
log: true,
|
|
128
|
+
debug: false,
|
|
129
|
+
...config,
|
|
130
|
+
};
|
|
131
|
+
const reasons = new Set(cfg.reasons);
|
|
132
|
+
const logPath = join(tmpdir(), "dsh-notify", "notify.log");
|
|
133
|
+
|
|
134
|
+
const log = (entry) => {
|
|
135
|
+
if (!cfg.log) return;
|
|
136
|
+
try {
|
|
137
|
+
mkdirSync(join(tmpdir(), "dsh-notify"), { recursive: true });
|
|
138
|
+
appendFileSync(logPath, JSON.stringify({ time: Date.now(), ...entry }) + "\n");
|
|
139
|
+
} catch {
|
|
140
|
+
// diagnostics must never break the agent loop
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const sendToast = (title, body) => {
|
|
145
|
+
const child = spawn(
|
|
146
|
+
"powershell.exe",
|
|
147
|
+
[
|
|
148
|
+
"-NoProfile",
|
|
149
|
+
"-NonInteractive",
|
|
150
|
+
"-ExecutionPolicy",
|
|
151
|
+
"Bypass",
|
|
152
|
+
"-File",
|
|
153
|
+
NOTIFY_SCRIPT,
|
|
154
|
+
"-Title",
|
|
155
|
+
title,
|
|
156
|
+
"-Body",
|
|
157
|
+
body,
|
|
158
|
+
"-Aumid",
|
|
159
|
+
cfg.aumid,
|
|
160
|
+
"-AppName",
|
|
161
|
+
cfg.appName,
|
|
162
|
+
],
|
|
163
|
+
{ stdio: "ignore", windowsHide: true },
|
|
164
|
+
);
|
|
165
|
+
child.on("error", (error) => {
|
|
166
|
+
log({ event: "error", message: String(error?.message ?? error) });
|
|
167
|
+
ctx.logger?.warn?.("dsh-notify: 启动通知进程失败", error);
|
|
168
|
+
});
|
|
169
|
+
child.on("exit", (code) => {
|
|
170
|
+
if (code !== 0) {
|
|
171
|
+
log({ event: "error", code });
|
|
172
|
+
ctx.logger?.warn?.("dsh-notify: 通知进程退出码 " + code);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const titleFor = (session) => {
|
|
178
|
+
try {
|
|
179
|
+
return ctx.sessionTitle?.get?.(session)?.title;
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** True for subagent sessions when subagents are excluded. */
|
|
186
|
+
const isFilteredSession = (session) => {
|
|
187
|
+
if (cfg.includeSubagents) return false;
|
|
188
|
+
return session?.header?.origin === "subagent" || session?.header?.delegationDepth !== undefined;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/** Last approval/policy event of the session, or undefined (defaults to ask). */
|
|
192
|
+
const effectivePolicy = (session) => {
|
|
193
|
+
const events = session?.events ?? [];
|
|
194
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
195
|
+
const event = events[index];
|
|
196
|
+
if (event?.type === "approval/policy") return event.data?.policy;
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
ctx.on("session/event", (session, event) => {
|
|
202
|
+
if (!cfg.enabled) return;
|
|
203
|
+
if (cfg.debug) {
|
|
204
|
+
log({ event: "debug", type: event.type, name: event.data?.name ?? null, sessionId: session?.id });
|
|
205
|
+
}
|
|
206
|
+
if (isFilteredSession(session)) return;
|
|
207
|
+
|
|
208
|
+
if (event.type === "turn/end") {
|
|
209
|
+
const reason = event.data?.reason;
|
|
210
|
+
if (typeof reason?.kind !== "string" || !reasons.has(reason.kind)) return;
|
|
211
|
+
const window = turnWindow(session, event);
|
|
212
|
+
|
|
213
|
+
// /goal 自动推进回合默认静默:仅在目标完成/阻塞的最终回合提醒。
|
|
214
|
+
if (!cfg.notifyOnGoalRounds) {
|
|
215
|
+
const isGoalRound = window.userSource?.kind === "goal" && window.userSource.round > 0;
|
|
216
|
+
const terminal = window.goalOps.some((op) => op === "complete" || op === "block");
|
|
217
|
+
if (isGoalRound && !terminal) return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const title = titleFor(session) || cfg.appName;
|
|
221
|
+
let body = REASON_TEXT[reason.kind] ?? "任务已结束";
|
|
222
|
+
if (Number.isInteger(event.data.turn)) body += "(第 " + event.data.turn + " 轮)";
|
|
223
|
+
if (cfg.excerpt && window.lastAssistantText) {
|
|
224
|
+
body += "\n" + truncate(window.lastAssistantText, cfg.excerptMaxChars);
|
|
225
|
+
}
|
|
226
|
+
sendToast(title, body);
|
|
227
|
+
log({
|
|
228
|
+
event: "notify",
|
|
229
|
+
sessionId: session?.id,
|
|
230
|
+
reason: reason.kind,
|
|
231
|
+
turn: event.data?.turn,
|
|
232
|
+
title,
|
|
233
|
+
body,
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (event.type === "approval/asked" && cfg.notifyOnApproval) {
|
|
239
|
+
// Never notify when the session policy auto-rejects: nothing waits for
|
|
240
|
+
// the user, so a toast would be noise.
|
|
241
|
+
if (effectivePolicy(session) === "never") return;
|
|
242
|
+
const data = event.data ?? {};
|
|
243
|
+
const body = typeof data.toolName === "string"
|
|
244
|
+
? (typeof data.reason === "string" && data.reason.length > 0
|
|
245
|
+
? "工具 " + data.toolName + ":" + truncate(data.reason, 100)
|
|
246
|
+
: "工具 " + data.toolName)
|
|
247
|
+
: "有一项操作等待你的批准";
|
|
248
|
+
sendToast(cfg.appName + " · 需要审批", body);
|
|
249
|
+
log({ event: "approval", sessionId: session?.id, id: data.id, toolName: data.toolName, reason: data.reason });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (event.type === "tool/call" && cfg.notifyOnAskUser) {
|
|
254
|
+
const data = event.data ?? {};
|
|
255
|
+
let question;
|
|
256
|
+
if (data.name === "ask_user_question") {
|
|
257
|
+
question = extractAskQuestion(data.arguments);
|
|
258
|
+
} else if (data.name === "run_code" && typeof data.arguments === "string" && data.arguments.includes("ask_user_question")) {
|
|
259
|
+
question = extractQuestionFromCode(data.arguments);
|
|
260
|
+
}
|
|
261
|
+
if (question !== undefined || data.name === "ask_user_question") {
|
|
262
|
+
sendToast(cfg.appName + " · 需要回答", question ? truncate(question, 100) : "Agent 正在等待你的确认");
|
|
263
|
+
log({ event: "ask-user", sessionId: session?.id, question: question ?? null });
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
log({ event: "start", version: VERSION, enabled: cfg.enabled, reasons: [...reasons], sessionId: null });
|
|
270
|
+
if (cfg.enabled && cfg.notifyOnStart) {
|
|
271
|
+
sendToast(cfg.appName, "任务完成提醒已激活(v" + VERSION + ")");
|
|
272
|
+
}
|
|
273
|
+
}
|
package/lib/notify.ps1
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
# dsh-notify toast sender (Windows PowerShell 5.1 required: WinRT projection).
|
|
2
|
-
# Parameters are bound by PowerShell from the process argument vector, so this
|
|
3
|
-
# file stays ASCII-only and needs no BOM.
|
|
4
|
-
param(
|
|
5
|
-
[Parameter(Mandatory = $true)][string]$Title,
|
|
6
|
-
[Parameter(Mandatory = $true)][string]$Body,
|
|
7
|
-
[string]$Aumid = "DeepSeekHarness.Notify",
|
|
8
|
-
[string]$AppName = "DeepSeek Harness"
|
|
9
|
-
)
|
|
10
|
-
$ErrorActionPreference = "Stop"
|
|
11
|
-
|
|
12
|
-
# Register the AppUserModelId once under HKCU so Win32 toasts are allowed.
|
|
13
|
-
# Best effort: if the write is denied we still try to show the toast below.
|
|
14
|
-
$key = "HKCU:\SOFTWARE\Classes\AppUserModelId\$Aumid"
|
|
15
|
-
try {
|
|
16
|
-
if (-not (Test-Path $key)) {
|
|
17
|
-
New-Item -Path $key -Force | Out-Null
|
|
18
|
-
New-ItemProperty -Path $key -Name DisplayName -Value $AppName -PropertyType String -Force | Out-Null
|
|
19
|
-
}
|
|
20
|
-
} catch {
|
|
21
|
-
Write-Warning ("AUMID registration failed: " + $_.Exception.Message)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
# Project the WinRT toast API (supported natively by .NET Framework).
|
|
25
|
-
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
26
|
-
|
|
27
|
-
$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
|
28
|
-
$texts = $xml.GetElementsByTagName("text")
|
|
29
|
-
$texts.Item(0).AppendChild($xml.CreateTextNode($Title)) | Out-Null
|
|
30
|
-
$texts.Item(1).AppendChild($xml.CreateTextNode($Body)) | Out-Null
|
|
31
|
-
|
|
32
|
-
$toast = New-Object Windows.UI.Notifications.ToastNotification($xml)
|
|
33
|
-
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($Aumid)
|
|
34
|
-
$notifier.Show($toast)
|
|
35
|
-
Write-Output "toast shown"
|
|
1
|
+
# dsh-notify toast sender (Windows PowerShell 5.1 required: WinRT projection).
|
|
2
|
+
# Parameters are bound by PowerShell from the process argument vector, so this
|
|
3
|
+
# file stays ASCII-only and needs no BOM.
|
|
4
|
+
param(
|
|
5
|
+
[Parameter(Mandatory = $true)][string]$Title,
|
|
6
|
+
[Parameter(Mandatory = $true)][string]$Body,
|
|
7
|
+
[string]$Aumid = "DeepSeekHarness.Notify",
|
|
8
|
+
[string]$AppName = "DeepSeek Harness"
|
|
9
|
+
)
|
|
10
|
+
$ErrorActionPreference = "Stop"
|
|
11
|
+
|
|
12
|
+
# Register the AppUserModelId once under HKCU so Win32 toasts are allowed.
|
|
13
|
+
# Best effort: if the write is denied we still try to show the toast below.
|
|
14
|
+
$key = "HKCU:\SOFTWARE\Classes\AppUserModelId\$Aumid"
|
|
15
|
+
try {
|
|
16
|
+
if (-not (Test-Path $key)) {
|
|
17
|
+
New-Item -Path $key -Force | Out-Null
|
|
18
|
+
New-ItemProperty -Path $key -Name DisplayName -Value $AppName -PropertyType String -Force | Out-Null
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
Write-Warning ("AUMID registration failed: " + $_.Exception.Message)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Project the WinRT toast API (supported natively by .NET Framework).
|
|
25
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
26
|
+
|
|
27
|
+
$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
|
28
|
+
$texts = $xml.GetElementsByTagName("text")
|
|
29
|
+
$texts.Item(0).AppendChild($xml.CreateTextNode($Title)) | Out-Null
|
|
30
|
+
$texts.Item(1).AppendChild($xml.CreateTextNode($Body)) | Out-Null
|
|
31
|
+
|
|
32
|
+
$toast = New-Object Windows.UI.Notifications.ToastNotification($xml)
|
|
33
|
+
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($Aumid)
|
|
34
|
+
$notifier.Show($toast)
|
|
35
|
+
Write-Output "toast shown"
|
package/package.json
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-notify-windows",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "DeepSeek Harness (DSH) 插件:任务完成 / 等待审批 / 等待回答时发送 Windows 系统桌面通知",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/SeverusZh/dsh-notify-windows.git"
|
|
8
8
|
},
|
|
9
|
+
"homepage": "https://github.com/SeverusZh/dsh-notify-windows",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/SeverusZh/dsh-notify-windows/issues"
|
|
12
|
+
},
|
|
9
13
|
"type": "module",
|
|
10
14
|
"main": "lib/index.js",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": "./lib/index.js"
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
|
-
"lib"
|
|
19
|
+
"lib",
|
|
20
|
+
"cordis.patch.yml",
|
|
21
|
+
"CHANGELOG.md"
|
|
16
22
|
],
|
|
17
23
|
"license": "MIT",
|
|
18
24
|
"engines": {
|
|
19
25
|
"node": ">=20"
|
|
20
26
|
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
29
|
+
},
|
|
30
|
+
"dsh": {
|
|
31
|
+
"bundle": {
|
|
32
|
+
"patch": "./cordis.patch.yml"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
21
35
|
"keywords": [
|
|
22
36
|
"deepseek-harness",
|
|
23
37
|
"dsh",
|
|
@@ -26,4 +40,4 @@
|
|
|
26
40
|
"toast",
|
|
27
41
|
"windows"
|
|
28
42
|
]
|
|
29
|
-
}
|
|
43
|
+
}
|