robit_agent/tool/
task_registry.rs1use std::collections::HashMap;
16use std::sync::{Arc, Mutex};
17use std::time::Instant;
18
19use crate::event::SessionId;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum AsyncTaskStatus {
24 Pending,
26 Completed,
28 Failed,
30 Cancelled,
32}
33
34impl AsyncTaskStatus {
35 pub fn as_str(&self) -> &'static str {
36 match self {
37 AsyncTaskStatus::Pending => "pending",
38 AsyncTaskStatus::Completed => "completed",
39 AsyncTaskStatus::Failed => "failed",
40 AsyncTaskStatus::Cancelled => "cancelled",
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct AsyncTaskRecord {
48 pub task_id: String,
49 pub tool_name: String,
50 pub tool_call_id: String,
51 pub session_id: SessionId,
52 pub status: AsyncTaskStatus,
53 pub started_at: Instant,
54 pub result_summary: Option<String>,
57}
58
59#[derive(Clone, Default)]
63pub struct TaskRegistry {
64 inner: Arc<Mutex<HashMap<String, AsyncTaskRecord>>>,
65}
66
67impl TaskRegistry {
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn register(&self, record: AsyncTaskRecord) {
74 let mut g = self.inner.lock().expect("task registry lock poisoned");
75 g.insert(record.task_id.clone(), record);
76 }
77
78 pub fn update(
80 &self,
81 task_id: &str,
82 status: AsyncTaskStatus,
83 result_summary: Option<String>,
84 ) {
85 let mut g = self.inner.lock().expect("task registry lock poisoned");
86 if let Some(r) = g.get_mut(task_id) {
87 r.status = status;
88 if result_summary.is_some() {
89 r.result_summary = result_summary;
90 }
91 }
92 }
93
94 pub fn remove(&self, task_id: &str) {
96 let mut g = self.inner.lock().expect("task registry lock poisoned");
97 g.remove(task_id);
98 }
99
100 pub fn get(&self, task_id: &str) -> Option<AsyncTaskRecord> {
102 self.inner
103 .lock()
104 .expect("task registry lock poisoned")
105 .get(task_id)
106 .cloned()
107 }
108
109 pub fn list_pending(&self) -> Vec<AsyncTaskRecord> {
111 let g = self.inner.lock().expect("task registry lock poisoned");
112 g.values()
113 .filter(|r| r.status == AsyncTaskStatus::Pending)
114 .cloned()
115 .collect()
116 }
117
118 pub fn list_all(&self) -> Vec<AsyncTaskRecord> {
120 let g = self.inner.lock().expect("task registry lock poisoned");
121 g.values().cloned().collect()
122 }
123
124 pub fn len(&self) -> usize {
126 self.inner.lock().expect("task registry lock poisoned").len()
127 }
128
129 pub fn is_empty(&self) -> bool {
130 self.len() == 0
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn record(id: &str, status: AsyncTaskStatus) -> AsyncTaskRecord {
139 AsyncTaskRecord {
140 task_id: id.into(),
141 tool_name: "test_tool".into(),
142 tool_call_id: "tc".into(),
143 session_id: "sess".into(),
144 status,
145 started_at: Instant::now(),
146 result_summary: None,
147 }
148 }
149
150 #[test]
151 fn register_and_get() {
152 let reg = TaskRegistry::new();
153 reg.register(record("t1", AsyncTaskStatus::Pending));
154 assert_eq!(reg.len(), 1);
155 let r = reg.get("t1").unwrap();
156 assert_eq!(r.status, AsyncTaskStatus::Pending);
157 }
158
159 #[test]
160 fn update_changes_status_and_summary() {
161 let reg = TaskRegistry::new();
162 reg.register(record("t1", AsyncTaskStatus::Pending));
163 reg.update("t1", AsyncTaskStatus::Completed, Some("ok".into()));
164 let r = reg.get("t1").unwrap();
165 assert_eq!(r.status, AsyncTaskStatus::Completed);
166 assert_eq!(r.result_summary.as_deref(), Some("ok"));
167 }
168
169 #[test]
170 fn list_pending_filters_by_status() {
171 let reg = TaskRegistry::new();
172 reg.register(record("t1", AsyncTaskStatus::Pending));
173 reg.register(record("t2", AsyncTaskStatus::Completed));
174 reg.register(record("t3", AsyncTaskStatus::Pending));
175 let pending = reg.list_pending();
176 assert_eq!(pending.len(), 2);
177 assert!(pending.iter().all(|r| r.status == AsyncTaskStatus::Pending));
178 }
179
180 #[test]
181 fn remove_drops_record() {
182 let reg = TaskRegistry::new();
183 reg.register(record("t1", AsyncTaskStatus::Pending));
184 reg.remove("t1");
185 assert!(reg.get("t1").is_none());
186 assert!(reg.is_empty());
187 }
188
189 #[test]
190 fn clone_shares_state() {
191 let reg = TaskRegistry::new();
192 let clone = reg.clone();
193 reg.register(record("t1", AsyncTaskStatus::Pending));
194 assert!(clone.get("t1").is_some());
196 }
197}