Files
ClaudeDo/src/ClaudeDo.Worker/Hub/WorkerHub.cs

145 lines
4.7 KiB
C#

using System.Reflection;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace ClaudeDo.Worker.Hub;
public record ActiveTaskDto(string Slot, string TaskId, DateTime StartedAt);
public record AppSettingsDto(
string DefaultClaudeInstructions,
string DefaultModel,
int DefaultMaxTurns,
string DefaultPermissionMode,
string WorktreeStrategy,
string? CentralWorktreeRoot,
bool WorktreeAutoCleanupEnabled,
int WorktreeAutoCleanupDays);
public record WorktreeCleanupDto(int Removed);
public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
{
private static readonly string Version =
Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "0.0.0";
private readonly QueueService _queue;
private readonly AgentFileService _agentService;
private readonly HubBroadcaster _broadcaster;
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
private readonly WorktreeMaintenanceService _wtMaintenance;
public WorkerHub(
QueueService queue,
AgentFileService agentService,
HubBroadcaster broadcaster,
IDbContextFactory<ClaudeDoDbContext> dbFactory,
WorktreeMaintenanceService wtMaintenance)
{
_queue = queue;
_agentService = agentService;
_broadcaster = broadcaster;
_dbFactory = dbFactory;
_wtMaintenance = wtMaintenance;
}
public string Ping() => $"pong v{Version}";
public IReadOnlyList<ActiveTaskDto> GetActive()
{
return _queue.GetActive()
.Select(a => new ActiveTaskDto(a.slot, a.taskId, a.startedAt))
.ToList();
}
public async Task RunNow(string taskId)
{
try
{
await _queue.RunNow(taskId);
}
catch (InvalidOperationException)
{
throw new HubException("override slot busy");
}
catch (KeyNotFoundException)
{
throw new HubException("task not found");
}
}
public async Task<string> ContinueTask(string taskId, string followUpPrompt)
{
try
{
return await _queue.ContinueTask(taskId, followUpPrompt);
}
catch (InvalidOperationException ex)
{
throw new HubException(ex.Message);
}
catch (KeyNotFoundException)
{
throw new HubException("task not found");
}
}
public bool CancelTask(string taskId) => _queue.CancelTask(taskId);
public void WakeQueue() => _queue.WakeQueue();
public async Task<List<AgentInfo>> GetAgents() => await _agentService.ScanAsync();
public async Task RefreshAgents() => await _agentService.ScanAsync();
public async Task<AppSettingsDto> GetAppSettings()
{
using var ctx = _dbFactory.CreateDbContext();
var row = await new AppSettingsRepository(ctx).GetAsync();
return new AppSettingsDto(
row.DefaultClaudeInstructions,
row.DefaultModel,
row.DefaultMaxTurns,
row.DefaultPermissionMode,
row.WorktreeStrategy,
row.CentralWorktreeRoot,
row.WorktreeAutoCleanupEnabled,
row.WorktreeAutoCleanupDays);
}
public async Task UpdateAppSettings(AppSettingsDto dto)
{
using var ctx = _dbFactory.CreateDbContext();
var repo = new AppSettingsRepository(ctx);
await repo.UpdateAsync(new AppSettingsEntity
{
Id = AppSettingsEntity.SingletonId,
DefaultClaudeInstructions = dto.DefaultClaudeInstructions ?? "",
DefaultModel = dto.DefaultModel ?? "sonnet",
DefaultMaxTurns = dto.DefaultMaxTurns,
DefaultPermissionMode = dto.DefaultPermissionMode ?? "bypassPermissions",
WorktreeStrategy = dto.WorktreeStrategy ?? "sibling",
CentralWorktreeRoot = dto.CentralWorktreeRoot,
WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled,
WorktreeAutoCleanupDays = dto.WorktreeAutoCleanupDays,
});
}
public async Task<WorktreeCleanupDto> CleanupFinishedWorktrees()
{
var result = await _wtMaintenance.CleanupFinishedAsync();
return new WorktreeCleanupDto(result.Removed);
}
public async Task<WorktreeResetDto> ResetAllWorktrees()
{
var result = await _wtMaintenance.ResetAllAsync();
return new WorktreeResetDto(result.Removed, result.TasksAffected, result.Blocked, result.RunningTasks);
}
}