155 lines
5.9 KiB
C#
155 lines
5.9 KiB
C#
using ControlCenter.Api.Dtos;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
namespace ControlCenter.Api.Hubs;
|
|
|
|
/// <summary>
|
|
/// SignalR hub for broadcasting agent status updates to connected clients.
|
|
///
|
|
/// <para>
|
|
/// Clients call <see cref="SendStatusUpdate"/> to broadcast a status change,
|
|
/// and the hub relays it to all connected clients via the
|
|
/// <see cref="IAgentStatusClient.AgentStatusChanged"/> callback.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Server-side code should use <see cref="AgentStatusHubExtensions.PushStatusUpdateAsync"/>
|
|
/// via <c>IHubContext<AgentStatusHub, IAgentStatusClient></c> for background-service broadcasts.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// </summary>
|
|
public class AgentStatusHub : Hub<IAgentStatusClient>
|
|
{
|
|
private readonly ILogger<AgentStatusHub> _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="AgentStatusHub"/> class.
|
|
/// </summary>
|
|
/// <param name="logger">Logger for diagnostic output.</param>
|
|
public AgentStatusHub(ILogger<AgentStatusHub> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Broadcasts an agent status update to all connected clients.
|
|
///
|
|
/// <para>
|
|
/// 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 <see cref="IAgentStatusClient.AgentStatusChanged"/> callback.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="update">The agent status update payload to broadcast.</param>
|
|
public async Task SendStatusUpdate(AgentStatusUpdateDto update)
|
|
{
|
|
_logger.LogInformation(
|
|
"Broadcasting status update for agent {AgentId}: {Status}",
|
|
update.AgentId, update.Status);
|
|
|
|
await Clients.All.AgentStatusChanged(update);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the calling connection to the fleet group.
|
|
/// Once joined, the client will receive all agent status updates.
|
|
/// </summary>
|
|
public async Task JoinFleet()
|
|
{
|
|
await Groups.AddToGroupAsync(Context.ConnectionId, FleetGroupName);
|
|
_logger.LogDebug("Connection {ConnectionId} joined fleet group", Context.ConnectionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the calling connection from the fleet group.
|
|
/// </summary>
|
|
public async Task LeaveFleet()
|
|
{
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, FleetGroupName);
|
|
_logger.LogDebug("Connection {ConnectionId} left fleet group", Context.ConnectionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides <see cref="Hub{T}.OnDisconnectedAsync"/> to log disconnections.
|
|
/// SignalR automatically removes disconnected connections from all groups.
|
|
/// </summary>
|
|
/// <param name="exception">Exception that caused the disconnection, if any.</param>
|
|
public override Task OnDisconnectedAsync(Exception? exception)
|
|
{
|
|
_logger.LogDebug("Connection {ConnectionId} disconnected", Context.ConnectionId);
|
|
return base.OnDisconnectedAsync(exception);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SignalR group name for the entire fleet (all agents).
|
|
/// </summary>
|
|
internal const string FleetGroupName = "fleet";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public interface IAgentStatusClient
|
|
{
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
/// <param name="update">The full status update payload.</param>
|
|
/// <returns>A Task that completes when the client has processed the update.</returns>
|
|
Task AgentStatusChanged(AgentStatusUpdateDto update);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extension methods for pushing real-time agent updates through
|
|
/// the <see cref="IHubContext{T}"/> of <see cref="AgentStatusHub"/>.
|
|
///
|
|
/// <para>
|
|
/// These methods are intended to be called from background services
|
|
/// or other server-side code that detects an agent state change,
|
|
/// using the injected <c>IHubContext<AgentStatusHub, IAgentStatusClient></c>.
|
|
/// </para>
|
|
/// </summary>
|
|
public static class AgentStatusHubExtensions
|
|
{
|
|
/// <summary>
|
|
/// Pushes an agent status update to all connected clients.
|
|
///
|
|
/// <para>
|
|
/// 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").
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="hubContext">The hub context injected via DI.</param>
|
|
/// <param name="update">The agent status update payload.</param>
|
|
/// <returns>A Task that completes when the message has been sent to all clients.</returns>
|
|
public static async Task PushStatusUpdateAsync(
|
|
this IHubContext<AgentStatusHub, IAgentStatusClient> hubContext,
|
|
AgentStatusUpdateDto update)
|
|
{
|
|
await hubContext.Clients.All.AgentStatusChanged(update);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pushes an agent status update to clients subscribed to the fleet group.
|
|
/// </summary>
|
|
/// <param name="hubContext">The hub context injected via DI.</param>
|
|
/// <param name="update">The agent status update payload.</param>
|
|
/// <returns>A Task that completes when the message has been sent to the fleet group.</returns>
|
|
public static async Task PushStatusUpdateToFleetAsync(
|
|
this IHubContext<AgentStatusHub, IAgentStatusClient> hubContext,
|
|
AgentStatusUpdateDto update)
|
|
{
|
|
await hubContext.Clients.Group(AgentStatusHub.FleetGroupName)
|
|
.AgentStatusChanged(update);
|
|
}
|
|
} |