1use crate::cmux::SurfaceInfo;
4use rz_agent_protocol::SENTINEL;
5
6pub struct SurfaceStatus {
8 pub surface_id: String,
9 pub title: String,
10 pub command: String,
11 pub running: bool,
12 pub message_count: usize,
13}
14
15pub struct StatusSummary {
17 pub total: usize,
18 pub running: usize,
19 pub exited: usize,
20 pub surfaces: Vec<SurfaceStatus>,
21}
22
23fn count_messages(scrollback: &str) -> usize {
25 scrollback.lines().filter(|l| l.contains(SENTINEL)).count()
26}
27
28pub fn summarize(
34 surfaces: &[SurfaceInfo],
35 get_scrollback: impl Fn(&str) -> Option<String>,
36) -> StatusSummary {
37 let mut running = 0usize;
38 let exited = 0usize;
39 let mut statuses = Vec::with_capacity(surfaces.len());
40
41 for surface in surfaces {
42 running += 1;
44
45 let msg_count = get_scrollback(&surface.id)
46 .map(|s| count_messages(&s))
47 .unwrap_or(0);
48
49 statuses.push(SurfaceStatus {
50 surface_id: surface.id.clone(),
51 title: surface.title.clone(),
52 command: "-".to_string(),
53 running: true,
54 message_count: msg_count,
55 });
56 }
57
58 StatusSummary {
59 total: surfaces.len(),
60 running,
61 exited,
62 surfaces: statuses,
63 }
64}
65
66pub fn format_summary(summary: &StatusSummary) -> String {
68 let mut out = format!(
69 "{} surfaces ({} running, {} exited)\n",
70 summary.total, summary.running, summary.exited,
71 );
72
73 for s in &summary.surfaces {
74 let state = if s.running {
75 "running".to_string()
76 } else {
77 "exited".to_string()
78 };
79 out.push_str(&format!(
80 " {} | {} | {} | {} | {} msgs\n",
81 s.surface_id, s.title, s.command, state, s.message_count,
82 ));
83 }
84
85 out
86}