opencode-smart-voice-notify 1.0.13 → 1.1.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/README.md CHANGED
@@ -33,6 +33,7 @@ The plugin automatically tries multiple TTS engines in order, falling back if on
33
33
  - Per-notification type delays (permission requests are more urgent)
34
34
  - **Smart Quota Handling**: Automatically falls back to free Edge TTS if ElevenLabs quota is exceeded
35
35
  - **Permission Batching**: Multiple simultaneous permission requests are batched into a single notification (e.g., "5 permission requests require your attention")
36
+ - **Question Tool Support** (SDK v1.1.7+): Notifies when the agent asks questions and needs user input
36
37
 
37
38
  ### System Integration
38
39
  - **Native Edge TTS**: No external dependencies (Python/pip) required
@@ -95,86 +96,153 @@ The auto-generated configuration includes all advanced settings, message arrays,
95
96
 
96
97
  If you prefer to create the config manually, add a `smart-voice-notify.jsonc` file in your OpenCode config directory (`~/.config/opencode/`):
97
98
 
98
- ```jsonc
99
- {
100
- // ============================================================
101
- // NOTIFICATION MODE SETTINGS (Smart Notification System)
102
- // ============================================================
103
- // Controls how notifications are delivered:
104
- // 'sound-first' - Play sound immediately, TTS reminder after delay (RECOMMENDED)
105
- // 'tts-first' - Speak TTS immediately, no sound
106
- // 'both' - Play sound AND speak TTS immediately
107
- // 'sound-only' - Only play sound, no TTS at all
108
- "notificationMode": "sound-first",
109
-
110
- // ============================================================
111
- // TTS REMINDER SETTINGS (When user doesn't respond to sound)
112
- // ============================================================
113
-
114
- // Enable TTS reminder if user doesn't respond after sound notification
115
- "enableTTSReminder": true,
116
-
117
- // Delay (in seconds) before TTS reminder fires
118
- "ttsReminderDelaySeconds": 30, // Global default
119
- "idleReminderDelaySeconds": 30, // For task completion notifications
120
- "permissionReminderDelaySeconds": 20, // For permission requests (more urgent)
121
-
99
+ ```jsonc
100
+ {
101
+ // ============================================================
102
+ // OpenCode Smart Voice Notify - Full Configuration Reference
103
+ // ============================================================
104
+ //
105
+ // IMPORTANT: This is a REFERENCE file showing ALL available options.
106
+ //
107
+ // To use this plugin:
108
+ // 1. Copy this file to: ~/.config/opencode/smart-voice-notify.jsonc
109
+ // (On Windows: C:\Users\<YourUser>\.config\opencode\smart-voice-notify.jsonc)
110
+ // 2. Customize the settings below to your preference
111
+ // 3. The plugin auto-creates a minimal config if none exists
112
+ //
113
+ // Sound files are automatically copied to ~/.config/opencode/assets/
114
+ // on first run. You can also use your own custom sound files.
115
+ //
116
+ // ============================================================
117
+
118
+ // ============================================================
119
+ // NOTIFICATION MODE SETTINGS (Smart Notification System)
120
+ // ============================================================
121
+ // Controls how notifications are delivered:
122
+ // 'sound-first' - Play sound immediately, TTS reminder after delay (RECOMMENDED)
123
+ // 'tts-first' - Speak TTS immediately, no sound
124
+ // 'both' - Play sound AND speak TTS immediately
125
+ // 'sound-only' - Only play sound, no TTS at all
126
+ "notificationMode": "sound-first",
127
+
128
+ // ============================================================
129
+ // TTS REMINDER SETTINGS (When user doesn't respond to sound)
130
+ // ============================================================
131
+
132
+ // Enable TTS reminder if user doesn't respond after sound notification
133
+ "enableTTSReminder": true,
134
+
135
+ // Delay (in seconds) before TTS reminder fires
136
+ // Set globally or per-notification type
137
+ "ttsReminderDelaySeconds": 30, // Global default
138
+ "idleReminderDelaySeconds": 30, // For task completion notifications
139
+ "permissionReminderDelaySeconds": 20, // For permission requests (more urgent)
140
+
122
141
  // Follow-up reminders if user STILL doesn't respond after first TTS
123
142
  "enableFollowUpReminders": true,
124
143
  "maxFollowUpReminders": 3, // Max number of follow-up TTS reminders
125
144
  "reminderBackoffMultiplier": 1.5, // Each follow-up waits longer (30s, 45s, 67s...)
126
-
145
+
127
146
  // ============================================================
128
147
  // PERMISSION BATCHING (Multiple permissions at once)
129
148
  // ============================================================
130
- // When multiple permissions arrive simultaneously, batch them into one notification
131
- "permissionBatchWindowMs": 800, // Batch window in milliseconds
132
-
149
+ // When multiple permissions arrive simultaneously (e.g., 5 at once),
150
+ // batch them into a single notification instead of playing 5 overlapping sounds.
151
+ // The notification will say "X permission requests require your attention".
152
+
153
+ // Batch window (ms) - how long to wait for more permissions before notifying
154
+ "permissionBatchWindowMs": 800,
155
+
133
156
  // ============================================================
134
157
  // TTS ENGINE SELECTION
135
158
  // ============================================================
136
- // 'elevenlabs' - Best quality, anime-like voices (requires API key)
159
+ // 'elevenlabs' - Best quality, anime-like voices (requires API key, free tier: 10k chars/month)
137
160
  // 'edge' - Good quality neural voices (Free, Native Node.js implementation)
138
- // 'sapi' - Windows built-in voices (free, offline)
139
- "ttsEngine": "edge",
161
+ // 'sapi' - Windows built-in voices (free, offline, robotic)
162
+ "ttsEngine": "elevenlabs",
163
+
164
+ // Enable TTS for notifications (falls back to sound files if TTS fails)
140
165
  "enableTTS": true,
141
-
142
- // ============================================================
143
- // ELEVENLABS SETTINGS (Best Quality - Anime-like Voices)
144
- // ============================================================
145
- // Get your API key from: https://elevenlabs.io/app/settings/api-keys
146
- // "elevenLabsApiKey": "YOUR_API_KEY_HERE",
147
- "elevenLabsVoiceId": "cgSgspJ2msm6clMCkdW9",
148
- "elevenLabsModel": "eleven_turbo_v2_5",
149
- "elevenLabsStability": 0.5,
150
- "elevenLabsSimilarity": 0.75,
151
- "elevenLabsStyle": 0.5,
152
-
153
- // ============================================================
154
- // EDGE TTS SETTINGS (Free Neural Voices - Default Engine)
155
- // ============================================================
156
- "edgeVoice": "en-US-AnaNeural",
157
- "edgePitch": "+50Hz",
158
- "edgeRate": "+10%",
159
-
160
- // ============================================================
161
- // SAPI SETTINGS (Windows Built-in - Last Resort Fallback)
162
- // ============================================================
163
- "sapiVoice": "Microsoft Zira Desktop",
164
- "sapiRate": -1,
165
- "sapiPitch": "medium",
166
- "sapiVolume": "loud",
167
-
168
- // ============================================================
169
- // INITIAL TTS MESSAGES (Used immediately or after sound)
170
- // ============================================================
171
- "idleTTSMessages": [
172
- "All done! Your task has been completed successfully.",
173
- "Hey there! I finished working on your request.",
174
- "Task complete! Ready for your review whenever you are.",
175
- "Good news! Everything is done and ready for you.",
176
- "Finished! Let me know if you need anything else."
177
- ],
166
+
167
+ // ============================================================
168
+ // ELEVENLABS SETTINGS (Best Quality - Anime-like Voices)
169
+ // ============================================================
170
+ // Get your API key from: https://elevenlabs.io/app/settings/api-keys
171
+ // Free tier: 10,000 characters/month
172
+ "elevenLabsApiKey": "YOUR_API_KEY_HERE",
173
+
174
+ // Voice ID - Recommended cute/anime-like voices:
175
+ // 'cgSgspJ2msm6clMCkdW9' - Jessica (Playful, Bright, Warm) - RECOMMENDED
176
+ // 'FGY2WhTYpPnrIDTdsKH5' - Laura (Enthusiast, Quirky)
177
+ // 'jsCqWAovK2LkecY7zXl4' - Freya (Expressive, Confident)
178
+ // 'EXAVITQu4vr4xnSDxMaL' - Sarah (Soft, Warm)
179
+ // Browse more at: https://elevenlabs.io/voice-library
180
+ "elevenLabsVoiceId": "cgSgspJ2msm6clMCkdW9",
181
+
182
+ // Model: 'eleven_turbo_v2_5' (fast, good), 'eleven_multilingual_v2' (highest quality)
183
+ "elevenLabsModel": "eleven_turbo_v2_5",
184
+
185
+ // Voice tuning (0.0 to 1.0)
186
+ "elevenLabsStability": 0.5, // Lower = more expressive, Higher = more consistent
187
+ "elevenLabsSimilarity": 0.75, // How closely to match the original voice
188
+ "elevenLabsStyle": 0.5, // Style exaggeration (higher = more expressive)
189
+
190
+ // ============================================================
191
+ // EDGE TTS SETTINGS (Free Neural Voices - Fallback)
192
+ // ============================================================
193
+ // Native Node.js implementation (No external dependencies)
194
+
195
+ // Voice options (run 'edge-tts --list-voices' to see all):
196
+ // 'en-US-AnaNeural' - Young, cute, cartoon-like (RECOMMENDED)
197
+ // 'en-US-JennyNeural' - Friendly, warm
198
+ // 'en-US-AriaNeural' - Confident, clear
199
+ // 'en-GB-SoniaNeural' - British, friendly
200
+ // 'en-AU-NatashaNeural' - Australian, warm
201
+ "edgeVoice": "en-US-AnaNeural",
202
+
203
+ // Pitch adjustment: +0Hz to +100Hz (higher = more anime-like)
204
+ "edgePitch": "+50Hz",
205
+
206
+ // Speech rate: -50% to +100%
207
+ "edgeRate": "+10%",
208
+
209
+ // ============================================================
210
+ // SAPI SETTINGS (Windows Built-in - Last Resort Fallback)
211
+ // ============================================================
212
+
213
+ // Voice (run PowerShell to list all installed voices):
214
+ // Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).GetInstalledVoices() | % { $_.VoiceInfo.Name }
215
+ //
216
+ // Common Windows voices:
217
+ // 'Microsoft Zira Desktop' - Female, US English
218
+ // 'Microsoft David Desktop' - Male, US English
219
+ // 'Microsoft Hazel Desktop' - Female, UK English
220
+ "sapiVoice": "Microsoft Zira Desktop",
221
+
222
+ // Speech rate: -10 (slowest) to +10 (fastest), 0 is normal
223
+ "sapiRate": -1,
224
+
225
+ // Pitch: 'x-low', 'low', 'medium', 'high', 'x-high'
226
+ "sapiPitch": "medium",
227
+
228
+ // Volume: 'silent', 'x-soft', 'soft', 'medium', 'loud', 'x-loud'
229
+ "sapiVolume": "loud",
230
+
231
+ // ============================================================
232
+ // INITIAL TTS MESSAGES (Used immediately or after sound)
233
+ // These are randomly selected each time for variety
234
+ // ============================================================
235
+
236
+ // Messages when agent finishes work (task completion)
237
+ "idleTTSMessages": [
238
+ "All done! Your task has been completed successfully.",
239
+ "Hey there! I finished working on your request.",
240
+ "Task complete! Ready for your review whenever you are.",
241
+ "Good news! Everything is done and ready for you.",
242
+ "Finished! Let me know if you need anything else."
243
+ ],
244
+
245
+ // Messages for permission requests
178
246
  "permissionTTSMessages": [
179
247
  "Attention please! I need your permission to continue.",
180
248
  "Hey! Quick approval needed to proceed with the task.",
@@ -182,22 +250,32 @@ If you prefer to create the config manually, add a `smart-voice-notify.jsonc` fi
182
250
  "Excuse me! I need your authorization before I can continue.",
183
251
  "Permission required! Please review and approve when ready."
184
252
  ],
253
+
185
254
  // Messages for MULTIPLE permission requests (use {count} placeholder)
255
+ // Used when several permissions arrive simultaneously
186
256
  "permissionTTSMessagesMultiple": [
187
257
  "Attention please! There are {count} permission requests waiting for your approval.",
188
- "Hey! {count} permissions need your approval to continue."
258
+ "Hey! {count} permissions need your approval to continue.",
259
+ "Heads up! You have {count} pending permission requests.",
260
+ "Excuse me! I need your authorization for {count} different actions.",
261
+ "{count} permissions required! Please review and approve when ready."
189
262
  ],
190
-
191
- // ============================================================
192
- // TTS REMINDER MESSAGES (Used after delay if no response)
193
- // ============================================================
194
- "idleReminderTTSMessages": [
195
- "Hey, are you still there? Your task has been waiting for review.",
196
- "Just a gentle reminder - I finished your request a while ago!",
197
- "Hello? I completed your task. Please take a look when you can.",
198
- "Still waiting for you! The work is done and ready for review.",
199
- "Knock knock! Your completed task is patiently waiting for you."
200
- ],
263
+
264
+ // ============================================================
265
+ // TTS REMINDER MESSAGES (More urgent - used after delay if no response)
266
+ // These are more personalized and urgent to get user attention
267
+ // ============================================================
268
+
269
+ // Reminder messages when agent finished but user hasn't responded
270
+ "idleReminderTTSMessages": [
271
+ "Hey, are you still there? Your task has been waiting for review.",
272
+ "Just a gentle reminder - I finished your request a while ago!",
273
+ "Hello? I completed your task. Please take a look when you can.",
274
+ "Still waiting for you! The work is done and ready for review.",
275
+ "Knock knock! Your completed task is patiently waiting for you."
276
+ ],
277
+
278
+ // Reminder messages when permission still needed
201
279
  "permissionReminderTTSMessages": [
202
280
  "Hey! I still need your permission to continue. Please respond!",
203
281
  "Reminder: There is a pending permission request. I cannot proceed without you.",
@@ -205,30 +283,105 @@ If you prefer to create the config manually, add a `smart-voice-notify.jsonc` fi
205
283
  "Please check your screen! I really need your permission to move forward.",
206
284
  "Still waiting for authorization! The task is on hold until you respond."
207
285
  ],
286
+
208
287
  // Reminder messages for MULTIPLE permissions (use {count} placeholder)
209
288
  "permissionReminderTTSMessagesMultiple": [
210
289
  "Hey! I still need your approval for {count} permissions. Please respond!",
211
- "Reminder: There are {count} pending permission requests."
290
+ "Reminder: There are {count} pending permission requests. I cannot proceed without you.",
291
+ "Hello? I am waiting for your approval on {count} items. This is getting urgent!",
292
+ "Please check your screen! {count} permissions are waiting for your response.",
293
+ "Still waiting for authorization on {count} requests! The task is on hold."
212
294
  ],
213
-
214
- // ============================================================
215
- // SOUND FILES (relative to OpenCode config directory)
216
- // ============================================================
217
- "idleSound": "assets/Soft-high-tech-notification-sound-effect.mp3",
218
- "permissionSound": "assets/Machine-alert-beep-sound-effect.mp3",
219
-
220
- // ============================================================
221
- // GENERAL SETTINGS
222
- // ============================================================
223
- "wakeMonitor": true,
224
- "forceVolume": true,
225
- "volumeThreshold": 50,
226
- "enableToast": true,
227
- "enableSound": true,
228
- "idleThresholdSeconds": 60,
229
- "debugLog": false
230
- }
231
- ```
295
+
296
+ // ============================================================
297
+ // QUESTION TOOL MESSAGES (SDK v1.1.7+ - Agent asking user questions)
298
+ // ============================================================
299
+ // The "question" tool allows the LLM to ask users questions during execution.
300
+ // This is useful for gathering preferences, clarifying instructions, or getting
301
+ // decisions on implementation choices.
302
+
303
+ // Messages when agent asks user a question
304
+ "questionTTSMessages": [
305
+ "Hey! I have a question for you. Please check your screen.",
306
+ "Attention! I need your input to continue.",
307
+ "Quick question! Please take a look when you have a moment.",
308
+ "I need some clarification. Could you please respond?",
309
+ "Question time! Your input is needed to proceed."
310
+ ],
311
+
312
+ // Messages for MULTIPLE questions (use {count} placeholder)
313
+ "questionTTSMessagesMultiple": [
314
+ "Hey! I have {count} questions for you. Please check your screen.",
315
+ "Attention! I need your input on {count} items to continue.",
316
+ "{count} questions need your attention. Please take a look!",
317
+ "I need some clarifications. There are {count} questions waiting for you.",
318
+ "Question time! {count} questions need your response to proceed."
319
+ ],
320
+
321
+ // Reminder messages for questions (more urgent - used after delay)
322
+ "questionReminderTTSMessages": [
323
+ "Hey! I am still waiting for your answer. Please check the questions!",
324
+ "Reminder: There is a question waiting for your response.",
325
+ "Hello? I need your input to continue. Please respond when you can.",
326
+ "Still waiting for your answer! The task is on hold.",
327
+ "Your input is needed! Please check the pending question."
328
+ ],
329
+
330
+ // Reminder messages for MULTIPLE questions (use {count} placeholder)
331
+ "questionReminderTTSMessagesMultiple": [
332
+ "Hey! I am still waiting for answers to {count} questions. Please respond!",
333
+ "Reminder: There are {count} questions waiting for your response.",
334
+ "Hello? I need your input on {count} items. Please respond when you can.",
335
+ "Still waiting for your answers on {count} questions! The task is on hold.",
336
+ "Your input is needed! {count} questions are pending your response."
337
+ ],
338
+
339
+ // Delay (in seconds) before question reminder fires
340
+ "questionReminderDelaySeconds": 25,
341
+
342
+ // Question batch window (ms) - how long to wait for more questions before notifying
343
+ "questionBatchWindowMs": 800,
344
+
345
+ // ============================================================
346
+ // SOUND FILES (For immediate notifications)
347
+ // These are played first before TTS reminder kicks in
348
+ // ============================================================
349
+ // Paths are relative to ~/.config/opencode/ directory
350
+ // The plugin automatically copies bundled sounds to assets/ on first run
351
+ // You can replace with your own custom MP3/WAV files
352
+
353
+ "idleSound": "assets/Soft-high-tech-notification-sound-effect.mp3",
354
+ "permissionSound": "assets/Machine-alert-beep-sound-effect.mp3",
355
+ "questionSound": "assets/Machine-alert-beep-sound-effect.mp3",
356
+
357
+ // ============================================================
358
+ // GENERAL SETTINGS
359
+ // ============================================================
360
+
361
+ // Wake monitor from sleep when notifying (Windows/macOS)
362
+ "wakeMonitor": true,
363
+
364
+ // Force system volume up if below threshold
365
+ "forceVolume": true,
366
+
367
+ // Volume threshold (0-100): force volume if current level is below this
368
+ "volumeThreshold": 50,
369
+
370
+ // Show TUI toast notifications in OpenCode terminal
371
+ "enableToast": true,
372
+
373
+ // Enable audio notifications (sound files and TTS)
374
+ "enableSound": true,
375
+
376
+ // Consider monitor asleep after this many seconds of inactivity (Windows only)
377
+ "idleThresholdSeconds": 60,
378
+
379
+ // Enable debug logging to ~/.config/opencode/logs/smart-voice-notify-debug.log
380
+ // The logs folder is created automatically when debug logging is enabled
381
+ // Useful for troubleshooting notification issues
382
+ "debugLog": false
383
+ }
384
+ ```
232
385
 
233
386
  See `example.config.jsonc` for more details.
234
387
 
@@ -257,10 +410,13 @@ See `example.config.jsonc` for more details.
257
410
  | `permission.asked` | Permission request (SDK v1.1.1+) - alert user |
258
411
  | `permission.updated` | Permission request (SDK v1.0.x) - alert user |
259
412
  | `permission.replied` | User responded - cancel pending reminders |
413
+ | `question.asked` | Agent asks question (SDK v1.1.7+) - notify user |
414
+ | `question.replied` | User answered question - cancel pending reminders |
415
+ | `question.rejected` | User dismissed question - cancel pending reminders |
260
416
  | `message.updated` | New user message - cancel pending reminders |
261
417
  | `session.created` | New session - reset state |
262
418
 
263
- > **Note**: The plugin supports both OpenCode SDK v1.0.x and v1.1.x for backward compatibility.
419
+ > **Note**: The plugin supports OpenCode SDK v1.0.x, v1.1.x, and v1.1.7+ for backward compatibility.
264
420
 
265
421
  ## Development
266
422
 
@@ -16,6 +16,13 @@
16
16
  //
17
17
  // ============================================================
18
18
 
19
+ // ============================================================
20
+ // PLUGIN ENABLE/DISABLE
21
+ // ============================================================
22
+ // Master switch to enable or disable the entire plugin.
23
+ // Set to false to disable all notifications without uninstalling.
24
+ "enabled": true,
25
+
19
26
  // ============================================================
20
27
  // NOTIFICATION MODE SETTINGS (Smart Notification System)
21
28
  // ============================================================
@@ -194,6 +201,55 @@
194
201
  "Still waiting for authorization on {count} requests! The task is on hold."
195
202
  ],
196
203
 
204
+ // ============================================================
205
+ // QUESTION TOOL MESSAGES (SDK v1.1.7+ - Agent asking user questions)
206
+ // ============================================================
207
+ // The "question" tool allows the LLM to ask users questions during execution.
208
+ // This is useful for gathering preferences, clarifying instructions, or getting
209
+ // decisions on implementation choices.
210
+
211
+ // Messages when agent asks user a question
212
+ "questionTTSMessages": [
213
+ "Hey! I have a question for you. Please check your screen.",
214
+ "Attention! I need your input to continue.",
215
+ "Quick question! Please take a look when you have a moment.",
216
+ "I need some clarification. Could you please respond?",
217
+ "Question time! Your input is needed to proceed."
218
+ ],
219
+
220
+ // Messages for MULTIPLE questions (use {count} placeholder)
221
+ "questionTTSMessagesMultiple": [
222
+ "Hey! I have {count} questions for you. Please check your screen.",
223
+ "Attention! I need your input on {count} items to continue.",
224
+ "{count} questions need your attention. Please take a look!",
225
+ "I need some clarifications. There are {count} questions waiting for you.",
226
+ "Question time! {count} questions need your response to proceed."
227
+ ],
228
+
229
+ // Reminder messages for questions (more urgent - used after delay)
230
+ "questionReminderTTSMessages": [
231
+ "Hey! I am still waiting for your answer. Please check the questions!",
232
+ "Reminder: There is a question waiting for your response.",
233
+ "Hello? I need your input to continue. Please respond when you can.",
234
+ "Still waiting for your answer! The task is on hold.",
235
+ "Your input is needed! Please check the pending question."
236
+ ],
237
+
238
+ // Reminder messages for MULTIPLE questions (use {count} placeholder)
239
+ "questionReminderTTSMessagesMultiple": [
240
+ "Hey! I am still waiting for answers to {count} questions. Please respond!",
241
+ "Reminder: There are {count} questions waiting for your response.",
242
+ "Hello? I need your input on {count} items. Please respond when you can.",
243
+ "Still waiting for your answers on {count} questions! The task is on hold.",
244
+ "Your input is needed! {count} questions are pending your response."
245
+ ],
246
+
247
+ // Delay (in seconds) before question reminder fires
248
+ "questionReminderDelaySeconds": 25,
249
+
250
+ // Question batch window (ms) - how long to wait for more questions before notifying
251
+ "questionBatchWindowMs": 800,
252
+
197
253
  // ============================================================
198
254
  // SOUND FILES (For immediate notifications)
199
255
  // These are played first before TTS reminder kicks in
@@ -204,6 +260,7 @@
204
260
 
205
261
  "idleSound": "assets/Soft-high-tech-notification-sound-effect.mp3",
206
262
  "permissionSound": "assets/Machine-alert-beep-sound-effect.mp3",
263
+ "questionSound": "assets/Machine-alert-beep-sound-effect.mp3",
207
264
 
208
265
  // ============================================================
209
266
  // GENERAL SETTINGS
package/index.js CHANGED
@@ -23,9 +23,28 @@ import { createTTS, getTTSConfig } from './util/tts.js';
23
23
  */
24
24
  export default async function SmartVoiceNotifyPlugin({ project, client, $, directory, worktree }) {
25
25
  const config = getTTSConfig();
26
+
27
+ // Master switch: if plugin is disabled, return empty handlers immediately
28
+ if (config.enabled === false) {
29
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), '.config', 'opencode');
30
+ const logsDir = path.join(configDir, 'logs');
31
+ const logFile = path.join(logsDir, 'smart-voice-notify-debug.log');
32
+ if (config.debugLog) {
33
+ try {
34
+ if (!fs.existsSync(logsDir)) {
35
+ fs.mkdirSync(logsDir, { recursive: true });
36
+ }
37
+ const timestamp = new Date().toISOString();
38
+ fs.appendFileSync(logFile, `[${timestamp}] Plugin disabled via config (enabled: false) - no event handlers registered\n`);
39
+ } catch (e) {}
40
+ }
41
+ return {};
42
+ }
43
+
26
44
  const tts = createTTS({ $, client });
27
45
 
28
46
  const platform = os.platform();
47
+
29
48
  const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), '.config', 'opencode');
30
49
  const logsDir = path.join(configDir, 'logs');
31
50
  const logFile = path.join(logsDir, 'smart-voice-notify-debug.log');
@@ -71,6 +90,25 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
71
90
  // Batch window duration in milliseconds (how long to wait for more permissions)
72
91
  const PERMISSION_BATCH_WINDOW_MS = config.permissionBatchWindowMs || 800;
73
92
 
93
+ // ========================================
94
+ // QUESTION BATCHING STATE (SDK v1.1.7+)
95
+ // Batches multiple simultaneous question requests into a single notification
96
+ // ========================================
97
+
98
+ // Array of question request objects waiting to be notified (collected during batch window)
99
+ // Each object contains { id: string, questionCount: number } to track actual question count
100
+ let pendingQuestionBatch = [];
101
+
102
+ // Timeout ID for the question batch window (debounce timer)
103
+ let questionBatchTimeout = null;
104
+
105
+ // Batch window duration in milliseconds (how long to wait for more questions)
106
+ const QUESTION_BATCH_WINDOW_MS = config.questionBatchWindowMs || 800;
107
+
108
+ // Track active question request to prevent race condition where user responds
109
+ // before async notification code runs. Set on question.asked, cleared on question.replied/rejected.
110
+ let activeQuestionId = null;
111
+
74
112
  /**
75
113
  * Write debug message to log file
76
114
  */
@@ -160,9 +198,9 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
160
198
  /**
161
199
  * Schedule a TTS reminder if user doesn't respond within configured delay.
162
200
  * The reminder uses a personalized TTS message.
163
- * @param {string} type - 'idle' or 'permission'
201
+ * @param {string} type - 'idle', 'permission', or 'question'
164
202
  * @param {string} message - The TTS message to speak (used directly, supports count-aware messages)
165
- * @param {object} options - Additional options (fallbackSound, permissionCount)
203
+ * @param {object} options - Additional options (fallbackSound, permissionCount, questionCount)
166
204
  */
167
205
  const scheduleTTSReminder = (type, message, options = {}) => {
168
206
  // Check if TTS reminders are enabled
@@ -172,18 +210,23 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
172
210
  }
173
211
 
174
212
  // Get delay from config (in seconds, convert to ms)
175
- const delaySeconds = type === 'permission'
176
- ? (config.permissionReminderDelaySeconds || config.ttsReminderDelaySeconds || 30)
177
- : (config.idleReminderDelaySeconds || config.ttsReminderDelaySeconds || 30);
213
+ let delaySeconds;
214
+ if (type === 'permission') {
215
+ delaySeconds = config.permissionReminderDelaySeconds || config.ttsReminderDelaySeconds || 30;
216
+ } else if (type === 'question') {
217
+ delaySeconds = config.questionReminderDelaySeconds || config.ttsReminderDelaySeconds || 25;
218
+ } else {
219
+ delaySeconds = config.idleReminderDelaySeconds || config.ttsReminderDelaySeconds || 30;
220
+ }
178
221
  const delayMs = delaySeconds * 1000;
179
222
 
180
223
  // Cancel any existing reminder of this type
181
224
  cancelPendingReminder(type);
182
225
 
183
- // Store permission count for generating count-aware messages in reminders
184
- const permissionCount = options.permissionCount || 1;
226
+ // Store count for generating count-aware messages in reminders
227
+ const itemCount = options.permissionCount || options.questionCount || 1;
185
228
 
186
- debugLog(`scheduleTTSReminder: scheduling ${type} TTS in ${delaySeconds}s (count=${permissionCount})`);
229
+ debugLog(`scheduleTTSReminder: scheduling ${type} TTS in ${delaySeconds}s (count=${itemCount})`);
187
230
 
188
231
  const timeoutId = setTimeout(async () => {
189
232
  try {
@@ -201,14 +244,16 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
201
244
  return;
202
245
  }
203
246
 
204
- debugLog(`scheduleTTSReminder: firing ${type} TTS reminder (count=${reminder?.permissionCount || 1})`);
247
+ debugLog(`scheduleTTSReminder: firing ${type} TTS reminder (count=${reminder?.itemCount || 1})`);
205
248
 
206
249
  // Get the appropriate reminder message
207
- // For permissions with count > 1, use the count-aware message generator
208
- const storedCount = reminder?.permissionCount || 1;
250
+ // For permissions/questions with count > 1, use the count-aware message generator
251
+ const storedCount = reminder?.itemCount || 1;
209
252
  let reminderMessage;
210
253
  if (type === 'permission') {
211
254
  reminderMessage = getPermissionMessage(storedCount, true);
255
+ } else if (type === 'question') {
256
+ reminderMessage = getQuestionMessage(storedCount, true);
212
257
  } else {
213
258
  reminderMessage = getRandomMessage(config.idleReminderTTSMessages);
214
259
  }
@@ -257,10 +302,12 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
257
302
  }
258
303
 
259
304
  // Use count-aware message for follow-ups too
260
- const followUpStoredCount = followUpReminder?.permissionCount || 1;
305
+ const followUpStoredCount = followUpReminder?.itemCount || 1;
261
306
  let followUpMessage;
262
307
  if (type === 'permission') {
263
308
  followUpMessage = getPermissionMessage(followUpStoredCount, true);
309
+ } else if (type === 'question') {
310
+ followUpMessage = getQuestionMessage(followUpStoredCount, true);
264
311
  } else {
265
312
  followUpMessage = getRandomMessage(config.idleReminderTTSMessages);
266
313
  }
@@ -279,7 +326,7 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
279
326
  timeoutId: followUpTimeoutId,
280
327
  scheduledAt: Date.now(),
281
328
  followUpCount,
282
- permissionCount: storedCount // Preserve the count for follow-ups
329
+ itemCount: storedCount // Preserve the count for follow-ups
283
330
  });
284
331
  }
285
332
  }
@@ -289,18 +336,18 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
289
336
  }
290
337
  }, delayMs);
291
338
 
292
- // Store the pending reminder with permission count
339
+ // Store the pending reminder with item count
293
340
  pendingReminders.set(type, {
294
341
  timeoutId,
295
342
  scheduledAt: Date.now(),
296
343
  followUpCount: 0,
297
- permissionCount // Store count for later use
344
+ itemCount // Store count for later use
298
345
  });
299
346
  };
300
347
 
301
348
  /**
302
349
  * Smart notification: play sound first, then schedule TTS reminder
303
- * @param {string} type - 'idle' or 'permission'
350
+ * @param {string} type - 'idle', 'permission', or 'question'
304
351
  * @param {object} options - Notification options
305
352
  */
306
353
  const smartNotify = async (type, options = {}) => {
@@ -309,7 +356,8 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
309
356
  soundLoops = 1,
310
357
  ttsMessage,
311
358
  fallbackSound,
312
- permissionCount = 1 // Support permission count for batched notifications
359
+ permissionCount = 1, // Support permission count for batched notifications
360
+ questionCount = 1 // Support question count for batched notifications
313
361
  } = options;
314
362
 
315
363
  // Step 1: Play the immediate sound notification
@@ -328,17 +376,27 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
328
376
  debugLog(`smartNotify: permission handled during sound - aborting reminder`);
329
377
  return;
330
378
  }
379
+ // For question notifications: check if the question was already answered/rejected
380
+ if (type === 'question' && !activeQuestionId) {
381
+ debugLog(`smartNotify: question handled during sound - aborting reminder`);
382
+ return;
383
+ }
331
384
 
332
385
  // Step 2: Schedule TTS reminder if user doesn't respond
333
386
  if (config.enableTTSReminder && ttsMessage) {
334
- scheduleTTSReminder(type, ttsMessage, { fallbackSound, permissionCount });
387
+ scheduleTTSReminder(type, ttsMessage, { fallbackSound, permissionCount, questionCount });
335
388
  }
336
389
 
337
390
  // Step 3: If TTS-first mode is enabled, also speak immediately
338
391
  if (config.notificationMode === 'tts-first' || config.notificationMode === 'both') {
339
- const immediateMessage = type === 'permission'
340
- ? getRandomMessage(config.permissionTTSMessages)
341
- : getRandomMessage(config.idleTTSMessages);
392
+ let immediateMessage;
393
+ if (type === 'permission') {
394
+ immediateMessage = getRandomMessage(config.permissionTTSMessages);
395
+ } else if (type === 'question') {
396
+ immediateMessage = getRandomMessage(config.questionTTSMessages);
397
+ } else {
398
+ immediateMessage = getRandomMessage(config.idleTTSMessages);
399
+ }
342
400
 
343
401
  await tts.speak(immediateMessage, {
344
402
  enableTTS: true,
@@ -378,6 +436,37 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
378
436
  }
379
437
  };
380
438
 
439
+ /**
440
+ * Get a count-aware TTS message for question requests (SDK v1.1.7+)
441
+ * @param {number} count - Number of question requests
442
+ * @param {boolean} isReminder - Whether this is a reminder message
443
+ * @returns {string} The formatted message
444
+ */
445
+ const getQuestionMessage = (count, isReminder = false) => {
446
+ const messages = isReminder
447
+ ? config.questionReminderTTSMessages
448
+ : config.questionTTSMessages;
449
+
450
+ if (count === 1) {
451
+ // Single question - use regular message
452
+ return getRandomMessage(messages);
453
+ } else {
454
+ // Multiple questions - use count-aware messages if available, or format dynamically
455
+ const countMessages = isReminder
456
+ ? config.questionReminderTTSMessagesMultiple
457
+ : config.questionTTSMessagesMultiple;
458
+
459
+ if (countMessages && countMessages.length > 0) {
460
+ // Use configured multi-question messages (replace {count} placeholder)
461
+ const template = getRandomMessage(countMessages);
462
+ return template.replace('{count}', count.toString());
463
+ } else {
464
+ // Fallback: generate a dynamic message
465
+ return `Hey! I have ${count} questions for you. Please check your screen.`;
466
+ }
467
+ }
468
+ };
469
+
381
470
  /**
382
471
  * Process the batched permission requests as a single notification
383
472
  * Called after the batch window expires
@@ -449,6 +538,81 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
449
538
  }
450
539
  };
451
540
 
541
+ /**
542
+ * Process the batched question requests as a single notification (SDK v1.1.7+)
543
+ * Called after the batch window expires
544
+ */
545
+ const processQuestionBatch = async () => {
546
+ // Capture and clear the batch
547
+ const batch = [...pendingQuestionBatch];
548
+ pendingQuestionBatch = [];
549
+ questionBatchTimeout = null;
550
+
551
+ if (batch.length === 0) {
552
+ debugLog('processQuestionBatch: empty batch, skipping');
553
+ return;
554
+ }
555
+
556
+ // Calculate total number of questions across all batched requests
557
+ // Each batch item is { id, questionCount } where questionCount is the number of questions in that request
558
+ const totalQuestionCount = batch.reduce((sum, item) => sum + (item.questionCount || 1), 0);
559
+
560
+ debugLog(`processQuestionBatch: processing ${batch.length} request(s) with ${totalQuestionCount} total question(s)`);
561
+
562
+ // Set activeQuestionId to the first one (for race condition checks)
563
+ // We track all IDs in the batch for proper cleanup
564
+ activeQuestionId = batch[0]?.id;
565
+
566
+ // Show toast with count
567
+ const toastMessage = totalQuestionCount === 1
568
+ ? "❓ The agent has a question for you"
569
+ : `❓ The agent has ${totalQuestionCount} questions for you`;
570
+ await showToast(toastMessage, "info", 8000);
571
+
572
+ // CHECK: Did user already respond while we were showing toast?
573
+ if (pendingQuestionBatch.length > 0) {
574
+ // New questions arrived during toast - they'll be handled in next batch
575
+ debugLog('processQuestionBatch: new questions arrived during toast');
576
+ }
577
+
578
+ // Check if any question was already replied to or rejected
579
+ if (activeQuestionId === null) {
580
+ debugLog('processQuestionBatch: aborted - user already responded');
581
+ return;
582
+ }
583
+
584
+ // Get count-aware TTS message (uses total question count, not request count)
585
+ const ttsMessage = getQuestionMessage(totalQuestionCount, false);
586
+ const reminderMessage = getQuestionMessage(totalQuestionCount, true);
587
+
588
+ // Smart notification: sound first, TTS reminder later
589
+ // Sound plays 2 times by default (matching permission behavior)
590
+ await smartNotify('question', {
591
+ soundFile: config.questionSound,
592
+ soundLoops: 2, // Fixed at 2 loops to match permission sound behavior
593
+ ttsMessage: reminderMessage,
594
+ fallbackSound: config.questionSound,
595
+ // Pass count for use in reminders
596
+ questionCount: totalQuestionCount
597
+ });
598
+
599
+ // Speak immediately if in TTS-first or both mode (with count-aware message)
600
+ if (config.notificationMode === 'tts-first' || config.notificationMode === 'both') {
601
+ await tts.wakeMonitor();
602
+ await tts.forceVolume();
603
+ await tts.speak(ttsMessage, {
604
+ enableTTS: true,
605
+ fallbackSound: config.questionSound
606
+ });
607
+ }
608
+
609
+ // Final check: if user responded during notification, cancel scheduled reminder
610
+ if (activeQuestionId === null) {
611
+ debugLog('processQuestionBatch: user responded during notification - cancelling reminder');
612
+ cancelPendingReminder('question');
613
+ }
614
+ };
615
+
452
616
  return {
453
617
  event: async ({ event }) => {
454
618
  try {
@@ -456,13 +620,16 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
456
620
  // USER ACTIVITY DETECTION
457
621
  // Cancels pending TTS reminders when user responds
458
622
  // ========================================
459
- // NOTE: OpenCode event types (supporting SDK v1.0.x and v1.1.x):
623
+ // NOTE: OpenCode event types (supporting SDK v1.0.x, v1.1.x, and v1.1.7+):
460
624
  // - message.updated: fires when a message is added/updated (use properties.info.role to check user vs assistant)
461
625
  // - permission.updated (SDK v1.0.x): fires when a permission request is created
462
626
  // - permission.asked (SDK v1.1.1+): fires when a permission request is created (replaces permission.updated)
463
627
  // - permission.replied: fires when user responds to a permission request
464
628
  // - SDK v1.0.x: uses permissionID, response
465
629
  // - SDK v1.1.1+: uses requestID, reply
630
+ // - question.asked (SDK v1.1.7+): fires when agent asks user a question
631
+ // - question.replied (SDK v1.1.7+): fires when user answers a question
632
+ // - question.rejected (SDK v1.1.7+): fires when user dismisses a question
466
633
  // - session.created: fires when a new session starts
467
634
  //
468
635
  // CRITICAL: message.updated fires for EVERY modification to a message (not just creation).
@@ -546,6 +713,7 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
546
713
  lastUserActivityTime = Date.now();
547
714
  lastSessionIdleTime = 0;
548
715
  activePermissionId = null;
716
+ activeQuestionId = null;
549
717
  seenUserMessageIds.clear();
550
718
  cancelAllPendingReminders();
551
719
 
@@ -556,6 +724,13 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
556
724
  permissionBatchTimeout = null;
557
725
  }
558
726
 
727
+ // Reset question batch state
728
+ pendingQuestionBatch = [];
729
+ if (questionBatchTimeout) {
730
+ clearTimeout(questionBatchTimeout);
731
+ questionBatchTimeout = null;
732
+ }
733
+
559
734
  debugLog(`Session created: ${event.type} - reset all tracking state`);
560
735
  }
561
736
 
@@ -633,6 +808,113 @@ export default async function SmartVoiceNotifyPlugin({ project, client, $, direc
633
808
 
634
809
  debugLog(`${event.type}: batch window reset (will process in ${PERMISSION_BATCH_WINDOW_MS}ms if no more arrive)`);
635
810
  }
811
+
812
+ // ========================================
813
+ // NOTIFICATION 3: Question Request (BATCHED) - SDK v1.1.7+
814
+ // ========================================
815
+ // The "question" tool allows the LLM to ask users questions during execution.
816
+ // Events: question.asked, question.replied, question.rejected
817
+ //
818
+ // BATCHING: When multiple question requests arrive simultaneously,
819
+ // we batch them into a single notification instead of playing overlapping sounds.
820
+ // NOTE: Each question.asked event can contain multiple questions in its questions array.
821
+ if (event.type === "question.asked") {
822
+ // Capture question request ID and count of questions in this request
823
+ const questionId = event.properties?.id;
824
+ const questionsArray = event.properties?.questions;
825
+ const questionCount = Array.isArray(questionsArray) ? questionsArray.length : 1;
826
+
827
+ if (!questionId) {
828
+ debugLog(`${event.type}: question ID missing. properties keys: ` + Object.keys(event.properties || {}).join(', '));
829
+ }
830
+
831
+ // Add to the pending batch (avoid duplicates by checking ID)
832
+ // Store as object with id and questionCount for proper counting
833
+ const existingIndex = pendingQuestionBatch.findIndex(item => item.id === questionId);
834
+ if (questionId && existingIndex === -1) {
835
+ pendingQuestionBatch.push({ id: questionId, questionCount });
836
+ debugLog(`${event.type}: added ${questionId} with ${questionCount} question(s) to batch (now ${pendingQuestionBatch.length} request(s) pending)`);
837
+ } else if (!questionId) {
838
+ // If no ID, still count it (use a placeholder)
839
+ pendingQuestionBatch.push({ id: `unknown-${Date.now()}`, questionCount });
840
+ debugLog(`${event.type}: added unknown question request with ${questionCount} question(s) to batch (now ${pendingQuestionBatch.length} request(s) pending)`);
841
+ }
842
+
843
+ // Reset the batch window timer (debounce)
844
+ // This gives more questions a chance to arrive before we notify
845
+ if (questionBatchTimeout) {
846
+ clearTimeout(questionBatchTimeout);
847
+ }
848
+
849
+ questionBatchTimeout = setTimeout(async () => {
850
+ try {
851
+ await processQuestionBatch();
852
+ } catch (e) {
853
+ debugLog(`processQuestionBatch error: ${e.message}`);
854
+ }
855
+ }, QUESTION_BATCH_WINDOW_MS);
856
+
857
+ debugLog(`${event.type}: batch window reset (will process in ${QUESTION_BATCH_WINDOW_MS}ms if no more arrive)`);
858
+ }
859
+
860
+ // Handle question.replied - user answered the question(s)
861
+ if (event.type === "question.replied") {
862
+ const repliedQuestionId = event.properties?.requestID;
863
+ const answers = event.properties?.answers;
864
+
865
+ // Remove this question from the pending batch (if still waiting)
866
+ // pendingQuestionBatch is now an array of { id, questionCount } objects
867
+ const existingIndex = pendingQuestionBatch.findIndex(item => item.id === repliedQuestionId);
868
+ if (repliedQuestionId && existingIndex !== -1) {
869
+ pendingQuestionBatch.splice(existingIndex, 1);
870
+ debugLog(`Question replied: removed ${repliedQuestionId} from pending batch (${pendingQuestionBatch.length} remaining)`);
871
+ }
872
+
873
+ // If batch is now empty and we have a pending batch timeout, we can cancel it
874
+ if (pendingQuestionBatch.length === 0 && questionBatchTimeout) {
875
+ clearTimeout(questionBatchTimeout);
876
+ questionBatchTimeout = null;
877
+ debugLog('Question replied: cancelled batch timeout (all questions handled)');
878
+ }
879
+
880
+ // Clear active question ID
881
+ if (activeQuestionId === repliedQuestionId || activeQuestionId === undefined) {
882
+ activeQuestionId = null;
883
+ debugLog(`Question replied: cleared activeQuestionId ${repliedQuestionId || '(unknown)'}`);
884
+ }
885
+ lastUserActivityTime = Date.now();
886
+ cancelPendingReminder('question'); // Cancel question-specific reminder
887
+ debugLog(`Question replied: ${event.type} (answers=${JSON.stringify(answers)}) - cancelled question reminder`);
888
+ }
889
+
890
+ // Handle question.rejected - user dismissed the question
891
+ if (event.type === "question.rejected") {
892
+ const rejectedQuestionId = event.properties?.requestID;
893
+
894
+ // Remove this question from the pending batch (if still waiting)
895
+ // pendingQuestionBatch is now an array of { id, questionCount } objects
896
+ const existingIndex = pendingQuestionBatch.findIndex(item => item.id === rejectedQuestionId);
897
+ if (rejectedQuestionId && existingIndex !== -1) {
898
+ pendingQuestionBatch.splice(existingIndex, 1);
899
+ debugLog(`Question rejected: removed ${rejectedQuestionId} from pending batch (${pendingQuestionBatch.length} remaining)`);
900
+ }
901
+
902
+ // If batch is now empty and we have a pending batch timeout, we can cancel it
903
+ if (pendingQuestionBatch.length === 0 && questionBatchTimeout) {
904
+ clearTimeout(questionBatchTimeout);
905
+ questionBatchTimeout = null;
906
+ debugLog('Question rejected: cancelled batch timeout (all questions handled)');
907
+ }
908
+
909
+ // Clear active question ID
910
+ if (activeQuestionId === rejectedQuestionId || activeQuestionId === undefined) {
911
+ activeQuestionId = null;
912
+ debugLog(`Question rejected: cleared activeQuestionId ${rejectedQuestionId || '(unknown)'}`);
913
+ }
914
+ lastUserActivityTime = Date.now();
915
+ cancelPendingReminder('question'); // Cancel question-specific reminder
916
+ debugLog(`Question rejected: ${event.type} - cancelled question reminder`);
917
+ }
636
918
  } catch (e) {
637
919
  debugLog(`event handler error: ${e.message}`);
638
920
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-smart-voice-notify",
3
- "version": "1.0.13",
3
+ "version": "1.1.2",
4
4
  "description": "Smart voice notification plugin for OpenCode with multiple TTS engines (ElevenLabs, Edge TTS, Windows SAPI) and intelligent reminder system",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/util/config.js CHANGED
@@ -59,6 +59,13 @@ const generateDefaultConfig = (overrides = {}, version = '1.0.0') => {
59
59
  // Internal version tracking - DO NOT REMOVE
60
60
  "_configVersion": "${version}",
61
61
 
62
+ // ============================================================
63
+ // PLUGIN ENABLE/DISABLE
64
+ // ============================================================
65
+ // Master switch to enable or disable the entire plugin.
66
+ // Set to false to disable all notifications without uninstalling.
67
+ "enabled": ${overrides.enabled !== undefined ? overrides.enabled : true},
68
+
62
69
  // ============================================================
63
70
  // NOTIFICATION MODE SETTINGS (Smart Notification System)
64
71
  // ============================================================
@@ -241,6 +248,55 @@ const generateDefaultConfig = (overrides = {}, version = '1.0.0') => {
241
248
  // Batch window (ms) - how long to wait for more permissions before notifying
242
249
  "permissionBatchWindowMs": ${overrides.permissionBatchWindowMs !== undefined ? overrides.permissionBatchWindowMs : 800},
243
250
 
251
+ // ============================================================
252
+ // QUESTION TOOL SETTINGS (SDK v1.1.7+ - Agent asking user questions)
253
+ // ============================================================
254
+ // The "question" tool allows the LLM to ask users questions during execution.
255
+ // This is useful for gathering preferences, clarifying instructions, or getting
256
+ // decisions on implementation choices.
257
+
258
+ // Messages when agent asks user a question
259
+ "questionTTSMessages": ${formatJSON(overrides.questionTTSMessages || [
260
+ "Hey! I have a question for you. Please check your screen.",
261
+ "Attention! I need your input to continue.",
262
+ "Quick question! Please take a look when you have a moment.",
263
+ "I need some clarification. Could you please respond?",
264
+ "Question time! Your input is needed to proceed."
265
+ ], 4)},
266
+
267
+ // Messages for MULTIPLE questions (use {count} placeholder)
268
+ "questionTTSMessagesMultiple": ${formatJSON(overrides.questionTTSMessagesMultiple || [
269
+ "Hey! I have {count} questions for you. Please check your screen.",
270
+ "Attention! I need your input on {count} items to continue.",
271
+ "{count} questions need your attention. Please take a look!",
272
+ "I need some clarifications. There are {count} questions waiting for you.",
273
+ "Question time! {count} questions need your response to proceed."
274
+ ], 4)},
275
+
276
+ // Reminder messages for questions (more urgent - used after delay)
277
+ "questionReminderTTSMessages": ${formatJSON(overrides.questionReminderTTSMessages || [
278
+ "Hey! I am still waiting for your answer. Please check the questions!",
279
+ "Reminder: There is a question waiting for your response.",
280
+ "Hello? I need your input to continue. Please respond when you can.",
281
+ "Still waiting for your answer! The task is on hold.",
282
+ "Your input is needed! Please check the pending question."
283
+ ], 4)},
284
+
285
+ // Reminder messages for MULTIPLE questions (use {count} placeholder)
286
+ "questionReminderTTSMessagesMultiple": ${formatJSON(overrides.questionReminderTTSMessagesMultiple || [
287
+ "Hey! I am still waiting for answers to {count} questions. Please respond!",
288
+ "Reminder: There are {count} questions waiting for your response.",
289
+ "Hello? I need your input on {count} items. Please respond when you can.",
290
+ "Still waiting for your answers on {count} questions! The task is on hold.",
291
+ "Your input is needed! {count} questions are pending your response."
292
+ ], 4)},
293
+
294
+ // Delay (in seconds) before question reminder fires
295
+ "questionReminderDelaySeconds": ${overrides.questionReminderDelaySeconds !== undefined ? overrides.questionReminderDelaySeconds : 25},
296
+
297
+ // Question batch window (ms) - how long to wait for more questions before notifying
298
+ "questionBatchWindowMs": ${overrides.questionBatchWindowMs !== undefined ? overrides.questionBatchWindowMs : 800},
299
+
244
300
  // ============================================================
245
301
  // SOUND FILES (For immediate notifications)
246
302
  // These are played first before TTS reminder kicks in
@@ -251,6 +307,7 @@ const generateDefaultConfig = (overrides = {}, version = '1.0.0') => {
251
307
 
252
308
  "idleSound": "${overrides.idleSound || 'assets/Soft-high-tech-notification-sound-effect.mp3'}",
253
309
  "permissionSound": "${overrides.permissionSound || 'assets/Machine-alert-beep-sound-effect.mp3'}",
310
+ "questionSound": "${overrides.questionSound || 'assets/Machine-alert-beep-sound-effect.mp3'}",
254
311
 
255
312
  // ============================================================
256
313
  // GENERAL SETTINGS
package/util/tts.js CHANGED
@@ -111,11 +111,52 @@ export const getTTSConfig = () => {
111
111
  // Permission batch window (ms) - how long to wait for more permissions before notifying
112
112
  permissionBatchWindowMs: 800,
113
113
 
114
+ // ============================================================
115
+ // QUESTION TOOL MESSAGES (SDK v1.1.7+ - Agent asking user questions)
116
+ // ============================================================
117
+ // Messages when agent asks user a question
118
+ questionTTSMessages: [
119
+ 'Hey! I have a question for you. Please check your screen.',
120
+ 'Attention! I need your input to continue.',
121
+ 'Quick question! Please take a look when you have a moment.',
122
+ 'I need some clarification. Could you please respond?',
123
+ 'Question time! Your input is needed to proceed.'
124
+ ],
125
+ // Messages for MULTIPLE questions (use {count} placeholder)
126
+ questionTTSMessagesMultiple: [
127
+ 'Hey! I have {count} questions for you. Please check your screen.',
128
+ 'Attention! I need your input on {count} items to continue.',
129
+ '{count} questions need your attention. Please take a look!',
130
+ 'I need some clarifications. There are {count} questions waiting for you.',
131
+ 'Question time! {count} questions need your response to proceed.'
132
+ ],
133
+ // Reminder messages for questions
134
+ questionReminderTTSMessages: [
135
+ 'Hey! I am still waiting for your answer. Please check the questions!',
136
+ 'Reminder: There is a question waiting for your response.',
137
+ 'Hello? I need your input to continue. Please respond when you can.',
138
+ 'Still waiting for your answer! The task is on hold.',
139
+ 'Your input is needed! Please check the pending question.'
140
+ ],
141
+ // Reminder messages for MULTIPLE questions (use {count} placeholder)
142
+ questionReminderTTSMessagesMultiple: [
143
+ 'Hey! I am still waiting for answers to {count} questions. Please respond!',
144
+ 'Reminder: There are {count} questions waiting for your response.',
145
+ 'Hello? I need your input on {count} items. Please respond when you can.',
146
+ 'Still waiting for your answers on {count} questions! The task is on hold.',
147
+ 'Your input is needed! {count} questions are pending your response.'
148
+ ],
149
+ // Question reminder delay (seconds) - slightly less urgent than permissions
150
+ questionReminderDelaySeconds: 25,
151
+ // Question batch window (ms) - how long to wait for more questions before notifying
152
+ questionBatchWindowMs: 800,
153
+
114
154
  // ============================================================
115
155
  // SOUND FILES (Used for immediate notifications)
116
156
  // ============================================================
117
157
  idleSound: 'assets/Soft-high-tech-notification-sound-effect.mp3',
118
158
  permissionSound: 'assets/Machine-alert-beep-sound-effect.mp3',
159
+ questionSound: 'assets/Machine-alert-beep-sound-effect.mp3',
119
160
 
120
161
  // ============================================================
121
162
  // GENERAL SETTINGS