pi-smart-voice-notify 0.3.5 → 0.5.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0] - 2026-05-22
4
+
5
+ ### Added
6
+ - Added `PI_SMART_NOTIFY_AGENT_ERROR_GRACE_MS` to delay agent-error notifications briefly so related idle/error state can settle before notifying.
7
+ - Added webhook destination hardening with public HTTP(S)-only validation, private/reserved host rejection, DNS validation, and DNS-pinned dispatch.
8
+
9
+ ### Changed
10
+ - Added request timeouts and a 10 MiB audio response cap for remote ElevenLabs/OpenAI-compatible TTS fetches.
11
+ - Improved Linux focus detection and moved debug log writes onto asynchronous file logging with flush support.
12
+ - Reorganized checked-in tests into the dedicated `test/` directory with webhook, TTS, Linux, sound-theme, and notification coverage.
13
+
14
+ ## [0.4.0] - 2026-04-30
15
+
16
+ ### Added
17
+ - Detect agent turn failures reported at `agent_end` and send error notifications instead of completion alerts.
18
+
19
+ ### Changed
20
+ - Renamed bundled notification sound assets to stable lowercase filenames and migrate legacy bundled sound paths automatically.
21
+ - Updated `@mariozechner/pi-*` peer dependency ranges to `^0.70.6` and synchronized package lock metadata.
22
+
3
23
  ## [0.3.5] - 2026-04-27
4
24
 
5
25
  ### Fixed
package/README.md CHANGED
@@ -16,6 +16,7 @@ Windows-optimized smart notification extension for the Pi coding agent.
16
16
  - **Voice** – auto-selectable TTS engines: Edge, espeak-ng, ElevenLabs, OpenAI-compatible, and Windows SAPI
17
17
  - **Desktop toasts** – cross-platform notifications via `node-notifier` (Windows/macOS/Linux)
18
18
  - **Webhook delivery** – optional Discord or generic HTTP webhook notifications
19
+ - Validates webhook targets as public HTTP(S) destinations and blocks localhost/private/reserved networks before dispatch
19
20
 
20
21
  - **Intelligent event detection**
21
22
  - Task completion (idle)
@@ -133,6 +134,9 @@ A starter template is provided in `config/config.example.json`. On startup, the
133
134
  | `enableErrorNotification` | boolean | `true` | Notify on errors |
134
135
  | `suppressIdleAfterError` | boolean | `true` | Skip idle notification if turn had errors |
135
136
 
137
+
138
+ Error notifications are delayed briefly before delivery so Pi can settle related turn state and suppress a redundant idle alert. Override the default 10000 ms delay with `PI_SMART_NOTIFY_AGENT_ERROR_GRACE_MS` when you need faster or slower error alerts.
139
+
136
140
  *Question notifications only work when a custom `question` tool is loaded.
137
141
 
138
142
  Forwarded permission watcher notifications use privacy-safe text, require the request `targetSessionId` to match the active Pi session, and never include raw forwarded `message` content.
@@ -166,10 +170,10 @@ Forwarded permission watcher notifications use privacy-safe text, require the re
166
170
 
167
171
  | Option | Type | Default |
168
172
  |--------|------|---------|
169
- | `idleSoundFile` | string | `"assets/Soft-high-tech-notification-sound-effect.mp3"` |
170
- | `permissionSoundFile` | string | `"assets/Machine-alert-beep-sound-effect.mp3"` |
171
- | `questionSoundFile` | string | `"assets/Machine-alert-beep-sound-effect.mp3"` |
172
- | `errorSoundFile` | string | `"assets/Machine-alert-beep-sound-effect.mp3"` |
173
+ | `idleSoundFile` | string | `"assets/soft-notification.mp3"` |
174
+ | `permissionSoundFile` | string | `"assets/attention-alert.mp3"` |
175
+ | `questionSoundFile` | string | `"assets/attention-alert.mp3"` |
176
+ | `errorSoundFile` | string | `"assets/attention-alert.mp3"` |
173
177
 
174
178
  Paths can be absolute or relative to the extension directory.
175
179
 
@@ -188,6 +192,9 @@ Paths can be absolute or relative to the extension directory.
188
192
  | `minNotificationIntervalMs` | number | `1500` | Throttle interval between same-type notifications |
189
193
  | `debugLog` | boolean | `false` | Enable debug logging to file |
190
194
 
195
+
196
+ Webhook dispatch only attempts public `http`/`https` URLs. The extension rejects localhost-style names, `.local`/`.internal`/`.lan`/`.home.arpa` hosts, private or reserved IP literals, and hostnames whose DNS results include private or reserved addresses; validated DNS results are pinned for the outbound request. Use `PI_SMART_NOTIFY_WEBHOOK_TIMEOUT_MS` to override the default 8000 ms webhook request timeout.
197
+
191
198
  ## Notification Modes
192
199
 
193
200
  | Mode | Behavior |
@@ -94,10 +94,10 @@
94
94
  "openaiTtsSpeed": 1,
95
95
 
96
96
  "_comment_sounds": "Direct sound file fallbacks.",
97
- "idleSoundFile": "assets/Soft-high-tech-notification-sound-effect.mp3",
98
- "permissionSoundFile": "assets/Machine-alert-beep-sound-effect.mp3",
99
- "questionSoundFile": "assets/Machine-alert-beep-sound-effect.mp3",
100
- "errorSoundFile": "assets/Machine-alert-beep-sound-effect.mp3",
97
+ "idleSoundFile": "assets/soft-notification.mp3",
98
+ "permissionSoundFile": "assets/attention-alert.mp3",
99
+ "questionSoundFile": "assets/attention-alert.mp3",
100
+ "errorSoundFile": "assets/attention-alert.mp3",
101
101
 
102
102
  "_comment_sound_theme": "Sound theme settings.",
103
103
  "themePath": "",
package/package.json CHANGED
@@ -1,70 +1,74 @@
1
- {
2
- "name": "pi-smart-voice-notify",
3
- "version": "0.3.5",
4
- "description": "Windows-optimized smart voice, sound, and desktop notifications for Pi coding agent.",
5
- "type": "module",
6
- "main": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts"
9
- },
10
- "files": [
11
- "index.ts",
12
- "src",
13
- "config/config.example.json",
14
- "assets",
15
- "README.md",
16
- "CHANGELOG.md",
17
- "LICENSE"
18
- ],
19
- "scripts": {
20
- "build": "npx --yes -p typescript@5.7.3 -p @types/node@20.17.57 tsc -p tsconfig.json --noEmit",
21
- "lint": "npm run build",
22
- "test": "node --experimental-strip-types --test src/abortable-command.test.ts src/reminder-playback.test.ts src/permission-forwarding-watcher.test.ts src/index.test.ts",
23
- "check": "npm run build && npm run test"
24
- },
25
- "keywords": [
26
- "pi-package",
27
- "pi",
28
- "pi-extension",
29
- "pi-coding-agent",
30
- "coding-agent",
31
- "voice-notify",
32
- "desktop-notification",
33
- "notifications",
34
- "voice",
35
- "audio",
36
- "text-to-speech",
37
- "tts",
38
- "windows",
39
- "linux",
40
- "macos"
41
- ],
42
- "author": "MasuRii",
43
- "license": "MIT",
44
- "repository": {
45
- "type": "git",
46
- "url": "git+https://github.com/MasuRii/pi-smart-voice-notify.git"
47
- },
48
- "homepage": "https://github.com/MasuRii/pi-smart-voice-notify#readme",
49
- "bugs": {
50
- "url": "https://github.com/MasuRii/pi-smart-voice-notify/issues"
51
- },
52
- "engines": {
53
- "node": ">=24"
54
- },
55
- "publishConfig": {
56
- "access": "public"
57
- },
58
- "pi": {
59
- "extensions": [
60
- "./index.ts"
61
- ]
62
- },
63
- "peerDependencies": {
64
- "@mariozechner/pi-coding-agent": "^0.70.2",
65
- "@mariozechner/pi-tui": "^0.70.2"
66
- },
67
- "dependencies": {
68
- "node-notifier": "^10.0.1"
69
- }
70
- }
1
+ {
2
+ "name": "pi-smart-voice-notify",
3
+ "version": "0.5.0",
4
+ "description": "Windows-optimized smart voice, sound, and desktop notifications for Pi coding agent.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "config/config.example.json",
14
+ "assets",
15
+ "README.md",
16
+ "CHANGELOG.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "npx --yes -p typescript@5.7.3 -p @types/node@20.17.57 tsc -p tsconfig.json --noEmit",
21
+ "lint": "npm run build",
22
+ "test": "node --experimental-strip-types --test test/abortable-command.test.ts test/reminder-playback.test.ts test/permission-forwarding-watcher.test.ts test/sound-theme.test.ts test/webhook.test.ts test/linux.test.ts test/index.test.ts test/tts.test.ts",
23
+ "check": "npm run build && npm run test"
24
+ },
25
+ "keywords": [
26
+ "pi-package",
27
+ "pi",
28
+ "pi-extension",
29
+ "pi-coding-agent",
30
+ "coding-agent",
31
+ "voice-notify",
32
+ "desktop-notification",
33
+ "notifications",
34
+ "voice",
35
+ "audio",
36
+ "text-to-speech",
37
+ "tts",
38
+ "windows",
39
+ "linux",
40
+ "macos"
41
+ ],
42
+ "author": "MasuRii",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/MasuRii/pi-smart-voice-notify.git"
47
+ },
48
+ "homepage": "https://github.com/MasuRii/pi-smart-voice-notify#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/MasuRii/pi-smart-voice-notify/issues"
51
+ },
52
+ "engines": {
53
+ "node": ">=24"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "pi": {
59
+ "extensions": [
60
+ "./index.ts"
61
+ ]
62
+ },
63
+ "peerDependencies": {
64
+ "@earendil-works/pi-coding-agent": "^0.75.4",
65
+ "@earendil-works/pi-tui": "^0.75.4"
66
+ },
67
+ "dependencies": {
68
+ "node-notifier": "^10.0.1",
69
+ "undici": "^8.3.0"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "20.17.57"
73
+ }
74
+ }
@@ -1,4 +1,4 @@
1
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { isAbsolute, join } from "node:path";
4
4
 
@@ -174,10 +174,10 @@ export const DEFAULT_CONFIG: VoiceNotifyConfig = {
174
174
  openaiTtsFormat: "mp3",
175
175
  openaiTtsSpeed: 1,
176
176
 
177
- idleSoundFile: "assets/Soft-high-tech-notification-sound-effect.mp3",
178
- permissionSoundFile: "assets/Machine-alert-beep-sound-effect.mp3",
179
- questionSoundFile: "assets/Machine-alert-beep-sound-effect.mp3",
180
- errorSoundFile: "assets/Machine-alert-beep-sound-effect.mp3",
177
+ idleSoundFile: "assets/soft-notification.mp3",
178
+ permissionSoundFile: "assets/attention-alert.mp3",
179
+ questionSoundFile: "assets/attention-alert.mp3",
180
+ errorSoundFile: "assets/attention-alert.mp3",
181
181
  themePath: "",
182
182
  themeName: "default",
183
183
  themesRootPath: "",
@@ -344,6 +344,34 @@ function stringOrDefault(value: unknown, fallback: string): string {
344
344
  return fallback;
345
345
  }
346
346
 
347
+ const LEGACY_BUNDLED_SOUND_FILES: Record<string, string> = {
348
+ "assets/machine-alert-beep-sound-effect.mp3": "assets/attention-alert.mp3",
349
+ "assets/soft-high-tech-notification-sound-effect.mp3": "assets/soft-notification.mp3",
350
+ };
351
+
352
+ function normalizeSoundFileLookup(value: string): string {
353
+ return value.replaceAll("\\", "/").replace(/^\.\//, "").toLowerCase();
354
+ }
355
+
356
+ function soundFileExists(value: string): boolean {
357
+ return existsSync(isAbsolute(value) ? value : join(CONFIG_DIR, value));
358
+ }
359
+
360
+ function normalizeSoundFile(value: unknown, fallback: string): string {
361
+ const selected = stringOrDefault(value, fallback);
362
+ const lookup = normalizeSoundFileLookup(selected);
363
+ const migrated = LEGACY_BUNDLED_SOUND_FILES[lookup];
364
+ if (migrated) {
365
+ return migrated;
366
+ }
367
+
368
+ if (lookup.startsWith("assets/") && !soundFileExists(selected) && soundFileExists(fallback)) {
369
+ return fallback;
370
+ }
371
+
372
+ return selected;
373
+ }
374
+
347
375
  function stringOrEmpty(value: unknown): string {
348
376
  if (typeof value === "string") {
349
377
  return value.trim();
@@ -746,13 +774,13 @@ export function normalizeConfig(raw: unknown): VoiceNotifyConfig {
746
774
  openaiTtsFormat: stringOrDefault(record.openaiTtsFormat, DEFAULT_CONFIG.openaiTtsFormat),
747
775
  openaiTtsSpeed: clampNumber(record.openaiTtsSpeed, DEFAULT_CONFIG.openaiTtsSpeed, 0.25, 4),
748
776
 
749
- idleSoundFile: stringOrDefault(record.idleSoundFile ?? record.idleSound, DEFAULT_CONFIG.idleSoundFile),
750
- permissionSoundFile: stringOrDefault(
777
+ idleSoundFile: normalizeSoundFile(record.idleSoundFile ?? record.idleSound, DEFAULT_CONFIG.idleSoundFile),
778
+ permissionSoundFile: normalizeSoundFile(
751
779
  record.permissionSoundFile ?? record.permissionSound,
752
780
  DEFAULT_CONFIG.permissionSoundFile,
753
781
  ),
754
- questionSoundFile: stringOrDefault(record.questionSoundFile ?? record.questionSound, DEFAULT_CONFIG.questionSoundFile),
755
- errorSoundFile: stringOrDefault(record.errorSoundFile ?? record.errorSound, DEFAULT_CONFIG.errorSoundFile),
782
+ questionSoundFile: normalizeSoundFile(record.questionSoundFile ?? record.questionSound, DEFAULT_CONFIG.questionSoundFile),
783
+ errorSoundFile: normalizeSoundFile(record.errorSoundFile ?? record.errorSound, DEFAULT_CONFIG.errorSoundFile),
756
784
  themePath: stringOrEmpty(record.themePath ?? record.soundThemeDir ?? record.themeDirectory),
757
785
  themeName: stringOrDefault(record.themeName, DEFAULT_CONFIG.themeName),
758
786
  themesRootPath: stringOrEmpty(record.themesRootPath ?? record.themesRootDirectory),
@@ -989,7 +1017,7 @@ export function isWindows(): boolean {
989
1017
 
990
1018
  export function resolveSoundFile(config: VoiceNotifyConfig, type: NotificationType): string | null {
991
1019
  const field = SOUND_FILE_FIELD[type];
992
- const value = config[field];
1020
+ const value = normalizeSoundFile(config[field], DEFAULT_CONFIG[field]);
993
1021
  if (!value.trim()) {
994
1022
  return null;
995
1023
  }