diff --git a/backend/ControlCenter/Hubs/AgentStatusHub.cs b/backend/ControlCenter/Hubs/AgentStatusHub.cs index f0d4497..c790b29 100644 --- a/backend/ControlCenter/Hubs/AgentStatusHub.cs +++ b/backend/ControlCenter/Hubs/AgentStatusHub.cs @@ -37,6 +37,33 @@ public class AgentStatusHub : Hub _logger = logger; } + /// + /// Broadcasts an agent status update to all connected clients. + /// + /// + /// Any connected client (or server-side caller) can invoke this method + /// to push a status update to every subscriber. The DTO is converted to + /// an record and relayed through the + /// callback. + /// + /// + /// The agent status update DTO to broadcast. + public async Task SendStatusUpdate(AgentStatusUpdateDto update) + { + _logger.LogInformation( + "Broadcasting status update for agent {AgentId}: {Status}", + update.AgentId, update.Status); + + var agentUpdate = update.ToUpdate(); + + // Broadcast to all connected clients + await Clients.All.AgentStatusChanged(agentUpdate); + + // Also push to the specific agent's group + var agentGroup = AgentGroupName(update.AgentId); + await Clients.Group(agentGroup).AgentStatusChanged(agentUpdate); + } + /// /// Adds the calling connection to the fleet group. /// Once joined, the client will receive all agent status changes diff --git a/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs index 3c9c97d..3edb603 100644 --- a/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs +++ b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs @@ -72,6 +72,80 @@ public record TaskProgressUpdate( string? Elapsed ); +/// +/// Data transfer object for broadcasting agent status updates +/// to all connected SignalR clients via the hub's SendStatusUpdate method. +/// +/// This DTO provides a mutable, serialization-friendly alternative to +/// for callers that construct updates +/// from external data sources (e.g., HTTP API payloads). +/// +public class AgentStatusUpdateDto +{ + /// + /// Agent identifier, e.g. "otto", "dex", "rex". + /// + public string AgentId { get; set; } = string.Empty; + + /// + /// Human-readable display name, e.g. "Otto", "Dex". + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// Role description, e.g. "Orchestrator Agent", "Backend Specialist". + /// + public string Role { get; set; } = string.Empty; + + /// + /// Current operational status of the agent as lowercase string: + /// "active", "idle", "thinking", "error". + /// + public string Status { get; set; } = string.Empty; + + /// + /// Description of the agent's current task, if any. + /// + public string? CurrentTask { get; set; } + + /// + /// Full session key, e.g. "agent:otto:telegram:direct:8787451565". + /// + public string SessionKey { get; set; } = string.Empty; + + /// + /// Communication channel, e.g. "telegram", "discord", "slack". + /// + public string Channel { get; set; } = string.Empty; + + /// + /// ISO 8601 timestamp of the agent's last activity. + /// + public string LastActivity { get; set; } = string.Empty; + + /// + /// Error message when the agent status is "error". + /// + public string? ErrorMessage { get; set; } + + /// + /// Converts this DTO to an immutable record + /// for use with the typed SignalR client interface. + /// + /// An with equivalent field values. + public AgentStatusUpdate ToUpdate() => new( + AgentId, + DisplayName, + Role, + Status, + CurrentTask, + SessionKey, + Channel, + LastActivity, + ErrorMessage + ); +} + /// /// Snapshot of an agent's full card data, sent on initial connection /// or when the fleet state is requested.