agentmux_cef\commands/
backend.rs1use std::sync::Arc;
8
9use crate::state::AppState;
10
11pub fn get_backend_endpoints(state: &Arc<AppState>) -> Result<serde_json::Value, String> {
13 let endpoints = state.backend_endpoints.lock();
14
15 if endpoints.ws_endpoint.is_empty() {
16 return Err("Backend not ready yet".to_string());
17 }
18
19 Ok(serde_json::json!({
20 "ws": endpoints.ws_endpoint,
21 "web": endpoints.web_endpoint,
22 }))
23}
24
25pub fn get_wave_init_opts(state: &Arc<AppState>) -> Result<serde_json::Value, String> {
27 let client_id = state.client_id.lock();
28 let window_id = state.window_id.lock();
29 let tab_id = state.active_tab_id.lock();
30
31 if client_id.is_none() || window_id.is_none() || tab_id.is_none() {
32 return Err("Window state not initialized yet".to_string());
33 }
34
35 Ok(serde_json::json!({
36 "clientId": client_id.as_ref().unwrap(),
37 "windowId": window_id.as_ref().unwrap(),
38 "tabId": tab_id.as_ref().unwrap(),
39 "activate": true,
40 "primaryTabStartup": true,
41 }))
42}
43
44pub fn get_backend_info(state: &Arc<AppState>) -> serde_json::Value {
46 let current_version = env!("CARGO_PKG_VERSION");
47 let endpoints = state.backend_endpoints.lock();
48 let pid = *state.backend_pid.lock();
49 let started_at = state.backend_started_at.lock().clone();
50
51 serde_json::json!({
52 "pid": pid,
53 "started_at": started_at,
54 "web_endpoint": endpoints.web_endpoint,
55 "version": current_version,
56 })
57}
58
59pub fn fe_log(args: &serde_json::Value) -> serde_json::Value {
61 let msg = args
62 .get("msg")
63 .and_then(|v| v.as_str())
64 .unwrap_or_default();
65 tracing::info!("[frontend] {}", msg);
66 serde_json::Value::Null
67}
68
69pub fn fe_log_structured(args: &serde_json::Value) -> serde_json::Value {
71 let level = args.get("level").and_then(|v| v.as_str()).unwrap_or("info");
72 let module = args.get("module").and_then(|v| v.as_str()).unwrap_or("unknown");
73 let message = args.get("message").and_then(|v| v.as_str()).unwrap_or("");
74 let data = args.get("data");
75
76 match level {
77 "error" => tracing::error!(module = %module, data = ?data, "[fe] {}", message),
78 "warn" => tracing::warn!(module = %module, data = ?data, "[fe] {}", message),
79 "debug" => tracing::debug!(module = %module, data = ?data, "[fe] {}", message),
80 _ => tracing::info!(module = %module, data = ?data, "[fe] {}", message),
81 }
82 serde_json::Value::Null
83}
84
85pub async fn restart_backend(state: Arc<AppState>) -> Result<serde_json::Value, String> {
96 tracing::info!("[restart_backend] user-initiated restart");
97
98 if std::env::var("AGENTMUX_BACKEND_PID").is_ok() {
99 let msg = "backend lifecycle is owned by the launcher in this run \
100 (AGENTMUX_BACKEND_PID set); host-initiated restart is \
101 disabled until Phase B.2 wires the launcher RPC. \
102 Restart the entire app to get a fresh srv.";
103 tracing::warn!("[restart_backend] refused: {}", msg);
104 return Err(msg.to_string());
105 }
106
107 {
109 let mut sidecar = state.sidecar_child.lock();
110 if let Some(ref mut child) = *sidecar {
111 let _ = child.kill();
112 tracing::info!("[restart_backend] killed stale sidecar");
113 }
114 *sidecar = None;
115 }
116
117 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
119
120 let result = crate::sidecar::spawn_backend(&state).await?;
122
123 {
125 let mut endpoints = state.backend_endpoints.lock();
126 endpoints.ws_endpoint = result.ws_endpoint.clone();
127 endpoints.web_endpoint = result.web_endpoint.clone();
128 }
129
130 let payload = serde_json::json!({
132 "ws": result.ws_endpoint,
133 "web": result.web_endpoint,
134 });
135 crate::events::emit_event_from_state(&state, "backend-ready", &payload);
136
137 tracing::info!(
138 "[restart_backend] backend restarted: ws={} web={}",
139 result.ws_endpoint,
140 result.web_endpoint
141 );
142
143 Ok(serde_json::Value::Null)
144}
145
146pub fn set_window_init_status(state: &Arc<AppState>, args: &serde_json::Value) -> serde_json::Value {
148 let status = args
149 .get("status")
150 .and_then(|v| v.as_str())
151 .unwrap_or_default();
152 let label = args
153 .get("label")
154 .and_then(|v| v.as_str())
155 .unwrap_or("main")
156 .to_string();
157 tracing::debug!("set_window_init_status status={} label={}", status, label);
158 *state.window_init_status.lock() = status.to_string();
159 #[cfg(target_os = "windows")]
162 if status == "ready" {
163 crate::commands::window::capture_hwnd_for_label(state, &label);
164 }
165 serde_json::Value::Null
166}