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