メインコンテンツへ

電話

一言でいうと

Twilio 経由の電話応対を扱うモジュールです。着信は Twilio の Webhook で通知され、TwiML で応答を組み立て、通話中の音声は WebSocket でストリーミング受信して逐次文字起こしします。この文字起こしは realtime モジュールの AI アシストパイプラインにそのまま流し込まれ、応対中のスタッフに示唆(4-bucket suggestions)を出す仕組みと直結しています。

主要ドメイン概念

概念役割
TenantPhoneNumberテナントが保有する Twilio 電話番号
CallLog通話記録(着信/発信、担当者、文字起こし、タイムラインへの追加可否)
PhoneCallContext / PhoneCallContextRegistry進行中通話のコンテキスト(callSid・テナント・担当者など)をメモリ上で保持するレジストリ
PhoneRealtimeSessionTwilio 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変換、SpeechToTextOrchestratorcore/ai の choke point)へ渡して逐次文字起こしイベントを発行
  • domain/service/CallTranscriptionService.kt: 文字起こし結果の整形・発話者判定
  • infrastructure/listener/PhoneEventListener.kt: 着信時にスタッフへ通知を出し、assistSessionId(後述)を通知ペイロードに含める

Frontend 構造

  • composables/: useSoftphone(ブラウザ発信の Twilio Voice SDK ラッパー)、useActiveCallTracker(進行中通話の追跡)、usePhoneTranscriptStream(文字起こしSSE購読)、useCallLogsuseCallRecordingSource
  • components/: CallLogTable.vue/CallLogDetailSheet.vue/CallLogInboxDialog.vueCallLogStatusBadge.vue/CallLogUrgencyBadge.vueTenantPhoneSettingsCard.vue

他モジュールとの辺

  • ai: 通話音声の文字起こしは core/phoneSpeechToTextOrchestrator を直接呼ぶ(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 側の購読と一致しなくなります。