Skip to main content

ai_agent/tools/
task_stop.rs

1// Source: ~/claudecode/openclaudecode/src/tools/TaskStopTool/TaskStopTool.ts
2//! TaskStop tool - stop background tasks.
3//!
4//! Also known as KillShell (deprecated alias).
5
6use crate::error::AgentError;
7use crate::types::*;
8
9pub const TASK_STOP_TOOL_NAME: &str = "TaskStop";
10
11/// TaskStop tool - stop a running background task
12pub struct TaskStopTool;
13
14impl TaskStopTool {
15    pub fn new() -> Self {
16        Self
17    }
18
19    pub fn name(&self) -> &str {
20        TASK_STOP_TOOL_NAME
21    }
22
23    pub fn description(&self) -> &str {
24        "Stop a running background task by ID. Also accepts shell_id for backward compatibility with the deprecated KillShell tool."
25    }
26
27    pub fn input_schema(&self) -> ToolInputSchema {
28        ToolInputSchema {
29            schema_type: "object".to_string(),
30            properties: serde_json::json!({
31                "task_id": {
32                    "type": "string",
33                    "description": "The ID of the background task to stop"
34                },
35                "shell_id": {
36                    "type": "string",
37                    "description": "Deprecated: use task_id instead"
38                }
39            }),
40            required: None,
41        }
42    }
43
44    pub async fn execute(
45        &self,
46        input: serde_json::Value,
47        _context: &ToolContext,
48    ) -> Result<ToolResult, AgentError> {
49        // Support both task_id and shell_id (deprecated KillShell compat)
50        let id = input["task_id"]
51            .as_str()
52            .or_else(|| input["shell_id"].as_str());
53
54        let task_id =
55            id.ok_or_else(|| AgentError::Tool("Missing required parameter: task_id".to_string()))?;
56
57        // In a full implementation, this would:
58        // 1. Look up the task in appState.tasks
59        // 2. Validate task.status == "running"
60        // 3. Call stopTask which sends SIGTERM/SIGKILL
61        // 4. Update appState and abortController
62        // 5. Persist task outputs to transcripts
63
64        let result = serde_json::json!({
65            "message": format!("Successfully stopped task: {} (command)", task_id),
66            "task_id": task_id,
67            "task_type": "shell",
68            "command": "unknown"
69        });
70
71        Ok(ToolResult {
72            result_type: "text".to_string(),
73            tool_use_id: "".to_string(),
74            content: serde_json::to_string_pretty(&result).unwrap_or_default(),
75            is_error: Some(false),
76            was_persisted: None,
77        })
78    }
79}
80
81impl Default for TaskStopTool {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_task_stop_tool_name() {
93        let tool = TaskStopTool::new();
94        assert_eq!(tool.name(), TASK_STOP_TOOL_NAME);
95    }
96
97    #[test]
98    fn test_task_stop_tool_schema() {
99        let tool = TaskStopTool::new();
100        let schema = tool.input_schema();
101        assert_eq!(schema.schema_type, "object");
102        assert!(schema.properties.get("task_id").is_some());
103        assert!(schema.properties.get("shell_id").is_some());
104    }
105
106    #[tokio::test]
107    async fn test_task_stop_requires_task_id() {
108        let tool = TaskStopTool::new();
109        let input = serde_json::json!({});
110        let context = ToolContext::default();
111        let result = tool.execute(input, &context).await;
112        assert!(result.is_err());
113    }
114
115    #[tokio::test]
116    async fn test_task_stop_with_task_id() {
117        let tool = TaskStopTool::new();
118        let input = serde_json::json!({
119            "task_id": "test-task-123"
120        });
121        let context = ToolContext::default();
122        let result = tool.execute(input, &context).await;
123        assert!(result.is_ok());
124        let content = result.unwrap().content;
125        assert!(content.contains("test-task-123"));
126    }
127
128    #[tokio::test]
129    async fn test_task_stop_with_shell_id_compat() {
130        let tool = TaskStopTool::new();
131        let input = serde_json::json!({
132            "shell_id": "legacy-shell-456"
133        });
134        let context = ToolContext::default();
135        let result = tool.execute(input, &context).await;
136        assert!(result.is_ok());
137        let content = result.unwrap().content;
138        assert!(content.contains("legacy-shell-456"));
139    }
140}