refactor(worker/queue): split queue waker and picker, auto-wake on enqueue

Slice 3 of the worker state and queue consolidation refactor.

- Add IQueueWaker / QueueWaker (singleton holding the wake semaphore).
- Add IQueuePicker / QueuePicker; raw SQL UPDATE...RETURNING moves out of
  TaskRepository.GetNextQueuedAgentTaskAsync (deleted) and now also filters
  on blocked_by_task_id IS NULL and writes started_at on claim.
- TaskStateService takes IQueueWaker directly; the Func<QueueService>
  indirection is gone. State transitions to Queued auto-wake the dispatcher.
- QueueService waits via the shared waker and dispatches via the picker.
- Drop explicit _queue.WakeQueue() calls in WorkerHub.QueuePlanningSubtasksAsync
  and ExternalMcpService.AddTask. The hub WakeQueue endpoint stays for
  diagnostics, delegating to _waker.Wake().
- Migrate tests; pre-existing flaky AppSettings/ExternalMcp tests untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-04-27 12:05:54 +02:00
parent 8823265e5a
commit 064a903076
18 changed files with 354 additions and 191 deletions

View File

@@ -117,7 +117,7 @@ public sealed class ExternalMcpService
ListId = listId,
Title = title,
Description = description,
Status = queueImmediately ? TaskStatus.Queued : TaskStatus.Manual,
Status = TaskStatus.Manual,
CreatedAt = DateTime.UtcNow,
CommitType = list.DefaultCommitType,
CreatedBy = createdBy,
@@ -128,7 +128,13 @@ public sealed class ExternalMcpService
await _tasks.SetTagsAsync(entity.Id, tags, cancellationToken);
if (queueImmediately)
_queue.WakeQueue();
{
// Routes through TaskStateService so the queue is woken automatically.
var enqueue = await _state.EnqueueAsync(entity.Id, cancellationToken);
if (!enqueue.Ok)
throw new InvalidOperationException(enqueue.Reason ?? "Cannot enqueue task.");
entity.Status = TaskStatus.Queued;
}
await _broadcaster.TaskUpdated(entity.Id);
return ToDto(entity);