Skip to main content

telos_agent/tools/builtin/
team_delete.rs

1//! `TeamDelete` tool — clean up a collaboration team.
2//!
3//! Removes the team config file and shared task directory. Fails if there are
4//! still active (non-lead) team members to prevent data loss.
5
6use async_trait::async_trait;
7use serde_json::{Value, json};
8
9use crate::error::AgentError;
10use crate::orchestration::team::{self, has_active_members, load_team_config};
11use crate::tools::api::{Tool, ToolContext, ToolDefinition, ToolOutput};
12
13use super::team_create::SharedTeamContext;
14
15/// `TeamDelete` — tear down the current team.
16pub struct TeamDeleteTool {
17    team_ctx: SharedTeamContext,
18}
19
20impl TeamDeleteTool {
21    pub fn new(team_ctx: SharedTeamContext) -> Self {
22        Self { team_ctx }
23    }
24}
25
26#[async_trait]
27impl Tool for TeamDeleteTool {
28    fn definition(&self) -> ToolDefinition {
29        ToolDefinition {
30            name: "TeamDelete".into(),
31            description: "Clean up the current collaboration team. Removes team config and \
32shared task directory. Fails if teammates are still active — ask them to finish \
33or stop them first. Use this when the team's work is complete."
34                .into(),
35            input_schema: json!({"type": "object", "properties": {}, "additionalProperties": false}),
36        }
37    }
38
39    fn prompt_text(&self) -> Option<&'static str> {
40        Some(TEAM_DELETE_PROMPT)
41    }
42
43    async fn invoke(
44        &self,
45        _arguments: Value,
46        _context: ToolContext,
47    ) -> Result<ToolOutput, AgentError> {
48        let team_name = {
49            let ctx = self.team_ctx.lock().unwrap();
50            match &ctx.team_name {
51                Some(name) => name.clone(),
52                None => {
53                    return Ok(ToolOutput::json(json!({
54                        "success": true,
55                        "message": "No active team to clean up."
56                    })));
57                }
58            }
59        };
60
61        // Load and validate before cleanup.
62        match load_team_config(&team_name) {
63            Ok(config) => {
64                if has_active_members(&config) {
65                    let active: Vec<_> = config
66                        .members
67                        .iter()
68                        .filter(|m| m.is_active && m.agent_id != config.lead_agent_id)
69                        .map(|m| m.name.clone())
70                        .collect();
71                    return Err(AgentError::Validation(format!(
72                        "Cannot clean up team with {} active member(s): {}. \
73Stop or wait for them to finish, then retry.",
74                        active.len(),
75                        active.join(", ")
76                    )));
77                }
78            }
79            Err(_) => {
80                // Config not found — maybe already cleaned up? Proceed anyway.
81            }
82        }
83
84        team::cleanup_team(&team_name)?;
85
86        {
87            let mut ctx = self.team_ctx.lock().unwrap();
88            ctx.team_name = None;
89            ctx.lead_agent_id = None;
90        }
91
92        Ok(ToolOutput::json(json!({
93            "success": true,
94            "team_name": team_name,
95            "message": format!("Team '{team_name}' cleaned up successfully.")
96        })))
97    }
98
99    fn is_concurrency_safe(&self, _: &Value) -> bool {
100        true
101    }
102}
103
104const TEAM_DELETE_PROMPT: &str = r#"TeamDelete cleans up a completed collaboration team.
105
106Use when all tasks are done or the user signals completion. Fails if active teammates remain — stop them first. Removes team config and shared task directory."#;