Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions assistant/src/calls/call-store.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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']),
),
)
Expand Down
38 changes: 32 additions & 6 deletions assistant/src/runtime/channel-readiness-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
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[] {
Expand All @@ -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();
Expand Down
Loading