diff --git a/backend/ControlCenter/ControlCenter.csproj b/backend/ControlCenter/ControlCenter.csproj
new file mode 100644
index 0000000..66ebc83
--- /dev/null
+++ b/backend/ControlCenter/ControlCenter.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+ CS1591
+ ControlCenter
+ ControlCenter
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/ControlCenter/Controllers/AgentsController.cs b/backend/ControlCenter/Controllers/AgentsController.cs
new file mode 100644
index 0000000..92da9b3
--- /dev/null
+++ b/backend/ControlCenter/Controllers/AgentsController.cs
@@ -0,0 +1,71 @@
+using Microsoft.AspNetCore.Mvc;
+using ControlCenter.Services;
+
+namespace ControlCenter.Controllers;
+
+///
+/// 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.
+///
+/// API contract for Rex (Frontend):
+///
+/// GET /api/agents — Returns all known agents with current status
+/// GET /api/agents/{agentId} — Returns a specific agent's status
+///
+///
+[ApiController]
+[Route("api/[controller]")]
+public class AgentsController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly GatewayEventBridgeService _bridgeService;
+
+ public AgentsController(
+ ILogger logger,
+ GatewayEventBridgeService bridgeService)
+ {
+ _logger = logger;
+ _bridgeService = bridgeService;
+ }
+
+ ///
+ /// Gets the current fleet status — all known agents with their latest state.
+ /// This is the initial load endpoint; subsequent updates arrive via SignalR.
+ ///
+ /// An array of agent card data for the entire fleet.
+ /// Returns the fleet snapshot.
+ [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);
+ }
+
+ ///
+ /// Gets the current status of a specific agent.
+ ///
+ /// The agent identifier, e.g. "otto", "dex".
+ /// The agent's current card data.
+ /// Returns the agent's status.
+ /// Agent not found in the fleet state.
+ [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);
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/Controllers/CommandController.cs b/backend/ControlCenter/Controllers/CommandController.cs
new file mode 100644
index 0000000..0dc75bc
--- /dev/null
+++ b/backend/ControlCenter/Controllers/CommandController.cs
@@ -0,0 +1,122 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace ControlCenter.Controllers;
+
+///
+/// REST API for sending control commands to agents.
+/// Provides the Command Hub's action endpoints for agent lifecycle control.
+///
+/// API contract for Rex (Frontend):
+///
+/// POST /api/command/stop/{agentId} — Stop/abort an agent's active session
+/// POST /api/command/restart/{agentId} — Restart an agent
+/// POST /api/command/steer/{agentId} — Inject a message into an agent's session
+///
+///
+/// Commands are forwarded to the OpenClaw Gateway via the
+/// WebSocket bridge service. The Gateway handles the actual execution.
+///
+[ApiController]
+[Route("api/[controller]")]
+public class CommandController : ControllerBase
+{
+ private readonly ILogger _logger;
+
+ public CommandController(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Stops (aborts) an agent's active session.
+ /// Sends an abort command to the OpenClaw Gateway.
+ ///
+ /// The agent identifier to stop.
+ /// Confirmation of the stop command.
+ /// Stop command sent successfully.
+ /// No active session found for the agent.
+ [HttpPost("stop/{agentId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public IActionResult StopAgent(string agentId)
+ {
+ _logger.LogInformation("Stop command received for agent {AgentId}", agentId);
+
+ // TODO: Forward to Gateway via bridge service
+ // await _bridgeService.SendRpcAsync("sessions.abort", new { sessionKey = ... });
+
+ return Ok(new
+ {
+ agentId,
+ command = "stop",
+ status = "sent",
+ timestamp = DateTime.UtcNow.ToString("o")
+ });
+ }
+
+ ///
+ /// Restarts an agent by aborting the current session and allowing
+ /// a new one to start on the next incoming message.
+ ///
+ /// The agent identifier to restart.
+ /// Confirmation of the restart command.
+ /// Restart command sent successfully.
+ [HttpPost("restart/{agentId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public IActionResult RestartAgent(string agentId)
+ {
+ _logger.LogInformation("Restart command received for agent {AgentId}", agentId);
+
+ // TODO: Forward to Gateway — abort current session + signal restart
+
+ return Ok(new
+ {
+ agentId,
+ command = "restart",
+ status = "sent",
+ timestamp = DateTime.UtcNow.ToString("o")
+ });
+ }
+
+ ///
+ /// Steers (injects a message into) an agent's active session.
+ /// Used by operators to redirect an agent's task mid-execution.
+ ///
+ /// The agent identifier to steer.
+ /// The steering message to inject.
+ /// Confirmation of the steer command.
+ /// Steer command sent successfully.
+ /// Missing or empty message.
+ [HttpPost("steer/{agentId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public IActionResult SteerAgent(string agentId, [FromBody] SteerRequest request)
+ {
+ if (string.IsNullOrWhiteSpace(request.Message))
+ {
+ return BadRequest(new { error = "Message is required" });
+ }
+
+ _logger.LogInformation("Steer command received for agent {AgentId}: {Message}",
+ agentId, request.Message.Length > 100
+ ? request.Message[..100] + "..." : request.Message);
+
+ // TODO: Forward to Gateway via bridge service
+ // await _bridgeService.SendRpcAsync("sessions.steer", new { sessionKey = ..., message = request.Message });
+
+ return Ok(new
+ {
+ agentId,
+ command = "steer",
+ message = request.Message,
+ status = "sent",
+ timestamp = DateTime.UtcNow.ToString("o")
+ });
+ }
+}
+
+///
+/// Request body for the steer command.
+///
+/// The message to inject into the agent's session.
+public record SteerRequest(string Message);
\ No newline at end of file
diff --git a/backend/ControlCenter/Controllers/LogsController.cs b/backend/ControlCenter/Controllers/LogsController.cs
new file mode 100644
index 0000000..238c345
--- /dev/null
+++ b/backend/ControlCenter/Controllers/LogsController.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace ControlCenter.Controllers;
+
+///
+/// REST API for querying agent session logs.
+/// Provides historical message and tool call logs for a specific agent.
+///
+/// API contract for Rex (Frontend):
+///
+/// GET /api/logs/{agentId} — Returns recent logs for an agent
+/// GET /api/logs/{agentId}/tools — Returns recent tool calls for an agent
+///
+///
+/// Log data is sourced from the OpenClaw Gateway's transcript files.
+/// The Gateway's logs.tail RPC provides the raw data, and this
+/// controller formats it for the frontend.
+///
+[ApiController]
+[Route("api/[controller]")]
+public class LogsController : ControllerBase
+{
+ private readonly ILogger _logger;
+
+ public LogsController(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Gets recent session logs for a specific agent.
+ /// Returns the last N messages from the agent's active session transcript.
+ ///
+ /// The agent identifier, e.g. "otto", "dex".
+ /// Maximum number of log entries to return (default: 50, max: 200).
+ /// An array of log entries for the agent.
+ /// Returns the agent's recent logs.
+ /// No active session found for the agent.
+ [HttpGet("{agentId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public IActionResult GetLogs(string agentId, [FromQuery] int limit = 50)
+ {
+ limit = Math.Clamp(limit, 1, 200);
+
+ _logger.LogDebug("Logs requested for agent {AgentId}, limit {Limit}", agentId, limit);
+
+ // TODO: Implement log retrieval by calling the Gateway's logs.tail RPC
+ // or reading transcript files. For now, return an empty array as the
+ // bridge service will provide this data when fully integrated.
+ return Ok(new
+ {
+ agentId,
+ logs = Array.Empty(),
+ count = 0,
+ hasMore = false
+ });
+ }
+
+ ///
+ /// Gets recent tool call logs for a specific agent.
+ /// Returns the last N tool invocations from the agent's session.
+ ///
+ /// The agent identifier.
+ /// Maximum number of tool entries to return (default: 20, max: 100).
+ /// An array of tool call entries for the agent.
+ /// Returns the agent's recent tool calls.
+ /// No active session found for the agent.
+ [HttpGet("{agentId}/tools")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public IActionResult GetToolLogs(string agentId, [FromQuery] int limit = 20)
+ {
+ limit = Math.Clamp(limit, 1, 100);
+
+ _logger.LogDebug("Tool logs requested for agent {AgentId}, limit {Limit}", agentId, limit);
+
+ // TODO: Implement tool log retrieval. Return empty for now.
+ return Ok(new
+ {
+ agentId,
+ tools = Array.Empty(),
+ count = 0,
+ hasMore = false
+ });
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/Hubs/AgentStatusHub.cs b/backend/ControlCenter/Hubs/AgentStatusHub.cs
new file mode 100644
index 0000000..f0d4497
--- /dev/null
+++ b/backend/ControlCenter/Hubs/AgentStatusHub.cs
@@ -0,0 +1,184 @@
+using Microsoft.AspNetCore.SignalR;
+
+namespace ControlCenter.Hubs;
+
+///
+/// SignalR hub for real-time agent status updates in the Command Hub.
+///
+/// Usage flow:
+///
+/// - Client connects to
/hubs/agent-status
+/// - Client calls
to subscribe to all agent updates
+/// - Client calls
to subscribe to a specific agent
+/// - Server pushes
+/// and events
+/// - Client calls
for initial state on connect
+///
+///
+/// Group naming:
+///
+/// - Fleet group:
fleet — receives all agent updates
+/// - Agent group:
agent:{agentId} — receives updates for one agent
+///
+///
+/// Typed client: — all server-to-client
+/// calls go through this interface for compile-time safety.
+///
+/// Architecture note: This hub bridges OpenClaw Gateway WebSocket 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;
+
+ public AgentStatusHub(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Adds the calling connection to the fleet group.
+ /// Once joined, the client will receive all agent status changes
+ /// and task progress updates across the entire fleet.
+ ///
+ 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);
+ }
+
+ ///
+ /// Adds the calling connection to a specific agent's group.
+ /// Once joined, the client will receive updates only for that agent.
+ ///
+ /// The agent identifier, e.g. "otto", "dex".
+ /// Thrown if agentId is null or empty.
+ public async Task JoinAgentGroup(string agentId)
+ {
+ if (string.IsNullOrWhiteSpace(agentId))
+ throw new HubException("agentId is required");
+
+ var groupName = AgentGroupName(agentId);
+ await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
+ _logger.LogDebug("Connection {ConnectionId} joined agent group {GroupName}",
+ Context.ConnectionId, groupName);
+ }
+
+ ///
+ /// Removes the calling connection from a specific agent's group.
+ ///
+ /// The agent identifier.
+ public async Task LeaveAgentGroup(string agentId)
+ {
+ if (string.IsNullOrWhiteSpace(agentId)) return;
+
+ var groupName = AgentGroupName(agentId);
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
+ }
+
+ ///
+ /// Returns a snapshot of the current fleet state.
+ /// Called by clients on initial connection to get the full picture
+ /// before incremental updates begin arriving.
+ ///
+ /// An array of representing all known agents.
+ public Task GetFleetSnapshot()
+ {
+ // The fleet state is managed by the GatewayEventBridgeService.
+ // For now, return an empty array — the bridge service will push
+ // updates as they arrive from the Gateway.
+ _logger.LogDebug("Fleet snapshot requested by {ConnectionId}", Context.ConnectionId);
+ return Task.FromResult(Array.Empty());
+ }
+
+ ///
+ /// Overrides to perform cleanup.
+ /// 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";
+
+ ///
+ /// Returns the SignalR group name for a specific agent.
+ /// Format: agent:{agentId} (lowercase for consistency).
+ ///
+ /// The agent identifier.
+ internal static string AgentGroupName(string agentId) =>
+ $"agent:{agentId.ToLowerInvariant()}";
+}
+
+///
+/// Extension methods for pushing real-time agent updates through
+/// the of .
+///
+/// These methods are intended to be called from background services
+/// (e.g., ) or other
+/// server-side code that detects an agent state change.
+///
+public static class AgentStatusHubExtensions
+{
+ ///
+ /// Pushes an agent status change to all clients subscribed to
+ /// the fleet group and the specific agent's group.
+ ///
+ /// 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 group members.
+ public static async Task PushAgentStatusAsync(
+ this IHubContext hubContext,
+ AgentStatusUpdate update)
+ {
+ // Broadcast to the fleet group (all subscribers)
+ await hubContext.Clients.Group(AgentStatusHub.FleetGroupName)
+ .AgentStatusChanged(update);
+
+ // Also push to the specific agent's group
+ var agentGroup = AgentStatusHub.AgentGroupName(update.AgentId);
+ await hubContext.Clients.Group(agentGroup)
+ .AgentStatusChanged(update);
+ }
+
+ ///
+ /// Pushes a task progress update to all clients subscribed to
+ /// the fleet group and the specific agent's group.
+ ///
+ /// The hub context injected via DI.
+ /// The task progress update payload.
+ /// A Task that completes when the message has been sent to all group members.
+ public static async Task PushTaskProgressAsync(
+ this IHubContext hubContext,
+ TaskProgressUpdate progress)
+ {
+ // Broadcast to the fleet group
+ await hubContext.Clients.Group(AgentStatusHub.FleetGroupName)
+ .AgentTaskProgress(progress);
+
+ // Also push to the specific agent's group
+ var agentGroup = AgentStatusHub.AgentGroupName(progress.AgentId);
+ await hubContext.Clients.Group(agentGroup)
+ .AgentTaskProgress(progress);
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/Hubs/IAgentStatusClient.cs b/backend/ControlCenter/Hubs/IAgentStatusClient.cs
new file mode 100644
index 0000000..4f3e1ea
--- /dev/null
+++ b/backend/ControlCenter/Hubs/IAgentStatusClient.cs
@@ -0,0 +1,30 @@
+namespace ControlCenter.Hubs;
+
+///
+/// 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 and task progress updates.
+///
+/// All server-to-client calls go through this interface for
+/// compile-time safety — matching the pattern used by the
+/// Extrudex PrinterHub.
+///
+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(AgentStatusUpdate update);
+
+ ///
+ /// Pushes a task progress update to all subscribed clients.
+ /// Fired when an agent reports progress on its current task.
+ ///
+ /// The task progress update payload.
+ /// A Task that completes when the client has processed the update.
+ Task AgentTaskProgress(TaskProgressUpdate progress);
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs
new file mode 100644
index 0000000..3c9c97d
--- /dev/null
+++ b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs
@@ -0,0 +1,92 @@
+namespace ControlCenter;
+
+///
+/// Agent operational status derived from OpenClaw Gateway session activity.
+/// Maps to the frontend AgentStatus type: 'active' | 'idle' | 'thinking' | 'error'.
+///
+public enum AgentStatus
+{
+ /// Agent is currently processing a turn.
+ Active,
+
+ /// Agent completed its last turn; no active work.
+ Idle,
+
+ /// LLM call in flight; tokens streaming.
+ Thinking,
+
+ /// Agent encountered an unhandled error.
+ Error
+}
+
+///
+/// Extended lifecycle status including offline — not all agents have active sessions.
+/// Used internally; clients only see (offline maps to idle).
+///
+public enum AgentLifecycleStatus
+{
+ Active,
+ Idle,
+ Thinking,
+ Error,
+ Offline
+}
+
+///
+/// Pushed to SignalR clients when an agent's status changes.
+/// Matches the TypeScript AgentStatusUpdate interface from the design spec.
+///
+/// Agent identifier, e.g. "otto", "dex".
+/// Human-readable name, e.g. "Otto".
+/// Role description, e.g. "Orchestrator Agent".
+/// Current operational status.
+/// Description of the current task, if any.
+/// Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+/// Channel the agent is operating on, e.g. "telegram".
+/// ISO 8601 timestamp of last activity.
+/// Error message when status is 'error'.
+public record AgentStatusUpdate(
+ string AgentId,
+ string DisplayName,
+ string Role,
+ string Status,
+ string? CurrentTask,
+ string SessionKey,
+ string Channel,
+ string LastActivity,
+ string? ErrorMessage = null
+);
+
+///
+/// Pushed to SignalR clients when an agent's task progress updates.
+/// Matches the TypeScript TaskProgressUpdate interface from the design spec.
+///
+/// Agent identifier.
+/// Description of the current task.
+/// Task progress percentage (0–100), if trackable.
+/// Elapsed time string, e.g. "04m 12s".
+public record TaskProgressUpdate(
+ string AgentId,
+ string TaskDescription,
+ int? Progress,
+ string? Elapsed
+);
+
+///
+/// Snapshot of an agent's full card data, sent on initial connection
+/// or when the fleet state is requested.
+/// Matches the TypeScript AgentCardData interface from the design spec.
+///
+public record AgentCardData(
+ string Id,
+ string DisplayName,
+ string Role,
+ string Status,
+ string? CurrentTask,
+ int? TaskProgress,
+ string? TaskElapsed,
+ string SessionKey,
+ string Channel,
+ string LastActivity,
+ string? ErrorMessage
+);
\ No newline at end of file
diff --git a/backend/ControlCenter/Program.cs b/backend/ControlCenter/Program.cs
new file mode 100644
index 0000000..757b20a
--- /dev/null
+++ b/backend/ControlCenter/Program.cs
@@ -0,0 +1,72 @@
+using System.Reflection;
+using ControlCenter.Hubs;
+using ControlCenter.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// ── API Services ───────────────────────────────────────────
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen(c =>
+{
+ c.SwaggerDoc("v1", new()
+ {
+ Title = "Control Center API",
+ Version = "v1",
+ Description = "OpenClaw Control Center — Command Hub backend with SignalR real-time updates"
+ });
+
+ // Include XML doc comments in Swagger output
+ var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+ var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
+ if (File.Exists(xmlPath))
+ {
+ c.IncludeXmlComments(xmlPath);
+ }
+});
+
+// ── CORS (kiosk + remote browser) ─────────────────────────
+// The Control Center frontend runs on a different origin than the backend.
+// SignalR requires credentials for WebSocket transport, so we use
+// specific origins rather than AllowAnyOrigin.
+var corsOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins")
+ .Get() ?? new[] { "http://localhost:4200", "http://localhost:5000" };
+
+builder.Services.AddCors(options =>
+{
+ options.AddDefaultPolicy(policy =>
+ {
+ policy.WithOrigins(corsOrigins)
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials(); // Required for SignalR WebSocket
+ });
+});
+
+// ── SignalR (real-time agent status updates) ───────────────
+builder.Services.AddSignalR();
+
+// ── Gateway Bridge Service ────────────────────────────────
+// Background service that connects to the OpenClaw Gateway WebSocket
+// and bridges events to the AgentStatus SignalR hub.
+builder.Services.AddSingleton();
+builder.Services.AddHostedService(sp => sp.GetRequiredService());
+
+var app = builder.Build();
+
+// ── Middleware ──────────────────────────────────────────────
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseCors();
+app.UseAuthorization();
+app.MapControllers();
+
+// ── Hub Endpoints ───────────────────────────────────────────
+// Agent status hub at /hubs/agent-status (matches the design spec)
+app.MapHub("/hubs/agent-status");
+
+app.Run();
\ No newline at end of file
diff --git a/backend/ControlCenter/Properties/launchSettings.json b/backend/ControlCenter/Properties/launchSettings.json
new file mode 100644
index 0000000..5596756
--- /dev/null
+++ b/backend/ControlCenter/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5053",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/backend/ControlCenter/Services/GatewayEventBridgeService.cs b/backend/ControlCenter/Services/GatewayEventBridgeService.cs
new file mode 100644
index 0000000..0f37285
--- /dev/null
+++ b/backend/ControlCenter/Services/GatewayEventBridgeService.cs
@@ -0,0 +1,523 @@
+using System.Collections.Concurrent;
+using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using ControlCenter.Hubs;
+using Microsoft.AspNetCore.SignalR;
+
+namespace ControlCenter.Services;
+
+///
+/// Background service that connects to the OpenClaw Gateway WebSocket
+/// and bridges Gateway events to the .
+///
+/// Architecture:
+///
+/// - Connects to the Gateway WS endpoint (configurable via appsettings)
+/// - Handles the v3 protocol handshake (challenge → connect → hello-ok)
+/// - Subscribes to
sessions.changed and related events
+/// - Translates session state changes into
+/// and objects
+/// - Pushes updates through the
SignalR hub
+///
+///
+/// This is the server-side bridge that allows Angular clients to
+/// receive real-time updates via SignalR instead of connecting directly
+/// to the Gateway WebSocket.
+///
+public class GatewayEventBridgeService : BackgroundService
+{
+ private readonly ILogger _logger;
+ private readonly IHubContext _hubContext;
+ private readonly IConfiguration _configuration;
+
+ ///
+ /// In-memory fleet state — maps agent IDs to their latest card data.
+ /// Updated on every sessions.changed event from the Gateway.
+ ///
+ private readonly ConcurrentDictionary _fleetState = new();
+
+ ///
+ /// Known agent roles for display in the Command Hub.
+ /// Maps agent IDs to their functional descriptions.
+ ///
+ private static readonly Dictionary AgentRoles = new()
+ {
+ ["main"] = "Primary Assistant",
+ ["otto"] = "Orchestrator Agent",
+ ["dave"] = "Network Admin Agent",
+ ["bob"] = "Content Writer Agent",
+ ["stuart"] = "Image & Creative Agent",
+ ["phil"] = "Home Automation Agent",
+ ["carl"] = "Security Agent",
+ ["larry"] = "Business Agent",
+ ["mel"] = "E-Commerce Agent",
+ ["norbert"] = "Product Agent",
+ ["jerry"] = "Market Research Agent",
+ ["rex"] = "Frontend Dev Agent",
+ ["dex"] = "Backend Dev Agent",
+ ["hex"] = "Database Agent",
+ ["pip"] = "Raspberry Pi Agent",
+ ["nano"] = "ESP32/Firmware Agent",
+ ["axiom"] = "Utility Agent",
+ ["bonnie"] = "Music Agent",
+ ["sketch"] = "UI/UX Design Agent",
+ ["flip"] = "Mobile Dev Agent",
+ ["buzz"] = "SEO Agent",
+ ["aries"] = "Companion Agent"
+ };
+
+ ///
+ /// Maps OpenClaw session status to .
+ ///
+ private static string MapSessionStatus(string? sessionStatus) => sessionStatus switch
+ {
+ "running" => "active",
+ "streaming" => "thinking",
+ "error" or "aborted" => "error",
+ "done" => "idle",
+ _ => "idle"
+ };
+
+ public GatewayEventBridgeService(
+ ILogger logger,
+ IHubContext hubContext,
+ IConfiguration configuration)
+ {
+ _logger = logger;
+ _hubContext = hubContext;
+ _configuration = configuration;
+ }
+
+ ///
+ /// Returns the current fleet state snapshot.
+ /// Used by the hub's GetFleetSnapshot method and by the
+ /// AgentsController REST endpoint.
+ ///
+ public AgentCardData[] GetFleetSnapshot() =>
+ _fleetState.Values.ToArray();
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("Gateway Event Bridge service starting");
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ await ConnectAndListenAsync(stoppingToken);
+ }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
+ {
+ _logger.LogInformation("Gateway Event Bridge service stopping");
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Gateway connection lost, reconnecting in 5 seconds...");
+ await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
+ }
+ }
+ }
+
+ ///
+ /// Connects to the OpenClaw Gateway WebSocket and processes events
+ /// until the connection is lost or cancellation is requested.
+ ///
+ private async Task ConnectAndListenAsync(CancellationToken stoppingToken)
+ {
+ var gatewayUrl = _configuration["Gateway:WebSocketUrl"]
+ ?? "ws://localhost:3271/ws";
+ var authToken = _configuration["Gateway:AuthToken"] ?? string.Empty;
+
+ _logger.LogInformation("Connecting to Gateway at {Url}", gatewayUrl);
+
+ using var ws = new ClientWebSocket();
+
+ // Set auth header if available
+ if (!string.IsNullOrEmpty(authToken))
+ {
+ ws.Options.SetRequestHeader("Authorization", $"Bearer {authToken}");
+ }
+
+ await ws.ConnectAsync(new Uri(gatewayUrl), stoppingToken);
+ _logger.LogInformation("Connected to Gateway WebSocket");
+
+ // Start receiving messages
+ await ReceiveMessagesAsync(ws, stoppingToken);
+ }
+
+ ///
+ /// Receives and processes WebSocket messages from the Gateway.
+ /// Handles the v3 protocol handshake and dispatches events.
+ ///
+ private async Task ReceiveMessagesAsync(ClientWebSocket ws, CancellationToken stoppingToken)
+ {
+ var buffer = new byte[8192];
+ var messageBuilder = new StringBuilder();
+
+ while (ws.State == WebSocketState.Open && !stoppingToken.IsCancellationRequested)
+ {
+ WebSocketReceiveResult result;
+ try
+ {
+ result = await ws.ReceiveAsync(buffer, stoppingToken);
+ }
+ catch (WebSocketException ex)
+ {
+ _logger.LogWarning(ex, "WebSocket receive error");
+ break;
+ }
+
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ _logger.LogInformation("Gateway WebSocket closed by server");
+ break;
+ }
+
+ messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
+
+ if (result.EndOfMessage)
+ {
+ var message = messageBuilder.ToString();
+ messageBuilder.Clear();
+ await ProcessMessageAsync(ws, message, stoppingToken);
+ }
+ }
+ }
+
+ ///
+ /// Processes a single WebSocket message from the Gateway.
+ /// Routes based on the message type: event, response, or challenge.
+ ///
+ private async Task ProcessMessageAsync(
+ ClientWebSocket ws, string message, CancellationToken stoppingToken)
+ {
+ try
+ {
+ using var doc = JsonDocument.Parse(message);
+ var root = doc.RootElement;
+
+ var type = root.GetProperty("type").GetString();
+
+ switch (type)
+ {
+ case "event":
+ await HandleGatewayEventAsync(root);
+ break;
+
+ case "res":
+ HandleGatewayResponse(root);
+ break;
+ }
+
+ // Special handling for connect.challenge events
+ if (root.TryGetProperty("event", out var eventName) &&
+ eventName.GetString() == "connect.challenge")
+ {
+ await HandleConnectChallengeAsync(ws, root, stoppingToken);
+ }
+ }
+ catch (JsonException ex)
+ {
+ _logger.LogWarning(ex, "Failed to parse Gateway message: {Message}",
+ message.Length > 200 ? message[..200] + "..." : message);
+ }
+ }
+
+ ///
+ /// Handles the Gateway connect.challenge event by sending
+ /// a connect request with authentication credentials.
+ ///
+ private async Task HandleConnectChallengeAsync(
+ ClientWebSocket ws, JsonElement root, CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("Received connect.challenge from Gateway");
+
+ var connectRequest = new
+ {
+ type = "req",
+ id = $"bridge-{Guid.NewGuid():N}",
+ method = "connect",
+ @params = new
+ {
+ minProtocol = 3,
+ maxProtocol = 3,
+ client = new
+ {
+ id = "control-center-backend",
+ version = "1.0.0",
+ platform = "server",
+ mode = "operator"
+ },
+ role = "operator",
+ scopes = new[] { "operator.read", "operator.write" },
+ auth = new
+ {
+ token = _configuration["Gateway:AuthToken"] ?? string.Empty
+ },
+ locale = "en-US",
+ userAgent = "control-center-backend/1.0.0"
+ }
+ };
+
+ var json = JsonSerializer.Serialize(connectRequest);
+ var bytes = Encoding.UTF8.GetBytes(json);
+ await ws.SendAsync(bytes, WebSocketMessageType.Text, true, stoppingToken);
+ }
+
+ ///
+ /// Handles a Gateway event message by dispatching to the
+ /// appropriate handler based on event name.
+ ///
+ private async Task HandleGatewayEventAsync(JsonElement root)
+ {
+ if (!root.TryGetProperty("event", out var eventProp)) return;
+ var eventName = eventProp.GetString();
+
+ _logger.LogDebug("Gateway event: {Event}", eventName);
+
+ switch (eventName)
+ {
+ case "sessions.changed":
+ await HandleSessionsChangedAsync(root);
+ break;
+
+ case "session.message":
+ HandleSessionMessage(root);
+ break;
+
+ case "session.tool":
+ HandleSessionTool(root);
+ break;
+
+ case "health":
+ HandleHealthEvent(root);
+ break;
+ }
+ }
+
+ ///
+ /// Handles a sessions.changed event from the Gateway.
+ /// Updates the fleet state and pushes status changes through SignalR.
+ ///
+ private async Task HandleSessionsChangedAsync(JsonElement root)
+ {
+ if (!root.TryGetProperty("payload", out var payload)) return;
+
+ // The payload may contain a snapshot of all sessions
+ if (payload.TryGetProperty("snapshot", out var snapshot) &&
+ snapshot.ValueKind == JsonValueKind.Array)
+ {
+ foreach (var session in snapshot.EnumerateArray())
+ {
+ var cardData = SessionToCardData(session);
+ if (cardData is null) continue;
+
+ _fleetState[cardData.Id] = cardData;
+
+ var update = new AgentStatusUpdate(
+ AgentId: cardData.Id,
+ DisplayName: cardData.DisplayName,
+ Role: cardData.Role,
+ Status: cardData.Status,
+ CurrentTask: cardData.CurrentTask,
+ SessionKey: cardData.SessionKey,
+ Channel: cardData.Channel,
+ LastActivity: cardData.LastActivity,
+ ErrorMessage: cardData.ErrorMessage
+ );
+
+ await _hubContext.PushAgentStatusAsync(update);
+ }
+ }
+
+ // Handle individual updates/added/removed
+ if (payload.TryGetProperty("updated", out var updated) &&
+ updated.ValueKind == JsonValueKind.Array)
+ {
+ foreach (var sessionKey in updated.EnumerateArray())
+ {
+ _logger.LogDebug("Session updated: {Key}", sessionKey.GetString());
+ }
+ }
+ }
+
+ ///
+ /// Handles a session.message event. Updates the agent's last activity
+ /// and pushes a status update if the status changed.
+ ///
+ private void HandleSessionMessage(JsonElement root)
+ {
+ if (!root.TryGetProperty("payload", out var payload)) return;
+ if (!payload.TryGetProperty("sessionKey", out var sessionKeyProp)) return;
+
+ var sessionKey = sessionKeyProp.GetString() ?? string.Empty;
+ var agentId = ExtractAgentId(sessionKey);
+
+ if (string.IsNullOrEmpty(agentId)) return;
+
+ // Update last activity timestamp
+ if (_fleetState.TryGetValue(agentId, out var existing))
+ {
+ _fleetState[agentId] = existing with
+ {
+ LastActivity = DateTime.UtcNow.ToString("o"),
+ Status = "active"
+ };
+ }
+ }
+
+ ///
+ /// Handles a session.tool event. Extracts tool progress information
+ /// and pushes a through SignalR.
+ ///
+ private void HandleSessionTool(JsonElement root)
+ {
+ if (!root.TryGetProperty("payload", out var payload)) return;
+ if (!payload.TryGetProperty("sessionKey", out var sessionKeyProp)) return;
+
+ var sessionKey = sessionKeyProp.GetString() ?? string.Empty;
+ var agentId = ExtractAgentId(sessionKey);
+
+ if (string.IsNullOrEmpty(agentId)) return;
+
+ var toolName = payload.TryGetProperty("toolName", out var tn) ? tn.GetString() : null;
+ var toolStatus = payload.TryGetProperty("status", out var ts) ? ts.GetString() : null;
+
+ if (toolName is null || toolStatus is null) return;
+
+ var progress = toolStatus switch
+ {
+ "started" => 0,
+ "completed" => 100,
+ _ => (int?)null
+ };
+
+ var update = new TaskProgressUpdate(
+ AgentId: agentId,
+ TaskDescription: $"{toolName} ({toolStatus})",
+ Progress: progress,
+ Elapsed: null
+ );
+
+ // Fire and forget — don't block the event loop
+ _ = _hubContext.PushTaskProgressAsync(update);
+ }
+
+ ///
+ /// Handles a health event from the Gateway.
+ /// Logs the health status for diagnostics.
+ ///
+ private void HandleHealthEvent(JsonElement root)
+ {
+ if (!root.TryGetProperty("payload", out var payload)) return;
+
+ var ok = payload.TryGetProperty("ok", out var okProp) && okProp.GetBoolean();
+ var status = payload.TryGetProperty("status", out var s) ? s.GetString() : "unknown";
+
+ _logger.LogInformation("Gateway health: ok={Ok}, status={Status}", ok, status);
+ }
+
+ ///
+ /// Handles a Gateway response message. Currently only logs for diagnostics.
+ ///
+ private void HandleGatewayResponse(JsonElement root)
+ {
+ if (root.TryGetProperty("ok", out var okProp) && okProp.GetBoolean())
+ {
+ _logger.LogDebug("Gateway RPC response OK");
+
+ // Check for hello-ok after connect
+ if (root.TryGetProperty("payload", out var payload) &&
+ payload.TryGetProperty("type", out var typeProp) &&
+ typeProp.GetString() == "hello-ok")
+ {
+ _logger.LogInformation("Gateway handshake complete (hello-ok received)");
+ }
+ }
+ else if (root.TryGetProperty("error", out var error))
+ {
+ var errorMsg = error.TryGetProperty("message", out var msg)
+ ? msg.GetString() : "unknown error";
+ _logger.LogWarning("Gateway RPC error: {Error}", errorMsg);
+ }
+ }
+
+ ///
+ /// Converts a raw Gateway session JSON element into an
+ /// record.
+ ///
+ private AgentCardData? SessionToCardData(JsonElement session)
+ {
+ // Extract agent ID from session key or agentId field
+ string? agentId = null;
+ if (session.TryGetProperty("agentId", out var aid))
+ agentId = aid.GetString();
+ else if (session.TryGetProperty("key", out var key))
+ agentId = ExtractAgentId(key.GetString() ?? string.Empty);
+
+ if (string.IsNullOrEmpty(agentId)) return null;
+
+ var sessionKey = session.TryGetProperty("key", out var sk)
+ ? sk.GetString() ?? string.Empty : string.Empty;
+
+ var status = session.TryGetProperty("status", out var s)
+ ? MapSessionStatus(s.GetString()) : "idle";
+
+ var channel = ExtractChannel(session);
+
+ var lastActivity = session.TryGetProperty("updatedAt", out var ua)
+ ? DateTimeOffset.FromUnixTimeMilliseconds(ua.GetInt64()).ToString("o")
+ : DateTime.UtcNow.ToString("o");
+
+ var displayName = char.ToUpperInvariant(agentId![0]) + agentId[1..];
+ var role = AgentRoles.GetValueOrDefault(agentId!, "Agent");
+
+ return new AgentCardData(
+ Id: agentId!,
+ DisplayName: displayName,
+ Role: role,
+ Status: status,
+ CurrentTask: null,
+ TaskProgress: null,
+ TaskElapsed: null,
+ SessionKey: sessionKey,
+ Channel: channel,
+ LastActivity: lastActivity,
+ ErrorMessage: status == "error" ? "Agent encountered an error" : null
+ );
+ }
+
+ ///
+ /// Extracts the agent ID from a session key.
+ /// Session key format: "agent:{agentId}:{channel}:..."
+ ///
+ private static string? ExtractAgentId(string sessionKey)
+ {
+ if (string.IsNullOrEmpty(sessionKey)) return null;
+
+ var parts = sessionKey.Split(':');
+ if (parts.Length >= 2 && parts[0] == "agent")
+ return parts[1];
+
+ return null;
+ }
+
+ ///
+ /// Extracts the channel from a session element.
+ ///
+ private static string ExtractChannel(JsonElement session)
+ {
+ // Try direct "channel" property
+ if (session.TryGetProperty("channel", out var ch))
+ return ch.GetString() ?? "unknown";
+
+ // Try origin.provider
+ if (session.TryGetProperty("origin", out var origin) &&
+ origin.TryGetProperty("provider", out var provider))
+ return provider.GetString() ?? "unknown";
+
+ return "unknown";
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/appsettings.Development.json b/backend/ControlCenter/appsettings.Development.json
new file mode 100644
index 0000000..626b82e
--- /dev/null
+++ b/backend/ControlCenter/appsettings.Development.json
@@ -0,0 +1,19 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Information",
+ "ControlCenter": "Debug"
+ }
+ },
+ "Gateway": {
+ "WebSocketUrl": "ws://localhost:3271/ws",
+ "AuthToken": ""
+ },
+ "Cors": {
+ "AllowedOrigins": [
+ "http://localhost:4200",
+ "http://localhost:5000"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/appsettings.json b/backend/ControlCenter/appsettings.json
new file mode 100644
index 0000000..60f801d
--- /dev/null
+++ b/backend/ControlCenter/appsettings.json
@@ -0,0 +1,22 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "ControlCenter": "Debug"
+ }
+ },
+ "AllowedHosts": "*",
+
+ "Gateway": {
+ "WebSocketUrl": "ws://localhost:3271/ws",
+ "AuthToken": ""
+ },
+
+ "Cors": {
+ "AllowedOrigins": [
+ "http://localhost:4200",
+ "http://localhost:5000"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter
new file mode 100755
index 0000000..ef5779a
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.deps.json b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.deps.json
new file mode 100644
index 0000000..d8d7dee
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.deps.json
@@ -0,0 +1,126 @@
+{
+ "runtimeTarget": {
+ "name": ".NETCoreApp,Version=v9.0",
+ "signature": ""
+ },
+ "compilationOptions": {},
+ "targets": {
+ ".NETCoreApp,Version=v9.0": {
+ "ControlCenter/1.0.0": {
+ "dependencies": {
+ "Swashbuckle.AspNetCore": "10.1.7"
+ },
+ "runtime": {
+ "ControlCenter.dll": {}
+ }
+ },
+ "Microsoft.Extensions.ApiDescription.Server/9.0.0": {},
+ "Microsoft.OpenApi/2.4.1": {
+ "dependencies": {
+ "System.Text.Json": "8.0.5"
+ },
+ "runtime": {
+ "lib/net8.0/Microsoft.OpenApi.dll": {
+ "assemblyVersion": "2.4.1.0",
+ "fileVersion": "2.4.1.0"
+ }
+ }
+ },
+ "Swashbuckle.AspNetCore/10.1.7": {
+ "dependencies": {
+ "Microsoft.Extensions.ApiDescription.Server": "9.0.0",
+ "Swashbuckle.AspNetCore.Swagger": "10.1.7",
+ "Swashbuckle.AspNetCore.SwaggerGen": "10.1.7",
+ "Swashbuckle.AspNetCore.SwaggerUI": "10.1.7"
+ }
+ },
+ "Swashbuckle.AspNetCore.Swagger/10.1.7": {
+ "dependencies": {
+ "Microsoft.OpenApi": "2.4.1"
+ },
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": {
+ "assemblyVersion": "10.1.7.0",
+ "fileVersion": "10.1.7.2427"
+ }
+ }
+ },
+ "Swashbuckle.AspNetCore.SwaggerGen/10.1.7": {
+ "dependencies": {
+ "Swashbuckle.AspNetCore.Swagger": "10.1.7"
+ },
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
+ "assemblyVersion": "10.1.7.0",
+ "fileVersion": "10.1.7.2427"
+ }
+ }
+ },
+ "Swashbuckle.AspNetCore.SwaggerUI/10.1.7": {
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
+ "assemblyVersion": "10.1.7.0",
+ "fileVersion": "10.1.7.2427"
+ }
+ }
+ },
+ "System.Text.Json/8.0.5": {}
+ }
+ },
+ "libraries": {
+ "ControlCenter/1.0.0": {
+ "type": "project",
+ "serviceable": false,
+ "sha512": ""
+ },
+ "Microsoft.Extensions.ApiDescription.Server/9.0.0": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-1Kzzf7pRey40VaUkHN9/uWxrKVkLu2AQjt+GVeeKLLpiEHAJ1xZRsLSh4ZZYEnyS7Kt2OBOPmsXNdU+wbcOl5w==",
+ "path": "microsoft.extensions.apidescription.server/9.0.0",
+ "hashPath": "microsoft.extensions.apidescription.server.9.0.0.nupkg.sha512"
+ },
+ "Microsoft.OpenApi/2.4.1": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-u7QhXCISMQuab3flasb1hoaiERmUqyWsW7tmQODyILoQ7mJV5IRGM+2KKZYo0QUfC13evEOcHAb6TPWgqEQtrw==",
+ "path": "microsoft.openapi/2.4.1",
+ "hashPath": "microsoft.openapi.2.4.1.nupkg.sha512"
+ },
+ "Swashbuckle.AspNetCore/10.1.7": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-vgef8DPT411JU5JjHiDbr0WOxsIVuAvegPGtqmm4Na4JRl/264dfBJcGkiPHsAr5P+Vda+qN1rZKRtBl1rF9aA==",
+ "path": "swashbuckle.aspnetcore/10.1.7",
+ "hashPath": "swashbuckle.aspnetcore.10.1.7.nupkg.sha512"
+ },
+ "Swashbuckle.AspNetCore.Swagger/10.1.7": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-EjLibt/d/QuRv170GoihTbcPUpgzSFm2WKHhnGJFZQ03JYzfuitsM79azaAR8NBwRunU7yScSX6HRE5JUlrEMQ==",
+ "path": "swashbuckle.aspnetcore.swagger/10.1.7",
+ "hashPath": "swashbuckle.aspnetcore.swagger.10.1.7.nupkg.sha512"
+ },
+ "Swashbuckle.AspNetCore.SwaggerGen/10.1.7": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-PuubO9BjvNn6U3D9kLpuWKY1JtziWw7SsGBq0age1E50uQjQ8Fzl8s0EwzrLfANqYJNgDnJi9l7N1QxcGVB2Zw==",
+ "path": "swashbuckle.aspnetcore.swaggergen/10.1.7",
+ "hashPath": "swashbuckle.aspnetcore.swaggergen.10.1.7.nupkg.sha512"
+ },
+ "Swashbuckle.AspNetCore.SwaggerUI/10.1.7": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-iJo3ODyUb/M8Vm8AH1r9y9iAba0w95xsCn3zFVl96ISRHbTDWxi+l7oFVCZqUEdjd97B8VMDPnMliWAdomR8uw==",
+ "path": "swashbuckle.aspnetcore.swaggerui/10.1.7",
+ "hashPath": "swashbuckle.aspnetcore.swaggerui.10.1.7.nupkg.sha512"
+ },
+ "System.Text.Json/8.0.5": {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
+ "path": "system.text.json/8.0.5",
+ "hashPath": "system.text.json.8.0.5.nupkg.sha512"
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.dll b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.dll
new file mode 100644
index 0000000..5a464c3
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.dll differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.pdb b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.pdb
new file mode 100644
index 0000000..f73d4c4
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.pdb differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.runtimeconfig.json b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.runtimeconfig.json
new file mode 100644
index 0000000..6925b65
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.runtimeconfig.json
@@ -0,0 +1,19 @@
+{
+ "runtimeOptions": {
+ "tfm": "net9.0",
+ "frameworks": [
+ {
+ "name": "Microsoft.NETCore.App",
+ "version": "9.0.0"
+ },
+ {
+ "name": "Microsoft.AspNetCore.App",
+ "version": "9.0.0"
+ }
+ ],
+ "configProperties": {
+ "System.GC.Server": true,
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.staticwebassets.endpoints.json b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.staticwebassets.endpoints.json
new file mode 100644
index 0000000..5576e88
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.staticwebassets.endpoints.json
@@ -0,0 +1 @@
+{"Version":1,"ManifestType":"Build","Endpoints":[]}
\ No newline at end of file
diff --git a/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.xml b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.xml
new file mode 100644
index 0000000..6335700
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.xml
@@ -0,0 +1,525 @@
+
+
+
+ ControlCenter
+
+
+
+
+ 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.
+
+ API contract for Rex (Frontend):
+
+ GET /api/agents — Returns all known agents with current status
+ GET /api/agents/{agentId} — Returns a specific agent's status
+
+
+
+
+
+ Gets the current fleet status — all known agents with their latest state.
+ This is the initial load endpoint; subsequent updates arrive via SignalR.
+
+ An array of agent card data for the entire fleet.
+ Returns the fleet snapshot.
+
+
+
+ Gets the current status of a specific agent.
+
+ The agent identifier, e.g. "otto", "dex".
+ The agent's current card data.
+ Returns the agent's status.
+ Agent not found in the fleet state.
+
+
+
+ REST API for sending control commands to agents.
+ Provides the Command Hub's action endpoints for agent lifecycle control.
+
+ API contract for Rex (Frontend):
+
+ POST /api/command/stop/{agentId} — Stop/abort an agent's active session
+ POST /api/command/restart/{agentId} — Restart an agent
+ POST /api/command/steer/{agentId} — Inject a message into an agent's session
+
+
+ Commands are forwarded to the OpenClaw Gateway via the
+ WebSocket bridge service. The Gateway handles the actual execution.
+
+
+
+
+ Stops (aborts) an agent's active session.
+ Sends an abort command to the OpenClaw Gateway.
+
+ The agent identifier to stop.
+ Confirmation of the stop command.
+ Stop command sent successfully.
+ No active session found for the agent.
+
+
+
+ Restarts an agent by aborting the current session and allowing
+ a new one to start on the next incoming message.
+
+ The agent identifier to restart.
+ Confirmation of the restart command.
+ Restart command sent successfully.
+
+
+
+ Steers (injects a message into) an agent's active session.
+ Used by operators to redirect an agent's task mid-execution.
+
+ The agent identifier to steer.
+ The steering message to inject.
+ Confirmation of the steer command.
+ Steer command sent successfully.
+ Missing or empty message.
+
+
+
+ Request body for the steer command.
+
+ The message to inject into the agent's session.
+
+
+
+ Request body for the steer command.
+
+ The message to inject into the agent's session.
+
+
+ The message to inject into the agent's session.
+
+
+
+ REST API for querying agent session logs.
+ Provides historical message and tool call logs for a specific agent.
+
+ API contract for Rex (Frontend):
+
+ GET /api/logs/{agentId} — Returns recent logs for an agent
+ GET /api/logs/{agentId}/tools — Returns recent tool calls for an agent
+
+
+ Log data is sourced from the OpenClaw Gateway's transcript files.
+ The Gateway's logs.tail RPC provides the raw data, and this
+ controller formats it for the frontend.
+
+
+
+
+ Gets recent session logs for a specific agent.
+ Returns the last N messages from the agent's active session transcript.
+
+ The agent identifier, e.g. "otto", "dex".
+ Maximum number of log entries to return (default: 50, max: 200).
+ An array of log entries for the agent.
+ Returns the agent's recent logs.
+ No active session found for the agent.
+
+
+
+ Gets recent tool call logs for a specific agent.
+ Returns the last N tool invocations from the agent's session.
+
+ The agent identifier.
+ Maximum number of tool entries to return (default: 20, max: 100).
+ An array of tool call entries for the agent.
+ Returns the agent's recent tool calls.
+ No active session found for the agent.
+
+
+
+ SignalR hub for real-time agent status updates in the Command Hub.
+
+ Usage flow:
+
+ - Client connects to
/hubs/agent-status
+ - Client calls
to subscribe to all agent updates
+ - Client calls
to subscribe to a specific agent
+ - Server pushes
+ and events
+ - Client calls
for initial state on connect
+
+
+ Group naming:
+
+ - Fleet group:
fleet — receives all agent updates
+ - Agent group:
agent:{agentId} — receives updates for one agent
+
+
+ Typed client: — all server-to-client
+ calls go through this interface for compile-time safety.
+
+ Architecture note: This hub bridges OpenClaw Gateway WebSocket events
+ to SignalR clients. A background service ( )
+ subscribes to Gateway events and pushes them through this hub's extension methods.
+
+
+
+
+ Adds the calling connection to the fleet group.
+ Once joined, the client will receive all agent status changes
+ and task progress updates across the entire fleet.
+
+
+
+
+ Removes the calling connection from the fleet group.
+
+
+
+
+ Adds the calling connection to a specific agent's group.
+ Once joined, the client will receive updates only for that agent.
+
+ The agent identifier, e.g. "otto", "dex".
+ Thrown if agentId is null or empty.
+
+
+
+ Removes the calling connection from a specific agent's group.
+
+ The agent identifier.
+
+
+
+ Returns a snapshot of the current fleet state.
+ Called by clients on initial connection to get the full picture
+ before incremental updates begin arriving.
+
+ An array of representing all known agents.
+
+
+
+ Overrides to perform cleanup.
+ SignalR automatically removes disconnected connections from all groups.
+
+ Exception that caused the disconnection, if any.
+
+
+
+ The SignalR group name for the entire fleet (all agents).
+
+
+
+
+ Returns the SignalR group name for a specific agent.
+ Format: agent:{agentId} (lowercase for consistency).
+
+ The agent identifier.
+
+
+
+ Extension methods for pushing real-time agent updates through
+ the of .
+
+ These methods are intended to be called from background services
+ (e.g., ) or other
+ server-side code that detects an agent state change.
+
+
+
+
+ Pushes an agent status change to all clients subscribed to
+ the fleet group and the specific agent's group.
+
+ 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 group members.
+
+
+
+ Pushes a task progress update to all clients subscribed to
+ the fleet group and the specific agent's group.
+
+ The hub context injected via DI.
+ The task progress update payload.
+ A Task that completes when the message has been sent to all group members.
+
+
+
+ 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 and task progress updates.
+
+ All server-to-client calls go through this interface for
+ compile-time safety — matching the pattern used by the
+ Extrudex PrinterHub.
+
+
+
+
+ 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.
+
+
+
+ Pushes a task progress update to all subscribed clients.
+ Fired when an agent reports progress on its current task.
+
+ The task progress update payload.
+ A Task that completes when the client has processed the update.
+
+
+
+ Agent operational status derived from OpenClaw Gateway session activity.
+ Maps to the frontend AgentStatus type: 'active' | 'idle' | 'thinking' | 'error'.
+
+
+
+ Agent is currently processing a turn.
+
+
+ Agent completed its last turn; no active work.
+
+
+ LLM call in flight; tokens streaming.
+
+
+ Agent encountered an unhandled error.
+
+
+
+ Extended lifecycle status including offline — not all agents have active sessions.
+ Used internally; clients only see (offline maps to idle).
+
+
+
+
+ Pushed to SignalR clients when an agent's status changes.
+ Matches the TypeScript AgentStatusUpdate interface from the design spec.
+
+ Agent identifier, e.g. "otto", "dex".
+ Human-readable name, e.g. "Otto".
+ Role description, e.g. "Orchestrator Agent".
+ Current operational status.
+ Description of the current task, if any.
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+ Channel the agent is operating on, e.g. "telegram".
+ ISO 8601 timestamp of last activity.
+ Error message when status is 'error'.
+
+
+
+ Pushed to SignalR clients when an agent's status changes.
+ Matches the TypeScript AgentStatusUpdate interface from the design spec.
+
+ Agent identifier, e.g. "otto", "dex".
+ Human-readable name, e.g. "Otto".
+ Role description, e.g. "Orchestrator Agent".
+ Current operational status.
+ Description of the current task, if any.
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+ Channel the agent is operating on, e.g. "telegram".
+ ISO 8601 timestamp of last activity.
+ Error message when status is 'error'.
+
+
+ Agent identifier, e.g. "otto", "dex".
+
+
+ Human-readable name, e.g. "Otto".
+
+
+ Role description, e.g. "Orchestrator Agent".
+
+
+ Current operational status.
+
+
+ Description of the current task, if any.
+
+
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+
+
+ Channel the agent is operating on, e.g. "telegram".
+
+
+ ISO 8601 timestamp of last activity.
+
+
+ Error message when status is 'error'.
+
+
+
+ Pushed to SignalR clients when an agent's task progress updates.
+ Matches the TypeScript TaskProgressUpdate interface from the design spec.
+
+ Agent identifier.
+ Description of the current task.
+ Task progress percentage (0–100), if trackable.
+ Elapsed time string, e.g. "04m 12s".
+
+
+
+ Pushed to SignalR clients when an agent's task progress updates.
+ Matches the TypeScript TaskProgressUpdate interface from the design spec.
+
+ Agent identifier.
+ Description of the current task.
+ Task progress percentage (0–100), if trackable.
+ Elapsed time string, e.g. "04m 12s".
+
+
+ Agent identifier.
+
+
+ Description of the current task.
+
+
+ Task progress percentage (0–100), if trackable.
+
+
+ Elapsed time string, e.g. "04m 12s".
+
+
+
+ Snapshot of an agent's full card data, sent on initial connection
+ or when the fleet state is requested.
+ Matches the TypeScript AgentCardData interface from the design spec.
+
+
+
+
+ Snapshot of an agent's full card data, sent on initial connection
+ or when the fleet state is requested.
+ Matches the TypeScript AgentCardData interface from the design spec.
+
+
+
+
+ Background service that connects to the OpenClaw Gateway WebSocket
+ and bridges Gateway events to the .
+
+ Architecture:
+
+ - Connects to the Gateway WS endpoint (configurable via appsettings)
+ - Handles the v3 protocol handshake (challenge → connect → hello-ok)
+ - Subscribes to
sessions.changed and related events
+ - Translates session state changes into
+ and objects
+ - Pushes updates through the
SignalR hub
+
+
+ This is the server-side bridge that allows Angular clients to
+ receive real-time updates via SignalR instead of connecting directly
+ to the Gateway WebSocket.
+
+
+
+
+ In-memory fleet state — maps agent IDs to their latest card data.
+ Updated on every sessions.changed event from the Gateway.
+
+
+
+
+ Known agent roles for display in the Command Hub.
+ Maps agent IDs to their functional descriptions.
+
+
+
+
+ Maps OpenClaw session status to .
+
+
+
+
+ Returns the current fleet state snapshot.
+ Used by the hub's GetFleetSnapshot method and by the
+ AgentsController REST endpoint.
+
+
+
+
+ Connects to the OpenClaw Gateway WebSocket and processes events
+ until the connection is lost or cancellation is requested.
+
+
+
+
+ Receives and processes WebSocket messages from the Gateway.
+ Handles the v3 protocol handshake and dispatches events.
+
+
+
+
+ Processes a single WebSocket message from the Gateway.
+ Routes based on the message type: event, response, or challenge.
+
+
+
+
+ Handles the Gateway connect.challenge event by sending
+ a connect request with authentication credentials.
+
+
+
+
+ Handles a Gateway event message by dispatching to the
+ appropriate handler based on event name.
+
+
+
+
+ Handles a sessions.changed event from the Gateway.
+ Updates the fleet state and pushes status changes through SignalR.
+
+
+
+
+ Handles a session.message event. Updates the agent's last activity
+ and pushes a status update if the status changed.
+
+
+
+
+ Handles a session.tool event. Extracts tool progress information
+ and pushes a through SignalR.
+
+
+
+
+ Handles a health event from the Gateway.
+ Logs the health status for diagnostics.
+
+
+
+
+ Handles a Gateway response message. Currently only logs for diagnostics.
+
+
+
+
+ Converts a raw Gateway session JSON element into an
+ record.
+
+
+
+
+ Extracts the agent ID from a session key.
+ Session key format: "agent:{agentId}:{channel}:..."
+
+
+
+
+ Extracts the channel from a session element.
+
+
+
+
diff --git a/backend/ControlCenter/bin/Debug/net9.0/Microsoft.OpenApi.dll b/backend/ControlCenter/bin/Debug/net9.0/Microsoft.OpenApi.dll
new file mode 100755
index 0000000..58b6245
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/Microsoft.OpenApi.dll differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.Swagger.dll b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.Swagger.dll
new file mode 100755
index 0000000..f9c8108
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.Swagger.dll differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll
new file mode 100755
index 0000000..a29b341
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll
new file mode 100755
index 0000000..1d440e2
Binary files /dev/null and b/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll differ
diff --git a/backend/ControlCenter/bin/Debug/net9.0/appsettings.Development.json b/backend/ControlCenter/bin/Debug/net9.0/appsettings.Development.json
new file mode 100644
index 0000000..626b82e
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/appsettings.Development.json
@@ -0,0 +1,19 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Information",
+ "ControlCenter": "Debug"
+ }
+ },
+ "Gateway": {
+ "WebSocketUrl": "ws://localhost:3271/ws",
+ "AuthToken": ""
+ },
+ "Cors": {
+ "AllowedOrigins": [
+ "http://localhost:4200",
+ "http://localhost:5000"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/bin/Debug/net9.0/appsettings.json b/backend/ControlCenter/bin/Debug/net9.0/appsettings.json
new file mode 100644
index 0000000..60f801d
--- /dev/null
+++ b/backend/ControlCenter/bin/Debug/net9.0/appsettings.json
@@ -0,0 +1,22 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "ControlCenter": "Debug"
+ }
+ },
+ "AllowedHosts": "*",
+
+ "Gateway": {
+ "WebSocketUrl": "ws://localhost:3271/ws",
+ "AuthToken": ""
+ },
+
+ "Cors": {
+ "AllowedOrigins": [
+ "http://localhost:4200",
+ "http://localhost:5000"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/ControlCenter.csproj.nuget.dgspec.json b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.dgspec.json
new file mode 100644
index 0000000..5eb989c
--- /dev/null
+++ b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.dgspec.json
@@ -0,0 +1,76 @@
+{
+ "format": 1,
+ "restore": {
+ "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj": {}
+ },
+ "projects": {
+ "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj": {
+ "version": "1.0.0",
+ "restore": {
+ "projectUniqueName": "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj",
+ "projectName": "ControlCenter",
+ "projectPath": "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj",
+ "packagesPath": "/home/overseer/.nuget/packages/",
+ "outputPath": "/home/overseer/projects/control-center/backend/ControlCenter/obj/",
+ "projectStyle": "PackageReference",
+ "configFilePaths": [
+ "/home/overseer/.nuget/NuGet/NuGet.Config"
+ ],
+ "originalTargetFrameworks": [
+ "net9.0"
+ ],
+ "sources": {
+ "https://api.nuget.org/v3/index.json": {}
+ },
+ "frameworks": {
+ "net9.0": {
+ "targetAlias": "net9.0",
+ "projectReferences": {}
+ }
+ },
+ "warningProperties": {
+ "warnAsError": [
+ "NU1605"
+ ]
+ },
+ "restoreAuditProperties": {
+ "enableAudit": "true",
+ "auditLevel": "low",
+ "auditMode": "direct"
+ },
+ "SdkAnalysisLevel": "9.0.300"
+ },
+ "frameworks": {
+ "net9.0": {
+ "targetAlias": "net9.0",
+ "dependencies": {
+ "Swashbuckle.AspNetCore": {
+ "target": "Package",
+ "version": "[10.1.7, )"
+ }
+ },
+ "imports": [
+ "net461",
+ "net462",
+ "net47",
+ "net471",
+ "net472",
+ "net48",
+ "net481"
+ ],
+ "assetTargetFallback": true,
+ "warn": true,
+ "frameworkReferences": {
+ "Microsoft.AspNetCore.App": {
+ "privateAssets": "none"
+ },
+ "Microsoft.NETCore.App": {
+ "privateAssets": "all"
+ }
+ },
+ "runtimeIdentifierGraphPath": "/home/overseer/.dotnet/sdk/9.0.312/PortableRuntimeIdentifierGraph.json"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.props b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.props
new file mode 100644
index 0000000..8920d2b
--- /dev/null
+++ b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.props
@@ -0,0 +1,22 @@
+
+
+
+ True
+ NuGet
+ $(MSBuildThisFileDirectory)project.assets.json
+ /home/overseer/.nuget/packages/
+ /home/overseer/.nuget/packages/
+ PackageReference
+ 6.14.0
+
+
+
+
+
+
+
+
+
+ /home/overseer/.nuget/packages/microsoft.extensions.apidescription.server/9.0.0
+
+
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.targets b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.targets
new file mode 100644
index 0000000..60f4bac
--- /dev/null
+++ b/backend/ControlCenter/obj/ControlCenter.csproj.nuget.g.targets
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/backend/ControlCenter/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
new file mode 100644
index 0000000..9e76325
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
@@ -0,0 +1,4 @@
+//
+using System;
+using System.Reflection;
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlC.8BE8DDC1.Up2Date b/backend/ControlCenter/obj/Debug/net9.0/ControlC.8BE8DDC1.Up2Date
new file mode 100644
index 0000000..e69de29
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfo.cs b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfo.cs
new file mode 100644
index 0000000..6c04b97
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfo.cs
@@ -0,0 +1,22 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Reflection;
+
+[assembly: System.Reflection.AssemblyCompanyAttribute("ControlCenter")]
+[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
+[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f490098af681d324519c399c3e75ade058357531")]
+[assembly: System.Reflection.AssemblyProductAttribute("ControlCenter")]
+[assembly: System.Reflection.AssemblyTitleAttribute("ControlCenter")]
+[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
+
+// Generated by the MSBuild WriteCodeFragment class.
+
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfoInputs.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfoInputs.cache
new file mode 100644
index 0000000..87ee5f5
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfoInputs.cache
@@ -0,0 +1 @@
+d9ac725746244a0c1e0ff1bcede0c898e3615303f498e39aec65b74e6488388a
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GeneratedMSBuildEditorConfig.editorconfig b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GeneratedMSBuildEditorConfig.editorconfig
new file mode 100644
index 0000000..c342709
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GeneratedMSBuildEditorConfig.editorconfig
@@ -0,0 +1,21 @@
+is_global = true
+build_property.TargetFramework = net9.0
+build_property.TargetPlatformMinVersion =
+build_property.UsingMicrosoftNETSdkWeb = true
+build_property.ProjectTypeGuids =
+build_property.InvariantGlobalization =
+build_property.PlatformNeutralAssembly =
+build_property.EnforceExtendedAnalyzerRules =
+build_property._SupportedPlatformList = Linux,macOS,Windows
+build_property.RootNamespace = ControlCenter
+build_property.RootNamespace = ControlCenter
+build_property.ProjectDir = /home/overseer/projects/control-center/backend/ControlCenter/
+build_property.EnableComHosting =
+build_property.EnableGeneratedComInterfaceComImportInterop =
+build_property.RazorLangVersion = 9.0
+build_property.SupportLocalizedComponentNames =
+build_property.GenerateRazorMetadataSourceChecksumAttributes =
+build_property.MSBuildProjectDirectory = /home/overseer/projects/control-center/backend/ControlCenter
+build_property._RazorSourceGeneratorDebug =
+build_property.EffectiveAnalysisLevelStyle = 9.0
+build_property.EnableCodeStyleSeverity =
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GlobalUsings.g.cs b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GlobalUsings.g.cs
new file mode 100644
index 0000000..025530a
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GlobalUsings.g.cs
@@ -0,0 +1,17 @@
+//
+global using global::Microsoft.AspNetCore.Builder;
+global using global::Microsoft.AspNetCore.Hosting;
+global using global::Microsoft.AspNetCore.Http;
+global using global::Microsoft.AspNetCore.Routing;
+global using global::Microsoft.Extensions.Configuration;
+global using global::Microsoft.Extensions.DependencyInjection;
+global using global::Microsoft.Extensions.Hosting;
+global using global::Microsoft.Extensions.Logging;
+global using global::System;
+global using global::System.Collections.Generic;
+global using global::System.IO;
+global using global::System.Linq;
+global using global::System.Net.Http;
+global using global::System.Net.Http.Json;
+global using global::System.Threading;
+global using global::System.Threading.Tasks;
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cache
new file mode 100644
index 0000000..e69de29
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cs b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cs
new file mode 100644
index 0000000..5c337f8
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cs
@@ -0,0 +1,16 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Reflection;
+
+[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Swashbuckle.AspNetCore.SwaggerGen")]
+
+// Generated by the MSBuild WriteCodeFragment class.
+
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.assets.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.assets.cache
new file mode 100644
index 0000000..5564151
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.assets.cache differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.AssemblyReference.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.AssemblyReference.cache
new file mode 100644
index 0000000..f1f28b0
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.AssemblyReference.cache differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.CoreCompileInputs.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.CoreCompileInputs.cache
new file mode 100644
index 0000000..87abb78
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+5a149a9be81d0d785f401346061a83ced0347895d02923e2209cde8264498329
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.FileListAbsolute.txt b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..680dfeb
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.FileListAbsolute.txt
@@ -0,0 +1,36 @@
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/rpswa.dswa.cache.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.GeneratedMSBuildEditorConfig.editorconfig
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfoInputs.cache
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.AssemblyInfo.cs
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.CoreCompileInputs.cache
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cache
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/appsettings.Development.json
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/appsettings.json
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.staticwebassets.endpoints.json
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.deps.json
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.runtimeconfig.json
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.dll
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.pdb
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/ControlCenter.xml
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/Microsoft.OpenApi.dll
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.Swagger.dll
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll
+/home/overseer/projects/control-center/backend/ControlCenter/bin/Debug/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.csproj.AssemblyReference.cache
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.MvcApplicationPartsAssemblyInfo.cs
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/rjimswa.dswa.cache.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/scopedcss/bundle/ControlCenter.styles.css
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json.cache
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.development.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.endpoints.json
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlC.8BE8DDC1.Up2Date
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.dll
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/refint/ControlCenter.dll
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.xml
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.pdb
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.genruntimeconfig.cache
+/home/overseer/projects/control-center/backend/ControlCenter/obj/Debug/net9.0/ref/ControlCenter.dll
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.dll b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.dll
new file mode 100644
index 0000000..5a464c3
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.dll differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.genruntimeconfig.cache b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.genruntimeconfig.cache
new file mode 100644
index 0000000..1706e2e
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.genruntimeconfig.cache
@@ -0,0 +1 @@
+fb735db4022e3ab26c1abd1408be390500bd48ea8b05809d0deadb8a6cf1b3d1
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.pdb b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.pdb
new file mode 100644
index 0000000..f73d4c4
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.pdb differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.xml b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.xml
new file mode 100644
index 0000000..6335700
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/ControlCenter.xml
@@ -0,0 +1,525 @@
+
+
+
+ ControlCenter
+
+
+
+
+ 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.
+
+ API contract for Rex (Frontend):
+
+ GET /api/agents — Returns all known agents with current status
+ GET /api/agents/{agentId} — Returns a specific agent's status
+
+
+
+
+
+ Gets the current fleet status — all known agents with their latest state.
+ This is the initial load endpoint; subsequent updates arrive via SignalR.
+
+ An array of agent card data for the entire fleet.
+ Returns the fleet snapshot.
+
+
+
+ Gets the current status of a specific agent.
+
+ The agent identifier, e.g. "otto", "dex".
+ The agent's current card data.
+ Returns the agent's status.
+ Agent not found in the fleet state.
+
+
+
+ REST API for sending control commands to agents.
+ Provides the Command Hub's action endpoints for agent lifecycle control.
+
+ API contract for Rex (Frontend):
+
+ POST /api/command/stop/{agentId} — Stop/abort an agent's active session
+ POST /api/command/restart/{agentId} — Restart an agent
+ POST /api/command/steer/{agentId} — Inject a message into an agent's session
+
+
+ Commands are forwarded to the OpenClaw Gateway via the
+ WebSocket bridge service. The Gateway handles the actual execution.
+
+
+
+
+ Stops (aborts) an agent's active session.
+ Sends an abort command to the OpenClaw Gateway.
+
+ The agent identifier to stop.
+ Confirmation of the stop command.
+ Stop command sent successfully.
+ No active session found for the agent.
+
+
+
+ Restarts an agent by aborting the current session and allowing
+ a new one to start on the next incoming message.
+
+ The agent identifier to restart.
+ Confirmation of the restart command.
+ Restart command sent successfully.
+
+
+
+ Steers (injects a message into) an agent's active session.
+ Used by operators to redirect an agent's task mid-execution.
+
+ The agent identifier to steer.
+ The steering message to inject.
+ Confirmation of the steer command.
+ Steer command sent successfully.
+ Missing or empty message.
+
+
+
+ Request body for the steer command.
+
+ The message to inject into the agent's session.
+
+
+
+ Request body for the steer command.
+
+ The message to inject into the agent's session.
+
+
+ The message to inject into the agent's session.
+
+
+
+ REST API for querying agent session logs.
+ Provides historical message and tool call logs for a specific agent.
+
+ API contract for Rex (Frontend):
+
+ GET /api/logs/{agentId} — Returns recent logs for an agent
+ GET /api/logs/{agentId}/tools — Returns recent tool calls for an agent
+
+
+ Log data is sourced from the OpenClaw Gateway's transcript files.
+ The Gateway's logs.tail RPC provides the raw data, and this
+ controller formats it for the frontend.
+
+
+
+
+ Gets recent session logs for a specific agent.
+ Returns the last N messages from the agent's active session transcript.
+
+ The agent identifier, e.g. "otto", "dex".
+ Maximum number of log entries to return (default: 50, max: 200).
+ An array of log entries for the agent.
+ Returns the agent's recent logs.
+ No active session found for the agent.
+
+
+
+ Gets recent tool call logs for a specific agent.
+ Returns the last N tool invocations from the agent's session.
+
+ The agent identifier.
+ Maximum number of tool entries to return (default: 20, max: 100).
+ An array of tool call entries for the agent.
+ Returns the agent's recent tool calls.
+ No active session found for the agent.
+
+
+
+ SignalR hub for real-time agent status updates in the Command Hub.
+
+ Usage flow:
+
+ - Client connects to
/hubs/agent-status
+ - Client calls
to subscribe to all agent updates
+ - Client calls
to subscribe to a specific agent
+ - Server pushes
+ and events
+ - Client calls
for initial state on connect
+
+
+ Group naming:
+
+ - Fleet group:
fleet — receives all agent updates
+ - Agent group:
agent:{agentId} — receives updates for one agent
+
+
+ Typed client: — all server-to-client
+ calls go through this interface for compile-time safety.
+
+ Architecture note: This hub bridges OpenClaw Gateway WebSocket events
+ to SignalR clients. A background service ( )
+ subscribes to Gateway events and pushes them through this hub's extension methods.
+
+
+
+
+ Adds the calling connection to the fleet group.
+ Once joined, the client will receive all agent status changes
+ and task progress updates across the entire fleet.
+
+
+
+
+ Removes the calling connection from the fleet group.
+
+
+
+
+ Adds the calling connection to a specific agent's group.
+ Once joined, the client will receive updates only for that agent.
+
+ The agent identifier, e.g. "otto", "dex".
+ Thrown if agentId is null or empty.
+
+
+
+ Removes the calling connection from a specific agent's group.
+
+ The agent identifier.
+
+
+
+ Returns a snapshot of the current fleet state.
+ Called by clients on initial connection to get the full picture
+ before incremental updates begin arriving.
+
+ An array of representing all known agents.
+
+
+
+ Overrides to perform cleanup.
+ SignalR automatically removes disconnected connections from all groups.
+
+ Exception that caused the disconnection, if any.
+
+
+
+ The SignalR group name for the entire fleet (all agents).
+
+
+
+
+ Returns the SignalR group name for a specific agent.
+ Format: agent:{agentId} (lowercase for consistency).
+
+ The agent identifier.
+
+
+
+ Extension methods for pushing real-time agent updates through
+ the of .
+
+ These methods are intended to be called from background services
+ (e.g., ) or other
+ server-side code that detects an agent state change.
+
+
+
+
+ Pushes an agent status change to all clients subscribed to
+ the fleet group and the specific agent's group.
+
+ 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 group members.
+
+
+
+ Pushes a task progress update to all clients subscribed to
+ the fleet group and the specific agent's group.
+
+ The hub context injected via DI.
+ The task progress update payload.
+ A Task that completes when the message has been sent to all group members.
+
+
+
+ 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 and task progress updates.
+
+ All server-to-client calls go through this interface for
+ compile-time safety — matching the pattern used by the
+ Extrudex PrinterHub.
+
+
+
+
+ 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.
+
+
+
+ Pushes a task progress update to all subscribed clients.
+ Fired when an agent reports progress on its current task.
+
+ The task progress update payload.
+ A Task that completes when the client has processed the update.
+
+
+
+ Agent operational status derived from OpenClaw Gateway session activity.
+ Maps to the frontend AgentStatus type: 'active' | 'idle' | 'thinking' | 'error'.
+
+
+
+ Agent is currently processing a turn.
+
+
+ Agent completed its last turn; no active work.
+
+
+ LLM call in flight; tokens streaming.
+
+
+ Agent encountered an unhandled error.
+
+
+
+ Extended lifecycle status including offline — not all agents have active sessions.
+ Used internally; clients only see (offline maps to idle).
+
+
+
+
+ Pushed to SignalR clients when an agent's status changes.
+ Matches the TypeScript AgentStatusUpdate interface from the design spec.
+
+ Agent identifier, e.g. "otto", "dex".
+ Human-readable name, e.g. "Otto".
+ Role description, e.g. "Orchestrator Agent".
+ Current operational status.
+ Description of the current task, if any.
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+ Channel the agent is operating on, e.g. "telegram".
+ ISO 8601 timestamp of last activity.
+ Error message when status is 'error'.
+
+
+
+ Pushed to SignalR clients when an agent's status changes.
+ Matches the TypeScript AgentStatusUpdate interface from the design spec.
+
+ Agent identifier, e.g. "otto", "dex".
+ Human-readable name, e.g. "Otto".
+ Role description, e.g. "Orchestrator Agent".
+ Current operational status.
+ Description of the current task, if any.
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+ Channel the agent is operating on, e.g. "telegram".
+ ISO 8601 timestamp of last activity.
+ Error message when status is 'error'.
+
+
+ Agent identifier, e.g. "otto", "dex".
+
+
+ Human-readable name, e.g. "Otto".
+
+
+ Role description, e.g. "Orchestrator Agent".
+
+
+ Current operational status.
+
+
+ Description of the current task, if any.
+
+
+ Full session key, e.g. "agent:otto:telegram:direct:8787451565".
+
+
+ Channel the agent is operating on, e.g. "telegram".
+
+
+ ISO 8601 timestamp of last activity.
+
+
+ Error message when status is 'error'.
+
+
+
+ Pushed to SignalR clients when an agent's task progress updates.
+ Matches the TypeScript TaskProgressUpdate interface from the design spec.
+
+ Agent identifier.
+ Description of the current task.
+ Task progress percentage (0–100), if trackable.
+ Elapsed time string, e.g. "04m 12s".
+
+
+
+ Pushed to SignalR clients when an agent's task progress updates.
+ Matches the TypeScript TaskProgressUpdate interface from the design spec.
+
+ Agent identifier.
+ Description of the current task.
+ Task progress percentage (0–100), if trackable.
+ Elapsed time string, e.g. "04m 12s".
+
+
+ Agent identifier.
+
+
+ Description of the current task.
+
+
+ Task progress percentage (0–100), if trackable.
+
+
+ Elapsed time string, e.g. "04m 12s".
+
+
+
+ Snapshot of an agent's full card data, sent on initial connection
+ or when the fleet state is requested.
+ Matches the TypeScript AgentCardData interface from the design spec.
+
+
+
+
+ Snapshot of an agent's full card data, sent on initial connection
+ or when the fleet state is requested.
+ Matches the TypeScript AgentCardData interface from the design spec.
+
+
+
+
+ Background service that connects to the OpenClaw Gateway WebSocket
+ and bridges Gateway events to the .
+
+ Architecture:
+
+ - Connects to the Gateway WS endpoint (configurable via appsettings)
+ - Handles the v3 protocol handshake (challenge → connect → hello-ok)
+ - Subscribes to
sessions.changed and related events
+ - Translates session state changes into
+ and objects
+ - Pushes updates through the
SignalR hub
+
+
+ This is the server-side bridge that allows Angular clients to
+ receive real-time updates via SignalR instead of connecting directly
+ to the Gateway WebSocket.
+
+
+
+
+ In-memory fleet state — maps agent IDs to their latest card data.
+ Updated on every sessions.changed event from the Gateway.
+
+
+
+
+ Known agent roles for display in the Command Hub.
+ Maps agent IDs to their functional descriptions.
+
+
+
+
+ Maps OpenClaw session status to .
+
+
+
+
+ Returns the current fleet state snapshot.
+ Used by the hub's GetFleetSnapshot method and by the
+ AgentsController REST endpoint.
+
+
+
+
+ Connects to the OpenClaw Gateway WebSocket and processes events
+ until the connection is lost or cancellation is requested.
+
+
+
+
+ Receives and processes WebSocket messages from the Gateway.
+ Handles the v3 protocol handshake and dispatches events.
+
+
+
+
+ Processes a single WebSocket message from the Gateway.
+ Routes based on the message type: event, response, or challenge.
+
+
+
+
+ Handles the Gateway connect.challenge event by sending
+ a connect request with authentication credentials.
+
+
+
+
+ Handles a Gateway event message by dispatching to the
+ appropriate handler based on event name.
+
+
+
+
+ Handles a sessions.changed event from the Gateway.
+ Updates the fleet state and pushes status changes through SignalR.
+
+
+
+
+ Handles a session.message event. Updates the agent's last activity
+ and pushes a status update if the status changed.
+
+
+
+
+ Handles a session.tool event. Extracts tool progress information
+ and pushes a through SignalR.
+
+
+
+
+ Handles a health event from the Gateway.
+ Logs the health status for diagnostics.
+
+
+
+
+ Handles a Gateway response message. Currently only logs for diagnostics.
+
+
+
+
+ Converts a raw Gateway session JSON element into an
+ record.
+
+
+
+
+ Extracts the agent ID from a session key.
+ Session key format: "agent:{agentId}:{channel}:..."
+
+
+
+
+ Extracts the channel from a session element.
+
+
+
+
diff --git a/backend/ControlCenter/obj/Debug/net9.0/apphost b/backend/ControlCenter/obj/Debug/net9.0/apphost
new file mode 100755
index 0000000..ef5779a
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/apphost differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/ref/ControlCenter.dll b/backend/ControlCenter/obj/Debug/net9.0/ref/ControlCenter.dll
new file mode 100644
index 0000000..489ecc2
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/ref/ControlCenter.dll differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/refint/ControlCenter.dll b/backend/ControlCenter/obj/Debug/net9.0/refint/ControlCenter.dll
new file mode 100644
index 0000000..489ecc2
Binary files /dev/null and b/backend/ControlCenter/obj/Debug/net9.0/refint/ControlCenter.dll differ
diff --git a/backend/ControlCenter/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/backend/ControlCenter/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
new file mode 100644
index 0000000..3818895
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
@@ -0,0 +1 @@
+{"GlobalPropertiesHash":"fP6C7NMofulnBE1eej8XjOwRkTKI5k6NpzaQX80fX8s=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RLHkEsLEvNvl8jLrrAgVApLy6dxoK6XfbeV9mw/9T7o=","rgcVP\u002BVr5KxJyslAN\u002ByJcFCRZcRu6\u002B1l2pjhD4NaFdw=","FFbGWElm3cdQZsLW1ACMByZe8G8gDhCtEJQz38GZLOQ=","59/k\u002BS6qSaOYsxQEpqPfNdLtDHcGS8jaZQOh\u002BdsAjCc="],"CachedAssets":{},"CachedCopyCandidates":{}}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/backend/ControlCenter/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
new file mode 100644
index 0000000..b8901b0
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
@@ -0,0 +1 @@
+{"GlobalPropertiesHash":"CM6+tGqDGZSCMIjvZW43Mig+XGvvGPauYcFS9pFbVbU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RLHkEsLEvNvl8jLrrAgVApLy6dxoK6XfbeV9mw/9T7o=","rgcVP\u002BVr5KxJyslAN\u002ByJcFCRZcRu6\u002B1l2pjhD4NaFdw=","FFbGWElm3cdQZsLW1ACMByZe8G8gDhCtEJQz38GZLOQ=","59/k\u002BS6qSaOYsxQEpqPfNdLtDHcGS8jaZQOh\u002BdsAjCc="],"CachedAssets":{},"CachedCopyCandidates":{}}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/rpswa.dswa.cache.json b/backend/ControlCenter/obj/Debug/net9.0/rpswa.dswa.cache.json
new file mode 100644
index 0000000..ead65fe
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/rpswa.dswa.cache.json
@@ -0,0 +1 @@
+{"GlobalPropertiesHash":"a1GQyj11k+1oHxZebIKZqb47X6WAtT1QozrGrmX2GLs=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RLHkEsLEvNvl8jLrrAgVApLy6dxoK6XfbeV9mw/9T7o=","rgcVP\u002BVr5KxJyslAN\u002ByJcFCRZcRu6\u002B1l2pjhD4NaFdw="],"CachedAssets":{},"CachedCopyCandidates":{}}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.endpoints.json b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.endpoints.json
new file mode 100644
index 0000000..5576e88
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.endpoints.json
@@ -0,0 +1 @@
+{"Version":1,"ManifestType":"Build","Endpoints":[]}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json
new file mode 100644
index 0000000..1ca3228
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json
@@ -0,0 +1 @@
+{"Version":1,"Hash":"VtMlOCwFm0Ev666s2/hr1q6JK4feLZLPBJxH6XDSLFM=","Source":"ControlCenter","BasePath":"_content/ControlCenter","Mode":"Default","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json.cache b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json.cache
new file mode 100644
index 0000000..00ea1d6
--- /dev/null
+++ b/backend/ControlCenter/obj/Debug/net9.0/staticwebassets.build.json.cache
@@ -0,0 +1 @@
+VtMlOCwFm0Ev666s2/hr1q6JK4feLZLPBJxH6XDSLFM=
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/project.assets.json b/backend/ControlCenter/obj/project.assets.json
new file mode 100644
index 0000000..61811bf
--- /dev/null
+++ b/backend/ControlCenter/obj/project.assets.json
@@ -0,0 +1,619 @@
+{
+ "version": 3,
+ "targets": {
+ "net9.0": {
+ "Microsoft.Extensions.ApiDescription.Server/9.0.0": {
+ "type": "package",
+ "build": {
+ "build/Microsoft.Extensions.ApiDescription.Server.props": {},
+ "build/Microsoft.Extensions.ApiDescription.Server.targets": {}
+ },
+ "buildMultiTargeting": {
+ "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props": {},
+ "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets": {}
+ }
+ },
+ "Microsoft.OpenApi/2.4.1": {
+ "type": "package",
+ "dependencies": {
+ "System.Text.Json": "8.0.5"
+ },
+ "compile": {
+ "lib/net8.0/Microsoft.OpenApi.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "runtime": {
+ "lib/net8.0/Microsoft.OpenApi.dll": {
+ "related": ".pdb;.xml"
+ }
+ }
+ },
+ "Swashbuckle.AspNetCore/10.1.7": {
+ "type": "package",
+ "dependencies": {
+ "Microsoft.Extensions.ApiDescription.Server": "9.0.0",
+ "Swashbuckle.AspNetCore.Swagger": "10.1.7",
+ "Swashbuckle.AspNetCore.SwaggerGen": "10.1.7",
+ "Swashbuckle.AspNetCore.SwaggerUI": "10.1.7"
+ },
+ "build": {
+ "build/Swashbuckle.AspNetCore.props": {}
+ },
+ "buildMultiTargeting": {
+ "buildMultiTargeting/Swashbuckle.AspNetCore.props": {}
+ }
+ },
+ "Swashbuckle.AspNetCore.Swagger/10.1.7": {
+ "type": "package",
+ "dependencies": {
+ "Microsoft.OpenApi": "2.4.1"
+ },
+ "compile": {
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "frameworkReferences": [
+ "Microsoft.AspNetCore.App"
+ ]
+ },
+ "Swashbuckle.AspNetCore.SwaggerGen/10.1.7": {
+ "type": "package",
+ "dependencies": {
+ "Swashbuckle.AspNetCore.Swagger": "10.1.7"
+ },
+ "compile": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
+ "related": ".pdb;.xml"
+ }
+ }
+ },
+ "Swashbuckle.AspNetCore.SwaggerUI/10.1.7": {
+ "type": "package",
+ "compile": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "runtime": {
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
+ "related": ".pdb;.xml"
+ }
+ },
+ "frameworkReferences": [
+ "Microsoft.AspNetCore.App"
+ ]
+ },
+ "System.Text.Json/8.0.5": {
+ "type": "package",
+ "compile": {
+ "lib/net8.0/System.Text.Json.dll": {
+ "related": ".xml"
+ }
+ },
+ "runtime": {
+ "lib/net8.0/System.Text.Json.dll": {
+ "related": ".xml"
+ }
+ },
+ "build": {
+ "buildTransitive/net6.0/System.Text.Json.targets": {}
+ }
+ }
+ }
+ },
+ "libraries": {
+ "Microsoft.Extensions.ApiDescription.Server/9.0.0": {
+ "sha512": "1Kzzf7pRey40VaUkHN9/uWxrKVkLu2AQjt+GVeeKLLpiEHAJ1xZRsLSh4ZZYEnyS7Kt2OBOPmsXNdU+wbcOl5w==",
+ "type": "package",
+ "path": "microsoft.extensions.apidescription.server/9.0.0",
+ "hasTools": true,
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "Icon.png",
+ "build/Microsoft.Extensions.ApiDescription.Server.props",
+ "build/Microsoft.Extensions.ApiDescription.Server.targets",
+ "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props",
+ "buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets",
+ "microsoft.extensions.apidescription.server.9.0.0.nupkg.sha512",
+ "microsoft.extensions.apidescription.server.nuspec",
+ "tools/Newtonsoft.Json.dll",
+ "tools/dotnet-getdocument.deps.json",
+ "tools/dotnet-getdocument.dll",
+ "tools/dotnet-getdocument.runtimeconfig.json",
+ "tools/net462-x86/GetDocument.Insider.exe",
+ "tools/net462-x86/GetDocument.Insider.exe.config",
+ "tools/net462-x86/Microsoft.OpenApi.dll",
+ "tools/net462-x86/Microsoft.Win32.Primitives.dll",
+ "tools/net462-x86/System.AppContext.dll",
+ "tools/net462-x86/System.Buffers.dll",
+ "tools/net462-x86/System.Collections.Concurrent.dll",
+ "tools/net462-x86/System.Collections.NonGeneric.dll",
+ "tools/net462-x86/System.Collections.Specialized.dll",
+ "tools/net462-x86/System.Collections.dll",
+ "tools/net462-x86/System.ComponentModel.EventBasedAsync.dll",
+ "tools/net462-x86/System.ComponentModel.Primitives.dll",
+ "tools/net462-x86/System.ComponentModel.TypeConverter.dll",
+ "tools/net462-x86/System.ComponentModel.dll",
+ "tools/net462-x86/System.Console.dll",
+ "tools/net462-x86/System.Data.Common.dll",
+ "tools/net462-x86/System.Diagnostics.Contracts.dll",
+ "tools/net462-x86/System.Diagnostics.Debug.dll",
+ "tools/net462-x86/System.Diagnostics.DiagnosticSource.dll",
+ "tools/net462-x86/System.Diagnostics.FileVersionInfo.dll",
+ "tools/net462-x86/System.Diagnostics.Process.dll",
+ "tools/net462-x86/System.Diagnostics.StackTrace.dll",
+ "tools/net462-x86/System.Diagnostics.TextWriterTraceListener.dll",
+ "tools/net462-x86/System.Diagnostics.Tools.dll",
+ "tools/net462-x86/System.Diagnostics.TraceSource.dll",
+ "tools/net462-x86/System.Diagnostics.Tracing.dll",
+ "tools/net462-x86/System.Drawing.Primitives.dll",
+ "tools/net462-x86/System.Dynamic.Runtime.dll",
+ "tools/net462-x86/System.Globalization.Calendars.dll",
+ "tools/net462-x86/System.Globalization.Extensions.dll",
+ "tools/net462-x86/System.Globalization.dll",
+ "tools/net462-x86/System.IO.Compression.ZipFile.dll",
+ "tools/net462-x86/System.IO.Compression.dll",
+ "tools/net462-x86/System.IO.FileSystem.DriveInfo.dll",
+ "tools/net462-x86/System.IO.FileSystem.Primitives.dll",
+ "tools/net462-x86/System.IO.FileSystem.Watcher.dll",
+ "tools/net462-x86/System.IO.FileSystem.dll",
+ "tools/net462-x86/System.IO.IsolatedStorage.dll",
+ "tools/net462-x86/System.IO.MemoryMappedFiles.dll",
+ "tools/net462-x86/System.IO.Pipes.dll",
+ "tools/net462-x86/System.IO.UnmanagedMemoryStream.dll",
+ "tools/net462-x86/System.IO.dll",
+ "tools/net462-x86/System.Linq.Expressions.dll",
+ "tools/net462-x86/System.Linq.Parallel.dll",
+ "tools/net462-x86/System.Linq.Queryable.dll",
+ "tools/net462-x86/System.Linq.dll",
+ "tools/net462-x86/System.Memory.dll",
+ "tools/net462-x86/System.Net.Http.dll",
+ "tools/net462-x86/System.Net.NameResolution.dll",
+ "tools/net462-x86/System.Net.NetworkInformation.dll",
+ "tools/net462-x86/System.Net.Ping.dll",
+ "tools/net462-x86/System.Net.Primitives.dll",
+ "tools/net462-x86/System.Net.Requests.dll",
+ "tools/net462-x86/System.Net.Security.dll",
+ "tools/net462-x86/System.Net.Sockets.dll",
+ "tools/net462-x86/System.Net.WebHeaderCollection.dll",
+ "tools/net462-x86/System.Net.WebSockets.Client.dll",
+ "tools/net462-x86/System.Net.WebSockets.dll",
+ "tools/net462-x86/System.Numerics.Vectors.dll",
+ "tools/net462-x86/System.ObjectModel.dll",
+ "tools/net462-x86/System.Reflection.Extensions.dll",
+ "tools/net462-x86/System.Reflection.Primitives.dll",
+ "tools/net462-x86/System.Reflection.dll",
+ "tools/net462-x86/System.Resources.Reader.dll",
+ "tools/net462-x86/System.Resources.ResourceManager.dll",
+ "tools/net462-x86/System.Resources.Writer.dll",
+ "tools/net462-x86/System.Runtime.CompilerServices.Unsafe.dll",
+ "tools/net462-x86/System.Runtime.CompilerServices.VisualC.dll",
+ "tools/net462-x86/System.Runtime.Extensions.dll",
+ "tools/net462-x86/System.Runtime.Handles.dll",
+ "tools/net462-x86/System.Runtime.InteropServices.RuntimeInformation.dll",
+ "tools/net462-x86/System.Runtime.InteropServices.dll",
+ "tools/net462-x86/System.Runtime.Numerics.dll",
+ "tools/net462-x86/System.Runtime.Serialization.Formatters.dll",
+ "tools/net462-x86/System.Runtime.Serialization.Json.dll",
+ "tools/net462-x86/System.Runtime.Serialization.Primitives.dll",
+ "tools/net462-x86/System.Runtime.Serialization.Xml.dll",
+ "tools/net462-x86/System.Runtime.dll",
+ "tools/net462-x86/System.Security.Claims.dll",
+ "tools/net462-x86/System.Security.Cryptography.Algorithms.dll",
+ "tools/net462-x86/System.Security.Cryptography.Csp.dll",
+ "tools/net462-x86/System.Security.Cryptography.Encoding.dll",
+ "tools/net462-x86/System.Security.Cryptography.Primitives.dll",
+ "tools/net462-x86/System.Security.Cryptography.X509Certificates.dll",
+ "tools/net462-x86/System.Security.Principal.dll",
+ "tools/net462-x86/System.Security.SecureString.dll",
+ "tools/net462-x86/System.Text.Encoding.Extensions.dll",
+ "tools/net462-x86/System.Text.Encoding.dll",
+ "tools/net462-x86/System.Text.RegularExpressions.dll",
+ "tools/net462-x86/System.Threading.Overlapped.dll",
+ "tools/net462-x86/System.Threading.Tasks.Parallel.dll",
+ "tools/net462-x86/System.Threading.Tasks.dll",
+ "tools/net462-x86/System.Threading.Thread.dll",
+ "tools/net462-x86/System.Threading.ThreadPool.dll",
+ "tools/net462-x86/System.Threading.Timer.dll",
+ "tools/net462-x86/System.Threading.dll",
+ "tools/net462-x86/System.ValueTuple.dll",
+ "tools/net462-x86/System.Xml.ReaderWriter.dll",
+ "tools/net462-x86/System.Xml.XDocument.dll",
+ "tools/net462-x86/System.Xml.XPath.XDocument.dll",
+ "tools/net462-x86/System.Xml.XPath.dll",
+ "tools/net462-x86/System.Xml.XmlDocument.dll",
+ "tools/net462-x86/System.Xml.XmlSerializer.dll",
+ "tools/net462-x86/netstandard.dll",
+ "tools/net462/GetDocument.Insider.exe",
+ "tools/net462/GetDocument.Insider.exe.config",
+ "tools/net462/Microsoft.OpenApi.dll",
+ "tools/net462/Microsoft.Win32.Primitives.dll",
+ "tools/net462/System.AppContext.dll",
+ "tools/net462/System.Buffers.dll",
+ "tools/net462/System.Collections.Concurrent.dll",
+ "tools/net462/System.Collections.NonGeneric.dll",
+ "tools/net462/System.Collections.Specialized.dll",
+ "tools/net462/System.Collections.dll",
+ "tools/net462/System.ComponentModel.EventBasedAsync.dll",
+ "tools/net462/System.ComponentModel.Primitives.dll",
+ "tools/net462/System.ComponentModel.TypeConverter.dll",
+ "tools/net462/System.ComponentModel.dll",
+ "tools/net462/System.Console.dll",
+ "tools/net462/System.Data.Common.dll",
+ "tools/net462/System.Diagnostics.Contracts.dll",
+ "tools/net462/System.Diagnostics.Debug.dll",
+ "tools/net462/System.Diagnostics.DiagnosticSource.dll",
+ "tools/net462/System.Diagnostics.FileVersionInfo.dll",
+ "tools/net462/System.Diagnostics.Process.dll",
+ "tools/net462/System.Diagnostics.StackTrace.dll",
+ "tools/net462/System.Diagnostics.TextWriterTraceListener.dll",
+ "tools/net462/System.Diagnostics.Tools.dll",
+ "tools/net462/System.Diagnostics.TraceSource.dll",
+ "tools/net462/System.Diagnostics.Tracing.dll",
+ "tools/net462/System.Drawing.Primitives.dll",
+ "tools/net462/System.Dynamic.Runtime.dll",
+ "tools/net462/System.Globalization.Calendars.dll",
+ "tools/net462/System.Globalization.Extensions.dll",
+ "tools/net462/System.Globalization.dll",
+ "tools/net462/System.IO.Compression.ZipFile.dll",
+ "tools/net462/System.IO.Compression.dll",
+ "tools/net462/System.IO.FileSystem.DriveInfo.dll",
+ "tools/net462/System.IO.FileSystem.Primitives.dll",
+ "tools/net462/System.IO.FileSystem.Watcher.dll",
+ "tools/net462/System.IO.FileSystem.dll",
+ "tools/net462/System.IO.IsolatedStorage.dll",
+ "tools/net462/System.IO.MemoryMappedFiles.dll",
+ "tools/net462/System.IO.Pipes.dll",
+ "tools/net462/System.IO.UnmanagedMemoryStream.dll",
+ "tools/net462/System.IO.dll",
+ "tools/net462/System.Linq.Expressions.dll",
+ "tools/net462/System.Linq.Parallel.dll",
+ "tools/net462/System.Linq.Queryable.dll",
+ "tools/net462/System.Linq.dll",
+ "tools/net462/System.Memory.dll",
+ "tools/net462/System.Net.Http.dll",
+ "tools/net462/System.Net.NameResolution.dll",
+ "tools/net462/System.Net.NetworkInformation.dll",
+ "tools/net462/System.Net.Ping.dll",
+ "tools/net462/System.Net.Primitives.dll",
+ "tools/net462/System.Net.Requests.dll",
+ "tools/net462/System.Net.Security.dll",
+ "tools/net462/System.Net.Sockets.dll",
+ "tools/net462/System.Net.WebHeaderCollection.dll",
+ "tools/net462/System.Net.WebSockets.Client.dll",
+ "tools/net462/System.Net.WebSockets.dll",
+ "tools/net462/System.Numerics.Vectors.dll",
+ "tools/net462/System.ObjectModel.dll",
+ "tools/net462/System.Reflection.Extensions.dll",
+ "tools/net462/System.Reflection.Primitives.dll",
+ "tools/net462/System.Reflection.dll",
+ "tools/net462/System.Resources.Reader.dll",
+ "tools/net462/System.Resources.ResourceManager.dll",
+ "tools/net462/System.Resources.Writer.dll",
+ "tools/net462/System.Runtime.CompilerServices.Unsafe.dll",
+ "tools/net462/System.Runtime.CompilerServices.VisualC.dll",
+ "tools/net462/System.Runtime.Extensions.dll",
+ "tools/net462/System.Runtime.Handles.dll",
+ "tools/net462/System.Runtime.InteropServices.RuntimeInformation.dll",
+ "tools/net462/System.Runtime.InteropServices.dll",
+ "tools/net462/System.Runtime.Numerics.dll",
+ "tools/net462/System.Runtime.Serialization.Formatters.dll",
+ "tools/net462/System.Runtime.Serialization.Json.dll",
+ "tools/net462/System.Runtime.Serialization.Primitives.dll",
+ "tools/net462/System.Runtime.Serialization.Xml.dll",
+ "tools/net462/System.Runtime.dll",
+ "tools/net462/System.Security.Claims.dll",
+ "tools/net462/System.Security.Cryptography.Algorithms.dll",
+ "tools/net462/System.Security.Cryptography.Csp.dll",
+ "tools/net462/System.Security.Cryptography.Encoding.dll",
+ "tools/net462/System.Security.Cryptography.Primitives.dll",
+ "tools/net462/System.Security.Cryptography.X509Certificates.dll",
+ "tools/net462/System.Security.Principal.dll",
+ "tools/net462/System.Security.SecureString.dll",
+ "tools/net462/System.Text.Encoding.Extensions.dll",
+ "tools/net462/System.Text.Encoding.dll",
+ "tools/net462/System.Text.RegularExpressions.dll",
+ "tools/net462/System.Threading.Overlapped.dll",
+ "tools/net462/System.Threading.Tasks.Parallel.dll",
+ "tools/net462/System.Threading.Tasks.dll",
+ "tools/net462/System.Threading.Thread.dll",
+ "tools/net462/System.Threading.ThreadPool.dll",
+ "tools/net462/System.Threading.Timer.dll",
+ "tools/net462/System.Threading.dll",
+ "tools/net462/System.ValueTuple.dll",
+ "tools/net462/System.Xml.ReaderWriter.dll",
+ "tools/net462/System.Xml.XDocument.dll",
+ "tools/net462/System.Xml.XPath.XDocument.dll",
+ "tools/net462/System.Xml.XPath.dll",
+ "tools/net462/System.Xml.XmlDocument.dll",
+ "tools/net462/System.Xml.XmlSerializer.dll",
+ "tools/net462/netstandard.dll",
+ "tools/net9.0/GetDocument.Insider.deps.json",
+ "tools/net9.0/GetDocument.Insider.dll",
+ "tools/net9.0/GetDocument.Insider.exe",
+ "tools/net9.0/GetDocument.Insider.runtimeconfig.json",
+ "tools/net9.0/Microsoft.AspNetCore.Connections.Abstractions.dll",
+ "tools/net9.0/Microsoft.AspNetCore.Connections.Abstractions.xml",
+ "tools/net9.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll",
+ "tools/net9.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.xml",
+ "tools/net9.0/Microsoft.AspNetCore.Http.Features.dll",
+ "tools/net9.0/Microsoft.AspNetCore.Http.Features.xml",
+ "tools/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.Features.dll",
+ "tools/net9.0/Microsoft.Extensions.Features.xml",
+ "tools/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.Logging.Abstractions.dll",
+ "tools/net9.0/Microsoft.Extensions.Options.dll",
+ "tools/net9.0/Microsoft.Extensions.Primitives.dll",
+ "tools/net9.0/Microsoft.Net.Http.Headers.dll",
+ "tools/net9.0/Microsoft.Net.Http.Headers.xml",
+ "tools/net9.0/Microsoft.OpenApi.dll",
+ "tools/netcoreapp2.1/GetDocument.Insider.deps.json",
+ "tools/netcoreapp2.1/GetDocument.Insider.dll",
+ "tools/netcoreapp2.1/GetDocument.Insider.runtimeconfig.json",
+ "tools/netcoreapp2.1/Microsoft.OpenApi.dll",
+ "tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll"
+ ]
+ },
+ "Microsoft.OpenApi/2.4.1": {
+ "sha512": "u7QhXCISMQuab3flasb1hoaiERmUqyWsW7tmQODyILoQ7mJV5IRGM+2KKZYo0QUfC13evEOcHAb6TPWgqEQtrw==",
+ "type": "package",
+ "path": "microsoft.openapi/2.4.1",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "README.md",
+ "lib/net8.0/Microsoft.OpenApi.dll",
+ "lib/net8.0/Microsoft.OpenApi.pdb",
+ "lib/net8.0/Microsoft.OpenApi.xml",
+ "lib/netstandard2.0/Microsoft.OpenApi.dll",
+ "lib/netstandard2.0/Microsoft.OpenApi.pdb",
+ "lib/netstandard2.0/Microsoft.OpenApi.xml",
+ "microsoft.openapi.2.4.1.nupkg.sha512",
+ "microsoft.openapi.nuspec"
+ ]
+ },
+ "Swashbuckle.AspNetCore/10.1.7": {
+ "sha512": "vgef8DPT411JU5JjHiDbr0WOxsIVuAvegPGtqmm4Na4JRl/264dfBJcGkiPHsAr5P+Vda+qN1rZKRtBl1rF9aA==",
+ "type": "package",
+ "path": "swashbuckle.aspnetcore/10.1.7",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "build/Swashbuckle.AspNetCore.props",
+ "buildMultiTargeting/Swashbuckle.AspNetCore.props",
+ "docs/package-readme.md",
+ "swashbuckle.aspnetcore.10.1.7.nupkg.sha512",
+ "swashbuckle.aspnetcore.nuspec"
+ ]
+ },
+ "Swashbuckle.AspNetCore.Swagger/10.1.7": {
+ "sha512": "EjLibt/d/QuRv170GoihTbcPUpgzSFm2WKHhnGJFZQ03JYzfuitsM79azaAR8NBwRunU7yScSX6HRE5JUlrEMQ==",
+ "type": "package",
+ "path": "swashbuckle.aspnetcore.swagger/10.1.7",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "lib/net10.0/Swashbuckle.AspNetCore.Swagger.dll",
+ "lib/net10.0/Swashbuckle.AspNetCore.Swagger.pdb",
+ "lib/net10.0/Swashbuckle.AspNetCore.Swagger.xml",
+ "lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll",
+ "lib/net8.0/Swashbuckle.AspNetCore.Swagger.pdb",
+ "lib/net8.0/Swashbuckle.AspNetCore.Swagger.xml",
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll",
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.pdb",
+ "lib/net9.0/Swashbuckle.AspNetCore.Swagger.xml",
+ "package-readme.md",
+ "swashbuckle.aspnetcore.swagger.10.1.7.nupkg.sha512",
+ "swashbuckle.aspnetcore.swagger.nuspec"
+ ]
+ },
+ "Swashbuckle.AspNetCore.SwaggerGen/10.1.7": {
+ "sha512": "PuubO9BjvNn6U3D9kLpuWKY1JtziWw7SsGBq0age1E50uQjQ8Fzl8s0EwzrLfANqYJNgDnJi9l7N1QxcGVB2Zw==",
+ "type": "package",
+ "path": "swashbuckle.aspnetcore.swaggergen/10.1.7",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
+ "package-readme.md",
+ "swashbuckle.aspnetcore.swaggergen.10.1.7.nupkg.sha512",
+ "swashbuckle.aspnetcore.swaggergen.nuspec"
+ ]
+ },
+ "Swashbuckle.AspNetCore.SwaggerUI/10.1.7": {
+ "sha512": "iJo3ODyUb/M8Vm8AH1r9y9iAba0w95xsCn3zFVl96ISRHbTDWxi+l7oFVCZqUEdjd97B8VMDPnMliWAdomR8uw==",
+ "type": "package",
+ "path": "swashbuckle.aspnetcore.swaggerui/10.1.7",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
+ "lib/net10.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
+ "lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
+ "lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
+ "package-readme.md",
+ "swashbuckle.aspnetcore.swaggerui.10.1.7.nupkg.sha512",
+ "swashbuckle.aspnetcore.swaggerui.nuspec"
+ ]
+ },
+ "System.Text.Json/8.0.5": {
+ "sha512": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
+ "type": "package",
+ "path": "system.text.json/8.0.5",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "Icon.png",
+ "LICENSE.TXT",
+ "PACKAGE.md",
+ "THIRD-PARTY-NOTICES.TXT",
+ "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll",
+ "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll",
+ "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll",
+ "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll",
+ "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll",
+ "buildTransitive/net461/System.Text.Json.targets",
+ "buildTransitive/net462/System.Text.Json.targets",
+ "buildTransitive/net6.0/System.Text.Json.targets",
+ "buildTransitive/netcoreapp2.0/System.Text.Json.targets",
+ "buildTransitive/netstandard2.0/System.Text.Json.targets",
+ "lib/net462/System.Text.Json.dll",
+ "lib/net462/System.Text.Json.xml",
+ "lib/net6.0/System.Text.Json.dll",
+ "lib/net6.0/System.Text.Json.xml",
+ "lib/net7.0/System.Text.Json.dll",
+ "lib/net7.0/System.Text.Json.xml",
+ "lib/net8.0/System.Text.Json.dll",
+ "lib/net8.0/System.Text.Json.xml",
+ "lib/netstandard2.0/System.Text.Json.dll",
+ "lib/netstandard2.0/System.Text.Json.xml",
+ "system.text.json.8.0.5.nupkg.sha512",
+ "system.text.json.nuspec",
+ "useSharedDesignerContext.txt"
+ ]
+ }
+ },
+ "projectFileDependencyGroups": {
+ "net9.0": [
+ "Swashbuckle.AspNetCore >= 10.1.7"
+ ]
+ },
+ "packageFolders": {
+ "/home/overseer/.nuget/packages/": {}
+ },
+ "project": {
+ "version": "1.0.0",
+ "restore": {
+ "projectUniqueName": "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj",
+ "projectName": "ControlCenter",
+ "projectPath": "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj",
+ "packagesPath": "/home/overseer/.nuget/packages/",
+ "outputPath": "/home/overseer/projects/control-center/backend/ControlCenter/obj/",
+ "projectStyle": "PackageReference",
+ "configFilePaths": [
+ "/home/overseer/.nuget/NuGet/NuGet.Config"
+ ],
+ "originalTargetFrameworks": [
+ "net9.0"
+ ],
+ "sources": {
+ "https://api.nuget.org/v3/index.json": {}
+ },
+ "frameworks": {
+ "net9.0": {
+ "targetAlias": "net9.0",
+ "projectReferences": {}
+ }
+ },
+ "warningProperties": {
+ "warnAsError": [
+ "NU1605"
+ ]
+ },
+ "restoreAuditProperties": {
+ "enableAudit": "true",
+ "auditLevel": "low",
+ "auditMode": "direct"
+ },
+ "SdkAnalysisLevel": "9.0.300"
+ },
+ "frameworks": {
+ "net9.0": {
+ "targetAlias": "net9.0",
+ "dependencies": {
+ "Swashbuckle.AspNetCore": {
+ "target": "Package",
+ "version": "[10.1.7, )"
+ }
+ },
+ "imports": [
+ "net461",
+ "net462",
+ "net47",
+ "net471",
+ "net472",
+ "net48",
+ "net481"
+ ],
+ "assetTargetFallback": true,
+ "warn": true,
+ "frameworkReferences": {
+ "Microsoft.AspNetCore.App": {
+ "privateAssets": "none"
+ },
+ "Microsoft.NETCore.App": {
+ "privateAssets": "all"
+ }
+ },
+ "runtimeIdentifierGraphPath": "/home/overseer/.dotnet/sdk/9.0.312/PortableRuntimeIdentifierGraph.json"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/ControlCenter/obj/project.nuget.cache b/backend/ControlCenter/obj/project.nuget.cache
new file mode 100644
index 0000000..1ebfc14
--- /dev/null
+++ b/backend/ControlCenter/obj/project.nuget.cache
@@ -0,0 +1,16 @@
+{
+ "version": 2,
+ "dgSpecHash": "MT3Qf7m9RtI=",
+ "success": true,
+ "projectFilePath": "/home/overseer/projects/control-center/backend/ControlCenter/ControlCenter.csproj",
+ "expectedPackageFiles": [
+ "/home/overseer/.nuget/packages/microsoft.extensions.apidescription.server/9.0.0/microsoft.extensions.apidescription.server.9.0.0.nupkg.sha512",
+ "/home/overseer/.nuget/packages/microsoft.openapi/2.4.1/microsoft.openapi.2.4.1.nupkg.sha512",
+ "/home/overseer/.nuget/packages/swashbuckle.aspnetcore/10.1.7/swashbuckle.aspnetcore.10.1.7.nupkg.sha512",
+ "/home/overseer/.nuget/packages/swashbuckle.aspnetcore.swagger/10.1.7/swashbuckle.aspnetcore.swagger.10.1.7.nupkg.sha512",
+ "/home/overseer/.nuget/packages/swashbuckle.aspnetcore.swaggergen/10.1.7/swashbuckle.aspnetcore.swaggergen.10.1.7.nupkg.sha512",
+ "/home/overseer/.nuget/packages/swashbuckle.aspnetcore.swaggerui/10.1.7/swashbuckle.aspnetcore.swaggerui.10.1.7.nupkg.sha512",
+ "/home/overseer/.nuget/packages/system.text.json/8.0.5/system.text.json.8.0.5.nupkg.sha512"
+ ],
+ "logs": []
+}
\ No newline at end of file