@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
package/bun.lock CHANGED
@@ -11,6 +11,7 @@
11
11
  "@modelcontextprotocol/sdk": "^1.15.1",
12
12
  "@qdrant/js-client-rest": "^1.16.2",
13
13
  "@sentry/node": "^10.38.0",
14
+ "@vellumai/proxy-sidecar": "file:../proxy-sidecar",
14
15
  "agentmail": "^0.1.0",
15
16
  "archiver": "^7.0.1",
16
17
  "commander": "^13.1.0",
@@ -517,6 +518,8 @@
517
518
 
518
519
  "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="],
519
520
 
521
+ "@vellumai/proxy-sidecar": ["@vellumai/proxy-sidecar@file:../proxy-sidecar", { "devDependencies": { "@types/bun": "^1.2.4", "@types/node": "^25.2.2", "typescript": "^5.7.3" } }],
522
+
520
523
  "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
521
524
 
522
525
  "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.4.23",
3
+ "version": "0.4.26",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vellum": "./src/index.ts"
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@anthropic-ai/claude-agent-sdk": "^0.2.42",
30
+ "@vellumai/proxy-sidecar": "file:../proxy-sidecar",
30
31
  "@anthropic-ai/sdk": "^0.39.0",
31
32
  "@google/genai": "^1.40.0",
32
33
  "@modelcontextprotocol/sdk": "^1.15.1",
@@ -768,14 +768,6 @@ exports[`IPC message snapshots ClientMessage types oauth_connect_start serialize
768
768
  }
769
769
  `;
770
770
 
771
- exports[`IPC message snapshots ClientMessage types browser_cdp_response serializes to expected JSON 1`] = `
772
- {
773
- "sessionId": "test-session",
774
- "success": true,
775
- "type": "browser_cdp_response",
776
- }
777
- `;
778
-
779
771
  exports[`IPC message snapshots ClientMessage types work_items_list serializes to expected JSON 1`] = `
780
772
  {
781
773
  "status": "queued",
@@ -2455,13 +2447,6 @@ exports[`IPC message snapshots ServerMessage types oauth_connect_result serializ
2455
2447
  }
2456
2448
  `;
2457
2449
 
2458
- exports[`IPC message snapshots ServerMessage types browser_cdp_request serializes to expected JSON 1`] = `
2459
- {
2460
- "sessionId": "test-session",
2461
- "type": "browser_cdp_request",
2462
- }
2463
- `;
2464
-
2465
2450
  exports[`IPC message snapshots ServerMessage types document_editor_show serializes to expected JSON 1`] = `
2466
2451
  {
2467
2452
  "initialContent": "# Hello World",
@@ -193,9 +193,15 @@ describe("SSE route — capacity limit", () => {
193
193
  expect(res2.status).toBe(200);
194
194
  expect(hub.subscriberCount()).toBe(1); // evicted 1, added 1
195
195
 
196
- // First stream should be closed by onEvict.
197
- const { done } = await reader1.read();
198
- expect(done).toBe(true);
196
+ // First stream: the immediate heartbeat was enqueued during start(),
197
+ // then eviction closed the controller. Read past any buffered data
198
+ // until the stream signals done.
199
+ let evictDone = false;
200
+ while (!evictDone) {
201
+ const result = await reader1.read();
202
+ evictDone = result.done;
203
+ }
204
+ expect(evictDone).toBe(true);
199
205
 
200
206
  ac2.abort();
201
207
  });
@@ -2138,6 +2138,86 @@ describe("call-controller", () => {
2138
2138
  controller.destroy();
2139
2139
  });
2140
2140
 
2141
+ test('silence timeout suppressed during in-call guardian consultation (pendingGuardianInput)', async () => {
2142
+ mockSilenceTimeoutMs = 50; // Short timeout for testing
2143
+ mockConsultationTimeoutMs = 10_000; // Long enough to not interfere
2144
+
2145
+ // LLM emits an ASK_GUARDIAN marker so the controller creates a pendingGuardianInput
2146
+ mockStartVoiceTurn.mockImplementation(
2147
+ createMockVoiceTurn(["Let me check with your guardian. [ASK_GUARDIAN: Can this caller access the account?]"]),
2148
+ );
2149
+ const { relay, controller } = setupController();
2150
+
2151
+ // Trigger a turn that creates a pending guardian input request
2152
+ await controller.handleCallerUtterance("I need to access the account");
2153
+ // Allow turn to complete
2154
+ await new Promise((r) => setTimeout(r, 50));
2155
+
2156
+ // Verify a guardian input request is now pending
2157
+ expect(controller.getPendingConsultationQuestionId()).not.toBeNull();
2158
+ // Relay state is still 'connected' (not 'awaiting_guardian_decision')
2159
+ expect(relay.mockConnectionState).toBe("connected");
2160
+
2161
+ // Clear any tokens from the turn itself
2162
+ relay.sentTokens.length = 0;
2163
+
2164
+ // Wait for the silence timeout to fire
2165
+ await new Promise((r) => setTimeout(r, 200));
2166
+
2167
+ // "Are you still there?" should NOT have been sent because
2168
+ // pendingGuardianInput is active
2169
+ const silenceTokens = relay.sentTokens.filter((t) =>
2170
+ t.token.includes("Are you still there?"),
2171
+ );
2172
+ expect(silenceTokens.length).toBe(0);
2173
+
2174
+ controller.destroy();
2175
+ });
2176
+
2177
+ test('silence nudge resumes after guardian consultation resolves', async () => {
2178
+ mockSilenceTimeoutMs = 50; // Short timeout for testing
2179
+ mockConsultationTimeoutMs = 10_000; // Long enough to not interfere
2180
+
2181
+ // LLM emits an ASK_GUARDIAN marker so the controller creates a pendingGuardianInput
2182
+ mockStartVoiceTurn.mockImplementation(
2183
+ createMockVoiceTurn(["Let me check. [ASK_GUARDIAN: Is this approved?]"]),
2184
+ );
2185
+ const { relay, controller } = setupController();
2186
+
2187
+ // Trigger a turn that creates a pending guardian input request
2188
+ await controller.handleCallerUtterance("Can I do this?");
2189
+ await new Promise((r) => setTimeout(r, 50));
2190
+
2191
+ // Verify guardian input request is pending
2192
+ expect(controller.getPendingConsultationQuestionId()).not.toBeNull();
2193
+
2194
+ // Now resolve the consultation by providing an answer
2195
+ // Mock the next LLM turn for the answer-driven follow-up
2196
+ mockStartVoiceTurn.mockImplementation(
2197
+ createMockVoiceTurn(["Great news, your guardian approved the request."]),
2198
+ );
2199
+ await controller.handleUserAnswer("Yes, approved");
2200
+ // Allow the answer-driven turn to complete
2201
+ await new Promise((r) => setTimeout(r, 100));
2202
+
2203
+ // Guardian input request should now be cleared
2204
+ expect(controller.getPendingConsultationQuestionId()).toBeNull();
2205
+
2206
+ // Clear tokens from the answer turn
2207
+ relay.sentTokens.length = 0;
2208
+
2209
+ // Wait for the silence timeout to fire again
2210
+ await new Promise((r) => setTimeout(r, 200));
2211
+
2212
+ // "Are you still there?" SHOULD fire now that guardian wait is resolved
2213
+ const silenceTokens = relay.sentTokens.filter((t) =>
2214
+ t.token.includes("Are you still there?"),
2215
+ );
2216
+ expect(silenceTokens.length).toBe(1);
2217
+
2218
+ controller.destroy();
2219
+ });
2220
+
2141
2221
  // ── Pointer message regression tests ─────────────────────────────
2142
2222
 
2143
2223
  test("END_CALL marker writes completed pointer to origin conversation", async () => {
@@ -658,10 +658,10 @@ describe("AssistantConfigSchema", () => {
658
658
  userConsultTimeoutSeconds: 120,
659
659
  ttsPlaybackDelayMs: 3000,
660
660
  accessRequestPollIntervalMs: 500,
661
- guardianWaitUpdateInitialIntervalMs: 5000,
661
+ guardianWaitUpdateInitialIntervalMs: 15000,
662
662
  guardianWaitUpdateInitialWindowMs: 30000,
663
- guardianWaitUpdateSteadyMinIntervalMs: 7000,
664
- guardianWaitUpdateSteadyMaxIntervalMs: 10000,
663
+ guardianWaitUpdateSteadyMinIntervalMs: 20000,
664
+ guardianWaitUpdateSteadyMaxIntervalMs: 30000,
665
665
  disclosure: {
666
666
  enabled: true,
667
667
  text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent. Do not say "AI assistant".',
@@ -670,21 +670,8 @@ describe("AssistantConfigSchema", () => {
670
670
  denyCategories: [],
671
671
  },
672
672
  voice: {
673
- mode: "twilio_standard",
674
673
  language: "en-US",
675
674
  transcriptionProvider: "Deepgram",
676
- fallbackToStandardOnError: true,
677
- elevenlabs: {
678
- voiceId: "",
679
- voiceModelId: "",
680
- speed: 1.0,
681
- stability: 0.5,
682
- similarityBoost: 0.75,
683
- useSpeakerBoost: true,
684
- agentId: "",
685
- apiBaseUrl: "https://api.elevenlabs.io",
686
- registerCallTimeoutMs: 5000,
687
- },
688
675
  },
689
676
  callerIdentity: {
690
677
  allowPerCallOverride: true,
@@ -789,43 +776,28 @@ describe("AssistantConfigSchema", () => {
789
776
 
790
777
  test("config without calls.voice parses correctly and produces defaults", () => {
791
778
  const result = AssistantConfigSchema.parse({});
792
- expect(result.calls.voice.mode).toBe("twilio_standard");
793
779
  expect(result.calls.voice.language).toBe("en-US");
794
780
  expect(result.calls.voice.transcriptionProvider).toBe("Deepgram");
795
- expect(result.calls.voice.fallbackToStandardOnError).toBe(true);
796
- expect(result.calls.voice.elevenlabs.voiceId).toBe("");
797
- expect(result.calls.voice.elevenlabs.voiceModelId).toBe("");
798
- expect(result.calls.voice.elevenlabs.speed).toBe(1.0);
799
- expect(result.calls.voice.elevenlabs.stability).toBe(0.5);
800
- expect(result.calls.voice.elevenlabs.similarityBoost).toBe(0.75);
801
- expect(result.calls.voice.elevenlabs.useSpeakerBoost).toBe(true);
802
- expect(result.calls.voice.elevenlabs.agentId).toBe("");
803
- expect(result.calls.voice.elevenlabs.apiBaseUrl).toBe(
804
- "https://api.elevenlabs.io",
805
- );
806
- expect(result.calls.voice.elevenlabs.registerCallTimeoutMs).toBe(5000);
807
781
  });
808
782
 
809
- test("legacy style field is silently stripped by schema", () => {
810
- const result = AssistantConfigSchema.parse({
811
- calls: { voice: { elevenlabs: { style: 0.5 } } },
812
- });
813
- expect(
814
- (result.calls.voice.elevenlabs as Record<string, unknown>).style,
815
- ).toBeUndefined();
816
- expect(result.calls.voice.elevenlabs.speed).toBe(1.0);
783
+ test("elevenlabs tuning params have correct defaults", () => {
784
+ const result = AssistantConfigSchema.parse({});
785
+ expect(result.elevenlabs.voiceModelId).toBe("");
786
+ expect(result.elevenlabs.speed).toBe(1.0);
787
+ expect(result.elevenlabs.stability).toBe(0.5);
788
+ expect(result.elevenlabs.similarityBoost).toBe(0.75);
817
789
  });
818
790
 
819
- test("rejects calls.voice.elevenlabs.speed below 0.7", () => {
791
+ test("rejects elevenlabs.speed below 0.7", () => {
820
792
  const result = AssistantConfigSchema.safeParse({
821
- calls: { voice: { elevenlabs: { speed: 0.5 } } },
793
+ elevenlabs: { speed: 0.5 },
822
794
  });
823
795
  expect(result.success).toBe(false);
824
796
  });
825
797
 
826
- test("rejects calls.voice.elevenlabs.speed above 1.2", () => {
798
+ test("rejects elevenlabs.speed above 1.2", () => {
827
799
  const result = AssistantConfigSchema.safeParse({
828
- calls: { voice: { elevenlabs: { speed: 1.5 } } },
800
+ elevenlabs: { speed: 1.5 },
829
801
  });
830
802
  expect(result.success).toBe(false);
831
803
  });
@@ -834,37 +806,20 @@ describe("AssistantConfigSchema", () => {
834
806
  const result = AssistantConfigSchema.parse({
835
807
  calls: {
836
808
  voice: {
837
- mode: "twilio_elevenlabs_tts",
838
809
  language: "es-ES",
839
810
  transcriptionProvider: "Google",
840
- fallbackToStandardOnError: false,
841
- elevenlabs: {
842
- voiceId: "abc123",
843
- stability: 0.8,
844
- },
845
811
  },
846
812
  },
813
+ elevenlabs: {
814
+ stability: 0.8,
815
+ },
847
816
  });
848
- expect(result.calls.voice.mode).toBe("twilio_elevenlabs_tts");
849
817
  expect(result.calls.voice.language).toBe("es-ES");
850
818
  expect(result.calls.voice.transcriptionProvider).toBe("Google");
851
- expect(result.calls.voice.fallbackToStandardOnError).toBe(false);
852
- expect(result.calls.voice.elevenlabs.voiceId).toBe("abc123");
853
- expect(result.calls.voice.elevenlabs.stability).toBe(0.8);
819
+ expect(result.elevenlabs.stability).toBe(0.8);
854
820
  // Defaults preserved for unset fields
855
- expect(result.calls.voice.elevenlabs.voiceModelId).toBe("");
856
- expect(result.calls.voice.elevenlabs.similarityBoost).toBe(0.75);
857
- });
858
-
859
- test("rejects invalid calls.voice.mode", () => {
860
- const result = AssistantConfigSchema.safeParse({
861
- calls: { voice: { mode: "invalid_mode" } },
862
- });
863
- expect(result.success).toBe(false);
864
- if (!result.success) {
865
- const msgs = result.error.issues.map((i) => i.message);
866
- expect(msgs.some((m) => m.includes("calls.voice.mode"))).toBe(true);
867
- }
821
+ expect(result.elevenlabs.voiceModelId).toBe("");
822
+ expect(result.elevenlabs.similarityBoost).toBe(0.75);
868
823
  });
869
824
 
870
825
  test("rejects invalid calls.voice.transcriptionProvider", () => {
@@ -880,23 +835,9 @@ describe("AssistantConfigSchema", () => {
880
835
  }
881
836
  });
882
837
 
883
- test("rejects calls.voice.elevenlabs.stability out of range", () => {
838
+ test("rejects elevenlabs.stability out of range", () => {
884
839
  const result = AssistantConfigSchema.safeParse({
885
- calls: { voice: { elevenlabs: { stability: 1.5 } } },
886
- });
887
- expect(result.success).toBe(false);
888
- });
889
-
890
- test("rejects calls.voice.elevenlabs.registerCallTimeoutMs below 1000", () => {
891
- const result = AssistantConfigSchema.safeParse({
892
- calls: { voice: { elevenlabs: { registerCallTimeoutMs: 500 } } },
893
- });
894
- expect(result.success).toBe(false);
895
- });
896
-
897
- test("rejects calls.voice.elevenlabs.registerCallTimeoutMs above 15000", () => {
898
- const result = AssistantConfigSchema.safeParse({
899
- calls: { voice: { elevenlabs: { registerCallTimeoutMs: 20000 } } },
840
+ elevenlabs: { stability: 1.5 },
900
841
  });
901
842
  expect(result.success).toBe(false);
902
843
  });
@@ -972,114 +913,40 @@ describe("AssistantConfigSchema", () => {
972
913
  // ---------------------------------------------------------------------------
973
914
 
974
915
  describe("resolveVoiceQualityProfile", () => {
975
- test("returns correct profile for twilio_standard", () => {
916
+ test("always returns ElevenLabs ttsProvider", () => {
976
917
  const config = AssistantConfigSchema.parse({});
977
918
  const profile = resolveVoiceQualityProfile(config);
978
- expect(profile.mode).toBe("twilio_standard");
979
- expect(profile.ttsProvider).toBe("Google");
980
- expect(profile.voice).toBe("Google.en-US-Journey-O");
919
+ expect(profile.ttsProvider).toBe("ElevenLabs");
981
920
  expect(profile.transcriptionProvider).toBe("Deepgram");
982
- expect(profile.fallbackToStandardOnError).toBe(true);
983
- expect(profile.validationErrors).toEqual([]);
984
921
  });
985
922
 
986
- test("returns correct profile for twilio_elevenlabs_tts with valid voiceId", () => {
923
+ test("uses shared elevenlabs.voiceId for voice", () => {
987
924
  const config = AssistantConfigSchema.parse({
988
- calls: {
989
- voice: {
990
- mode: "twilio_elevenlabs_tts",
991
- elevenlabs: { voiceId: "test-voice-id" },
992
- },
993
- },
925
+ elevenlabs: { voiceId: "test-voice-id" },
994
926
  });
995
927
  const profile = resolveVoiceQualityProfile(config);
996
- expect(profile.mode).toBe("twilio_elevenlabs_tts");
997
928
  expect(profile.ttsProvider).toBe("ElevenLabs");
998
929
  expect(profile.voice).toBe("test-voice-id");
999
- expect(profile.validationErrors).toEqual([]);
1000
930
  });
1001
931
 
1002
- test("falls back for twilio_elevenlabs_tts with empty voiceId and fallback enabled", () => {
1003
- const config = AssistantConfigSchema.parse({
1004
- calls: {
1005
- voice: {
1006
- mode: "twilio_elevenlabs_tts",
1007
- fallbackToStandardOnError: true,
1008
- elevenlabs: { voiceId: "" },
1009
- },
1010
- },
1011
- });
1012
- const profile = resolveVoiceQualityProfile(config);
1013
- expect(profile.mode).toBe("twilio_standard");
1014
- expect(profile.ttsProvider).toBe("Google");
1015
- expect(profile.voice).toBe("Google.en-US-Journey-O");
1016
- expect(profile.validationErrors.length).toBe(1);
1017
- expect(profile.validationErrors[0]).toContain("falling back");
1018
- });
1019
-
1020
- test("returns errors for twilio_elevenlabs_tts with empty voiceId and fallback disabled", () => {
1021
- const config = AssistantConfigSchema.parse({
1022
- calls: {
1023
- voice: {
1024
- mode: "twilio_elevenlabs_tts",
1025
- fallbackToStandardOnError: false,
1026
- elevenlabs: { voiceId: "" },
1027
- },
1028
- },
1029
- });
1030
- const profile = resolveVoiceQualityProfile(config);
1031
- expect(profile.mode).toBe("twilio_elevenlabs_tts");
1032
- expect(profile.validationErrors.length).toBe(1);
1033
- expect(profile.validationErrors[0]).toContain("voiceId is required");
1034
- });
1035
-
1036
- test("returns correct profile for elevenlabs_agent with valid agentId", () => {
1037
- const config = AssistantConfigSchema.parse({
1038
- calls: {
1039
- voice: {
1040
- mode: "elevenlabs_agent",
1041
- elevenlabs: { agentId: "agent-123", voiceId: "v1" },
1042
- },
1043
- },
1044
- });
1045
- const profile = resolveVoiceQualityProfile(config);
1046
- expect(profile.mode).toBe("elevenlabs_agent");
1047
- expect(profile.ttsProvider).toBe("ElevenLabs");
1048
- expect(profile.voice).toBe("v1");
1049
- expect(profile.agentId).toBe("agent-123");
1050
- expect(profile.validationErrors).toEqual([]);
1051
- });
1052
-
1053
- test("falls back for elevenlabs_agent with empty agentId and fallback enabled", () => {
1054
- const config = AssistantConfigSchema.parse({
1055
- calls: {
1056
- voice: {
1057
- mode: "elevenlabs_agent",
1058
- fallbackToStandardOnError: true,
1059
- elevenlabs: { agentId: "" },
1060
- },
1061
- },
1062
- });
932
+ test("defaults to Rachel voice ID when elevenlabs.voiceId is not set", () => {
933
+ const config = AssistantConfigSchema.parse({});
1063
934
  const profile = resolveVoiceQualityProfile(config);
1064
- expect(profile.mode).toBe("twilio_standard");
1065
- expect(profile.validationErrors.length).toBe(1);
1066
- expect(profile.validationErrors[0]).toContain("agentId is empty");
935
+ expect(profile.voice).toBe("21m00Tcm4TlvDq8ikWAM");
1067
936
  });
1068
937
 
1069
- test("returns errors for elevenlabs_agent with empty agentId and fallback disabled", () => {
938
+ test("applies voice tuning params from elevenlabs config", () => {
1070
939
  const config = AssistantConfigSchema.parse({
1071
- calls: {
1072
- voice: {
1073
- mode: "elevenlabs_agent",
1074
- fallbackToStandardOnError: false,
1075
- elevenlabs: { agentId: "" },
1076
- },
940
+ elevenlabs: {
941
+ voiceId: "abc123",
942
+ voiceModelId: "turbo_v2_5",
943
+ speed: 0.9,
944
+ stability: 0.8,
945
+ similarityBoost: 0.9,
1077
946
  },
1078
947
  });
1079
948
  const profile = resolveVoiceQualityProfile(config);
1080
- expect(profile.mode).toBe("elevenlabs_agent");
1081
- expect(profile.validationErrors.length).toBe(1);
1082
- expect(profile.validationErrors[0]).toContain("agentId is required");
949
+ expect(profile.voice).toBe("abc123-turbo_v2_5-0.9_0.8_0.9");
1083
950
  });
1084
951
  });
1085
952
 
@@ -1123,14 +990,9 @@ describe("buildElevenLabsVoiceSpec", () => {
1123
990
 
1124
991
  test("default config uses a bare voiceId when no model override is set", () => {
1125
992
  const config = AssistantConfigSchema.parse({
1126
- calls: {
1127
- voice: {
1128
- mode: "twilio_elevenlabs_tts",
1129
- elevenlabs: { voiceId: "test" },
1130
- },
1131
- },
993
+ elevenlabs: { voiceId: "test" },
1132
994
  });
1133
- const spec = buildElevenLabsVoiceSpec(config.calls.voice.elevenlabs);
995
+ const spec = buildElevenLabsVoiceSpec(config.elevenlabs);
1134
996
  expect(spec).toBe("test");
1135
997
  });
1136
998
  });
@@ -1375,10 +1237,8 @@ describe("loadConfig with schema validation", () => {
1375
1237
  expect(config.calls.userConsultTimeoutSeconds).toBe(120);
1376
1238
  expect(config.calls.disclosure.enabled).toBe(true);
1377
1239
  expect(config.calls.safety.denyCategories).toEqual([]);
1378
- expect(config.calls.voice.mode).toBe("twilio_standard");
1379
1240
  expect(config.calls.voice.language).toBe("en-US");
1380
1241
  expect(config.calls.voice.transcriptionProvider).toBe("Deepgram");
1381
- expect(config.calls.voice.elevenlabs.voiceId).toBe("");
1382
1242
  expect(config.calls.model).toBeUndefined();
1383
1243
  expect(config.calls.callerIdentity).toEqual({
1384
1244
  allowPerCallOverride: true,
@@ -84,7 +84,10 @@ mock.module("../runtime/guardian-context-resolver.js", () => ({
84
84
  trustClass: "guardian",
85
85
  sourceChannel: "vellum",
86
86
  }),
87
- toGuardianRuntimeContext: (ctx: unknown) => ctx,
87
+ toGuardianRuntimeContext: (sourceChannel: unknown, ctx: unknown) => ({
88
+ ...(ctx as Record<string, unknown>),
89
+ sourceChannel,
90
+ }),
88
91
  }));
89
92
 
90
93
  import type { AuthContext } from "../runtime/auth/types.js";
@@ -226,7 +226,6 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
226
226
  "tools/network/script-proxy/session-manager.ts", // proxy credential injection at runtime
227
227
  "messaging/registry.ts", // checks stored credentials for connected providers
228
228
  "calls/call-domain.ts", // caller identity resolution (user phone number lookup)
229
- "calls/elevenlabs-config.ts", // ElevenLabs voice quality API key lookup
230
229
  "calls/twilio-config.ts", // call infrastructure credential lookup
231
230
  "calls/twilio-provider.ts", // call infrastructure credential lookup
232
231
  "calls/twilio-rest.ts", // Twilio REST API credential lookup
@@ -234,7 +233,6 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
234
233
  "runtime/http-server.ts", // HTTP server credential lookup
235
234
  "daemon/handlers/twitter-auth.ts", // Twitter OAuth token storage
236
235
  "twitter/oauth-client.ts", // Twitter OAuth API client (reads access token for API calls)
237
- "calls/elevenlabs-config.ts", // ElevenLabs credential lookup
238
236
  "cli/config-commands.ts", // CLI config management
239
237
  "messaging/providers/telegram-bot/adapter.ts", // Telegram bot token lookup for connectivity check
240
238
  "messaging/providers/sms/adapter.ts", // Twilio credential lookup for SMS connectivity check
@@ -119,13 +119,13 @@ describe("guardian-verify-setup skill — voice auto-followup", () => {
119
119
  expect(pollingSection).toContain("Non-rebind flows");
120
120
  });
121
121
 
122
- test("polling is voice-only — does not apply to SMS or Telegram", () => {
122
+ test("polling is voice-only — does not apply to Telegram", () => {
123
123
  const pollingSection =
124
124
  skillContent
125
125
  .split("## Voice Auto-Check Polling")[1]
126
126
  ?.split("## Step 6")[0] ?? "";
127
127
  expect(pollingSection).toContain("voice-only");
128
- expect(pollingSection).toContain("Do NOT poll for SMS or Telegram");
128
+ expect(pollingSection).toContain("Do NOT poll for Telegram");
129
129
  });
130
130
 
131
131
  test('no instruction requires waiting for user to ask "did it work?"', () => {
@@ -476,11 +476,6 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
476
476
  service: "gmail",
477
477
  requestedScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
478
478
  },
479
- browser_cdp_response: {
480
- type: "browser_cdp_response",
481
- sessionId: "test-session",
482
- success: true,
483
- },
484
479
  work_items_list: {
485
480
  type: "work_items_list",
486
481
  status: "queued",
@@ -1592,10 +1587,6 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
1592
1587
  grantedScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
1593
1588
  accountInfo: "user@example.com",
1594
1589
  },
1595
- browser_cdp_request: {
1596
- type: "browser_cdp_request",
1597
- sessionId: "test-session",
1598
- },
1599
1590
  document_editor_show: {
1600
1591
  type: "document_editor_show",
1601
1592
  sessionId: "sess-001",
@@ -14,11 +14,11 @@ describe("onboarding template contracts", () => {
14
14
  expect(lower).toContain("who am i");
15
15
  });
16
16
 
17
- test("infers personality indirectly instead of asking directly", () => {
17
+ test("infers personality organically instead of asking directly", () => {
18
18
  const lower = bootstrap.toLowerCase();
19
- // Personality step must instruct indirect/organic discovery
19
+ // Personality step must instruct organic discovery via conversation
20
20
  expect(lower).toContain("personality");
21
- expect(lower).toContain("indirectly");
21
+ expect(lower).toContain("emerge");
22
22
  expect(lower).toContain("vibe");
23
23
  });
24
24
 
@@ -41,15 +41,15 @@ describe("onboarding template contracts", () => {
41
41
  const lower = bootstrap.toLowerCase();
42
42
  // The template must prompt the assistant to ask about names.
43
43
  expect(lower).toContain("name");
44
- // The first step should be about locking in the assistant's name
45
- expect(lower).toContain("lock in your name");
44
+ // The first step should be about the assistant's name
45
+ expect(lower).toContain("your name");
46
46
  // The conversation sequence must include identity/naming
47
47
  expect(lower).toContain("who am i");
48
48
  });
49
49
 
50
50
  test("asks user name AFTER assistant identity is established", () => {
51
- // Step 1 is locking in the assistant's name, step 3 is asking the user's name
52
- const assistantNameIdx = bootstrap.indexOf("Lock in your name.");
51
+ // Step 1 is the assistant's name, step 4 is asking the user's name
52
+ const assistantNameIdx = bootstrap.indexOf("Your name:");
53
53
  const userNameIdx = bootstrap.indexOf("who am I talking to?");
54
54
  expect(assistantNameIdx).toBeGreaterThan(-1);
55
55
  expect(userNameIdx).toBeGreaterThan(-1);
@@ -87,10 +87,8 @@ describe("onboarding template contracts", () => {
87
87
  expect(lower).toContain("home base");
88
88
  });
89
89
 
90
- test("contains privacy/refusal policy", () => {
90
+ test("contains refusal policy", () => {
91
91
  const lower = bootstrap.toLowerCase();
92
- // Must have a privacy section
93
- expect(lower).toContain("privacy");
94
92
  // Assistant name is hard-required, user details are best-effort
95
93
  expect(lower).toContain("hard-required");
96
94
  expect(lower).toContain("best-effort");
@@ -107,16 +105,8 @@ describe("onboarding template contracts", () => {
107
105
  expect(lower).toContain("declined");
108
106
  });
109
107
 
110
- test("preserves no em dashes instruction", () => {
111
- const lower = bootstrap.toLowerCase();
112
- expect(lower).toContain("em dashes");
113
- });
114
-
115
- test("preserves no technical jargon instruction", () => {
116
- const lower = bootstrap.toLowerCase();
117
- expect(lower).toContain("technical jargon");
118
- expect(lower).toContain("system internals");
119
- });
108
+ // em-dash and technical jargon instructions are now hardcoded in the system
109
+ // prompt builder (buildSystemPrompt) rather than in the BOOTSTRAP.md template.
120
110
 
121
111
  test("preserves comment line format instruction", () => {
122
112
  // The template must start with the comment format explanation
@@ -2276,7 +2276,7 @@ describe("relay-server", () => {
2276
2276
  .map((raw) => JSON.parse(raw) as { type: string; token?: string })
2277
2277
  .filter((m) => m.type === "text");
2278
2278
  const promptText = textMessages.map((m) => m.token ?? "").join("");
2279
- expect(promptText).toContain("Hi, this is my guardian's assistant.");
2279
+ expect(promptText).toContain("Hi, this is my human's assistant.");
2280
2280
  expect(promptText).not.toContain("Vellum");
2281
2281
  expect(promptText).toContain("don't recognize this number");
2282
2282
  expect(promptText).toContain("Can I get your name");
@@ -2326,13 +2326,13 @@ describe("relay-server", () => {
2326
2326
  // Should have transitioned to awaiting guardian decision
2327
2327
  expect(relay.getConnectionState()).toBe("awaiting_guardian_decision");
2328
2328
 
2329
- // Should have sent the hold message (guardian label defaults to "my guardian")
2329
+ // Should have sent the hold message (guardian label defaults to "my human")
2330
2330
  const textMessages = ws.sentMessages
2331
2331
  .map((raw) => JSON.parse(raw) as { type: string; token?: string })
2332
2332
  .filter((m) => m.type === "text");
2333
2333
  expect(
2334
2334
  textMessages.some((m) =>
2335
- (m.token ?? "").includes("I've let my guardian know"),
2335
+ (m.token ?? "").includes("I've let my human know"),
2336
2336
  ),
2337
2337
  ).toBe(true);
2338
2338
  expect(