Skip to main content

sparrow/streaming/
lane.rs

1//! Multi-agent lane display for streaming output.
2//!
3//! Shows multiple concurrent agent progress lanes in the terminal,
4//! similar to `docker compose logs` or `htop`.
5
6use std::collections::HashMap;
7use std::time::Instant;
8
9/// A single agent lane showing current action and elapsed time.
10struct AgentLane {
11    name: String,
12    status: String,
13    started: Instant,
14}
15
16/// Displays multiple agent lanes concurrently.
17pub struct LaneDisplay {
18    lanes: HashMap<String, AgentLane>,
19}
20
21impl LaneDisplay {
22    /// Create a new lane display.
23    pub fn new() -> Self {
24        Self {
25            lanes: HashMap::new(),
26        }
27    }
28
29    /// Add a new lane for an agent.
30    pub fn add_lane(&mut self, name: &str) {
31        self.lanes.insert(
32            name.to_string(),
33            AgentLane {
34                name: name.to_string(),
35                status: "starting...".to_string(),
36                started: Instant::now(),
37            },
38        );
39    }
40
41    /// Update the status of an agent lane.
42    pub fn update_lane(&mut self, name: &str, status: &str) {
43        if let Some(lane) = self.lanes.get_mut(name) {
44            lane.status = status.to_string();
45        }
46    }
47
48    /// Remove an agent lane.
49    pub fn remove_lane(&mut self, name: &str) {
50        self.lanes.remove(name);
51    }
52
53    /// Render all lanes to the terminal.
54    pub fn render(&self) {
55        // Sort by name for consistent display
56        let mut sorted: Vec<&AgentLane> = self.lanes.values().collect();
57        sorted.sort_by(|a, b| a.name.cmp(&b.name));
58
59        for lane in &sorted {
60            let elapsed = lane.started.elapsed().as_secs();
61            eprintln!(
62                "  \x1b[36m[{elapsed:>3}s]\x1b[0m \x1b[1m{}\x1b[0m — {}",
63                lane.name, lane.status
64            );
65        }
66    }
67
68    /// Check if there are any active lanes.
69    pub fn is_empty(&self) -> bool {
70        self.lanes.is_empty()
71    }
72
73    /// Get the number of active lanes.
74    pub fn len(&self) -> usize {
75        self.lanes.len()
76    }
77}
78
79impl Default for LaneDisplay {
80    fn default() -> Self {
81        Self::new()
82    }
83}