@@ -91,6 +91,13 @@ impl<C: Channel + ?Sized + 'static> WhatsAppChannel<C> {
9191 ) ;
9292 }
9393 Err ( e) => {
94+ telemetry:: reply_failed (
95+ "whatsapp" ,
96+ recipient,
97+ "reply" ,
98+ start. elapsed ( ) . as_millis ( ) as u64 ,
99+ & e,
100+ ) ;
94101 warn ! ( recipient = %recipient, error = %e, "WhatsApp: failed to send reply" ) ;
95102 }
96103 }
@@ -123,7 +130,7 @@ impl<C: Channel + ?Sized + 'static> WhatsAppChannel<C> {
123130
124131 telemetry:: authorized_message ( "whatsapp" , & identity. id , & from, text. len ( ) , delivery_lag_ms) ;
125132
126- let chat_key = format ! ( "whatsapp-{}" , identity. id) ;
133+ let chat_key = conversation_chat_key ( & identity. id , & reply_target ) ;
127134
128135 if self . scan_enabled ( ) {
129136 let verdict = self
@@ -386,6 +393,10 @@ impl<C: Channel + ?Sized + 'static> WhatsAppChannel<C> {
386393 }
387394}
388395
396+ fn conversation_chat_key ( identity_id : & str , reply_target : & str ) -> String {
397+ format ! ( "whatsapp-{identity_id}-{reply_target}" )
398+ }
399+
389400const MIGRATION_TOML : & str = r#"
390401[[channels]]
391402kind = "whatsapp"
@@ -803,6 +814,99 @@ mod tests {
803814 assert_eq ! ( sent[ 0 ] . recipient, "12345@g.us" ) ;
804815 }
805816
817+ #[ tokio:: test]
818+ async fn test_group_targets_do_not_share_context_between_agents ( ) {
819+ let mut config = ( * make_test_config ( |_| { } ) ) . clone ( ) ;
820+ config. agents = vec ! [
821+ AgentConfig {
822+ id: "librarian" . to_string( ) ,
823+ kind: "artifact-cli" . to_string( ) ,
824+ command: Some ( "/bin/sh" . to_string( ) ) ,
825+ args: Some ( vec![ "-c" . to_string( ) , "cat" . to_string( ) ] ) ,
826+ ..Default :: default ( )
827+ } ,
828+ AgentConfig {
829+ id: "critic" . to_string( ) ,
830+ kind: "artifact-cli" . to_string( ) ,
831+ command: Some ( "/bin/sh" . to_string( ) ) ,
832+ args: Some ( vec![ "-c" . to_string( ) , "cat" . to_string( ) ] ) ,
833+ ..Default :: default ( )
834+ } ,
835+ ] ;
836+ config. routing [ 0 ] . allowed_agents = vec ! [ "librarian" . to_string( ) , "critic" . to_string( ) ] ;
837+ let config = Arc :: new ( config) ;
838+ let transport = Arc :: new ( MockChannel :: new ( ) ) ;
839+ let bridge = dummy_bridge_with ( config, transport. clone ( ) ) ;
840+
841+ bridge
842+ . bridge
843+ . clone ( )
844+ . handle_message ( ChannelMessage {
845+ id : "1" . into ( ) ,
846+ sender : "+15555550100" . into ( ) ,
847+ reply_target : "group-a@g.us" . into ( ) ,
848+ content : "alpha private context" . into ( ) ,
849+ channel : "whatsapp" . into ( ) ,
850+ timestamp : 0 ,
851+ thread_ts : None ,
852+ interruption_scope_id : None ,
853+ attachments : vec ! [ ] ,
854+ } )
855+ . await ;
856+ transport. wait_for_sent_len ( 1 ) . await ;
857+ let first = transport. drain ( ) ;
858+ assert_eq ! ( first[ 0 ] . recipient, "group-a@g.us" ) ;
859+ assert ! ( first[ 0 ] . content. contains( "alpha private context" ) ) ;
860+
861+ bridge
862+ . bridge
863+ . clone ( )
864+ . handle_message ( ChannelMessage {
865+ id : "2" . into ( ) ,
866+ sender : "+15555550100" . into ( ) ,
867+ reply_target : "group-b@g.us" . into ( ) ,
868+ content : "!switch critic" . into ( ) ,
869+ channel : "whatsapp" . into ( ) ,
870+ timestamp : 0 ,
871+ thread_ts : None ,
872+ interruption_scope_id : None ,
873+ attachments : vec ! [ ] ,
874+ } )
875+ . await ;
876+ transport. wait_for_sent_len ( 1 ) . await ;
877+ let switch_reply = transport. drain ( ) ;
878+ assert_eq ! ( switch_reply[ 0 ] . recipient, "group-b@g.us" ) ;
879+
880+ bridge
881+ . bridge
882+ . handle_message ( ChannelMessage {
883+ id : "3" . into ( ) ,
884+ sender : "+15555550100" . into ( ) ,
885+ reply_target : "group-b@g.us" . into ( ) ,
886+ content : "beta fresh prompt" . into ( ) ,
887+ channel : "whatsapp" . into ( ) ,
888+ timestamp : 0 ,
889+ thread_ts : None ,
890+ interruption_scope_id : None ,
891+ attachments : vec ! [ ] ,
892+ } )
893+ . await ;
894+ transport. wait_for_sent_len ( 1 ) . await ;
895+ let second = transport. drain ( ) ;
896+ assert_eq ! ( second[ 0 ] . recipient, "group-b@g.us" ) ;
897+ assert ! ( second[ 0 ] . content. contains( "beta fresh prompt" ) ) ;
898+ assert ! (
899+ !second[ 0 ] . content. contains( "alpha private context" ) ,
900+ "group B must not receive group A context: {}" ,
901+ second[ 0 ] . content
902+ ) ;
903+ assert ! (
904+ !second[ 0 ] . content. contains( "[Recent context:" ) ,
905+ "new group/agent pair should start without another group's preamble: {}" ,
906+ second[ 0 ] . content
907+ ) ;
908+ }
909+
806910 #[ tokio:: test]
807911 async fn test_handle_message_renders_artifact_fallback ( ) {
808912 let mut config = ( * make_test_config ( |_| { } ) ) . clone ( ) ;
0 commit comments