feat(state): advance WaitingForChildren parent to review when children terminal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -392,5 +392,56 @@ public sealed class TaskStateService : ITaskStateService
|
||||
{
|
||||
_logger.LogWarning(ex, "TryCompleteParent failed for {ParentId}", parentId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await TryAdvanceImprovementParentAsync(parentId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "TryAdvanceImprovementParent failed for {ParentId}", parentId);
|
||||
}
|
||||
}
|
||||
|
||||
// Improvement parents sit in WaitingForChildren while their suggested children run.
|
||||
// Once every child is terminal (Done/Failed/Cancelled) the parent surfaces for review;
|
||||
// a failed or cancelled child does not wedge the parent — it is flagged on the result.
|
||||
private async Task TryAdvanceImprovementParentAsync(string parentId)
|
||||
{
|
||||
string? parentResult;
|
||||
List<TaskStatus> childStatuses;
|
||||
await using (var ctx = await _dbFactory.CreateDbContextAsync(CancellationToken.None))
|
||||
{
|
||||
var parent = await ctx.Tasks.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Id == parentId, CancellationToken.None);
|
||||
if (parent is null || parent.Status != TaskStatus.WaitingForChildren) return;
|
||||
parentResult = parent.Result;
|
||||
childStatuses = await ctx.Tasks
|
||||
.Where(t => t.ParentTaskId == parentId)
|
||||
.Select(t => t.Status)
|
||||
.ToListAsync(CancellationToken.None);
|
||||
}
|
||||
if (childStatuses.Count == 0) return;
|
||||
|
||||
bool allTerminal = childStatuses.All(s =>
|
||||
s == TaskStatus.Done || s == TaskStatus.Failed || s == TaskStatus.Cancelled);
|
||||
if (!allTerminal) return;
|
||||
|
||||
int failed = childStatuses.Count(s => s == TaskStatus.Failed);
|
||||
int cancelled = childStatuses.Count(s => s == TaskStatus.Cancelled);
|
||||
var newResult = parentResult;
|
||||
if (failed + cancelled > 0)
|
||||
{
|
||||
var note = $"⚠ Children: {failed} failed, {cancelled} cancelled.";
|
||||
newResult = string.IsNullOrWhiteSpace(parentResult) ? note : $"{parentResult}\n\n{note}";
|
||||
}
|
||||
|
||||
await using var writeCtx = await _dbFactory.CreateDbContextAsync(CancellationToken.None);
|
||||
await writeCtx.Tasks
|
||||
.Where(t => t.Id == parentId && t.Status == TaskStatus.WaitingForChildren)
|
||||
.ExecuteUpdateAsync(s => s
|
||||
.SetProperty(t => t.Status, TaskStatus.WaitingForReview)
|
||||
.SetProperty(t => t.Result, newResult), CancellationToken.None);
|
||||
await _broadcaster.TaskUpdated(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user