@game25011/dsh-notify-toast 0.0.0-stage → 0.4.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/AI-DISCLOSURE.md +29 -0
- package/CHANGELOG.md +87 -0
- package/LICENSE +22 -0
- package/README.en.md +290 -0
- package/README.md +308 -3
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/assets/sounds/bubble.ogg +0 -0
- package/cordis.patch.yml +5 -0
- package/docs/INTERFACE.md +180 -0
- package/docs/RELEASING.md +282 -0
- package/docs/TEST-REPORT.md +183 -0
- package/docs/assets/native-toast.png +0 -0
- package/dsh-plugin.json +14 -0
- package/icon.svg +17 -0
- package/lib/all-done.js +126 -0
- package/lib/client.js +3486 -0
- package/lib/config.js +420 -0
- package/lib/goal-summary.js +438 -0
- package/lib/index.js +1411 -0
- package/lib/native-focus.js +350 -0
- package/lib/native-toast-helper.cs +206 -0
- package/lib/native-toast.js +372 -0
- package/lib/native-toast.ps1 +72 -0
- package/lib/taskbar-badge-helper.cs +126 -0
- package/lib/taskbar-badge-main.cs +43 -0
- package/lib/taskbar-badge.js +347 -0
- package/lib/taskbar-badge.ps1 +59 -0
- package/lib/text.js +174 -0
- package/locale/en.json +17 -0
- package/locale/zh.json +17 -0
- package/package.json +76 -6
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# dsh-notify-toast 发布手册
|
|
2
|
+
|
|
3
|
+
> 适用版本:`0.3.0`(首个公开版本)。面向维护者,所有命令默认在仓库根目录执行。
|
|
4
|
+
> 本项目的 CI 只做检查:**不会自动发布**。打 tag、GitHub Release、`npm publish` 都是人工步骤。
|
|
5
|
+
|
|
6
|
+
## 0. 发布边界
|
|
7
|
+
|
|
8
|
+
- `.github/workflows/ci.yml` 只在 `push`(main)、`pull_request`、`workflow_dispatch` 时运行测试与 `npm pack --dry-run`;没有 publish/release/deploy job,也没有任何 secrets。
|
|
9
|
+
- 本插件零运行时依赖,发布流程不需要 `npm install` / `npm ci`,也不需要联网安装工具(npm 登录和发布除外)。
|
|
10
|
+
- 发布前先读完本手册的「发布前自检」;所有占位符见最后一节。
|
|
11
|
+
|
|
12
|
+
## 1. 前置条件
|
|
13
|
+
|
|
14
|
+
| 项 | 要求 | 检查命令 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Node.js | `>= 20`(`engines.node` 的运行时下限);测试与 CI 只在 22 / 24 上验证过,发布验证使用 24.19.0,建议用 22+ 执行本手册 | `node --version` |
|
|
17
|
+
| npm | 随 Node 安装(发布验证使用 11.17.0) | `npm --version` |
|
|
18
|
+
| npm 账号 | 已注册,并已开启 2FA(Two-Factor Authentication) | `npm whoami --registry https://registry.npmjs.org` |
|
|
19
|
+
| git | `>= 2.28`(需要 `git init -b main`) | `git --version` |
|
|
20
|
+
| GitHub | 对 `<github-owner>` 有建仓 / 发布 Release 权限 | - |
|
|
21
|
+
| gh CLI(可选) | 用于建仓和创建 Release | `gh auth status` |
|
|
22
|
+
|
|
23
|
+
### 1.1 registry:本机默认不是官方源
|
|
24
|
+
|
|
25
|
+
本机 npm 默认 registry 可能是只读镜像(实测为 `https://registry.npmmirror.com`),**不能直接用来发布**。发布相关命令必须显式指定官方源:
|
|
26
|
+
|
|
27
|
+
npm config get registry
|
|
28
|
+
npm login --registry https://registry.npmjs.org
|
|
29
|
+
npm whoami --registry https://registry.npmjs.org
|
|
30
|
+
npm publish --access public --registry https://registry.npmjs.org
|
|
31
|
+
npm view dsh-notify-toast version --registry https://registry.npmjs.org
|
|
32
|
+
|
|
33
|
+
不建议依赖全局 `npm config set registry ...` 的临时切换;如果切换过,发布后要改回并再次确认。所有发布命令带上 `--registry https://registry.npmjs.org` 最稳妥。
|
|
34
|
+
|
|
35
|
+
### 1.2 2FA、OTP 与密钥
|
|
36
|
+
|
|
37
|
+
- 推荐交互式 `npm login`,publish 时由 npm 提示输入一次性验证码(OTP)。
|
|
38
|
+
- 如使用 `npm publish --otp <code>`,OTP 会留在 shell 历史;发布后自行清理历史,不要把 OTP 写进任何文件。
|
|
39
|
+
- 仓库、Release 附件、CI 配置中不得出现 npm token、GitHub token、证书或私钥。本项目的 CI 不读取 secrets。
|
|
40
|
+
|
|
41
|
+
## 2. 发布前自检
|
|
42
|
+
|
|
43
|
+
在**源码仓库根目录**执行(不要在 tgz 解包目录里跑:tgz 按 `files` 白名单打包,不包含 `test/` 和 `scripts/`):
|
|
44
|
+
|
|
45
|
+
node --version
|
|
46
|
+
npm config get registry
|
|
47
|
+
npm test
|
|
48
|
+
npm run smoke
|
|
49
|
+
node scripts/probe-rpc.mjs --self-check
|
|
50
|
+
npm pack --dry-run
|
|
51
|
+
|
|
52
|
+
0.3.0 在 Node 24(实测 24.19.0 / npm 11.17.0)的预期:
|
|
53
|
+
|
|
54
|
+
| 命令 | 预期输出 / 结果 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `npm test` | 94 项;0 failed、退出码 0(本机 profile 下 94 passed;无 profile 时个别 profile 相关测试会 skip) |
|
|
57
|
+
| `npm run smoke` | `client smoke test: 25 passed, 0 failed` |
|
|
58
|
+
| `node scripts/probe-rpc.mjs --self-check` | `7/7 checks passed` |
|
|
59
|
+
| `npm pack --dry-run` | `entryCount: 27`,共 27 个文件(含原生 helper、Bubble 音效与 README 截图;以实际输出为准,见 2.2) |
|
|
60
|
+
|
|
61
|
+
- `npm test` 必须使用 `package.json` 里的命令(`node --test "test/*.test.mjs"`)。**不要**在 Node 24 Windows 上把它改成 `node --test test/`:实测会把目录当模块加载并报 `Cannot find module ...\test`(退出码 1)。CI 同样固定使用 `npm test`。
|
|
62
|
+
- 2 项 skip 是预期的:`test/config.test.mjs` 里两项「真实 profile schemastery」测试在找不到 DSH profile 时会自动跳过(GitHub Actions 上没有 DSH);退出码仍为 0。
|
|
63
|
+
- `npm pack --dry-run` 只预览,不写 tgz、不发布。
|
|
64
|
+
|
|
65
|
+
### 2.1 干净临时目录复验(推荐)
|
|
66
|
+
|
|
67
|
+
用于确认测试不依赖工作区残留。零依赖、无需 `npm install`。任选 Git Bash / WSL / macOS / Linux 执行:
|
|
68
|
+
|
|
69
|
+
rm -rf /tmp/dsh-notify-toast-preflight
|
|
70
|
+
mkdir -p /tmp/dsh-notify-toast-preflight
|
|
71
|
+
tar -czf /tmp/dsh-notify-toast-preflight.tar.gz --exclude='./.git' --exclude='./release' --exclude='*.tgz' .
|
|
72
|
+
tar -xzf /tmp/dsh-notify-toast-preflight.tar.gz -C /tmp/dsh-notify-toast-preflight
|
|
73
|
+
cd /tmp/dsh-notify-toast-preflight
|
|
74
|
+
npm test
|
|
75
|
+
npm run smoke
|
|
76
|
+
node scripts/probe-rpc.mjs --self-check
|
|
77
|
+
|
|
78
|
+
Windows 建议在 Git Bash 中执行;`rm -rf` 前先确认目标就是上述临时目录。注意:临时目录本身不会改变测试结果;结果取决于是否存在 DSH profile——本机 profile 下 94 passed;无 profile 时个别 profile 相关测试会 skip,0 failed。
|
|
79
|
+
|
|
80
|
+
### 2.2 `npm pack` 文件清单(0.3.0 实测 27 个)
|
|
81
|
+
|
|
82
|
+
8 lib/*.js(all-done.js、client.js、config.js、goal-summary.js、index.js、native-focus.js、native-toast.js、text.js)
|
|
83
|
+
2 lib/native-toast.ps1、lib/native-toast-helper.cs
|
|
84
|
+
2 locale/*.json(en.json、zh.json)
|
|
85
|
+
3 docs/*.md(INTERFACE.md、TEST-REPORT.md、RELEASING.md)
|
|
86
|
+
1 docs/assets/native-toast.png(README 截图)
|
|
87
|
+
9 根/资源文件(files 白名单):cordis.patch.yml、dsh-plugin.json、icon.svg、README.md、AI-DISCLOSURE.md、LICENSE、CHANGELOG.md、assets/sounds/bubble.ogg、THIRD_PARTY_NOTICES.md
|
|
88
|
+
1 package.json
|
|
89
|
+
1 README.en.md(npm 始终收录 README*,即使不在 files 白名单)
|
|
90
|
+
--
|
|
91
|
+
27
|
|
92
|
+
|
|
93
|
+
说明:`files` 白名单与 `npm pack` 实际输出以命令结果为准;0.3.0 实测为 27 个(含 native-toast helper、goal-summary、Bubble 音效、第三方声明、AI 参与说明与 README 截图)。若未来增删 `docs/`、`lib/` 或 README* 文件,以 `npm pack --dry-run` 的实际输出为准并回填本节与 7.2 / 7.4。
|
|
94
|
+
|
|
95
|
+
## 3. 本地 git init 与首次 commit
|
|
96
|
+
|
|
97
|
+
git init -b main
|
|
98
|
+
git add --all
|
|
99
|
+
git status --short
|
|
100
|
+
git config --local user.name "<github-owner>"
|
|
101
|
+
git config --local user.email "<maintainer-email>"
|
|
102
|
+
git commit -m "chore: initial public release of dsh-notify-toast 0.3.0"
|
|
103
|
+
|
|
104
|
+
- 首次 commit 前用 `git status --short` 确认没有把 `*.tgz`、`release/`、`node_modules/`、日志、`.npmrc` 或截图原图加入暂存区(仓库的 `.gitignore` 已排除常见构建产物)。
|
|
105
|
+
- **git 邮箱会公开**:commit 的 author/committer 邮箱会出现在 GitHub 历史中。建议使用 GitHub noreply 邮箱(形如 `12345678+<github-owner>@users.noreply.github.com`,其中数字是 GitHub 用户 ID),或使用愿意公开的维护者邮箱;不要把私人邮箱写进仓库文件。
|
|
106
|
+
- 如果本机已有全局 git 身份,可只用 `--local` 覆盖本仓库。
|
|
107
|
+
- 发布 tag 必须指向实际发布的 commit;commit 后不要再无意义地改写历史。
|
|
108
|
+
|
|
109
|
+
## 4. 创建 GitHub 仓库并 push
|
|
110
|
+
|
|
111
|
+
先把 `<github-owner>`、`<repo>` 替换成真实值,并确认仓库名与 npm 包名策略一致。
|
|
112
|
+
|
|
113
|
+
方式 A:GitHub CLI(可选)
|
|
114
|
+
|
|
115
|
+
gh auth status
|
|
116
|
+
gh repo create <github-owner>/<repo> --public --source=. --remote=origin --push
|
|
117
|
+
|
|
118
|
+
方式 B:网页建仓
|
|
119
|
+
|
|
120
|
+
1. 打开 `https://github.com/new`,创建空仓库 `<repo>`;不要勾选自动生成 README / LICENSE / .gitignore。
|
|
121
|
+
2. 关联并推送:
|
|
122
|
+
|
|
123
|
+
git remote add origin https://github.com/<github-owner>/<repo>.git
|
|
124
|
+
git push -u origin main
|
|
125
|
+
|
|
126
|
+
- 认证使用本机凭据管理器、`gh auth login` 或 GitHub PAT;不要把 token 写进命令历史、`.git/config` 或仓库文件。
|
|
127
|
+
- push 后确认默认分支为 `main`,并确认 CI 已在 push 上运行(仓库 Settings → Actions 需允许工作流)。
|
|
128
|
+
|
|
129
|
+
## 5. 打 tag v0.3.0
|
|
130
|
+
|
|
131
|
+
tag 指向已验证的发布 commit:
|
|
132
|
+
|
|
133
|
+
git tag -a v0.3.0 -m "dsh-notify-toast 0.3.0"
|
|
134
|
+
git show --stat v0.3.0
|
|
135
|
+
git push origin v0.3.0
|
|
136
|
+
|
|
137
|
+
也可以推送全部本地 tag(会推送所有 tag,执行前先 `git tag -l` 检查):
|
|
138
|
+
|
|
139
|
+
git push --tags
|
|
140
|
+
|
|
141
|
+
核对远端:
|
|
142
|
+
|
|
143
|
+
git ls-remote --tags origin
|
|
144
|
+
|
|
145
|
+
## 6. 创建 GitHub Release(不会自动发布到 npm)
|
|
146
|
+
|
|
147
|
+
1. 打开仓库 → Releases → Draft a new release。
|
|
148
|
+
2. Tag 选择 `v0.3.0`,标题为 `dsh-notify-toast 0.3.0`。
|
|
149
|
+
3. 发布说明从 `CHANGELOG.md` 的 `0.3.0 — 2026-10-18` 段落生成;把「未发布(0.3.0 发布前修正)」中的用户可见项并入 0.3.0 说明,不要保留「未发布」措辞。以 CHANGELOG 为唯一来源,不改代码。
|
|
150
|
+
4. 可选附件:在仓库根目录执行 `npm pack`,把生成的 `dsh-notify-toast-0.3.0.tgz` 上传到 Release。0.3.0 的 DSH 插件管理器安装仍以 tgz / 本地目录 link 为主,附上 tgz 便于用户直接安装。
|
|
151
|
+
5. 点击 Publish release。
|
|
152
|
+
|
|
153
|
+
- GitHub Release 只是版本节点和附件,**不会触发 npm 发布**;npm 仍需按第 7 节手动 publish。
|
|
154
|
+
- 可选 `gh` 命令(先把发布说明保存为 `release-notes.md`):
|
|
155
|
+
|
|
156
|
+
gh release create v0.3.0 dsh-notify-toast-0.3.0.tgz --title "dsh-notify-toast 0.3.0" --notes-file release-notes.md
|
|
157
|
+
|
|
158
|
+
- 不要为了「自动发布」给 CI 增加 token 或 publish 步骤;本项目的发布是人工、可审计的。
|
|
159
|
+
|
|
160
|
+
## 7. 发布到 npm(人工)
|
|
161
|
+
|
|
162
|
+
### 7.1 发布前最后确认
|
|
163
|
+
|
|
164
|
+
npm view dsh-notify-toast version --registry https://registry.npmjs.org
|
|
165
|
+
npm whoami --registry https://registry.npmjs.org
|
|
166
|
+
|
|
167
|
+
未发布时 `npm view` 返回 404(预期);如果返回了版本号,说明包名已被占用或该版本已发布,先停下来,见 9.3。
|
|
168
|
+
|
|
169
|
+
### 7.2 登录与发布
|
|
170
|
+
|
|
171
|
+
npm login --registry https://registry.npmjs.org
|
|
172
|
+
npm publish --access public --registry https://registry.npmjs.org
|
|
173
|
+
|
|
174
|
+
- 本机默认 registry 是镜像时,**不带 `--registry` 的 `npm publish` 不会发到 npm 官方源**;必须显式指定。
|
|
175
|
+
- `--access public`:本包是 unscoped 包,该参数用于显式声明公开;不要使用 `--access restricted`。
|
|
176
|
+
- 开启 2FA 的账号会在 publish 时提示 OTP;也可以 `--otp <code>`,但不要把 OTP 写进仓库或文档。
|
|
177
|
+
- 在仓库根目录执行;npm 按 `files` 白名单打包,并会自动收录 README*,当前共 27 个文件。项目没有 `prepublishOnly` / `prepare` 等生命周期脚本,不会额外执行命令。
|
|
178
|
+
- 如果 npm 返回 403 / 404 表示包名不可用,不要反复重试;按 9.3 处理。
|
|
179
|
+
|
|
180
|
+
### 7.3 发布后立即校验
|
|
181
|
+
|
|
182
|
+
npm view dsh-notify-toast version --registry https://registry.npmjs.org
|
|
183
|
+
npm view dsh-notify-toast dist.integrity dist.tarball --registry https://registry.npmjs.org
|
|
184
|
+
|
|
185
|
+
`version` 应为 `0.3.0`。
|
|
186
|
+
|
|
187
|
+
### 7.4 从 registry 重新下载并复验
|
|
188
|
+
|
|
189
|
+
rm -rf /tmp/dsh-notify-toast-registry-check
|
|
190
|
+
mkdir -p /tmp/dsh-notify-toast-registry-check
|
|
191
|
+
cd /tmp/dsh-notify-toast-registry-check
|
|
192
|
+
npm pack dsh-notify-toast@0.3.0 --registry https://registry.npmjs.org
|
|
193
|
+
tar -xzf dsh-notify-toast-0.3.0.tgz
|
|
194
|
+
node -e "const p=require('./package/package.json'); console.log(p.name, p.version, p.license, p.type)"
|
|
195
|
+
node --check package/lib/index.js
|
|
196
|
+
node --check package/lib/client.js
|
|
197
|
+
tar -tzf dsh-notify-toast-0.3.0.tgz
|
|
198
|
+
tar -tzf dsh-notify-toast-0.3.0.tgz | grep -vc '/$'
|
|
199
|
+
|
|
200
|
+
预期:`package.json` 输出 `dsh-notify-toast 0.3.0 MIT module`;最后一条命令输出 `27`(tar 列表去掉目录条目后的文件数;不包含 `test/` 与 `scripts/`)。
|
|
201
|
+
|
|
202
|
+
## 8. 发布后验证
|
|
203
|
+
|
|
204
|
+
- npm 包页面 `https://www.npmjs.com/package/dsh-notify-toast` 显示 0.3.0、README、LICENSE 与版本列表。
|
|
205
|
+
- GitHub Release `https://github.com/<github-owner>/<repo>/releases/tag/v0.3.0` 可访问,附件(可选)可下载。
|
|
206
|
+
- CI `https://github.com/<github-owner>/<repo>/actions` 最近一次运行绿色;矩阵为 windows-latest / ubuntu-latest × Node 22 / 24,共 4 个组合。
|
|
207
|
+
- DSH 插件管理器:0.3.0 仍按 tgz 交付——用 `plugin_manager` 的 `install_bundle` 指向 Release 附件 `dsh-notify-toast-0.3.0.tgz`(或本地 `npm pack` 产物);首次安装/升级后重启 DSH。**从 npm registry 直接安装的体验尚未在 0.3.0 上验证,不要写入安装文档宣称已验证。**
|
|
208
|
+
- 回填文档:README、README.en.md、SECURITY 已写入本仓库真实地址 `https://github.com/game25011/dsh-notify-toast`;Code of Conduct 使用 SECURITY.md 的私密渠道。发布前只需确认 README/README.en.md 的截图已补,且本手册示例中的 `<github-owner>` / `<repo>` / `<maintainer-email>` 已按实际替换;如果这些改动影响已发布的 tgz,按 9.1 发 patch,而不是改已发布的 0.3.0。
|
|
209
|
+
|
|
210
|
+
## 9. 回滚与撤销
|
|
211
|
+
|
|
212
|
+
### 9.1 npm:优先 deprecate + patch,不要轻易 unpublish
|
|
213
|
+
|
|
214
|
+
npm 的撤销政策要点(以 npm 官方文档为准):
|
|
215
|
+
|
|
216
|
+
- 发布后 **72 小时内**可申请整包撤销(`npm unpublish <package> --force`),限制很严;
|
|
217
|
+
- 可单独撤销某个版本(`npm unpublish <package>@<version>`),但**同一版本号通常不能原样重发**;
|
|
218
|
+
- 一旦有其他包依赖该版本,不能删除;
|
|
219
|
+
- 超过 72 小时后一般不再允许整包删除,只有安全/恶意内容等特殊情况可联系 npm support。
|
|
220
|
+
|
|
221
|
+
推荐流程(内容有问题但包仍可用):
|
|
222
|
+
|
|
223
|
+
npm deprecate dsh-notify-toast@0.3.0 "Deprecated: use a newer release" --registry https://registry.npmjs.org
|
|
224
|
+
|
|
225
|
+
修复问题、提升版本号后发布 patch:
|
|
226
|
+
|
|
227
|
+
npm publish --access public --registry https://registry.npmjs.org
|
|
228
|
+
|
|
229
|
+
只有在刚发布且确认无人依赖时才考虑单版本撤销:
|
|
230
|
+
|
|
231
|
+
npm unpublish dsh-notify-toast@0.3.0 --registry https://registry.npmjs.org --force
|
|
232
|
+
|
|
233
|
+
整包撤销(危险,仅 72 小时内):
|
|
234
|
+
|
|
235
|
+
npm unpublish dsh-notify-toast --force --registry https://registry.npmjs.org
|
|
236
|
+
|
|
237
|
+
- 撤销/弃用不会影响本地 tgz 与 GitHub Release;需要同步更新 Release 说明和文档。
|
|
238
|
+
- `npm deprecate` 后可用 `npm view dsh-notify-toast deprecated --registry https://registry.npmjs.org` 核对弃用说明。
|
|
239
|
+
|
|
240
|
+
### 9.2 git tag 与 GitHub Release
|
|
241
|
+
|
|
242
|
+
删除并重打**尚未公开**的 tag:
|
|
243
|
+
|
|
244
|
+
git tag -d v0.3.0
|
|
245
|
+
git push origin :refs/tags/v0.3.0
|
|
246
|
+
git tag -a v0.3.0 -m "dsh-notify-toast 0.3.0"
|
|
247
|
+
git push origin v0.3.0
|
|
248
|
+
|
|
249
|
+
- **已经公开或被引用的 tag 不要 force push 重写**;正确做法是发布新版本、新 tag。tag 一旦被用户下载,删除会让历史不可复现。
|
|
250
|
+
- GitHub Release 撤回:编辑 Release → 取消 Publish 或删除。删除 Release 默认不会删除 git tag(tag 需按上面的命令单独删)。
|
|
251
|
+
- 如果 npm 版本已发布,GitHub tag、Release 与 npm 版本号要保持对应;撤销 npm 版本后不要复用 `0.3.0` 版本号。
|
|
252
|
+
|
|
253
|
+
### 9.3 包名被占用时的备选
|
|
254
|
+
|
|
255
|
+
如果 `dsh-notify-toast` 已被他人占用(`npm view` 返回了版本),不要抢注、覆盖或发布到镜像:
|
|
256
|
+
|
|
257
|
+
1. 首选改为 scope 包:`@<github-owner>/dsh-notify-toast`;
|
|
258
|
+
2. 需要同步修改 `package.json` 的 `name` 以及代码、文档、DSH 安装说明中的包名——**本手册不负责改代码**,需另开变更并完整回归;
|
|
259
|
+
3. scope 包发布同样使用 `npm publish --access public --registry https://registry.npmjs.org`(scope 包默认 restricted,必须显式 public);
|
|
260
|
+
4. 改名前先跑 `npm test`、`npm run smoke`、`npm pack --dry-run`,并确认 DSH 插件管理器能安装 scope 包;
|
|
261
|
+
5. 不要在同一个 tag 上改名后继续复用;重新打 tag、更新 Release。
|
|
262
|
+
|
|
263
|
+
## 10. 发布前必须替换的占位符
|
|
264
|
+
|
|
265
|
+
| 占位符 | 出现位置(需人工替换) | 替换为 |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `<github-owner>` | 本手册的 GitHub URL / `gh repo create` / `git remote` 示例 | GitHub 用户名或组织名 |
|
|
268
|
+
| `<repo>` | 本手册的 GitHub URL / `gh repo create` 示例 | 仓库名(建议 `dsh-notify-toast`) |
|
|
269
|
+
| `<maintainer-email>` | 本手册的 git 身份示例 | 公开维护者邮箱,建议 GitHub noreply 邮箱 |
|
|
270
|
+
| `<github-user-id>` | 采用 noreply 邮箱时的 `12345678+<github-owner>@users.noreply.github.com` 前缀 | GitHub 数字用户 ID |
|
|
271
|
+
| 截图 | README / README.en.md 的截图待补说明 | 一张真实 Windows 右下角 toast 截图(PNG,不伪造) |
|
|
272
|
+
|
|
273
|
+
> 本仓库实际地址为 `https://github.com/game25011/dsh-notify-toast`,已写入 `package.json`、`README.md`、`README.en.md` 与 `SECURITY.md`;上表中的占位符只保留在本手册的命令示例里,首次执行时按实际替换即可。
|
|
274
|
+
|
|
275
|
+
发布前检查仓库中是否还有未替换的占位符和本机信息:
|
|
276
|
+
|
|
277
|
+
git grep -n "<github-owner>"
|
|
278
|
+
git grep -n "<repo>"
|
|
279
|
+
git grep -n "<maintainer-email>"
|
|
280
|
+
git grep -n -E "(^|[^A-Za-z])[A-Za-z]:[\\\\/]|/(Users|home)/|sk-|npm_[A-Za-z0-9]{20,}"
|
|
281
|
+
|
|
282
|
+
命中项逐条确认:文档示例中刻意保留的通用路径可以保留;真实本机绝对路径、个人邮箱、token/密钥必须删除。本手册自身包含上述检查模式,`git grep` 命中本文件属于预期。
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# dsh-notify-toast 0.1.0 — 测试与验收报告(历史)
|
|
2
|
+
|
|
3
|
+
> 0.3.0 注:本文件是 0.1.0 的历史报告,不应作为当前验收结论。0.3.0 当前自动测试为 `npm test` 94/94、client smoke 25/25(goal 汇总与原生生命周期),并已完成原生进度 toast、点击回调和真实 DSH 点击回会话验证;详见 README、CHANGELOG 与 0.3.0 维护记录。
|
|
4
|
+
|
|
5
|
+
> 环境:Windows 10/11 · DeepSeek Harness Desktop `0.2.1-rc.2`(Electron 壳)· profile `desktop` · 页面 origin `dsh-app://app`
|
|
6
|
+
> 记录时间:2026-10-18(Asia/Shanghai)
|
|
7
|
+
> 结论:见文末「验收结论」。
|
|
8
|
+
|
|
9
|
+
## 0. 验证手段与边界
|
|
10
|
+
|
|
11
|
+
| 层 | 手段 | 能证明什么 | 不能证明什么 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| 文本/配置/合并/路由 | `npm test`(5 个测试文件,68 项) | 清洗、截断、根会话判定、合并、配置校验、RPC 信封/栅栏/断开清理 | 真实事件负载到达 UI |
|
|
14
|
+
| Client 加载/行为 | `npm run smoke`(23 项,Node `vm` 模拟 `__ModuleLoader__`/Notification/AudioContext) | factory/apply、双语字典、toast/声音/点击/焦点/设置保存/降级/清理 | 真实浏览器里 React 渲染外观 |
|
|
15
|
+
| Host 集成 | `npm run probe`(假 Cordis ctx + 真实 profile cwd) | schemastery 解析、回退路由注册、官方 RPC 信封、settings 读写、环形缓冲、清理 | 真实 host 进程内的完整事件流 |
|
|
16
|
+
| 插件安装/激活 | 官方 `plugin_manager` + Host `Config.listConfigs` | bundle 安装、Host 行激活、Config schema 注册 | toast 是否真的弹出 |
|
|
17
|
+
| 浏览器路由 | `curl http://127.0.0.1:<port>/dsh-notify-toast/health` | 路由已挂载、`connection.requestRejection` 栅栏生效(401) | 带签名的页面请求是否成功 |
|
|
18
|
+
| 设置页注册 | Client Inspect `Slots.listSubTree settings.section` | 客户端 bundle 已加载、设置页 occupant 出现(id/order 正确) | 页面外观/交互手感 |
|
|
19
|
+
| 端到端通知 | 用户前台试用(聚焦/失焦/提问/审批/完成/点击/声音) | 真实 Windows toast、声音、点击回窗 | 本机无 GUI 眼动/通知中心读取能力 |
|
|
20
|
+
|
|
21
|
+
> 说明:本机为无 GUI 眼动的自动化环境,无法替用户点击界面或读取 Windows 通知中心;端到端 toast 由第 5 节手工验收清单覆盖,并已在第 4 节记录所有可自动观察的环境证据。
|
|
22
|
+
|
|
23
|
+
## 1. 单元测试(68/68 通过)
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
npm test
|
|
27
|
+
test/text.test.mjs 19 passed / 0 failed
|
|
28
|
+
test/config.test.mjs 17 passed / 0 failed (含真实 profile schemastery 的 schema 校验)
|
|
29
|
+
test/merge.test.mjs 11 passed / 0 failed
|
|
30
|
+
test/rpc-route.test.mjs 12 passed / 0 failed (信封/栅栏/缺失 requestRejection 的 fail-closed/405/404/415/400/413/业务失败/断开 Abort)
|
|
31
|
+
test/all-done.test.mjs 9 passed / 0 failed (单会话不触发、多会话 arm、最后会话改名、非 completed 不触发、开关/标题配置、dispose)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
覆盖:Markdown/代码围栏清洗、首段提取、码点截断与省略号、默认值与范围校验、volatile Config schema、
|
|
35
|
+
完成合并窗口(count/最新正文/去重/dispose 清计时器)、RPC 路径解析、官方 server-response 信封、
|
|
36
|
+
`requestRejection` 栅栏透传、长轮询客户端断开时 AbortSignal 生效。
|
|
37
|
+
|
|
38
|
+
## 2. Client 冒烟(23/23 通过)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
npm run smoke
|
|
42
|
+
client smoke test: 23 passed, 0 failed
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
覆盖:factory/inject/apply;zh+en 字典一致;正文长度/隐私/审批/完成回退;焦点计算;
|
|
46
|
+
`settings.section` 注册(id `dsh-notify-toast`、order 42、label、locale、inject);config/poll 载荷
|
|
47
|
+
`{after:0,wait:25000}`;首次手势请求通知权限 + 解锁音频(仅一次);未聚焦时 `silent:true` toast + 风铃振荡器;
|
|
48
|
+
审批正文「工具名 · 目的」+ 点击后 focus RPC/window.focus/openSession;聚焦时完全跳过;开关与防抖保存;
|
|
49
|
+
Host 业务拒绝显示保存失败;dispose(abort、关闭 AudioContext、释放 leader 租约、移除监听、无未处理 rejection);
|
|
50
|
+
重复加载/重复 apply 与畸形 RPC 响应不崩溃。
|
|
51
|
+
|
|
52
|
+
## 3. 安装与激活(真实设备)
|
|
53
|
+
|
|
54
|
+
| 项 | 结果 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| 安装方式 | `plugin_manager install_bundle` → `target: <插件目录绝对路径>` |
|
|
57
|
+
| 安装结果 | `application: applied`,`enabled: true`,`warnings: []` |
|
|
58
|
+
| profile 记录 | `desktop/package.json`:`"dsh-notify-toast": "link:<plugin-dir>"`,bundles 末尾含 `dsh-notify-toast` |
|
|
59
|
+
| 实体 | `profiles/desktop/node_modules/dsh-notify-toast` 为 link;改代码 + 插件开关触发的 recomposition 即可生效 |
|
|
60
|
+
| Host 行 | `plugin_manager list_plugins`:`include:dsh-notify-toast`,`fiberPhase: active`,`enabled: true` |
|
|
61
|
+
| Config schema | `cordis_inspect_query Config.listConfigs name=dsh-notify-toast` → `status: "schema"`(schemastery 解析成功,settings 可持久化) |
|
|
62
|
+
| 设置页 | Client Inspect `settings.section` occupants 含 `{id:"dsh-notify-toast", order:42, active:true}`(客户端 bundle 已加载) |
|
|
63
|
+
| Host 代码生效 | 通过官方插件开关触发的 profile recomposition 载入最终 Host 代码(无需整机重启);路由探针由修复前的 `405` 变为 `401`(已挂载 + 栅栏生效) |
|
|
64
|
+
|
|
65
|
+
## 4. 运行时证据
|
|
66
|
+
|
|
67
|
+
运行中实例:`DeepSeek Harness.exe`(host PID `<pid>`,2026-10-18 12:25 启动),HTTP 监听 `127.0.0.1:<port>`。
|
|
68
|
+
|
|
69
|
+
Host 路由探针(不带浏览器 cookie,验证「路由已注册 + 认证栅栏生效」):
|
|
70
|
+
|
|
71
|
+
| 请求 | 结果 | 解读 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `POST /dsh-notify-toast/health`(无 Origin) | **401** | 路由存在,`requestRejection` 认证栅栏生效 |
|
|
74
|
+
| `GET /dsh-notify-toast/health` | **401** | 同上(栅栏先于方法检查) |
|
|
75
|
+
| `POST /random-xyz/health`(对照) | **405** | 未注册路由;说明 401 并非全局默认 |
|
|
76
|
+
|
|
77
|
+
- **Host 集成探针**(`npm run probe`,真实 profile cwd):
|
|
78
|
+
- `Config: resolved | source: cwd`(Host 进程 cwd = profile 目录,修复了 `DSH_PROFILE_DIR` 缺失时的 schema 解析);
|
|
79
|
+
- 回退路由 `prefix /dsh-notify-toast` 注册 1 条;`requestRejection` 拒绝 → 403;
|
|
80
|
+
- `config`:`transport: "route"`、`settingsAvailable: true`、默认配置完整;
|
|
81
|
+
- `save`:`settings.update("dsh-notify-toast", {bodyLength:55,sound:"bell"})` 被调用;越界值被业务拒绝且不写 settings;
|
|
82
|
+
- `test` + `poll`:事件入环形缓冲、`after` 游标返回、`next` 递增;
|
|
83
|
+
- `focus`:返回 `{ok:true,native:true}` 且不抛错;
|
|
84
|
+
- 卸载清理:3 个 effect disposer 全部执行。
|
|
85
|
+
- **koffi 置前**:独立 Electron-as-Node 探针(`resources/app.asar.unpacked/.../koffi.node`,koffi 3.1.1),
|
|
86
|
+
按 `process.ppid` 匹配 DSH 主窗口并执行 `ShowWindow/BringWindowToTop/ALT 轻敲/AttachThreadInput/SetForegroundWindow`,
|
|
87
|
+
`GetForegroundWindow() === target` 返回 `true`;Host 探针 `nativeFocus: true`。
|
|
88
|
+
- **页面 ↔ Host 连接**:Host 端口存在多条来自 Electron 主进程的 ESTABLISHED 连接(页面请求经 `dsh-app://app` 代理 + undici 连接池),
|
|
89
|
+
说明页面仍在持续访问 Host;长轮询的具体状态需在设置页「Host 连接」上人工确认。
|
|
90
|
+
- **诊断日志(已删除)**:修复前 `.host-debug.log` 记录过 0.2.1-rc.2 官方 `connection.rpc.handle` 抛
|
|
91
|
+
`cannot get property "webServer" without inject`;当前代码的回退分支与 401 探针结果一致。
|
|
92
|
+
|
|
93
|
+
## 5. 手工验收清单(用户试用)
|
|
94
|
+
|
|
95
|
+
打开 **设置 →「通知」**,先点一次页面(解锁音频),然后:
|
|
96
|
+
|
|
97
|
+
| # | 场景 | 期望 | 结果 |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| 1 | DSH 聚焦时触发提问/授权/完成 | 无 toast、无声音 | ✅ 用户确认(聚焦静默) |
|
|
100
|
+
| 2 | DSH 失焦时提问 | 原生 toast「DSH 想问你一个问题」+ 问题前 40 字 + 叮咚 | ✅ 用户确认(原生 toast 通道) |
|
|
101
|
+
| 3 | DSH 失焦时授权 | toast「DSH 需要你的授权」+「工具名 · 目的」 | ✅ 用户确认(原生 toast 通道) |
|
|
102
|
+
| 4 | DSH 失焦时根会话完成 | toast「DSH 完成了一项任务」+ 最后回复首段 | ✅ 用户确认(原生 toast 通道) |
|
|
103
|
+
| 5 | 设置页「发送测试通知」 | 立即出现测试 toast + 可闻提示音(测试不受聚焦限制) | ✅ 用户确认 |
|
|
104
|
+
| 6 | 正文长度 20/40/60 | 截断位置正确、超出有「…」 | ⚠️ 单元/冒烟覆盖;未逐档人工目测 |
|
|
105
|
+
| 7 | 隐私模式开启 | 只有标题、正文空 | ⚠️ 单元/冒烟覆盖;未人工目测 |
|
|
106
|
+
| 8 | 2.5 s 内两次完成 | 只出现一条合并 toast | ⚠️ 合并调度单测覆盖;未人工制造并发 |
|
|
107
|
+
| 9 | 点击 toast(含 DSH 最小化) | 从最小化恢复、置前并打开对应会话 | ✅ 用户确认(最终验收点) |
|
|
108
|
+
| 10 | 子代理完成 | 不提醒 | ⚠️ 代码按 `origin==='subagent'` 过滤;未人工构造子代理完成 |
|
|
109
|
+
| 11 | 提示音切换/音量 0/关闭 | 即时生效,0/关闭无声 | ⚠️ 冒烟覆盖;用户确认基本音效可用 |
|
|
110
|
+
| 12 | 重启 DSH | 设置页「设置持久化:官方设置」,配置保留 | ⚠️ 多次重启后插件/设置正常;未逐字段核对 |
|
|
111
|
+
| 13 | 卸载插件 | 不再提醒、设置页条目消失 | ⚠️ 按官方卸载步骤,未在本轮执行(见 README) |
|
|
112
|
+
| 14 | 通知权限拒绝 | 不崩溃、设置页显示状态 | ⚠️ 冒烟覆盖拒绝路径;未在系统层复验 |
|
|
113
|
+
|
|
114
|
+
## 6. 已知限制复核
|
|
115
|
+
|
|
116
|
+
- 页面必须打开:设置页与长轮询都依赖页面,符合设计。
|
|
117
|
+
- 首次声音需一次用户手势:client 冒烟已验证一次性解锁逻辑;真实环境需点一次页面。
|
|
118
|
+
- koffi 不可用降级 `window.focus()`:`status.nativeFocus` 已暴露给设置页。
|
|
119
|
+
- 多窗口 leader:client 冒烟覆盖租约与 storage 竞争解析。
|
|
120
|
+
- 0.2.1-rc.2 官方 `connection.rpc.handle` bug 已由同路径回退路由绕过(见 CHANGELOG)。
|
|
121
|
+
- 单条正文 ≥40 字截断、隐私模式、完成合并等规则已在单元/冒烟测试中覆盖。
|
|
122
|
+
|
|
123
|
+
## 6.1 试用反馈迭代(2026-10-18 13:xx)
|
|
124
|
+
|
|
125
|
+
用户先反馈「只有声音、几乎听不到提示音,且不喜欢默认风铃」;随后更正为**声音一直正常,真正的问题是看不到右下角弹出提示**(期望像 Codex:几秒自动消失、点击回到对应会话)。本轮改动:
|
|
126
|
+
|
|
127
|
+
- **无声根因加固**:旧实现只有 `resume()` 成功后才排程音符;当 AudioContext 因自动播放策略处于 suspended 且 `resume()` 被拒/未决时,声音会被静默丢弃。
|
|
128
|
+
现改为:**先排程、再 resume**;测试按钮点击(用户手势)先同步 `unlockAudioContext()`;首次手势用 20ms 近静音振荡器 prime;master gain 0.35→0.5 并加 DynamicsCompressor。
|
|
129
|
+
- **新增默认提示音「叮咚」(ding)**,保留 风铃/清铃/轻点/关闭;设置页提示音旁新增「试听」按钮,切音效即时试听。
|
|
130
|
+
- **新增「提示音引擎」状态行**(已就绪/待解锁/未初始化)用于诊断无声。
|
|
131
|
+
- Host Config 枚举/默认值同步更新(已通过插件开关触发重新组合;schema 实测 `sound.default = "ding"` 且枚举含 `ding`)。
|
|
132
|
+
- 回归:单元测试 68/68、Client 冒烟 23/23 全通过;RPC 路由 401 对照 405 栅栏仍生效。
|
|
133
|
+
|
|
134
|
+
## 6.2 原生 toast 诊断(2026-10-18 13:xx,第二轮反馈)
|
|
135
|
+
|
|
136
|
+
用户确认声音正常,缺的是**屏幕右下角 Windows 原生 toast 弹窗**(像 Codex 那样在 DSH 之外可见),并明确**不接受应用内弹框**(窗口内的方框在切换到其它应用时不可见,因此无用)。
|
|
137
|
+
|
|
138
|
+
- **定位**:Windows 注册表里存在 `electron.app.DeepSeek Harness` 通知条目且 `LastNotificationAddedTime` 为最近时间;全局 `ToastEnabled=1`。
|
|
139
|
+
说明 DSH/Electron 已经把通知交给 Windows(至少在通知中心层),弹不弹横幅取决于 Windows 通知横幅/专注助手等系统设置。
|
|
140
|
+
- **修复「测试系统通知」误报**:先前诊断按钮误传 3 个构造参数,Electron 将第二个字符串参数按 `NotificationOptions` 校验并抛
|
|
141
|
+
`Failed to construct 'Notification': The provided value is not of type 'NotificationOptions'`;正常通知路径使用的是 2 参数,不受影响。
|
|
142
|
+
现已改为 `new Ctor(title, { body, silent: true })`。
|
|
143
|
+
- **移除应用内兜底卡片**:按用户反馈,事件只走 Windows 原生 toast,不再在 DSH 窗口内弹卡片。
|
|
144
|
+
- **保留诊断**:设置页「系统通知」块(尝试/创建/显示/失败 + 最近错误)与「测试系统通知」按钮,便于判断是权限、构造还是系统横幅问题。
|
|
145
|
+
原生 toast 自动关闭时间 15s → **6s**;`permission==='default'` 时构造前再请求一次权限。
|
|
146
|
+
- 回归:`node --check` 通过;单元测试 68/68;Client 冒烟 23/23;Host schema 已通过插件开关重新组合为最终版(`sound.default="ding"`,无多余字段)。
|
|
147
|
+
|
|
148
|
+
## 6.3 点击回窗修复(2026-10-18 14:xx,第三轮反馈:最小化时点通知不拉起)
|
|
149
|
+
|
|
150
|
+
用户确认 toast 已正常弹出;问题变为**DSH 最小化时点击通知不会把窗口拉起来**。
|
|
151
|
+
|
|
152
|
+
- **真实窗口探测**(Electron-as-Node,对真实 DSH 窗口):
|
|
153
|
+
- Electron 主进程名下存在多个同 PID 可见顶层窗口:`Chrome_WidgetWin_1`(真正主窗口)、`OleDdeWndClass / DDE Server Window`、
|
|
154
|
+
`MSCTFIME UI`、`Default IME`。旧实现按 PID 取“第一个可见且有标题”的窗口,可能命中 DDE/IME 窗口,
|
|
155
|
+
于是 `ShowWindow/SetForegroundWindow` 打到了错误窗口,看起来就是“点了没反应”。
|
|
156
|
+
- 修正后的匹配:过滤非应用类窗口,按 `Chrome_WidgetWin*` 类(+50)与「DeepSeek Harness」标题(+100)、父 PID(+20)打分,
|
|
157
|
+
逐个候选执行 `ShowWindow(SW_RESTORE)` → `ALT 轻敲` → `AttachThreadInput` + `SetForegroundWindow` → 验证前台窗口。
|
|
158
|
+
- 探针实测(最小化后调用修正版 `focusDesktopWindow()`):`iconic=false`、`GetForegroundWindow()===目标窗口`,恢复成功;
|
|
159
|
+
候选打分输出示例:DSH 主窗口 150 分,浏览器 Chrome 窗口仅 50 分(被主窗口优先)。
|
|
160
|
+
- **点击链路**:toast 不再 6 秒后主动 `close()`(关闭会同时从通知中心移除,稍后点击就无事件);
|
|
161
|
+
点击同时挂 `onclick` 与 `addEventListener('click')`(去重),focus RPC 失败/`native=false` 自动重试 3 次;
|
|
162
|
+
设置页「系统通知」新增 `点击 / 置前成功 / 置前失败` 计数 + 最近错误,便于确认点击事件是否到达页面。
|
|
163
|
+
- **注意**:Host 侧窗口定位代码属于模块级修复,需重启 DSH 一次才能加载(客户端部分可热更新)。
|
|
164
|
+
- 回归:`node --check` 通过;单元测试 68/68;Client 冒烟 23/23;本轮不再使用应用内卡片。
|
|
165
|
+
|
|
166
|
+
## 6.4 用户最终试用(2026-10-18 15:05)
|
|
167
|
+
|
|
168
|
+
重启 DSH 加载修正后的 Host 窗口定位代码后,用户回复 **「试用成功」**:Windows 右下角原生 toast 正常弹出,
|
|
169
|
+
**DSH 最小化时点击通知可将其恢复并置前**(最终验收点通过)。
|
|
170
|
+
|
|
171
|
+
## 7. 验收结论
|
|
172
|
+
|
|
173
|
+
**验收通过** —— 自动化(单元 68/68、Client 冒烟 23/23、Host 集成探针、真实窗口 focus 探针)+ 真实设备端到端试用均通过:
|
|
174
|
+
|
|
175
|
+
- Windows 原生 toast:右下角弹出、Windows 自动收起进通知中心、点击回窗(含最小化恢复)——用户确认;
|
|
176
|
+
- 触发时机、聚焦静默、声音(叮咚默认,可试听/切换/调音量/关闭)、正文截取与合并、设置页持久化——自动化覆盖,核心行为用户确认;
|
|
177
|
+
- 安装方式:官方 `plugin_manager install_bundle`(本地目录 link),Host 行 active、Config schema 正常、RPC 路由 401/405 探针正常。
|
|
178
|
+
|
|
179
|
+
未逐项人工复验的次要项(正文长度 20/60 档位、隐私模式外观、并发完成合并、子代理完成过滤、卸载清理、权限拒绝外观)已在单元/冒烟测试中覆盖,
|
|
180
|
+
如需可在后续试用中继续确认;这些不影响核心交付。
|
|
181
|
+
|
|
182
|
+
已知限制与环境依赖见 README「已知限制」:页面需打开、Windows 通知横幅/专注助手会决定是否显示横幅、首次声音需一次用户手势、
|
|
183
|
+
点击置前依赖 DSH 自带 koffi(失败降级 `window.focus()`)、0.2.1-rc.2 官方 `connection.rpc.handle` bug 已由同源回退路由绕过。
|
|
Binary file
|
package/dsh-plugin.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "dsh.notify-toast",
|
|
3
|
+
"name": "轻量原生通知(Windows)",
|
|
4
|
+
"version": "0.4.2",
|
|
5
|
+
"manifestVersion": "0.15",
|
|
6
|
+
"facets": {
|
|
7
|
+
"host": { "entry": "lib/index.js", "apiVersion": "v1alpha1" }
|
|
8
|
+
},
|
|
9
|
+
"requires": { "contracts": [] },
|
|
10
|
+
"permissions": [],
|
|
11
|
+
"contributes": {},
|
|
12
|
+
"subscriptions": [],
|
|
13
|
+
"notes": "单插件交付 Host + Web Client:Host 监听 approval/request、user-questions/request、根会话 session/event;原生进度模式下 Host 通过 PowerShell/C#/WinRT helper 创建带进度条的 Windows 原生通知,Web Notification 作为兼容回退;声音由本机 Web Audio 合成(另含内嵌的 uisfx CC0「弹一下」音频),点击回调经 Host 恢复/置前 DSH 窗口。Host 注册同源 webServer 前缀 RPC `/dsh-notify-toast`(config/save/poll/test/focus/health),复用官方 requestRejection 栅栏与一元 RPC 信封;poll 为长轮询,事件环形缓冲在内存、默认 200 条。插件配置经官方 settings 持久化;不访问外部网络、不调用模型、不采集;原生 helper 会写本机编译缓存;会加载 DSH 自带 koffi 原生绑定并调用 user32(仅用于点击回窗),失败降级 window.focus()。client bundle 只注册 settings.section;已删除 shell.overlay 应用内卡片死代码。设置导航行图标由客户端注入(DOM/CSS mask,无联网、无新权限)。capability 声明不构成沙箱。完成通知标题按场景自动生成(单步、2–4 步、5 步以上、提问、授权、全部完成、目标暂停/受阻),不再提供自定义完成标题。目标模式 active+armed 期间不逐轮发完成通知,按 goal/change 的 roundsStarted 增量在 complete/paused/blocked/round-limit/disarmed 后汇总一条;默认开启,可关闭;提问/授权仍即时。提示音新增可选「弹一下」:uisfx 0.4.0 的 CC0 音频内嵌,默认仍是叮咚。0.4.1 修复审批通知漏发(approval waterfall 观察者改为 prepend/global 并继续 next())。0.4.0 新增任务栏未读角标:Host 用一次性 PowerShell/C# helper 调用 ITaskbarList3::SetOverlayIcon,按不同根会话计数,1–3 显示深灰底白字蓝环数字、超过 3 显示红点,窗口聚焦后清除;不增加常驻后台进程。0.3.0 使用 Host 侧 PowerShell/C#/WinRT helper 创建带动态进度条的 Windows 原生通知,自动移除时间为近似值(3–30 秒):到期停止进度更新,短暂缓冲后 Hide() 收回横幅(ExpirationTime 约束通知中心保留);helper 启动时后台预热并在 %LOCALAPPDATA%/dsh-notify-toast/native-cache 编译缓存一个小 DLL,编译/启动失败时自动退回 Web Notification。点击回调经 Host 的 127.0.0.1 loopback server 发布 activate 事件,尽量定位到对应会话;回调失败时不保证 session 定位。原生横幅实际停留时间仍由 Windows 控制。0.4.2 更新 DSH 0.2.2-rc.1 兼容声明与 engines.dsh 范围,不改变通知核心逻辑。"
|
|
14
|
+
}
|
package/icon.svg
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" role="img" aria-label="Toast notification bell">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#4d6bfe"/>
|
|
5
|
+
<stop offset="1" stop-color="#2b3fb8"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="bell" x1="0" y1="0" x2="0" y2="1">
|
|
8
|
+
<stop offset="0" stop-color="#ffffff"/>
|
|
9
|
+
<stop offset="1" stop-color="#dfe6ff"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect x="2" y="2" width="60" height="60" rx="16" fill="url(#bg)"/>
|
|
13
|
+
<path d="M32 12c-6.9 0-12.2 5.4-12.2 12.4v7.2l-3.4 5.9c-.9 1.6.3 3.5 2.1 3.5h27c1.9 0 3-1.9 2.1-3.5l-3.4-5.9v-7.2C44.2 17.4 38.9 12 32 12z" fill="url(#bell)"/>
|
|
14
|
+
<path d="M26.5 44.5h11c-.5 3.2-2.7 5.5-5.5 5.5s-5-2.3-5.5-5.5z" fill="#ffffff" opacity="0.92"/>
|
|
15
|
+
<circle cx="46.5" cy="18.5" r="6.5" fill="#ffb648"/>
|
|
16
|
+
<circle cx="46.5" cy="18.5" r="2.6" fill="#7a4300"/>
|
|
17
|
+
</svg>
|
package/lib/all-done.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-notify-toast — "all sessions finished" title marker.
|
|
3
|
+
*
|
|
4
|
+
* Tracks root session turns that are currently running. The marker only arms
|
|
5
|
+
* when at least two sessions overlap (the user asked for a multi-session
|
|
6
|
+
* workflow); when the last of them ends successfully, it renames that last
|
|
7
|
+
* session's title (default: "所有任务均已完成").
|
|
8
|
+
*
|
|
9
|
+
* Pure state machine: no timers, no I/O and no Cordis dependency, so it can be
|
|
10
|
+
* unit-tested with fake sessions. The Host wires the title service and the
|
|
11
|
+
* live config in.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** @returns {string|undefined} the session id when it is usable. */
|
|
15
|
+
function sessionId(session) {
|
|
16
|
+
const value = session && session.id
|
|
17
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @returns {string|undefined} end reason kind. */
|
|
21
|
+
function endReason(event) {
|
|
22
|
+
const data = event && event.data
|
|
23
|
+
const reason = data && data.reason
|
|
24
|
+
return reason && typeof reason.kind === 'string' ? reason.kind : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create the all-done title tracker.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} [options]
|
|
31
|
+
* @param {() => {allDoneTitleEnabled?: boolean, allDoneTitle?: string}} [options.getConfig]
|
|
32
|
+
* reads the live config; called at every observation so settings changes apply live.
|
|
33
|
+
* @param {(session: any, title: string) => unknown} [options.renameTitle]
|
|
34
|
+
* asks the Host to rename a session; returning false means "unavailable".
|
|
35
|
+
* @param {(error: unknown) => void} [options.onError] fatal-free error sink.
|
|
36
|
+
* @returns {{
|
|
37
|
+
* onTurnStart: (session: any) => void,
|
|
38
|
+
* onTurnEnd: (session: any, event: any) => void,
|
|
39
|
+
* snapshot: () => {running: number, armed: boolean, dirty: boolean},
|
|
40
|
+
* dispose: () => void,
|
|
41
|
+
* }}
|
|
42
|
+
*/
|
|
43
|
+
export function createAllDoneTitleTracker(options = {}) {
|
|
44
|
+
const getConfig = typeof options.getConfig === 'function' ? options.getConfig : () => ({})
|
|
45
|
+
const renameTitle = typeof options.renameTitle === 'function' ? options.renameTitle : () => false
|
|
46
|
+
const onError = typeof options.onError === 'function' ? options.onError : () => {}
|
|
47
|
+
|
|
48
|
+
/** @type {Map<string, any>} */
|
|
49
|
+
const running = new Map()
|
|
50
|
+
let armed = false
|
|
51
|
+
let dirty = false
|
|
52
|
+
let last = null
|
|
53
|
+
let disposed = false
|
|
54
|
+
|
|
55
|
+
function enabled() {
|
|
56
|
+
try {
|
|
57
|
+
return getConfig().allDoneTitleEnabled === true
|
|
58
|
+
} catch (error) {
|
|
59
|
+
onError(error)
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function title() {
|
|
65
|
+
try {
|
|
66
|
+
const value = getConfig().allDoneTitle
|
|
67
|
+
if (typeof value === 'string' && value.trim().length > 0) return value.trim()
|
|
68
|
+
} catch (error) {
|
|
69
|
+
onError(error)
|
|
70
|
+
}
|
|
71
|
+
return '所有任务均已完成'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function armIfNeeded() {
|
|
75
|
+
if (running.size >= 2 && enabled()) armed = true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resetBatch() {
|
|
79
|
+
armed = false
|
|
80
|
+
dirty = false
|
|
81
|
+
last = null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function onTurnStart(session) {
|
|
85
|
+
if (disposed) return
|
|
86
|
+
const id = sessionId(session)
|
|
87
|
+
if (!id) return
|
|
88
|
+
if (running.size === 0) resetBatch()
|
|
89
|
+
running.set(id, session)
|
|
90
|
+
armIfNeeded()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onTurnEnd(session, event) {
|
|
94
|
+
if (disposed) return
|
|
95
|
+
const id = sessionId(session)
|
|
96
|
+
if (!id) return
|
|
97
|
+
// A feature enabled while several sessions are already running must arm
|
|
98
|
+
// before this end removes one of them.
|
|
99
|
+
armIfNeeded()
|
|
100
|
+
if (endReason(event) !== 'completed') dirty = true
|
|
101
|
+
running.delete(id)
|
|
102
|
+
last = session
|
|
103
|
+
if (running.size > 0) return
|
|
104
|
+
const shouldRename = armed && !dirty && enabled() && last !== null
|
|
105
|
+
if (shouldRename) {
|
|
106
|
+
try {
|
|
107
|
+
renameTitle(last, title())
|
|
108
|
+
} catch (error) {
|
|
109
|
+
onError(error)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
resetBatch()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function snapshot() {
|
|
116
|
+
return { running: running.size, armed, dirty }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function dispose() {
|
|
120
|
+
disposed = true
|
|
121
|
+
running.clear()
|
|
122
|
+
resetBatch()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { onTurnStart, onTurnEnd, snapshot, dispose }
|
|
126
|
+
}
|