use crate::command::chat::storage::{ChatMessage, MessageRole, TeammateSnapshotPersist};
use crate::util::log::write_info_log;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{
Arc, Mutex, OnceLock,
atomic::{AtomicBool, AtomicUsize, Ordering},
};
use tokio_util::sync::CancellationToken;
#[derive(Clone, Debug, PartialEq)]
pub enum TeammateStatus {
Initializing,
Working,
WaitingForMessage,
Completed,
Cancelled,
Error(String),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TeammateStatusPersist {
Initializing,
Working,
WaitingForMessage,
Completed,
Cancelled,
Error(String),
}
impl From<TeammateStatus> for TeammateStatusPersist {
fn from(status: TeammateStatus) -> Self {
match status {
TeammateStatus::Initializing => Self::Initializing,
TeammateStatus::Working => Self::Working,
TeammateStatus::WaitingForMessage => Self::WaitingForMessage,
TeammateStatus::Completed => Self::Completed,
TeammateStatus::Cancelled => Self::Cancelled,
TeammateStatus::Error(e) => Self::Error(e),
}
}
}
impl From<TeammateStatusPersist> for TeammateStatus {
fn from(status: TeammateStatusPersist) -> Self {
match status {
TeammateStatusPersist::Initializing => Self::Initializing,
TeammateStatusPersist::Working => Self::Working,
TeammateStatusPersist::WaitingForMessage => Self::WaitingForMessage,
TeammateStatusPersist::Completed => Self::Completed,
TeammateStatusPersist::Cancelled => Self::Cancelled,
TeammateStatusPersist::Error(e) => Self::Error(e),
}
}
}
impl TeammateStatus {
pub fn icon(&self) -> &'static str {
match self {
Self::Initializing => "◐",
Self::Working => "●",
Self::WaitingForMessage => "○",
Self::Completed => "✓",
Self::Cancelled => "✗",
Self::Error(_) => "✗",
}
}
pub fn label(&self) -> &'static str {
match self {
Self::Initializing => "初始化",
Self::Working => "工作中",
Self::WaitingForMessage => "等待中",
Self::Completed => "已完成",
Self::Cancelled => "已取消",
Self::Error(_) => "错误",
}
}
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Completed | Self::Cancelled | Self::Error(_))
}
}
#[derive(Clone, Debug)]
pub struct TeammateSnapshot {
pub name: String,
pub role: String,
pub status: TeammateStatus,
pub current_tool: Option<String>,
pub tool_calls_count: usize,
}
static GLOBAL_FILE_LOCKS: OnceLock<Mutex<HashMap<PathBuf, String>>> = OnceLock::new();
fn global_file_locks() -> &'static Mutex<HashMap<PathBuf, String>> {
GLOBAL_FILE_LOCKS.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn acquire_global_file_lock(
path: &std::path::Path,
agent_name: &str,
) -> Result<FileLockGuard, String> {
let canonical = path.to_path_buf();
let mut map = global_file_locks()
.lock()
.map_err(|_| "file_locks mutex poisoned".to_string())?;
if let Some(holder) = map.get(&canonical)
&& holder != agent_name
{
return Err(holder.clone());
}
map.insert(canonical.clone(), agent_name.to_string());
Ok(FileLockGuard {
path: canonical,
agent_name: agent_name.to_string(),
})
}
#[allow(dead_code)]
pub struct TeammateHandle {
pub name: String,
pub role: String,
pub pending_user_messages: Arc<Mutex<Vec<ChatMessage>>>,
pub streaming_content: Arc<Mutex<String>>,
pub cancel_token: CancellationToken,
pub is_running: Arc<AtomicBool>,
pub thread_handle: Option<std::thread::JoinHandle<()>>,
pub system_prompt_snapshot: Arc<Mutex<String>>,
pub messages_snapshot: Arc<Mutex<Vec<ChatMessage>>>,
pub status: Arc<Mutex<TeammateStatus>>,
pub tool_calls_count: Arc<AtomicUsize>,
pub current_tool: Arc<Mutex<Option<String>>>,
pub wake_flag: Arc<AtomicBool>,
pub work_done: Arc<AtomicBool>,
}
#[allow(dead_code)]
impl TeammateHandle {
pub fn running(&self) -> bool {
self.is_running.load(Ordering::Relaxed)
}
pub fn cancel(&self) {
self.cancel_token.cancel();
}
}
pub struct FileLockGuard {
path: PathBuf,
agent_name: String,
}
impl Drop for FileLockGuard {
fn drop(&mut self) {
if let Ok(mut map) = global_file_locks().lock()
&& map.get(&self.path).map(|s| s.as_str()) == Some(self.agent_name.as_str())
{
map.remove(&self.path);
}
}
}
#[allow(dead_code)]
pub struct TeammateManager {
pub teammates: HashMap<String, TeammateHandle>,
pub main_agent_inbox: Arc<Mutex<Vec<ChatMessage>>>,
pub ui_messages: Arc<Mutex<Vec<ChatMessage>>>,
recovered_teammates: HashMap<String, TeammateSnapshotPersist>,
}
#[allow(dead_code)]
impl TeammateManager {
pub fn new(
main_agent_inbox: Arc<Mutex<Vec<ChatMessage>>>,
ui_messages: Arc<Mutex<Vec<ChatMessage>>>,
) -> Self {
Self {
teammates: HashMap::new(),
main_agent_inbox,
ui_messages,
recovered_teammates: HashMap::new(),
}
}
pub fn broadcast(&self, from: &str, text: &str, at_target: Option<&str>) {
let broadcast_message = if let Some(target) = at_target {
format!("<{}> @{} {}", from, target, text)
} else {
format!("<{}> {}", from, text)
};
write_info_log(
"TeammateManager",
&format!(
"broadcast from={}: {}",
from,
&broadcast_message[..{
let mut b = broadcast_message.len().min(100);
while b > 0 && !broadcast_message.is_char_boundary(b) {
b -= 1;
}
b
}]
),
);
if from != "Main"
&& let Ok(mut pending) = self.main_agent_inbox.lock()
{
pending.push(ChatMessage::text(MessageRole::User, &broadcast_message));
}
for (name, handle) in &self.teammates {
if name == from {
continue; }
if let Ok(mut pending) = handle.pending_user_messages.lock() {
pending.push(ChatMessage::text(MessageRole::User, &broadcast_message));
}
let should_wake = from == "Main" || at_target == Some(name.as_str());
if should_wake {
handle.wake_flag.store(true, Ordering::Relaxed);
}
}
if from != "Main"
&& let Ok(mut shared) = self.ui_messages.lock()
{
shared.push(ChatMessage::text(
MessageRole::Assistant,
&broadcast_message,
));
}
}
pub fn team_summary(&self) -> String {
if self.teammates.is_empty() && self.recovered_teammates.is_empty() {
return String::new();
}
let mut summary = String::from("## Teammates\n\n当前团队成员:\n");
summary.push_str("- Main (主协调者)\n");
for (name, handle) in &self.teammates {
let status = handle
.status
.lock()
.map(|status_val| format!("{} {}", status_val.icon(), status_val.label()))
.unwrap_or_else(|_| {
if handle.running() {
"● 工作中".to_string()
} else {
"○ 空闲".to_string()
}
});
summary.push_str(&format!("- {} ({}) [{}]\n", name, handle.role, status));
}
for (name, snapshot) in &self.recovered_teammates {
let status: TeammateStatus = snapshot.status.clone().into();
summary.push_str(&format!(
"- {} ({}) [{} 🔄session-recovery]\n",
name,
snapshot.role,
status.label()
));
}
summary.push_str(
"\n使用 SendMessage 工具向其他 agent 发送消息。可以用 @AgentName 指定目标。\n",
);
summary
}
pub fn all_names(&self) -> Vec<String> {
let mut names = vec!["Main".to_string()];
names.extend(self.teammates.keys().cloned());
names
}
pub fn teammate_snapshots(&self) -> Vec<TeammateSnapshot> {
self.teammates
.iter()
.map(|(name, handle)| {
let status = handle
.status
.lock()
.map(|s| s.clone())
.unwrap_or(TeammateStatus::Initializing);
let current_tool = handle.current_tool.lock().ok().and_then(|t| t.clone());
let tool_calls_count = handle.tool_calls_count.load(Ordering::Relaxed);
TeammateSnapshot {
name: name.clone(),
role: handle.role.clone(),
status,
current_tool,
tool_calls_count,
}
})
.collect()
}
pub fn stop_teammate(&mut self, name: &str) {
if let Some(handle) = self.teammates.get(name) {
handle.cancel();
write_info_log("TeammateManager", &format!("stopped teammate: {}", name));
}
}
pub fn stop_all(&mut self) {
for (name, handle) in &self.teammates {
handle.cancel();
write_info_log("TeammateManager", &format!("stopping teammate: {}", name));
}
}
pub fn cleanup_finished(&mut self) {
let finished: Vec<String> = self
.teammates
.iter()
.filter(|(_, h)| {
!h.running()
&& h.thread_handle
.as_ref()
.map(|t| t.is_finished())
.unwrap_or(true)
})
.map(|(name, _)| name.clone())
.collect();
for name in finished {
if let Some(mut handle) = self.teammates.remove(&name) {
if let Some(thread) = handle.thread_handle.take() {
let _ = thread.join();
}
write_info_log("TeammateManager", &format!("cleaned up teammate: {}", name));
}
}
}
pub fn register_teammate(&mut self, handle: TeammateHandle) {
write_info_log(
"TeammateManager",
&format!("registered teammate: {} ({})", handle.name, handle.role),
);
self.teammates.insert(handle.name.clone(), handle);
}
}
impl Default for TeammateManager {
fn default() -> Self {
Self {
teammates: HashMap::new(),
main_agent_inbox: Arc::new(Mutex::new(Vec::new())),
ui_messages: Arc::new(Mutex::new(Vec::new())),
recovered_teammates: HashMap::new(),
}
}
}
impl TeammateManager {
pub fn set_recovered_teammates(&mut self, teammates: Vec<TeammateSnapshotPersist>) {
self.recovered_teammates = teammates.into_iter().map(|t| (t.name.clone(), t)).collect();
}
pub fn clear_recovered_teammates(&mut self) {
self.recovered_teammates.clear();
}
pub fn recovered_teammates_snapshot(&self) -> HashMap<String, TeammateSnapshotPersist> {
self.recovered_teammates.clone()
}
#[allow(dead_code)]
pub fn recovered_teammates_list(&self) -> Vec<(String, String, TeammateStatusPersist)> {
self.recovered_teammates
.iter()
.map(|(name, t)| (name.clone(), t.role.clone(), t.status.clone()))
.collect()
}
pub fn get_recovered_teammate(&self, name: &str) -> Option<TeammateSnapshotPersist> {
self.recovered_teammates.get(name).cloned()
}
pub fn remove_recovered_teammate(&mut self, name: &str) {
self.recovered_teammates.remove(name);
}
}