Merge claudedo/616befd0f8a64dcb9e3dac9d6499de16
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
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<QueueSlotDto> ActiveSlots,
|
||||
IReadOnlyList<string> WaitingTaskIds);
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class QueueStateMcpTools
|
||||
{
|
||||
private readonly QueueService _queue;
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
|
||||
public QueueStateMcpTools(QueueService queue, IDbContextFactory<ClaudeDoDbContext> 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<GetQueueStateResult> 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);
|
||||
}
|
||||
}
|
||||
+11
-6
@@ -12,10 +12,13 @@ public sealed record WaitForTaskChangeResult(IReadOnlyList<TaskStatusChangeDto>
|
||||
[McpServerToolType]
|
||||
public sealed class TaskWaitMcpTools
|
||||
{
|
||||
// InteractiveLaunchSpecService sets MCP_TOOL_TIMEOUT=200000ms for the list handler
|
||||
// session; this cap leaves a ~30s margin so the tool itself reports TimedOut instead
|
||||
// of racing the client's own abort.
|
||||
internal const int MaxTimeoutSeconds = 170;
|
||||
// Every ClaudeDo-owned launcher (ClaudeProcess for headless runs, InteractiveLaunchSpecService
|
||||
// for ConPTY sessions) sets MCP_TOOL_TIMEOUT=930000ms on the claude CLI process; this cap
|
||||
// leaves a ~30s margin under that so the tool itself reports TimedOut instead of racing the
|
||||
// client's own abort. A caller running claude with a different MCP_TOOL_TIMEOUT (or none --
|
||||
// the CLI default is 60s) will see its own client-side timeout fire first; this tool has no
|
||||
// way to detect or compensate for that from the server side.
|
||||
internal const int MaxTimeoutSeconds = 900;
|
||||
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
@@ -27,9 +30,11 @@ public sealed class TaskWaitMcpTools
|
||||
|
||||
[McpServerTool, Description(
|
||||
"Blocks until at least one of the given tasks leaves Queued/Running, or until timeoutSeconds elapses " +
|
||||
"(clamped server-side to 170s). Returns immediately if any task is already outside Queued/Running " +
|
||||
"(clamped server-side to 900s). Returns immediately if any task is already outside Queued/Running " +
|
||||
"when called (an unknown id is reported as status \"NotFound\" and counts as changed). Use this instead " +
|
||||
"of polling get_task in a loop. Result: { changed: [{ taskId, status }], timedOut }.")]
|
||||
"of polling get_task in a loop. Requires the calling claude process to run with " +
|
||||
"MCP_TOOL_TIMEOUT >= 930000 (ms) for a long wait to actually be held open -- ClaudeDo's own " +
|
||||
"launchers already set this. Result: { changed: [{ taskId, status }], timedOut }.")]
|
||||
public async Task<WaitForTaskChangeResult> WaitForTaskChange(
|
||||
string[] taskIds, int timeoutSeconds = 60, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user