1use std::sync::OnceLock;
7
8pub fn linked_core_names() -> &'static [&'static str] {
10 static NAMES: OnceLock<Vec<&'static str>> = OnceLock::new();
11 NAMES.get_or_init(|| {
12 let mut names: Vec<&'static str> = Vec::new();
13 #[cfg(feature = "d2b")]
14 names.push("dig2browser");
15 #[cfg(feature = "m4a")]
16 names.push("mail4agent");
17 #[cfg(feature = "session-restore")]
18 names.push("session-restore");
19 #[cfg(feature = "wireguard")]
20 names.push("wireguard");
21 names
22 })
23}
24
25pub fn force_link() -> usize {
29 let mut acc: usize = 0;
30 #[cfg(feature = "d2b")]
31 {
32 acc = acc.wrapping_add(dig2browser::browser_stream::LOCAL_PAGE_HTML.len());
33 }
34 #[cfg(feature = "m4a")]
35 {
36 let mail: fn() -> Result<(), mail4agent::MainError> = mail4agent::run;
37 acc = acc.wrapping_add(mail as usize);
38 }
39 #[cfg(feature = "session-restore")]
40 {
41 let claude = std::mem::size_of::<claude_session_restore::transcript::SessionEvent>();
42 let codex = codex_session_restore::MAX_HEAD_BYTES;
43 let grok: fn() = grok_session_restore::main;
44 let kimi: fn() = kimi_session_restore::main;
45 acc = acc
46 .wrapping_add(claude)
47 .wrapping_add(codex)
48 .wrapping_add(grok as usize)
49 .wrapping_add(kimi as usize);
50 }
51 #[cfg(feature = "wireguard")]
52 {
53 let wireguard: fn(
54 &crate::kit_wireguard::NodeWgPeerConfig,
55 ) -> Result<std::net::IpAddr, crate::kit_wireguard::NodeWgPeerError> =
56 crate::kit_wireguard::bring_up_node_wireguard;
57 acc = acc
58 .wrapping_add(wireguard as usize)
59 .wrapping_add(crate::kit_wireguard::LINK_TAG.len());
60 }
61 acc
62}
63
64#[cfg(feature = "m4a")]
67pub fn start_mailbox() -> Result<MailboxHandle, mail4agent::MainError> {
71 let join = std::thread::Builder::new()
72 .name("mail4agent".to_owned())
73 .spawn(|| {
74 let result = mail4agent::run();
75 if let Err(error) = &result {
76 tracing::error!(%error, "mail4agent mailbox stopped");
77 }
78 result
79 })
80 .map_err(mail4agent::MainError::Runtime)?;
81 Ok(MailboxHandle { join })
82}
83
84#[cfg(feature = "m4a")]
86pub struct MailboxHandle {
87 join: std::thread::JoinHandle<Result<(), mail4agent::MainError>>,
88}
89
90#[cfg(feature = "m4a")]
91impl MailboxHandle {
92 pub fn is_finished(&self) -> bool {
94 self.join.is_finished()
95 }
96
97 pub fn join(self) -> Result<(), mail4agent::MainError> {
99 self.join.join().unwrap_or_else(|_| {
100 Err(mail4agent::MainError::Runtime(std::io::Error::other("mail4agent thread panicked")))
101 })
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[cfg(all(feature = "d2b", feature = "m4a", feature = "session-restore", feature = "wireguard"))]
110 #[test]
111 fn kit_names_the_four_cores() {
112 assert_eq!(
113 linked_core_names(),
114 ["dig2browser", "mail4agent", "session-restore", "wireguard"]
115 );
116 assert!(force_link() > 0);
117 }
118
119 #[test]
120 fn linked_names_match_enabled_features() {
121 let expected = cfg!(feature = "d2b") as usize
122 + cfg!(feature = "m4a") as usize
123 + cfg!(feature = "session-restore") as usize
124 + cfg!(feature = "wireguard") as usize;
125 assert_eq!(linked_core_names().len(), expected);
126 }
127
128 #[cfg(feature = "m4a")]
129 #[test]
130 fn kit_exposes_mailbox_automaton_entry() {
131 let start: fn() -> Result<MailboxHandle, mail4agent::MainError> = start_mailbox;
132 assert!(start as usize != 0);
133 }
134}