feat(mission-control): Handler-Run per Knopf abbrechen

Ein "Abandon run"-Knopf an Handler-Panes fragt HandlerRoundState.Get(handlerTaskId)
nach den noch offenen (Queued/Running) Tasks des Laufs, bestaetigt mit Anzahl,
cancelt jeden ueber den bestehenden CancelTaskAsync-Pfad und schliesst danach die
Session -- ein Schritt statt Tile schliessen + Queue separat abbrechen. Ohne
offene Tasks (oder ohne Round-Eintrag) schliesst die Aktion direkt, ohne Dialog.
Nur an den beiden Handler-Panes sichtbar (IsListHandler), normale Task-/Ad-hoc-
Panes bleiben unveraendert.
This commit is contained in:
mika kuns
2026-09-02 22:39:12 +02:00
parent b298594a2c
commit bd5aa265d1
13 changed files with 417 additions and 5 deletions
+22 -1
View File
@@ -73,6 +73,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
private readonly TicketClientFactory _ticketClients;
private readonly TicketImportService _ticketImport;
private readonly HandlerRestartCoordinator? _handlerRestart;
private readonly HandlerRoundState? _roundState;
public WorkerHub(
QueueService queue,
@@ -112,7 +113,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
TicketPatStore ticketPat = null!,
TicketClientFactory ticketClients = null!,
TicketImportService ticketImport = null!,
HandlerRestartCoordinator? handlerRestart = null)
HandlerRestartCoordinator? handlerRestart = null,
HandlerRoundState? roundState = null)
{
_queue = queue;
_waker = waker;
@@ -152,6 +154,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
_ticketClients = ticketClients;
_ticketImport = ticketImport;
_handlerRestart = handlerRestart;
_roundState = roundState;
}
// Persistence boundary for the session_skills JSON-array columns (task/list/global).
@@ -810,6 +813,24 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
_ = _handlerRestart.OnSessionExitedAsync(taskId, exitCode, CancellationToken.None);
}
// The Mission Control "abandon run" action: what a handler round queued/started that is still
// open (Queued or Running), read from the structured HandlerRoundState store rather than
// parsed out of the handler task's description. Empty when there is no round entry (7-day
// retention, or a run from before this feature) or the round has no open tasks -- the caller
// treats both as "nothing to confirm, just close the pane".
public async Task<IReadOnlyList<HandlerOpenTaskDto>> GetHandlerOpenTasks(string handlerTaskId)
{
var round = _roundState?.Get(handlerTaskId);
if (round is not { TaskIds.Count: > 0 }) return Array.Empty<HandlerOpenTaskDto>();
await using var ctx = await _dbFactory.CreateDbContextAsync();
return await ctx.Tasks.AsNoTracking()
.Where(t => round.TaskIds.Contains(t.Id)
&& (t.Status == TaskStatus.Queued || t.Status == TaskStatus.Running))
.Select(t => new HandlerOpenTaskDto(t.Id, t.Title))
.ToListAsync();
}
// Starts a planning session (worktree + prompt files + token, task -> Planning) and returns
// the launch spec for an embedded ConPTY planning terminal -- the ConPTY replacement for
// StartPlanningSessionAsync's external wt window. On any spec-build failure the just-started