//! Boucle de polling replay. //! //! Poll le serveur toutes les secondes pour recuperer les actions a executer. //! Quand une action est recue, l'execute via executor et rapporte le resultat. //! Gere le backoff exponentiel en cas d'indisponibilite du serveur. //! //! Reproduit le comportement de _replay_poll_loop dans agent_v1/main.py. use crate::capture; use crate::config::Config; use crate::executor; use crate::network; use crate::notifications; use crate::state::AgentState; use reqwest::blocking::Client; use std::thread; use std::time::Duration; /// Boucle de polling replay (tourne dans un thread dedie). /// /// - Poll GET /replay/next toutes les secondes /// - Execute l'action via executor /// - Capture un screenshot post-action /// - Rapporte le resultat via POST /replay/result /// - Backoff exponentiel si le serveur est indisponible pub fn replay_poll_loop(config: &Config, state: &AgentState) { let client = Client::new(); let mut poll_count: u64 = 0; let backoff = config.replay_poll_interval_s; let _backoff_max = 30.0_f64; let _backoff_factor = 1.5_f64; let mut replay_active = false; println!( "[REPLAY] Boucle replay demarree — poll toutes les {:.0}s sur {}", config.replay_poll_interval_s, config.server_url ); while state.is_running() { // Verifier l'arret d'urgence if state .emergency_stop .load(std::sync::atomic::Ordering::SeqCst) { if replay_active { println!("[REPLAY] ARRET D'URGENCE — replay interrompu"); replay_active = false; state.set_replay_active(false); } thread::sleep(Duration::from_secs(1)); continue; } poll_count += 1; // Log periodique toutes les 60s pour confirmer que la boucle tourne let polls_per_minute = (60.0 / backoff).ceil() as u64; if polls_per_minute > 0 && poll_count % polls_per_minute == 0 { println!( "[REPLAY] Poll #{} — session={} — serveur={}", poll_count, config.agent_session_id(), config.server_url, ); } match network::poll_next_action(&client, config) { Some(action) => { if !replay_active { replay_active = true; state.set_replay_active(true); notifications::replay_started("workflow"); println!("[REPLAY] Replay demarre"); } let action_type = action.action_type.clone(); let action_id = action.action_id.clone(); println!( "\n>>> REPLAY ACTION RECUE : {} (id={})", action_type, action_id ); // Obtenir les dimensions de l'ecran let (sw, sh) = capture::screen_dimensions().unwrap_or((1920, 1080)); // Executer l'action (avec config pour la resolution visuelle) println!(">>> Execution de l'action {}...", action_type); let mut result = executor::execute_action(&action, sw, sh, config); println!( ">>> Resultat execution : success={}, error={:?}", result.success, result.error ); // Capture screenshot post-action (apres 500ms) thread::sleep(Duration::from_millis(500)); if let Some(img) = capture::capture_screenshot() { let b64 = capture::screenshot_to_jpeg_base64(&img, 60); if !b64.is_empty() { result.screenshot = Some(b64); } } // Rapporter le resultat au serveur (TOUJOURS, meme en erreur) network::report_result(&client, config, &result); // Poll plus rapidement pour enchainer les actions thread::sleep(Duration::from_millis(200)); continue; } None => { if replay_active { println!("[REPLAY] Replay termine — retour en mode capture"); replay_active = false; state.set_replay_active(false); notifications::replay_finished(true); } } } let sleep_duration = Duration::from_secs_f64(backoff); thread::sleep(sleep_duration); } println!("[REPLAY] Boucle arretee."); }