feat(worker-mcp): raise wait_for_task_change timeout, expose queue slot state

MaxTimeoutSeconds was 170s against runs that take tens of minutes, forcing
a dozen full-context wait rounds per long-running batch. Raise it to 900s
and raise MCP_TOOL_TIMEOUT in lockstep (ClaudeProcess + every
InteractiveLaunchSpecService launch spec) to 930000ms so the client
connection actually stays open that long instead of aborting first.

Add get_queue_state (QueueStateMcpTools): configured vs. effective
parallel-slot count (via QueueService.GetSlotCountsAsync, extracted from
the former GetEffectiveMaxParallelAsync), active slots with taskId +
startedAt including the run_task_now override slot, and queued tasks in
pick order -- so a caller can observe queue occupancy instead of inferring
it from maxParallelExecutions.
This commit is contained in:
mika kuns
2026-08-05 20:47:57 +02:00
parent bdee731376
commit d43b5fcefc
10 changed files with 299 additions and 36 deletions
+10 -8
View File
@@ -125,7 +125,7 @@ public sealed class QueueService : BackgroundService
await Task.WhenAny(wakeTask, timerTask);
var maxParallel = await GetEffectiveMaxParallelAsync(stoppingToken);
var (_, maxParallel) = await GetSlotCountsAsync(stoppingToken);
var gateDecision = await _usageGate.EvaluateAsync(stoppingToken);
await ReportUsageGateTransitionAsync(gateDecision);
@@ -200,11 +200,13 @@ public sealed class QueueService : BackgroundService
}
/// <summary>
/// Configured parallelism, stepped down by <see cref="UsageThrottle"/> ahead of the hard usage
/// gate. A missing snapshot (poll hasn't landed / endpoint unreachable) fails open to the
/// configured value — a broken usage poll must never stall the queue.
/// Configured parallelism, and that same value stepped down by <see cref="UsageThrottle"/>
/// ahead of the hard usage gate. A missing snapshot (poll hasn't landed / endpoint
/// unreachable) fails open to the configured value — a broken usage poll must never stall
/// the queue. Also called by <c>get_queue_state</c> (External MCP) to surface the throttle
/// from outside the process.
/// </summary>
private async Task<int> GetEffectiveMaxParallelAsync(CancellationToken ct)
public async Task<(int Configured, int Effective)> GetSlotCountsAsync(CancellationToken ct)
{
int configured;
int softPct, hardPct, gateFivePct, gateSevenPct;
@@ -221,14 +223,14 @@ public sealed class QueueService : BackgroundService
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to read max parallel executions; defaulting to 1");
return 1;
return (1, 1);
}
var snapshot = _usageState.Snapshot;
if (snapshot is null || _usageState.LastError is not null)
{
_lastEffectiveSlots = configured;
return configured;
return (configured, configured);
}
var effective = UsageThrottle.EffectiveSlots(
@@ -236,7 +238,7 @@ public sealed class QueueService : BackgroundService
softPct, hardPct, gateFivePct, gateSevenPct);
ReportThrottleTransition(configured, effective, snapshot);
return effective;
return (configured, effective);
}
private void ReportThrottleTransition(int configured, int effective, UsageSnapshot snapshot)