mcp-voice-hooks 1.0.8 → 1.0.13
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 +56 -81
- package/bin/cli.js +10 -100
- package/dist/hook-merger.d.ts +1 -1
- package/dist/hook-merger.js +1 -1
- package/dist/hook-merger.js.map +1 -1
- package/dist/unified-server.js +147 -95
- package/dist/unified-server.js.map +1 -1
- package/package.json +1 -1
- package/public/app.js +451 -45
- package/public/index.html +255 -61
- package/.claude/hooks/pre-speak-hook.sh +0 -3
- package/.claude/hooks/pre-tool-hook.sh +0 -3
- package/.claude/hooks/pre-wait-hook.sh +0 -3
- package/.claude/hooks/stop-hook.sh +0 -3
- package/CLAUDE.local.md +0 -25
- package/test-npx-clean/mcp-voice-hooks-1.0.1.tgz +0 -0
- package/test-npx-clean/package/dist/chunk-IYGM5COW.js +0 -12
- package/test-npx-clean/package/dist/chunk-IYGM5COW.js.map +0 -1
- package/test-npx-clean/package/dist/index.d.ts +0 -2
- package/test-npx-clean/package/dist/index.js +0 -125
- package/test-npx-clean/package/dist/index.js.map +0 -1
- package/test-npx-clean/package/dist/unified-server.d.ts +0 -1
- package/test-npx-clean/package/dist/unified-server.js +0 -352
- package/test-npx-clean/package/dist/unified-server.js.map +0 -1
- package/test-npx-clean/package/mcp-voice-hooks-1.0.0.tgz +0 -0
- package/test-npx-clean/package/mcp-voice-hooks-1.0.1.tgz +0 -0
package/dist/unified-server.js
CHANGED
@@ -19,9 +19,7 @@ import {
|
|
19
19
|
} from "@modelcontextprotocol/sdk/types.js";
|
20
20
|
var __filename = fileURLToPath(import.meta.url);
|
21
21
|
var __dirname = path.dirname(__filename);
|
22
|
-
var
|
23
|
-
var MIN_WAIT_TIMEOUT_SECONDS = 30;
|
24
|
-
var MAX_WAIT_TIMEOUT_SECONDS = 60;
|
22
|
+
var WAIT_TIMEOUT_SECONDS = 60;
|
25
23
|
var execAsync = promisify(exec);
|
26
24
|
async function playNotificationSound() {
|
27
25
|
try {
|
@@ -62,9 +60,10 @@ var UtteranceQueue = class {
|
|
62
60
|
};
|
63
61
|
var IS_MCP_MANAGED = process.argv.includes("--mcp-managed");
|
64
62
|
var queue = new UtteranceQueue();
|
65
|
-
var
|
66
|
-
|
67
|
-
|
63
|
+
var voicePreferences = {
|
64
|
+
voiceResponsesEnabled: false,
|
65
|
+
voiceInputActive: false
|
66
|
+
};
|
68
67
|
var app = express();
|
69
68
|
app.use(cors());
|
70
69
|
app.use(express.json());
|
@@ -99,7 +98,7 @@ app.get("/api/utterances", (req, res) => {
|
|
99
98
|
}))
|
100
99
|
});
|
101
100
|
});
|
102
|
-
app.get("/api/utterances/status", (
|
101
|
+
app.get("/api/utterances/status", (_req, res) => {
|
103
102
|
const total = queue.utterances.length;
|
104
103
|
const pending = queue.utterances.filter((u) => u.status === "pending").length;
|
105
104
|
const delivered = queue.utterances.filter((u) => u.status === "delivered").length;
|
@@ -110,6 +109,13 @@ app.get("/api/utterances/status", (req, res) => {
|
|
110
109
|
});
|
111
110
|
});
|
112
111
|
app.post("/api/dequeue-utterances", (req, res) => {
|
112
|
+
if (!voicePreferences.voiceInputActive) {
|
113
|
+
res.status(400).json({
|
114
|
+
success: false,
|
115
|
+
error: "Voice input is not active. Cannot dequeue utterances when voice input is disabled."
|
116
|
+
});
|
117
|
+
return;
|
118
|
+
}
|
113
119
|
const { limit = 10 } = req.body;
|
114
120
|
const pendingUtterances = queue.utterances.filter((u) => u.status === "pending").sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit);
|
115
121
|
pendingUtterances.forEach((u) => {
|
@@ -124,36 +130,23 @@ app.post("/api/dequeue-utterances", (req, res) => {
|
|
124
130
|
});
|
125
131
|
});
|
126
132
|
app.post("/api/wait-for-utterances", async (req, res) => {
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
133
|
+
if (!voicePreferences.voiceInputActive) {
|
134
|
+
res.status(400).json({
|
135
|
+
success: false,
|
136
|
+
error: "Voice input is not active. Cannot wait for utterances when voice input is disabled."
|
137
|
+
});
|
138
|
+
return;
|
139
|
+
}
|
140
|
+
const secondsToWait = WAIT_TIMEOUT_SECONDS;
|
132
141
|
const maxWaitMs = secondsToWait * 1e3;
|
133
142
|
const startTime = Date.now();
|
134
143
|
debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);
|
135
|
-
if (lastTimeoutTimestamp) {
|
136
|
-
const hasNewUtterances = queue.utterances.some(
|
137
|
-
(u) => u.timestamp > lastTimeoutTimestamp
|
138
|
-
);
|
139
|
-
if (!hasNewUtterances) {
|
140
|
-
debugLog("[Server] No new utterances since last timeout, returning immediately");
|
141
|
-
res.json({
|
142
|
-
success: true,
|
143
|
-
utterances: [],
|
144
|
-
message: `No utterances found after waiting ${secondsToWait} seconds.`,
|
145
|
-
waitTime: 0
|
146
|
-
});
|
147
|
-
return;
|
148
|
-
}
|
149
|
-
}
|
150
144
|
let firstTime = true;
|
151
145
|
while (Date.now() - startTime < maxWaitMs) {
|
152
146
|
const pendingUtterances = queue.utterances.filter(
|
153
|
-
(u) => u.status === "pending"
|
147
|
+
(u) => u.status === "pending"
|
154
148
|
);
|
155
149
|
if (pendingUtterances.length > 0) {
|
156
|
-
lastTimeoutTimestamp = null;
|
157
150
|
const sortedUtterances = pendingUtterances.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
158
151
|
sortedUtterances.forEach((u) => {
|
159
152
|
queue.markDelivered(u.id);
|
@@ -178,7 +171,6 @@ app.post("/api/wait-for-utterances", async (req, res) => {
|
|
178
171
|
}
|
179
172
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
180
173
|
}
|
181
|
-
lastTimeoutTimestamp = /* @__PURE__ */ new Date();
|
182
174
|
res.json({
|
183
175
|
success: true,
|
184
176
|
utterances: [],
|
@@ -186,11 +178,7 @@ app.post("/api/wait-for-utterances", async (req, res) => {
|
|
186
178
|
waitTime: maxWaitMs
|
187
179
|
});
|
188
180
|
});
|
189
|
-
app.get("/api/
|
190
|
-
const shouldWait = !lastTimeoutTimestamp || queue.utterances.some((u) => u.timestamp > lastTimeoutTimestamp);
|
191
|
-
res.json({ shouldWait });
|
192
|
-
});
|
193
|
-
app.get("/api/has-pending-utterances", (req, res) => {
|
181
|
+
app.get("/api/has-pending-utterances", (_req, res) => {
|
194
182
|
const pendingCount = queue.utterances.filter((u) => u.status === "pending").length;
|
195
183
|
const hasPending = pendingCount > 0;
|
196
184
|
res.json({
|
@@ -200,19 +188,21 @@ app.get("/api/has-pending-utterances", (req, res) => {
|
|
200
188
|
});
|
201
189
|
app.post("/api/validate-action", (req, res) => {
|
202
190
|
const { action } = req.body;
|
203
|
-
const voiceResponsesEnabled =
|
191
|
+
const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;
|
204
192
|
if (!action || !["tool-use", "stop"].includes(action)) {
|
205
193
|
res.status(400).json({ error: 'Invalid action. Must be "tool-use" or "stop"' });
|
206
194
|
return;
|
207
195
|
}
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
196
|
+
if (voicePreferences.voiceInputActive) {
|
197
|
+
const pendingUtterances = queue.utterances.filter((u) => u.status === "pending");
|
198
|
+
if (pendingUtterances.length > 0) {
|
199
|
+
res.json({
|
200
|
+
allowed: false,
|
201
|
+
requiredAction: "dequeue_utterances",
|
202
|
+
reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`
|
203
|
+
});
|
204
|
+
return;
|
205
|
+
}
|
216
206
|
}
|
217
207
|
if (voiceResponsesEnabled) {
|
218
208
|
const deliveredUtterances = queue.utterances.filter((u) => u.status === "delivered");
|
@@ -225,9 +215,8 @@ app.post("/api/validate-action", (req, res) => {
|
|
225
215
|
return;
|
226
216
|
}
|
227
217
|
}
|
228
|
-
if (action === "stop") {
|
229
|
-
|
230
|
-
if (shouldWait) {
|
218
|
+
if (action === "stop" && voicePreferences.voiceInputActive) {
|
219
|
+
if (queue.utterances.length > 0) {
|
231
220
|
res.json({
|
232
221
|
allowed: false,
|
233
222
|
requiredAction: "wait_for_utterance",
|
@@ -241,13 +230,16 @@ app.post("/api/validate-action", (req, res) => {
|
|
241
230
|
});
|
242
231
|
});
|
243
232
|
function handleHookRequest(attemptedAction) {
|
244
|
-
const voiceResponsesEnabled =
|
245
|
-
const
|
246
|
-
if (
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
233
|
+
const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;
|
234
|
+
const voiceInputActive = voicePreferences.voiceInputActive;
|
235
|
+
if (voiceInputActive) {
|
236
|
+
const pendingUtterances = queue.utterances.filter((u) => u.status === "pending");
|
237
|
+
if (pendingUtterances.length > 0) {
|
238
|
+
return {
|
239
|
+
decision: "block",
|
240
|
+
reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`
|
241
|
+
};
|
242
|
+
}
|
251
243
|
}
|
252
244
|
if (voiceResponsesEnabled) {
|
253
245
|
const deliveredUtterances = queue.utterances.filter((u) => u.status === "delivered");
|
@@ -262,33 +254,19 @@ function handleHookRequest(attemptedAction) {
|
|
262
254
|
}
|
263
255
|
}
|
264
256
|
if (attemptedAction === "tool") {
|
265
|
-
lastToolUseTimestamp = /* @__PURE__ */ new Date();
|
266
257
|
return { decision: "approve" };
|
267
258
|
}
|
268
259
|
if (attemptedAction === "wait") {
|
269
|
-
if (voiceResponsesEnabled && lastToolUseTimestamp && (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {
|
270
|
-
return {
|
271
|
-
decision: "block",
|
272
|
-
reason: "Assistant must speak after using tools. Please use the speak tool to respond before waiting for utterances."
|
273
|
-
};
|
274
|
-
}
|
275
260
|
return { decision: "approve" };
|
276
261
|
}
|
277
262
|
if (attemptedAction === "speak") {
|
278
263
|
return { decision: "approve" };
|
279
264
|
}
|
280
265
|
if (attemptedAction === "stop") {
|
281
|
-
if (
|
282
|
-
return {
|
283
|
-
decision: "block",
|
284
|
-
reason: "Assistant must speak after using tools. Please use the speak tool to respond before proceeding."
|
285
|
-
};
|
286
|
-
}
|
287
|
-
const shouldWait = !lastTimeoutTimestamp || queue.utterances.some((u) => u.timestamp > lastTimeoutTimestamp);
|
288
|
-
if (shouldWait) {
|
266
|
+
if (voiceInputActive) {
|
289
267
|
return {
|
290
268
|
decision: "block",
|
291
|
-
reason: "Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input"
|
269
|
+
reason: "Assistant tried to end its response, but voice input is active. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input"
|
292
270
|
};
|
293
271
|
}
|
294
272
|
return {
|
@@ -314,7 +292,7 @@ app.post("/api/hooks/pre-wait", (_req, res) => {
|
|
314
292
|
const result = handleHookRequest("wait");
|
315
293
|
res.json(result);
|
316
294
|
});
|
317
|
-
app.delete("/api/utterances", (
|
295
|
+
app.delete("/api/utterances", (_req, res) => {
|
318
296
|
const clearedCount = queue.utterances.length;
|
319
297
|
queue.clear();
|
320
298
|
res.json({
|
@@ -323,21 +301,67 @@ app.delete("/api/utterances", (req, res) => {
|
|
323
301
|
clearedCount
|
324
302
|
});
|
325
303
|
});
|
304
|
+
var ttsClients = /* @__PURE__ */ new Set();
|
305
|
+
app.get("/api/tts-events", (_req, res) => {
|
306
|
+
res.writeHead(200, {
|
307
|
+
"Content-Type": "text/event-stream",
|
308
|
+
"Cache-Control": "no-cache",
|
309
|
+
"Connection": "keep-alive"
|
310
|
+
});
|
311
|
+
res.write('data: {"type":"connected"}\n\n');
|
312
|
+
ttsClients.add(res);
|
313
|
+
res.on("close", () => {
|
314
|
+
ttsClients.delete(res);
|
315
|
+
});
|
316
|
+
});
|
317
|
+
function notifyTTSClients(text) {
|
318
|
+
const message = JSON.stringify({ type: "speak", text });
|
319
|
+
ttsClients.forEach((client) => {
|
320
|
+
client.write(`data: ${message}
|
321
|
+
|
322
|
+
`);
|
323
|
+
});
|
324
|
+
}
|
325
|
+
app.post("/api/voice-preferences", (req, res) => {
|
326
|
+
const { voiceResponsesEnabled } = req.body;
|
327
|
+
voicePreferences.voiceResponsesEnabled = !!voiceResponsesEnabled;
|
328
|
+
debugLog(`[Preferences] Updated: voiceResponses=${voicePreferences.voiceResponsesEnabled}`);
|
329
|
+
res.json({
|
330
|
+
success: true,
|
331
|
+
preferences: voicePreferences
|
332
|
+
});
|
333
|
+
});
|
334
|
+
app.post("/api/voice-input-state", (req, res) => {
|
335
|
+
const { active } = req.body;
|
336
|
+
voicePreferences.voiceInputActive = !!active;
|
337
|
+
debugLog(`[Voice Input] ${voicePreferences.voiceInputActive ? "Started" : "Stopped"} listening`);
|
338
|
+
res.json({
|
339
|
+
success: true,
|
340
|
+
voiceInputActive: voicePreferences.voiceInputActive
|
341
|
+
});
|
342
|
+
});
|
326
343
|
app.post("/api/speak", async (req, res) => {
|
327
344
|
const { text } = req.body;
|
328
345
|
if (!text || !text.trim()) {
|
329
346
|
res.status(400).json({ error: "Text is required" });
|
330
347
|
return;
|
331
348
|
}
|
349
|
+
if (!voicePreferences.voiceResponsesEnabled) {
|
350
|
+
debugLog(`[Speak] Voice responses disabled, returning error`);
|
351
|
+
res.status(400).json({
|
352
|
+
error: "Voice responses are disabled",
|
353
|
+
message: "Cannot speak when voice responses are disabled"
|
354
|
+
});
|
355
|
+
return;
|
356
|
+
}
|
332
357
|
try {
|
333
|
-
|
334
|
-
debugLog(`[Speak]
|
358
|
+
notifyTTSClients(text);
|
359
|
+
debugLog(`[Speak] Sent text to browser for TTS: "${text}"`);
|
335
360
|
const deliveredUtterances = queue.utterances.filter((u) => u.status === "delivered");
|
336
361
|
deliveredUtterances.forEach((u) => {
|
337
362
|
u.status = "responded";
|
338
363
|
debugLog(`[Queue] marked as responded: "${u.text}" [id: ${u.id}]`);
|
339
364
|
});
|
340
|
-
lastSpeakTimestamp = /* @__PURE__ */ new Date();
|
341
365
|
res.json({
|
342
366
|
success: true,
|
343
367
|
message: "Text spoken successfully",
|
@@ -351,6 +375,27 @@ app.post("/api/speak", async (req, res) => {
|
|
351
375
|
});
|
352
376
|
}
|
353
377
|
});
|
378
|
+
app.post("/api/speak-system", async (req, res) => {
|
379
|
+
const { text, rate = 150 } = req.body;
|
380
|
+
if (!text || !text.trim()) {
|
381
|
+
res.status(400).json({ error: "Text is required" });
|
382
|
+
return;
|
383
|
+
}
|
384
|
+
try {
|
385
|
+
await execAsync(`say -r ${rate} "${text.replace(/"/g, '\\"')}"`);
|
386
|
+
debugLog(`[Speak System] Spoke text using macOS say: "${text}" (rate: ${rate})`);
|
387
|
+
res.json({
|
388
|
+
success: true,
|
389
|
+
message: "Text spoken successfully via system voice"
|
390
|
+
});
|
391
|
+
} catch (error) {
|
392
|
+
debugLog(`[Speak System] Failed to speak text: ${error}`);
|
393
|
+
res.status(500).json({
|
394
|
+
error: "Failed to speak text via system voice",
|
395
|
+
details: error instanceof Error ? error.message : String(error)
|
396
|
+
});
|
397
|
+
}
|
398
|
+
});
|
354
399
|
app.get("/", (_req, res) => {
|
355
400
|
res.sendFile(path.join(__dirname, "..", "public", "index.html"));
|
356
401
|
});
|
@@ -360,7 +405,7 @@ app.listen(HTTP_PORT, () => {
|
|
360
405
|
console.log(`[Mode] Running in ${IS_MCP_MANAGED ? "MCP-managed" : "standalone"} mode`);
|
361
406
|
});
|
362
407
|
function getVoiceResponseReminder() {
|
363
|
-
const voiceResponsesEnabled =
|
408
|
+
const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;
|
364
409
|
return voiceResponsesEnabled ? "\n\nThe user has enabled voice responses, so use the 'speak' tool to respond to the user's voice input before proceeding." : "";
|
365
410
|
}
|
366
411
|
if (IS_MCP_MANAGED) {
|
@@ -395,18 +440,10 @@ if (IS_MCP_MANAGED) {
|
|
395
440
|
},
|
396
441
|
{
|
397
442
|
name: "wait_for_utterance",
|
398
|
-
description: "Wait for an utterance to be available or until timeout
|
443
|
+
description: "Wait for an utterance to be available or until timeout",
|
399
444
|
inputSchema: {
|
400
445
|
type: "object",
|
401
|
-
properties: {
|
402
|
-
seconds_to_wait: {
|
403
|
-
type: "number",
|
404
|
-
description: `Maximum seconds to wait for an utterance (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}, min: ${MIN_WAIT_TIMEOUT_SECONDS}, max: ${MAX_WAIT_TIMEOUT_SECONDS})`,
|
405
|
-
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
406
|
-
minimum: MIN_WAIT_TIMEOUT_SECONDS,
|
407
|
-
maximum: MAX_WAIT_TIMEOUT_SECONDS
|
408
|
-
}
|
409
|
-
}
|
446
|
+
properties: {}
|
410
447
|
}
|
411
448
|
},
|
412
449
|
{
|
@@ -437,6 +474,16 @@ if (IS_MCP_MANAGED) {
|
|
437
474
|
body: JSON.stringify({ limit })
|
438
475
|
});
|
439
476
|
const data = await response.json();
|
477
|
+
if (!response.ok) {
|
478
|
+
return {
|
479
|
+
content: [
|
480
|
+
{
|
481
|
+
type: "text",
|
482
|
+
text: `Error: ${data.error || "Failed to dequeue utterances"}`
|
483
|
+
}
|
484
|
+
]
|
485
|
+
};
|
486
|
+
}
|
440
487
|
if (data.utterances.length === 0) {
|
441
488
|
return {
|
442
489
|
content: [
|
@@ -459,18 +506,23 @@ ${data.utterances.reverse().map((u) => `"${u.text}" [time: ${new Date(u.timestam
|
|
459
506
|
};
|
460
507
|
}
|
461
508
|
if (name === "wait_for_utterance") {
|
462
|
-
|
463
|
-
const secondsToWait = Math.max(
|
464
|
-
MIN_WAIT_TIMEOUT_SECONDS,
|
465
|
-
Math.min(MAX_WAIT_TIMEOUT_SECONDS, requestedSeconds)
|
466
|
-
);
|
467
|
-
debugLog(`[MCP] Calling wait_for_utterance with ${secondsToWait}s timeout`);
|
509
|
+
debugLog(`[MCP] Calling wait_for_utterance`);
|
468
510
|
const response = await fetch(`http://localhost:${HTTP_PORT}/api/wait-for-utterances`, {
|
469
511
|
method: "POST",
|
470
512
|
headers: { "Content-Type": "application/json" },
|
471
|
-
body: JSON.stringify({
|
513
|
+
body: JSON.stringify({})
|
472
514
|
});
|
473
515
|
const data = await response.json();
|
516
|
+
if (!response.ok) {
|
517
|
+
return {
|
518
|
+
content: [
|
519
|
+
{
|
520
|
+
type: "text",
|
521
|
+
text: `Error: ${data.error || "Failed to wait for utterances"}`
|
522
|
+
}
|
523
|
+
]
|
524
|
+
};
|
525
|
+
}
|
474
526
|
if (data.utterances && data.utterances.length > 0) {
|
475
527
|
const utteranceTexts = data.utterances.map((u) => `[${u.timestamp}] "${u.text}"`).join("\n");
|
476
528
|
return {
|
@@ -488,7 +540,7 @@ ${utteranceTexts}${getVoiceResponseReminder()}`
|
|
488
540
|
content: [
|
489
541
|
{
|
490
542
|
type: "text",
|
491
|
-
text: data.message || `No utterances found
|
543
|
+
text: data.message || `No utterances found. Timed out.`
|
492
544
|
}
|
493
545
|
]
|
494
546
|
};
|
@@ -518,8 +570,8 @@ ${utteranceTexts}${getVoiceResponseReminder()}`
|
|
518
570
|
content: [
|
519
571
|
{
|
520
572
|
type: "text",
|
521
|
-
text:
|
522
|
-
|
573
|
+
text: ""
|
574
|
+
// Return empty string for success
|
523
575
|
}
|
524
576
|
]
|
525
577
|
};
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/unified-server.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport express from 'express';\nimport type { Request, Response } from 'express';\nimport cors from 'cors';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { randomUUID } from 'crypto';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { debugLog } from './debug.ts';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Constants\nconst DEFAULT_WAIT_TIMEOUT_SECONDS = 30;\nconst MIN_WAIT_TIMEOUT_SECONDS = 30;\nconst MAX_WAIT_TIMEOUT_SECONDS = 60;\n\n// Promisified exec for async/await\nconst execAsync = promisify(exec);\n\n// Function to play a sound notification\nasync function playNotificationSound() {\n try {\n // Use macOS system sound\n await execAsync('afplay /System/Library/Sounds/Funk.aiff');\n debugLog('[Sound] Played notification sound');\n } catch (error) {\n debugLog(`[Sound] Failed to play sound: ${error}`);\n // Don't throw - sound is not critical\n }\n}\n\n// Shared utterance queue\ninterface Utterance {\n id: string;\n text: string;\n timestamp: Date;\n status: 'pending' | 'delivered' | 'responded';\n}\n\nclass UtteranceQueue {\n utterances: Utterance[] = [];\n\n add(text: string, timestamp?: Date): Utterance {\n const utterance: Utterance = {\n id: randomUUID(),\n text: text.trim(),\n timestamp: timestamp || new Date(),\n status: 'pending'\n };\n\n this.utterances.push(utterance);\n debugLog(`[Queue] queued: \"${utterance.text}\"\t[id: ${utterance.id}]`);\n return utterance;\n }\n\n getRecent(limit: number = 10): Utterance[] {\n return this.utterances\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n }\n\n markDelivered(id: string): void {\n const utterance = this.utterances.find(u => u.id === id);\n if (utterance) {\n utterance.status = 'delivered';\n debugLog(`[Queue] delivered: \"${utterance.text}\"\t[id: ${id}]`);\n }\n }\n\n clear(): void {\n const count = this.utterances.length;\n this.utterances = [];\n debugLog(`[Queue] Cleared ${count} utterances`);\n }\n}\n\n// Determine if we're running in MCP-managed mode\nconst IS_MCP_MANAGED = process.argv.includes('--mcp-managed');\n\n// Global state\nconst queue = new UtteranceQueue();\nlet lastTimeoutTimestamp: Date | null = null;\nlet lastToolUseTimestamp: Date | null = null;\nlet lastSpeakTimestamp: Date | null = null;\n\n// HTTP Server Setup (always created)\nconst app = express();\napp.use(cors());\napp.use(express.json());\napp.use(express.static(path.join(__dirname, '..', 'public')));\n\n// API Routes\napp.post('/api/potential-utterances', (req: Request, res: Response) => {\n const { text, timestamp } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n const parsedTimestamp = timestamp ? new Date(timestamp) : undefined;\n const utterance = queue.add(text, parsedTimestamp);\n res.json({\n success: true,\n utterance: {\n id: utterance.id,\n text: utterance.text,\n timestamp: utterance.timestamp,\n status: utterance.status,\n },\n });\n});\n\napp.get('/api/utterances', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 10;\n const utterances = queue.getRecent(limit);\n\n res.json({\n utterances: utterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: u.status,\n })),\n });\n});\n\napp.get('/api/utterances/status', (req: Request, res: Response) => {\n const total = queue.utterances.length;\n const pending = queue.utterances.filter(u => u.status === 'pending').length;\n const delivered = queue.utterances.filter(u => u.status === 'delivered').length;\n\n res.json({\n total,\n pending,\n delivered,\n });\n});\n\n// MCP server integration\napp.post('/api/dequeue-utterances', (req: Request, res: Response) => {\n const { limit = 10 } = req.body;\n const pendingUtterances = queue.utterances\n .filter(u => u.status === 'pending')\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n\n // Mark as delivered\n pendingUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: pendingUtterances.map(u => ({\n text: u.text,\n timestamp: u.timestamp,\n })),\n });\n});\n\n// Wait for utterance endpoint\napp.post('/api/wait-for-utterances', async (req: Request, res: Response) => {\n const { seconds_to_wait = DEFAULT_WAIT_TIMEOUT_SECONDS } = req.body;\n const secondsToWait = Math.max(\n MIN_WAIT_TIMEOUT_SECONDS,\n Math.min(MAX_WAIT_TIMEOUT_SECONDS, seconds_to_wait)\n );\n const maxWaitMs = secondsToWait * 1000;\n const startTime = Date.now();\n\n debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);\n\n // Check if we should return immediately\n if (lastTimeoutTimestamp) {\n const hasNewUtterances = queue.utterances.some(\n u => u.timestamp > lastTimeoutTimestamp!\n );\n if (!hasNewUtterances) {\n debugLog('[Server] No new utterances since last timeout, returning immediately');\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: 0,\n });\n return;\n }\n }\n\n let firstTime = true;\n\n // Poll for utterances\n while (Date.now() - startTime < maxWaitMs) {\n const pendingUtterances = queue.utterances.filter(\n u => u.status === 'pending' &&\n (!lastTimeoutTimestamp || u.timestamp > lastTimeoutTimestamp)\n );\n\n if (pendingUtterances.length > 0) {\n // Found utterances - clear lastTimeoutTimestamp\n lastTimeoutTimestamp = null;\n\n // Sort by timestamp (oldest first)\n const sortedUtterances = pendingUtterances\n .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());\n\n // Mark utterances as delivered\n sortedUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: sortedUtterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: 'delivered', // They are now delivered\n })),\n count: pendingUtterances.length,\n waitTime: Date.now() - startTime,\n });\n return;\n }\n\n if (firstTime) {\n firstTime = false;\n // Play notification sound since we're about to start waiting\n await playNotificationSound();\n }\n\n // Wait 100ms before checking again\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n\n // Timeout reached - no utterances found\n lastTimeoutTimestamp = new Date();\n\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: maxWaitMs,\n });\n});\n\n// API for the stop hook to check if it should wait\napp.get('/api/should-wait', (req: Request, res: Response) => {\n const shouldWait = !lastTimeoutTimestamp ||\n queue.utterances.some(u => u.timestamp > lastTimeoutTimestamp!);\n\n res.json({ shouldWait });\n});\n\n// API for pre-tool hook to check for pending utterances\napp.get('/api/has-pending-utterances', (req: Request, res: Response) => {\n const pendingCount = queue.utterances.filter(u => u.status === 'pending').length;\n const hasPending = pendingCount > 0;\n\n res.json({\n hasPending,\n pendingCount\n });\n});\n\n// Unified action validation endpoint\napp.post('/api/validate-action', (req: Request, res: Response) => {\n const { action } = req.body;\n const voiceResponsesEnabled = process.env.VOICE_RESPONSES_ENABLED === 'true';\n\n if (!action || !['tool-use', 'stop'].includes(action)) {\n res.status(400).json({ error: 'Invalid action. Must be \"tool-use\" or \"stop\"' });\n return;\n }\n\n // Check for pending utterances (both actions)\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'dequeue_utterances',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n });\n return;\n }\n\n // Check for delivered but unresponded utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'speak',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n });\n return;\n }\n }\n\n // For stop action, check if we should wait\n if (action === 'stop') {\n const shouldWait = !lastTimeoutTimestamp ||\n queue.utterances.some(u => u.timestamp > lastTimeoutTimestamp!);\n\n if (shouldWait) {\n res.json({\n allowed: false,\n requiredAction: 'wait_for_utterance',\n reason: 'Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n });\n return;\n }\n }\n\n // All checks passed - action is allowed\n res.json({\n allowed: true\n });\n});\n\n// Unified hook handler\nfunction handleHookRequest(attemptedAction: 'tool' | 'speak' | 'wait' | 'stop'): { decision: 'approve' | 'block', reason?: string } {\n const voiceResponsesEnabled = process.env.VOICE_RESPONSES_ENABLED === 'true';\n \n // 1. Check for pending utterances\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n // Allow dequeue to proceed (dequeue doesn't go through hooks)\n return {\n decision: 'block',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n };\n }\n \n // 2. Check for delivered utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n // Only allow speak to proceed\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n return {\n decision: 'block',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n };\n }\n }\n \n // 3. Handle tool action\n if (attemptedAction === 'tool') {\n lastToolUseTimestamp = new Date();\n return { decision: 'approve' };\n }\n \n // 4. Handle wait for utterance\n if (attemptedAction === 'wait') {\n if (voiceResponsesEnabled && lastToolUseTimestamp && \n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before waiting for utterances.'\n };\n }\n return { decision: 'approve' };\n }\n \n // 5. Handle speak\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n \n // 6. Handle stop\n if (attemptedAction === 'stop') {\n // Check if must speak after tool use\n if (voiceResponsesEnabled && lastToolUseTimestamp && \n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before proceeding.'\n };\n }\n \n // Check if should wait for utterances\n const shouldWait = !lastTimeoutTimestamp ||\n queue.utterances.some(u => u.timestamp > lastTimeoutTimestamp!);\n \n if (shouldWait) {\n return {\n decision: 'block',\n reason: 'Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n };\n }\n \n return {\n decision: 'approve',\n reason: 'No utterances since last timeout'\n };\n }\n \n // Default to approve (shouldn't reach here)\n return { decision: 'approve' };\n}\n\n// Dedicated hook endpoints that return in Claude's expected format\napp.post('/api/hooks/pre-tool', (_req: Request, res: Response) => {\n const result = handleHookRequest('tool');\n res.json(result);\n});\n\napp.post('/api/hooks/stop', (_req: Request, res: Response) => {\n const result = handleHookRequest('stop');\n res.json(result);\n});\n\n// Pre-speak hook endpoint\napp.post('/api/hooks/pre-speak', (_req: Request, res: Response) => {\n const result = handleHookRequest('speak');\n res.json(result);\n});\n\n// Pre-wait hook endpoint\napp.post('/api/hooks/pre-wait', (_req: Request, res: Response) => {\n const result = handleHookRequest('wait');\n res.json(result);\n});\n\n// API to clear all utterances\napp.delete('/api/utterances', (req: Request, res: Response) => {\n const clearedCount = queue.utterances.length;\n queue.clear();\n\n res.json({\n success: true,\n message: `Cleared ${clearedCount} utterances`,\n clearedCount\n });\n});\n\n// API for text-to-speech\napp.post('/api/speak', async (req: Request, res: Response) => {\n const { text } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n try {\n // Execute text-to-speech using macOS say command\n await execAsync(`say -r 350 \"${text.replace(/\"/g, '\\\\\"')}\"`);\n debugLog(`[Speak] Spoke text: \"${text}\"`);\n\n // Mark all delivered utterances as responded\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n deliveredUtterances.forEach(u => {\n u.status = 'responded';\n debugLog(`[Queue] marked as responded: \"${u.text}\"\t[id: ${u.id}]`);\n });\n\n // Track that speak was called\n lastSpeakTimestamp = new Date();\n\n res.json({\n success: true,\n message: 'Text spoken successfully',\n respondedCount: deliveredUtterances.length\n });\n } catch (error) {\n debugLog(`[Speak] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\napp.get('/', (_req: Request, res: Response) => {\n res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));\n});\n\n// Start HTTP server\nconst HTTP_PORT = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\napp.listen(HTTP_PORT, () => {\n console.log(`[HTTP] Server listening on http://localhost:${HTTP_PORT}`);\n console.log(`[Mode] Running in ${IS_MCP_MANAGED ? 'MCP-managed' : 'standalone'} mode`);\n});\n\n// Helper function to get voice response reminder\nfunction getVoiceResponseReminder(): string {\n const voiceResponsesEnabled = process.env.VOICE_RESPONSES_ENABLED === 'true';\n return voiceResponsesEnabled\n ? '\\n\\nThe user has enabled voice responses, so use the \\'speak\\' tool to respond to the user\\'s voice input before proceeding.'\n : '';\n}\n\n// MCP Server Setup (only if MCP-managed)\nif (IS_MCP_MANAGED) {\n console.log('[MCP] Initializing MCP server...');\n\n const mcpServer = new Server(\n {\n name: 'voice-hooks',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Tool handlers\n mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [\n {\n name: 'dequeue_utterances',\n description: 'Dequeue pending utterances and mark them as delivered',\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'number',\n description: 'Maximum number of utterances to dequeue (default: 10)',\n default: 10,\n },\n },\n },\n },\n {\n name: 'wait_for_utterance',\n description: 'Wait for an utterance to be available or until timeout. Returns immediately if no utterances since last timeout.',\n inputSchema: {\n type: 'object',\n properties: {\n seconds_to_wait: {\n type: 'number',\n description: `Maximum seconds to wait for an utterance (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}, min: ${MIN_WAIT_TIMEOUT_SECONDS}, max: ${MAX_WAIT_TIMEOUT_SECONDS})`,\n default: DEFAULT_WAIT_TIMEOUT_SECONDS,\n minimum: MIN_WAIT_TIMEOUT_SECONDS,\n maximum: MAX_WAIT_TIMEOUT_SECONDS,\n },\n },\n },\n },\n {\n name: 'speak',\n description: 'Speak text using text-to-speech and mark delivered utterances as responded',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The text to speak',\n },\n },\n required: ['text'],\n },\n },\n ],\n };\n });\n\n mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n if (name === 'dequeue_utterances') {\n const limit = (args?.limit as number) ?? 10;\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/dequeue-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ limit }),\n });\n\n const data = await response.json() as any;\n\n if (data.utterances.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No recent utterances found.',\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Dequeued ${data.utterances.length} utterance(s):\\n\\n${data.utterances.reverse().map((u: any) => `\"${u.text}\"\\t[time: ${new Date(u.timestamp).toISOString()}]`).join('\\n')\n }${getVoiceResponseReminder()}`,\n },\n ],\n };\n }\n\n if (name === 'wait_for_utterance') {\n const requestedSeconds = (args?.seconds_to_wait as number) ?? DEFAULT_WAIT_TIMEOUT_SECONDS;\n const secondsToWait = Math.max(\n MIN_WAIT_TIMEOUT_SECONDS,\n Math.min(MAX_WAIT_TIMEOUT_SECONDS, requestedSeconds)\n );\n debugLog(`[MCP] Calling wait_for_utterance with ${secondsToWait}s timeout`);\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/wait-for-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ seconds_to_wait: secondsToWait }),\n });\n\n const data = await response.json() as any;\n\n if (data.utterances && data.utterances.length > 0) {\n const utteranceTexts = data.utterances\n .map((u: any) => `[${u.timestamp}] \"${u.text}\"`)\n .join('\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Found ${data.count} utterance(s):\\n\\n${utteranceTexts}${getVoiceResponseReminder()}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: data.message || `No utterances found after waiting ${secondsToWait} seconds.`,\n },\n ],\n };\n }\n }\n\n if (name === 'speak') {\n const text = args?.text as string;\n\n if (!text || !text.trim()) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: Text is required for speak tool',\n },\n ],\n isError: true,\n };\n }\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/speak`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n\n const data = await response.json() as any;\n\n if (response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Spoke: \"${text}\"\\n${data.respondedCount > 0 ? `Marked ${data.respondedCount} utterance(s) as responded.` : 'No delivered utterances to mark as responded.'}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: `Error speaking text: ${data.error || 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n throw new Error(`Unknown tool: ${name}`);\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n });\n\n // Connect via stdio\n const transport = new StdioServerTransport();\n mcpServer.connect(transport);\n console.log('[MCP] Server connected via stdio');\n} else {\n console.log('[MCP] Skipping MCP server initialization (not in MCP-managed mode)');\n}"],"mappings":"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","names":[]}
|
1
|
+
{"version":3,"sources":["../src/unified-server.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport express from 'express';\nimport type { Request, Response } from 'express';\nimport cors from 'cors';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { randomUUID } from 'crypto';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { debugLog } from './debug.ts';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Constants\nconst WAIT_TIMEOUT_SECONDS = 60;\n\n// Promisified exec for async/await\nconst execAsync = promisify(exec);\n\n// Function to play a sound notification\nasync function playNotificationSound() {\n try {\n // Use macOS system sound\n await execAsync('afplay /System/Library/Sounds/Funk.aiff');\n debugLog('[Sound] Played notification sound');\n } catch (error) {\n debugLog(`[Sound] Failed to play sound: ${error}`);\n // Don't throw - sound is not critical\n }\n}\n\n// Shared utterance queue\ninterface Utterance {\n id: string;\n text: string;\n timestamp: Date;\n status: 'pending' | 'delivered' | 'responded';\n}\n\nclass UtteranceQueue {\n utterances: Utterance[] = [];\n\n add(text: string, timestamp?: Date): Utterance {\n const utterance: Utterance = {\n id: randomUUID(),\n text: text.trim(),\n timestamp: timestamp || new Date(),\n status: 'pending'\n };\n\n this.utterances.push(utterance);\n debugLog(`[Queue] queued: \"${utterance.text}\"\t[id: ${utterance.id}]`);\n return utterance;\n }\n\n getRecent(limit: number = 10): Utterance[] {\n return this.utterances\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n }\n\n markDelivered(id: string): void {\n const utterance = this.utterances.find(u => u.id === id);\n if (utterance) {\n utterance.status = 'delivered';\n debugLog(`[Queue] delivered: \"${utterance.text}\"\t[id: ${id}]`);\n }\n }\n\n clear(): void {\n const count = this.utterances.length;\n this.utterances = [];\n debugLog(`[Queue] Cleared ${count} utterances`);\n }\n}\n\n// Determine if we're running in MCP-managed mode\nconst IS_MCP_MANAGED = process.argv.includes('--mcp-managed');\n\n// Global state\nconst queue = new UtteranceQueue();\n// TODO: Uncomment these when Claude Code 1.0.45 is released and we reinstate speak-before-stop requirement\n// let lastToolUseTimestamp: Date | null = null;\n// let lastSpeakTimestamp: Date | null = null;\n\n// Voice preferences (controlled by browser)\nlet voicePreferences = {\n voiceResponsesEnabled: false,\n voiceInputActive: false\n};\n\n// HTTP Server Setup (always created)\nconst app = express();\napp.use(cors());\napp.use(express.json());\napp.use(express.static(path.join(__dirname, '..', 'public')));\n\n// API Routes\napp.post('/api/potential-utterances', (req: Request, res: Response) => {\n const { text, timestamp } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n const parsedTimestamp = timestamp ? new Date(timestamp) : undefined;\n const utterance = queue.add(text, parsedTimestamp);\n res.json({\n success: true,\n utterance: {\n id: utterance.id,\n text: utterance.text,\n timestamp: utterance.timestamp,\n status: utterance.status,\n },\n });\n});\n\napp.get('/api/utterances', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 10;\n const utterances = queue.getRecent(limit);\n\n res.json({\n utterances: utterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: u.status,\n })),\n });\n});\n\napp.get('/api/utterances/status', (_req: Request, res: Response) => {\n const total = queue.utterances.length;\n const pending = queue.utterances.filter(u => u.status === 'pending').length;\n const delivered = queue.utterances.filter(u => u.status === 'delivered').length;\n\n res.json({\n total,\n pending,\n delivered,\n });\n});\n\n// MCP server integration\napp.post('/api/dequeue-utterances', (req: Request, res: Response) => {\n // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot dequeue utterances when voice input is disabled.'\n });\n return;\n }\n\n const { limit = 10 } = req.body;\n const pendingUtterances = queue.utterances\n .filter(u => u.status === 'pending')\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n\n // Mark as delivered\n pendingUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: pendingUtterances.map(u => ({\n text: u.text,\n timestamp: u.timestamp,\n })),\n });\n});\n\n// Wait for utterance endpoint\napp.post('/api/wait-for-utterances', async (req: Request, res: Response) => {\n // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot wait for utterances when voice input is disabled.'\n });\n return;\n }\n\n const secondsToWait = WAIT_TIMEOUT_SECONDS;\n const maxWaitMs = secondsToWait * 1000;\n const startTime = Date.now();\n\n debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);\n\n\n let firstTime = true;\n\n // Poll for utterances\n while (Date.now() - startTime < maxWaitMs) {\n const pendingUtterances = queue.utterances.filter(\n u => u.status === 'pending'\n );\n\n if (pendingUtterances.length > 0) {\n // Found utterances\n\n // Sort by timestamp (oldest first)\n const sortedUtterances = pendingUtterances\n .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());\n\n // Mark utterances as delivered\n sortedUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: sortedUtterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: 'delivered', // They are now delivered\n })),\n count: pendingUtterances.length,\n waitTime: Date.now() - startTime,\n });\n return;\n }\n\n if (firstTime) {\n firstTime = false;\n // Play notification sound since we're about to start waiting\n await playNotificationSound();\n }\n\n // Wait 100ms before checking again\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n\n // Timeout reached - no utterances found\n\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: maxWaitMs,\n });\n});\n\n\n// API for pre-tool hook to check for pending utterances\napp.get('/api/has-pending-utterances', (_req: Request, res: Response) => {\n const pendingCount = queue.utterances.filter(u => u.status === 'pending').length;\n const hasPending = pendingCount > 0;\n\n res.json({\n hasPending,\n pendingCount\n });\n});\n\n// Unified action validation endpoint\napp.post('/api/validate-action', (req: Request, res: Response) => {\n const { action } = req.body;\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n\n if (!action || !['tool-use', 'stop'].includes(action)) {\n res.status(400).json({ error: 'Invalid action. Must be \"tool-use\" or \"stop\"' });\n return;\n }\n\n // Only check for pending utterances if voice input is active\n if (voicePreferences.voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'dequeue_utterances',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n });\n return;\n }\n }\n\n // Check for delivered but unresponded utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'speak',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n });\n return;\n }\n }\n\n // For stop action, check if we should wait (only if voice input is active)\n if (action === 'stop' && voicePreferences.voiceInputActive) {\n if (queue.utterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'wait_for_utterance',\n reason: 'Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n });\n return;\n }\n }\n\n // All checks passed - action is allowed\n res.json({\n allowed: true\n });\n});\n\n// Unified hook handler\nfunction handleHookRequest(attemptedAction: 'tool' | 'speak' | 'wait' | 'stop'): { decision: 'approve' | 'block', reason?: string } {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n const voiceInputActive = voicePreferences.voiceInputActive;\n\n // 1. Check for pending utterances (only if voice input is active)\n if (voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n // Allow dequeue to proceed (dequeue doesn't go through hooks)\n return {\n decision: 'block',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n };\n }\n }\n\n // 2. Check for delivered utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n // Only allow speak to proceed\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n return {\n decision: 'block',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n };\n }\n }\n\n // 3. Handle tool action\n if (attemptedAction === 'tool') {\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastToolUseTimestamp = new Date();\n return { decision: 'approve' };\n }\n\n // 4. Handle wait for utterance\n if (attemptedAction === 'wait') {\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before waiting for utterances.'\n };\n }\n */\n return { decision: 'approve' };\n }\n\n // 5. Handle speak\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n\n // 6. Handle stop\n if (attemptedAction === 'stop') {\n // Check if must speak after tool use\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before proceeding.'\n };\n }\n */\n\n // Check if should wait for utterances (only if voice input is active)\n if (voiceInputActive) {\n return {\n decision: 'block',\n reason: 'Assistant tried to end its response, but voice input is active. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n };\n }\n\n return {\n decision: 'approve',\n reason: 'No utterances since last timeout'\n };\n }\n\n // Default to approve (shouldn't reach here)\n return { decision: 'approve' };\n}\n\n// Dedicated hook endpoints that return in Claude's expected format\napp.post('/api/hooks/pre-tool', (_req: Request, res: Response) => {\n const result = handleHookRequest('tool');\n res.json(result);\n});\n\napp.post('/api/hooks/stop', (_req: Request, res: Response) => {\n const result = handleHookRequest('stop');\n res.json(result);\n});\n\n// Pre-speak hook endpoint\napp.post('/api/hooks/pre-speak', (_req: Request, res: Response) => {\n const result = handleHookRequest('speak');\n res.json(result);\n});\n\n// Pre-wait hook endpoint\napp.post('/api/hooks/pre-wait', (_req: Request, res: Response) => {\n const result = handleHookRequest('wait');\n res.json(result);\n});\n\n// API to clear all utterances\napp.delete('/api/utterances', (_req: Request, res: Response) => {\n const clearedCount = queue.utterances.length;\n queue.clear();\n\n res.json({\n success: true,\n message: `Cleared ${clearedCount} utterances`,\n clearedCount\n });\n});\n\n// Server-Sent Events for TTS notifications\nconst ttsClients = new Set<Response>();\n\napp.get('/api/tts-events', (_req: Request, res: Response) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial connection message\n res.write('data: {\"type\":\"connected\"}\\n\\n');\n\n // Add client to set\n ttsClients.add(res);\n\n // Remove client on disconnect\n res.on('close', () => {\n ttsClients.delete(res);\n });\n});\n\n// Helper function to notify all connected TTS clients\nfunction notifyTTSClients(text: string) {\n const message = JSON.stringify({ type: 'speak', text });\n ttsClients.forEach(client => {\n client.write(`data: ${message}\\n\\n`);\n });\n}\n\n// API for voice preferences\napp.post('/api/voice-preferences', (req: Request, res: Response) => {\n const { voiceResponsesEnabled } = req.body;\n\n // Update preferences\n voicePreferences.voiceResponsesEnabled = !!voiceResponsesEnabled;\n\n debugLog(`[Preferences] Updated: voiceResponses=${voicePreferences.voiceResponsesEnabled}`);\n\n res.json({\n success: true,\n preferences: voicePreferences\n });\n});\n\n// API for voice input state\napp.post('/api/voice-input-state', (req: Request, res: Response) => {\n const { active } = req.body;\n\n // Update voice input state\n voicePreferences.voiceInputActive = !!active;\n\n debugLog(`[Voice Input] ${voicePreferences.voiceInputActive ? 'Started' : 'Stopped'} listening`);\n\n res.json({\n success: true,\n voiceInputActive: voicePreferences.voiceInputActive\n });\n});\n\n// API for text-to-speech\napp.post('/api/speak', async (req: Request, res: Response) => {\n const { text } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n // Check if voice responses are enabled\n if (!voicePreferences.voiceResponsesEnabled) {\n debugLog(`[Speak] Voice responses disabled, returning error`);\n res.status(400).json({\n error: 'Voice responses are disabled',\n message: 'Cannot speak when voice responses are disabled'\n });\n return;\n }\n\n try {\n // Always notify browser clients - they decide how to speak\n notifyTTSClients(text);\n debugLog(`[Speak] Sent text to browser for TTS: \"${text}\"`);\n\n // Note: The browser will decide whether to use system voice or browser voice\n\n // Mark all delivered utterances as responded\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n deliveredUtterances.forEach(u => {\n u.status = 'responded';\n debugLog(`[Queue] marked as responded: \"${u.text}\"\t[id: ${u.id}]`);\n });\n\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastSpeakTimestamp = new Date();\n\n res.json({\n success: true,\n message: 'Text spoken successfully',\n respondedCount: deliveredUtterances.length\n });\n } catch (error) {\n debugLog(`[Speak] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\n// API for system text-to-speech (always uses Mac say command)\napp.post('/api/speak-system', async (req: Request, res: Response) => {\n const { text, rate = 150 } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n try {\n // Execute text-to-speech using macOS say command\n // Note: Mac say command doesn't support volume control\n await execAsync(`say -r ${rate} \"${text.replace(/\"/g, '\\\\\"')}\"`);\n debugLog(`[Speak System] Spoke text using macOS say: \"${text}\" (rate: ${rate})`);\n\n res.json({\n success: true,\n message: 'Text spoken successfully via system voice'\n });\n } catch (error) {\n debugLog(`[Speak System] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text via system voice',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\napp.get('/', (_req: Request, res: Response) => {\n res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));\n});\n\n// Start HTTP server\nconst HTTP_PORT = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\napp.listen(HTTP_PORT, () => {\n console.log(`[HTTP] Server listening on http://localhost:${HTTP_PORT}`);\n console.log(`[Mode] Running in ${IS_MCP_MANAGED ? 'MCP-managed' : 'standalone'} mode`);\n});\n\n// Helper function to get voice response reminder\nfunction getVoiceResponseReminder(): string {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n return voiceResponsesEnabled\n ? '\\n\\nThe user has enabled voice responses, so use the \\'speak\\' tool to respond to the user\\'s voice input before proceeding.'\n : '';\n}\n\n// MCP Server Setup (only if MCP-managed)\nif (IS_MCP_MANAGED) {\n console.log('[MCP] Initializing MCP server...');\n\n const mcpServer = new Server(\n {\n name: 'voice-hooks',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Tool handlers\n mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [\n {\n name: 'dequeue_utterances',\n description: 'Dequeue pending utterances and mark them as delivered',\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'number',\n description: 'Maximum number of utterances to dequeue (default: 10)',\n default: 10,\n },\n },\n },\n },\n {\n name: 'wait_for_utterance',\n description: 'Wait for an utterance to be available or until timeout',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'speak',\n description: 'Speak text using text-to-speech and mark delivered utterances as responded',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The text to speak',\n },\n },\n required: ['text'],\n },\n },\n ],\n };\n });\n\n mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n if (name === 'dequeue_utterances') {\n const limit = (args?.limit as number) ?? 10;\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/dequeue-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ limit }),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to dequeue utterances'}`,\n },\n ],\n };\n }\n\n if (data.utterances.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No recent utterances found.',\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Dequeued ${data.utterances.length} utterance(s):\\n\\n${data.utterances.reverse().map((u: any) => `\"${u.text}\"\\t[time: ${new Date(u.timestamp).toISOString()}]`).join('\\n')\n }${getVoiceResponseReminder()}`,\n },\n ],\n };\n }\n\n if (name === 'wait_for_utterance') {\n debugLog(`[MCP] Calling wait_for_utterance`);\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/wait-for-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to wait for utterances'}`,\n },\n ],\n };\n }\n\n if (data.utterances && data.utterances.length > 0) {\n const utteranceTexts = data.utterances\n .map((u: any) => `[${u.timestamp}] \"${u.text}\"`)\n .join('\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Found ${data.count} utterance(s):\\n\\n${utteranceTexts}${getVoiceResponseReminder()}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: data.message || `No utterances found. Timed out.`,\n },\n ],\n };\n }\n }\n\n if (name === 'speak') {\n const text = args?.text as string;\n\n if (!text || !text.trim()) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: Text is required for speak tool',\n },\n ],\n isError: true,\n };\n }\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/speak`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n\n const data = await response.json() as any;\n\n if (response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: '', // Return empty string for success\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: `Error speaking text: ${data.error || 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n throw new Error(`Unknown tool: ${name}`);\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n });\n\n // Connect via stdio\n const transport = new StdioServerTransport();\n mcpServer.connect(transport);\n console.log('[MCP] Server connected via stdio');\n} else {\n console.log('[MCP] Skipping MCP server initialization (not in MCP-managed mode)');\n}"],"mappings":"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","names":[]}
|