Skip to main content

hatchery_node/
kit.rs

1//! Standard node kit cores: dig2browser (`d2b`), mail4agent (`m4a`), the four
2//! session-restore crates (`session-restore`) and kernel WireGuard
3//! (`wireguard`). Each core is compiled only with its own feature; the default
4//! build (`kit`) enables all of them.
5
6use std::sync::OnceLock;
7
8/// Names of the cores linked into this build, in owner order.
9pub 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
25/// Touches a real symbol in each enabled core so the node binary links them.
26/// Does not start a browser, a provider CLI, or a tunnel, and does not read
27/// a secret. The mailbox is started separately by [`start_mailbox`].
28pub 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::MailboxJoinHandle, mail4agent::MainError> =
37            mail4agent::spawn;
38        acc = acc.wrapping_add(mail as usize);
39    }
40    #[cfg(feature = "session-restore")]
41    {
42        let claude = std::mem::size_of::<claude_session_restore::transcript::SessionEvent>();
43        let codex = codex_session_restore::MAX_HEAD_BYTES;
44        let grok: fn() = grok_session_restore::main;
45        let kimi: fn() = kimi_session_restore::main;
46        acc = acc
47            .wrapping_add(claude)
48            .wrapping_add(codex)
49            .wrapping_add(grok as usize)
50            .wrapping_add(kimi as usize);
51    }
52    #[cfg(feature = "wireguard")]
53    {
54        let wireguard: fn(
55            &crate::kit_wireguard::NodeWgPeerConfig,
56        ) -> Result<std::net::IpAddr, crate::kit_wireguard::NodeWgPeerError> =
57            crate::kit_wireguard::bring_up_node_wireguard;
58        acc = acc
59            .wrapping_add(wireguard as usize)
60            .wrapping_add(crate::kit_wireguard::LINK_TAG.len());
61    }
62    acc
63}
64
65/// Start the mail4agent mailbox on a background thread (`m4a` only).
66/// Same daemon the standalone `mail4agent` binary runs.
67#[cfg(feature = "m4a")]
68pub fn start_mailbox() -> Result<mail4agent::MailboxJoinHandle, mail4agent::MainError> {
69    mail4agent::spawn()
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[cfg(all(feature = "d2b", feature = "m4a", feature = "session-restore", feature = "wireguard"))]
77    #[test]
78    fn kit_names_the_four_cores() {
79        assert_eq!(
80            linked_core_names(),
81            ["dig2browser", "mail4agent", "session-restore", "wireguard"]
82        );
83        assert!(force_link() > 0);
84    }
85
86    #[test]
87    fn linked_names_match_enabled_features() {
88        let expected = cfg!(feature = "d2b") as usize
89            + cfg!(feature = "m4a") as usize
90            + cfg!(feature = "session-restore") as usize
91            + cfg!(feature = "wireguard") as usize;
92        assert_eq!(linked_core_names().len(), expected);
93    }
94
95    #[cfg(feature = "m4a")]
96    #[test]
97    fn kit_exposes_mailbox_automaton_entry() {
98        let start: fn() -> Result<mail4agent::MailboxJoinHandle, mail4agent::MainError> =
99            start_mailbox;
100        assert!(start as usize != 0);
101    }
102}