diff --git a/assistant/src/calls/call-store.ts b/assistant/src/calls/call-store.ts index 126d7ecfc4d..cfdbe414948 100644 --- a/assistant/src/calls/call-store.ts +++ b/assistant/src/calls/call-store.ts @@ -1,4 +1,4 @@ -import { eq, and, notInArray, desc } from 'drizzle-orm'; +import { eq, and, or, notInArray, desc } from 'drizzle-orm'; import { v4 as uuid } from 'uuid'; import { getDb } from '../memory/db.js'; import { callSessions, callEvents, callPendingQuestions } from '../memory/schema.js'; @@ -117,7 +117,10 @@ export function getActiveCallSessionForConversation(conversationId: string): Cal .from(callSessions) .where( and( - eq(callSessions.conversationId, conversationId), + or( + eq(callSessions.conversationId, conversationId), + eq(callSessions.initiatedFromConversationId, conversationId), + ), notInArray(callSessions.status, ['completed', 'failed', 'cancelled']), ), ) diff --git a/assistant/src/runtime/channel-readiness-service.ts b/assistant/src/runtime/channel-readiness-service.ts index f627a0c66af..cffcea2eed8 100644 --- a/assistant/src/runtime/channel-readiness-service.ts +++ b/assistant/src/runtime/channel-readiness-service.ts @@ -167,6 +167,30 @@ const smsProbe: ChannelProbe = { // ── Voice Probe ───────────────────────────────────────────────────────────── +/** + * Resolve voice from-number with the same precedence as SMS: + * assistant mapping -> env override -> config sms.phoneNumber -> secure key fallback. + * + * Voice and SMS share the same Twilio phone number infrastructure, so the + * resolution logic is identical to resolveSmsPhoneNumber. + */ +function resolveVoicePhoneNumber(assistantId?: string): string { + try { + const raw = loadRawConfig(); + const smsConfig = (raw?.sms ?? {}) as Record; + const mapped = getAssistantMappedPhoneNumber(smsConfig, assistantId); + return mapped + || process.env.TWILIO_PHONE_NUMBER + || (smsConfig.phoneNumber as string) + || getSecureKey('credential:twilio:phone_number') + || ''; + } catch { + return process.env.TWILIO_PHONE_NUMBER + || getSecureKey('credential:twilio:phone_number') + || ''; + } +} + const voiceProbe: ChannelProbe = { channel: 'voice', runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[] { @@ -181,16 +205,18 @@ const voiceProbe: ChannelProbe = { : 'Twilio Account SID and Auth Token are not configured', }); - const phoneNumber = process.env.TWILIO_PHONE_NUMBER - || getSecureKey('credential:twilio:phone_number') - || ''; - const hasPhone = !!phoneNumber; + const resolvedNumber = resolveVoicePhoneNumber(context?.assistantId); + const hasPhone = !!resolvedNumber || (!context?.assistantId && hasAnyAssistantMappedPhoneNumberSafe()); results.push({ name: 'phone_number', passed: hasPhone, message: hasPhone - ? 'Phone number is assigned for voice calls' - : 'No phone number assigned for voice calls', + ? (context?.assistantId && !resolvedNumber + ? `Assistant ${context.assistantId} has no direct mapping, but phone numbers are assigned` + : 'Phone number is assigned for voice calls') + : (context?.assistantId + ? `No phone number assigned for assistant ${context.assistantId}` + : 'No phone number assigned for voice calls'), }); const hasIngress = hasIngressConfigured();