1use crate::state::RunCheckpoint;
7
8#[derive(Debug, Clone, serde::Serialize)]
10pub struct StatusOutput {
11 pub run_id: String,
12 pub run_dir: String,
13 pub task: String,
14 pub status: String,
15 pub current_phase: u32,
16 pub completed_phases: usize,
17 pub total_started: usize,
18 pub completed_agents: usize,
19 pub running_agents: usize,
20 pub total_tokens: u64,
21 pub created_at: String,
22 pub updated_at: String,
23}
24
25impl From<(&str, &RunCheckpoint)> for StatusOutput {
26 fn from((run_dir, cp): (&str, &RunCheckpoint)) -> Self {
27 let created = chrono::DateTime::from_timestamp(cp.created_at as i64, 0)
28 .map(|dt| dt.to_rfc3339())
29 .unwrap_or_default();
30 let updated = chrono::DateTime::from_timestamp(cp.updated_at as i64, 0)
31 .map(|dt| dt.to_rfc3339())
32 .unwrap_or_default();
33
34 Self {
35 run_id: cp.run_id.to_string(),
36 run_dir: run_dir.to_string(),
37 task: cp.task.clone(),
38 status: format!("{:?}", cp.status).to_lowercase(),
39 current_phase: cp.current_phase,
40 completed_phases: cp.completed_phases.len(),
41 total_started: cp.started_agent_ids.len(),
42 completed_agents: cp.agent_results.len(),
43 running_agents: cp
44 .started_agent_ids
45 .len()
46 .saturating_sub(cp.agent_results.len()),
47 total_tokens: cp.total_tokens,
48 created_at: created,
49 updated_at: updated,
50 }
51 }
52}
53
54pub enum ReportStatus {
55 Found(serde_json::Value),
56 NotFound,
57 RunFinished,
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::state::{AgentResultCache, CheckpointStatus, PhaseSummary};
64 use std::collections::HashMap;
65
66 #[test]
67 fn status_output_from_checkpoint() {
68 let run_id = uuid::Uuid::now_v7();
69 let cp = RunCheckpoint {
70 run_id,
71 task: "test task".into(),
72 status: CheckpointStatus::Running,
73 current_phase: 1,
74 completed_phases: vec![],
75 agent_results: HashMap::new(),
76 agent_sessions: HashMap::new(),
77 findings: vec![],
78 total_tokens: 0,
79 created_at: 1719000000,
80 updated_at: 1719000100,
81 workflow_meta: None,
82 started_agent_ids: vec![],
83 };
84 let output = StatusOutput::from(("run_dir", &cp));
85 assert_eq!(output.run_id, run_id.to_string());
86 assert_eq!(output.run_dir, "run_dir");
87 assert_eq!(output.task, "test task");
88 assert_eq!(output.status, "running");
89 assert_eq!(output.current_phase, 1);
90 }
91
92 #[test]
93 fn status_output_with_completed_agents() {
94 let run_id = uuid::Uuid::now_v7();
95 let agent_id = uuid::Uuid::now_v7();
96 let mut agent_results = HashMap::new();
97 agent_results.insert(
98 agent_id,
99 AgentResultCache {
100 agent_id,
101 phase_id: 1,
102 status: "ok".into(),
103 output: serde_json::json!({}),
104 findings: vec![],
105 tokens: 500,
106 completed_at: 1719000100,
107 cache_key_hash: None,
108 description: None,
109 role: None,
110 },
111 );
112 let cp = RunCheckpoint {
113 run_id,
114 task: "task".into(),
115 status: CheckpointStatus::Completed,
116 current_phase: 2,
117 completed_phases: vec![PhaseSummary {
118 phase_id: 1,
119 label: "phase 1".into(),
120 planned: 1,
121 ok: 1,
122 failed: 0,
123 description: None,
124 role: None,
125 }],
126 agent_results,
127 agent_sessions: HashMap::new(),
128 findings: vec![],
129 total_tokens: 500,
130 created_at: 1719000000,
131 updated_at: 1719000100,
132 workflow_meta: None,
133 started_agent_ids: vec![agent_id],
134 };
135 let output = StatusOutput::from(("run_dir", &cp));
136 assert_eq!(output.status, "completed");
137 assert_eq!(output.completed_agents, 1);
138 assert_eq!(output.total_tokens, 500);
139 }
140}