using System.ComponentModel; using ClaudeDo.Data; using ClaudeDo.Worker.Queue; using Microsoft.EntityFrameworkCore; using ModelContextProtocol.Server; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.External; public sealed record QueueSlotDto(string Slot, string TaskId, DateTime StartedAt); public sealed record GetQueueStateResult( int ConfiguredSlots, int EffectiveSlots, IReadOnlyList ActiveSlots, IReadOnlyList WaitingTaskIds); [McpServerToolType] public sealed class QueueStateMcpTools { private readonly QueueService _queue; private readonly IDbContextFactory _dbFactory; public QueueStateMcpTools(QueueService queue, IDbContextFactory dbFactory) { _queue = queue; _dbFactory = dbFactory; } [McpServerTool, Description( "Read-only snapshot of the execution queue -- observe slot occupancy instead of inferring " + "it from maxParallelExecutions. Result: { configuredSlots, effectiveSlots, activeSlots: " + "[{ slot, taskId, startedAt }], waitingTaskIds }. configuredSlots is Settings -> " + "MaxParallelExecutions; effectiveSlots is that value stepped down by the usage throttle " + "(lower when the 5h/7d usage window is filling up) -- compare the two to see whether " + "throttling is currently active. activeSlots lists every task presently holding an " + "execution slot, with slot \"queue\" for a normal queue slot or \"override\" for the single " + "run_task_now/continue_task slot. waitingTaskIds lists queued, unblocked, non-manual, due " + "tasks in the order the queue would pick them next.")] public async Task GetQueueState(CancellationToken cancellationToken = default) { var (configured, effective) = await _queue.GetSlotCountsAsync(cancellationToken); var activeSlots = _queue.GetActive() .Select(a => new QueueSlotDto(a.slot, a.taskId, a.startedAt)) .ToList(); await using var ctx = await _dbFactory.CreateDbContextAsync(cancellationToken); var now = DateTime.UtcNow; var waitingTaskIds = await ctx.Tasks .Where(t => t.Status == TaskStatus.Queued && t.BlockedByTaskId == null && !t.IsManual && (t.ScheduledFor == null || t.ScheduledFor <= now)) .OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt) .Select(t => t.Id) .ToListAsync(cancellationToken); return new GetQueueStateResult(configured, effective, activeSlots, waitingTaskIds); } }