vv_agent/runtime/sub_task_manager/
status.rs1use std::thread::{self, JoinHandle};
2use std::time::{Duration, Instant};
3
4use serde_json::{json, Value};
5
6use super::manager::SubTaskManager;
7use super::record::ManagedSubTask;
8use super::types::ManagedSubTaskSnapshot;
9
10impl SubTaskManager {
11 pub fn status_entries(
12 &self,
13 task_ids: &[String],
14 detail_level: &str,
15 workspace_file_limit: usize,
16 ) -> Vec<Value> {
17 let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
18 for record in tasks.values_mut() {
19 if record.handle.as_ref().is_some_and(JoinHandle::is_finished) {
20 if let Some(handle) = record.handle.take() {
21 let _ = handle.join();
22 }
23 }
24 }
25 task_ids
26 .iter()
27 .map(|task_id| {
28 let Some(record) = tasks.get(task_id) else {
29 return json!({
30 "task_id": task_id,
31 "status": "missing",
32 "error": format!("Sub-task {task_id} not found."),
33 });
34 };
35 record.to_status_entry(detail_level, workspace_file_limit)
36 })
37 .collect()
38 }
39
40 pub fn get(&self, task_id: &str) -> Option<ManagedSubTaskSnapshot> {
41 self.join_finished_tasks();
42 self.tasks
43 .lock()
44 .expect("sub-task manager poisoned")
45 .get(task_id)
46 .map(ManagedSubTask::snapshot)
47 }
48
49 pub fn task_session_id(&self, task_id: &str) -> Option<String> {
50 self.join_finished_tasks();
51 self.tasks
52 .lock()
53 .expect("sub-task manager poisoned")
54 .get(task_id)
55 .map(|record| record.session_id.clone())
56 }
57
58 pub fn task_status_label(&self, task_id: &str) -> Option<String> {
59 self.join_finished_tasks();
60 self.tasks
61 .lock()
62 .expect("sub-task manager poisoned")
63 .get(task_id)
64 .map(|record| record.status_label().to_string())
65 }
66
67 pub fn is_running(&self, task_id: &str) -> bool {
68 self.join_finished_tasks();
69 self.tasks
70 .lock()
71 .expect("sub-task manager poisoned")
72 .get(task_id)
73 .is_some_and(ManagedSubTask::is_running)
74 }
75
76 pub fn wait(&self, task_id: &str, timeout: Option<Duration>) -> bool {
77 let deadline = timeout.map(|duration| Instant::now() + duration);
78 loop {
79 let handle = {
80 let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
81 let Some(record) = tasks.get_mut(task_id) else {
82 return false;
83 };
84 match record.handle.as_ref() {
85 Some(handle) if timeout.is_none() || handle.is_finished() => {
86 record.handle.take()
87 }
88 Some(_) => None,
89 None => return true,
90 }
91 };
92
93 if let Some(handle) = handle {
94 let _ = handle.join();
95 return true;
96 }
97
98 if deadline.is_some_and(|deadline| Instant::now() >= deadline) {
99 return false;
100 }
101 thread::sleep(Duration::from_millis(10));
102 }
103 }
104
105 pub fn wait_for_record(
106 &self,
107 task_id: &str,
108 timeout: Option<Duration>,
109 ) -> Option<ManagedSubTaskSnapshot> {
110 if !self.wait(task_id, timeout) {
111 return None;
112 }
113 self.get(task_id)
114 }
115
116 fn join_finished_tasks(&self) {
117 let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
118 for record in tasks.values_mut() {
119 if record.handle.as_ref().is_some_and(JoinHandle::is_finished) {
120 if let Some(handle) = record.handle.take() {
121 let _ = handle.join();
122 }
123 }
124 }
125 }
126}