From 1a5ae4e3ce87453de70b2d2380a990d917868690 Mon Sep 17 00:00:00 2001 From: Philippe Branchu Date: Sun, 22 Mar 2026 16:49:37 +0000 Subject: [PATCH] Prepend sender identity to channel messages for agent context The bridge now prefixes messages with [From: Name ] so agents know who is speaking. Essential for multi-user rooms and for agents that need to act on behalf of specific users (e.g., checking the correct email account or calendar). Updated bridge integration tests to match the new format. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/openfang-channels/src/bridge.rs | 18 +++++++++++++++- .../tests/bridge_integration_test.rs | 21 +++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs index cf1b383f..ffd908e9 100644 --- a/crates/openfang-channels/src/bridge.rs +++ b/crates/openfang-channels/src/bridge.rs @@ -890,8 +890,24 @@ async fn dispatch_message( // (which expire typing after ~5s) keep showing it during long LLM calls. let typing_task = spawn_typing_loop(adapter_arc.clone(), message.sender.clone()); + // Prepend sender context so the agent knows who is speaking. + // In group spaces this is essential for multi-user conversations. + let sender_name = &message.sender.display_name; + let sender_email = message + .metadata + .get("sender_email") + .and_then(|v| v.as_str()); + let prefixed_text = if !sender_name.is_empty() { + match sender_email { + Some(email) => format!("[From: {sender_name} <{email}>] {text}"), + None => format!("[From: {sender_name}] {text}"), + } + } else { + text.clone() + }; + // Send to agent and relay response - let result = handle.send_message(agent_id, &text).await; + let result = handle.send_message(agent_id, &prefixed_text).await; // Stop the typing refresh now that we have a response typing_task.abort(); diff --git a/crates/openfang-channels/tests/bridge_integration_test.rs b/crates/openfang-channels/tests/bridge_integration_test.rs index e4c64776..e954d4df 100644 --- a/crates/openfang-channels/tests/bridge_integration_test.rs +++ b/crates/openfang-channels/tests/bridge_integration_test.rs @@ -229,14 +229,23 @@ async fn test_bridge_dispatch_text_message() { let sent = adapter_ref.get_sent(); assert_eq!(sent.len(), 1, "Expected 1 response, got {}", sent.len()); assert_eq!(sent[0].0, "user1"); - assert_eq!(sent[0].1, "Echo: Hello agent!"); + // The bridge prepends sender identity: [From: Name] or [From: Name ] + assert!( + sent[0].1.contains("Hello agent!"), + "Response should contain original text, got: {}", + sent[0].1 + ); - // Verify: handle received the message + // Verify: handle received the message (with sender prefix) { let received = handle.received.lock().unwrap(); assert_eq!(received.len(), 1); assert_eq!(received[0].0, agent_id); - assert_eq!(received[0].1, "Hello agent!"); + assert!( + received[0].1.contains("Hello agent!"), + "Handle should receive text containing original message, got: {}", + received[0].1 + ); } manager.stop().await; @@ -486,7 +495,7 @@ async fn test_bridge_manager_lifecycle() { assert_eq!(sent.len(), 5, "Expected 5 responses, got {}", sent.len()); for (i, (_, text)) in sent.iter().enumerate() { - assert_eq!(*text, format!("Echo: message {i}")); + assert!(text.contains(&format!("message {i}")), "Expected 'message {i}' in: {text}"); } // Stop — should complete without hanging @@ -535,11 +544,11 @@ async fn test_bridge_multiple_adapters() { let tg_sent = tg_ref.get_sent(); assert_eq!(tg_sent.len(), 1); - assert_eq!(tg_sent[0].1, "Echo: from telegram"); + assert!(tg_sent[0].1.contains("from telegram"), "Expected 'from telegram' in: {}", tg_sent[0].1); let dc_sent = dc_ref.get_sent(); assert_eq!(dc_sent.len(), 1); - assert_eq!(dc_sent[0].1, "Echo: from discord"); + assert!(dc_sent[0].1.contains("from discord"), "Expected 'from discord' in: {}", dc_sent[0].1); manager.stop().await; }