feat(ui): wire list-handler nextPhase through the handoff hub event

HandoffRequested now carries nextPhase end to end (IWorkerClient ->
WorkerClient -> MissionControlViewModel -> GetMergeHelperHandoffLaunchSpecAsync),
and the outgoing tile is left open on handoff instead of being closed --
lookups that need the active pane for a task now use LastOrDefault since a
handler task's ConPtySessions can hold more than one pane.
This commit is contained in:
mika kuns
2026-08-11 09:40:14 +02:00
parent ee7003d964
commit c8244c67f3
5 changed files with 37 additions and 31 deletions
@@ -25,9 +25,10 @@ public interface IWorkerClient : INotifyPropertyChanged
/// <summary>A pending question was answered, timed out, or the run ended: (taskId, questionId).</summary>
event Action<string, string>? TaskQuestionResolvedEvent;
/// <summary>A running list-handler session called handoff_list_handler at the end of Phase 2:
/// (handlerTaskId, survivingTaskIds). The UI opens a second ConPTY tile for the same task.</summary>
event Action<string, IReadOnlyList<string>>? HandoffRequestedEvent;
/// <summary>A running list-handler session called handoff_list_handler at the end of a phase:
/// (handlerTaskId, survivingTaskIds, nextPhase). The UI opens a second ConPTY tile for the same
/// task to carry out nextPhase ("wait" | "merge" | "wait_final" | "merge_final").</summary>
event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
event Action? PrepStartedEvent;
event Action<string>? PrepLineEvent;
@@ -109,10 +110,11 @@ public interface IWorkerClient : INotifyPropertyChanged
/// never queued) so the ConPTY tile can be task-based instead of ad-hoc. Returns the new task id.</summary>
Task<string> CreateMergeHelperTaskAsync(
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default);
/// <summary>Launch spec for the fresh ConPTY session a merge-helper run hands off to once Phase 2
/// is done -- reuses the SAME handler task id (no new task created).</summary>
/// <summary>Launch spec for the fresh ConPTY session a merge-helper run hands off to once a phase
/// is done -- reuses the SAME handler task id (no new task created). nextPhase picks the next
/// session's role: "wait" | "merge" | "wait_final" | "merge_final".</summary>
Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
string taskId, IReadOnlyList<string> survivingTaskIds, CancellationToken ct = default);
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default);
/// <summary>Starts a planning session and returns the launch spec for an embedded ConPTY
/// planning terminal (replaces StartPlanningSessionAsync's external wt window).</summary>
Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default);
+5 -5
View File
@@ -49,7 +49,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public event Action<string>? TaskUpdatedEvent;
public event Action<string, string, string>? TaskQuestionAskedEvent;
public event Action<string, string>? TaskQuestionResolvedEvent;
public event Action<string, IReadOnlyList<string>>? HandoffRequestedEvent;
public event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
public event Action? ConnectionRestoredEvent;
public event Action<string>? WorktreeUpdatedEvent;
public event Action<string>? ListUpdatedEvent;
@@ -151,9 +151,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
Dispatcher.UIThread.Post(() => TaskQuestionResolvedEvent?.Invoke(taskId, questionId));
});
_hub.On<string, IReadOnlyList<string>>("HandoffRequested", (taskId, survivingTaskIds) =>
_hub.On<string, IReadOnlyList<string>, string>("HandoffRequested", (taskId, survivingTaskIds, nextPhase) =>
{
Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds));
Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds, nextPhase));
});
_hub.On<string>("WorktreeUpdated", taskId =>
@@ -566,8 +566,8 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
=> await _hub.InvokeAsync<string>("CreateMergeHelperTask", taskIds, listId, title, descriptionHeader, ct);
public async Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
string taskId, IReadOnlyList<string> survivingTaskIds, CancellationToken ct = default)
=> await _hub.InvokeAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, ct);
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default)
=> await _hub.InvokeAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, nextPhase, ct);
public async Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default)
=> await _hub.InvokeAsync<LaunchSpec>("GetPlanningStartLaunchSpec", taskId, ct);