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 +20 -0
- package/README.md +11 -4
- package/config/config.example.json +4 -4
- package/package.json +74 -70
- package/src/config-store.ts +38 -10
- package/src/focus-detect.ts +497 -474
- package/src/index.ts +126 -16
- package/src/linux.ts +2 -32
- package/src/logging.ts +86 -72
- package/src/notify-audio.ts +12 -11
- package/src/permission-forwarding-watcher.ts +637 -577
- package/src/sound-theme.ts +15 -2
- package/src/tts.ts +110 -8
- package/src/webhook.ts +204 -4
- package/src/zellij-modal.ts +2 -2
- package/assets/pi-smart-voice-notify.png +0 -0
- package/src/abortable-command.test.ts +0 -45
- package/src/index.test.ts +0 -911
- package/src/permission-forwarding-watcher.test.ts +0 -180
- package/src/reminder-playback.test.ts +0 -62
- /package/assets/{Machine-alert-beep-sound-effect.mp3 → attention-alert.mp3} +0 -0
- /package/assets/{Soft-high-tech-notification-sound-effect.mp3 → soft-notification.mp3} +0 -0
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/
|
|
170
|
-
| `permissionSoundFile` | string | `"assets/
|
|
171
|
-
| `questionSoundFile` | string | `"assets/
|
|
172
|
-
| `errorSoundFile` | string | `"assets/
|
|
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/
|
|
98
|
-
"permissionSoundFile": "assets/
|
|
99
|
-
"questionSoundFile": "assets/
|
|
100
|
-
"errorSoundFile": "assets/
|
|
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.
|
|
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
|
|
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
|
-
"@
|
|
65
|
-
"@
|
|
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
|
+
}
|
package/src/config-store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAgentDir } from "@
|
|
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/
|
|
178
|
-
permissionSoundFile: "assets/
|
|
179
|
-
questionSoundFile: "assets/
|
|
180
|
-
errorSoundFile: "assets/
|
|
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:
|
|
750
|
-
permissionSoundFile:
|
|
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:
|
|
755
|
-
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
|
}
|