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 -- call this to observe slot occupancy instead " + "of inferring it from maxParallelExecutions. effectiveSlots is configuredSlots stepped down " + "by the usage throttle (lower when the 5h/7d usage window fills up), so comparing the two " + "shows whether throttling is currently active. Each active slot is \"queue\" (a normal " + "queue slot) or \"override\" (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); } }