dsh-notify-windows 0.7.4-beta.0 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,48 @@
2
2
 
3
3
  本项目遵循 [Semantic Versioning](https://semver.org/)。
4
4
 
5
- ## [Unreleased]
5
+ ## [0.7.5] - 2026-09-20
6
+
7
+ ### 修复:DSH 0.1.2-alpha.4+ 已无 `session.events`,历史读取改用 `snapshotEvents()`
8
+
9
+ - **根因**:`Session` 的公开 `events` 属性**自 DSH 0.1.2-alpha.4 起就不再存在**(0.1.2-alpha.3
10
+ 及更早仍有 `get events()`;本机逐个解包 npm 上 `@deepseek-ai/dsh-session` 的
11
+ 0.1.0-rc.8 / 0.1.2-alpha.3 / 0.1.2-alpha.4 / 0.1.5-rc.2 核实),历史事件改为按需读取
12
+ `snapshotEvents(fromSeq?, toSeqExclusive?)`(返回 `readonly SessionEvent[]`)。也就是说该降级
13
+ 覆盖插件声明支持的全部版本(0.1.2-alpha.4+),并非 0.1.5 才出现的回归。插件旧代码在两处读取
14
+ `session?.events ?? []`:
15
+ - `turnWindow()`(通知摘要 / `/goal` 轮次识别);
16
+ - `effectivePolicy()`(审批策略读取)。
17
+
18
+ 该属性恒为 `undefined`,`?? []` 兜底后**静默降级**(不报错但功能失效):通知摘要
19
+ (excerpt)为空、`userSource` 恒 `undefined` 导致 `/goal` 中间回合静默失效、审批策略恒
20
+ 读不到导致 `never` 策略下仍发多余审批提醒。
21
+ - **修复**:新增 `sessionEvents(session, onUnavailable)` 辅助函数,统一两处历史读取:
22
+ - **优先旧 API** `session.events`(`Array.isArray` 命中)——只对 DSH ≤ 0.1.2-alpha.3 生效;
23
+ - **回退新 API** `session.snapshotEvents()`(本机在 DSH 0.1.5-rc.1 的
24
+ `@deepseek-ai/dsh-session`(0.1.5-rc.2)上核实的契约:直接返回 `readonly SessionEvent[]`);
25
+ - 全程 `try/catch` 容错:`snapshotEvents()` 抛错时返回 `[]`,**历史读取异常绝不打断通知路径**;
26
+ - **降级不再静默**:两条失败路径经 `onUnavailable` 写入 notify.log 的 `history-unavailable`
27
+ 条目(含原因),避免同类问题再次无声无息。
28
+ - **兼容范围**:0.1.2-alpha.4+ 走 `snapshotEvents()`;旧 API 分支仅服务 DSH ≤ 0.1.2-alpha.3
29
+ (按 README,这类版本使用插件 0.7.3)。探针套件在真实 Cordis 上 15/15 通过,并按上述版本谱系
30
+ 核对契约。
31
+ - **测试**:`test/probe.test.mjs` 新增 `sessionEvents()` 路径用例(旧 API 命中 / 新 API 命中 /
32
+ 都不可用返回 `[]` / `snapshotEvents` 抛错不崩 / 失败经 `onUnavailable` 上报)与「仅提供
33
+ `snapshotEvents()` 的 0.1.2-alpha.4+ 式会话仍能生成摘要、`/goal` 终态判定与 `never` 策略
34
+ 抑制」的端到端用例、以及「历史读取抛错时通知照发并留痕」(turn/end 与 approval 两条路径各一)
35
+ 的端到端用例;0.1.2-alpha.4+ 式会话 stub 改用 class 方法,确保 this 绑定被真实覆盖;
36
+ `onUnavailable` 本身也做了异常隔离,上报失败不得反过来打断通知路径。
37
+ - **文档**:README 与 README.en.md 的 FAQ 补齐并修正升级指引——Issue #1(重启后通知静默:
38
+ **0.7.2 及更早**有缺陷,**0.7.3 已修**,建议升到 0.7.5+)与 Issue #2(0.1.2-alpha.4+ 的
39
+ `session.events` 静默降级,升 0.7.5+);README 的 DSH 兼容徽章同步到 0.1.5-rc.1。
40
+
41
+ ### 其它
42
+
43
+ - `lib/index.js` 的 `VERSION` 常量此前停留在 `0.7.3`(发布 0.7.4 时漏改),本次一并更正为
44
+ `0.7.5`,与 `package.json` 一致。
45
+
46
+ ## [0.7.4] - 2026-09-04
6
47
 
7
48
  ### 兼容:DSH 0.1.2-alpha.4
8
49
 
package/README.en.md CHANGED
@@ -11,6 +11,8 @@
11
11
 
12
12
  **Task finished** ✅ | **Approval pending** 🔐 | **Question pending** ❓ — never miss anything that needs you, even away from the screen.
13
13
 
14
+ > **Compatibility**: v0.7.5 supports DSH **0.1.2-alpha.4+** and was verified against the `snapshotEvents()` contract on **0.1.5-rc.1** with the real-Cordis probe suite (15/15; peer `@deepseek-ai/cordis ^4.0.2`). Session history is read through both the legacy `session.events` (DSH <= 0.1.2-alpha.3) and `snapshotEvents()` (DSH 0.1.2-alpha.4+, where that property no longer exists), so excerpts, `/goal` round detection and approval-policy suppression work again after upgrading to 0.7.5. If you saw "all notifications went silent after a restart", upgrade to **0.7.3+**.
15
+
14
16
  ## ✨ Features
15
17
 
16
18
  - **Task completion** — listens to `turn/end` session events; toasts on completed / errored / max-tokens turns, with the session title and reason;
@@ -125,6 +127,8 @@ Runs the plugin on a bare cordis Context with synthetic events: three test toast
125
127
  - **No toast?** Check Windows notification settings for this app and Focus Assist; the AUMID registers itself on first use.
126
128
  - **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.
127
129
  - **Approval toasts missing?** Sessions whose approval policy is `never` auto-reject — nothing waits, so the plugin stays quiet. Only `ask`-policy sessions notify.
130
+ - **All notifications went silent after restarting DSH?** Fixed in **0.7.3**: the subagent check used to be `delegationDepth !== undefined`, which treated every restored main session (persisted with `delegationDepth: 0`) as a subagent and filtered it out. Upgrade to **0.7.3 or newer** (0.7.5 recommended), then restart the DSH host.
131
+ - **Empty excerpts, /goal intermediate rounds toasting, approvals toasting under `never`?** The plugin read `session.events`, a property DSH stopped exposing in **0.1.2-alpha.4** (history moved to `snapshotEvents()`); **0.7.4 and earlier** silently degraded in that case. Upgrade to **0.7.5 or newer**. If `history-unavailable` still appears in `%TEMP%\dsh-notify\notify.log`, please attach the log.
128
132
  - **Hot-update after code changes?** Run `dsh plugin --profile web update dsh-notify-windows`, then restart the DSH host.
129
133
 
130
134
  ## 📄 License
package/README.md CHANGED
@@ -7,10 +7,13 @@
7
7
  <img src="https://img.shields.io/github/license/SeverusZh/dsh-notify-windows" alt="license">
8
8
  <img src="https://img.shields.io/badge/platform-Windows%2010%2F11-blue" alt="platform">
9
9
  <img src="https://img.shields.io/github/stars/SeverusZh/dsh-notify-windows?style=social" alt="stars">
10
+ <img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.5--rc.1-blue" alt="dsh">
10
11
  </p>
11
12
 
12
13
  **任务完成** ✅ | **等待审批** 🔐 | **等待回答** ❓ —— 离开屏幕也不错过任何需要你处理的事。
13
14
 
15
+ > **兼容性**:v0.7.5 支持 DSH **0.1.2-alpha.4+**,已在 **0.1.5-rc.1** 上核对 `snapshotEvents()` 契约并通过真实-Cordis 探针(15/15;peer `@deepseek-ai/cordis ^4.0.2`)。历史读取同时兼容旧版 `session.events`(DSH ≤ 0.1.2-alpha.3)与新版 `snapshotEvents()`(DSH 0.1.2-alpha.4 起该属性已不存在),升级到 0.7.5 后摘要 / `/goal` 轮次 / 审批策略恢复正常。收到过「重启后所有通知静默」问题的请升级到 **0.7.3+**(见 FAQ)。
16
+
14
17
  ## ✨ 功能
15
18
 
16
19
  - **任务完成提醒**:监听会话 `turn/end` 事件,任务完成 / 出错 / 输出超限时立即弹窗,标题自动使用会话标题,正文显示原因与轮次;
@@ -123,6 +126,8 @@ node scripts\smoke-test.mjs
123
126
  - **WSL 下收不到通知?** 插件会自动用 `wslpath -w` 把脚本路径转成 `\\wsl.localhost\...` UNC 路径,并优先通过 `/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` 调用 PowerShell(不依赖 PATH,systemd / cron 等最小环境也能工作)。若仍失败,查看 `%TEMP%\dsh-notify\notify.log` 中的 `error` 条目。
124
127
  - **/goal 模式会提醒吗?** 默认不会:自动推进的中间回合保持静默,只有目标完成(或阻塞)的最终回合才提醒;如需每个回合都提醒,把 `notifyOnGoalRounds` 设为 `true`。
125
128
  - **为什么审批提醒有时不弹?** 会话审批策略为 `never` 时审批会被自动拒绝、不会等待,插件会跳过提醒;策略为 `ask` 时才提醒。
129
+ - **重启 DSH 后所有通知都没了?** 这是 **0.7.2 及更早**的已知缺陷:子代理判定当时写作 `delegationDepth !== undefined`,会把每次 `dsh web` 重启后带 `delegationDepth: 0` 的**主会话**误判为子代理并静默过滤。该缺陷已在 **0.7.3** 修复(判定改为 `(delegationDepth ?? 0) > 0`):请把插件升级到 **0.7.3 及以上**(推荐 0.7.5),执行 `dsh plugin --profile web update dsh-notify-windows` 后重启 DSH 宿主。
130
+ - **通知摘要变空、`/goal` 中间回合开始提醒、`never` 策略仍提醒?** 插件读取的 `session.events` 属性**自 DSH 0.1.2-alpha.4 起就不存在**(历史读取改为 `snapshotEvents()`;0.1.2-alpha.3 及更早才有该属性),**0.7.4 及更早**读取该属性会拿到 `undefined` 后静默降级,出现上述三种症状。**请升级到 0.7.5 及以上**(历史读取已同时兼容两种 API;若升级后 `%TEMP%\dsh-notify\notify.log` 仍出现 `history-unavailable` 条目,请附日志反馈)。
126
131
  - **更新插件代码后如何生效?** 执行 `dsh plugin --profile web update dsh-notify-windows` 后重启 DSH 宿主。
127
132
 
128
133
  ## 📄 许可证
package/lib/index.js CHANGED
@@ -25,7 +25,7 @@ import { join } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
 
27
27
  const NOTIFY_SCRIPT = fileURLToPath(new URL("./notify.ps1", import.meta.url));
28
- const VERSION = "0.7.3";
28
+ const VERSION = "0.7.5";
29
29
 
30
30
  // Convert a script path so powershell.exe can read it.
31
31
  // - On WSL: powershell.exe is a Windows process and can't read Linux paths
@@ -111,14 +111,51 @@ function textOfMessage(message) {
111
111
  return undefined;
112
112
  }
113
113
 
114
+ /**
115
+ * Read a session's event log across DSH generations.
116
+ *
117
+ * DSH stopped exposing the public `session.events` property in 0.1.2-alpha.4
118
+ * (0.1.2-alpha.3 and earlier still had `get events()`); from there on history
119
+ * is read on demand through `snapshotEvents()`, which returns
120
+ * `readonly SessionEvent[]`. Prefer the legacy property so those older DSH
121
+ * builds stay supported, then fall back to the new accessor.
122
+ *
123
+ * A host-side read failure must degrade to "no history" rather than throw:
124
+ * losing the excerpt or the goal-round check is acceptable, but breaking the
125
+ * notification path is not. The degradation is reported through
126
+ * `onUnavailable` so it never disappears without a trace again.
127
+ */
128
+ export function sessionEvents(session, onUnavailable) {
129
+ // Reporting must never break the notification path either: a throwing
130
+ // `onUnavailable` would otherwise escape into the turn/end handler.
131
+ const report = (reason) => {
132
+ try {
133
+ onUnavailable?.(reason, session);
134
+ } catch {
135
+ // diagnostics must never break the notify path
136
+ }
137
+ };
138
+ const legacy = session?.events;
139
+ if (Array.isArray(legacy)) return legacy;
140
+ try {
141
+ const snapshot = session?.snapshotEvents?.();
142
+ if (Array.isArray(snapshot)) return snapshot;
143
+ } catch (error) {
144
+ report("snapshotEvents() threw: " + String(error?.message ?? error));
145
+ return [];
146
+ }
147
+ report("session exposes neither events nor snapshotEvents()");
148
+ return [];
149
+ }
150
+
114
151
  /**
115
152
  * Scan the session log from the turn/end event back to its turn/start and
116
153
  * collect what the notification needs: the driving user message's source,
117
154
  * every goal/change operation in the window, and the last assistant reply
118
155
  * text (for the excerpt).
119
156
  */
120
- function turnWindow(session, turnEnd) {
121
- const events = session?.events ?? [];
157
+ function turnWindow(session, turnEnd, onUnavailable) {
158
+ const events = sessionEvents(session, onUnavailable);
122
159
  const turn = turnEnd?.data?.turn;
123
160
  let endIndex = events.length - 1;
124
161
  for (let i = events.length - 1; i >= 0; i -= 1) {
@@ -177,6 +214,10 @@ export const apply = (ctx, config = {}) => {
177
214
  }
178
215
  };
179
216
 
217
+ /** Report a history-read degradation instead of silently losing the excerpt. */
218
+ const historyUnavailable = (reason, session) =>
219
+ log({ event: "history-unavailable", reason, sessionId: session?.id });
220
+
180
221
  // Authoritative base URL discovery (verified against rc.8).
181
222
  // Always loopback: ctx.webServer.host may be 0.0.0.0, which is not a valid
182
223
  // browser target. Use the actual bound port (correct even when config port=0).
@@ -274,7 +315,7 @@ export const apply = (ctx, config = {}) => {
274
315
 
275
316
  /** Last approval/policy event of the session, or undefined (defaults to ask). */
276
317
  const effectivePolicy = (session) => {
277
- const events = session?.events ?? [];
318
+ const events = sessionEvents(session, historyUnavailable);
278
319
  for (let index = events.length - 1; index >= 0; index -= 1) {
279
320
  const event = events[index];
280
321
  if (event?.type === "approval/policy") return event.data?.policy;
@@ -292,7 +333,7 @@ export const apply = (ctx, config = {}) => {
292
333
  if (event.type === "turn/end") {
293
334
  const reason = event.data?.reason;
294
335
  if (typeof reason?.kind !== "string" || !reasons.has(reason.kind)) return;
295
- const window = turnWindow(session, event);
336
+ const window = turnWindow(session, event, historyUnavailable);
296
337
 
297
338
  // /goal 自动推进回合默认静默:仅在目标完成/阻塞的最终回合提醒。
298
339
  if (!cfg.notifyOnGoalRounds) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-windows",
3
- "version": "0.7.4-beta.0",
3
+ "version": "0.7.5",
4
4
  "description": "DeepSeek Harness (DSH) 插件:任务完成 / 等待审批 / 等待回答时发送 Windows 系统桌面通知",
5
5
  "repository": {
6
6
  "type": "git",