Skip to main content

vv_agent/runtime/sub_task_manager/
submission.rs

1use std::collections::BTreeMap;
2use std::panic::{catch_unwind, AssertUnwindSafe};
3use std::sync::Arc;
4use std::thread;
5
6use crate::types::{AgentStatus, SubTaskOutcome};
7use crate::workspace::WorkspaceBackend;
8
9use super::helpers::{now_iso, panic_payload_to_string};
10use super::manager::SubTaskManager;
11use super::record::ManagedSubTask;
12
13impl SubTaskManager {
14    pub fn submit(
15        &self,
16        task_id: impl Into<String>,
17        session_id: impl Into<String>,
18        agent_name: impl Into<String>,
19        task_title: impl Into<String>,
20        runner: impl FnOnce() -> SubTaskOutcome + Send + 'static,
21    ) -> Result<(), String> {
22        self.submit_with_workspace(task_id, session_id, agent_name, task_title, None, runner)
23    }
24
25    pub fn submit_with_workspace(
26        &self,
27        task_id: impl Into<String>,
28        session_id: impl Into<String>,
29        agent_name: impl Into<String>,
30        task_title: impl Into<String>,
31        workspace_backend: Option<Arc<dyn WorkspaceBackend>>,
32        runner: impl FnOnce() -> SubTaskOutcome + Send + 'static,
33    ) -> Result<(), String> {
34        let task_id = task_id.into();
35        let session_id = session_id.into();
36        let agent_name = agent_name.into();
37        let task_title = task_title.into();
38        {
39            let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
40            if tasks.get(&task_id).is_some_and(ManagedSubTask::is_running) {
41                return Err(format!("Sub-task {task_id} is already running."));
42            }
43            tasks.insert(
44                task_id.clone(),
45                ManagedSubTask {
46                    task_id: task_id.clone(),
47                    session_id: session_id.clone(),
48                    agent_name: agent_name.clone(),
49                    task_title,
50                    workspace_backend,
51                    session: None,
52                    outcome: None,
53                    resolved: BTreeMap::new(),
54                    current_cycle_index: None,
55                    recent_activity: None,
56                    latest_cycle: None,
57                    latest_tool_call: None,
58                    handle: None,
59                    updated_at: now_iso(),
60                    manager_listener_attached: false,
61                },
62            );
63        }
64
65        let tasks = self.tasks.clone();
66        let task_id_for_thread = task_id.clone();
67        let handle = thread::spawn(move || {
68            let outcome = catch_unwind(AssertUnwindSafe(runner));
69            let mut tasks = tasks.lock().expect("sub-task manager poisoned");
70            if let Some(record) = tasks.get_mut(&task_id_for_thread) {
71                let outcome = match outcome {
72                    Ok(outcome) => outcome,
73                    Err(payload) => SubTaskOutcome {
74                        task_id: record.task_id.clone(),
75                        agent_name: record.agent_name.clone(),
76                        status: AgentStatus::Failed,
77                        session_id: Some(record.session_id.clone()),
78                        final_answer: None,
79                        wait_reason: None,
80                        error: Some(panic_payload_to_string(payload.as_ref())),
81                        cycles: 0,
82                        todo_list: Vec::new(),
83                        resolved: record.resolved.clone(),
84                    },
85                };
86                if !outcome.resolved.is_empty() {
87                    record.resolved = outcome.resolved.clone();
88                }
89                record.update_from_outcome(&outcome);
90                record.outcome = Some(outcome);
91                record.updated_at = now_iso();
92                record.handle = None;
93            }
94        });
95
96        let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
97        if let Some(record) = tasks.get_mut(&task_id) {
98            record.handle = Some(handle);
99            record.updated_at = now_iso();
100        }
101        Ok(())
102    }
103
104    pub fn record_outcome(&self, task_id: &str, outcome: SubTaskOutcome) {
105        let mut tasks = self.tasks.lock().expect("sub-task manager poisoned");
106        let task_id = task_id.to_string();
107        match tasks.get_mut(&task_id) {
108            Some(record) => {
109                record.session_id = outcome
110                    .session_id
111                    .clone()
112                    .unwrap_or_else(|| record.session_id.clone());
113                record.agent_name = outcome.agent_name.clone();
114                if !outcome.resolved.is_empty() {
115                    record.resolved = outcome.resolved.clone();
116                }
117                record.update_from_outcome(&outcome);
118                record.outcome = Some(outcome);
119                record.updated_at = now_iso();
120            }
121            None => {
122                let mut record = ManagedSubTask {
123                    session_id: outcome.session_id.clone().unwrap_or_default(),
124                    agent_name: outcome.agent_name.clone(),
125                    task_title: String::new(),
126                    workspace_backend: None,
127                    session: None,
128                    outcome: None,
129                    resolved: outcome.resolved.clone(),
130                    current_cycle_index: None,
131                    recent_activity: None,
132                    latest_cycle: None,
133                    latest_tool_call: None,
134                    task_id: task_id.clone(),
135                    handle: None,
136                    updated_at: now_iso(),
137                    manager_listener_attached: false,
138                };
139                record.update_from_outcome(&outcome);
140                record.outcome = Some(outcome);
141                tasks.insert(task_id.clone(), record);
142            }
143        }
144    }
145}