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 to_agent = match to {
51 Some(t) => t,
52 None => return "Error: to_agent is required for task creation".to_string(),
53 };
54 let description = desc.unwrap_or("(no description)");
55 let id = store.create_task(from, to_agent, description);
56 format!("Task created: {id}\n From: {from}\n To: {to_agent}\n Description: {description}")
57}
58
59fn handle_update(
60 store: &mut TaskStore,
61 agent: &str,
62 task_id: Option<&str>,
63 state: Option<&str>,
64 message: Option<&str>,
65) -> String {
66 let tid = match task_id {
67 Some(id) => id,
68 None => return "Error: task_id is required".to_string(),
69 };
70 let new_state = match state {
71 Some(s) => match TaskState::parse_str(s) {
72 Some(st) => st,
73 None => return format!("Error: invalid state '{s}'. Use: working, input-required, completed, failed, canceled"),
74 },
75 None => return "Error: state is required for update".to_string(),
76 };
77
78 let task = match store.get_task_mut(tid) {
79 Some(t) => t,
80 None => return format!("Error: task '{tid}' not found"),
81 };
82
83 if task.to_agent != agent && task.from_agent != agent {
84 return format!("Error: agent '{agent}' is not involved in task '{tid}'");
85 }
86
87 match task.transition(new_state.clone(), message) {
88 Ok(()) => {
89 if let Some(msg) = message {
90 task.add_message(
91 agent,
92 vec![TaskPart::Text {
93 text: msg.to_string(),
94 }],
95 );
96 }
97 format!(
98 "Task {tid} updated → {new_state}\n History: {} transitions",
99 task.history.len()
100 )
101 }
102 Err(e) => format!("Error: {e}"),
103 }
104}
105
106fn handle_list(store: &TaskStore, agent: &str) -> String {
107 let tasks = store.tasks_for_agent(agent);
108 if tasks.is_empty() {
109 return "No tasks found for this agent.".to_string();
110 }
111
112 let mut lines = vec![format!("Tasks ({}):", tasks.len())];
113 for task in &tasks {
114 let direction = if task.from_agent == agent {
115 format!("→ {}", task.to_agent)
116 } else {
117 format!("← {}", task.from_agent)
118 };
119 lines.push(format!(
120 " {} [{}] {} — {}",
121 task.id, task.state, direction, task.description
122 ));
123 }
124
125 let pending = store.pending_tasks_for(agent);
126 if !pending.is_empty() {
127 lines.push(format!(
128 "\n{} pending task(s) assigned to you.",
129 pending.len()
130 ));
131 }
132
133 lines.join("\n")
134}
135
136fn handle_get(store: &TaskStore, task_id: Option<&str>) -> String {
137 let tid = match task_id {
138 Some(id) => id,
139 None => return "Error: task_id is required".to_string(),
140 };
141 let task = match store.get_task(tid) {
142 Some(t) => t,
143 None => return format!("Error: task '{tid}' not found"),
144 };
145
146 let mut lines = vec![
147 format!("Task: {}", task.id),
148 format!(" State: {}", task.state),
149 format!(" From: {}", task.from_agent),
150 format!(" To: {}", task.to_agent),
151 format!(" Description: {}", task.description),
152 format!(" Created: {}", task.created_at),
153 format!(" Updated: {}", task.updated_at),
154 format!(" Messages: {}", task.messages.len()),
155 format!(" Artifacts: {}", task.artifacts.len()),
156 ];
157
158 if !task.history.is_empty() {
159 lines.push(" History:".to_string());
160 for t in &task.history {
161 lines.push(format!(
162 " {} → {} ({})",
163 t.from,
164 t.to,
165 t.reason.as_deref().unwrap_or("-")
166 ));
167 }
168 }
169
170 lines.join("\n")
171}
172
173fn handle_cancel(
174 store: &mut TaskStore,
175 agent: &str,
176 task_id: Option<&str>,
177 reason: Option<&str>,
178) -> String {
179 let tid = match task_id {
180 Some(id) => id,
181 None => return "Error: task_id is required".to_string(),
182 };
183 let task = match store.get_task_mut(tid) {
184 Some(t) => t,
185 None => return format!("Error: task '{tid}' not found"),
186 };
187
188 if task.from_agent != agent {
189 return format!(
190 "Error: only the task creator can cancel (creator: {})",
191 task.from_agent
192 );
193 }
194
195 match task.transition(TaskState::Canceled, reason) {
196 Ok(()) => format!("Task {tid} canceled."),
197 Err(e) => format!("Error: {e}"),
198 }
199}
200
201fn handle_message(
202 store: &mut TaskStore,
203 agent: &str,
204 task_id: Option<&str>,
205 message: Option<&str>,
206) -> String {
207 let tid = match task_id {
208 Some(id) => id,
209 None => return "Error: task_id is required".to_string(),
210 };
211 let msg = match message {
212 Some(m) => m,
213 None => return "Error: message is required".to_string(),
214 };
215 let task = match store.get_task_mut(tid) {
216 Some(t) => t,
217 None => return format!("Error: task '{tid}' not found"),
218 };
219
220 task.add_message(
221 agent,
222 vec![TaskPart::Text {
223 text: msg.to_string(),
224 }],
225 );
226 format!(
227 "Message added to task {tid} ({} messages total)",
228 task.messages.len()
229 )
230}
231
232fn handle_info(store: &TaskStore) -> String {
233 let total = store.tasks.len();
234 let active = store
235 .tasks
236 .iter()
237 .filter(|t| !t.state.is_terminal())
238 .count();
239 let completed = store
240 .tasks
241 .iter()
242 .filter(|t| t.state == TaskState::Completed)
243 .count();
244 let failed = store
245 .tasks
246 .iter()
247 .filter(|t| t.state == TaskState::Failed)
248 .count();
249
250 format!("Task Store: {total} total, {active} active, {completed} completed, {failed} failed")
251}