Prepend sender identity to channel messages for agent context

The bridge now prefixes messages with [From: Name <email>] 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) <noreply@anthropic.com>
This commit is contained in:
Philippe Branchu
2026-03-22 16:49:37 +00:00
parent db86ff4ce3
commit 1a5ae4e3ce
2 changed files with 32 additions and 7 deletions

View File

@@ -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();

View File

@@ -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 <email>]
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;
}