電話
一言でいうと
Twilio 経由の電話応対を扱うモジュールです。着信は Twilio の Webhook で通知され、TwiML で応答を組み立て、通話中の音声は WebSocket でストリーミング受信して逐次文字起こしします。この文字起こしは realtime モジュールの AI アシストパイプラインにそのまま流し込まれ、応対中のスタッフに示唆(4-bucket suggestions)を出す仕組みと直結しています。
主要ドメイン概念
| 概念 | 役割 |
|---|---|
TenantPhoneNumber | テナントが保有する Twilio 電話番号 |
CallLog | 通話記録(着信/発信、担当者、文字起こし、タイムラインへの追加可否) |
PhoneCallContext / PhoneCallContextRegistry | 進行中通話のコンテキスト(callSid・テナント・担当者など)をメモリ上で保持するレジストリ |
PhoneRealtimeSession | Twilio callSid から realtime-assist セッションIDを決定的に導出するユーティリティ(下記callout参照) |
| イベント群 | CallReceivedEvent/CallEndedEvent/PhoneTranscriptPartialEvent/PhoneTranscriptFinalEvent/RecordingReadyEvent |
Backend 構造
api/controller/TwilioWebhookController.kt:/api/v1/public/phone/twilio/**の Twilio Webhook 受け口(permitAll— Twilio 自体は認証済みユーザーではないため)。着信TwiML組み立て、スタッフへのダイヤル、AI応対TwiML、録音完了・ステータスコールバックを処理api/controller/CallLogController.kt/TenantPhoneNumberController.kt/PhoneVoiceTokenController.kt/PhoneCallBriefingController.kt: 通話ログCRUD、番号管理、ブラウザ発信用の Twilio Voice トークン発行、通話前ブリーフィングセッション作成infrastructure/websocket/PhoneStreamWebSocketHandler.kt: Twilio Media Streams(mulaw音声)を受信しMulawToPcmConverterでPCM変換、SpeechToTextOrchestrator(core/aiの choke point)へ渡して逐次文字起こしイベントを発行domain/service/CallTranscriptionService.kt: 文字起こし結果の整形・発話者判定infrastructure/listener/PhoneEventListener.kt: 着信時にスタッフへ通知を出し、assistSessionId(後述)を通知ペイロードに含める
Frontend 構造
composables/:useSoftphone(ブラウザ発信の Twilio Voice SDK ラッパー)、useActiveCallTracker(進行中通話の追跡)、usePhoneTranscriptStream(文字起こしSSE購読)、useCallLogs、useCallRecordingSourcecomponents/:CallLogTable.vue/CallLogDetailSheet.vue/CallLogInboxDialog.vue、CallLogStatusBadge.vue/CallLogUrgencyBadge.vue、TenantPhoneSettingsCard.vue
他モジュールとの辺
ai: 通話音声の文字起こしはcore/phoneがSpeechToTextOrchestratorを直接呼ぶ(realtimeを経由しない)realtime:PhoneRealtimeSession.assistSessionId(callSid)がUUID.nameUUIDFromBytes("phone:$callSid")で決定的なセッションIDを導出する。電話通話はrealtime_sessions行を作らず、この決定的IDを使ってRealtimeAssistGenerator(realtime側)が示唆を publish し、frontend は同じIDで/events/realtime-assist?sessionId=...を購読する。導出ロジックのSSoTはphone側にあり、realtimeはこの結果をRealtimeSessionIdでラップして使う(依存方向は realtime → phone、循環回避のため)editor: 通話の録音・文字起こしはドキュメントとして保存されうる
TwilioWebhookController は /api/v1/public/** 配下の permitAll エンドポイントです。backend の permitAll ルール(TenantContextFilter を経由しない = 未検証ヘッダを信頼しない)に従っており、テナント識別は Twilio の署名検証済みリクエストから電話番号経由で解決しています。新しい public エンドポイントを追加する際は、ここを前例にしてよいか自問する前に security review が必須です。また assistSessionId の導出は PhoneRealtimeSession の1箇所がSSoTです — 同じ callSid から別の方法でセッションIDを作ると realtime 側の購読と一致しなくなります。