Skip to main content

agentmux_launcher/
startup_events.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Startup event bus: typed events emitted by each launcher startup stage and
5//! consumed by the native splash screen renderer.
6//!
7//! Transport: `std::sync::mpsc::channel` (unbounded).  `Sender::send` never
8//! blocks and is safe to call from async tokio code.  The splash thread drains
9//! the receiver with `try_recv` every animation frame (~16 ms).
10//!
11//! On non-Windows platforms (or when the splash is disabled), the receiver is
12//! dropped immediately and subsequent sends silently return `Err` — callers
13//! use `let _ = self.tx.send(...)` so nothing blows up.
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum StartupStatus {
17    Ok,
18    Warn,
19    Error,
20}
21
22#[derive(Debug)]
23pub enum StartupEvent {
24    /// A top-level startup stage has begun.
25    StageBegin {
26        stage: &'static str,
27        label: &'static str,
28    },
29    /// A top-level startup stage has completed.
30    StageEnd {
31        stage: &'static str,
32        duration_ms: u64,
33        status: StartupStatus,
34        detail: Option<String>,
35    },
36    /// A sub-item within a stage has begun (e.g. an individual migration).
37    SubBegin {
38        stage: &'static str,
39        id: String,
40        label: String,
41    },
42    /// A sub-item within a stage has completed.
43    SubEnd {
44        stage: &'static str,
45        id: String,
46        duration_ms: u64,
47        status: StartupStatus,
48        /// Short human-readable annotation shown after the time (e.g. "✓").
49        detail: Option<String>,
50    },
51}
52
53/// Clone-able sender half — one per startup stage, cloned as needed.
54#[derive(Clone, Debug)]
55pub struct StartupEventSink {
56    tx: std::sync::mpsc::Sender<StartupEvent>,
57}
58
59impl StartupEventSink {
60    /// Create a linked `(sink, receiver)` pair.  Pass the receiver to
61    /// `splash::spawn_splash`; keep the sink in the launcher startup path.
62    pub fn new() -> (Self, std::sync::mpsc::Receiver<StartupEvent>) {
63        let (tx, rx) = std::sync::mpsc::channel();
64        (Self { tx }, rx)
65    }
66
67    pub fn stage_begin(&self, stage: &'static str, label: &'static str) {
68        let _ = self.tx.send(StartupEvent::StageBegin { stage, label });
69    }
70
71    pub fn stage_end(
72        &self,
73        stage: &'static str,
74        duration_ms: u64,
75        status: StartupStatus,
76        detail: Option<String>,
77    ) {
78        let _ = self.tx.send(StartupEvent::StageEnd {
79            stage,
80            duration_ms,
81            status,
82            detail,
83        });
84    }
85
86    pub fn sub_begin(
87        &self,
88        stage: &'static str,
89        id: impl Into<String>,
90        label: impl Into<String>,
91    ) {
92        let _ = self.tx.send(StartupEvent::SubBegin {
93            stage,
94            id: id.into(),
95            label: label.into(),
96        });
97    }
98
99    pub fn sub_end(
100        &self,
101        stage: &'static str,
102        id: impl Into<String>,
103        duration_ms: u64,
104        status: StartupStatus,
105        detail: Option<String>,
106    ) {
107        let _ = self.tx.send(StartupEvent::SubEnd {
108            stage,
109            id: id.into(),
110            duration_ms,
111            status,
112            detail,
113        });
114    }
115}