feat(CUB-19): implement AgentStatus SignalR hub for real-time updates
- Add AgentStatusHub with typed IAgentStatusClient interface
- Hub at /hubs/agent-status (matches design spec)
- Fleet group + per-agent group subscription
- AgentStatusChanged and AgentTaskProgress push events
- Extension methods for server-side push via IHubContext
- Add GatewayEventBridgeService background service
- Connects to OpenClaw Gateway WebSocket (v3 protocol)
- Handles challenge → connect → hello-ok handshake
- Bridges sessions.changed, session.message, session.tool events
- Translates Gateway session status to AgentStatus enum
- Maintains in-memory fleet state for snapshot queries
- Add REST API controllers
- GET /api/agents — fleet status snapshot
- GET /api/agents/{agentId} — single agent status
- GET /api/logs/{agentId} — agent session logs (stub)
- POST /api/command/stop/{agentId} — stop agent
- POST /api/command/restart/{agentId} — restart agent
- POST /api/command/steer/{agentId} — inject message
- Add models matching TypeScript spec interfaces
- AgentStatusUpdate, TaskProgressUpdate, AgentCardData
- AgentStatus enum (active/idle/thinking/error)
- Configure CORS with credentials for SignalR WebSocket
- Configure Swagger/OpenAPI with XML doc comments
- Agent role map matching frontend AGENT_ROLES constant
This commit is contained in:
71
backend/ControlCenter/Controllers/AgentsController.cs
Normal file
71
backend/ControlCenter/Controllers/AgentsController.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ControlCenter.Services;
|
||||
|
||||
namespace ControlCenter.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// REST API for querying agent fleet status.
|
||||
/// Provides the initial data load for the Command Hub,
|
||||
/// while real-time updates flow through the AgentStatus SignalR hub.
|
||||
///
|
||||
/// <para>API contract for Rex (Frontend):</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><c>GET /api/agents</c> — Returns all known agents with current status</item>
|
||||
/// <item><c>GET /api/agents/{agentId}</c> — Returns a specific agent's status</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AgentsController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<AgentsController> _logger;
|
||||
private readonly GatewayEventBridgeService _bridgeService;
|
||||
|
||||
public AgentsController(
|
||||
ILogger<AgentsController> logger,
|
||||
GatewayEventBridgeService bridgeService)
|
||||
{
|
||||
_logger = logger;
|
||||
_bridgeService = bridgeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current fleet status — all known agents with their latest state.
|
||||
/// This is the initial load endpoint; subsequent updates arrive via SignalR.
|
||||
/// </summary>
|
||||
/// <returns>An array of agent card data for the entire fleet.</returns>
|
||||
/// <response code="200">Returns the fleet snapshot.</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(AgentCardData[]), StatusCodes.Status200OK)]
|
||||
public IActionResult GetAgents()
|
||||
{
|
||||
var snapshot = _bridgeService.GetFleetSnapshot();
|
||||
_logger.LogDebug("Fleet snapshot requested: {Count} agents", snapshot.Length);
|
||||
return Ok(snapshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of a specific agent.
|
||||
/// </summary>
|
||||
/// <param name="agentId">The agent identifier, e.g. "otto", "dex".</param>
|
||||
/// <returns>The agent's current card data.</returns>
|
||||
/// <response code="200">Returns the agent's status.</response>
|
||||
/// <response code="404">Agent not found in the fleet state.</response>
|
||||
[HttpGet("{agentId}")]
|
||||
[ProducesResponseType(typeof(AgentCardData), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetAgent(string agentId)
|
||||
{
|
||||
var snapshot = _bridgeService.GetFleetSnapshot();
|
||||
var agent = snapshot.FirstOrDefault(a =>
|
||||
a.Id.Equals(agentId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (agent is null)
|
||||
{
|
||||
_logger.LogWarning("Agent not found: {AgentId}", agentId);
|
||||
return NotFound(new { error = $"Agent '{agentId}' not found" });
|
||||
}
|
||||
|
||||
return Ok(agent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user