feat(planning): gate subtask queueing behind plan finalization
Planning subtasks are now "Draft" until their parent plan is finalized, then "Planned" (queueable). Finalizing a plan no longer auto-queues the child chain; the user sends the plan to the queue explicitly. - TaskStateService rejects a child entering Queued/Running unless its parent is Finalized; this single invariant covers UI, queue, RunNow and MCP paths - WorkerHub.SetTaskStatus routes Queued through the gated EnqueueAsync - Finalize call sites pass queueAgentTasks: false - PlanningChainCoordinator.QueuePlanAsync guards the chain build on Finalized - TaskRowViewModel derives Draft/Planned from ParentFinalized; gates CanSendToQueue / CanQueuePlan; view shows a PLANNED badge Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,24 @@ public sealed class PlanningChainCoordinator
|
||||
return sequenceable.Count;
|
||||
}
|
||||
|
||||
// User-triggered "send plan to queue". Only valid once the plan is finalized
|
||||
// (children are "Planned"); otherwise the children are still drafts.
|
||||
public async Task<int> QueuePlanAsync(string parentTaskId, CancellationToken ct = default)
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var phase = await ctx.Tasks.AsNoTracking()
|
||||
.Where(t => t.Id == parentTaskId)
|
||||
.Select(t => (PlanningPhase?)t.PlanningPhase)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (phase is null)
|
||||
throw new InvalidOperationException($"Task {parentTaskId} not found.");
|
||||
if (phase != PlanningPhase.Finalized)
|
||||
throw new InvalidOperationException("Plan must be finalized before it can be queued.");
|
||||
|
||||
return await SetupChainAsync(parentTaskId, ct);
|
||||
}
|
||||
|
||||
public async Task<string?> OnChildFinishedAsync(
|
||||
string childTaskId, TaskStatus finalStatus, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user