From 47cbeed456ac9ad063e502fc43125ce9c39d0d77 Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:07:05 +0000 Subject: [PATCH 1/3] feat(CUB-56): [Control Center] Agent State Database Migration --- backend/ControlCenter.Api.csproj | 7 +++--- backend/Program.cs | 38 -------------------------------- 2 files changed, 4 insertions(+), 41 deletions(-) delete mode 100644 backend/Program.cs diff --git a/backend/ControlCenter.Api.csproj b/backend/ControlCenter.Api.csproj index d2896da..7d43500 100644 --- a/backend/ControlCenter.Api.csproj +++ b/backend/ControlCenter.Api.csproj @@ -8,11 +8,12 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/backend/Program.cs b/backend/Program.cs deleted file mode 100644 index c45c355..0000000 --- a/backend/Program.cs +++ /dev/null @@ -1,38 +0,0 @@ -using ControlCenter.Api.Data; -using ControlCenter.Api.Hubs; -using Microsoft.EntityFrameworkCore; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. -builder.Services.AddOpenApi(); - -// Register SignalR for real-time agent status updates -builder.Services.AddSignalR(); - -// Register DbContext with PostgreSQL -builder.Services.AddDbContext(options => -{ - var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") - ?? "Host=localhost;Database=control_center;Username=postgres;Password=postgres"; - - options.UseNpgsql(connectionString, npgsqlOptions => - { - npgsqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName); - }); -}); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.MapOpenApi(); -} - -app.UseHttpsRedirection(); - -// Map SignalR hubs -app.MapHub("/hubs/agent-status"); - -app.Run(); \ No newline at end of file -- 2.53.0 From 040d4cb54d08f41d37a3c95e7965fb1d84cd3d5f Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 02:24:51 +0000 Subject: [PATCH 2/3] fix(CUB-56): Restore Program.cs deletion - PR should only add Swashbuckle package --- backend/Program.cs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 backend/Program.cs diff --git a/backend/Program.cs b/backend/Program.cs new file mode 100644 index 0000000..c45c355 --- /dev/null +++ b/backend/Program.cs @@ -0,0 +1,38 @@ +using ControlCenter.Api.Data; +using ControlCenter.Api.Hubs; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddOpenApi(); + +// Register SignalR for real-time agent status updates +builder.Services.AddSignalR(); + +// Register DbContext with PostgreSQL +builder.Services.AddDbContext(options => +{ + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? "Host=localhost;Database=control_center;Username=postgres;Password=postgres"; + + options.UseNpgsql(connectionString, npgsqlOptions => + { + npgsqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +// Map SignalR hubs +app.MapHub("/hubs/agent-status"); + +app.Run(); \ No newline at end of file -- 2.53.0 From f170def0eabafa4a854ee63910ea379bd73b546b Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 04:10:18 +0000 Subject: [PATCH 3/3] feat(CUB-54): implement Agent State Repository with EF Core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AgentState read model (Models/AgentState.cs) - Add IAgentStateRepository interface with GetAllAsync, GetBySessionKeyAsync, UpdateStatusAsync - Add AgentStateRepository EF Core implementation mapping Agent entity → AgentState model - Register IAgentStateRepository in DI (Program.cs) - Exclude ControlCenter sub-project from Api compilation Build: 0 warnings, 0 errors --- backend/ControlCenter.Api.csproj | 5 ++ backend/Models/AgentState.cs | 19 +++++ backend/Program.cs | 4 + backend/Repositories/AgentStateRepository.cs | 76 +++++++++++++++++++ backend/Repositories/IAgentStateRepository.cs | 27 +++++++ 5 files changed, 131 insertions(+) create mode 100644 backend/Models/AgentState.cs create mode 100644 backend/Repositories/AgentStateRepository.cs create mode 100644 backend/Repositories/IAgentStateRepository.cs diff --git a/backend/ControlCenter.Api.csproj b/backend/ControlCenter.Api.csproj index 7d43500..a509f52 100644 --- a/backend/ControlCenter.Api.csproj +++ b/backend/ControlCenter.Api.csproj @@ -6,6 +6,11 @@ enable + + + + + diff --git a/backend/Models/AgentState.cs b/backend/Models/AgentState.cs new file mode 100644 index 0000000..7c5f4a6 --- /dev/null +++ b/backend/Models/AgentState.cs @@ -0,0 +1,19 @@ +namespace ControlCenter.Api.Models; + +/// +/// Read-only model representing an agent's current state. +/// Used as the return type from the Agent State Repository +/// to decouple consumers from the persistence layer. +/// +public class AgentState +{ + public Guid Id { get; set; } + public string Status { get; set; } = string.Empty; + public string? Task { get; set; } + public int? Progress { get; set; } + public string SessionKey { get; set; } = string.Empty; + public string Channel { get; set; } = string.Empty; + public DateTime LastActivity { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/backend/Program.cs b/backend/Program.cs index c45c355..d23abf3 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -1,5 +1,6 @@ using ControlCenter.Api.Data; using ControlCenter.Api.Hubs; +using ControlCenter.Api.Repositories; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -10,6 +11,9 @@ builder.Services.AddOpenApi(); // Register SignalR for real-time agent status updates builder.Services.AddSignalR(); +// Register Agent State Repository +builder.Services.AddScoped(); + // Register DbContext with PostgreSQL builder.Services.AddDbContext(options => { diff --git a/backend/Repositories/AgentStateRepository.cs b/backend/Repositories/AgentStateRepository.cs new file mode 100644 index 0000000..ca452ff --- /dev/null +++ b/backend/Repositories/AgentStateRepository.cs @@ -0,0 +1,76 @@ +using ControlCenter.Api.Data; +using ControlCenter.Api.Entities; +using ControlCenter.Api.Models; +using Microsoft.EntityFrameworkCore; + +namespace ControlCenter.Api.Repositories; + +/// +/// EF Core implementation of the Agent State Repository. +/// Maps between the persisted entity and the +/// read-oriented model. +/// +public class AgentStateRepository : IAgentStateRepository +{ + private readonly AppDbContext _db; + + public AgentStateRepository(AppDbContext db) + { + _db = db; + } + + /// + public async Task> GetAllAsync(CancellationToken ct = default) + { + var agents = await _db.Agents + .AsNoTracking() + .OrderByDescending(a => a.LastActivity) + .ToListAsync(ct); + + return agents.Select(ToModel).ToList(); + } + + /// + public async Task GetBySessionKeyAsync(string sessionKey, CancellationToken ct = default) + { + var agent = await _db.Agents + .AsNoTracking() + .FirstOrDefaultAsync(a => a.SessionKey == sessionKey, ct); + + return agent is null ? null : ToModel(agent); + } + + /// + public async Task UpdateStatusAsync(Guid id, string status, CancellationToken ct = default) + { + if (!Enum.TryParse(status, ignoreCase: true, out var parsedStatus)) + return false; + + var agent = await _db.Agents.FindAsync([id], ct); + if (agent is null) + return false; + + agent.Status = parsedStatus; + agent.UpdatedAt = DateTime.UtcNow; + agent.LastActivity = DateTime.UtcNow; + + await _db.SaveChangesAsync(ct); + return true; + } + + /// + /// Maps a persisted entity to a model. + /// + private static AgentState ToModel(Agent agent) => new() + { + Id = agent.Id, + Status = agent.Status.ToString(), + Task = agent.Task, + Progress = agent.Progress, + SessionKey = agent.SessionKey, + Channel = agent.Channel, + LastActivity = agent.LastActivity, + CreatedAt = agent.CreatedAt, + UpdatedAt = agent.UpdatedAt, + }; +} \ No newline at end of file diff --git a/backend/Repositories/IAgentStateRepository.cs b/backend/Repositories/IAgentStateRepository.cs new file mode 100644 index 0000000..4c0c2b9 --- /dev/null +++ b/backend/Repositories/IAgentStateRepository.cs @@ -0,0 +1,27 @@ +using ControlCenter.Api.Models; + +namespace ControlCenter.Api.Repositories; + +/// +/// Repository interface for accessing and mutating Agent State. +/// Provides a clean abstraction over the EF Core data access layer. +/// +public interface IAgentStateRepository +{ + /// + /// Retrieve all agent states. + /// + Task> GetAllAsync(CancellationToken ct = default); + + /// + /// Retrieve a single agent state by its session key. + /// Returns null if no agent is found with the given session key. + /// + Task GetBySessionKeyAsync(string sessionKey, CancellationToken ct = default); + + /// + /// Update the status of an agent by its primary key. + /// Returns true if the agent was found and updated, false otherwise. + /// + Task UpdateStatusAsync(Guid id, string status, CancellationToken ct = default); +} \ No newline at end of file -- 2.53.0