use std::collections::HashMap;
use std::sync::RwLock;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq)]
pub enum SubAgentState {
Running,
AwaitingInput,
Completed,
Failed(String),
Cancelled,
}
pub struct SubAgent {
pub id: String,
pub label: String,
pub session_id: Uuid,
pub state: SubAgentState,
pub cancel_token: CancellationToken,
pub join_handle: Option<JoinHandle<()>>,
pub input_tx: Option<mpsc::UnboundedSender<String>>,
pub output: Option<String>,
pub spawned_at: chrono::DateTime<chrono::Utc>,
}
pub struct SubAgentManager {
agents: RwLock<HashMap<String, SubAgent>>,
}
impl SubAgentManager {
pub fn new() -> Self {
Self {
agents: RwLock::new(HashMap::new()),
}
}
pub fn generate_id() -> String {
Uuid::new_v4().to_string()[..8].to_string()
}
pub fn insert(&self, agent: SubAgent) {
let id = agent.id.clone();
self.agents
.write()
.expect("subagent manager lock poisoned")
.insert(id, agent);
}
pub fn get_state(&self, id: &str) -> Option<SubAgentState> {
self.agents
.read()
.expect("subagent manager lock poisoned")
.get(id)
.map(|a| a.state.clone())
}
pub fn get_output(&self, id: &str) -> Option<String> {
self.agents
.read()
.expect("subagent manager lock poisoned")
.get(id)
.and_then(|a| a.output.clone())
}
pub fn get_input_tx(&self, id: &str) -> Option<mpsc::UnboundedSender<String>> {
self.agents
.read()
.expect("subagent manager lock poisoned")
.get(id)
.and_then(|a| a.input_tx.clone())
}
pub fn cancel(&self, id: &str) -> bool {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id)
&& matches!(
agent.state,
SubAgentState::Running | SubAgentState::AwaitingInput
)
{
agent.cancel_token.cancel();
agent.state = SubAgentState::Cancelled;
agent.input_tx = None;
return true;
}
false
}
pub fn take_join_handle(&self, id: &str) -> Option<JoinHandle<()>> {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
agents.get_mut(id).and_then(|a| a.join_handle.take())
}
pub fn update_output(&self, id: &str, output: String) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id) {
agent.output = Some(output);
}
}
pub fn mark_awaiting_input(&self, id: &str) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id)
&& agent.state == SubAgentState::Running
{
agent.state = SubAgentState::AwaitingInput;
}
}
pub fn mark_running_again(&self, id: &str) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id)
&& agent.state == SubAgentState::AwaitingInput
{
agent.state = SubAgentState::Running;
}
}
pub fn mark_completed(&self, id: &str, output: String) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id) {
agent.state = SubAgentState::Completed;
agent.output = Some(output);
agent.input_tx = None;
}
}
pub fn mark_failed(&self, id: &str, error: String) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id) {
agent.state = SubAgentState::Failed(error);
agent.input_tx = None;
}
}
pub fn prepare_resume(
&self,
id: &str,
cancel_token: CancellationToken,
input_tx: mpsc::UnboundedSender<String>,
) -> bool {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id)
&& matches!(
agent.state,
SubAgentState::Completed | SubAgentState::Failed(_)
)
{
agent.state = SubAgentState::Running;
agent.cancel_token = cancel_token;
agent.input_tx = Some(input_tx);
agent.output = None;
return true;
}
false
}
pub fn set_join_handle(&self, id: &str, handle: JoinHandle<()>) {
let mut agents = self.agents.write().expect("subagent manager lock poisoned");
if let Some(agent) = agents.get_mut(id) {
agent.join_handle = Some(handle);
}
}
pub fn list(&self) -> Vec<(String, String, SubAgentState)> {
self.agents
.read()
.expect("subagent manager lock poisoned")
.values()
.map(|a| (a.id.clone(), a.label.clone(), a.state.clone()))
.collect()
}
pub fn exists(&self, id: &str) -> bool {
self.agents
.read()
.expect("subagent manager lock poisoned")
.contains_key(id)
}
pub fn get_session_id(&self, id: &str) -> Option<Uuid> {
self.agents
.read()
.expect("subagent manager lock poisoned")
.get(id)
.map(|a| a.session_id)
}
pub fn remove(&self, id: &str) -> Option<SubAgent> {
self.agents
.write()
.expect("subagent manager lock poisoned")
.remove(id)
}
}
impl Default for SubAgentManager {
fn default() -> Self {
Self::new()
}
}