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