pub mod coordinator;
pub mod mailbox;
use echo_core::agent::Agent;
use futures::lock::Mutex as AsyncMutex;
use std::collections::HashMap;
use std::sync::Arc;
use tracing::info;
use super::types::SubagentDefinition;
use coordinator::TeamCoordinator;
use mailbox::Mailbox;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TeamRole {
Leader,
Worker,
Reviewer,
}
#[derive(Debug, Clone)]
pub struct TeamConfig {
pub max_concurrent: usize,
pub default_timeout_secs: u64,
pub allow_reassignment: bool,
pub cross_talk: bool,
pub mailbox_capacity: usize,
}
impl Default for TeamConfig {
fn default() -> Self {
Self {
max_concurrent: 5,
default_timeout_secs: 300,
allow_reassignment: true,
cross_talk: false,
mailbox_capacity: 64,
}
}
}
pub struct TeamMember {
pub name: String,
pub role: TeamRole,
pub agent: Arc<AsyncMutex<Box<dyn Agent>>>,
pub mailbox: Mailbox,
pub definition: SubagentDefinition,
}
pub struct Team {
pub id: String,
pub name: String,
pub config: TeamConfig,
members: HashMap<String, TeamMember>,
pub coordinator: TeamCoordinator,
}
impl Team {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
leader_name: &str,
config: TeamConfig,
) -> Self {
let id = id.into();
let leader = leader_name.to_string();
Self {
id,
name: name.into(),
config,
members: HashMap::new(),
coordinator: TeamCoordinator::new(&leader),
}
}
pub fn add_member(
&mut self,
name: &str,
role: TeamRole,
agent: Box<dyn Agent>,
definition: SubagentDefinition,
) {
info!(team = %self.name, member = %name, role = ?role, "Adding team member");
let mailbox = Mailbox::with_capacity(self.config.mailbox_capacity);
self.members.insert(
name.to_string(),
TeamMember {
name: name.to_string(),
role,
agent: Arc::new(AsyncMutex::new(agent)),
mailbox,
definition,
},
);
}
pub fn get_member(&self, name: &str) -> Option<&TeamMember> {
self.members.get(name)
}
pub fn get_mailbox_sender(&self, name: &str) -> Option<mailbox::MailboxSender> {
self.members.get(name).map(|m| m.mailbox.sender())
}
pub fn member_names(&self) -> Vec<String> {
self.members.keys().cloned().collect()
}
pub fn worker_names(&self) -> Vec<String> {
self.members
.iter()
.filter(|(_, m)| m.role == TeamRole::Worker)
.map(|(name, _)| name.clone())
.collect()
}
pub fn len(&self) -> usize {
self.members.len()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub fn members(&self) -> impl Iterator<Item = &TeamMember> {
self.members.values()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::MockAgent;
#[test]
fn test_team_new() {
let team = Team::new("t1", "Test Team", "leader", TeamConfig::default());
assert_eq!(team.id, "t1");
assert_eq!(team.name, "Test Team");
assert!(team.is_empty());
}
#[test]
fn test_team_add_members() {
let mut team = Team::new("t1", "Team", "leader", TeamConfig::default());
team.add_member(
"leader",
TeamRole::Leader,
Box::new(MockAgent::new("leader")),
SubagentDefinition::simple_sync("leader"),
);
team.add_member(
"worker1",
TeamRole::Worker,
Box::new(MockAgent::new("worker1")),
SubagentDefinition::simple_sync("worker1"),
);
assert_eq!(team.len(), 2);
assert_eq!(team.worker_names(), vec!["worker1"]);
}
#[test]
fn test_team_get_member() {
let mut team = Team::new("t1", "Team", "leader", TeamConfig::default());
team.add_member(
"w",
TeamRole::Worker,
Box::new(MockAgent::new("w")),
SubagentDefinition::simple_sync("w"),
);
assert!(team.get_member("w").is_some());
assert!(team.get_member("missing").is_none());
}
#[tokio::test]
async fn test_team_mailbox_sender() {
let mut team = Team::new("t1", "Team", "leader", TeamConfig::default());
team.add_member(
"w",
TeamRole::Worker,
Box::new(MockAgent::new("w")),
SubagentDefinition::simple_sync("w"),
);
let sender = team.get_mailbox_sender("w").unwrap();
let result = sender
.send(mailbox::MailboxMessage::new(
"leader",
"w",
mailbox::MessageKind::Status {
message: "ok".into(),
},
))
.await;
assert!(result.is_ok());
}
}