fix(worker): close status-force bypasses on Running tasks

update_task_status Idle/Done, WorkerHub.SetTaskStatus, and the UI's Mark
Done/Cancelled menu items could all force a Running task's status without
going through TaskStateService's guards, leaving its CLI process untracked
and its worktree writes silently dropped. Idle now routes through the
already-guarded TaskStateService.ResetToIdleAsync instead of the repo's
unconditional ResetToManualAsync; Done and SetTaskStatus explicitly reject
Running; the context menu disables both entries with a tooltip while
running. TaskRunner also now logs a warning whenever a terminal transition
(SubmitForChildren/SubmitForReview/Complete/Fail) is rejected instead of
silently discarding the run result. DeleteTask's MCP path gets the same
friendly foreign-key message WorkerHub.DeleteTask already had for a task
with children.
This commit is contained in:
Mika Kuns
2026-08-26 15:01:13 +02:00
parent c4928b4def
commit 299c7b749c
7 changed files with 183 additions and 11 deletions
@@ -171,6 +171,13 @@ public sealed partial class TaskRowViewModel : ViewModelBase
IsRunning ? (true, null) : (false, Loc.T("tasks.reasonNotRunning"));
public string? CancelDisabledReason => CancelGate().Reason;
// Gates "Mark Done"/"Mark Cancelled" in the context menu — forcing either status while the
// task is actively running would strand the CLI process untracked (WorkerHub.SetTaskStatus
// rejects it server-side too; this is the UI-visible half of that guard).
private (bool Can, string? Reason) MarkAsGate() =>
IsRunning ? (false, Loc.T("tasks.reasonAlreadyRunning")) : (true, null);
public string? MarkAsDisabledReason => MarkAsGate().Reason;
// "Open quick session" has no precondition today (see A1's file header) — kept as a
// placeholder so the menu wiring is uniform; always null until a real gate exists.
public string? QuickSessionDisabledReason => null;
@@ -318,6 +325,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
OnPropertyChanged(nameof(CancelDisabledReason));
OnPropertyChanged(nameof(RefineDisabledReason));
OnPropertyChanged(nameof(PlanningDisabledReason));
OnPropertyChanged(nameof(MarkAsDisabledReason));
}
partial void OnParentTaskIdChanged(string? value)