diff --git a/server/src/transport.rs b/server/src/chat/mod.rs similarity index 85% rename from server/src/transport.rs rename to server/src/chat/mod.rs index d977298..8ece371 100644 --- a/server/src/transport.rs +++ b/server/src/chat/mod.rs @@ -4,6 +4,8 @@ //! sending and editing messages, allowing the bot logic (commands, htop, //! notifications) to work against any chat platform — Matrix, WhatsApp, etc. +pub mod transport; + use async_trait::async_trait; /// A platform-agnostic identifier for a sent message. @@ -65,11 +67,11 @@ mod tests { #[test] fn whatsapp_transport_satisfies_trait() { fn assert_transport() {} - assert_transport::(); + assert_transport::(); // Verify it can be wrapped in Arc. let _: Arc = - Arc::new(crate::whatsapp::WhatsAppTransport::new( + Arc::new(crate::chat::transport::whatsapp::WhatsAppTransport::new( "test-phone".to_string(), "test-token".to_string(), "pipeline_notification".to_string(), @@ -81,7 +83,7 @@ mod tests { #[test] fn matrix_transport_is_send_sync() { fn assert_send_sync() {} - assert_send_sync::(); + assert_send_sync::(); } /// Verify that SlackTransport satisfies the ChatTransport trait and @@ -89,10 +91,10 @@ mod tests { #[test] fn slack_transport_satisfies_trait() { fn assert_transport() {} - assert_transport::(); + assert_transport::(); let _: Arc = - Arc::new(crate::slack::SlackTransport::new("xoxb-test".to_string())); + Arc::new(crate::chat::transport::slack::SlackTransport::new("xoxb-test".to_string())); } /// Verify that TwilioWhatsAppTransport satisfies the ChatTransport trait @@ -100,10 +102,10 @@ mod tests { #[test] fn twilio_transport_satisfies_trait() { fn assert_transport() {} - assert_transport::(); + assert_transport::(); let _: Arc = - Arc::new(crate::whatsapp::TwilioWhatsAppTransport::new( + Arc::new(crate::chat::transport::whatsapp::TwilioWhatsAppTransport::new( "ACtest".to_string(), "authtoken".to_string(), "+14155551234".to_string(), diff --git a/server/src/matrix/assign.rs b/server/src/chat/transport/matrix/assign.rs similarity index 100% rename from server/src/matrix/assign.rs rename to server/src/chat/transport/matrix/assign.rs diff --git a/server/src/matrix/bot.rs b/server/src/chat/transport/matrix/bot.rs similarity index 99% rename from server/src/matrix/bot.rs rename to server/src/chat/transport/matrix/bot.rs index c4db522..d424b57 100644 --- a/server/src/matrix/bot.rs +++ b/server/src/chat/transport/matrix/bot.rs @@ -2,7 +2,7 @@ use crate::agents::AgentPool; use crate::http::context::{PermissionDecision, PermissionForward}; use crate::llm::providers::claude_code::{ClaudeCodeProvider, ClaudeCodeResult}; use crate::slog; -use crate::transport::ChatTransport; +use crate::chat::ChatTransport; use matrix_sdk::{ Client, config::SyncSettings, @@ -371,14 +371,14 @@ pub async fn run_bot( "whatsapp" => { if config.whatsapp_provider == "twilio" { slog!("[matrix-bot] Using WhatsApp/Twilio transport"); - Arc::new(crate::whatsapp::TwilioWhatsAppTransport::new( + Arc::new(crate::chat::transport::whatsapp::TwilioWhatsAppTransport::new( config.twilio_account_sid.clone().unwrap_or_default(), config.twilio_auth_token.clone().unwrap_or_default(), config.twilio_whatsapp_number.clone().unwrap_or_default(), )) } else { slog!("[matrix-bot] Using WhatsApp/Meta transport"); - Arc::new(crate::whatsapp::WhatsAppTransport::new( + Arc::new(crate::chat::transport::whatsapp::WhatsAppTransport::new( config.whatsapp_phone_number_id.clone().unwrap_or_default(), config.whatsapp_access_token.clone().unwrap_or_default(), config @@ -1612,7 +1612,7 @@ mod tests { ambient_rooms: Arc::new(std::sync::Mutex::new(HashSet::new())), agents: Arc::new(AgentPool::new_test(3000)), htop_sessions: Arc::new(TokioMutex::new(HashMap::new())), - transport: Arc::new(crate::whatsapp::WhatsAppTransport::new("test-phone".to_string(), "test-token".to_string(), "pipeline_notification".to_string())), + transport: Arc::new(crate::chat::transport::whatsapp::WhatsAppTransport::new("test-phone".to_string(), "test-token".to_string(), "pipeline_notification".to_string())), }; // Clone must work (required by Matrix SDK event handler injection). let _cloned = ctx.clone(); diff --git a/server/src/matrix/commands/ambient.rs b/server/src/chat/transport/matrix/commands/ambient.rs similarity index 99% rename from server/src/matrix/commands/ambient.rs rename to server/src/chat/transport/matrix/commands/ambient.rs index e73b977..ff52cf4 100644 --- a/server/src/matrix/commands/ambient.rs +++ b/server/src/chat/transport/matrix/commands/ambient.rs @@ -1,7 +1,7 @@ //! Handler for the `ambient` command. use super::CommandContext; -use crate::matrix::config::save_ambient_rooms; +use crate::chat::transport::matrix::config::save_ambient_rooms; /// Toggle ambient mode for this room. /// diff --git a/server/src/matrix/commands/assign.rs b/server/src/chat/transport/matrix/commands/assign.rs similarity index 90% rename from server/src/matrix/commands/assign.rs rename to server/src/chat/transport/matrix/commands/assign.rs index 1954bba..a7f6d74 100644 --- a/server/src/matrix/commands/assign.rs +++ b/server/src/chat/transport/matrix/commands/assign.rs @@ -1,6 +1,6 @@ //! Handler stub for the `assign` command. //! -//! The real implementation lives in `crate::matrix::assign` (async). This +//! The real implementation lives in `crate::chat::transport::matrix::assign` (async). This //! stub exists only so that `assign` appears in the help registry — the //! handler always returns `None` so the bot's message loop falls through to //! the async handler in `bot.rs`. @@ -8,7 +8,7 @@ use super::CommandContext; pub(super) fn handle_assign(_ctx: &CommandContext) -> Option { - // Handled asynchronously in bot.rs / crate::matrix::assign. + // Handled asynchronously in bot.rs / crate::chat::transport::matrix::assign. None } diff --git a/server/src/matrix/commands/cost.rs b/server/src/chat/transport/matrix/commands/cost.rs similarity index 100% rename from server/src/matrix/commands/cost.rs rename to server/src/chat/transport/matrix/commands/cost.rs diff --git a/server/src/matrix/commands/git.rs b/server/src/chat/transport/matrix/commands/git.rs similarity index 100% rename from server/src/matrix/commands/git.rs rename to server/src/chat/transport/matrix/commands/git.rs diff --git a/server/src/matrix/commands/help.rs b/server/src/chat/transport/matrix/commands/help.rs similarity index 100% rename from server/src/matrix/commands/help.rs rename to server/src/chat/transport/matrix/commands/help.rs diff --git a/server/src/matrix/commands/mod.rs b/server/src/chat/transport/matrix/commands/mod.rs similarity index 99% rename from server/src/matrix/commands/mod.rs rename to server/src/chat/transport/matrix/commands/mod.rs index 8bbdd57..a27cedc 100644 --- a/server/src/matrix/commands/mod.rs +++ b/server/src/chat/transport/matrix/commands/mod.rs @@ -40,7 +40,7 @@ pub struct BotCommand { /// message body. /// /// All identifiers are platform-agnostic strings so this struct works with -/// any [`ChatTransport`](crate::transport::ChatTransport) implementation. +/// any [`ChatTransport`](crate::chat::ChatTransport) implementation. pub struct CommandDispatch<'a> { /// The bot's display name (e.g., "Timmy"). pub bot_name: &'a str, diff --git a/server/src/matrix/commands/move_story.rs b/server/src/chat/transport/matrix/commands/move_story.rs similarity index 100% rename from server/src/matrix/commands/move_story.rs rename to server/src/chat/transport/matrix/commands/move_story.rs diff --git a/server/src/matrix/commands/overview.rs b/server/src/chat/transport/matrix/commands/overview.rs similarity index 100% rename from server/src/matrix/commands/overview.rs rename to server/src/chat/transport/matrix/commands/overview.rs diff --git a/server/src/matrix/commands/show.rs b/server/src/chat/transport/matrix/commands/show.rs similarity index 100% rename from server/src/matrix/commands/show.rs rename to server/src/chat/transport/matrix/commands/show.rs diff --git a/server/src/matrix/commands/status.rs b/server/src/chat/transport/matrix/commands/status.rs similarity index 100% rename from server/src/matrix/commands/status.rs rename to server/src/chat/transport/matrix/commands/status.rs diff --git a/server/src/matrix/commands/triage.rs b/server/src/chat/transport/matrix/commands/triage.rs similarity index 100% rename from server/src/matrix/commands/triage.rs rename to server/src/chat/transport/matrix/commands/triage.rs diff --git a/server/src/matrix/config.rs b/server/src/chat/transport/matrix/config.rs similarity index 100% rename from server/src/matrix/config.rs rename to server/src/chat/transport/matrix/config.rs diff --git a/server/src/matrix/delete.rs b/server/src/chat/transport/matrix/delete.rs similarity index 100% rename from server/src/matrix/delete.rs rename to server/src/chat/transport/matrix/delete.rs diff --git a/server/src/matrix/htop.rs b/server/src/chat/transport/matrix/htop.rs similarity index 99% rename from server/src/matrix/htop.rs rename to server/src/chat/transport/matrix/htop.rs index 045ade3..243c971 100644 --- a/server/src/matrix/htop.rs +++ b/server/src/chat/transport/matrix/htop.rs @@ -14,7 +14,7 @@ use tokio::sync::{Mutex as TokioMutex, watch}; use crate::agents::{AgentPool, AgentStatus}; use crate::slog; -use crate::transport::ChatTransport; +use crate::chat::ChatTransport; use super::bot::markdown_to_html; diff --git a/server/src/matrix/mod.rs b/server/src/chat/transport/matrix/mod.rs similarity index 100% rename from server/src/matrix/mod.rs rename to server/src/chat/transport/matrix/mod.rs diff --git a/server/src/matrix/notifications.rs b/server/src/chat/transport/matrix/notifications.rs similarity index 99% rename from server/src/matrix/notifications.rs rename to server/src/chat/transport/matrix/notifications.rs index f178406..cadaae6 100644 --- a/server/src/matrix/notifications.rs +++ b/server/src/chat/transport/matrix/notifications.rs @@ -6,7 +6,7 @@ use crate::io::story_metadata::parse_front_matter; use crate::io::watcher::WatcherEvent; use crate::slog; -use crate::transport::ChatTransport; +use crate::chat::ChatTransport; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -265,7 +265,7 @@ pub fn spawn_notification_listener( mod tests { use super::*; use async_trait::async_trait; - use crate::transport::MessageId; + use crate::chat::MessageId; // ── MockTransport ─────────────────────────────────────────────────────── @@ -284,7 +284,7 @@ mod tests { } #[async_trait] - impl crate::transport::ChatTransport for MockTransport { + impl crate::chat::ChatTransport for MockTransport { async fn send_message(&self, room_id: &str, plain: &str, html: &str) -> Result { self.calls.lock().unwrap().push((room_id.to_string(), plain.to_string(), html.to_string())); Ok("mock-msg-id".to_string()) diff --git a/server/src/matrix/rebuild.rs b/server/src/chat/transport/matrix/rebuild.rs similarity index 100% rename from server/src/matrix/rebuild.rs rename to server/src/chat/transport/matrix/rebuild.rs diff --git a/server/src/matrix/reset.rs b/server/src/chat/transport/matrix/reset.rs similarity index 95% rename from server/src/matrix/reset.rs rename to server/src/chat/transport/matrix/reset.rs index 558c140..afe5c93 100644 --- a/server/src/matrix/reset.rs +++ b/server/src/chat/transport/matrix/reset.rs @@ -5,7 +5,7 @@ //! with clean context. File-system memories (auto-memory directory) are not //! affected — only the in-memory/persisted conversation state is cleared. -use crate::matrix::bot::{ConversationHistory, RoomConversation}; +use crate::chat::transport::matrix::bot::{ConversationHistory, RoomConversation}; use matrix_sdk::ruma::OwnedRoomId; use std::path::Path; @@ -52,7 +52,7 @@ pub async fn handle_reset( let conv = guard.entry(room_id.clone()).or_insert_with(RoomConversation::default); conv.session_id = None; conv.entries.clear(); - crate::matrix::bot::save_history(project_root, &guard); + crate::chat::transport::matrix::bot::save_history(project_root, &guard); } crate::slog!("[matrix-bot] reset command: cleared session for room {room_id} (bot={bot_name})"); "Session reset. Starting fresh — previous context has been cleared.".to_string() @@ -138,7 +138,7 @@ mod tests { #[tokio::test] async fn handle_reset_clears_session_and_entries() { - use crate::matrix::bot::{ConversationEntry, ConversationRole}; + use crate::chat::transport::matrix::bot::{ConversationEntry, ConversationRole}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex as TokioMutex; diff --git a/server/src/matrix/rmtree.rs b/server/src/chat/transport/matrix/rmtree.rs similarity index 100% rename from server/src/matrix/rmtree.rs rename to server/src/chat/transport/matrix/rmtree.rs diff --git a/server/src/matrix/start.rs b/server/src/chat/transport/matrix/start.rs similarity index 98% rename from server/src/matrix/start.rs rename to server/src/chat/transport/matrix/start.rs index 465d3c1..637bb9e 100644 --- a/server/src/matrix/start.rs +++ b/server/src/chat/transport/matrix/start.rs @@ -356,14 +356,14 @@ mod tests { #[test] fn start_command_is_registered() { - use crate::matrix::commands::commands; + use crate::chat::transport::matrix::commands::commands; let found = commands().iter().any(|c| c.name == "start"); assert!(found, "start command must be in the registry"); } #[test] fn start_command_appears_in_help() { - let result = crate::matrix::commands::tests::try_cmd_addressed( + let result = crate::chat::transport::matrix::commands::tests::try_cmd_addressed( "Timmy", "@timmy:homeserver.local", "@timmy help", @@ -378,7 +378,7 @@ mod tests { #[test] fn start_command_falls_through_to_none_in_registry() { // The start handler in the registry returns None (handled async in bot.rs). - let result = crate::matrix::commands::tests::try_cmd_addressed( + let result = crate::chat::transport::matrix::commands::tests::try_cmd_addressed( "Timmy", "@timmy:homeserver.local", "@timmy start 42", diff --git a/server/src/matrix/transport_impl.rs b/server/src/chat/transport/matrix/transport_impl.rs similarity index 98% rename from server/src/matrix/transport_impl.rs rename to server/src/chat/transport/matrix/transport_impl.rs index 8e30ed1..9b8cee0 100644 --- a/server/src/matrix/transport_impl.rs +++ b/server/src/chat/transport/matrix/transport_impl.rs @@ -10,7 +10,7 @@ use matrix_sdk::ruma::events::room::message::{ ReplacementMetadata, RoomMessageEventContent, RoomMessageEventContentWithoutRelation, }; -use crate::transport::{ChatTransport, MessageId}; +use crate::chat::{ChatTransport, MessageId}; /// Matrix-backed [`ChatTransport`] implementation. /// diff --git a/server/src/chat/transport/mod.rs b/server/src/chat/transport/mod.rs new file mode 100644 index 0000000..a863087 --- /dev/null +++ b/server/src/chat/transport/mod.rs @@ -0,0 +1,3 @@ +pub mod matrix; +pub mod slack; +pub mod whatsapp; diff --git a/server/src/slack.rs b/server/src/chat/transport/slack.rs similarity index 97% rename from server/src/slack.rs rename to server/src/chat/transport/slack.rs index de6beb6..cad7293 100644 --- a/server/src/slack.rs +++ b/server/src/chat/transport/slack.rs @@ -14,9 +14,9 @@ use std::sync::Arc; use tokio::sync::Mutex as TokioMutex; use crate::agents::AgentPool; -use crate::matrix::{ConversationEntry, ConversationRole, RoomConversation}; +use crate::chat::transport::matrix::{ConversationEntry, ConversationRole, RoomConversation}; use crate::slog; -use crate::transport::{ChatTransport, MessageId}; +use crate::chat::{ChatTransport, MessageId}; // ── Slack API base URL (overridable for tests) ────────────────────────── @@ -669,7 +669,7 @@ pub async fn slash_command_receive( format!("{} {keyword} {}", ctx.bot_name, payload.text) }; - use crate::matrix::commands::{CommandDispatch, try_handle_command}; + use crate::chat::transport::matrix::commands::{CommandDispatch, try_handle_command}; let dispatch = CommandDispatch { bot_name: &ctx.bot_name, @@ -701,7 +701,7 @@ async fn handle_incoming_message( user: &str, message: &str, ) { - use crate::matrix::commands::{CommandDispatch, try_handle_command}; + use crate::chat::transport::matrix::commands::{CommandDispatch, try_handle_command}; let dispatch = CommandDispatch { bot_name: &ctx.bot_name, @@ -721,12 +721,12 @@ async fn handle_incoming_message( } // Check for async commands (htop, delete). - if let Some(htop_cmd) = crate::matrix::htop::extract_htop_command( + if let Some(htop_cmd) = crate::chat::transport::matrix::htop::extract_htop_command( message, &ctx.bot_name, &ctx.bot_user_id, ) { - use crate::matrix::htop::HtopCommand; + use crate::chat::transport::matrix::htop::HtopCommand; slog!("[slack] Handling htop command from {user} in {channel}"); match htop_cmd { HtopCommand::Stop => { @@ -738,7 +738,7 @@ async fn handle_incoming_message( HtopCommand::Start { duration_secs } => { // On Slack, htop uses native message editing for live updates. let snapshot = - crate::matrix::htop::build_htop_message(&ctx.agents, 0, duration_secs); + crate::chat::transport::matrix::htop::build_htop_message(&ctx.agents, 0, duration_secs); let msg_id = match ctx.transport.send_message(channel, &snapshot, "").await { Ok(id) => id, Err(e) => { @@ -755,7 +755,7 @@ async fn handle_incoming_message( let total_ticks = (duration_secs as usize) / 2; for tick in 1..=total_ticks { tokio::time::sleep(interval).await; - let updated = crate::matrix::htop::build_htop_message( + let updated = crate::chat::transport::matrix::htop::build_htop_message( &agents, (tick * 2) as u32, duration_secs, @@ -773,15 +773,15 @@ async fn handle_incoming_message( return; } - if let Some(del_cmd) = crate::matrix::delete::extract_delete_command( + if let Some(del_cmd) = crate::chat::transport::matrix::delete::extract_delete_command( message, &ctx.bot_name, &ctx.bot_user_id, ) { let response = match del_cmd { - crate::matrix::delete::DeleteCommand::Delete { story_number } => { + crate::chat::transport::matrix::delete::DeleteCommand::Delete { story_number } => { slog!("[slack] Handling delete command from {user}: story {story_number}"); - crate::matrix::delete::handle_delete( + crate::chat::transport::matrix::delete::handle_delete( &ctx.bot_name, &story_number, &ctx.project_root, @@ -789,7 +789,7 @@ async fn handle_incoming_message( ) .await } - crate::matrix::delete::DeleteCommand::BadArgs => { + crate::chat::transport::matrix::delete::DeleteCommand::BadArgs => { format!("Usage: `{} delete `", ctx.bot_name) } }; @@ -810,7 +810,7 @@ async fn handle_llm_message( user_message: &str, ) { use crate::llm::providers::claude_code::{ClaudeCodeProvider, ClaudeCodeResult}; - use crate::matrix::drain_complete_paragraphs; + use crate::chat::transport::matrix::drain_complete_paragraphs; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::watch; @@ -1408,7 +1408,7 @@ mod tests { fn slash_command_dispatches_through_command_registry() { // Verify that the synthetic message built by the slash handler // correctly dispatches through try_handle_command. - use crate::matrix::commands::{CommandDispatch, try_handle_command}; + use crate::chat::transport::matrix::commands::{CommandDispatch, try_handle_command}; let agents = test_agents(); let ambient_rooms = test_ambient_rooms(); @@ -1435,7 +1435,7 @@ mod tests { #[test] fn slash_command_show_passes_args_through_registry() { - use crate::matrix::commands::{CommandDispatch, try_handle_command}; + use crate::chat::transport::matrix::commands::{CommandDispatch, try_handle_command}; let agents = test_agents(); let ambient_rooms = test_ambient_rooms(); diff --git a/server/src/whatsapp.rs b/server/src/chat/transport/whatsapp.rs similarity index 98% rename from server/src/whatsapp.rs rename to server/src/chat/transport/whatsapp.rs index 3681877..12d9caa 100644 --- a/server/src/whatsapp.rs +++ b/server/src/chat/transport/whatsapp.rs @@ -14,9 +14,9 @@ use std::sync::Arc; use tokio::sync::Mutex as TokioMutex; use crate::agents::AgentPool; -use crate::matrix::{ConversationEntry, ConversationRole, RoomConversation}; +use crate::chat::transport::matrix::{ConversationEntry, ConversationRole, RoomConversation}; use crate::slog; -use crate::transport::{ChatTransport, MessageId}; +use crate::chat::{ChatTransport, MessageId}; // ── API base URLs (overridable for tests) ──────────────────────────────── @@ -908,7 +908,7 @@ async fn handle_incoming_message( sender: &str, message: &str, ) { - use crate::matrix::commands::{CommandDispatch, try_handle_command}; + use crate::chat::transport::matrix::commands::{CommandDispatch, try_handle_command}; // Record this inbound message to keep the 24-hour window open. ctx.window_tracker.record_message(sender); @@ -931,12 +931,12 @@ async fn handle_incoming_message( } // Check for async commands (htop, delete). - if let Some(htop_cmd) = crate::matrix::htop::extract_htop_command( + if let Some(htop_cmd) = crate::chat::transport::matrix::htop::extract_htop_command( message, &ctx.bot_name, &ctx.bot_user_id, ) { - use crate::matrix::htop::HtopCommand; + use crate::chat::transport::matrix::htop::HtopCommand; slog!("[whatsapp] Handling htop command from {sender}"); match htop_cmd { HtopCommand::Stop => { @@ -951,7 +951,7 @@ async fn handle_incoming_message( // On WhatsApp, send a single snapshot instead of a live-updating // dashboard since we can't edit messages. let snapshot = - crate::matrix::htop::build_htop_message(&ctx.agents, 0, duration_secs); + crate::chat::transport::matrix::htop::build_htop_message(&ctx.agents, 0, duration_secs); let _ = ctx .transport .send_message(sender, &snapshot, "") @@ -961,15 +961,15 @@ async fn handle_incoming_message( return; } - if let Some(del_cmd) = crate::matrix::delete::extract_delete_command( + if let Some(del_cmd) = crate::chat::transport::matrix::delete::extract_delete_command( message, &ctx.bot_name, &ctx.bot_user_id, ) { let response = match del_cmd { - crate::matrix::delete::DeleteCommand::Delete { story_number } => { + crate::chat::transport::matrix::delete::DeleteCommand::Delete { story_number } => { slog!("[whatsapp] Handling delete command from {sender}: story {story_number}"); - crate::matrix::delete::handle_delete( + crate::chat::transport::matrix::delete::handle_delete( &ctx.bot_name, &story_number, &ctx.project_root, @@ -977,7 +977,7 @@ async fn handle_incoming_message( ) .await } - crate::matrix::delete::DeleteCommand::BadArgs => { + crate::chat::transport::matrix::delete::DeleteCommand::BadArgs => { format!("Usage: `{} delete `", ctx.bot_name) } }; @@ -997,7 +997,7 @@ async fn handle_llm_message( user_message: &str, ) { use crate::llm::providers::claude_code::{ClaudeCodeProvider, ClaudeCodeResult}; - use crate::matrix::drain_complete_paragraphs; + use crate::chat::transport::matrix::drain_complete_paragraphs; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::watch; diff --git a/server/src/http/bot_command.rs b/server/src/http/bot_command.rs index 6f03bcb..d8dbc51 100644 --- a/server/src/http/bot_command.rs +++ b/server/src/http/bot_command.rs @@ -10,7 +10,7 @@ //! (it clears local session state and message history) and is not routed here. use crate::http::context::{AppContext, OpenApiResult}; -use crate::matrix::commands::CommandDispatch; +use crate::chat::transport::matrix::commands::CommandDispatch; use poem::http::StatusCode; use poem_openapi::{Object, OpenApi, Tags, payload::Json}; use serde::{Deserialize, Serialize}; @@ -112,7 +112,7 @@ fn dispatch_sync( format!("{bot_name} {cmd} {args}") }; - match crate::matrix::commands::try_handle_command(&dispatch, &synthetic) { + match crate::chat::transport::matrix::commands::try_handle_command(&dispatch, &synthetic) { Some(response) => response, None => { // Command exists in the registry but its fallback handler returns None @@ -138,7 +138,7 @@ async fn dispatch_assign( return "Usage: `/assign ` (e.g. `/assign 42 opus`)".to_string(); } - crate::matrix::assign::handle_assign("web-ui", number_str, model_str, project_root, agents) + crate::chat::transport::matrix::assign::handle_assign("web-ui", number_str, model_str, project_root, agents) .await } @@ -163,7 +163,7 @@ async fn dispatch_start( Some(hint_str) }; - crate::matrix::start::handle_start("web-ui", number_str, agent_hint, project_root, agents) + crate::chat::transport::matrix::start::handle_start("web-ui", number_str, agent_hint, project_root, agents) .await } @@ -176,14 +176,14 @@ async fn dispatch_delete( if number_str.is_empty() || !number_str.chars().all(|c| c.is_ascii_digit()) { return "Usage: `/delete ` (e.g. `/delete 42`)".to_string(); } - crate::matrix::delete::handle_delete("web-ui", number_str, project_root, agents).await + crate::chat::transport::matrix::delete::handle_delete("web-ui", number_str, project_root, agents).await } async fn dispatch_rebuild( project_root: &std::path::Path, agents: &Arc, ) -> String { - crate::matrix::rebuild::handle_rebuild("web-ui", project_root, agents).await + crate::chat::transport::matrix::rebuild::handle_rebuild("web-ui", project_root, agents).await } // --------------------------------------------------------------------------- diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index 63893d4..aac08d3 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -31,8 +31,8 @@ use settings::SettingsApi; use std::path::{Path, PathBuf}; use std::sync::Arc; -use crate::slack::SlackWebhookContext; -use crate::whatsapp::WhatsAppWebhookContext; +use crate::chat::transport::slack::SlackWebhookContext; +use crate::chat::transport::whatsapp::WhatsAppWebhookContext; const DEFAULT_PORT: u16 = 3001; @@ -85,8 +85,8 @@ pub fn build_routes( if let Some(wa_ctx) = whatsapp_ctx { route = route.at( "/webhook/whatsapp", - get(crate::whatsapp::webhook_verify) - .post(crate::whatsapp::webhook_receive) + get(crate::chat::transport::whatsapp::webhook_verify) + .post(crate::chat::transport::whatsapp::webhook_receive) .data(wa_ctx), ); } @@ -95,11 +95,11 @@ pub fn build_routes( route = route .at( "/webhook/slack", - post(crate::slack::webhook_receive).data(sl_ctx.clone()), + post(crate::chat::transport::slack::webhook_receive).data(sl_ctx.clone()), ) .at( "/webhook/slack/command", - post(crate::slack::slash_command_receive).data(sl_ctx), + post(crate::chat::transport::slack::slash_command_receive).data(sl_ctx), ); } diff --git a/server/src/main.rs b/server/src/main.rs index aa3bcee..7b42520 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -9,13 +9,10 @@ mod http; mod io; mod llm; pub mod log_buffer; -mod matrix; +mod chat; pub mod rebuild; -pub mod slack; mod state; mod store; -pub mod transport; -pub mod whatsapp; mod workflow; mod worktree; @@ -267,15 +264,15 @@ async fn main() -> Result<(), std::io::Error> { let agents_for_shutdown = Arc::clone(&agents); // Build WhatsApp webhook context if bot.toml configures transport = "whatsapp". - let whatsapp_ctx: Option> = startup_root + let whatsapp_ctx: Option> = startup_root .as_ref() - .and_then(|root| matrix::BotConfig::load(root)) + .and_then(|root| chat::transport::matrix::BotConfig::load(root)) .filter(|cfg| cfg.transport == "whatsapp") .map(|cfg| { let provider = cfg.whatsapp_provider.clone(); - let transport: Arc = + let transport: Arc = if provider == "twilio" { - Arc::new(whatsapp::TwilioWhatsAppTransport::new( + Arc::new(chat::transport::whatsapp::TwilioWhatsAppTransport::new( cfg.twilio_account_sid.clone().unwrap_or_default(), cfg.twilio_auth_token.clone().unwrap_or_default(), cfg.twilio_whatsapp_number.clone().unwrap_or_default(), @@ -285,7 +282,7 @@ async fn main() -> Result<(), std::io::Error> { .whatsapp_notification_template .clone() .unwrap_or_else(|| "pipeline_notification".to_string()); - Arc::new(whatsapp::WhatsAppTransport::new( + Arc::new(chat::transport::whatsapp::WhatsAppTransport::new( cfg.whatsapp_phone_number_id.clone().unwrap_or_default(), cfg.whatsapp_access_token.clone().unwrap_or_default(), template_name, @@ -296,8 +293,8 @@ async fn main() -> Result<(), std::io::Error> { .clone() .unwrap_or_else(|| "Assistant".to_string()); let root = startup_root.clone().unwrap(); - let history = whatsapp::load_whatsapp_history(&root); - Arc::new(whatsapp::WhatsAppWebhookContext { + let history = chat::transport::whatsapp::load_whatsapp_history(&root); + Arc::new(chat::transport::whatsapp::WhatsAppWebhookContext { verify_token: cfg.whatsapp_verify_token.clone().unwrap_or_default(), provider, transport, @@ -308,17 +305,17 @@ async fn main() -> Result<(), std::io::Error> { ambient_rooms: Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())), history: std::sync::Arc::new(tokio::sync::Mutex::new(history)), history_size: cfg.history_size, - window_tracker: Arc::new(whatsapp::MessagingWindowTracker::new()), + window_tracker: Arc::new(chat::transport::whatsapp::MessagingWindowTracker::new()), }) }); // Build Slack webhook context if bot.toml configures transport = "slack". - let slack_ctx: Option> = startup_root + let slack_ctx: Option> = startup_root .as_ref() - .and_then(|root| matrix::BotConfig::load(root)) + .and_then(|root| chat::transport::matrix::BotConfig::load(root)) .filter(|cfg| cfg.transport == "slack") .map(|cfg| { - let transport = Arc::new(slack::SlackTransport::new( + let transport = Arc::new(chat::transport::slack::SlackTransport::new( cfg.slack_bot_token.clone().unwrap_or_default(), )); let bot_name = cfg @@ -326,10 +323,10 @@ async fn main() -> Result<(), std::io::Error> { .clone() .unwrap_or_else(|| "Assistant".to_string()); let root = startup_root.clone().unwrap(); - let history = slack::load_slack_history(&root); + let history = chat::transport::slack::load_slack_history(&root); let channel_ids: std::collections::HashSet = cfg.slack_channel_ids.iter().cloned().collect(); - Arc::new(slack::SlackWebhookContext { + Arc::new(chat::transport::slack::SlackWebhookContext { signing_secret: cfg.slack_signing_secret.clone().unwrap_or_default(), transport, project_root: root, @@ -353,7 +350,7 @@ async fn main() -> Result<(), std::io::Error> { if let Some(ref ctx) = slack_ctx { let channels: Vec = ctx.channel_ids.iter().cloned().collect(); Some(Arc::new(BotShutdownNotifier::new( - Arc::clone(&ctx.transport) as Arc, + Arc::clone(&ctx.transport) as Arc, channels, ctx.bot_name.clone(), ))) @@ -362,7 +359,7 @@ async fn main() -> Result<(), std::io::Error> { }; // Retain a reference to the WhatsApp context for shutdown notifications. // At shutdown time we read ambient_rooms to get the current set of active senders. - let whatsapp_ctx_for_shutdown: Option> = + let whatsapp_ctx_for_shutdown: Option> = whatsapp_ctx.clone(); // Watch channel: signals the Matrix bot task to send a shutdown announcement. @@ -391,7 +388,7 @@ async fn main() -> Result<(), std::io::Error> { // Optional Matrix bot: connect to the homeserver and start listening for // messages if `.storkit/bot.toml` is present and enabled. if let Some(ref root) = startup_root { - matrix::spawn_bot( + chat::transport::matrix::spawn_bot( root, watcher_tx_for_bot, perm_rx_for_bot, @@ -446,7 +443,7 @@ async fn main() -> Result<(), std::io::Error> { let rooms: Vec = ctx.ambient_rooms.lock().unwrap().iter().cloned().collect(); if !rooms.is_empty() { let wa_notifier = BotShutdownNotifier::new( - Arc::clone(&ctx.transport) as Arc, + Arc::clone(&ctx.transport) as Arc, rooms, ctx.bot_name.clone(), ); diff --git a/server/src/rebuild.rs b/server/src/rebuild.rs index 596267b..db6f19a 100644 --- a/server/src/rebuild.rs +++ b/server/src/rebuild.rs @@ -2,7 +2,7 @@ use crate::agents::AgentPool; use crate::slog; -use crate::transport::ChatTransport; +use crate::chat::ChatTransport; use std::path::Path; use std::sync::Arc; @@ -186,7 +186,7 @@ pub async fn rebuild_and_restart( mod tests { use super::*; use async_trait::async_trait; - use crate::transport::MessageId; + use crate::chat::MessageId; use std::sync::Mutex; /// In-memory transport that records sent messages.