Skip to main content

j_agent/tools/task/
task_manager.rs

1use super::entity::AgentTask;
2use crate::permission::JcliConfig;
3use crate::storage::{load_tasks_state, sessions_dir};
4use crate::util::safe_lock;
5use serde_json::Value;
6use std::fs;
7use std::path::PathBuf;
8use std::sync::Mutex;
9
10// ========== TaskManager ==========
11
12/// 任务管理器,负责 CRUD 操作和持久化
13#[derive(Debug)]
14pub struct TaskManager {
15    tasks_dir: PathBuf,
16    write_lock: Mutex<()>,
17}
18
19impl Default for TaskManager {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl TaskManager {
26    pub fn new() -> Self {
27        // 优先使用 .jcli/tasks/,找不到则在 cwd 下创建 .jcli/tasks/
28        let config_dir = JcliConfig::find_config_dir().or_else(JcliConfig::ensure_config_dir);
29        let tasks_dir = match config_dir {
30            Some(dir) => dir.join("tasks"),
31            None => {
32                // 极端 fallback:使用全局目录
33                let data_dir = crate::constants::data_root();
34                data_dir.join("agent").join("data").join("tasks")
35            }
36        };
37        let _ = fs::create_dir_all(&tasks_dir);
38        Self {
39            tasks_dir,
40            write_lock: Mutex::new(()),
41        }
42    }
43
44    /// 创建 session 级 TaskManager(存储到 sessions/{id}/tasks.json)
45    pub fn new_with_session(session_id: &str) -> Self {
46        let sdir = sessions_dir();
47        let session_dir = sdir.join(session_id);
48        let tasks_dir = session_dir.join("tasks_session");
49        let _ = fs::create_dir_all(&tasks_dir);
50        let mgr = Self {
51            tasks_dir,
52            write_lock: Mutex::new(()),
53        };
54        // 如果有 session 级 tasks.json,从中加载
55        if let Some(tasks) = load_tasks_state(session_id) {
56            mgr.replace_all(tasks);
57        }
58        mgr
59    }
60
61    /// 生成下一个任务 ID(基于已有文件的最大 ID + 1)
62    fn next_id(&self) -> u64 {
63        let mut max_id: u64 = 0;
64        if let Ok(entries) = fs::read_dir(&self.tasks_dir) {
65            for entry in entries.flatten() {
66                let name = entry.file_name();
67                let name = name.to_string_lossy();
68                // 格式: task_{id}.json
69                if let Some(rest) = name.strip_prefix("task_")
70                    && let Some(id_str) = rest.strip_suffix(".json")
71                    && let Ok(id) = id_str.parse::<u64>()
72                {
73                    max_id = max_id.max(id);
74                }
75            }
76        }
77        max_id + 1
78    }
79
80    fn task_path(&self, id: u64) -> PathBuf {
81        self.tasks_dir.join(format!("task_{}.json", id))
82    }
83
84    pub fn create_task(
85        &self,
86        title: &str,
87        description: &str,
88        blocked_by: Vec<u64>,
89        task_doc_paths: Vec<String>,
90    ) -> Result<AgentTask, String> {
91        let _lock = safe_lock(&self.write_lock, "TaskManager::create_task");
92        let task_id = self.next_id();
93        let task = AgentTask {
94            task_id,
95            title: title.to_string(),
96            description: description.to_string(),
97            status: "pending".to_string(),
98            blocked_by,
99            owner: String::new(),
100            task_doc_paths,
101        };
102        self.save_task(&task)?;
103
104        Ok(task)
105    }
106
107    /// Return tasks that are pending and have no unresolved blockers (ready to work on)
108    pub fn list_ready_tasks(&self) -> Vec<AgentTask> {
109        self.list_tasks()
110            .into_iter()
111            .filter(|t| t.status == "pending" && t.blocked_by.is_empty())
112            .collect()
113    }
114
115    pub fn get_task(&self, id: u64) -> Result<AgentTask, String> {
116        let path = self.task_path(id);
117        if !path.exists() {
118            return Err(format!("Task {} does not exist", id));
119        }
120        let data = fs::read_to_string(&path).map_err(|e| format!("Failed to read task: {}", e))?;
121        serde_json::from_str(&data).map_err(|e| format!("Failed to parse task: {}", e))
122    }
123
124    pub fn list_tasks(&self) -> Vec<AgentTask> {
125        let mut tasks = Vec::new();
126        if let Ok(entries) = fs::read_dir(&self.tasks_dir) {
127            for entry in entries.flatten() {
128                let path = entry.path();
129                if path.extension().is_some_and(|e| e == "json")
130                    && let Ok(data) = fs::read_to_string(&path)
131                    && let Ok(task) = serde_json::from_str::<AgentTask>(&data)
132                {
133                    tasks.push(task);
134                }
135            }
136        }
137        tasks.sort_by_key(|t| t.task_id);
138        tasks
139    }
140
141    pub fn update_task(&self, id: u64, updates: &Value) -> Result<AgentTask, String> {
142        let _lock = safe_lock(&self.write_lock, "TaskManager::update_task");
143        let mut task = self.get_task(id)?;
144
145        if let Some(status) = updates.get("status").and_then(|s| s.as_str()) {
146            match status {
147                "deleted" => {
148                    // 删除任务文件
149                    let path = self.task_path(id);
150                    let _ = fs::remove_file(&path);
151                    // 清理其他任务中对该任务的引用
152                    self.clean_references(id);
153                    task.status = "deleted".to_string();
154                    return Ok(task);
155                }
156                "pending" | "in_progress" | "completed" => {
157                    task.status = status.to_string();
158                    // 完成时自动清理其他任务的 blocked_by
159                    if status == "completed" {
160                        self.clean_references(id);
161                    }
162                }
163                _ => return Err(format!("Invalid status: {}", status)),
164            }
165        }
166
167        if let Some(title) = updates.get("title").and_then(|s| s.as_str()) {
168            task.title = title.to_string();
169        }
170        if let Some(description) = updates.get("description").and_then(|s| s.as_str()) {
171            task.description = description.to_string();
172        }
173        if let Some(owner) = updates.get("owner").and_then(|s| s.as_str()) {
174            task.owner = owner.to_string();
175        }
176
177        // 添加依赖关系
178        if let Some(add_blocked_by) = updates.get("addBlockedBy").and_then(|v| v.as_array()) {
179            for id_val in add_blocked_by {
180                if let Some(dep_id) = id_val.as_u64()
181                    && !task.blocked_by.contains(&dep_id)
182                {
183                    task.blocked_by.push(dep_id);
184                }
185            }
186        }
187
188        self.save_task(&task)?;
189        Ok(task)
190    }
191
192    fn save_task(&self, task: &AgentTask) -> Result<(), String> {
193        let path = self.task_path(task.task_id);
194        let data = serde_json::to_string_pretty(task)
195            .map_err(|e| format!("Failed to serialize task: {}", e))?;
196        fs::write(&path, data).map_err(|e| format!("Failed to write task: {}", e))
197    }
198
199    /// 当任务完成或删除时,从所有其他任务的 blocked_by 中移除该 ID
200    fn clean_references(&self, completed_id: u64) {
201        let tasks = self.list_tasks();
202        for mut task in tasks {
203            if task.blocked_by.contains(&completed_id) {
204                task.blocked_by.retain(|&id| id != completed_id);
205                let _ = self.save_task(&task);
206            }
207        }
208    }
209
210    /// 替换所有任务(session 恢复时使用)
211    pub fn replace_all(&self, new_tasks: Vec<AgentTask>) {
212        let _lock = safe_lock(&self.write_lock, "TaskManager::replace_all");
213        // 删除所有现有任务文件
214        if let Ok(entries) = fs::read_dir(&self.tasks_dir) {
215            for entry in entries.flatten() {
216                let path = entry.path();
217                if path.extension().is_some_and(|e| e == "json") {
218                    let _ = fs::remove_file(&path);
219                }
220            }
221        }
222        // 写入新任务
223        for task in new_tasks {
224            let _ = self.save_task(&task);
225        }
226    }
227}