1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "lowercase")]
7pub enum TaskSessionTransport {
8 Acp,
10 A2a,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(rename_all = "snake_case")]
17pub enum TaskLaneSessionStatus {
18 Running,
19 Completed,
20 Failed,
21 TimedOut,
22 Transitioned,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27#[serde(rename_all = "snake_case")]
28pub enum TaskLaneSessionLoopMode {
29 WatchdogRetry,
30 RalphLoop,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "snake_case")]
36pub enum TaskLaneSessionCompletionRequirement {
37 TurnComplete,
38 CompletionSummary,
39 VerificationReport,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "snake_case")]
45pub enum TaskLaneSessionRecoveryReason {
46 WatchdogInactivity,
47 AgentFailed,
48 CompletionCriteriaNotMet,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(rename_all = "camelCase")]
54pub struct TaskLaneSession {
55 pub session_id: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub routa_agent_id: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub column_id: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub column_name: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub step_id: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub step_index: Option<i64>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub step_name: Option<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub provider: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub role: Option<String>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub specialist_id: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub specialist_name: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub transport: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub external_task_id: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub context_id: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub attempt: Option<i64>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub loop_mode: Option<TaskLaneSessionLoopMode>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub completion_requirement: Option<TaskLaneSessionCompletionRequirement>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub objective: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub last_activity_at: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub recovered_from_session_id: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub recovery_reason: Option<TaskLaneSessionRecoveryReason>,
99 pub status: TaskLaneSessionStatus,
100 pub started_at: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub completed_at: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "snake_case")]
108pub enum TaskLaneHandoffRequestType {
109 EnvironmentPreparation,
110 RuntimeContext,
111 Clarification,
112 RerunCommand,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "snake_case")]
118pub enum TaskLaneHandoffStatus {
119 Requested,
120 Delivered,
121 Completed,
122 Blocked,
123 Failed,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
128#[serde(rename_all = "camelCase")]
129pub struct TaskLaneHandoff {
130 pub id: String,
131 pub from_session_id: String,
132 pub to_session_id: String,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub from_column_id: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub to_column_id: Option<String>,
137 pub request_type: TaskLaneHandoffRequestType,
138 pub request: String,
139 pub status: TaskLaneHandoffStatus,
140 pub requested_at: String,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub responded_at: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub response_summary: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
148pub enum TaskPriority {
149 #[serde(rename = "low")]
150 Low,
151 #[serde(rename = "medium")]
152 Medium,
153 #[serde(rename = "high")]
154 High,
155 #[serde(rename = "urgent")]
156 Urgent,
157}
158
159impl TaskPriority {
160 pub fn as_str(&self) -> &'static str {
161 match self {
162 Self::Low => "low",
163 Self::Medium => "medium",
164 Self::High => "high",
165 Self::Urgent => "urgent",
166 }
167 }
168
169 #[allow(clippy::should_implement_trait)]
170 pub fn from_str(s: &str) -> Option<Self> {
171 match s {
172 "low" => Some(Self::Low),
173 "medium" => Some(Self::Medium),
174 "high" => Some(Self::High),
175 "urgent" => Some(Self::Urgent),
176 _ => None,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
182pub enum TaskStatus {
183 #[serde(rename = "PENDING")]
184 Pending,
185 #[serde(rename = "IN_PROGRESS")]
186 InProgress,
187 #[serde(rename = "REVIEW_REQUIRED")]
188 ReviewRequired,
189 #[serde(rename = "COMPLETED")]
190 Completed,
191 #[serde(rename = "NEEDS_FIX")]
192 NeedsFix,
193 #[serde(rename = "BLOCKED")]
194 Blocked,
195 #[serde(rename = "CANCELLED")]
196 Cancelled,
197}
198
199impl TaskStatus {
200 pub fn as_str(&self) -> &'static str {
201 match self {
202 Self::Pending => "PENDING",
203 Self::InProgress => "IN_PROGRESS",
204 Self::ReviewRequired => "REVIEW_REQUIRED",
205 Self::Completed => "COMPLETED",
206 Self::NeedsFix => "NEEDS_FIX",
207 Self::Blocked => "BLOCKED",
208 Self::Cancelled => "CANCELLED",
209 }
210 }
211
212 #[allow(clippy::should_implement_trait)]
213 pub fn from_str(s: &str) -> Option<Self> {
214 match s {
215 "PENDING" => Some(Self::Pending),
216 "IN_PROGRESS" => Some(Self::InProgress),
217 "REVIEW_REQUIRED" => Some(Self::ReviewRequired),
218 "COMPLETED" => Some(Self::Completed),
219 "NEEDS_FIX" => Some(Self::NeedsFix),
220 "BLOCKED" => Some(Self::Blocked),
221 "CANCELLED" => Some(Self::Cancelled),
222 _ => None,
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
228pub enum VerificationVerdict {
229 #[serde(rename = "APPROVED")]
230 Approved,
231 #[serde(rename = "NOT_APPROVED")]
232 NotApproved,
233 #[serde(rename = "BLOCKED")]
234 Blocked,
235}
236
237impl VerificationVerdict {
238 pub fn as_str(&self) -> &'static str {
239 match self {
240 Self::Approved => "APPROVED",
241 Self::NotApproved => "NOT_APPROVED",
242 Self::Blocked => "BLOCKED",
243 }
244 }
245
246 #[allow(clippy::should_implement_trait)]
247 pub fn from_str(s: &str) -> Option<Self> {
248 match s {
249 "APPROVED" => Some(Self::Approved),
250 "NOT_APPROVED" => Some(Self::NotApproved),
251 "BLOCKED" => Some(Self::Blocked),
252 _ => None,
253 }
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
258#[serde(rename_all = "camelCase")]
259pub struct Task {
260 pub id: String,
261 pub title: String,
262 pub objective: String,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub comment: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub scope: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub acceptance_criteria: Option<Vec<String>>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub verification_commands: Option<Vec<String>>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub test_cases: Option<Vec<String>>,
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub assigned_to: Option<String>,
275 pub status: TaskStatus,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub board_id: Option<String>,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub column_id: Option<String>,
280 #[serde(default)]
281 pub position: i64,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub priority: Option<TaskPriority>,
284 #[serde(default)]
285 pub labels: Vec<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub assignee: Option<String>,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub assigned_provider: Option<String>,
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub assigned_role: Option<String>,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub assigned_specialist_id: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub assigned_specialist_name: Option<String>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub trigger_session_id: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub github_id: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub github_number: Option<i64>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub github_url: Option<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub github_repo: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub github_state: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub github_synced_at: Option<DateTime<Utc>>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub last_sync_error: Option<String>,
312 #[serde(default)]
313 pub dependencies: Vec<String>,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 pub parallel_group: Option<String>,
316 pub workspace_id: String,
317 #[serde(skip_serializing_if = "Option::is_none")]
319 pub session_id: Option<String>,
320 #[serde(default)]
322 pub codebase_ids: Vec<String>,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub worktree_id: Option<String>,
326 #[serde(default)]
328 pub session_ids: Vec<String>,
329 #[serde(default)]
331 pub lane_sessions: Vec<TaskLaneSession>,
332 #[serde(default)]
334 pub lane_handoffs: Vec<TaskLaneHandoff>,
335 pub created_at: DateTime<Utc>,
336 pub updated_at: DateTime<Utc>,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub completion_summary: Option<String>,
339 #[serde(skip_serializing_if = "Option::is_none")]
340 pub verification_verdict: Option<VerificationVerdict>,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 pub verification_report: Option<String>,
343}
344
345impl Task {
346 #[allow(clippy::too_many_arguments)]
347 pub fn new(
348 id: String,
349 title: String,
350 objective: String,
351 workspace_id: String,
352 session_id: Option<String>,
353 scope: Option<String>,
354 acceptance_criteria: Option<Vec<String>>,
355 verification_commands: Option<Vec<String>>,
356 test_cases: Option<Vec<String>>,
357 dependencies: Option<Vec<String>>,
358 parallel_group: Option<String>,
359 ) -> Self {
360 let now = Utc::now();
361 Self {
362 id,
363 title,
364 objective,
365 comment: None,
366 scope,
367 acceptance_criteria,
368 verification_commands,
369 test_cases,
370 assigned_to: None,
371 status: TaskStatus::Pending,
372 board_id: None,
373 column_id: Some("backlog".to_string()),
374 position: 0,
375 priority: None,
376 labels: Vec::new(),
377 assignee: None,
378 assigned_provider: None,
379 assigned_role: None,
380 assigned_specialist_id: None,
381 assigned_specialist_name: None,
382 trigger_session_id: None,
383 github_id: None,
384 github_number: None,
385 github_url: None,
386 github_repo: None,
387 github_state: None,
388 github_synced_at: None,
389 last_sync_error: None,
390 dependencies: dependencies.unwrap_or_default(),
391 parallel_group,
392 workspace_id,
393 session_id,
394 codebase_ids: Vec::new(),
395 worktree_id: None,
396 session_ids: Vec::new(),
397 lane_sessions: Vec::new(),
398 lane_handoffs: Vec::new(),
399 created_at: now,
400 updated_at: now,
401 completion_summary: None,
402 verification_verdict: None,
403 verification_report: None,
404 }
405 }
406}