Move interface declarations into per-area Interfaces/ subfolders, merge the small task-list filter classes into StatusFilter/SmartFlagFilter, and simplify related services, converters and hub DTO handling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
128 lines
4.1 KiB
C#
128 lines
4.1 KiB
C#
using ClaudeDo.Data;
|
|
using ClaudeDo.Data.Repositories;
|
|
using ClaudeDo.Worker.Runner;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ClaudeDo.Worker.Queue;
|
|
|
|
public sealed class OverrideSlotService
|
|
{
|
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
|
private readonly TaskRunner _runner;
|
|
private readonly ILogger<OverrideSlotService> _logger;
|
|
|
|
private readonly object _lock = new();
|
|
private volatile QueueSlotState? _slot;
|
|
|
|
public OverrideSlotService(
|
|
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
|
TaskRunner runner,
|
|
ILogger<OverrideSlotService> logger)
|
|
{
|
|
_dbFactory = dbFactory;
|
|
_runner = runner;
|
|
_logger = logger;
|
|
}
|
|
|
|
public QueueSlotState? CurrentSlot => _slot;
|
|
|
|
public async Task RunNow(string taskId)
|
|
{
|
|
using (var context = _dbFactory.CreateDbContext())
|
|
{
|
|
var exists = await new TaskRepository(context).GetByIdAsync(taskId);
|
|
if (exists is null)
|
|
throw new KeyNotFoundException($"Task '{taskId}' not found.");
|
|
}
|
|
|
|
StartInSlot(taskId, ct => RunInSlotAsync(taskId, ct), "RunInSlotAsync failed for task {TaskId}");
|
|
}
|
|
|
|
public async Task<string> ContinueTask(string taskId, string followUpPrompt)
|
|
{
|
|
using (var context = _dbFactory.CreateDbContext())
|
|
{
|
|
var task = await new TaskRepository(context).GetByIdAsync(taskId)
|
|
?? throw new KeyNotFoundException($"Task '{taskId}' not found.");
|
|
|
|
if (task.Status == Data.Models.TaskStatus.Running)
|
|
throw new InvalidOperationException("task is already running");
|
|
}
|
|
|
|
StartInSlot(taskId, ct => RunContinueInSlotAsync(taskId, followUpPrompt, ct),
|
|
"RunContinueInSlotAsync failed for task {TaskId}");
|
|
|
|
return taskId;
|
|
}
|
|
|
|
// Claims the single override slot under lock, runs <work> in the background,
|
|
// and releases the slot when it completes. Throws if the slot is already busy.
|
|
private void StartInSlot(string taskId, Func<CancellationToken, Task> work, string faultMessage)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_slot is not null)
|
|
throw new InvalidOperationException("override slot busy");
|
|
|
|
var cts = new CancellationTokenSource();
|
|
_slot = new QueueSlotState { TaskId = taskId, StartedAt = DateTime.UtcNow, Cts = cts };
|
|
|
|
_ = work(cts.Token).ContinueWith(t =>
|
|
{
|
|
if (t.IsFaulted)
|
|
_logger.LogError(t.Exception, faultMessage, taskId);
|
|
lock (_lock) { _slot = null; }
|
|
cts.Dispose();
|
|
}, TaskScheduler.Default);
|
|
}
|
|
}
|
|
|
|
public bool TryCancel(string taskId)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_slot is not null && _slot.TaskId == taskId)
|
|
{
|
|
_slot.Cts.Cancel();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private async Task RunInSlotAsync(string taskId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Starting task {TaskId} in override slot", taskId);
|
|
|
|
Data.Models.TaskEntity task;
|
|
using (var context = _dbFactory.CreateDbContext())
|
|
{
|
|
var taskRepo = new TaskRepository(context);
|
|
task = await taskRepo.GetByIdAsync(taskId, ct)
|
|
?? throw new KeyNotFoundException($"Task '{taskId}' not found.");
|
|
}
|
|
|
|
await _runner.RunAsync(task, "override", ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Override slot runner error for task {TaskId}", taskId);
|
|
}
|
|
}
|
|
|
|
private async Task RunContinueInSlotAsync(string taskId, string followUpPrompt, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Continuing task {TaskId} in override slot", taskId);
|
|
await _runner.ContinueAsync(taskId, followUpPrompt, "override", ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Continue runner error for task {TaskId}", taskId);
|
|
}
|
|
}
|
|
}
|