@vellumai/assistant 0.4.2 → 0.4.3
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/ARCHITECTURE.md +84 -7
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/assistant-id-boundary-guard.test.ts +290 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/channel-guardian.test.ts +6 -5
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +21 -0
- package/src/__tests__/guardian-outbound-http.test.ts +0 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/non-member-access-request.test.ts +28 -1
- package/src/__tests__/notification-decision-strategy.test.ts +44 -0
- package/src/__tests__/relay-server.test.ts +644 -4
- package/src/__tests__/session-init.benchmark.test.ts +0 -1
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +43 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/approvals/guardian-decision-primitive.ts +2 -1
- package/src/approvals/guardian-request-resolvers.ts +42 -3
- package/src/calls/call-constants.ts +8 -0
- package/src/calls/call-controller.ts +2 -1
- package/src/calls/call-domain.ts +5 -4
- package/src/calls/relay-server.ts +513 -116
- package/src/calls/twilio-routes.ts +3 -5
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +164 -1
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +214 -0
- package/src/config/calls-schema.ts +12 -0
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -2
- package/src/daemon/handlers/config-channels.ts +5 -7
- package/src/daemon/handlers/config-inbox.ts +2 -0
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +11 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +3 -1
- package/src/daemon/server.ts +2 -1
- package/src/daemon/session-agent-loop.ts +2 -1
- package/src/daemon/session-runtime-assembly.ts +3 -1
- package/src/daemon/session-surfaces.ts +29 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +4 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +10 -5
- package/src/notifications/copy-composer.ts +11 -1
- package/src/notifications/emit-signal.ts +2 -1
- package/src/runtime/access-request-helper.ts +11 -3
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -4
- package/src/runtime/http-server.ts +11 -20
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +2 -1
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/events-routes.ts +2 -3
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +4 -3
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +2 -1
- package/src/workspace/git-service.ts +19 -0
|
@@ -103,7 +103,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
103
103
|
getTCPPort: () => 8765,
|
|
104
104
|
isIOSPairingEnabled: () => false,
|
|
105
105
|
isTCPEnabled: () => false,
|
|
106
|
-
normalizeAssistantId: (id: string) => id,
|
|
107
106
|
readHttpToken: () => null,
|
|
108
107
|
readLockfile: () => null,
|
|
109
108
|
readPlatformToken: () => null,
|
|
@@ -615,7 +615,10 @@ describe('injectInboundActorContext', () => {
|
|
|
615
615
|
|
|
616
616
|
const result = injectInboundActorContext(baseUserMessage, ctx);
|
|
617
617
|
const text = (result.content[0] as { type: 'text'; text: string }).text;
|
|
618
|
-
expect(text).toContain('non-guardian
|
|
618
|
+
expect(text).toContain('trusted contact (non-guardian)');
|
|
619
|
+
expect(text).toContain('attempt to fulfill it normally');
|
|
620
|
+
expect(text).toContain('tool execution layer will automatically deny it and escalate');
|
|
621
|
+
expect(text).toContain('Do not self-approve');
|
|
619
622
|
expect(text).toContain('Do not explain the verification system');
|
|
620
623
|
expect(text).toContain('member_status: active');
|
|
621
624
|
expect(text).toContain('member_policy: default');
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'bun:test';
|
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
CardSurfaceData,
|
|
5
|
+
DynamicPageSurfaceData,
|
|
5
6
|
ServerMessage,
|
|
6
7
|
SurfaceData,
|
|
7
8
|
SurfaceType,
|
|
@@ -96,6 +97,48 @@ describe('task_progress surface compatibility', () => {
|
|
|
96
97
|
expect((card.templateData as Record<string, unknown>).status).toBe('in_progress');
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
test('ui_show normalizes top-level dynamic_page fields into data', async () => {
|
|
101
|
+
const sent: ServerMessage[] = [];
|
|
102
|
+
const ctx = makeContext(sent);
|
|
103
|
+
|
|
104
|
+
const result = await surfaceProxyResolver(ctx, 'ui_show', {
|
|
105
|
+
surface_type: 'dynamic_page',
|
|
106
|
+
title: 'My Slides',
|
|
107
|
+
html: '<h1>Hello</h1>',
|
|
108
|
+
preview: { title: 'Slides', subtitle: '3 slides about Apple' },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.isError).toBe(false);
|
|
112
|
+
|
|
113
|
+
const showMessage = sent.find((msg): msg is UiSurfaceShow => msg.type === 'ui_surface_show');
|
|
114
|
+
expect(showMessage).toBeDefined();
|
|
115
|
+
if (!showMessage || showMessage.surfaceType !== 'dynamic_page') return;
|
|
116
|
+
|
|
117
|
+
const page = showMessage.data as DynamicPageSurfaceData;
|
|
118
|
+
expect(page.html).toBe('<h1>Hello</h1>');
|
|
119
|
+
expect(page.preview).toEqual({ title: 'Slides', subtitle: '3 slides about Apple' });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('ui_show dynamic_page uses data.html when properly nested', async () => {
|
|
123
|
+
const sent: ServerMessage[] = [];
|
|
124
|
+
const ctx = makeContext(sent);
|
|
125
|
+
|
|
126
|
+
const result = await surfaceProxyResolver(ctx, 'ui_show', {
|
|
127
|
+
surface_type: 'dynamic_page',
|
|
128
|
+
title: 'My Slides',
|
|
129
|
+
data: { html: '<h1>Nested</h1>' },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.isError).toBe(false);
|
|
133
|
+
|
|
134
|
+
const showMessage = sent.find((msg): msg is UiSurfaceShow => msg.type === 'ui_surface_show');
|
|
135
|
+
expect(showMessage).toBeDefined();
|
|
136
|
+
if (!showMessage || showMessage.surfaceType !== 'dynamic_page') return;
|
|
137
|
+
|
|
138
|
+
const page = showMessage.data as DynamicPageSurfaceData;
|
|
139
|
+
expect(page.html).toBe('<h1>Nested</h1>');
|
|
140
|
+
});
|
|
141
|
+
|
|
99
142
|
test('ui_update normalizes top-level task_progress fields into templateData', async () => {
|
|
100
143
|
const sent: ServerMessage[] = [];
|
|
101
144
|
const ctx = makeContext(sent);
|
|
@@ -34,7 +34,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
34
34
|
getDbPath: () => join(testDir, 'test.db'),
|
|
35
35
|
getLogPath: () => join(testDir, 'test.log'),
|
|
36
36
|
ensureDataDir: () => {},
|
|
37
|
-
normalizeAssistantId: (id: string) => id === 'self' ? 'self' : id,
|
|
38
37
|
readHttpToken: () => 'test-bearer-token',
|
|
39
38
|
}));
|
|
40
39
|
|
|
@@ -82,6 +81,7 @@ mock.module('../runtime/approval-message-composer.js', () => ({
|
|
|
82
81
|
composeApprovalMessageGenerative: async () => 'mock generative message',
|
|
83
82
|
}));
|
|
84
83
|
|
|
84
|
+
import { getResolver } from '../approvals/guardian-request-resolvers.js';
|
|
85
85
|
import {
|
|
86
86
|
createApprovalRequest,
|
|
87
87
|
createBinding,
|
|
@@ -489,6 +489,16 @@ describe('trusted contact activated notification signal', () => {
|
|
|
489
489
|
expect(activatedSignals.length).toBe(0);
|
|
490
490
|
});
|
|
491
491
|
|
|
492
|
+
test('voice access_request resolver has registered handler for access_request kind', () => {
|
|
493
|
+
// The access_request resolver is registered during module load. When the
|
|
494
|
+
// source channel is 'voice', it should directly activate the member via
|
|
495
|
+
// upsertMember (no verification session). This test validates the resolver
|
|
496
|
+
// is registered and accessible.
|
|
497
|
+
const resolver = getResolver('access_request');
|
|
498
|
+
expect(resolver).toBeDefined();
|
|
499
|
+
expect(resolver!.kind).toBe('access_request');
|
|
500
|
+
});
|
|
501
|
+
|
|
492
502
|
test('member is persisted BEFORE activated signal is emitted', async () => {
|
|
493
503
|
// Set up a guardian binding
|
|
494
504
|
createBinding({
|
|
@@ -29,7 +29,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
29
29
|
getDbPath: () => join(testDir, 'test.db'),
|
|
30
30
|
getLogPath: () => join(testDir, 'test.log'),
|
|
31
31
|
ensureDataDir: () => {},
|
|
32
|
-
normalizeAssistantId: (id: string) => id === 'self' ? 'self' : id,
|
|
33
32
|
readHttpToken: () => 'test-bearer-token',
|
|
34
33
|
}));
|
|
35
34
|
|
|
@@ -32,7 +32,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
32
32
|
getDbPath: () => join(testDir, 'test.db'),
|
|
33
33
|
getLogPath: () => join(testDir, 'test.log'),
|
|
34
34
|
ensureDataDir: () => {},
|
|
35
|
-
normalizeAssistantId: (id: string) => id === 'self' ? 'self' : id,
|
|
36
35
|
readHttpToken: () => 'test-bearer-token',
|
|
37
36
|
}));
|
|
38
37
|
|
|
@@ -704,19 +704,20 @@ describe('twilio webhook routes', () => {
|
|
|
704
704
|
expect(res.status).toBe(400);
|
|
705
705
|
});
|
|
706
706
|
|
|
707
|
-
test('inbound webhook
|
|
707
|
+
test('inbound webhook creates session with internal scope assistantId', async () => {
|
|
708
708
|
const req = makeInboundVoiceRequest({
|
|
709
709
|
CallSid: 'CA_inbound_assist_1',
|
|
710
710
|
From: '+14155551234',
|
|
711
711
|
To: '+15550001111',
|
|
712
712
|
});
|
|
713
713
|
|
|
714
|
-
const res = await handleVoiceWebhook(req
|
|
714
|
+
const res = await handleVoiceWebhook(req);
|
|
715
715
|
|
|
716
716
|
expect(res.status).toBe(200);
|
|
717
717
|
const session = getCallSessionByCallSid('CA_inbound_assist_1');
|
|
718
718
|
expect(session).not.toBeNull();
|
|
719
|
-
|
|
719
|
+
// Daemon always uses internal scope — external assistant IDs are not leaked into session state.
|
|
720
|
+
expect(session!.assistantId).toBe('self');
|
|
720
721
|
});
|
|
721
722
|
|
|
722
723
|
test('outbound call flow remains non-regressed with callSessionId present', async () => {
|
|
@@ -53,7 +53,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
53
53
|
getInterfacesDir: () => '',
|
|
54
54
|
getClipboardCommand: () => null,
|
|
55
55
|
readLockfile: () => null,
|
|
56
|
-
normalizeAssistantId: (id: string) => id,
|
|
57
56
|
writeLockfile: () => {},
|
|
58
57
|
readPlatformToken: () => null,
|
|
59
58
|
readSessionToken: () => null,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type GuardianApprovalRequest,
|
|
36
36
|
updateApprovalDecision,
|
|
37
37
|
} from '../memory/channel-guardian-store.js';
|
|
38
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
38
39
|
import type {
|
|
39
40
|
ApprovalAction,
|
|
40
41
|
ApprovalDecisionResult,
|
|
@@ -233,7 +234,7 @@ export function mintCanonicalRequestGrant(params: {
|
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
const result = mintGrantFromDecision({
|
|
236
|
-
assistantId:
|
|
237
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
237
238
|
scopeMode: 'tool_signature',
|
|
238
239
|
toolName: request.toolName,
|
|
239
240
|
inputDigest: request.inputDigest,
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
import { answerCall } from '../calls/call-domain.js';
|
|
15
15
|
import type { CanonicalGuardianRequest } from '../memory/canonical-guardian-store.js';
|
|
16
|
+
import { upsertMember } from '../memory/ingress-member-store.js';
|
|
16
17
|
import { emitNotificationSignal } from '../notifications/emit-signal.js';
|
|
17
18
|
import { addRule } from '../permissions/trust-store.js';
|
|
19
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
18
20
|
import type { ApprovalAction } from '../runtime/channel-approval-types.js';
|
|
19
21
|
import { createOutboundSession } from '../runtime/channel-guardian-service.js';
|
|
20
22
|
import { deliverChannelReply } from '../runtime/gateway-client.js';
|
|
@@ -24,6 +26,10 @@ import { getLogger } from '../util/logger.js';
|
|
|
24
26
|
|
|
25
27
|
const log = getLogger('guardian-request-resolvers');
|
|
26
28
|
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Helpers
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
27
33
|
// ---------------------------------------------------------------------------
|
|
28
34
|
// Types
|
|
29
35
|
// ---------------------------------------------------------------------------
|
|
@@ -278,7 +284,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
278
284
|
const requesterExternalUserId = request.requesterExternalUserId ?? '';
|
|
279
285
|
const requesterChatId = request.requesterChatId ?? request.requesterExternalUserId ?? '';
|
|
280
286
|
const decidedByExternalUserId = ctx.actor.externalUserId ?? '';
|
|
281
|
-
const assistantId =
|
|
287
|
+
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
282
288
|
|
|
283
289
|
if (decision.action === 'reject') {
|
|
284
290
|
log.info(
|
|
@@ -340,7 +346,40 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
340
346
|
return { ok: true, applied: true };
|
|
341
347
|
}
|
|
342
348
|
|
|
343
|
-
//
|
|
349
|
+
// Voice approvals: directly activate the trusted contact without minting
|
|
350
|
+
// a verification session. The caller is already on the line and the
|
|
351
|
+
// relay server's in-call wait loop will detect the approved status.
|
|
352
|
+
if (channel === 'voice') {
|
|
353
|
+
try {
|
|
354
|
+
upsertMember({
|
|
355
|
+
assistantId,
|
|
356
|
+
sourceChannel: 'voice',
|
|
357
|
+
externalUserId: requesterExternalUserId,
|
|
358
|
+
externalChatId: requesterChatId,
|
|
359
|
+
status: 'active',
|
|
360
|
+
policy: 'allow',
|
|
361
|
+
});
|
|
362
|
+
} catch (err) {
|
|
363
|
+
log.error(
|
|
364
|
+
{ err, requesterExternalUserId },
|
|
365
|
+
'Access request resolver: failed to activate voice caller as trusted contact',
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
log.info(
|
|
370
|
+
{
|
|
371
|
+
event: 'resolver_access_request_voice_approved',
|
|
372
|
+
requestId: request.id,
|
|
373
|
+
channel,
|
|
374
|
+
requesterExternalUserId,
|
|
375
|
+
},
|
|
376
|
+
'Access request resolver: voice approval — direct trusted-contact activation (no verification session)',
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
return { ok: true, applied: true };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Non-voice approvals: mint an identity-bound verification session so the
|
|
344
383
|
// requester can verify their identity.
|
|
345
384
|
const session = createOutboundSession({
|
|
346
385
|
assistantId,
|
|
@@ -461,7 +500,7 @@ const toolGrantRequestResolver: GuardianRequestResolver = {
|
|
|
461
500
|
async resolve(ctx: ResolverContext): Promise<ResolverResult> {
|
|
462
501
|
const { request, decision, channelDeliveryContext } = ctx;
|
|
463
502
|
const requesterChatId = request.requesterChatId ?? request.requesterExternalUserId ?? '';
|
|
464
|
-
const assistantId =
|
|
503
|
+
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
465
504
|
|
|
466
505
|
if (decision.action === 'reject') {
|
|
467
506
|
log.info(
|
|
@@ -41,6 +41,14 @@ export function getUserConsultationTimeoutMs(): number {
|
|
|
41
41
|
return getConfig().calls.userConsultTimeoutSeconds * 1000;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export function getTtsPlaybackDelayMs(): number {
|
|
45
|
+
return getConfig().calls.ttsPlaybackDelayMs;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getAccessRequestPollIntervalMs(): number {
|
|
49
|
+
return getConfig().calls.accessRequestPollIntervalMs;
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export const SILENCE_TIMEOUT_MS = 30 * 1000; // 30 seconds
|
|
45
53
|
|
|
46
54
|
// Legacy named exports for backward compatibility (use functions above for config-backed values)
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
listCanonicalGuardianDeliveries,
|
|
19
19
|
} from '../memory/canonical-guardian-store.js';
|
|
20
20
|
import { revokeScopedApprovalGrantsForContext } from '../memory/scoped-approval-grants.js';
|
|
21
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
21
22
|
import { computeToolApprovalDigest } from '../security/tool-approval-digest.js';
|
|
22
23
|
import { getLogger } from '../util/logger.js';
|
|
23
24
|
import { readHttpToken } from '../util/platform.js';
|
|
@@ -245,7 +246,7 @@ export class CallController {
|
|
|
245
246
|
this.task = task;
|
|
246
247
|
this.isInbound = !task;
|
|
247
248
|
this.broadcast = opts?.broadcast;
|
|
248
|
-
this.assistantId = opts?.assistantId ??
|
|
249
|
+
this.assistantId = opts?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
249
250
|
this.guardianContext = opts?.guardianContext ?? null;
|
|
250
251
|
|
|
251
252
|
// Resolve the conversation ID from the call session
|
package/src/calls/call-domain.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { getOrCreateConversation } from '../memory/conversation-key-store.js';
|
|
|
14
14
|
import { queueGenerateConversationTitle } from '../memory/conversation-title-service.js';
|
|
15
15
|
import { upsertBinding } from '../memory/external-conversation-store.js';
|
|
16
16
|
import { revokeScopedApprovalGrantsForContext } from '../memory/scoped-approval-grants.js';
|
|
17
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
17
18
|
import { isGuardian } from '../runtime/channel-guardian-service.js';
|
|
18
19
|
import { getSecureKey } from '../security/secure-keys.js';
|
|
19
20
|
import { getLogger } from '../util/logger.js';
|
|
@@ -208,7 +209,7 @@ export type CreateInboundVoiceSessionResult = {
|
|
|
208
209
|
export function createInboundVoiceSession(
|
|
209
210
|
input: CreateInboundVoiceSessionInput,
|
|
210
211
|
): CreateInboundVoiceSessionResult {
|
|
211
|
-
const { callSid, fromNumber, toNumber, assistantId =
|
|
212
|
+
const { callSid, fromNumber, toNumber, assistantId = DAEMON_INTERNAL_ASSISTANT_ID } = input;
|
|
212
213
|
|
|
213
214
|
// Check if a session already exists for this CallSid (replay protection)
|
|
214
215
|
const existing = getCallSessionByCallSid(callSid);
|
|
@@ -219,7 +220,7 @@ export function createInboundVoiceSession(
|
|
|
219
220
|
|
|
220
221
|
// Create a dedicated voice conversation keyed by CallSid so inbound calls
|
|
221
222
|
// get their own conversation thread.
|
|
222
|
-
const voiceConvKey = assistantId && assistantId !==
|
|
223
|
+
const voiceConvKey = assistantId && assistantId !== DAEMON_INTERNAL_ASSISTANT_ID
|
|
223
224
|
? `asst:${assistantId}:voice:inbound:${callSid}`
|
|
224
225
|
: `voice:inbound:${callSid}`;
|
|
225
226
|
const { conversationId: voiceConversationId } = getOrCreateConversation(voiceConvKey);
|
|
@@ -272,7 +273,7 @@ export function createInboundVoiceSession(
|
|
|
272
273
|
* Initiate a new outbound call.
|
|
273
274
|
*/
|
|
274
275
|
export async function startCall(input: StartCallInput): Promise<StartCallResult | CallError> {
|
|
275
|
-
const { phoneNumber, task, context: callContext, conversationId, callerIdentityMode, assistantId =
|
|
276
|
+
const { phoneNumber, task, context: callContext, conversationId, callerIdentityMode, assistantId = DAEMON_INTERNAL_ASSISTANT_ID } = input;
|
|
276
277
|
|
|
277
278
|
if (!phoneNumber || typeof phoneNumber !== 'string') {
|
|
278
279
|
return { ok: false, error: 'phone_number is required and must be a string', status: 400 };
|
|
@@ -644,7 +645,7 @@ export type StartGuardianVerificationCallResult =
|
|
|
644
645
|
export async function startGuardianVerificationCall(
|
|
645
646
|
input: StartGuardianVerificationCallInput,
|
|
646
647
|
): Promise<StartGuardianVerificationCallResult> {
|
|
647
|
-
const { phoneNumber, guardianVerificationSessionId, assistantId =
|
|
648
|
+
const { phoneNumber, guardianVerificationSessionId, assistantId = DAEMON_INTERNAL_ASSISTANT_ID, originConversationId } = input;
|
|
648
649
|
|
|
649
650
|
if (!phoneNumber || !E164_REGEX.test(phoneNumber)) {
|
|
650
651
|
return { ok: false, error: 'phone_number must be in E.164 format', status: 400 };
|