@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.
- package/bun.lock +3 -0
- package/package.json +2 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -15
- package/src/__tests__/assistant-events-sse-hardening.test.ts +9 -3
- package/src/__tests__/call-controller.test.ts +80 -0
- 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__/ipc-snapshot.test.ts +0 -9
- package/src/__tests__/onboarding-template-contract.test.ts +10 -20
- package/src/__tests__/relay-server.test.ts +3 -3
- 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-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/call-controller.ts +34 -29
- 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 +144 -83
- 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/handlers/browser.ts +1 -6
- package/src/daemon/ipc-contract/browser.ts +5 -14
- package/src/daemon/ipc-contract-inventory.json +0 -2
- package/src/daemon/session-agent-loop-handlers.ts +3 -0
- package/src/daemon/session-runtime-assembly.ts +9 -7
- package/src/mcp/client.ts +2 -1
- package/src/memory/conversation-crud.ts +339 -166
- package/src/runtime/auth/middleware.ts +87 -26
- 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-manager.ts +72 -228
- package/src/tools/browser/browser-screencast.ts +0 -5
- 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
|
@@ -1,125 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
maxSessionsPerConversation: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ProxyEnvVars {
|
|
24
|
-
HTTP_PROXY: string;
|
|
25
|
-
HTTPS_PROXY: string;
|
|
26
|
-
NO_PROXY: string;
|
|
27
|
-
NODE_EXTRA_CA_CERTS?: string;
|
|
28
|
-
/** Combined CA bundle (system roots + proxy CA) for non-Node TLS clients (curl, Python, etc.). */
|
|
29
|
-
SSL_CERT_FILE?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Policy engine types
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
import type { CredentialInjectionTemplate } from '../../credentials/policy-types.js';
|
|
37
|
-
|
|
38
|
-
/** A single credential matched — inject it. */
|
|
39
|
-
export interface PolicyDecisionMatched {
|
|
40
|
-
kind: 'matched';
|
|
41
|
-
credentialId: string;
|
|
42
|
-
template: CredentialInjectionTemplate;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Multiple credentials match — caller must disambiguate. */
|
|
46
|
-
export interface PolicyDecisionAmbiguous {
|
|
47
|
-
kind: 'ambiguous';
|
|
48
|
-
candidates: Array<{ credentialId: string; template: CredentialInjectionTemplate }>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** No credential matches the target host/path. */
|
|
52
|
-
export interface PolicyDecisionMissing {
|
|
53
|
-
kind: 'missing';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** No credential_ids were requested — pass-through. */
|
|
57
|
-
export interface PolicyDecisionUnauthenticated {
|
|
58
|
-
kind: 'unauthenticated';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Approval hook outcomes — structured data for triggering permission prompts.
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/** Context about the outbound request target, used to build permission prompts. */
|
|
66
|
-
export interface RequestTargetContext {
|
|
67
|
-
hostname: string;
|
|
68
|
-
port: number | null;
|
|
69
|
-
path: string;
|
|
70
|
-
/** The protocol scheme of the original request ('http' or 'https'). */
|
|
71
|
-
scheme: 'http' | 'https';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* The target host matches a known credential template pattern, but the
|
|
76
|
-
* session has no credential bound for it. The UI should prompt the user
|
|
77
|
-
* to bind or create a credential.
|
|
78
|
-
*/
|
|
79
|
-
export interface PolicyDecisionAskMissingCredential {
|
|
80
|
-
kind: 'ask_missing_credential';
|
|
81
|
-
target: RequestTargetContext;
|
|
82
|
-
/** Host patterns from the known registry that matched the target. */
|
|
83
|
-
matchingPatterns: string[];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The request doesn't match any known credential template and the session
|
|
88
|
-
* has no credentials. The UI should prompt the user to allow or deny the
|
|
89
|
-
* unauthenticated request.
|
|
90
|
-
*/
|
|
91
|
-
export interface PolicyDecisionAskUnauthenticated {
|
|
92
|
-
kind: 'ask_unauthenticated';
|
|
93
|
-
target: RequestTargetContext;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type PolicyDecision =
|
|
97
|
-
| PolicyDecisionMatched
|
|
98
|
-
| PolicyDecisionAmbiguous
|
|
99
|
-
| PolicyDecisionMissing
|
|
100
|
-
| PolicyDecisionUnauthenticated
|
|
101
|
-
| PolicyDecisionAskMissingCredential
|
|
102
|
-
| PolicyDecisionAskUnauthenticated;
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Proxy approval callback — wires policy "ask" decisions to the UI prompter.
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Payload passed to the approval callback when the policy engine emits an
|
|
110
|
-
* `ask_missing_credential` or `ask_unauthenticated` decision. Contains
|
|
111
|
-
* enough context for the prompter to build a meaningful confirmation dialog.
|
|
112
|
-
*/
|
|
113
|
-
export interface ProxyApprovalRequest {
|
|
114
|
-
/** The policy decision that triggered the approval prompt. */
|
|
115
|
-
decision: PolicyDecisionAskMissingCredential | PolicyDecisionAskUnauthenticated;
|
|
116
|
-
/** The proxy session ID that originated the request. */
|
|
117
|
-
sessionId: ProxySessionId;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Callback signature for proxy approval prompts. The proxy service calls
|
|
122
|
-
* this when an outbound request requires user confirmation. Returns `true`
|
|
123
|
-
* if the user approves, `false` if denied.
|
|
124
|
-
*/
|
|
125
|
-
export type ProxyApprovalCallback = (request: ProxyApprovalRequest) => Promise<boolean>;
|
|
1
|
+
export type {
|
|
2
|
+
CredentialInjectionTemplate,
|
|
3
|
+
CredentialInjectionType,
|
|
4
|
+
PolicyDecision,
|
|
5
|
+
PolicyDecisionAmbiguous,
|
|
6
|
+
PolicyDecisionAskMissingCredential,
|
|
7
|
+
PolicyDecisionAskUnauthenticated,
|
|
8
|
+
PolicyDecisionMatched,
|
|
9
|
+
PolicyDecisionMissing,
|
|
10
|
+
PolicyDecisionUnauthenticated,
|
|
11
|
+
ProxyApprovalCallback,
|
|
12
|
+
ProxyApprovalRequest,
|
|
13
|
+
ProxyEnvVars,
|
|
14
|
+
ProxySession,
|
|
15
|
+
ProxySessionConfig,
|
|
16
|
+
ProxySessionId,
|
|
17
|
+
ProxySessionStatus,
|
|
18
|
+
RequestTargetContext,
|
|
19
|
+
} from "@vellumai/proxy-sidecar";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { invalidateConfigCache, loadRawConfig, saveRawConfig, setNestedValue } from '../../config/loader.js';
|
|
1
2
|
import { normalizeActivationKey } from '../../daemon/handlers/config-voice.js';
|
|
2
3
|
import { RiskLevel } from '../../permissions/types.js';
|
|
3
4
|
import type { ToolDefinition } from '../../providers/types.js';
|
|
@@ -11,6 +12,7 @@ const VOICE_SETTINGS = {
|
|
|
11
12
|
wake_word_enabled: { userDefaultsKey: 'wakeWordEnabled', type: 'boolean' as const },
|
|
12
13
|
wake_word_keyword: { userDefaultsKey: 'wakeWordKeyword', type: 'string' as const },
|
|
13
14
|
wake_word_timeout: { userDefaultsKey: 'wakeWordTimeoutSeconds', type: 'number' as const },
|
|
15
|
+
tts_voice_id: { userDefaultsKey: 'ttsVoiceId', type: 'string' as const },
|
|
14
16
|
} as const;
|
|
15
17
|
|
|
16
18
|
type VoiceSettingName = keyof typeof VOICE_SETTINGS;
|
|
@@ -55,6 +57,16 @@ function validateSetting(setting: string, value: unknown): { ok: true; coerced:
|
|
|
55
57
|
}
|
|
56
58
|
return { ok: true, coerced: num };
|
|
57
59
|
}
|
|
60
|
+
case 'tts_voice_id': {
|
|
61
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
62
|
+
return { ok: false, error: 'tts_voice_id must be a non-empty string (ElevenLabs voice ID)' };
|
|
63
|
+
}
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!/^[a-zA-Z0-9]+$/.test(trimmed)) {
|
|
66
|
+
return { ok: false, error: 'tts_voice_id must contain only alphanumeric characters (ElevenLabs voice ID format)' };
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, coerced: trimmed };
|
|
69
|
+
}
|
|
58
70
|
default:
|
|
59
71
|
return { ok: false, error: `Unknown setting "${setting}"` };
|
|
60
72
|
}
|
|
@@ -65,12 +77,13 @@ const FRIENDLY_NAMES: Record<VoiceSettingName, string> = {
|
|
|
65
77
|
wake_word_enabled: 'Wake word',
|
|
66
78
|
wake_word_keyword: 'Wake word keyword',
|
|
67
79
|
wake_word_timeout: 'Wake word timeout',
|
|
80
|
+
tts_voice_id: 'ElevenLabs voice',
|
|
68
81
|
};
|
|
69
82
|
|
|
70
83
|
export class VoiceConfigUpdateTool implements Tool {
|
|
71
84
|
name = 'voice_config_update';
|
|
72
85
|
description =
|
|
73
|
-
'Update a voice configuration setting (PTT activation key, wake word enabled/keyword/timeout). ' +
|
|
86
|
+
'Update a voice configuration setting (TTS voice ID, PTT activation key, wake word enabled/keyword/timeout). ' +
|
|
74
87
|
'Changes take effect immediately via IPC broadcast to the desktop client.';
|
|
75
88
|
category = 'system';
|
|
76
89
|
defaultRiskLevel = RiskLevel.Low;
|
|
@@ -141,6 +154,15 @@ export class VoiceConfigUpdateTool implements Tool {
|
|
|
141
154
|
});
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
// For tts_voice_id, also persist to the config file (elevenlabs.voiceId)
|
|
158
|
+
// so phone calls and other consumers pick it up.
|
|
159
|
+
if (setting === 'tts_voice_id') {
|
|
160
|
+
const raw = loadRawConfig();
|
|
161
|
+
setNestedValue(raw, 'elevenlabs.voiceId', validation.coerced);
|
|
162
|
+
saveRawConfig(raw);
|
|
163
|
+
invalidateConfigCache();
|
|
164
|
+
}
|
|
165
|
+
|
|
144
166
|
return {
|
|
145
167
|
content: `${friendlyName} updated to ${JSON.stringify(validation.coerced)}. The change has been broadcast to the desktop client.`,
|
|
146
168
|
isError: false,
|
package/src/util/logger.ts
CHANGED
|
@@ -156,7 +156,10 @@ function getRootLogger(): pino.Logger {
|
|
|
156
156
|
|
|
157
157
|
try {
|
|
158
158
|
const logPath = getLogPath();
|
|
159
|
-
|
|
159
|
+
// Use sync: true so the fd is opened immediately. This prevents
|
|
160
|
+
// "sonic boom is not ready yet" errors when commander calls
|
|
161
|
+
// process.exit(0) for --help/--version before the async fd is ready.
|
|
162
|
+
const fileDest = pino.destination({ dest: logPath, sync: true, mkdir: true, mode: 0o600 });
|
|
160
163
|
// Tighten permissions on pre-existing log files that may have been created with looser modes
|
|
161
164
|
try { chmodSync(logPath, 0o600); } catch { /* best-effort */ }
|
|
162
165
|
const fileStream = pinoPretty(prettyOpts({ destination: fileDest, colorize: false }));
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// ── Mocks (must come before source imports) ──────────────────────────
|
|
4
|
-
|
|
5
|
-
let mockApiKey: string | null = null;
|
|
6
|
-
|
|
7
|
-
mock.module("../util/logger.js", () => ({
|
|
8
|
-
getLogger: () =>
|
|
9
|
-
new Proxy({} as Record<string, unknown>, {
|
|
10
|
-
get: () => () => {},
|
|
11
|
-
}),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
mock.module("../security/secure-keys.js", () => ({
|
|
15
|
-
getSecureKey: (key: string) => {
|
|
16
|
-
if (key === "credential:elevenlabs:api_key") return mockApiKey;
|
|
17
|
-
return null;
|
|
18
|
-
},
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
let mockVoiceConfig = {
|
|
22
|
-
elevenlabs: {
|
|
23
|
-
agentId: "agent-123",
|
|
24
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
25
|
-
registerCallTimeoutMs: 5000,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
mock.module("../config/loader.js", () => ({
|
|
30
|
-
getConfig: () => ({
|
|
31
|
-
ui: {},
|
|
32
|
-
|
|
33
|
-
calls: { voice: mockVoiceConfig },
|
|
34
|
-
}),
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
import { getElevenLabsConfig } from "../calls/elevenlabs-config.js";
|
|
38
|
-
|
|
39
|
-
describe("elevenlabs-config", () => {
|
|
40
|
-
test("returns config when API key and agent ID are set", () => {
|
|
41
|
-
mockApiKey = "sk-test-key-123";
|
|
42
|
-
mockVoiceConfig = {
|
|
43
|
-
elevenlabs: {
|
|
44
|
-
agentId: "agent-abc",
|
|
45
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
46
|
-
registerCallTimeoutMs: 10000,
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const config = getElevenLabsConfig();
|
|
51
|
-
expect(config.apiKey).toBe("sk-test-key-123");
|
|
52
|
-
expect(config.agentId).toBe("agent-abc");
|
|
53
|
-
expect(config.apiBaseUrl).toBe("https://api.elevenlabs.io");
|
|
54
|
-
expect(config.registerCallTimeoutMs).toBe(10000);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("throws ConfigError when API key is missing", () => {
|
|
58
|
-
mockApiKey = null;
|
|
59
|
-
mockVoiceConfig = {
|
|
60
|
-
elevenlabs: {
|
|
61
|
-
agentId: "agent-abc",
|
|
62
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
63
|
-
registerCallTimeoutMs: 5000,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
expect(() => getElevenLabsConfig()).toThrow(/API key is not configured/);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("throws ConfigError when API key is empty string", () => {
|
|
71
|
-
mockApiKey = "";
|
|
72
|
-
mockVoiceConfig = {
|
|
73
|
-
elevenlabs: {
|
|
74
|
-
agentId: "agent-abc",
|
|
75
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
76
|
-
registerCallTimeoutMs: 5000,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
expect(() => getElevenLabsConfig()).toThrow(/API key is not configured/);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("throws ConfigError when agent ID is missing", () => {
|
|
84
|
-
mockApiKey = "sk-valid";
|
|
85
|
-
mockVoiceConfig = {
|
|
86
|
-
elevenlabs: {
|
|
87
|
-
agentId: "",
|
|
88
|
-
apiBaseUrl: "https://api.elevenlabs.io",
|
|
89
|
-
registerCallTimeoutMs: 5000,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
expect(() => getElevenLabsConfig()).toThrow(/agent ID is not configured/);
|
|
94
|
-
});
|
|
95
|
-
});
|