@vellumai/assistant 0.4.15 → 0.4.17

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.
Files changed (73) hide show
  1. package/Dockerfile +6 -6
  2. package/README.md +1 -2
  3. package/package.json +1 -1
  4. package/src/__tests__/approval-routes-http.test.ts +383 -254
  5. package/src/__tests__/call-controller.test.ts +1074 -751
  6. package/src/__tests__/call-routes-http.test.ts +329 -279
  7. package/src/__tests__/channel-approval-routes.test.ts +2 -13
  8. package/src/__tests__/channel-approvals.test.ts +227 -182
  9. package/src/__tests__/channel-guardian.test.ts +1 -0
  10. package/src/__tests__/conversation-attention-telegram.test.ts +157 -114
  11. package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
  12. package/src/__tests__/conversation-routes.test.ts +71 -41
  13. package/src/__tests__/daemon-server-session-init.test.ts +258 -191
  14. package/src/__tests__/deterministic-verification-control-plane.test.ts +183 -134
  15. package/src/__tests__/extract-email.test.ts +42 -0
  16. package/src/__tests__/gateway-only-enforcement.test.ts +467 -368
  17. package/src/__tests__/gateway-only-guard.test.ts +54 -55
  18. package/src/__tests__/gmail-integration.test.ts +48 -46
  19. package/src/__tests__/guardian-action-followup-executor.test.ts +215 -150
  20. package/src/__tests__/guardian-outbound-http.test.ts +334 -208
  21. package/src/__tests__/guardian-routing-invariants.test.ts +680 -613
  22. package/src/__tests__/guardian-routing-state.test.ts +257 -209
  23. package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -40
  24. package/src/__tests__/handle-user-message-secret-resume.test.ts +44 -21
  25. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +269 -195
  26. package/src/__tests__/inbound-invite-redemption.test.ts +194 -151
  27. package/src/__tests__/ingress-reconcile.test.ts +184 -142
  28. package/src/__tests__/non-member-access-request.test.ts +291 -247
  29. package/src/__tests__/notification-telegram-adapter.test.ts +60 -46
  30. package/src/__tests__/pairing-concurrent.test.ts +78 -0
  31. package/src/__tests__/recording-intent-handler.test.ts +422 -291
  32. package/src/__tests__/runtime-attachment-metadata.test.ts +107 -69
  33. package/src/__tests__/runtime-events-sse.test.ts +67 -50
  34. package/src/__tests__/send-endpoint-busy.test.ts +314 -232
  35. package/src/__tests__/session-approval-overrides.test.ts +93 -91
  36. package/src/__tests__/sms-messaging-provider.test.ts +74 -47
  37. package/src/__tests__/trusted-contact-approval-notifier.test.ts +339 -274
  38. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -372
  39. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +261 -239
  40. package/src/__tests__/trusted-contact-multichannel.test.ts +179 -140
  41. package/src/__tests__/twilio-config.test.ts +49 -41
  42. package/src/__tests__/twilio-routes-elevenlabs.test.ts +189 -162
  43. package/src/__tests__/twilio-routes.test.ts +389 -280
  44. package/src/calls/call-controller.ts +1 -1
  45. package/src/calls/guardian-action-sweep.ts +6 -6
  46. package/src/calls/twilio-routes.ts +2 -4
  47. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +29 -4
  48. package/src/config/bundled-skills/messaging/SKILL.md +5 -4
  49. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +69 -4
  50. package/src/config/env.ts +39 -29
  51. package/src/daemon/handlers/config-inbox.ts +5 -5
  52. package/src/daemon/handlers/skills.ts +18 -10
  53. package/src/daemon/ipc-contract/messages.ts +1 -0
  54. package/src/daemon/ipc-contract/surfaces.ts +7 -1
  55. package/src/daemon/pairing-store.ts +15 -2
  56. package/src/daemon/session-agent-loop-handlers.ts +5 -0
  57. package/src/daemon/session-agent-loop.ts +1 -1
  58. package/src/daemon/session-process.ts +1 -1
  59. package/src/daemon/session-slash.ts +4 -4
  60. package/src/daemon/session-surfaces.ts +42 -2
  61. package/src/runtime/auth/token-service.ts +95 -45
  62. package/src/runtime/channel-retry-sweep.ts +2 -2
  63. package/src/runtime/http-server.ts +8 -7
  64. package/src/runtime/http-types.ts +1 -1
  65. package/src/runtime/routes/conversation-routes.ts +1 -1
  66. package/src/runtime/routes/guardian-bootstrap-routes.ts +3 -2
  67. package/src/runtime/routes/guardian-expiry-sweep.ts +5 -5
  68. package/src/runtime/routes/pairing-routes.ts +4 -1
  69. package/src/sequence/reply-matcher.ts +14 -4
  70. package/src/skills/frontmatter.ts +9 -6
  71. package/src/tools/ui-surface/definitions.ts +3 -1
  72. package/src/util/platform.ts +0 -12
  73. package/docs/architecture/http-token-refresh.md +0 -274
@@ -5,151 +5,164 @@
5
5
  * (elevenlabs_agent guard) paths by mocking resolveVoiceQualityProfile
6
6
  * to return specific profiles and asserting on HTTP response status/body.
7
7
  */
8
- import { mkdtempSync, realpathSync,rmSync } from 'node:fs';
9
- import { tmpdir } from 'node:os';
10
- import { join } from 'node:path';
8
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
11
 
12
- import { afterAll, beforeEach, describe, expect, mock,test } from 'bun:test';
12
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
13
13
 
14
- const testDir = realpathSync(mkdtempSync(join(tmpdir(), 'twilio-routes-11labs-test-')));
14
+ mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
15
15
 
16
- mock.module('../util/platform.js', () => ({
16
+ const testDir = realpathSync(
17
+ mkdtempSync(join(tmpdir(), "twilio-routes-11labs-test-")),
18
+ );
19
+
20
+ mock.module("../util/platform.js", () => ({
17
21
  getRootDir: () => testDir,
18
22
  getDataDir: () => testDir,
19
- isMacOS: () => process.platform === 'darwin',
20
- isLinux: () => process.platform === 'linux',
21
- isWindows: () => process.platform === 'win32',
22
- getSocketPath: () => join(testDir, 'test.sock'),
23
- getPidPath: () => join(testDir, 'test.pid'),
24
- getDbPath: () => join(testDir, 'test.db'),
25
- getLogPath: () => join(testDir, 'test.log'),
23
+ isMacOS: () => process.platform === "darwin",
24
+ isLinux: () => process.platform === "linux",
25
+ isWindows: () => process.platform === "win32",
26
+ getSocketPath: () => join(testDir, "test.sock"),
27
+ getPidPath: () => join(testDir, "test.pid"),
28
+ getDbPath: () => join(testDir, "test.db"),
29
+ getLogPath: () => join(testDir, "test.log"),
26
30
  ensureDataDir: () => {},
27
31
  }));
28
32
 
29
- mock.module('../util/logger.js', () => ({
30
- getLogger: () => new Proxy({} as Record<string, unknown>, {
31
- get: () => () => {},
32
- }),
33
+ mock.module("../util/logger.js", () => ({
34
+ getLogger: () =>
35
+ new Proxy({} as Record<string, unknown>, {
36
+ get: () => () => {},
37
+ }),
33
38
  }));
34
39
 
35
- mock.module('../config/loader.js', () => ({
40
+ mock.module("../config/loader.js", () => ({
36
41
  getConfig: () => ({
37
42
  ui: {},
38
-
39
- model: 'test',
40
- provider: 'test',
43
+
44
+ model: "test",
45
+ provider: "test",
41
46
  apiKeys: {},
42
47
  memory: { enabled: false },
43
48
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
44
49
  secretDetection: { enabled: false },
45
50
  }),
46
51
  loadConfig: () => ({
47
- model: 'test',
48
- provider: 'test',
52
+ model: "test",
53
+ provider: "test",
49
54
  apiKeys: {},
50
55
  memory: { enabled: false },
51
56
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
52
57
  secretDetection: { enabled: false },
53
58
  calls: {
54
59
  voice: {
55
- mode: 'twilio_standard',
56
- language: 'en-US',
57
- transcriptionProvider: 'Deepgram',
60
+ mode: "twilio_standard",
61
+ language: "en-US",
62
+ transcriptionProvider: "Deepgram",
58
63
  fallbackToStandardOnError: true,
59
64
  elevenlabs: {
60
- voiceId: '',
61
- voiceModelId: '',
65
+ voiceId: "",
66
+ voiceModelId: "",
62
67
  speed: 1.0,
63
68
  stability: 0.5,
64
69
  similarityBoost: 0.75,
65
70
  useSpeakerBoost: true,
66
- agentId: '',
67
- apiBaseUrl: 'https://api.elevenlabs.io',
71
+ agentId: "",
72
+ apiBaseUrl: "https://api.elevenlabs.io",
68
73
  registerCallTimeoutMs: 5000,
69
74
  },
70
75
  },
71
76
  },
72
- ingress: { enabled: false, publicBaseUrl: '' },
77
+ ingress: { enabled: false, publicBaseUrl: "" },
73
78
  }),
74
79
  }));
75
80
 
76
- mock.module('../security/secure-keys.js', () => ({
81
+ mock.module("../security/secure-keys.js", () => ({
77
82
  getSecureKey: () => undefined,
78
83
  }));
79
84
 
80
- mock.module('../calls/twilio-provider.js', () => ({
85
+ mock.module("../calls/twilio-provider.js", () => ({
81
86
  TwilioConversationRelayProvider: class {
82
- readonly name = 'twilio';
83
- static getAuthToken(): string | null { return null; }
84
- static verifyWebhookSignature(): boolean { return true; }
85
- async initiateCall() { return { callSid: 'CA_mock_test' }; }
86
- async endCall() { return; }
87
+ readonly name = "twilio";
88
+ static getAuthToken(): string | null {
89
+ return null;
90
+ }
91
+ static verifyWebhookSignature(): boolean {
92
+ return true;
93
+ }
94
+ async initiateCall() {
95
+ return { callSid: "CA_mock_test" };
96
+ }
97
+ async endCall() {
98
+ return;
99
+ }
87
100
  },
88
101
  }));
89
102
 
90
- mock.module('../calls/twilio-config.js', () => ({
103
+ mock.module("../calls/twilio-config.js", () => ({
91
104
  getTwilioConfig: () => ({
92
- accountSid: 'AC_test',
93
- authToken: 'test-auth-token',
94
- phoneNumber: '+15550001111',
95
- webhookBaseUrl: 'https://test.example.com',
96
- wssBaseUrl: 'wss://test.example.com',
105
+ accountSid: "AC_test",
106
+ authToken: "test-auth-token",
107
+ phoneNumber: "+15550001111",
108
+ webhookBaseUrl: "https://test.example.com",
109
+ wssBaseUrl: "wss://test.example.com",
97
110
  }),
98
111
  }));
99
112
 
100
113
  // Mock ElevenLabs client — should never be called when guard is active.
101
114
  // If any test path reaches register-call, the mock will throw to fail the test.
102
- const mockRegisterCall = mock(() => { throw new Error('register-call should not be reached while guard is active'); });
115
+ const mockRegisterCall = mock(() => {
116
+ throw new Error("register-call should not be reached while guard is active");
117
+ });
103
118
 
104
- mock.module('../calls/elevenlabs-client.js', () => ({
119
+ mock.module("../calls/elevenlabs-client.js", () => ({
105
120
  ElevenLabsClient: class {
106
121
  registerCall = mockRegisterCall;
107
122
  },
108
123
  }));
109
124
 
110
- mock.module('../calls/elevenlabs-config.js', () => ({
125
+ mock.module("../calls/elevenlabs-config.js", () => ({
111
126
  getElevenLabsConfig: () => ({
112
- apiBaseUrl: 'https://api.elevenlabs.io',
113
- apiKey: 'test-key',
114
- agentId: 'agent-abc',
127
+ apiBaseUrl: "https://api.elevenlabs.io",
128
+ apiKey: "test-key",
129
+ agentId: "agent-abc",
115
130
  registerCallTimeoutMs: 5000,
116
131
  }),
117
132
  }));
118
133
 
119
134
  // Mock resolveVoiceQualityProfile and isVoiceProfileValid so we can control
120
135
  // the profile returned to handleVoiceWebhook per-test.
121
- import type { VoiceQualityProfile } from '../calls/voice-quality.js';
136
+ import type { VoiceQualityProfile } from "../calls/voice-quality.js";
122
137
 
123
138
  let mockProfile: VoiceQualityProfile = {
124
- mode: 'twilio_standard',
125
- language: 'en-US',
126
- transcriptionProvider: 'Deepgram',
127
- ttsProvider: 'Google',
128
- voice: 'Google.en-US-Journey-O',
139
+ mode: "twilio_standard",
140
+ language: "en-US",
141
+ transcriptionProvider: "Deepgram",
142
+ ttsProvider: "Google",
143
+ voice: "Google.en-US-Journey-O",
129
144
  fallbackToStandardOnError: true,
130
145
  validationErrors: [],
131
146
  };
132
147
 
133
148
  const mockResolveVoiceQualityProfile = mock(() => mockProfile);
134
149
 
135
- mock.module('../calls/voice-quality.js', () => ({
150
+ mock.module("../calls/voice-quality.js", () => ({
136
151
  resolveVoiceQualityProfile: mockResolveVoiceQualityProfile,
137
- isVoiceProfileValid: (profile: VoiceQualityProfile) => profile.validationErrors.length === 0,
152
+ isVoiceProfileValid: (profile: VoiceQualityProfile) =>
153
+ profile.validationErrors.length === 0,
138
154
  }));
139
155
 
140
156
  // Mock the ingress URL to avoid config lookup issues
141
- mock.module('../inbound/public-ingress-urls.js', () => ({
142
- getTwilioRelayUrl: () => 'wss://test.example.com/v1/calls/relay',
143
- getPublicBaseUrl: () => 'https://test.example.com',
157
+ mock.module("../inbound/public-ingress-urls.js", () => ({
158
+ getTwilioRelayUrl: () => "wss://test.example.com/v1/calls/relay",
159
+ getPublicBaseUrl: () => "https://test.example.com",
144
160
  }));
145
161
 
146
- import {
147
- createCallSession,
148
- updateCallSession,
149
- } from '../calls/call-store.js';
150
- import { handleVoiceWebhook } from '../calls/twilio-routes.js';
151
- import { getDb, initializeDb, resetDb } from '../memory/db.js';
152
- import { conversations } from '../memory/schema.js';
162
+ import { createCallSession, updateCallSession } from "../calls/call-store.js";
163
+ import { handleVoiceWebhook } from "../calls/twilio-routes.js";
164
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
165
+ import { conversations } from "../memory/schema.js";
153
166
 
154
167
  initializeDb();
155
168
 
@@ -161,25 +174,27 @@ function ensureConversation(id: string): void {
161
174
  if (ensuredConvIds.has(id)) return;
162
175
  const db = getDb();
163
176
  const now = Date.now();
164
- db.insert(conversations).values({
165
- id,
166
- title: `Test conversation ${id}`,
167
- createdAt: now,
168
- updatedAt: now,
169
- }).run();
177
+ db.insert(conversations)
178
+ .values({
179
+ id,
180
+ title: `Test conversation ${id}`,
181
+ createdAt: now,
182
+ updatedAt: now,
183
+ })
184
+ .run();
170
185
  ensuredConvIds.add(id);
171
186
  }
172
187
 
173
188
  function resetTables() {
174
189
  const db = getDb();
175
- db.run('DELETE FROM guardian_action_deliveries');
176
- db.run('DELETE FROM guardian_action_requests');
177
- db.run('DELETE FROM processed_callbacks');
178
- db.run('DELETE FROM call_pending_questions');
179
- db.run('DELETE FROM call_events');
180
- db.run('DELETE FROM call_sessions');
181
- db.run('DELETE FROM messages');
182
- db.run('DELETE FROM conversations');
190
+ db.run("DELETE FROM guardian_action_deliveries");
191
+ db.run("DELETE FROM guardian_action_requests");
192
+ db.run("DELETE FROM processed_callbacks");
193
+ db.run("DELETE FROM call_pending_questions");
194
+ db.run("DELETE FROM call_events");
195
+ db.run("DELETE FROM call_sessions");
196
+ db.run("DELETE FROM messages");
197
+ db.run("DELETE FROM conversations");
183
198
  ensuredConvIds = new Set();
184
199
  }
185
200
 
@@ -187,26 +202,32 @@ function createTestSession(convId: string, callSid: string) {
187
202
  ensureConversation(convId);
188
203
  const session = createCallSession({
189
204
  conversationId: convId,
190
- provider: 'twilio',
191
- fromNumber: '+15550001111',
192
- toNumber: '+15559998888',
193
- task: 'test task',
205
+ provider: "twilio",
206
+ fromNumber: "+15550001111",
207
+ toNumber: "+15559998888",
208
+ task: "test task",
194
209
  });
195
210
  updateCallSession(session.id, { providerCallSid: callSid });
196
211
  return session;
197
212
  }
198
213
 
199
- function makeVoiceRequest(sessionId: string, params: Record<string, string>): Request {
200
- return new Request(`http://127.0.0.1/v1/calls/twilio/voice-webhook?callSessionId=${sessionId}`, {
201
- method: 'POST',
202
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
203
- body: new URLSearchParams(params).toString(),
204
- });
214
+ function makeVoiceRequest(
215
+ sessionId: string,
216
+ params: Record<string, string>,
217
+ ): Request {
218
+ return new Request(
219
+ `http://127.0.0.1/v1/calls/twilio/voice-webhook?callSessionId=${sessionId}`,
220
+ {
221
+ method: "POST",
222
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
223
+ body: new URLSearchParams(params).toString(),
224
+ },
225
+ );
205
226
  }
206
227
 
207
228
  // ── Tests ──────────────────────────────────────────────────────────────
208
229
 
209
- describe('handleVoiceWebhook ElevenLabs branches', () => {
230
+ describe("handleVoiceWebhook ElevenLabs branches", () => {
210
231
  beforeEach(() => {
211
232
  resetTables();
212
233
  // Reset mock to default implementation
@@ -214,11 +235,11 @@ describe('handleVoiceWebhook ElevenLabs branches', () => {
214
235
  mockRegisterCall.mockClear();
215
236
  // Reset to standard default profile between tests
216
237
  mockProfile = {
217
- mode: 'twilio_standard',
218
- language: 'en-US',
219
- transcriptionProvider: 'Deepgram',
220
- ttsProvider: 'Google',
221
- voice: 'Google.en-US-Journey-O',
238
+ mode: "twilio_standard",
239
+ language: "en-US",
240
+ transcriptionProvider: "Deepgram",
241
+ ttsProvider: "Google",
242
+ voice: "Google.en-US-Journey-O",
222
243
  fallbackToStandardOnError: true,
223
244
  validationErrors: [],
224
245
  };
@@ -227,108 +248,114 @@ describe('handleVoiceWebhook ElevenLabs branches', () => {
227
248
 
228
249
  afterAll(() => {
229
250
  resetDb();
230
- try { rmSync(testDir, { recursive: true, force: true }); } catch { /* best effort */ }
251
+ try {
252
+ rmSync(testDir, { recursive: true, force: true });
253
+ } catch {
254
+ /* best effort */
255
+ }
231
256
  });
232
257
 
233
258
  // ── WS-A: Invalid config + fallback disabled => 500 ────────────────
234
- test('twilio_elevenlabs_tts invalid config with fallback disabled returns 500', async () => {
259
+ test("twilio_elevenlabs_tts invalid config with fallback disabled returns 500", async () => {
235
260
  mockProfile = {
236
- mode: 'twilio_elevenlabs_tts',
237
- language: 'en-US',
238
- transcriptionProvider: 'Deepgram',
239
- ttsProvider: 'ElevenLabs',
240
- voice: '',
261
+ mode: "twilio_elevenlabs_tts",
262
+ language: "en-US",
263
+ transcriptionProvider: "Deepgram",
264
+ ttsProvider: "ElevenLabs",
265
+ voice: "",
241
266
  fallbackToStandardOnError: false,
242
- validationErrors: ['voiceId is required'],
267
+ validationErrors: ["voiceId is required"],
243
268
  };
244
269
 
245
- const session = createTestSession('conv-11labs-1', 'CA_11labs_1');
246
- const req = makeVoiceRequest(session.id, { CallSid: 'CA_11labs_1' });
270
+ const session = createTestSession("conv-11labs-1", "CA_11labs_1");
271
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_1" });
247
272
 
248
273
  const res = await handleVoiceWebhook(req);
249
274
 
250
275
  expect(res.status).toBe(500);
251
276
  const body = await res.text();
252
- expect(body).toContain('Voice quality configuration error');
253
- expect(body).toContain('voiceId is required');
277
+ expect(body).toContain("Voice quality configuration error");
278
+ expect(body).toContain("voiceId is required");
254
279
  });
255
280
 
256
281
  // ── WS-A: Invalid config + fallback enabled => standard TwiML ──────
257
- test('twilio_elevenlabs_tts invalid config with fallback enabled returns standard TwiML', async () => {
282
+ test("twilio_elevenlabs_tts invalid config with fallback enabled returns standard TwiML", async () => {
258
283
  // When fallback is enabled and voiceId is missing, resolveVoiceQualityProfile
259
284
  // falls back to standard mode but retains validation errors.
260
285
  mockProfile = {
261
- mode: 'twilio_standard',
262
- language: 'en-US',
263
- transcriptionProvider: 'Deepgram',
264
- ttsProvider: 'Google',
265
- voice: 'Google.en-US-Journey-O',
286
+ mode: "twilio_standard",
287
+ language: "en-US",
288
+ transcriptionProvider: "Deepgram",
289
+ ttsProvider: "Google",
290
+ voice: "Google.en-US-Journey-O",
266
291
  fallbackToStandardOnError: true,
267
- validationErrors: ['calls.voice.elevenlabs.voiceId is empty; falling back to twilio_standard'],
292
+ validationErrors: [
293
+ "calls.voice.elevenlabs.voiceId is empty; falling back to twilio_standard",
294
+ ],
268
295
  };
269
296
 
270
- const session = createTestSession('conv-11labs-2', 'CA_11labs_2');
271
- const req = makeVoiceRequest(session.id, { CallSid: 'CA_11labs_2' });
297
+ const session = createTestSession("conv-11labs-2", "CA_11labs_2");
298
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_2" });
272
299
 
273
300
  const res = await handleVoiceWebhook(req);
274
301
 
275
302
  expect(res.status).toBe(200);
276
303
  const twiml = await res.text();
277
304
  expect(twiml).toContain('ttsProvider="Google"');
278
- expect(twiml).toContain('<ConversationRelay');
305
+ expect(twiml).toContain("<ConversationRelay");
279
306
  expect(twiml).toContain('voice="Google.en-US-Journey-O"');
280
307
  });
281
308
 
282
309
  // ── WS-B: elevenlabs_agent strict mode (fallback false) => 501 ─────
283
- test('elevenlabs_agent with fallback disabled returns 501', async () => {
310
+ test("elevenlabs_agent with fallback disabled returns 501", async () => {
284
311
  mockProfile = {
285
- mode: 'elevenlabs_agent',
286
- language: 'en-US',
287
- transcriptionProvider: 'Deepgram',
288
- ttsProvider: 'ElevenLabs',
289
- voice: 'voice123-turbo_v2_5-1_0.5_0.75',
290
- agentId: 'agent-abc',
312
+ mode: "elevenlabs_agent",
313
+ language: "en-US",
314
+ transcriptionProvider: "Deepgram",
315
+ ttsProvider: "ElevenLabs",
316
+ voice: "voice123-turbo_v2_5-1_0.5_0.75",
317
+ agentId: "agent-abc",
291
318
  fallbackToStandardOnError: false,
292
319
  validationErrors: [],
293
320
  };
294
321
 
295
- const session = createTestSession('conv-11labs-3', 'CA_11labs_3');
296
- const req = makeVoiceRequest(session.id, { CallSid: 'CA_11labs_3' });
322
+ const session = createTestSession("conv-11labs-3", "CA_11labs_3");
323
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_3" });
297
324
 
298
325
  const res = await handleVoiceWebhook(req);
299
326
 
300
327
  expect(res.status).toBe(501);
301
328
  const body = await res.text();
302
- expect(body).toContain('consultation bridging');
303
- expect(body).toContain('elevenlabs_agent mode is restricted');
329
+ expect(body).toContain("consultation bridging");
330
+ expect(body).toContain("elevenlabs_agent mode is restricted");
304
331
  });
305
332
 
306
333
  // ── WS-B: elevenlabs_agent with fallback true => standard TwiML ────
307
- test('elevenlabs_agent with fallback enabled returns standard TwiML', async () => {
334
+ test("elevenlabs_agent with fallback enabled returns standard TwiML", async () => {
308
335
  // First call returns the elevenlabs_agent profile (triggers the guard)
309
336
  mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
310
- mode: 'elevenlabs_agent' as const,
311
- language: 'en-US',
312
- transcriptionProvider: 'Deepgram',
313
- ttsProvider: 'ElevenLabs',
314
- voice: 'voice123-turbo_v2_5-1_0.5_0.75',
315
- agentId: 'agent-abc',
337
+ mode: "elevenlabs_agent" as const,
338
+ language: "en-US",
339
+ transcriptionProvider: "Deepgram",
340
+ ttsProvider: "ElevenLabs",
341
+ voice: "voice123-turbo_v2_5-1_0.5_0.75",
342
+ agentId: "agent-abc",
316
343
  fallbackToStandardOnError: true,
317
344
  validationErrors: [],
318
345
  }));
319
346
  // Second call returns the standard profile (used for TwiML generation)
320
347
  mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
321
- mode: 'twilio_standard' as const,
322
- language: 'en-US',
323
- transcriptionProvider: 'Deepgram',
324
- ttsProvider: 'Google',
325
- voice: 'Google.en-US-Journey-O',
348
+ mode: "twilio_standard" as const,
349
+ language: "en-US",
350
+ transcriptionProvider: "Deepgram",
351
+ ttsProvider: "Google",
352
+ voice: "Google.en-US-Journey-O",
326
353
  fallbackToStandardOnError: true,
327
354
  validationErrors: [],
328
355
  }));
329
356
 
330
- const session = createTestSession('conv-11labs-4', 'CA_11labs_4');
331
- const req = makeVoiceRequest(session.id, { CallSid: 'CA_11labs_4' });
357
+ const session = createTestSession("conv-11labs-4", "CA_11labs_4");
358
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_4" });
332
359
 
333
360
  const res = await handleVoiceWebhook(req);
334
361
 
@@ -337,39 +364,39 @@ describe('handleVoiceWebhook ElevenLabs branches', () => {
337
364
  // When elevenlabs_agent is guarded with fallback enabled, the handler
338
365
  // replaces the profile with a standard twilio_standard profile and
339
366
  // generates TwiML with Google TTS instead of ElevenLabs.
340
- expect(twiml).toContain('<ConversationRelay');
367
+ expect(twiml).toContain("<ConversationRelay");
341
368
  expect(twiml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
342
- expect(twiml).toContain('<Response>');
343
- expect(twiml).toContain('<Connect>');
369
+ expect(twiml).toContain("<Response>");
370
+ expect(twiml).toContain("<Connect>");
344
371
  expect(twiml).toContain('ttsProvider="Google"');
345
372
  expect(twiml).toContain('voice="Google.en-US-Journey-O"');
346
373
  });
347
374
 
348
375
  // ── Guard prevents register-call attempt ────────────────────────────
349
- test('guarded elevenlabs_agent does not attempt ElevenLabs register-call', async () => {
376
+ test("guarded elevenlabs_agent does not attempt ElevenLabs register-call", async () => {
350
377
  // Configure as elevenlabs_agent with fallback (guard will fire)
351
378
  mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
352
- mode: 'elevenlabs_agent' as const,
353
- language: 'en-US',
354
- transcriptionProvider: 'Deepgram',
355
- ttsProvider: 'ElevenLabs',
356
- voice: 'voice123-turbo_v2_5-1_0.5_0.75',
357
- agentId: 'agent-abc',
379
+ mode: "elevenlabs_agent" as const,
380
+ language: "en-US",
381
+ transcriptionProvider: "Deepgram",
382
+ ttsProvider: "ElevenLabs",
383
+ voice: "voice123-turbo_v2_5-1_0.5_0.75",
384
+ agentId: "agent-abc",
358
385
  fallbackToStandardOnError: true,
359
386
  validationErrors: [],
360
387
  }));
361
388
  mockResolveVoiceQualityProfile.mockImplementationOnce(() => ({
362
- mode: 'twilio_standard' as const,
363
- language: 'en-US',
364
- transcriptionProvider: 'Deepgram',
365
- ttsProvider: 'Google',
366
- voice: 'Google.en-US-Journey-O',
389
+ mode: "twilio_standard" as const,
390
+ language: "en-US",
391
+ transcriptionProvider: "Deepgram",
392
+ ttsProvider: "Google",
393
+ voice: "Google.en-US-Journey-O",
367
394
  fallbackToStandardOnError: true,
368
395
  validationErrors: [],
369
396
  }));
370
397
 
371
- const session = createTestSession('conv-11labs-5', 'CA_11labs_5');
372
- const req = makeVoiceRequest(session.id, { CallSid: 'CA_11labs_5' });
398
+ const session = createTestSession("conv-11labs-5", "CA_11labs_5");
399
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_11labs_5" });
373
400
 
374
401
  const res = await handleVoiceWebhook(req);
375
402