using ControlCenter.Api.Dtos; using Microsoft.AspNetCore.SignalR; namespace ControlCenter.Api.Hubs; /// /// SignalR hub for broadcasting agent status updates to connected clients. /// /// /// Clients call to broadcast a status change, /// and the hub relays it to all connected clients via the /// callback. /// /// /// /// Server-side code should use /// via IHubContext<AgentStatusHub, IAgentStatusClient> for background-service broadcasts. /// /// /// /// Architecture note: This hub bridges OpenClaw Gateway events to SignalR clients. /// A background service subscribes to Gateway events and pushes them through /// this hub's extension methods. /// /// public class AgentStatusHub : Hub { private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// Logger for diagnostic output. public AgentStatusHub(ILogger logger) { _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 update is relayed /// through the callback. /// /// /// The agent status update payload to broadcast. public async Task SendStatusUpdate(AgentStatusUpdateDto update) { _logger.LogInformation( "Broadcasting status update for agent {AgentId}: {Status}", update.AgentId, update.Status); await Clients.All.AgentStatusChanged(update); } /// /// Adds the calling connection to the fleet group. /// Once joined, the client will receive all agent status updates. /// public async Task JoinFleet() { await Groups.AddToGroupAsync(Context.ConnectionId, FleetGroupName); _logger.LogDebug("Connection {ConnectionId} joined fleet group", Context.ConnectionId); } /// /// Removes the calling connection from the fleet group. /// public async Task LeaveFleet() { await Groups.RemoveFromGroupAsync(Context.ConnectionId, FleetGroupName); _logger.LogDebug("Connection {ConnectionId} left fleet group", Context.ConnectionId); } /// /// Overrides to log disconnections. /// SignalR automatically removes disconnected connections from all groups. /// /// Exception that caused the disconnection, if any. public override Task OnDisconnectedAsync(Exception? exception) { _logger.LogDebug("Connection {ConnectionId} disconnected", Context.ConnectionId); return base.OnDisconnectedAsync(exception); } /// /// The SignalR group name for the entire fleet (all agents). /// internal const string FleetGroupName = "fleet"; } /// /// Strongly-typed client interface for the AgentStatus SignalR hub. /// Defines the methods the server can invoke on connected clients /// to push real-time agent status updates. /// public interface IAgentStatusClient { /// /// Pushes an agent status change to all subscribed clients. /// Fired whenever an agent's operational status changes /// (e.g., idle → active, active → thinking, active → error). /// /// The full status update payload. /// A Task that completes when the client has processed the update. Task AgentStatusChanged(AgentStatusUpdateDto update); } /// /// Extension methods for pushing real-time agent updates through /// the of . /// /// /// These methods are intended to be called from background services /// or other server-side code that detects an agent state change, /// using the injected IHubContext<AgentStatusHub, IAgentStatusClient>. /// /// public static class AgentStatusHubExtensions { /// /// Pushes an agent status update to all connected clients. /// /// /// Call this from any background service when an agent's /// operational status changes (e.g., the Gateway reports a /// session transition from "running" to "done"). /// /// /// The hub context injected via DI. /// The agent status update payload. /// A Task that completes when the message has been sent to all clients. public static async Task PushStatusUpdateAsync( this IHubContext hubContext, AgentStatusUpdateDto update) { await hubContext.Clients.All.AgentStatusChanged(update); } /// /// Pushes an agent status update to clients subscribed to the fleet group. /// /// The hub context injected via DI. /// The agent status update payload. /// A Task that completes when the message has been sent to the fleet group. public static async Task PushStatusUpdateToFleetAsync( this IHubContext hubContext, AgentStatusUpdateDto update) { await hubContext.Clients.Group(AgentStatusHub.FleetGroupName) .AgentStatusChanged(update); } }