Compare commits

..

1 Commits

Author SHA1 Message Date
cubecraft-agents[bot]
fb88eab4d1 feat(CUB-55): add SendStatusUpdate method to AgentStatusHub 2026-04-27 10:07:07 +00:00
5 changed files with 114 additions and 41 deletions

View File

@@ -37,6 +37,33 @@ public class AgentStatusHub : Hub<IAgentStatusClient>
_logger = logger;
}
/// <summary>
/// Broadcasts an agent status update to all connected clients.
///
/// <para>
/// Any connected client (or server-side caller) can invoke this method
/// to push a status update to every subscriber. The DTO is converted to
/// an <see cref="AgentStatusUpdate"/> record and relayed through the
/// <see cref="IAgentStatusClient.AgentStatusChanged"/> callback.
/// </para>
/// </summary>
/// <param name="update">The agent status update DTO to broadcast.</param>
public async Task SendStatusUpdate(AgentStatusUpdateDto update)
{
_logger.LogInformation(
"Broadcasting status update for agent {AgentId}: {Status}",
update.AgentId, update.Status);
var agentUpdate = update.ToUpdate();
// Broadcast to all connected clients
await Clients.All.AgentStatusChanged(agentUpdate);
// Also push to the specific agent's group
var agentGroup = AgentGroupName(update.AgentId);
await Clients.Group(agentGroup).AgentStatusChanged(agentUpdate);
}
/// <summary>
/// Adds the calling connection to the fleet group.
/// Once joined, the client will receive all agent status changes

View File

@@ -72,6 +72,80 @@ public record TaskProgressUpdate(
string? Elapsed
);
/// <summary>
/// Data transfer object for broadcasting agent status updates
/// to all connected SignalR clients via the hub's SendStatusUpdate method.
///
/// <para>This DTO provides a mutable, serialization-friendly alternative to
/// <see cref="AgentStatusUpdate"/> for callers that construct updates
/// from external data sources (e.g., HTTP API payloads).</para>
/// </summary>
public class AgentStatusUpdateDto
{
/// <summary>
/// Agent identifier, e.g. "otto", "dex", "rex".
/// </summary>
public string AgentId { get; set; } = string.Empty;
/// <summary>
/// Human-readable display name, e.g. "Otto", "Dex".
/// </summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// Role description, e.g. "Orchestrator Agent", "Backend Specialist".
/// </summary>
public string Role { get; set; } = string.Empty;
/// <summary>
/// Current operational status of the agent as lowercase string:
/// "active", "idle", "thinking", "error".
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// Description of the agent's current task, if any.
/// </summary>
public string? CurrentTask { get; set; }
/// <summary>
/// Full session key, e.g. "agent:otto:telegram:direct:8787451565".
/// </summary>
public string SessionKey { get; set; } = string.Empty;
/// <summary>
/// Communication channel, e.g. "telegram", "discord", "slack".
/// </summary>
public string Channel { get; set; } = string.Empty;
/// <summary>
/// ISO 8601 timestamp of the agent's last activity.
/// </summary>
public string LastActivity { get; set; } = string.Empty;
/// <summary>
/// Error message when the agent status is "error".
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// Converts this DTO to an immutable <see cref="AgentStatusUpdate"/> record
/// for use with the typed SignalR client interface.
/// </summary>
/// <returns>An <see cref="AgentStatusUpdate"/> with equivalent field values.</returns>
public AgentStatusUpdate ToUpdate() => new(
AgentId,
DisplayName,
Role,
Status,
CurrentTask,
SessionKey,
Channel,
LastActivity,
ErrorMessage
);
}
/// <summary>
/// Snapshot of an agent's full card data, sent on initial connection
/// or when the fleet state is requested.

View File

@@ -1,28 +0,0 @@
// ============================================================================
// Hub Page — Responsive AgentCard Grid
// Desktop (≥1024px): 2×2 grid
// Mobile (<1024px): single-column stack
// ============================================================================
.hub-page {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
padding: var(--cc-section-padding, 16px);
min-height: 400px;
overflow-x: hidden;
}
.hub-page__placeholder {
color: var(--cc-on-surface-variant);
font-size: 16px;
text-align: center;
padding: 24px 0;
}
// Desktop / kiosk breakpoint — 2-column grid
@media (min-width: 1024px) {
.hub-page {
grid-template-columns: repeat(2, 1fr);
}
}

View File

@@ -9,7 +9,18 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
<p class="hub-page__placeholder">Command Hub — Fleet status grid will render here</p>
</div>
`,
styleUrl: './hub-page.component.scss',
styles: [`
.hub-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.hub-page__placeholder {
color: var(--cc-on-surface-variant);
font-size: 16px;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HubPageComponent {}

View File

@@ -48,17 +48,6 @@ html {
// These are NOT part of the M3 tonal palette; they are semantic overrides.
// ---------------------------------------------------------------------------
:root {
// --- Tactical Dark Mode color palette (CUB-47) ---
--color-surface: #0F172A;
--color-surface-light: #1E293B;
--color-primary: #38BDF8;
--color-secondary: #2DD4BF;
--color-accent: #A78BFA;
--color-danger: #F87171;
--color-text-primary: #FFFFFF;
--color-text-secondary: #94A3B8;
--color-border: #334155;
// --- Status colors ---
--status-active: #38BDF8;
--status-idle: #2DD4BF;
@@ -101,7 +90,7 @@ html {
// Global Body Styles
// ---------------------------------------------------------------------------
body {
background-color: var(--color-surface);
background-color: var(--cc-background);
color: var(--cc-on-surface);
font-family: 'Inter', 'Roboto', sans-serif;
margin: 0;