@vellumai/assistant 0.4.22 → 0.4.25

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 (72) hide show
  1. package/bun.lock +3 -0
  2. package/package.json +2 -1
  3. package/scripts/ipc/check-swift-decoder-drift.ts +55 -44
  4. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -90
  5. package/src/__tests__/assistant-events-sse-hardening.test.ts +9 -3
  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__/headless-browser-interactions.test.ts +0 -4
  11. package/src/__tests__/ipc-snapshot.test.ts +0 -63
  12. package/src/__tests__/onboarding-template-contract.test.ts +10 -20
  13. package/src/__tests__/relay-server.test.ts +3 -3
  14. package/src/__tests__/resolve-guardian-trust-class.test.ts +61 -0
  15. package/src/__tests__/runtime-events-sse-parity.test.ts +10 -0
  16. package/src/__tests__/runtime-events-sse.test.ts +7 -0
  17. package/src/__tests__/session-init.benchmark.test.ts +0 -4
  18. package/src/__tests__/session-runtime-assembly.test.ts +34 -8
  19. package/src/__tests__/system-prompt.test.ts +7 -1
  20. package/src/__tests__/trusted-contact-approval-notifier.test.ts +12 -8
  21. package/src/__tests__/twilio-routes-twiml.test.ts +2 -2
  22. package/src/__tests__/twilio-routes.test.ts +2 -3
  23. package/src/__tests__/voice-quality.test.ts +21 -132
  24. package/src/calls/relay-server.ts +11 -5
  25. package/src/calls/twilio-routes.ts +4 -38
  26. package/src/calls/voice-quality.ts +7 -63
  27. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +7 -10
  28. package/src/config/bundled-skills/messaging/SKILL.md +3 -5
  29. package/src/config/bundled-skills/phone-calls/SKILL.md +143 -82
  30. package/src/config/bundled-skills/sms-setup/SKILL.md +0 -20
  31. package/src/config/bundled-skills/twilio-setup/SKILL.md +9 -17
  32. package/src/config/bundled-skills/voice-setup/SKILL.md +36 -1
  33. package/src/config/bundled-skills/voice-setup/icon.svg +20 -0
  34. package/src/config/calls-schema.ts +3 -53
  35. package/src/config/elevenlabs-schema.ts +33 -0
  36. package/src/config/schema.ts +183 -137
  37. package/src/config/types.ts +0 -1
  38. package/src/daemon/daemon-control.ts +3 -0
  39. package/src/daemon/handlers/browser.ts +2 -53
  40. package/src/daemon/ipc-contract/browser.ts +5 -84
  41. package/src/daemon/ipc-contract/surfaces.ts +51 -48
  42. package/src/daemon/ipc-contract-inventory.json +0 -9
  43. package/src/daemon/session-agent-loop-handlers.ts +3 -0
  44. package/src/daemon/session-agent-loop.ts +2 -1
  45. package/src/daemon/session-runtime-assembly.ts +9 -7
  46. package/src/daemon/session-tool-setup.ts +27 -13
  47. package/src/mcp/client.ts +2 -1
  48. package/src/memory/conversation-crud.ts +339 -166
  49. package/src/memory/migrations/102-alter-table-columns.ts +254 -37
  50. package/src/memory/schema.ts +1227 -1035
  51. package/src/runtime/routes/events-routes.ts +7 -0
  52. package/src/runtime/routes/inbound-message-handler.ts +3 -4
  53. package/src/schedule/scheduler.ts +159 -45
  54. package/src/security/secure-keys.ts +3 -3
  55. package/src/tools/browser/browser-execution.ts +314 -331
  56. package/src/tools/browser/browser-handoff.ts +11 -37
  57. package/src/tools/browser/browser-manager.ts +203 -352
  58. package/src/tools/browser/browser-screencast.ts +15 -76
  59. package/src/tools/network/script-proxy/certs.ts +7 -237
  60. package/src/tools/network/script-proxy/connect-tunnel.ts +1 -82
  61. package/src/tools/network/script-proxy/http-forwarder.ts +2 -151
  62. package/src/tools/network/script-proxy/logging.ts +12 -196
  63. package/src/tools/network/script-proxy/mitm-handler.ts +2 -270
  64. package/src/tools/network/script-proxy/policy.ts +4 -152
  65. package/src/tools/network/script-proxy/router.ts +2 -60
  66. package/src/tools/network/script-proxy/server.ts +5 -137
  67. package/src/tools/network/script-proxy/types.ts +19 -125
  68. package/src/tools/system/voice-config.ts +23 -1
  69. package/src/util/logger.ts +4 -1
  70. package/src/__tests__/elevenlabs-config.test.ts +0 -95
  71. package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -407
  72. package/src/calls/elevenlabs-config.ts +0 -32
@@ -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?"', () => {
@@ -64,10 +64,6 @@ mock.module("../tools/browser/browser-screencast.js", () => ({
64
64
  stopBrowserScreencast: async () => {},
65
65
  stopAllScreencasts: async () => {},
66
66
  ensureScreencast: async () => {},
67
- updateBrowserStatus: () => {},
68
- updatePagesList: async () => {},
69
- getElementBounds: async () => null,
70
- updateHighlights: () => {},
71
67
  }));
72
68
 
73
69
  import {
@@ -476,39 +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
- browser_user_click: {
485
- type: "browser_user_click",
486
- sessionId: "test-session",
487
- surfaceId: "test-surface",
488
- x: 100,
489
- y: 200,
490
- },
491
- browser_user_scroll: {
492
- type: "browser_user_scroll",
493
- sessionId: "test-session",
494
- surfaceId: "test-surface",
495
- deltaX: 0,
496
- deltaY: -100,
497
- x: 100,
498
- y: 200,
499
- },
500
- browser_user_keypress: {
501
- type: "browser_user_keypress",
502
- sessionId: "test-session",
503
- surfaceId: "test-surface",
504
- key: "Enter",
505
- },
506
- browser_interactive_mode: {
507
- type: "browser_interactive_mode",
508
- sessionId: "test-session",
509
- surfaceId: "test-surface",
510
- enabled: true,
511
- },
512
479
  work_items_list: {
513
480
  type: "work_items_list",
514
481
  status: "queued",
@@ -1590,19 +1557,6 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
1590
1557
  type: "app_files_changed",
1591
1558
  appId: "app-001",
1592
1559
  },
1593
- browser_frame: {
1594
- type: "browser_frame",
1595
- sessionId: "sess-001",
1596
- surfaceId: "surface-001",
1597
- frame: "base64-jpeg-data",
1598
- metadata: {
1599
- offsetTop: 0,
1600
- pageScaleFactor: 1,
1601
- scrollOffsetX: 0,
1602
- scrollOffsetY: 0,
1603
- timestamp: 1700000000,
1604
- },
1605
- },
1606
1560
  diagnostics_export_response: {
1607
1561
  type: "diagnostics_export_response",
1608
1562
  success: true,
@@ -1633,23 +1587,6 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
1633
1587
  grantedScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
1634
1588
  accountInfo: "user@example.com",
1635
1589
  },
1636
- browser_cdp_request: {
1637
- type: "browser_cdp_request",
1638
- sessionId: "test-session",
1639
- },
1640
- browser_interactive_mode_changed: {
1641
- type: "browser_interactive_mode_changed",
1642
- sessionId: "test-session",
1643
- surfaceId: "test-surface",
1644
- enabled: true,
1645
- },
1646
- browser_handoff_request: {
1647
- type: "browser_handoff_request",
1648
- sessionId: "test-session",
1649
- surfaceId: "test-surface",
1650
- reason: "auth" as const,
1651
- message: "Login required",
1652
- },
1653
1590
  document_editor_show: {
1654
1591
  type: "document_editor_show",
1655
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(
@@ -0,0 +1,61 @@
1
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { GuardianRuntimeContext } from "../daemon/session-runtime-assembly.js";
4
+
5
+ // ── Module mocks ─────────────────────────────────────────────────────
6
+
7
+ let fakeHttpAuthDisabled = false;
8
+
9
+ mock.module("../config/env.js", () => ({
10
+ isHttpAuthDisabled: () => fakeHttpAuthDisabled,
11
+ hasUngatedHttpAuthDisabled: () => false,
12
+ }));
13
+
14
+ // ── Real imports (after mocks) ───────────────────────────────────────
15
+
16
+ import { resolveGuardianTrustClass } from "../daemon/session-tool-setup.js";
17
+
18
+ afterAll(() => {
19
+ mock.restore();
20
+ });
21
+
22
+ // ── Tests ────────────────────────────────────────────────────────────
23
+
24
+ describe("resolveGuardianTrustClass", () => {
25
+ beforeEach(() => {
26
+ fakeHttpAuthDisabled = false;
27
+ });
28
+
29
+ test("returns guardian context trust class when auth is enabled", () => {
30
+ const ctx: Pick<GuardianRuntimeContext, "trustClass"> = {
31
+ trustClass: "trusted_contact",
32
+ };
33
+ expect(resolveGuardianTrustClass(ctx as GuardianRuntimeContext)).toBe(
34
+ "trusted_contact",
35
+ );
36
+ });
37
+
38
+ test("defaults to guardian when no guardian context and auth is enabled", () => {
39
+ expect(resolveGuardianTrustClass(undefined)).toBe("guardian");
40
+ });
41
+
42
+ test("forces guardian when HTTP auth is disabled, regardless of context trust class", () => {
43
+ fakeHttpAuthDisabled = true;
44
+ const ctx: Pick<GuardianRuntimeContext, "trustClass"> = {
45
+ trustClass: "trusted_contact",
46
+ };
47
+ expect(resolveGuardianTrustClass(ctx as GuardianRuntimeContext)).toBe(
48
+ "guardian",
49
+ );
50
+ });
51
+
52
+ test("forces guardian for unknown trust class when HTTP auth is disabled", () => {
53
+ fakeHttpAuthDisabled = true;
54
+ const ctx: Pick<GuardianRuntimeContext, "trustClass"> = {
55
+ trustClass: "unknown",
56
+ };
57
+ expect(resolveGuardianTrustClass(ctx as GuardianRuntimeContext)).toBe(
58
+ "guardian",
59
+ );
60
+ });
61
+ });
@@ -106,6 +106,11 @@ async function publishAndReadFrame(
106
106
  await assistantEventHub.publish(event);
107
107
 
108
108
  const reader = response.body!.getReader();
109
+
110
+ // The first chunk is the immediate heartbeat comment enqueued in start().
111
+ await reader.read();
112
+
113
+ // The second chunk is the actual assistant event.
109
114
  const { value } = await reader.read();
110
115
  ac.abort();
111
116
 
@@ -366,6 +371,11 @@ describe("SSE IPC parity — streaming/delta message types", () => {
366
371
  await assistantEventHub.publish(published);
367
372
 
368
373
  const reader = response.body!.getReader();
374
+
375
+ // The first chunk is the immediate heartbeat comment enqueued in start().
376
+ await reader.read();
377
+
378
+ // The second chunk is the actual assistant event.
369
379
  const { value } = await reader.read();
370
380
  ac.abort();
371
381
 
@@ -168,6 +168,13 @@ describe("SSE assistant-events endpoint", () => {
168
168
 
169
169
  // Read the first frame directly from the response body stream.
170
170
  const reader = response.body!.getReader();
171
+
172
+ // The first chunk is the immediate heartbeat comment enqueued in start().
173
+ const initial = await reader.read();
174
+ expect(initial.done).toBe(false);
175
+ expect(new TextDecoder().decode(initial.value)).toBe(": heartbeat\n\n");
176
+
177
+ // The second chunk is the actual assistant event.
171
178
  const { value, done } = await reader.read();
172
179
  ac.abort();
173
180
 
@@ -277,11 +277,7 @@ mock.module("../tools/browser/browser-screencast.js", () => ({
277
277
  registerSessionSender: () => {},
278
278
  unregisterSessionSender: () => {},
279
279
  ensureScreencast: () => Promise.resolve(),
280
- updateBrowserStatus: () => {},
281
- updatePagesList: () => Promise.resolve(),
282
280
  stopBrowserScreencast: () => Promise.resolve(),
283
- getElementBounds: () => Promise.resolve(null),
284
- updateHighlights: () => {},
285
281
  stopAllScreencasts: () => Promise.resolve(),
286
282
  isScreencastActive: () => false,
287
283
  getSender: () => undefined,
@@ -44,6 +44,14 @@ describe("resolveChannelCapabilities", () => {
44
44
  expect(caps.supportsVoiceInput).toBe(true);
45
45
  });
46
46
 
47
+ test("vellum channel with vellum interface supports dynamic UI", () => {
48
+ const caps = resolveChannelCapabilities("vellum", "vellum");
49
+ expect(caps.channel).toBe("vellum");
50
+ expect(caps.dashboardCapable).toBe(false);
51
+ expect(caps.supportsDynamicUi).toBe(true);
52
+ expect(caps.supportsVoiceInput).toBe(false);
53
+ });
54
+
47
55
  test("defaults to vellum for null source channel", () => {
48
56
  const caps = resolveChannelCapabilities(null);
49
57
  expect(caps.channel).toBe("vellum");
@@ -407,6 +415,24 @@ describe("trust-gating via channel capabilities", () => {
407
415
  expect(injected).toContain("Present information as well-formatted text");
408
416
  expect(injected).toContain("desktop app");
409
417
  });
418
+
419
+ test("vellum web interface allows dynamic UI but constrains dashboard references", () => {
420
+ const caps = resolveChannelCapabilities("vellum", "vellum");
421
+ const message: Message = {
422
+ role: "user",
423
+ content: [{ type: "text", text: "Show me a form" }],
424
+ };
425
+
426
+ const result = injectChannelCapabilityContext(message, caps);
427
+ const injected = (result.content[0] as { type: "text"; text: string }).text;
428
+
429
+ expect(injected).toContain("CHANNEL CONSTRAINTS");
430
+ expect(injected).toContain("Do NOT reference the dashboard UI");
431
+ expect(injected).not.toContain("Do NOT use ui_show");
432
+ expect(injected).not.toContain("Present information as well-formatted text");
433
+ expect(injected).toContain("supports_dynamic_ui: true");
434
+ expect(injected).toContain("dashboard_capable: false");
435
+ });
410
436
  });
411
437
 
412
438
  // ---------------------------------------------------------------------------
@@ -994,12 +1020,12 @@ describe("sanitizePttActivationKey", () => {
994
1020
  expect(sanitizePttActivationKey("none")).toBe("none");
995
1021
  });
996
1022
 
997
- test('returns "unknown" for invalid keys', () => {
998
- expect(sanitizePttActivationKey("malicious\nprompt injection")).toBe(
999
- "unknown",
1000
- );
1001
- expect(sanitizePttActivationKey("arbitrary_value")).toBe("unknown");
1002
- expect(sanitizePttActivationKey("")).toBe("unknown");
1023
+ test("returns undefined for invalid keys", () => {
1024
+ expect(
1025
+ sanitizePttActivationKey("malicious\nprompt injection"),
1026
+ ).toBeUndefined();
1027
+ expect(sanitizePttActivationKey("arbitrary_value")).toBeUndefined();
1028
+ expect(sanitizePttActivationKey("")).toBeUndefined();
1003
1029
  });
1004
1030
  });
1005
1031
 
@@ -1015,11 +1041,11 @@ describe("resolveChannelCapabilities with PTT metadata", () => {
1015
1041
  expect(caps.pttActivationKey).toBe("fn");
1016
1042
  });
1017
1043
 
1018
- test("sanitizes invalid pttActivationKey to unknown", () => {
1044
+ test("sanitizes invalid pttActivationKey to undefined", () => {
1019
1045
  const caps = resolveChannelCapabilities("macos", "macos", {
1020
1046
  pttActivationKey: "evil\nprompt",
1021
1047
  });
1022
- expect(caps.pttActivationKey).toBe("unknown");
1048
+ expect(caps.pttActivationKey).toBeUndefined();
1023
1049
  });
1024
1050
 
1025
1051
  test("passes through microphonePermissionGranted", () => {
@@ -75,9 +75,15 @@ const {
75
75
  buildPhoneCallsRoutingSection,
76
76
  } = await import("../config/system-prompt.js");
77
77
 
78
- /** Strip the Configuration and Skills sections so base-prompt tests stay focused. */
78
+ /** Strip the Configuration, Skills, and hardcoded preamble sections so base-prompt tests stay focused. */
79
79
  function basePrompt(result: string): string {
80
80
  let s = result;
81
+ // Strip the hardcoded em-dash instruction preamble
82
+ const emDashLine =
83
+ "IMPORTANT: Never use em dashes (\u2014) in your messages. Use commas, periods, or just start a new sentence instead.";
84
+ if (s.startsWith(emDashLine)) {
85
+ s = s.slice(emDashLine.length).replace(/^\n\n/, "");
86
+ }
81
87
  for (const heading of [
82
88
  "## Configuration",
83
89
  "## Skills Catalog",
@@ -120,8 +120,14 @@ mock.module("../config/env.js", () => ({
120
120
  getGatewayInternalBaseUrl: () => "http://localhost:3000",
121
121
  }));
122
122
 
123
+ // ── User reference mock ──
124
+ mock.module("../config/user-reference.js", () => ({
125
+ resolveUserReference: () => "my human",
126
+ }));
127
+
123
128
  // Import module under test AFTER mocks are set up
124
129
  import type { ChannelId } from "../channels/types.js";
130
+ import { resolveUserReference } from "../config/user-reference.js";
125
131
  import type { GuardianContext } from "../runtime/guardian-context-resolver.js";
126
132
 
127
133
  // We need to test the private functions by importing the module.
@@ -220,9 +226,7 @@ async function simulateNotifierPoll(params: {
220
226
  }
221
227
  }
222
228
 
223
- const waitingText = guardianName
224
- ? `Waiting for ${guardianName}'s approval...`
225
- : "Waiting for your guardian's approval...";
229
+ const waitingText = `Waiting for ${guardianName ?? resolveUserReference()}'s approval...`;
226
230
 
227
231
  try {
228
232
  await deliverChannelReply(
@@ -330,7 +334,7 @@ describe("trusted-contact pending-approval notifier", () => {
330
334
  );
331
335
  });
332
336
 
333
- test("uses generic phrasing when no guardian name is available", async () => {
337
+ test("falls back to user reference when no guardian name is available", async () => {
334
338
  mockPendingApprovals = [
335
339
  {
336
340
  requestId: "req-3",
@@ -359,11 +363,11 @@ describe("trusted-contact pending-approval notifier", () => {
359
363
 
360
364
  expect(deliveredReplies).toHaveLength(1);
361
365
  expect(deliveredReplies[0].payload.text).toBe(
362
- "Waiting for your guardian's approval...",
366
+ "Waiting for my human's approval...",
363
367
  );
364
368
  });
365
369
 
366
- test("uses generic phrasing when no guardian binding exists", async () => {
370
+ test("falls back to user reference when no guardian binding exists", async () => {
367
371
  mockPendingApprovals = [
368
372
  {
369
373
  requestId: "req-4",
@@ -388,7 +392,7 @@ describe("trusted-contact pending-approval notifier", () => {
388
392
 
389
393
  expect(deliveredReplies).toHaveLength(1);
390
394
  expect(deliveredReplies[0].payload.text).toBe(
391
- "Waiting for your guardian's approval...",
395
+ "Waiting for my human's approval...",
392
396
  );
393
397
  });
394
398
 
@@ -736,7 +740,7 @@ describe("trusted-contact pending-approval notifier", () => {
736
740
  expect(deliveredReplies).toHaveLength(1);
737
741
  // Falls back to generic phrasing
738
742
  expect(deliveredReplies[0].payload.text).toBe(
739
- "Waiting for your guardian's approval...",
743
+ "Waiting for my human's approval...",
740
744
  );
741
745
  });
742
746
  });
@@ -46,7 +46,7 @@ describe("generateTwiML with voice quality profile", () => {
46
46
  expect(twiml).toContain('voice="voice123-turbo_v2_5-1_0.5_0.75"');
47
47
  });
48
48
 
49
- test("voice attribute reflects configured voice for twilio_standard mode", () => {
49
+ test("voice attribute reflects configured Google voice", () => {
50
50
  const twiml = generateTwiML(callSessionId, relayUrl, welcomeGreeting, {
51
51
  language: "en-US",
52
52
  transcriptionProvider: "Deepgram",
@@ -57,7 +57,7 @@ describe("generateTwiML with voice quality profile", () => {
57
57
  expect(twiml).toContain('voice="Google.en-US-Journey-O"');
58
58
  });
59
59
 
60
- test("voice attribute reflects configured voice for twilio_elevenlabs_tts mode", () => {
60
+ test("voice attribute reflects configured ElevenLabs voice", () => {
61
61
  const twiml = generateTwiML(callSessionId, relayUrl, welcomeGreeting, {
62
62
  language: "en-US",
63
63
  transcriptionProvider: "Deepgram",
@@ -60,13 +60,12 @@ const mockConfigObj = {
60
60
  memory: { enabled: false },
61
61
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
62
62
  secretDetection: { enabled: false },
63
+ elevenlabs: { voiceId: "21m00Tcm4TlvDq8ikWAM" },
63
64
  calls: {
64
65
  voice: {
65
- mode: "twilio_standard",
66
66
  language: "en-US",
67
67
  transcriptionProvider: "Deepgram",
68
- fallbackToStandardOnError: true,
69
- elevenlabs: { voiceId: "" },
68
+ elevenlabs: {},
70
69
  },
71
70
  },
72
71
  };