1mod bundle_catalog;
4
5mod context_pack;
6
7mod harness_mcp_proxy;
8
9mod bundle_provider;
10
11mod git_worktree;
12pub mod plugins;
13pub mod provider_inventory;
14pub mod session_signals;
15
16mod host_directory_browser;
17
18mod environment_profiles;
19
20mod session_environment;
21
22mod worktree_service;
23
24pub(crate) use gate4agent_node_core::session_registry;
25
26mod standalone_workspace;
27
28#[cfg(windows)]
29mod workspace_file_windows;
30
31#[cfg(unix)]
32mod workspace_file_unix;
33
34#[cfg(feature = "dig2-station-probe")]
35mod dig2_station_lease;
36#[cfg(feature = "dig2-station-probe")]
37mod dig2_station_probe;
38pub mod kit;
39pub mod local_infra;
40pub mod node_control;
41#[cfg(feature = "wireguard")]
42pub(crate) use gate4agent_node_core::kit_wireguard;
43mod network_allowlist_catalog;
44pub(crate) use gate4agent_node_core::platform;
45pub mod enroll_client;
46pub mod uplink_journal;
47pub mod restream_session;
48pub mod uplink_calls;
49pub mod machine_env;
50pub use machine_env::collect as collect_machine_environment;
51pub mod restream_g4a_bridge;
52#[cfg(feature = "d2b")]
53pub mod browser_capture;
54pub mod grok_leader;
55pub mod m4a_accounts;
56pub mod m4a_attach;
57#[cfg(unix)]
58pub mod keystore_proxy;
59#[cfg(unix)]
61pub mod m4a_signer_proxy {
62 pub use super::keystore_proxy::*;
63}
64pub mod door;
65#[cfg(feature = "local-server")]
66pub mod local_server;
67pub mod wake_pipeline;
68#[cfg(unix)]
69pub mod isolation;
70pub use kit::{force_link, linked_core_names};
71#[cfg(feature = "m4a")]
72pub use kit::start_mailbox;
73#[cfg(feature = "m4a")]
74pub use kit::MailboxHandle;
75#[cfg(feature = "wireguard")]
76pub use kit_wireguard::{
77 bring_up_node_wireguard, node_wg_from_env, NodeWgPeerConfig, NodeWgPeerError,
78 LINK_TAG as WIREGUARD_LINK_TAG, WG_ADDRESS_ENV, WG_INTERFACE_ENV, WG_KEEPALIVE_ENV,
79 WG_LISTEN_PORT_ENV, WG_PEER_ADDRESS_ENV, WG_PEER_ENDPOINT_ENV, WG_PEER_KEY_ENV,
80 WG_PRIVATE_KEY_ENV,
81};
82mod provider_runtime;
83mod server;
84pub(crate) use gate4agent_node_core::spawn_spec;
85
86#[cfg(feature = "fixture")]
87pub use bundle_catalog::protect_bundle_source_tree_fixture;
88pub use bundle_catalog::{
89 BundleCatalog, BundleCatalogError, NodeBundle, NodeBundleError, NodeBundleFile,
90 MAX_BUNDLE_CATALOG_ENTRIES, MAX_BUNDLE_FILES, MAX_BUNDLE_FILE_BYTES, MAX_BUNDLE_PATH_BYTES,
91 MAX_BUNDLE_TOTAL_BYTES,
92};
93pub use environment_profiles::{
94 NodeEnvironmentProfile, NodeEnvironmentProfileError, MAX_NODE_ENVIRONMENT_PROFILES,
95};
96pub use gate4agent_node_protocol::WorktreeServiceMode;
97pub use gate4agent_runtime_native::{
98 orca_home_roots, HistorySourceLayout, NativeHistoryConfig, NativeHistoryRoot,
99};
100pub use network_allowlist_catalog::{
101 claude_allowed_domains_from_permits, claude_bash_sandbox_network_os_supported,
102 claude_settings_network_overlay_args, claude_station_network_settings_json,
103 codex_network_access_config_overlay, load_network_allowlist_catalog_file,
104 parse_network_allowlist_catalog_text, permit_peer_to_allowed_domain,
105 provider_native_mapping_supported, resolve_network_allowlist_catalog,
106 resolve_provider_native_launch_overlay, NetworkAllowlistCatalog, NetworkAllowlistCatalogError,
107 NetworkAllowlistEntry, NetworkPermitProtocol, NetworkPermitSketch, ProviderNativeNetworkSketch,
108 CLAUDE_STATION_NETWORK_SETTINGS_FILE, MAX_NETWORK_ALLOWLIST_CATALOG_ENTRIES,
109 MAX_NETWORK_PERMITS_PER_ENTRY, NETWORK_ALLOWLIST_CATALOG_ENV,
110 NETWORK_ALLOWLIST_CATALOG_SCHEMA_VERSION,
111};
112#[cfg(feature = "fixture")]
113pub use server::SpawnManagedWorktreeV2FailureProbe;
114pub use server::node_wake::{NodeLetter, NodeWake};
115pub use server::{
116 default_node_endpoint, default_state_path, NodeServer, NodeServerConfig, NodeServerError,
117 NodeShutdownHandle, WorkspaceConfig,
118};
119pub use session_environment::{
120 NodeSecretReference, NodeSecretResolveError, NodeSecretResolver, NodeSecretValue,
121 NodeSecretValueError, NodeSessionEnvironmentMutation, NodeSessionFile,
122 NodeSessionMaterializationProfile, NodeSessionMaterializationProfileError,
123 NodeSessionPathBinding, NodeSessionPathClass, MAX_NODE_SECRET_REFERENCE_BYTES,
124 MAX_NODE_SECRET_VALUE_BYTES, MAX_SESSION_ENVIRONMENT_ENTRIES,
125 MAX_SESSION_MATERIALIZATION_FILES, MAX_SESSION_MATERIALIZATION_FILE_BYTES,
126 MAX_SESSION_MATERIALIZATION_RELATIVE_PATH_BYTES,
127};
128pub use spawn_spec::{
129 SpawnProfileRegistry, SpawnProfileRegistryError, DEFAULT_SPAWN_PROFILE_ID, MAX_SPAWN_PROFILES,
130};
131pub use worktree_service::ManagedWorktreeProfile;
132
133#[cfg(windows)]
134pub use server::DEFAULT_NODE_ENDPOINT;
135
136pub use gate4agent_node_protocol as protocol;
137
138#[cfg(test)]
139mod kit_manifest {
140 #[test]
141 fn default_features_are_the_full_kit() {
142 let manifest = include_str!("../Cargo.toml");
143 let features = manifest
144 .split("[features]")
145 .nth(1)
146 .expect("features section")
147 .split("\n[")
148 .next()
149 .expect("features body");
150 assert!(
151 features.contains("default = [\"kit\"]"),
152 "default build must include the kit"
153 );
154 let kit = feature_array(features, "kit");
155 for core in ["m4a", "d2b", "session-restore", "wireguard", "memory"] {
156 assert!(
157 kit.iter().any(|item| item == core),
158 "kit feature missing {core}: {kit:?}"
159 );
160 }
161 assert!(
162 !features.contains("bare = "),
163 "there is no light mode"
164 );
165 let m4a = feature_array(features, "m4a");
166 assert_eq!(m4a, ["dep:mail4agent"]);
167 let d2b = feature_array(features, "d2b");
168 assert_eq!(d2b, ["dep:dig2browser", "dep:tokio-tungstenite", "dep:futures-util", "dep:image"]);
170 let wg = feature_array(features, "wireguard");
171 assert_eq!(wg, ["gate4agent-node-core/wireguard"]);
172 let code = manifest
173 .lines()
174 .map(|line| line.split('#').next().unwrap_or(""))
175 .collect::<Vec<_>>()
176 .join("\n");
177 assert!(
178 !code.contains("hatchery-tui") && !code.contains("hatchery_tui"),
179 "node must not depend on the hatchery TUI"
180 );
181 assert!(manifest.contains("dig2browser = { version = \"0.5.3\""));
182 assert!(manifest.contains("mail4agent = { version = \"0.4.17\""));
183 assert!(!manifest.contains("[patch.crates-io]"), "published state has no patch table");
184 assert!(!manifest.contains("../../../"), "no path dependency leaves the workspace");
185 assert!(manifest.contains("claude-session-restore = { version = \"0.1.3\""));
186 assert!(manifest.contains("codex-session-restore = { version = \"0.1.3\""));
187 assert!(manifest.contains("grok-session-restore"));
188 assert!(manifest.contains("kimi-session-restore"));
189 }
190
191 fn feature_array(features: &str, name: &str) -> Vec<String> {
192 let line_start = features
193 .find(&format!("{name} = "))
194 .unwrap_or_else(|| panic!("missing feature {name}"));
195 let rest = &features[line_start + name.len() + 3..];
196 if rest.starts_with("[]") {
197 return Vec::new();
198 }
199 assert!(rest.starts_with("["), "{name} must be an array");
200 let end = rest.find(']').unwrap_or_else(|| panic!("{name} array"));
201 rest[1..end]
202 .split(',')
203 .map(|item| item.trim().trim_matches('"').to_owned())
204 .filter(|item| !item.is_empty())
205 .collect()
206 }
207}
208
209
210#[cfg(test)]
211mod deploy_artifacts {
212 #[test]
217 fn service_unit_is_mode1_kit_entry() {
218 let unit = include_str!("../deploy/node/gate4agent-node.service");
219 assert!(unit.contains("ExecStart=/usr/local/bin/gate4agent-node"));
220 assert!(unit.contains("gate4agent node (standard kit)"));
221 assert!(
222 !unit.contains("INSTALL_PROVIDER_CLIS"),
223 "mode 1 unit must not install provider CLIs"
224 );
225 assert!(
226 unit.contains("GATE4AGENT_WG_INTERFACE") || unit.contains("WireGuard"),
227 "unit must document optional WG env"
228 );
229 }
230
231 #[test]
232 fn dockerfile_and_build_script_are_mode2() {
233 let dockerfile = include_str!("../deploy/node/Dockerfile");
234 assert!(dockerfile.contains("cargo build --locked --release -p hatchery-node"));
235 assert!(dockerfile.contains("ENTRYPOINT [\"gate4agent-node\"]"));
236 assert!(!dockerfile.contains("COPY mail4agent"));
237 assert!(!dockerfile.contains("COPY gate4agent"), "crates.io only: no sibling checkouts");
238 assert!(
239 !dockerfile.contains("GATE4AGENT_NODE_TOKEN"),
240 "image must not bake a node token"
241 );
242 let build = include_str!("../deploy/node/docker-build.sh");
243 assert!(build.contains("docker build"));
244 assert!(build.contains("gate4agent-node:kit"));
245 assert!(!build.contains("copy_tree \"$WS/mail4agent\""));
246 }
247
248 #[test]
249 fn qemu_guest_script_is_mode3_underlay_only() {
250 let script = include_str!("../deploy/node/qemu-guest.sh");
251 assert!(script.contains("Mode 3"));
252 assert!(script.contains("QEMU_NET"));
253 assert!(script.contains("tap") && script.contains("user"));
254 assert!(
255 script.contains("Kernel WireGuard is not a QEMU network"),
256 "script must keep WG out of the QEMU NIC story"
257 );
258 assert!(script.contains("GATE4AGENT_WG_"));
259 assert!(script.contains("gate4agent-node"));
260 assert!(
262 !script.contains("cargo test"),
263 "qemu guest script must not be wired into cargo test"
264 );
265 }
266
267 #[test]
268 fn deploy_readme_names_three_modes() {
269 let readme = include_str!("../deploy/node/README.md");
270 assert!(readme.contains("Mode 1 — service"));
271 assert!(readme.contains("Mode 2 — container"));
272 assert!(readme.contains("Mode 3 — QEMU guest"));
273 assert!(readme.contains("no light mode"));
274 }
275}