// Created by: Claude // Date: 2026-01-04 // Purpose: Receive terminal output from QUIC stream // Refs: protocol_events_v_2.md, AGENT.md use crate::p2p::protocol::TerminalMessage; use quinn::{RecvStream, SendStream}; use anyhow::Result; use tracing::info; use tokio::io::AsyncReadExt; pub struct TerminalReceiver { has_control: bool, } impl TerminalReceiver { pub fn new() -> Self { Self { has_control: false, } } /// Receive and display terminal output pub async fn receive_output(&self, mut stream: RecvStream, mut on_output: F) -> Result<()> where F: FnMut(String), { loop { let msg = self.receive_message(&mut stream).await?; match msg { TerminalMessage::Output { data } => { on_output(data); } _ => { info!("Received terminal message: {:?}", msg); } } } } /// Send input to remote terminal (if has_control) pub async fn send_input(&self, stream: &mut SendStream, data: String) -> Result<()> { if !self.has_control { anyhow::bail!("Cannot send input: no control capability"); } let msg = TerminalMessage::Input { data }; self.send_message(stream, &msg).await } /// Send resize command pub async fn send_resize(&self, stream: &mut SendStream, cols: u16, rows: u16) -> Result<()> { let msg = TerminalMessage::Resize { cols, rows }; self.send_message(stream, &msg).await } pub fn grant_control(&mut self) { info!("Terminal control granted"); self.has_control = true; } pub fn revoke_control(&mut self) { info!("Terminal control revoked"); self.has_control = false; } async fn receive_message(&self, stream: &mut RecvStream) -> Result { let mut len_buf = [0u8; 4]; stream.read_exact(&mut len_buf).await?; let len = u32::from_be_bytes(len_buf) as usize; let mut buf = vec![0u8; len]; stream.read_exact(&mut buf).await?; Ok(serde_json::from_slice(&buf)?) } async fn send_message(&self, stream: &mut SendStream, msg: &TerminalMessage) -> Result<()> { use tokio::io::AsyncWriteExt; let json = serde_json::to_vec(msg)?; let len = (json.len() as u32).to_be_bytes(); stream.write_all(&len).await?; stream.write_all(&json).await?; Ok(()) } }