feat(worker): surface task numbers in MCP tool return payloads
Adds Number alongside every task id in External/'s DTOs -- the two central mappers (ToDto/ToRefDto -> TaskDto/TaskRefDto) plus every DTO that carries a bare task id and bypasses them (batch results, queue state, wait-for-change, config, attachments, handoff, lifecycle, merge-preview-set, worktree list). Input resolution (#123 as an argument) stays for slice 3.
This commit is contained in:
+26
-9
@@ -7,16 +7,17 @@ using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.External;
|
||||
|
||||
public sealed record QueueSlotDto(string Slot, string TaskId, DateTime StartedAt);
|
||||
public sealed record QueueSlotDto(string Slot, string TaskId, int? Number, DateTime StartedAt);
|
||||
|
||||
public sealed record QueueWaitReasonDto(string TaskId, string Reason, string BlockedByTaskId);
|
||||
public sealed record QueueWaitReasonDto(string TaskId, int Number, string Reason, string BlockedByTaskId, int? BlockedByNumber);
|
||||
|
||||
public sealed record GetQueueStateResult(
|
||||
int ConfiguredSlots,
|
||||
int EffectiveSlots,
|
||||
IReadOnlyList<QueueSlotDto> ActiveSlots,
|
||||
IReadOnlyList<string> WaitingTaskIds,
|
||||
IReadOnlyList<QueueWaitReasonDto> ScopeBlockedTasks);
|
||||
IReadOnlyList<QueueWaitReasonDto> ScopeBlockedTasks,
|
||||
IReadOnlyList<int> WaitingTaskNumbers);
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class QueueStateMcpTools
|
||||
@@ -44,13 +45,21 @@ public sealed class QueueStateMcpTools
|
||||
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();
|
||||
var active = _queue.GetActive();
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(cancellationToken);
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var activeTaskIds = active.Select(a => a.taskId).ToList();
|
||||
var activeNumbers = await ctx.Tasks
|
||||
.Where(t => activeTaskIds.Contains(t.Id))
|
||||
.Select(t => new { t.Id, t.Number })
|
||||
.ToDictionaryAsync(t => t.Id, t => t.Number, cancellationToken);
|
||||
|
||||
var activeSlots = active
|
||||
.Select(a => new QueueSlotDto(a.slot, a.taskId, activeNumbers.TryGetValue(a.taskId, out var n) ? n : null, a.startedAt))
|
||||
.ToList();
|
||||
|
||||
var waiting = await ctx.Tasks
|
||||
.Where(t => t.Status == TaskStatus.Queued
|
||||
&& t.BlockedByTaskId == null
|
||||
@@ -73,10 +82,18 @@ public sealed class QueueStateMcpTools
|
||||
if (!serializingListIds.Contains(t.ListId)) continue;
|
||||
var blockerId = await ScopeOverlap.FindBlockingSiblingAsync(ctx, t, cancellationToken);
|
||||
if (blockerId is not null)
|
||||
scopeBlocked.Add(new QueueWaitReasonDto(t.Id, "scope_overlap", blockerId));
|
||||
{
|
||||
var blockerNumber = await ctx.Tasks
|
||||
.Where(b => b.Id == blockerId)
|
||||
.Select(b => (int?)b.Number)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
scopeBlocked.Add(new QueueWaitReasonDto(t.Id, t.Number, "scope_overlap", blockerId, blockerNumber));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GetQueueStateResult(configured, effective, activeSlots, waiting.Select(t => t.Id).ToList(), scopeBlocked);
|
||||
return new GetQueueStateResult(
|
||||
configured, effective, activeSlots, waiting.Select(t => t.Id).ToList(), scopeBlocked,
|
||||
waiting.Select(t => t.Number).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user