use std::sync::Arc; use tokio::sync::{oneshot, watch}; use crate::bus::CommittedTurnDelta; use crate::channels::{ChannelManager, TurnTarget}; use crate::delivery::{DeliveryCoordinator, DeliveryError}; use crate::session::TurnSnapshot; use crate::task_supervisor::TaskSupervisor; use super::coordinator::SinkRoute; /// Gateway-owned facade that resolves a target Channel and starts exactly one /// TurnSink lifecycle. Session workers depend on this abstraction rather than /// on Channel implementations or WebSocket/Feishu protocols. #[derive(Clone)] pub struct TurnDeliveryService { coordinator: DeliveryCoordinator, channels: ChannelManager, supervisor: TaskSupervisor, } /// Completion handle for one TurnSink lifecycle. /// /// Creating a sink only proves that delivery started. Callers consume this /// handle after publishing a terminal snapshot to learn whether the terminal /// write actually reached the Channel. pub struct TurnDeliveryHandle { pub(crate) completion: oneshot::Receiver>, } impl TurnDeliveryHandle { pub async fn wait(self) -> Result<(), DeliveryError> { self.completion .await .unwrap_or(Err(DeliveryError::CompletionLost)) } } impl TurnDeliveryService { pub fn new( coordinator: DeliveryCoordinator, channels: ChannelManager, supervisor: TaskSupervisor, ) -> Self { Self { coordinator, channels, supervisor, } } pub async fn start( &self, target: TurnTarget, snapshots: watch::Receiver>, ) -> Result { let channel = self .channels .get_channel(&target.channel) .await .ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?; let live_policy = channel.live_policy(); let presentation = channel.presentation_policy(); let sink = channel .open_turn(target.clone()) .await .map_err(DeliveryError::OpenFailed)?; let completion = self.coordinator.spawn_sink( &self.supervisor, SinkRoute { channel: target.channel, chat_id: target.chat_id, live_policy, presentation, }, snapshots, sink, )?; Ok(TurnDeliveryHandle { completion }) } pub async fn commit( &self, target: &TurnTarget, delta: CommittedTurnDelta, ) -> Result<(), DeliveryError> { let channel = self .channels .get_channel(&target.channel) .await .ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?; channel .commit_turn(target, delta) .await .map_err(DeliveryError::FinalFailed) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn handle_reports_terminal_delivery_failure() { let (sender, completion) = oneshot::channel(); sender.send(Err(DeliveryError::FinalTimedOut)).unwrap(); let result = TurnDeliveryHandle { completion }.wait().await; assert!(matches!(result, Err(DeliveryError::FinalTimedOut))); } #[tokio::test] async fn handle_reports_lost_delivery_task() { let (sender, completion) = oneshot::channel(); drop(sender); let result = TurnDeliveryHandle { completion }.wait().await; assert!(matches!(result, Err(DeliveryError::CompletionLost))); } }