Skip to main content

claude_agent/
task_queue.rs

1// Background task queue for agent work.
2// Tasks run as spawned sub-agents and can be polled for status.
3
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::Mutex;
7
8use crate::agents::{self, AgentResult};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum TaskStatus {
12    Pending,
13    Running,
14    Completed,
15    Failed,
16}
17
18#[derive(Debug, Clone)]
19pub struct TaskEntry {
20    pub id: String,
21    pub prompt: String,
22    pub status: TaskStatus,
23    pub result: Option<AgentResult>,
24}
25
26pub struct TaskQueue {
27    tasks: Arc<Mutex<HashMap<String, TaskEntry>>>,
28}
29
30impl Default for TaskQueue {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl TaskQueue {
37    pub fn new() -> Self {
38        Self {
39            tasks: Arc::new(Mutex::new(HashMap::new())),
40        }
41    }
42
43    /// Submit a task to run in the background. Returns task ID.
44    pub async fn submit(&self, prompt: &str, cwd: &str) -> String {
45        let task_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
46
47        let entry = TaskEntry {
48            id: task_id.clone(),
49            prompt: prompt.to_string(),
50            status: TaskStatus::Pending,
51            result: None,
52        };
53
54        self.tasks.lock().await.insert(task_id.clone(), entry);
55
56        // Spawn background work
57        let tasks = self.tasks.clone();
58        let tid = task_id.clone();
59        let p = prompt.to_string();
60        let c = cwd.to_string();
61
62        tokio::spawn(async move {
63            // Mark as running
64            if let Some(entry) = tasks.lock().await.get_mut(&tid) {
65                entry.status = TaskStatus::Running;
66            }
67
68            let result = agents::spawn_agent(&p, &c, None).await;
69
70            let mut tasks = tasks.lock().await;
71            if let Some(entry) = tasks.get_mut(&tid) {
72                match result {
73                    Ok(r) => {
74                        entry.status = if r.is_error { TaskStatus::Failed } else { TaskStatus::Completed };
75                        entry.result = Some(r);
76                    }
77                    Err(e) => {
78                        entry.status = TaskStatus::Failed;
79                        entry.result = Some(AgentResult {
80                            agent_id: tid.clone(),
81                            output: format!("Task error: {e}"),
82                            is_error: true,
83                            duration_ms: 0,
84                        });
85                    }
86                }
87            }
88        });
89
90        task_id
91    }
92
93    /// Get current status of a task.
94    pub async fn status(&self, task_id: &str) -> Option<TaskEntry> {
95        self.tasks.lock().await.get(task_id).cloned()
96    }
97
98    /// List all tasks.
99    pub async fn list(&self) -> Vec<TaskEntry> {
100        self.tasks.lock().await.values().cloned().collect()
101    }
102
103    /// Get result of a completed task.
104    pub async fn result(&self, task_id: &str) -> Option<AgentResult> {
105        self.tasks.lock().await
106            .get(task_id)
107            .and_then(|e| e.result.clone())
108    }
109
110    /// Count of pending + running tasks.
111    pub async fn active_count(&self) -> usize {
112        self.tasks.lock().await.values()
113            .filter(|t| matches!(t.status, TaskStatus::Pending | TaskStatus::Running))
114            .count()
115    }
116}