@vellumai/assistant 0.4.23 → 0.4.26

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