@goodandready/dsh-plugin-notify 0.3.7 → 0.4.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 +21 -5
- package/README.md +58 -4
- package/README.ru.md +48 -4
- package/README.zh.md +48 -4
- package/lib/client.js +240 -7
- package/lib/index.js +490 -24
- package/package.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.7
|
|
4
|
-
|
|
5
|
-
### Fixed
|
|
6
|
-
- **Peer gate on DSH 0.2.0-rc.1** (#58): DSH skips a profile bundle whose `peerDependencies` exclude the running version, so this plugin was absent from the profile with no error in the UI. Every `@deepseek-ai/dsh-*` peer now names both the 0.1.7-rc.2 and 0.2.0-rc.1 lines, because semver does not admit a prerelease of the next minor into a range that does not name it.
|
|
7
|
-
|
|
8
3
|
All notable changes to this project will be documented in this file.
|
|
9
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
10
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
11
6
|
|
|
7
|
+
## [0.4.0] - 2026-10-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Web Push (PWA) notification channel (#52, GitHub PR #2)**: Background push notifications via W3C Push API and Service Worker (`/dsh-plugin-notify/sw.js`), delivering alerts even when all DSH tabs are closed.
|
|
11
|
+
- VAPID key generation and persistent storage (`$DSH_HOME/plugin-notify-state.json`) with env overrides (`DSH_NOTIFY_VAPID_PUBLIC_KEY`, `DSH_NOTIFY_VAPID_PRIVATE_KEY`).
|
|
12
|
+
- Secure push routes: `/dsh-plugin-notify/push/key`, `/subscribe`, `/unsubscribe`, `/test` protected by DSH connection auth.
|
|
13
|
+
- Smart away filtering: `webPush.onlyWhenAway` skips push while an active browser SSE client is connected.
|
|
14
|
+
- Service worker notification click deep linking: navigates to or opens triggering session (`?dshNotifySession=<id>`).
|
|
15
|
+
- Interactive Web UI card controls: subscription status, "Enable push on this device", "Disable push on this device", and "Test push".
|
|
16
|
+
- Comprehensive 27-test suite for web-push dispatch, routes, and state persistence (total 48 tests).
|
|
17
|
+
|
|
18
|
+
## [0.3.8] - 2026-09-29
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Settings schema unwrapping**: safely unwrap volatile schema fields across DSH 0.1.7-rc.2 and 0.2.0.
|
|
22
|
+
|
|
23
|
+
## [0.3.7] - 2026-09-29
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Peer gate on DSH 0.2.0-rc.1** (#58): DSH skips a profile bundle whose `peerDependencies` exclude the running version, so this plugin was absent from the profile with no error in the UI. Every `@deepseek-ai/dsh-*` peer now names both the 0.1.7-rc.2 and 0.2.0-rc.1 lines, because semver does not admit a prerelease of the next minor into a range that does not name it.
|
|
27
|
+
|
|
12
28
|
## [0.3.6] - 2026-09-25
|
|
13
29
|
|
|
14
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -39,11 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
DeepSeek Harness already knows when a turn finished, failed, or is waiting for approval. Without this plugin, those events remain confined inside the session. If you are working across multiple parallel sessions or minimized in another app, you have to keep manually checking back.
|
|
41
41
|
|
|
42
|
-
This plugin bridges that gap across
|
|
42
|
+
This plugin bridges that gap across five flexible notification layers:
|
|
43
43
|
1. **Audio Chimes**: Synthesized Web Audio pentatonic chimes that play immediately upon turn completion, failure, or approval requests without external audio files.
|
|
44
44
|
2. **Cross-Session Toasts**: On-screen floating banners that alert you across sessions with an interactive **"Go to session"** button to jump directly to the session that fired the event.
|
|
45
45
|
3. **Desktop / OS Push Notifications**: Native Windows, macOS, Linux, and DSH Desktop notifications via HTML5 `Notification API` with window focus and session switching on click.
|
|
46
46
|
4. **Remote IM Webhooks**: Outbound JSON webhooks for Feishu, WeCom, DingTalk, Slack, Discord, or generic custom endpoints. Webhook URLs are kept secret in DSH Credentials.
|
|
47
|
+
5. **Web Push (PWA)**: True background push via the W3C Push API and a service worker — your phone or desktop gets notified **even when every dsh browser tab is closed** (the other browser channels require an open tab). Tapping the notification opens dsh directly on the triggering session. HTTPS (or localhost) required; on iOS the PWA must be added to the Home Screen (iOS 16.4+).
|
|
47
48
|
|
|
48
49
|
## Architecture
|
|
49
50
|
|
|
@@ -58,6 +59,9 @@ graph TD
|
|
|
58
59
|
F -->|Web Audio API| G[Audio Chimes]
|
|
59
60
|
F -->|DOM overlay| H[Cross-Session Toasts]
|
|
60
61
|
F -->|Notification API| I[Desktop / OS Push]
|
|
62
|
+
B -->|web-push + VAPID| K[Push service]
|
|
63
|
+
K -->|background push| L[Service worker /dsh-plugin-notify/sw.js]
|
|
64
|
+
L -->|tap: jump to session| F
|
|
61
65
|
J[Settings Card] -->|configuration| B
|
|
62
66
|
```
|
|
63
67
|
|
|
@@ -74,6 +78,10 @@ graph TD
|
|
|
74
78
|
- Posts with `AbortSignal.timeout(timeoutMs)` (default 5000 ms). Failed POSTs are logged and never retried, and they never block the agent loop.
|
|
75
79
|
- Optional DND window (`HH:MM`, including overnight ranges). Events are still observed; webhooks, chimes, and local popups are skipped.
|
|
76
80
|
- `excludeSessionPrefixes` skips sessions whose id starts with a configured prefix.
|
|
81
|
+
- **Web Push host**: `webPush.enabled` turns on the Push API channel. Deliveries are skipped while an SSE client is connected when `webPush.onlyWhenAway` is on (default), so you are not buzzed while watching.
|
|
82
|
+
- Routes: `GET /dsh-plugin-notify/push/key` (VAPID public key), `POST /dsh-plugin-notify/push/subscribe`, `POST /dsh-plugin-notify/push/unsubscribe`, `POST /dsh-plugin-notify/push/test`, `GET /dsh-plugin-notify/sw.js` (service worker; served with `Service-Worker-Allowed: /`). The four push routes use the same trust fence as the SSE stream.
|
|
83
|
+
- VAPID keys are generated on first use and persisted to the state file; set `DSH_NOTIFY_VAPID_PUBLIC_KEY` + `DSH_NOTIFY_VAPID_PRIVATE_KEY` to manage them yourself. Subscriptions persist in the same file (default `$DSH_HOME/plugin-notify-state.json`, override with `webPush.stateFile`).
|
|
84
|
+
- Dead subscriptions (push service answers 404/410) are pruned automatically. A slow or failing push service never blocks the agent loop.
|
|
77
85
|
|
|
78
86
|
### Client (`lib/client.js`)
|
|
79
87
|
|
|
@@ -81,6 +89,7 @@ graph TD
|
|
|
81
89
|
- Web Audio API dual-tone chime synthesizer for `task_done`, `error`, and `approval_requested` with interactive "Test sound" button.
|
|
82
90
|
- Non-intrusive floating toast manager with session navigation button and interactive "Test toast" button.
|
|
83
91
|
- Native HTML5 desktop push integration with permission request workflow.
|
|
92
|
+
- Web push: service-worker registration (document-relative, survives `--public-url` proxy mounts), permission + subscription flow, "Enable push on this device" and "Test push" buttons with live status, and `?dshNotifySession=<id>` deep-link handling (plus service-worker `postMessage`) that lands on the triggering session.
|
|
84
93
|
- Real-time SSE subscriber with exponential-backoff auto-reconnect and optional `notifyBackgroundOnly` filter.
|
|
85
94
|
- Snapshot status `loading` / `unavailable` / `ready` before the form is writable.
|
|
86
95
|
- Save writes every field and lists named failures.
|
|
@@ -125,6 +134,11 @@ Put each webhook URL into **Settings → Credentials**. In the plugin card, type
|
|
|
125
134
|
includeSession: true
|
|
126
135
|
includeDuration: true
|
|
127
136
|
excludeSessionPrefixes: []
|
|
137
|
+
webPush:
|
|
138
|
+
enabled: false
|
|
139
|
+
onlyWhenAway: true
|
|
140
|
+
subject: 'mailto:notify@localhost'
|
|
141
|
+
stateFile: ''
|
|
128
142
|
```
|
|
129
143
|
|
|
130
144
|
| Parameter | Type | Default | Description |
|
|
@@ -133,6 +147,10 @@ Put each webhook URL into **Settings → Credentials**. In the plugin card, type
|
|
|
133
147
|
| `enableToasts` | boolean | `false` | Show cross-session on-screen banner toasts with interactive session switching (opt-in). |
|
|
134
148
|
| `enableDesktopNotifications` | boolean | `false` | Show native OS push notifications via HTML5 Notification API (opt-in). |
|
|
135
149
|
| `notifyBackgroundOnly` | boolean | `false` | Only trigger audio, toasts, and push when event is from an inactive/background session. |
|
|
150
|
+
| `webPush.enabled` | boolean | `false` | Web Push (PWA) channel: background push that reaches devices with every dsh tab closed (opt-in). |
|
|
151
|
+
| `webPush.onlyWhenAway` | boolean | `true` | Skip web push while a browser client is connected to the SSE stream. |
|
|
152
|
+
| `webPush.subject` | string | `mailto:notify@localhost` | VAPID subject (mailto:/https: contact) advertised to push services. |
|
|
153
|
+
| `webPush.stateFile` | string | empty | State file for VAPID keys and subscriptions (default `$DSH_HOME/plugin-notify-state.json`). |
|
|
136
154
|
| `webhooks.*` | string | empty | Credential **name** whose value is the webhook URL. Empty disables the channel. |
|
|
137
155
|
| `events` | string[] | `task_done`, `error`, `approval_requested` | Event whitelist. Empty restores the default three. |
|
|
138
156
|
| `local` | boolean | `true` | macOS `osascript` popup; ignored on other platforms. |
|
|
@@ -144,6 +162,14 @@ Put each webhook URL into **Settings → Credentials**. In the plugin card, type
|
|
|
144
162
|
|
|
145
163
|
A leftover raw `http(s)://` value in `webhooks.*` still posts, with a deprecation warning. Migrate it to Credentials.
|
|
146
164
|
|
|
165
|
+
### Enabling web push
|
|
166
|
+
|
|
167
|
+
1. In the plugin card, check **Enable web push channel** and save.
|
|
168
|
+
2. Click **Enable push on this device** and accept the browser permission prompt — the device subscribes and uploads its push endpoint to the host.
|
|
169
|
+
3. Click **Test push** to confirm delivery.
|
|
170
|
+
|
|
171
|
+
Desktop Chrome, Edge, Firefox, and Android Chrome work out of the box. On iOS, add dsh to the Home Screen first (iOS 16.4+). A secure context (HTTPS or `localhost`) is required — real deployments already terminate TLS at their reverse proxy. Unlike the other browser channels, web push reaches the device even when no dsh tab is running; with `webPush.onlyWhenAway` (default) it also stays quiet while you are actively watching.
|
|
172
|
+
|
|
147
173
|
## Message shape
|
|
148
174
|
|
|
149
175
|
| Channel | JSON body |
|
|
@@ -154,6 +180,7 @@ A leftover raw `http(s)://` value in `webhooks.*` still posts, with a deprecatio
|
|
|
154
180
|
| Slack | `{ text }` |
|
|
155
181
|
| Discord | `{ content: text }` |
|
|
156
182
|
| custom | `{ text, kind, title, sessionId, durationMs, time }` |
|
|
183
|
+
| Web Push | `{ kind, title, body, sessionId, tag, ts }` (encrypted by the push service; ≤4 KB pointer payload, no secrets) |
|
|
157
184
|
|
|
158
185
|
Text body:
|
|
159
186
|
|
|
@@ -176,7 +203,7 @@ npm test
|
|
|
176
203
|
|
|
177
204
|
`pretest` runs `node --check` on `lib/index.js` and `lib/client.js`. `npm test` then runs `node --test test/*.test.mjs`.
|
|
178
205
|
|
|
179
|
-
The suite stubs `fetch` /
|
|
206
|
+
The suite stubs `fetch` / the push sender. It does not call a real IM provider or push service. Live delivery needs a webhook you own (or a browser subscription, for web push).
|
|
180
207
|
|
|
181
208
|
Expected output:
|
|
182
209
|
|
|
@@ -202,8 +229,35 @@ Expected output:
|
|
|
202
229
|
✔ summarizeTurn safely reverse iterates events and handles missing session.events (issue #34 fix)
|
|
203
230
|
✔ textOf handles plain strings, arrays of blocks, and strings in arrays (issue #35 fix)
|
|
204
231
|
✔ lifecycle: apply -> dispose closes SSE clients, resets turnStarts, and isolates re-apply (issue #40 fix)
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
✔ web push delivers approval requests to stored subscriptions
|
|
233
|
+
✔ turn completion produces a task_done push honoring text options
|
|
234
|
+
✔ onlyWhenAway skips push while a browser client is connected
|
|
235
|
+
✔ onlyWhenAway=false pushes even with clients connected
|
|
236
|
+
✔ disabled web push never sends
|
|
237
|
+
✔ DND window suppresses push like every other channel
|
|
238
|
+
✔ event filter and session-prefix exclusions apply to push
|
|
239
|
+
✔ dead subscriptions (404/410) are pruned, live failures are not
|
|
240
|
+
✔ no subscriptions means no sends and no errors
|
|
241
|
+
✔ sw.js is served unauthenticated with scope and cache headers
|
|
242
|
+
✔ sw.js rejects non-GET with 405
|
|
243
|
+
✔ push routes refuse non-loopback peers when no connection service exists
|
|
244
|
+
✔ push routes honor connection.requestRejection like the SSE stream
|
|
245
|
+
✔ push/key: 404 while disabled, key material while enabled
|
|
246
|
+
✔ push/subscribe: persists valid subscriptions, rejects invalid bodies
|
|
247
|
+
✔ push/unsubscribe: removes by endpoint
|
|
248
|
+
✔ push/test: sends to stored subscriptions via the injected library
|
|
249
|
+
✔ push routes answer 405 on wrong methods
|
|
250
|
+
✔ generateVapidKeys returns base64url P-256 keys
|
|
251
|
+
✔ resolveStatePath: explicit stateFile wins, then DSH_HOME, then ~/.dsh
|
|
252
|
+
✔ loadState tolerates missing and corrupted files
|
|
253
|
+
✔ saveState round-trips and leaves no temp files behind
|
|
254
|
+
✔ getVapidKeys generates once, persists, then reuses
|
|
255
|
+
✔ getVapidKeys env override wins over the state file
|
|
256
|
+
✔ addSubscription validates and dedupes by endpoint
|
|
257
|
+
✔ removeSubscription removes and reports
|
|
258
|
+
✔ concurrent saveState calls serialize into a valid file
|
|
259
|
+
ℹ tests 48
|
|
260
|
+
ℹ pass 48
|
|
207
261
|
ℹ fail 0
|
|
208
262
|
```
|
|
209
263
|
|
package/README.ru.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
<h3
|
|
5
|
+
<h3>Web Push, IM-вебхуки, звуковые сигналы, тосты и системные уведомления о завершении хода, ошибке и ожидании approval</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-plugin-notify"><img src="https://img.shields.io/npm/v/@goodandready/dsh-plugin-notify.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -39,11 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
DeepSeek Harness уже знает, когда ход завершился, упал или ждёт approval. Без этого плагина события остаются внутри сессии. Если вы ведёте несколько параллельных сессий или переключились в другое приложение, приходится постоянно заглядывать и проверять статус вручную.
|
|
41
41
|
|
|
42
|
-
Плагин закрывает этот разрыв
|
|
42
|
+
Плагин закрывает этот разрыв пятью уровнями уведомлений:
|
|
43
43
|
1. **Звуковые сигналы (Web Audio)**: Приятные пентатонические переливы колокольчиков прямо в браузере или приложении DSH без внешних аудиофайлов.
|
|
44
44
|
2. **Экранные тосты (In-App Toasts)**: Всплывающие карточки поверх сессий с интерактивной кнопкой **«Перейти в сессию»** для мгновенного перехода к нужной задаче.
|
|
45
45
|
3. **Системные уведомления (OS / Desktop Push)**: Нативные уведомления Windows, macOS, Linux и DSH Desktop через HTML5 `Notification API` с фокусировкой окна и переходом в сессию.
|
|
46
46
|
4. **Удалённые вебхуки (Remote IM)**: Отправка JSON-событий в Feishu, WeCom, DingTalk, Slack, Discord или custom HTTP-эндпоинт. Секретные URL вебхуков надёжно хранятся в DSH Credentials.
|
|
47
|
+
5. **Web Push (PWA)**: Настоящий фоновый push через W3C Push API и service worker — телефон или десктоп получает уведомление, **даже если все вкладки dsh закрыты** (остальные браузерные каналы требуют открытой вкладки). Тап по уведомлению открывает dsh прямо на нужной сессии. Требуется HTTPS (или localhost); на iOS сначала добавьте dsh на главный экран (iOS 16.4+).
|
|
47
48
|
|
|
48
49
|
Русский интерфейс карточки настроек даёт отдельный языковой пакет `dsh-russian-lang`. Этот плагин регистрирует словари `en` и `zh`.
|
|
49
50
|
|
|
@@ -60,6 +61,9 @@ graph TD
|
|
|
60
61
|
F -->|Web Audio API| G[Звуковые сигналы]
|
|
61
62
|
F -->|DOM overlay| H[Экранные тосты]
|
|
62
63
|
F -->|Notification API| I[Десктопные уведомления OS]
|
|
64
|
+
B -->|web-push + VAPID| K[Push-сервис]
|
|
65
|
+
K -->|фоновый push| L[Service Worker /dsh-plugin-notify/sw.js]
|
|
66
|
+
L -->|тап: переход в сессию| F
|
|
63
67
|
J[Карточка настроек] -->|параметры| B
|
|
64
68
|
```
|
|
65
69
|
|
|
@@ -76,6 +80,10 @@ graph TD
|
|
|
76
80
|
- POST с `AbortSignal.timeout(timeoutMs)` (по умолчанию 5000 мс). Ошибка POST только логируется, без ретрая и без блокировки цикла агента.
|
|
77
81
|
- Опциональное окно DND (`HH:MM`, в том числе через полночь). События наблюдаются; вебхуки, звуки и локальные попапы пропускаются.
|
|
78
82
|
- `excludeSessionPrefixes` пропускает сессии, чей id начинается с заданного префикса.
|
|
83
|
+
- **Web Push на хосте**: `webPush.enabled` включает канал Push API. При включённом `webPush.onlyWhenAway` (по умолчанию) доставка пропускается, пока подключён браузерный клиент по SSE.
|
|
84
|
+
- Маршруты: `GET /dsh-plugin-notify/push/key` (публичный VAPID-ключ), `POST /dsh-plugin-notify/push/subscribe`, `POST /dsh-plugin-notify/push/unsubscribe`, `POST /dsh-plugin-notify/push/test`, `GET /dsh-plugin-notify/sw.js` (service worker с заголовком `Service-Worker-Allowed: /`). Четыре push-маршрута используют тот же контур доверия, что и SSE-поток.
|
|
85
|
+
- VAPID-ключи генерируются при первом включении и сохраняются в файл состояния; переменные `DSH_NOTIFY_VAPID_PUBLIC_KEY` + `DSH_NOTIFY_VAPID_PRIVATE_KEY` позволяют управлять ими вручную. Подписки хранятся в том же файле (по умолчанию `$DSH_HOME/plugin-notify-state.json`, переопределяется `webPush.stateFile`).
|
|
86
|
+
- Мёртвые подписки (push-сервис отвечает 404/410) удаляются автоматически. Медленный или сломанный push-сервис никогда не блокирует цикл агента.
|
|
79
87
|
|
|
80
88
|
### Клиент (`lib/client.js`)
|
|
81
89
|
|
|
@@ -126,6 +134,11 @@ dsh plugin --profile web add @goodandready/dsh-plugin-notify
|
|
|
126
134
|
includeSession: true
|
|
127
135
|
includeDuration: true
|
|
128
136
|
excludeSessionPrefixes: []
|
|
137
|
+
webPush:
|
|
138
|
+
enabled: false
|
|
139
|
+
onlyWhenAway: true
|
|
140
|
+
subject: 'mailto:notify@localhost'
|
|
141
|
+
stateFile: ''
|
|
129
142
|
```
|
|
130
143
|
|
|
131
144
|
| Параметр | Тип | По умолчанию | Описание |
|
|
@@ -142,6 +155,10 @@ dsh plugin --profile web add @goodandready/dsh-plugin-notify
|
|
|
142
155
|
| `includeSession` | boolean | `true` | Добавить строку `Session: …`. |
|
|
143
156
|
| `includeDuration` | boolean | `true` | Добавить `Duration: …`, если известно время старта хода. |
|
|
144
157
|
| `excludeSessionPrefixes` | string[] | `[]` | Не слать уведомления, если `session.id` начинается с префикса. |
|
|
158
|
+
| `webPush.enabled` | boolean | `false` | Канал Web Push (PWA): фоновая доставка при закрытых вкладках dsh (opt-in). |
|
|
159
|
+
| `webPush.onlyWhenAway` | boolean | `true` | Пропускать web push, пока подключён браузерный клиент. |
|
|
160
|
+
| `webPush.subject` | string | `mailto:notify@localhost` | VAPID-контакт (mailto:/https:), передаваемый push-сервисам. |
|
|
161
|
+
| `webPush.stateFile` | string | пусто | Файл состояния для VAPID-ключей и подписок (по умолчанию `$DSH_HOME/plugin-notify-state.json`). |
|
|
145
162
|
|
|
146
163
|
Сырой `http(s)://` в `webhooks.*` всё ещё отправляется с предупреждением. Перенесите URL в Credentials.
|
|
147
164
|
|
|
@@ -191,8 +208,35 @@ npm test
|
|
|
191
208
|
✔ summarizeTurn safely reverse iterates events and handles missing session.events (issue #34 fix)
|
|
192
209
|
✔ textOf handles plain strings, arrays of blocks, and strings in arrays (issue #35 fix)
|
|
193
210
|
✔ lifecycle: apply -> dispose closes SSE clients, resets turnStarts, and isolates re-apply (issue #40 fix)
|
|
194
|
-
|
|
195
|
-
|
|
211
|
+
✔ web push delivers approval requests to stored subscriptions
|
|
212
|
+
✔ turn completion produces a task_done push honoring text options
|
|
213
|
+
✔ onlyWhenAway skips push while a browser client is connected
|
|
214
|
+
✔ onlyWhenAway=false pushes even with clients connected
|
|
215
|
+
✔ disabled web push never sends
|
|
216
|
+
✔ DND window suppresses push like every other channel
|
|
217
|
+
✔ event filter and session-prefix exclusions apply to push
|
|
218
|
+
✔ dead subscriptions (404/410) are pruned, live failures are not
|
|
219
|
+
✔ no subscriptions means no sends and no errors
|
|
220
|
+
✔ sw.js is served unauthenticated with scope and cache headers
|
|
221
|
+
✔ sw.js rejects non-GET with 405
|
|
222
|
+
✔ push routes refuse non-loopback peers when no connection service exists
|
|
223
|
+
✔ push routes honor connection.requestRejection like the SSE stream
|
|
224
|
+
✔ push/key: 404 while disabled, key material while enabled
|
|
225
|
+
✔ push/subscribe: persists valid subscriptions, rejects invalid bodies
|
|
226
|
+
✔ push/unsubscribe: removes by endpoint
|
|
227
|
+
✔ push/test: sends to stored subscriptions via the injected library
|
|
228
|
+
✔ push routes answer 405 on wrong methods
|
|
229
|
+
✔ generateVapidKeys returns base64url P-256 keys
|
|
230
|
+
✔ resolveStatePath: explicit stateFile wins, then DSH_HOME, then ~/.dsh
|
|
231
|
+
✔ loadState tolerates missing and corrupted files
|
|
232
|
+
✔ saveState round-trips and leaves no temp files behind
|
|
233
|
+
✔ getVapidKeys generates once, persists, then reuses
|
|
234
|
+
✔ getVapidKeys env override wins over the state file
|
|
235
|
+
✔ addSubscription validates and dedupes by endpoint
|
|
236
|
+
✔ removeSubscription removes and reports
|
|
237
|
+
✔ concurrent saveState calls serialize into a valid file
|
|
238
|
+
ℹ tests 48
|
|
239
|
+
ℹ pass 48
|
|
196
240
|
ℹ fail 0
|
|
197
241
|
```
|
|
198
242
|
|
package/README.zh.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
<h3
|
|
5
|
+
<h3>网页推送、IM Webhook、提示音、横幅与桌面通知:回合完成、出错、等待审批全场景覆盖</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-plugin-notify"><img src="https://img.shields.io/npm/v/@goodandready/dsh-plugin-notify.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -39,11 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
DeepSeek Harness 已经知道回合何时完成、失败或等待审批。没有本插件时,这些事件只留在会话里。如果你同时打开多个并行会话或切换到了其他应用,不得不频繁切回窗口手动查看状态。
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
本插件通过五个层级全面覆盖通知场景:
|
|
43
43
|
1. **音频提示音 (Web Audio)**:使用浏览器内置 Web Audio API 合成双音五声音阶提示音,在任务完成、出错或等待审批时轻柔提示,无需外部音频文件。
|
|
44
44
|
2. **跨会话屏幕横幅 (In-App Toasts)**:在界面上方显示浮动通知卡片,带有交互式**“跳转至会话”**按钮,可一键切换到触发事件的目标会话。
|
|
45
45
|
3. **桌面 / 系统级通知 (OS Push)**:通过 HTML5 `Notification API` 触发 Windows、macOS、Linux 及 DSH 桌面端系统原生通知,点击自动聚焦窗口并跳转会话。
|
|
46
46
|
4. **远程 IM Webhook**:向飞书、企业微信、钉钉、Slack、Discord 或自定义 HTTP 接口投递 JSON 负载。Webhook URL 作为机密安全保存在 DSH 凭据中。
|
|
47
|
+
5. **网页推送 (Web Push / PWA)**:基于 W3C Push API 与 Service Worker 的真后台推送——**即使所有 dsh 标签页都已关闭**,手机或桌面仍能收到通知(其余浏览器通道需要标签页保持打开)。点击通知直接跳转到触发事件的会话。需要 HTTPS(或 localhost);iOS 需先添加到主屏幕(iOS 16.4+)。
|
|
47
48
|
|
|
48
49
|
## 架构
|
|
49
50
|
|
|
@@ -58,6 +59,9 @@ graph TD
|
|
|
58
59
|
F -->|Web Audio API| G[音频提示音]
|
|
59
60
|
F -->|DOM overlay| H[跨会话屏幕横幅]
|
|
60
61
|
F -->|Notification API| I[系统桌面通知]
|
|
62
|
+
B -->|web-push + VAPID| K[推送服务]
|
|
63
|
+
K -->|后台推送| L[Service Worker /dsh-plugin-notify/sw.js]
|
|
64
|
+
L -->|点击跳转会话| F
|
|
61
65
|
J[设置卡片] -->|参数配置| B
|
|
62
66
|
```
|
|
63
67
|
|
|
@@ -74,6 +78,10 @@ graph TD
|
|
|
74
78
|
- 使用 `AbortSignal.timeout(timeoutMs)`(默认 5000 ms)。POST 失败只记录,不重试,不阻塞 agent 循环。
|
|
75
79
|
- 可选免打扰窗口(`HH:MM`,支持跨夜)。事件仍会观察;Webhook、提示音和本机弹窗会跳过。
|
|
76
80
|
- `excludeSessionPrefixes` 会跳过 id 匹配前缀的会话。
|
|
81
|
+
- **网页推送宿主**:`webPush.enabled` 开启 Push API 通道。默认 `webPush.onlyWhenAway`(仅在无浏览器连接时推送),正在观看时不会被打扰。
|
|
82
|
+
- 路由:`GET /dsh-plugin-notify/push/key`(VAPID 公钥)、`POST /dsh-plugin-notify/push/subscribe`、`POST /dsh-plugin-notify/push/unsubscribe`、`POST /dsh-plugin-notify/push/test`、`GET /dsh-plugin-notify/sw.js`(Service Worker,响应头 `Service-Worker-Allowed: /`)。四个推送路由与 SSE 流使用同一信任围栏。
|
|
83
|
+
- VAPID 密钥首次使用时自动生成并持久化;可通过 `DSH_NOTIFY_VAPID_PUBLIC_KEY` + `DSH_NOTIFY_VAPID_PRIVATE_KEY` 自行管理。订阅保存在同一状态文件(默认 `$DSH_HOME/plugin-notify-state.json`,可用 `webPush.stateFile` 覆盖)。
|
|
84
|
+
- 失效订阅(推送服务返回 404/410)自动清理。推送服务缓慢或故障绝不阻塞 agent 循环。
|
|
77
85
|
|
|
78
86
|
### 客户端(`lib/client.js`)
|
|
79
87
|
|
|
@@ -125,6 +133,11 @@ dsh plugin --profile web add @goodandready/dsh-plugin-notify
|
|
|
125
133
|
includeSession: true
|
|
126
134
|
includeDuration: true
|
|
127
135
|
excludeSessionPrefixes: []
|
|
136
|
+
webPush:
|
|
137
|
+
enabled: false
|
|
138
|
+
onlyWhenAway: true
|
|
139
|
+
subject: 'mailto:notify@localhost'
|
|
140
|
+
stateFile: ''
|
|
128
141
|
```
|
|
129
142
|
|
|
130
143
|
| 参数 | 类型 | 默认值 | 说明 |
|
|
@@ -141,6 +154,10 @@ dsh plugin --profile web add @goodandready/dsh-plugin-notify
|
|
|
141
154
|
| `includeSession` | boolean | `true` | 在正文追加 `Session: …`。 |
|
|
142
155
|
| `includeDuration` | boolean | `true` | 在已知回合开始时间时追加 `Duration: …`。 |
|
|
143
156
|
| `excludeSessionPrefixes` | string[] | `[]` | 当 `session.id` 以任一前缀开头时跳过通知。 |
|
|
157
|
+
| `webPush.enabled` | boolean | `false` | 网页推送 (PWA) 通道:所有 dsh 标签页关闭时也能送达的后台推送(需手动开启)。 |
|
|
158
|
+
| `webPush.onlyWhenAway` | boolean | `true` | 有浏览器客户端连接时跳过网页推送。 |
|
|
159
|
+
| `webPush.subject` | string | `mailto:notify@localhost` | 向推送服务公示的 VAPID 联系方式(mailto:/https:)。 |
|
|
160
|
+
| `webPush.stateFile` | string | 空 | VAPID 密钥与订阅的状态文件(默认 `$DSH_HOME/plugin-notify-state.json`)。 |
|
|
144
161
|
|
|
145
162
|
`webhooks.*` 中残留的原始 `http(s)://` 仍会发送,但会给出弃用警告。请迁移到凭据。
|
|
146
163
|
|
|
@@ -190,8 +207,35 @@ npm test
|
|
|
190
207
|
✔ summarizeTurn safely reverse iterates events and handles missing session.events (issue #34 fix)
|
|
191
208
|
✔ textOf handles plain strings, arrays of blocks, and strings in arrays (issue #35 fix)
|
|
192
209
|
✔ lifecycle: apply -> dispose closes SSE clients, resets turnStarts, and isolates re-apply (issue #40 fix)
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
✔ web push delivers approval requests to stored subscriptions
|
|
211
|
+
✔ turn completion produces a task_done push honoring text options
|
|
212
|
+
✔ onlyWhenAway skips push while a browser client is connected
|
|
213
|
+
✔ onlyWhenAway=false pushes even with clients connected
|
|
214
|
+
✔ disabled web push never sends
|
|
215
|
+
✔ DND window suppresses push like every other channel
|
|
216
|
+
✔ event filter and session-prefix exclusions apply to push
|
|
217
|
+
✔ dead subscriptions (404/410) are pruned, live failures are not
|
|
218
|
+
✔ no subscriptions means no sends and no errors
|
|
219
|
+
✔ sw.js is served unauthenticated with scope and cache headers
|
|
220
|
+
✔ sw.js rejects non-GET with 405
|
|
221
|
+
✔ push routes refuse non-loopback peers when no connection service exists
|
|
222
|
+
✔ push routes honor connection.requestRejection like the SSE stream
|
|
223
|
+
✔ push/key: 404 while disabled, key material while enabled
|
|
224
|
+
✔ push/subscribe: persists valid subscriptions, rejects invalid bodies
|
|
225
|
+
✔ push/unsubscribe: removes by endpoint
|
|
226
|
+
✔ push/test: sends to stored subscriptions via the injected library
|
|
227
|
+
✔ push routes answer 405 on wrong methods
|
|
228
|
+
✔ generateVapidKeys returns base64url P-256 keys
|
|
229
|
+
✔ resolveStatePath: explicit stateFile wins, then DSH_HOME, then ~/.dsh
|
|
230
|
+
✔ loadState tolerates missing and corrupted files
|
|
231
|
+
✔ saveState round-trips and leaves no temp files behind
|
|
232
|
+
✔ getVapidKeys generates once, persists, then reuses
|
|
233
|
+
✔ getVapidKeys env override wins over the state file
|
|
234
|
+
✔ addSubscription validates and dedupes by endpoint
|
|
235
|
+
✔ removeSubscription removes and reports
|
|
236
|
+
✔ concurrent saveState calls serialize into a valid file
|
|
237
|
+
ℹ tests 48
|
|
238
|
+
ℹ pass 48
|
|
195
239
|
ℹ fail 0
|
|
196
240
|
```
|
|
197
241
|
|