Skip to main content

luft_core/
query.rs

1//! Query DTO types shared between the CLI and storage layers.
2//!
3//! The actual query functions now live in `luft-storage::reader` (SQLite-backed)
4//! or in the `luft` main crate. This module retains only the data types.
5
6use crate::state::RunCheckpoint;
7
8/// Summary view of a run's checkpoint — the query DTO shared by the CLI.
9#[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}