using System; using System.Threading; using System.Threading.Tasks; using Terius.Automate.Agent.Models; namespace Terius.Automate.Agent.Services; public sealed class AgentLoop(ApiClient api, JobExecutor executor) { public event Action? Activity; public event Action? ConnectionChanged; public event Action? JobCompleted; public async Task RunAsync(CancellationToken cancellationToken) { var failureCount = 0; var lastHeartbeat = DateTime.MinValue; while (!cancellationToken.IsCancellationRequested) { try { if (DateTime.UtcNow - lastHeartbeat >= TimeSpan.FromSeconds(30)) { await api.HeartbeatAsync(cancellationToken); lastHeartbeat = DateTime.UtcNow; ConnectionChanged?.Invoke(true); } var job = await api.GetNextJobAsync(cancellationToken); if (job is not null) { await ExecuteJobAsync(job, cancellationToken); } failureCount = 0; await Task.Delay(TimeSpan.FromSeconds(job is null ? 5 : 1), cancellationToken); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception exception) { failureCount++; LocalLog.Write($"Error de conexión: {exception}"); ConnectionChanged?.Invoke(false); Activity?.Invoke($"Conexión: {exception.Message}"); var delay = Math.Min(30, Math.Pow(2, Math.Min(failureCount, 5))); await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken); } } } private async Task ExecuteJobAsync(JobDefinition job, CancellationToken cancellationToken) { Activity?.Invoke($"Trabajo #{job.Id}: {job.AutomationName}"); await api.StartJobAsync(job.Id, cancellationToken); await api.LogAsync(job.Id, "info", "Ejecución iniciada por el agente Windows.", cancellationToken); ExecutionResult result; try { result = await executor.ExecuteAsync(job, message => Activity?.Invoke(message), cancellationToken); } catch (Exception exception) { LocalLog.Write($"Error ejecutando trabajo #{job.Id}: {exception}"); result = new ExecutionResult(false, [], exception.Message); } await api.LogAsync(job.Id, result.Success ? "info" : "error", result.Success ? "Ejecución completada." : result.Error, cancellationToken); await api.FinishJobAsync(job.Id, result, cancellationToken); JobCompleted?.Invoke(new JobExecutionSummary(job.Id, job.AutomationName, result.Success, DateTime.Now, result.Error)); Activity?.Invoke(result.Success ? $"Trabajo #{job.Id} completado." : $"Trabajo #{job.Id} falló: {result.Error}"); } }