@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.
- package/bun.lock +3 -0
- package/package.json +2 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +55 -44
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -90
- package/src/__tests__/assistant-events-sse-hardening.test.ts +9 -3
- package/src/__tests__/config-schema.test.ts +38 -178
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +4 -1
- package/src/__tests__/credential-security-invariants.test.ts +0 -2
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +2 -2
- package/src/__tests__/headless-browser-interactions.test.ts +0 -4
- package/src/__tests__/ipc-snapshot.test.ts +0 -63
- package/src/__tests__/onboarding-template-contract.test.ts +10 -20
- package/src/__tests__/relay-server.test.ts +3 -3
- package/src/__tests__/resolve-guardian-trust-class.test.ts +61 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +10 -0
- package/src/__tests__/runtime-events-sse.test.ts +7 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -4
- package/src/__tests__/session-runtime-assembly.test.ts +34 -8
- package/src/__tests__/system-prompt.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +12 -8
- package/src/__tests__/twilio-routes-twiml.test.ts +2 -2
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/voice-quality.test.ts +21 -132
- package/src/calls/relay-server.ts +11 -5
- package/src/calls/twilio-routes.ts +4 -38
- package/src/calls/voice-quality.ts +7 -63
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +7 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +143 -82
- package/src/config/bundled-skills/sms-setup/SKILL.md +0 -20
- package/src/config/bundled-skills/twilio-setup/SKILL.md +9 -17
- package/src/config/bundled-skills/voice-setup/SKILL.md +36 -1
- package/src/config/bundled-skills/voice-setup/icon.svg +20 -0
- package/src/config/calls-schema.ts +3 -53
- package/src/config/elevenlabs-schema.ts +33 -0
- package/src/config/schema.ts +183 -137
- package/src/config/types.ts +0 -1
- package/src/daemon/daemon-control.ts +3 -0
- package/src/daemon/handlers/browser.ts +2 -53
- package/src/daemon/ipc-contract/browser.ts +5 -84
- package/src/daemon/ipc-contract/surfaces.ts +51 -48
- package/src/daemon/ipc-contract-inventory.json +0 -9
- package/src/daemon/session-agent-loop-handlers.ts +3 -0
- package/src/daemon/session-agent-loop.ts +2 -1
- package/src/daemon/session-runtime-assembly.ts +9 -7
- package/src/daemon/session-tool-setup.ts +27 -13
- package/src/mcp/client.ts +2 -1
- package/src/memory/conversation-crud.ts +339 -166
- package/src/memory/migrations/102-alter-table-columns.ts +254 -37
- package/src/memory/schema.ts +1227 -1035
- package/src/runtime/routes/events-routes.ts +7 -0
- package/src/runtime/routes/inbound-message-handler.ts +3 -4
- package/src/schedule/scheduler.ts +159 -45
- package/src/security/secure-keys.ts +3 -3
- package/src/tools/browser/browser-execution.ts +314 -331
- package/src/tools/browser/browser-handoff.ts +11 -37
- package/src/tools/browser/browser-manager.ts +203 -352
- package/src/tools/browser/browser-screencast.ts +15 -76
- package/src/tools/network/script-proxy/certs.ts +7 -237
- package/src/tools/network/script-proxy/connect-tunnel.ts +1 -82
- package/src/tools/network/script-proxy/http-forwarder.ts +2 -151
- package/src/tools/network/script-proxy/logging.ts +12 -196
- package/src/tools/network/script-proxy/mitm-handler.ts +2 -270
- package/src/tools/network/script-proxy/policy.ts +4 -152
- package/src/tools/network/script-proxy/router.ts +2 -60
- package/src/tools/network/script-proxy/server.ts +5 -137
- package/src/tools/network/script-proxy/types.ts +19 -125
- package/src/tools/system/voice-config.ts +23 -1
- package/src/util/logger.ts +4 -1
- package/src/__tests__/elevenlabs-config.test.ts +0 -95
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -407
- 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.
|
|
3
|
+
"version": "0.4.25",
|
|
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",
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
* bun run ipc:check-swift-drift # check for drift
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import * as fs from
|
|
16
|
-
import * as path from
|
|
15
|
+
import * as fs from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
17
|
|
|
18
|
-
import { extractInventory } from
|
|
18
|
+
import { extractInventory } from "../../src/daemon/ipc-contract-inventory.js";
|
|
19
19
|
|
|
20
|
-
const ROOT = path.resolve(import.meta.dirname ?? __dirname,
|
|
21
|
-
const CONTRACT_PATH = path.join(ROOT,
|
|
20
|
+
const ROOT = path.resolve(import.meta.dirname ?? __dirname, "../..");
|
|
21
|
+
const CONTRACT_PATH = path.join(ROOT, "src/daemon/ipc-contract.ts");
|
|
22
22
|
const SWIFT_PATH = path.resolve(
|
|
23
23
|
ROOT,
|
|
24
|
-
|
|
24
|
+
"../clients/shared/IPC/IPCMessages.swift",
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -31,37 +31,35 @@ const SWIFT_PATH = path.resolve(
|
|
|
31
31
|
*/
|
|
32
32
|
const SWIFT_OMIT_ALLOWLIST = new Set<string>([
|
|
33
33
|
// Server-internal events not surfaced to macOS client
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
"context_compacted",
|
|
35
|
+
"memory_recalled",
|
|
36
|
+
"model_info",
|
|
37
|
+
"secret_detected",
|
|
38
|
+
"sessions_clear_response",
|
|
39
|
+
"usage_response",
|
|
40
|
+
"usage_update",
|
|
41
41
|
// Gallery and cloud sharing — not yet consumed by the macOS client
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
"gallery_install_response",
|
|
43
|
+
"gallery_list_response",
|
|
44
|
+
"share_app_cloud_response",
|
|
45
45
|
// Page publishing — not yet consumed by the macOS client
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
"publish_page_response",
|
|
47
|
+
"unpublish_page_response",
|
|
48
48
|
// Heartbeat alerts — not yet consumed by the macOS client
|
|
49
|
-
|
|
50
|
-
// Browser handoff — not yet consumed by the macOS client
|
|
51
|
-
'browser_handoff_request',
|
|
49
|
+
"heartbeat_alert",
|
|
52
50
|
// Guardian verification — daemon-internal for Telegram channel setup
|
|
53
|
-
|
|
51
|
+
"guardian_verification_response",
|
|
54
52
|
// Ingress invite/member management — not yet consumed by the macOS client
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
"ingress_invite_response",
|
|
54
|
+
"ingress_member_response",
|
|
57
55
|
// Inbox escalation — not yet consumed by the macOS client
|
|
58
|
-
|
|
56
|
+
"assistant_inbox_escalation_response",
|
|
59
57
|
// Work item messages — not yet consumed by the macOS client
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
"work_item_get_response",
|
|
59
|
+
"work_item_run_task_response",
|
|
60
|
+
"work_item_status_changed",
|
|
61
|
+
"work_item_update_response",
|
|
62
|
+
"work_items_list_response",
|
|
65
63
|
]);
|
|
66
64
|
|
|
67
65
|
/**
|
|
@@ -72,7 +70,7 @@ const SWIFT_OMIT_ALLOWLIST = new Set<string>([
|
|
|
72
70
|
const INVENTORY_UNEXTRACTABLE = new Set<string>([
|
|
73
71
|
// UiSurfaceShow is a union of UiSurfaceShowCard | UiSurfaceShowForm | ...
|
|
74
72
|
// The shared wire type 'ui_surface_show' comes from UiSurfaceShowBase.
|
|
75
|
-
|
|
73
|
+
"ui_surface_show",
|
|
76
74
|
]);
|
|
77
75
|
|
|
78
76
|
/**
|
|
@@ -82,9 +80,9 @@ const INVENTORY_UNEXTRACTABLE = new Set<string>([
|
|
|
82
80
|
*/
|
|
83
81
|
const SWIFT_AHEAD_ALLOWLIST = new Set<string>([
|
|
84
82
|
// Defined in Swift LayoutConfig.swift ahead of daemon implementation
|
|
85
|
-
|
|
83
|
+
"ui_layout_config",
|
|
86
84
|
// Defined in Swift HTTPDaemonClient ahead of daemon token rotation endpoint
|
|
87
|
-
|
|
85
|
+
"token_rotated",
|
|
88
86
|
]);
|
|
89
87
|
|
|
90
88
|
// --- Extract Swift decode cases ---
|
|
@@ -97,9 +95,13 @@ function extractSwiftDecodeCases(swiftSource: string): Set<string> {
|
|
|
97
95
|
let match: RegExpExecArray | null;
|
|
98
96
|
|
|
99
97
|
// Only scan inside the ServerMessage init(from decoder:) block
|
|
100
|
-
const decoderStart = swiftSource.indexOf(
|
|
98
|
+
const decoderStart = swiftSource.indexOf(
|
|
99
|
+
"public init(from decoder: Decoder) throws",
|
|
100
|
+
);
|
|
101
101
|
if (decoderStart === -1) {
|
|
102
|
-
throw new Error(
|
|
102
|
+
throw new Error(
|
|
103
|
+
"Could not find ServerMessage decoder in IPCMessages.swift",
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
const decoderSection = swiftSource.slice(decoderStart);
|
|
@@ -120,7 +122,7 @@ const contractServerTypes = new Set([
|
|
|
120
122
|
...INVENTORY_UNEXTRACTABLE,
|
|
121
123
|
]);
|
|
122
124
|
|
|
123
|
-
const swiftSource = fs.readFileSync(SWIFT_PATH,
|
|
125
|
+
const swiftSource = fs.readFileSync(SWIFT_PATH, "utf-8");
|
|
124
126
|
const swiftDecodeCases = extractSwiftDecodeCases(swiftSource);
|
|
125
127
|
|
|
126
128
|
const diffs: string[] = [];
|
|
@@ -134,7 +136,10 @@ for (const wireType of contractServerTypes) {
|
|
|
134
136
|
|
|
135
137
|
// Types decoded in Swift but not in contract
|
|
136
138
|
for (const wireType of swiftDecodeCases) {
|
|
137
|
-
if (
|
|
139
|
+
if (
|
|
140
|
+
!contractServerTypes.has(wireType) &&
|
|
141
|
+
!SWIFT_AHEAD_ALLOWLIST.has(wireType)
|
|
142
|
+
) {
|
|
138
143
|
diffs.push(` - Swift decodes "${wireType}" but it is not in the contract`);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
@@ -142,30 +147,36 @@ for (const wireType of swiftDecodeCases) {
|
|
|
142
147
|
// Stale allowlist entries
|
|
143
148
|
for (const wireType of SWIFT_OMIT_ALLOWLIST) {
|
|
144
149
|
if (!contractServerTypes.has(wireType)) {
|
|
145
|
-
diffs.push(
|
|
150
|
+
diffs.push(
|
|
151
|
+
` ? Omit-allowlist entry "${wireType}" is not in the contract (stale?)`,
|
|
152
|
+
);
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
for (const wireType of INVENTORY_UNEXTRACTABLE) {
|
|
149
156
|
if (!swiftDecodeCases.has(wireType)) {
|
|
150
|
-
diffs.push(
|
|
157
|
+
diffs.push(
|
|
158
|
+
` ? Unextractable entry "${wireType}" is not decoded in Swift (stale?)`,
|
|
159
|
+
);
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
for (const wireType of SWIFT_AHEAD_ALLOWLIST) {
|
|
154
163
|
if (contractServerTypes.has(wireType)) {
|
|
155
|
-
diffs.push(
|
|
164
|
+
diffs.push(
|
|
165
|
+
` ? Ahead-allowlist entry "${wireType}" is now in the contract (remove from allowlist)`,
|
|
166
|
+
);
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
if (diffs.length > 0) {
|
|
160
|
-
console.error(
|
|
171
|
+
console.error("IPC Swift decoder drift detected:\n");
|
|
161
172
|
for (const line of diffs) {
|
|
162
173
|
console.error(line);
|
|
163
174
|
}
|
|
164
175
|
console.error(
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
"\nFix: update IPCMessages.swift decode cases, the contract, or the",
|
|
177
|
+
"allowlist in check-swift-decoder-drift.ts.",
|
|
167
178
|
);
|
|
168
179
|
process.exit(1);
|
|
169
180
|
}
|
|
170
181
|
|
|
171
|
-
console.log(
|
|
182
|
+
console.log("IPC Swift decoder is in sync with the contract.");
|
|
@@ -768,54 +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
|
-
exports[`IPC message snapshots ClientMessage types browser_user_click serializes to expected JSON 1`] = `
|
|
780
|
-
{
|
|
781
|
-
"sessionId": "test-session",
|
|
782
|
-
"surfaceId": "test-surface",
|
|
783
|
-
"type": "browser_user_click",
|
|
784
|
-
"x": 100,
|
|
785
|
-
"y": 200,
|
|
786
|
-
}
|
|
787
|
-
`;
|
|
788
|
-
|
|
789
|
-
exports[`IPC message snapshots ClientMessage types browser_user_scroll serializes to expected JSON 1`] = `
|
|
790
|
-
{
|
|
791
|
-
"deltaX": 0,
|
|
792
|
-
"deltaY": -100,
|
|
793
|
-
"sessionId": "test-session",
|
|
794
|
-
"surfaceId": "test-surface",
|
|
795
|
-
"type": "browser_user_scroll",
|
|
796
|
-
"x": 100,
|
|
797
|
-
"y": 200,
|
|
798
|
-
}
|
|
799
|
-
`;
|
|
800
|
-
|
|
801
|
-
exports[`IPC message snapshots ClientMessage types browser_user_keypress serializes to expected JSON 1`] = `
|
|
802
|
-
{
|
|
803
|
-
"key": "Enter",
|
|
804
|
-
"sessionId": "test-session",
|
|
805
|
-
"surfaceId": "test-surface",
|
|
806
|
-
"type": "browser_user_keypress",
|
|
807
|
-
}
|
|
808
|
-
`;
|
|
809
|
-
|
|
810
|
-
exports[`IPC message snapshots ClientMessage types browser_interactive_mode serializes to expected JSON 1`] = `
|
|
811
|
-
{
|
|
812
|
-
"enabled": true,
|
|
813
|
-
"sessionId": "test-session",
|
|
814
|
-
"surfaceId": "test-surface",
|
|
815
|
-
"type": "browser_interactive_mode",
|
|
816
|
-
}
|
|
817
|
-
`;
|
|
818
|
-
|
|
819
771
|
exports[`IPC message snapshots ClientMessage types work_items_list serializes to expected JSON 1`] = `
|
|
820
772
|
{
|
|
821
773
|
"status": "queued",
|
|
@@ -2437,22 +2389,6 @@ exports[`IPC message snapshots ServerMessage types app_files_changed serializes
|
|
|
2437
2389
|
}
|
|
2438
2390
|
`;
|
|
2439
2391
|
|
|
2440
|
-
exports[`IPC message snapshots ServerMessage types browser_frame serializes to expected JSON 1`] = `
|
|
2441
|
-
{
|
|
2442
|
-
"frame": "base64-jpeg-data",
|
|
2443
|
-
"metadata": {
|
|
2444
|
-
"offsetTop": 0,
|
|
2445
|
-
"pageScaleFactor": 1,
|
|
2446
|
-
"scrollOffsetX": 0,
|
|
2447
|
-
"scrollOffsetY": 0,
|
|
2448
|
-
"timestamp": 1700000000,
|
|
2449
|
-
},
|
|
2450
|
-
"sessionId": "sess-001",
|
|
2451
|
-
"surfaceId": "surface-001",
|
|
2452
|
-
"type": "browser_frame",
|
|
2453
|
-
}
|
|
2454
|
-
`;
|
|
2455
|
-
|
|
2456
2392
|
exports[`IPC message snapshots ServerMessage types diagnostics_export_response serializes to expected JSON 1`] = `
|
|
2457
2393
|
{
|
|
2458
2394
|
"filePath": "/tmp/diagnostics-conv-001.zip",
|
|
@@ -2511,32 +2447,6 @@ exports[`IPC message snapshots ServerMessage types oauth_connect_result serializ
|
|
|
2511
2447
|
}
|
|
2512
2448
|
`;
|
|
2513
2449
|
|
|
2514
|
-
exports[`IPC message snapshots ServerMessage types browser_cdp_request serializes to expected JSON 1`] = `
|
|
2515
|
-
{
|
|
2516
|
-
"sessionId": "test-session",
|
|
2517
|
-
"type": "browser_cdp_request",
|
|
2518
|
-
}
|
|
2519
|
-
`;
|
|
2520
|
-
|
|
2521
|
-
exports[`IPC message snapshots ServerMessage types browser_interactive_mode_changed serializes to expected JSON 1`] = `
|
|
2522
|
-
{
|
|
2523
|
-
"enabled": true,
|
|
2524
|
-
"sessionId": "test-session",
|
|
2525
|
-
"surfaceId": "test-surface",
|
|
2526
|
-
"type": "browser_interactive_mode_changed",
|
|
2527
|
-
}
|
|
2528
|
-
`;
|
|
2529
|
-
|
|
2530
|
-
exports[`IPC message snapshots ServerMessage types browser_handoff_request serializes to expected JSON 1`] = `
|
|
2531
|
-
{
|
|
2532
|
-
"message": "Login required",
|
|
2533
|
-
"reason": "auth",
|
|
2534
|
-
"sessionId": "test-session",
|
|
2535
|
-
"surfaceId": "test-surface",
|
|
2536
|
-
"type": "browser_handoff_request",
|
|
2537
|
-
}
|
|
2538
|
-
`;
|
|
2539
|
-
|
|
2540
2450
|
exports[`IPC message snapshots ServerMessage types document_editor_show serializes to expected JSON 1`] = `
|
|
2541
2451
|
{
|
|
2542
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
|
|
197
|
-
|
|
198
|
-
|
|
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
|
});
|
|
@@ -658,10 +658,10 @@ describe("AssistantConfigSchema", () => {
|
|
|
658
658
|
userConsultTimeoutSeconds: 120,
|
|
659
659
|
ttsPlaybackDelayMs: 3000,
|
|
660
660
|
accessRequestPollIntervalMs: 500,
|
|
661
|
-
guardianWaitUpdateInitialIntervalMs:
|
|
661
|
+
guardianWaitUpdateInitialIntervalMs: 15000,
|
|
662
662
|
guardianWaitUpdateInitialWindowMs: 30000,
|
|
663
|
-
guardianWaitUpdateSteadyMinIntervalMs:
|
|
664
|
-
guardianWaitUpdateSteadyMaxIntervalMs:
|
|
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("
|
|
810
|
-
const result = AssistantConfigSchema.parse({
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
expect(
|
|
814
|
-
|
|
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
|
|
791
|
+
test("rejects elevenlabs.speed below 0.7", () => {
|
|
820
792
|
const result = AssistantConfigSchema.safeParse({
|
|
821
|
-
|
|
793
|
+
elevenlabs: { speed: 0.5 },
|
|
822
794
|
});
|
|
823
795
|
expect(result.success).toBe(false);
|
|
824
796
|
});
|
|
825
797
|
|
|
826
|
-
test("rejects
|
|
798
|
+
test("rejects elevenlabs.speed above 1.2", () => {
|
|
827
799
|
const result = AssistantConfigSchema.safeParse({
|
|
828
|
-
|
|
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.
|
|
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.
|
|
856
|
-
expect(result.
|
|
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
|
|
838
|
+
test("rejects elevenlabs.stability out of range", () => {
|
|
884
839
|
const result = AssistantConfigSchema.safeParse({
|
|
885
|
-
|
|
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
|
|
916
|
+
test("always returns ElevenLabs ttsProvider", () => {
|
|
976
917
|
const config = AssistantConfigSchema.parse({});
|
|
977
918
|
const profile = resolveVoiceQualityProfile(config);
|
|
978
|
-
expect(profile.
|
|
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("
|
|
923
|
+
test("uses shared elevenlabs.voiceId for voice", () => {
|
|
987
924
|
const config = AssistantConfigSchema.parse({
|
|
988
|
-
|
|
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("
|
|
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.
|
|
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("
|
|
938
|
+
test("applies voice tuning params from elevenlabs config", () => {
|
|
1070
939
|
const config = AssistantConfigSchema.parse({
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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.
|
|
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
|
-
|
|
1127
|
-
voice: {
|
|
1128
|
-
mode: "twilio_elevenlabs_tts",
|
|
1129
|
-
elevenlabs: { voiceId: "test" },
|
|
1130
|
-
},
|
|
1131
|
-
},
|
|
993
|
+
elevenlabs: { voiceId: "test" },
|
|
1132
994
|
});
|
|
1133
|
-
const spec = buildElevenLabsVoiceSpec(config.
|
|
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,
|