Merge branch 'claudedo/a92b87e7748840ababcfc87423a8636c'
This commit is contained in:
@@ -99,11 +99,15 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
public bool CanRemoveFromQueue => IsQueued || HasQueuedSubtasks;
|
||||
// "Send to queue" is the single queue entry. On a finalized planning parent it queues the
|
||||
// plan (children) via CanQueuePlan; an Active (not-yet-finalized) planning parent is hidden —
|
||||
// it must be finalized first.
|
||||
// it must be finalized first. The worker never sees a UI-hosted ConPTY session (it never
|
||||
// touches task status), so this gate has to live here: queueing a task the user is actively
|
||||
// hand-editing in an interactive pane would spawn an autonomous run racing it in the same
|
||||
// worktree.
|
||||
public bool CanSendToQueue => !IsRunning && !IsQueued && !IsWaitingForReview && !HasQueuedSubtasks
|
||||
&& (!IsChild || ParentFinalized)
|
||||
&& PlanningPhase != PlanningPhase.Active
|
||||
&& !IsManual;
|
||||
&& !IsManual
|
||||
&& !HasInteractiveSession;
|
||||
// Parent-level "send plan to queue" — only once the plan is finalized (children Planned).
|
||||
// Drives the routing inside SendToQueue, not a separate menu entry.
|
||||
public bool CanQueuePlan => !IsChild && HasPlanningChildren
|
||||
@@ -242,6 +246,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(StatusLabel));
|
||||
OnPropertyChanged(nameof(ShowStatusChip));
|
||||
OnPropertyChanged(nameof(InteractiveChipTooltip));
|
||||
OnPropertyChanged(nameof(CanSendToQueue));
|
||||
}
|
||||
|
||||
partial void OnHasQueuedSubtasksChanged(bool value)
|
||||
|
||||
@@ -833,26 +833,18 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
[RelayCommand]
|
||||
private async Task SendToQueueAsync(TaskRowViewModel? row)
|
||||
{
|
||||
if (row is null || row.IsRunning) return;
|
||||
if (row is null || row.IsRunning || row.HasInteractiveSession || _worker is null) return;
|
||||
// A finalized planning parent queues its plan (children sequentially), not itself.
|
||||
if (row.CanQueuePlan)
|
||||
{
|
||||
await QueuePlanningSubtasksAsync(row);
|
||||
return;
|
||||
}
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
|
||||
if (entity is null) return;
|
||||
entity.Status = TaskStatus.Queued;
|
||||
await db.SaveChangesAsync();
|
||||
row.Status = TaskStatus.Queued;
|
||||
if (_worker is not null)
|
||||
{
|
||||
try { await _worker.WakeQueueAsync(); } catch { }
|
||||
}
|
||||
Regroup();
|
||||
UpdateSubtitle();
|
||||
TasksChanged?.Invoke(this, EventArgs.Empty);
|
||||
// Goes through the worker hub (TaskStateService.EnqueueAsync) rather than a raw EF write
|
||||
// so the manual/draft-child guards apply here too; the row refreshes from the resulting
|
||||
// TaskUpdated broadcast.
|
||||
try { await _worker.SetTaskStatusAsync(row.Id, TaskStatus.Queued); }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.sendToQueueFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -115,22 +115,16 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
catch { /* best-effort queue refresh */ }
|
||||
}
|
||||
|
||||
// Drop-to-queue: a task dragged from the main app onto Mission Control gets queued.
|
||||
// Drop-to-queue: a task dragged from the main app onto Mission Control gets queued. Goes
|
||||
// through the worker hub (TaskStateService.EnqueueAsync) rather than a raw EF write so the
|
||||
// manual/draft-child guards apply here too. The interactive-session check has to live here
|
||||
// rather than on the worker side: the worker never touches task status for a UI-hosted ConPTY
|
||||
// session, so it has no way to know one is open — only Mission Control's own pane list does.
|
||||
public async System.Threading.Tasks.Task EnqueueTaskAsync(string taskId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(taskId)) return;
|
||||
try
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == taskId);
|
||||
if (entity is null
|
||||
|| entity.Status == ClaudeDo.Data.Models.TaskStatus.Running
|
||||
|| entity.Status == ClaudeDo.Data.Models.TaskStatus.Queued)
|
||||
return;
|
||||
entity.Status = ClaudeDo.Data.Models.TaskStatus.Queued;
|
||||
await db.SaveChangesAsync();
|
||||
await _worker.WakeQueueAsync();
|
||||
}
|
||||
if (ConPtySessions.Any(s => s.TaskId == taskId)) return;
|
||||
try { await _worker.SetTaskStatusAsync(taskId, ClaudeDo.Data.Models.TaskStatus.Queued); }
|
||||
catch { /* best-effort enqueue */ }
|
||||
await RefreshQueueAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user