1use super::*;
7
8pub struct ReducerPlumbing {
10 pub srv_state: Arc<tokio::sync::Mutex<state::State>>,
11 pub srv_events_tx: tokio::sync::broadcast::Sender<agentmux_common::ipc::Event>,
12 #[cfg(target_os = "windows")]
13 pub srv_event_log: Arc<event_log::EventLog>,
14}
15
16pub async fn spawn_reducer_plumbing(
28 mstore: &Arc<Store>,
29 event_bus: &Arc<EventBus>,
30 subagent_watcher: &Arc<backend::subagent_watcher::SubagentWatcher>,
31) -> ReducerPlumbing {
32 let mstore_for_persist = Arc::clone(mstore);
33 let srv_state = std::sync::Arc::new(tokio::sync::Mutex::new(state::State::default()));
34 let (srv_events_tx, _) =
35 tokio::sync::broadcast::channel::<agentmux_common::ipc::Event>(1024);
36 let srv_event_log = std::sync::Arc::new(event_log::EventLog::new(Some(
37 base::get_mux_data_dir().join("srv-events.log"),
38 )));
39
40 persist::bootstrap_state_from_mstore(&srv_state, &mstore_for_persist).await;
44
45 let disk_writer_rx = srv_events_tx.subscribe();
48 let log_for_writer = std::sync::Arc::clone(&srv_event_log);
49 tokio::spawn(event_log::run_disk_writer(log_for_writer, disk_writer_rx));
50 let subscriber_rx = srv_events_tx.subscribe();
51 persist_subscriber::spawn_persist_subscriber(
52 subscriber_rx,
53 std::sync::Arc::clone(&mstore_for_persist),
54 std::sync::Arc::clone(&srv_state),
55 );
56
57 let bridge_rx = srv_events_tx.subscribe();
70 let bridge_handle = server::mux_obj_bridge::spawn_mux_obj_bridge(
71 bridge_rx,
72 std::sync::Arc::clone(&mstore_for_persist),
73 std::sync::Arc::clone(event_bus),
74 );
75 tokio::spawn(async move {
76 match bridge_handle.await {
77 Ok(()) => tracing::info!(
78 target: "wave-obj-bridge",
79 "bridge task exited normally (events channel closed at srv shutdown)"
80 ),
81 Err(e) if e.is_panic() => tracing::error!(
82 target: "wave-obj-bridge",
83 "bridge task PANICKED at top level — frontend MOS will stop receiving updates until srv restart. Panic: {}",
84 e
85 ),
86 Err(e) => tracing::error!(
87 target: "wave-obj-bridge",
88 "bridge task terminated unexpectedly (non-panic JoinError): {}",
89 e
90 ),
91 }
92 });
93
94 let block_prune_rx = srv_events_tx.subscribe();
101 backend::subagent_watcher::spawn_block_prune_subscriber(subagent_watcher.clone(), block_prune_rx);
102
103 ReducerPlumbing {
104 srv_state,
105 srv_events_tx,
106 #[cfg(target_os = "windows")]
107 srv_event_log,
108 }
109}
110
111pub fn build_app_state(
116 config: &config::Config,
117 version: String,
118 stores: Stores,
119 bg: BackgroundSubsystems,
120 net: &NetworkBundle,
121 reducer: &ReducerPlumbing,
122) -> AppState {
123 let shared_store_for_cron = stores.shared_store.clone();
125 net.process_tracker.set_config(bg.config_watcher.clone());
127 let broker = bg.broker;
128
129 let dev_proxy = crate::backend::dev_proxy::DevProxyRegistry::new();
134
135 let viewer = crate::backend::viewer::ViewerService::new(Some(base::get_mux_data_dir()), &broker);
139 viewer.install_global();
140
141 let fleet_feed = Arc::new(
142 crate::backend::fleet_feed::FleetFeed::new(
143 net.hostname.clone(),
144 crate::backend::reactive::registry::local_channel_id(),
145 version.clone(),
146 )
147 .with_install_id(crate::backend::fleet_source::install_id())
148 .with_viewer_port(viewer.advertised_port_handle()),
149 );
150
151 AppState {
152 auth_key: config.auth_key.clone(),
153 lan_key: config.lan_key.clone(),
154 boot_id: Arc::from(uuid::Uuid::new_v4().to_string()),
155 version,
156 hostname: net.hostname.clone(),
157 app_path: config.app_path.clone(),
158 mstore: stores.mstore,
159 shared_store: stores.shared_store,
160 id_store: stores.id_store,
161 identity_store: stores.identity_store,
162 filestore: stores.filestore,
163 global_transcript_store: stores.global_transcript_store,
164 event_bus: bg.event_bus,
165 broker: broker.clone(),
166 reactive_handler: bg.reactive_handler,
167 poller: bg.poller,
168 config_watcher: bg.config_watcher,
169 messagebus: bg.messagebus,
170 subagent_watcher: bg.subagent_watcher,
171 history_service: bg.history_service,
172 lan_discovery: net.lan_discovery.clone(),
173 lan_listeners: net.lan_listeners.clone(),
174 fleet_feed,
175 viewer,
176 lsp_supervisor: net.lsp_supervisor.clone(),
177 local_web_url: net.local_web_url.clone(),
178 http_client: reqwest::Client::builder()
186 .timeout(std::time::Duration::from_secs(10))
187 .build()
188 .unwrap_or_else(|e| {
189 tracing::warn!(error = %e, "failed to build http_client with timeout, using default");
190 reqwest::Client::new()
191 }),
192 host_ipc: std::sync::Arc::new(tokio::sync::Mutex::new(None)),
193 host_reg_secret: config.host_reg_secret.clone(),
194 process_tracker: net.process_tracker.clone(),
195 process_broker: net.process_broker.clone(),
196 dock_snapshots: std::sync::Arc::new(crate::backend::dock_snapshot::DockSnapshotCache::new()),
197 pending_background_pids: std::sync::Arc::new(crate::backend::pending_background_pids::PendingBackgroundPids::new()),
198 narrated_events: Arc::new(crate::backend::narrated_events::NarratedEvents::new()),
199 srv_state: std::sync::Arc::clone(&reducer.srv_state),
204 srv_events_tx: reducer.srv_events_tx.clone(),
205 saga_id_alloc: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(stores.saga_id_seed)),
210 saga_log: stores.saga_log,
211 auth_session_manager: {
212 let mgr = std::sync::Arc::new(crate::identity::auth_session::AuthSessionManager::new());
213 mgr.spawn_sweeper();
215 mgr
216 },
217 install_sessions: crate::server::install_handlers::InstallSessionRegistry::new(),
218 container_manager: {
219 let handle = std::sync::Arc::new(
225 crate::backend::container::ContainerRuntimeHandle::connect_at_startup(),
226 );
227 handle.set_dev_proxy_registry(dev_proxy.clone());
228 let handle_check = handle.clone();
229 tokio::spawn(async move {
230 if handle_check.is_available().await {
231 tracing::info!("Docker daemon available — container agent panes enabled");
232 } else {
233 tracing::warn!(
234 "Docker daemon not reachable at startup; container agent panes will \
235 become available automatically once Docker is running"
236 );
237 }
238 });
239 handle
240 },
241 dev_proxy,
242 shell_sessions: crate::backend::shell_node::ShellSessionRegistry::new(),
243 cron_scheduler: crate::backend::cron::CronScheduler::new(
244 shared_store_for_cron,
245 reqwest::Client::new(),
246 net.local_web_url.clone(),
247 config.auth_key.clone(),
248 Arc::clone(&broker),
249 ),
250 editor_file_watcher: bg.editor_file_watcher,
251 media_file_watcher: bg.media_file_watcher,
252 files_watcher: bg.files_watcher,
253 fs_watch_pool: bg.fs_watch_pool,
254 }
255}
256
257#[cfg(target_os = "windows")]
265pub fn bind_srv_pipe_ipc(
266 version: &str,
267 srv_state: std::sync::Arc<tokio::sync::Mutex<state::State>>,
268 srv_events_tx: tokio::sync::broadcast::Sender<agentmux_common::ipc::Event>,
269 srv_event_log: std::sync::Arc<event_log::EventLog>,
270) {
271 if let Ok(srv_pipe_path) = std::env::var("AGENTMUX_SRV_PIPE_PATH") {
272 if !srv_pipe_path.is_empty() {
273 match crate::srv_ipc::server::bind_first_pipe_instance(&srv_pipe_path) {
274 Ok(first_pipe) => {
275 let srv_ctx = crate::srv_ipc::ServerCtx {
280 srv_pid: std::process::id(),
281 srv_version: version.to_string(),
282 state: srv_state,
283 events_tx: srv_events_tx,
284 event_log: srv_event_log,
285 };
286 let _srv_ipc_handle = crate::srv_ipc::run_srv_ipc_server(
287 srv_pipe_path.clone(),
288 first_pipe,
289 srv_ctx,
290 );
291 tracing::info!(
292 target: "srv-ipc",
293 "[srv-ipc] bound + spawned on {}",
294 srv_pipe_path
295 );
296 }
297 Err(e) => {
298 tracing::error!(
299 target: "srv-ipc",
300 "[srv-ipc] bind failed on {}: {} — srv runs without pipe IPC",
301 srv_pipe_path,
302 e
303 );
304 }
305 }
306 }
307 }
308}