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);
}
}