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 has_attached_session(&self, task_id: &str) -> Option<bool> {
68 self.join_finished_tasks();
69 self.tasks
70 .lock()
71 .expect("sub-task manager poisoned")
72 .get(task_id)
73 .map(|record| record.session.is_some())
74 }
75
76 pub fn is_running(&self, task_id: &str) -> bool {
77 self.join_finished_tasks();
78 self.tasks
79 .lock()
80 .expect("sub-task manager poisoned")
81 .get(task_id)
82 .is_some_and(ManagedSubTask::is_running)
83 }
84
85 pub fn wait(&self, task_id: &str, timeout: Option<Duration>) -> bool {
86 let deadline = timeout.map(|duration| Instant::now() + duration);
87 loop {
88 let handle = {
89 let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
90 let Some(record) = tasks.get_mut(task_id) else {
91 return false;
92 };
93 match record.handle.as_ref() {
94 Some(handle) if timeout.is_none() || handle.is_finished() => {
95 record.handle.take()
96 }
97 Some(_) => None,
98 None if record.is_running() => None,
99 None => return true,
100 }
101 };
102
103 if let Some(handle) = handle {
104 let _ = handle.join();
105 return true;
106 }
107
108 if deadline.is_some_and(|deadline| Instant::now() >= deadline) {
109 return false;
110 }
111 thread::sleep(Duration::from_millis(10));
112 }
113 }
114
115 pub fn wait_for_record(
116 &self,
117 task_id: &str,
118 timeout: Option<Duration>,
119 ) -> Option<ManagedSubTaskSnapshot> {
120 if !self.wait(task_id, timeout) {
121 return None;
122 }
123 self.get(task_id)
124 }
125
126 fn join_finished_tasks(&self) {
127 let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
128 for record in tasks.values_mut() {
129 if record.handle.as_ref().is_some_and(JoinHandle::is_finished) {
130 if let Some(handle) = record.handle.take() {
131 let _ = handle.join();
132 }
133 }
134 }
135 }
136}