Skip to main content

matrixcode_core/tools/
todo_write.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6use crate::approval::RiskLevel;
7
8pub struct TodoWriteTool;
9
10#[async_trait]
11impl Tool for TodoWriteTool {
12    fn definition(&self) -> ToolDefinition {
13        ToolDefinition {
14            name: "todo_write".to_string(),
15            description:
16                "维护结构化待办列表,用于规划和跟踪多步骤工作。\
17                 每次调用会替换整个列表。用于非 trivial 任务(3 步以上)以展示进度。\
18                 同时只保持一个任务 'in_progress',完成后立即标记为 'completed'。"
19                    .to_string(),
20            parameters: json!({
21                "type": "object",
22                "properties": {
23                    "todos": {
24                        "type": "array",
25                        "description": "待办项的完整替换列表",
26                        "items": {
27                            "type": "object",
28                            "properties": {
29                                "content": {
30                                    "type": "string",
31                                    "description": "命令式描述(如 '运行测试')"
32                                },
33                                "activeForm": {
34                                    "type": "string",
35                                    "description": "进行式描述(如 '正在运行测试')"
36                                },
37                                "status": {
38                                    "type": "string",
39                                    "enum": ["pending", "in_progress", "completed"]
40                                }
41                            },
42                            "required": ["content", "activeForm", "status"]
43                        }
44                    }
45                },
46                "required": ["todos"]
47            }),
48        }
49    }
50
51    async fn execute(&self, params: Value) -> Result<String> {
52        let todos = params["todos"]
53            .as_array()
54            .ok_or_else(|| anyhow::anyhow!("missing 'todos' array"))?;
55
56        // Show spinner while updating todo list - RAII guard ensures cleanup on error
57        // let mut spinner = ToolSpinner::new(&format!("updating todos ({} items)", todos.len()));
58
59        let mut in_progress_count = 0;
60        let _completed_count = 0;
61        let _pending_count = 0;
62        let mut lines = Vec::with_capacity(todos.len() + 1);
63
64        for (i, todo) in todos.iter().enumerate() {
65            let content = todo["content"]
66                .as_str()
67                .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'content'", i))?;
68            let active = todo["activeForm"]
69                .as_str()
70                .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'activeForm'", i))?;
71            let status = todo["status"]
72                .as_str()
73                .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'status'", i))?;
74
75            let (marker, display) = match status {
76                "pending" => ("[ ]", content),
77                "in_progress" => {
78                    in_progress_count += 1;
79                    ("[~]", active)
80                }
81                "completed" => ("[x]", content),
82                other => anyhow::bail!("todo {}: invalid status '{}'", i, other),
83            };
84
85            lines.push(format!("  {} {}", marker, display));
86        }
87
88        if in_progress_count > 1 {
89            // spinner.finish_error("multiple in_progress");
90            anyhow::bail!(
91                "at most one task may be 'in_progress' at a time (found {})",
92                in_progress_count
93            );
94        }
95
96        if lines.is_empty() {
97            // spinner.finish_success("cleared");
98            return Ok("Todo list cleared.".to_string());
99        }
100
101        let header = format!("Todos ({}):", lines.len());
102        let mut out = String::with_capacity(header.len() + lines.iter().map(|l| l.len() + 1).sum::<usize>());
103        out.push_str(&header);
104        for l in &lines {
105            out.push('\n');
106            out.push_str(l);
107        }
108
109        // spinner.finish_success(&format!(
110//     "{} pending, {} in_progress, {} done",
111//     pending_count, in_progress_count, completed_count
112// ));
113        Ok(out)
114    }
115
116    fn risk_level(&self) -> RiskLevel {
117        RiskLevel::Mutating
118    }
119}