1use crate::core::a2a::task::{TaskPart, TaskState, TaskStore};
2
3pub fn handle(
4 action: &str,
5 current_agent_id: Option<&str>,
6 task_id: Option<&str>,
7 to_agent: Option<&str>,
8 description: Option<&str>,
9 state: Option<&str>,
10 message: Option<&str>,
11) -> String {
12 let agent = match current_agent_id {
13 Some(id) => id,
14 None if action == "list" || action == "info" => "unknown",
15 None => {
16 return "Error: agent must be registered first (use ctx_agent action=register)"
17 .to_string()
18 }
19 };
20
21 let mut store = TaskStore::load();
22 store.cleanup_old(72);
23
24 let result = match action {
25 "create" => handle_create(&mut store, agent, to_agent, description),
26 "update" => handle_update(&mut store, agent, task_id, state, message),
27 "list" => handle_list(&store, agent),
28 "get" => handle_get(&store, task_id),
29 "cancel" => handle_cancel(&mut store, agent, task_id, message),
30 "message" => handle_message(&mut store, agent, task_id, message),
31 "info" => handle_info(&store),
32 _ => format!(
33 "Unknown action '{action}'. Available: create, update, list, get, cancel, message, info"
34 ),
35 };
36
37 if matches!(action, "create" | "update" | "cancel" | "message") {
38 let _ = store.save();
39 }
40
41 result
42}
43
44fn handle_create(
45 store: &mut TaskStore,
46 from: &str,
47 to: Option<&str>,
48 desc: Option<&str>,
49) -> String {
50 let Some(to_agent) = to else {
51 return "Error: to_agent is required for task creation".to_string();
52 };
53 let description = desc.unwrap_or("(no description)");
54 let id = store.create_task(from, to_agent, description);
55 format!("Task created: {id}\n From: {from}\n To: {to_agent}\n Description: {description}")
56}
57
58fn handle_update(
59 store: &mut TaskStore,
60 agent: &str,
61 task_id: Option<&str>,
62 state: Option<&str>,
63 message: Option<&str>,
64) -> String {
65 let Some(tid) = task_id else {
66 return "Error: task_id is required".to_string();
67 };
68 let new_state = match state {
69 Some(s) => match TaskState::parse_str(s) {
70 Some(st) => st,
71 None => return format!("Error: invalid state '{s}'. Use: working, input-required, completed, failed, canceled"),
72 },
73 None => return "Error: state is required for update".to_string(),
74 };
75
76 let Some(task) = store.get_task_mut(tid) else {
77 return format!("Error: task '{tid}' not found");
78 };
79
80 if task.to_agent != agent && task.from_agent != agent {
81 return format!("Error: agent '{agent}' is not involved in task '{tid}'");
82 }
83
84 match task.transition(new_state.clone(), message) {
85 Ok(()) => {
86 if let Some(msg) = message {
87 task.add_message(
88 agent,
89 vec![TaskPart::Text {
90 text: msg.to_string(),
91 }],
92 );
93 }
94 format!(
95 "Task {tid} updated → {new_state}\n History: {} transitions",
96 task.history.len()
97 )
98 }
99 Err(e) => format!("Error: {e}"),
100 }
101}
102
103fn handle_list(store: &TaskStore, agent: &str) -> String {
104 let tasks = store.tasks_for_agent(agent);
105 if tasks.is_empty() {
106 return "No tasks found for this agent.".to_string();
107 }
108
109 let mut lines = vec![format!("Tasks ({}):", tasks.len())];
110 for task in &tasks {
111 let direction = if task.from_agent == agent {
112 format!("→ {}", task.to_agent)
113 } else {
114 format!("← {}", task.from_agent)
115 };
116 lines.push(format!(
117 " {} [{}] {} — {}",
118 task.id, task.state, direction, task.description
119 ));
120 }
121
122 let pending = store.pending_tasks_for(agent);
123 if !pending.is_empty() {
124 lines.push(format!(
125 "\n{} pending task(s) assigned to you.",
126 pending.len()
127 ));
128 }
129
130 lines.join("\n")
131}
132
133fn handle_get(store: &TaskStore, task_id: Option<&str>) -> String {
134 let Some(tid) = task_id else {
135 return "Error: task_id is required".to_string();
136 };
137 let Some(task) = store.get_task(tid) else {
138 return format!("Error: task '{tid}' not found");
139 };
140
141 let mut lines = vec![
142 format!("Task: {}", task.id),
143 format!(" State: {}", task.state),
144 format!(" From: {}", task.from_agent),
145 format!(" To: {}", task.to_agent),
146 format!(" Description: {}", task.description),
147 format!(" Created: {}", task.created_at),
148 format!(" Updated: {}", task.updated_at),
149 format!(" Messages: {}", task.messages.len()),
150 format!(" Artifacts: {}", task.artifacts.len()),
151 ];
152
153 if !task.history.is_empty() {
154 lines.push(" History:".to_string());
155 for t in &task.history {
156 lines.push(format!(
157 " {} → {} ({})",
158 t.from,
159 t.to,
160 t.reason.as_deref().unwrap_or("-")
161 ));
162 }
163 }
164
165 lines.join("\n")
166}
167
168fn handle_cancel(
169 store: &mut TaskStore,
170 agent: &str,
171 task_id: Option<&str>,
172 reason: Option<&str>,
173) -> String {
174 let Some(tid) = task_id else {
175 return "Error: task_id is required".to_string();
176 };
177 let Some(task) = store.get_task_mut(tid) else {
178 return format!("Error: task '{tid}' not found");
179 };
180
181 if task.from_agent != agent {
182 return format!(
183 "Error: only the task creator can cancel (creator: {})",
184 task.from_agent
185 );
186 }
187
188 match task.transition(TaskState::Canceled, reason) {
189 Ok(()) => format!("Task {tid} canceled."),
190 Err(e) => format!("Error: {e}"),
191 }
192}
193
194fn handle_message(
195 store: &mut TaskStore,
196 agent: &str,
197 task_id: Option<&str>,
198 message: Option<&str>,
199) -> String {
200 let Some(tid) = task_id else {
201 return "Error: task_id is required".to_string();
202 };
203 let Some(msg) = message else {
204 return "Error: message is required".to_string();
205 };
206 let Some(task) = store.get_task_mut(tid) else {
207 return format!("Error: task '{tid}' not found");
208 };
209
210 task.add_message(
211 agent,
212 vec![TaskPart::Text {
213 text: msg.to_string(),
214 }],
215 );
216 format!(
217 "Message added to task {tid} ({} messages total)",
218 task.messages.len()
219 )
220}
221
222fn handle_info(store: &TaskStore) -> String {
223 let total = store.tasks.len();
224 let active = store
225 .tasks
226 .iter()
227 .filter(|t| !t.state.is_terminal())
228 .count();
229 let completed = store
230 .tasks
231 .iter()
232 .filter(|t| t.state == TaskState::Completed)
233 .count();
234 let failed = store
235 .tasks
236 .iter()
237 .filter(|t| t.state == TaskState::Failed)
238 .count();
239
240 format!("Task Store: {total} total, {active} active, {completed} completed, {failed} failed")
241}