From 24b1aa6e7f16bb8ef6e30c3d4acc9e436bb2245f Mon Sep 17 00:00:00 2001 From: dave Date: Thu, 26 Mar 2026 20:49:23 +0000 Subject: [PATCH] storkit: merge 401_bug_whatsapp_and_slack_missing_start_command_handler --- server/src/chat/transport/slack.rs | 68 +++++++++++++++++++++++++++ server/src/chat/transport/whatsapp.rs | 67 ++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/server/src/chat/transport/slack.rs b/server/src/chat/transport/slack.rs index d6701088..baf1f87b 100644 --- a/server/src/chat/transport/slack.rs +++ b/server/src/chat/transport/slack.rs @@ -885,6 +885,35 @@ async fn handle_incoming_message( return; } + if let Some(start_cmd) = crate::chat::transport::matrix::start::extract_start_command( + message, + &ctx.bot_name, + &ctx.bot_user_id, + ) { + let response = match start_cmd { + crate::chat::transport::matrix::start::StartCommand::Start { + story_number, + agent_hint, + } => { + slog!("[slack] Handling start command from {user} in {channel}: story {story_number}"); + crate::chat::transport::matrix::start::handle_start( + &ctx.bot_name, + &story_number, + agent_hint.as_deref(), + &ctx.project_root, + &ctx.agents, + ) + .await + } + crate::chat::transport::matrix::start::StartCommand::BadArgs => { + format!("Usage: `{} start `", ctx.bot_name) + } + }; + let response = markdown_to_slack(&response); + let _ = ctx.transport.send_message(channel, &response, "").await; + return; + } + // No command matched — forward to LLM for conversational response. slog!("[slack] No command matched, forwarding to LLM for {user} in {channel}"); handle_llm_message(ctx, channel, user, message).await; @@ -1806,4 +1835,43 @@ mod tests { assert!(conv.session_id.is_none(), "session_id should be cleared"); assert!(conv.entries.is_empty(), "entries should be cleared"); } + + #[test] + fn start_command_extracted_from_plain_slack_message() { + // Slack messages may arrive without a bot mention prefix. + // extract_start_command must recognise "start 42" by itself. + let result = crate::chat::transport::matrix::start::extract_start_command( + "start 42", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_some(), "plain 'start 42' should be recognised"); + assert_eq!( + result, + Some(crate::chat::transport::matrix::start::StartCommand::Start { + story_number: "42".to_string(), + agent_hint: None, + }) + ); + } + + #[test] + fn start_command_extracted_with_bot_name_prefix_slack() { + let result = crate::chat::transport::matrix::start::extract_start_command( + "Timmy start 99", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_some(), "'Timmy start 99' should be recognised"); + } + + #[test] + fn non_start_slack_message_not_extracted() { + let result = crate::chat::transport::matrix::start::extract_start_command( + "help", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_none(), "'help' should not be recognised as start"); + } } diff --git a/server/src/chat/transport/whatsapp.rs b/server/src/chat/transport/whatsapp.rs index 805654ea..27aedccd 100644 --- a/server/src/chat/transport/whatsapp.rs +++ b/server/src/chat/transport/whatsapp.rs @@ -1146,6 +1146,34 @@ async fn handle_incoming_message(ctx: &WhatsAppWebhookContext, sender: &str, mes return; } + if let Some(start_cmd) = crate::chat::transport::matrix::start::extract_start_command( + message, + &ctx.bot_name, + &ctx.bot_user_id, + ) { + let response = match start_cmd { + crate::chat::transport::matrix::start::StartCommand::Start { + story_number, + agent_hint, + } => { + slog!("[whatsapp] Handling start command from {sender}: story {story_number}"); + crate::chat::transport::matrix::start::handle_start( + &ctx.bot_name, + &story_number, + agent_hint.as_deref(), + &ctx.project_root, + &ctx.agents, + ) + .await + } + crate::chat::transport::matrix::start::StartCommand::BadArgs => { + format!("Usage: `{} start `", ctx.bot_name) + } + }; + let _ = ctx.transport.send_message(sender, &response, "").await; + return; + } + // No command matched — forward to LLM for conversational response. slog!("[whatsapp] No command matched, forwarding to LLM for {sender}"); handle_llm_message(ctx, sender, message).await; @@ -2208,4 +2236,43 @@ mod tests { assert!(conv.session_id.is_none(), "session_id should be cleared"); assert!(conv.entries.is_empty(), "entries should be cleared"); } + + #[test] + fn start_command_extracted_from_plain_message() { + // WhatsApp messages arrive without a bot mention prefix. + // extract_start_command must recognise "start 42" by itself. + let result = crate::chat::transport::matrix::start::extract_start_command( + "start 42", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_some(), "plain 'start 42' should be recognised"); + assert_eq!( + result, + Some(crate::chat::transport::matrix::start::StartCommand::Start { + story_number: "42".to_string(), + agent_hint: None, + }) + ); + } + + #[test] + fn start_command_extracted_with_bot_name_prefix() { + let result = crate::chat::transport::matrix::start::extract_start_command( + "Timmy start 99", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_some(), "'Timmy start 99' should be recognised"); + } + + #[test] + fn non_start_whatsapp_message_not_extracted() { + let result = crate::chat::transport::matrix::start::extract_start_command( + "help", + "Timmy", + "@timmy:home.local", + ); + assert!(result.is_none(), "'help' should not be recognised as start"); + } }