The merge commit message was hand-rolled per caller ("Merge task: <title>",
"Merge <branch>", "Merge subtask") and ignored the task's commit type. Every
caller now passes a blank message and TaskMergeService fills in
CommitMessageBuilder.BuildMerge -> {commitType}(list-slug): merge <title> plus the
ClaudeDo-Task trailer; the merge modal prefills it from GetMergeTargets.
A merge whose list has a verify command holds the MergeTask call for minutes (5m46s
on this repo), during which the modal only disabled its button - no spinner, no
message, so a landed merge looked like a dead app. TaskMergeService now broadcasts
MergeProgress(taskId, phase, elapsedSeconds) for the merging and verifying phases
(re-reported every 30s) plus a WorkerLog line when verify starts; the modal shows a
spinner and the localized phase.
92 lines
4.6 KiB
C#
92 lines
4.6 KiB
C#
using ClaudeDo.Data.Models;
|
|
using ClaudeDo.Worker.Prime;
|
|
using ClaudeDo.Worker.Refine;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
namespace ClaudeDo.Worker.Hub;
|
|
|
|
public sealed class HubBroadcaster : IPrimeBroadcaster, IRefineBroadcaster
|
|
{
|
|
private readonly IHubContext<WorkerHub> _hub;
|
|
|
|
public HubBroadcaster(IHubContext<WorkerHub> hub) => _hub = hub;
|
|
|
|
public Task TaskStarted(string slot, string taskId, DateTime startedAt) =>
|
|
_hub.Clients.All.SendAsync("TaskStarted", slot, taskId, startedAt);
|
|
|
|
public Task TaskFinished(string slot, string taskId, string status, DateTime finishedAt) =>
|
|
_hub.Clients.All.SendAsync("TaskFinished", slot, taskId, status, finishedAt);
|
|
|
|
public Task TaskMessage(string taskId, string ndjsonLine) =>
|
|
_hub.Clients.All.SendAsync("TaskMessage", taskId, ndjsonLine);
|
|
|
|
public Task WorktreeUpdated(string taskId) =>
|
|
_hub.Clients.All.SendAsync("WorktreeUpdated", taskId);
|
|
|
|
public Task TaskUpdated(string taskId) =>
|
|
_hub.Clients.All.SendAsync("TaskUpdated", taskId);
|
|
|
|
public Task TaskQuestionAsked(string taskId, string questionId, string question) =>
|
|
_hub.Clients.All.SendAsync("TaskQuestionAsked", taskId, questionId, question);
|
|
|
|
public Task TaskQuestionResolved(string taskId, string questionId) =>
|
|
_hub.Clients.All.SendAsync("TaskQuestionResolved", taskId, questionId);
|
|
|
|
// A running list-handler session called handoff_list_handler at the end of Phase 2 -- the UI
|
|
// opens a second ConPTY tile for the same handler task id to carry out Phases 3-5.
|
|
public Task HandoffRequested(string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase) =>
|
|
_hub.Clients.All.SendAsync("HandoffRequested", taskId, survivingTaskIds, nextPhase);
|
|
|
|
public Task ListUpdated(string listId) =>
|
|
_hub.Clients.All.SendAsync("ListUpdated", listId);
|
|
|
|
public Task UsageUpdated(UsageSnapshotDto snapshot) =>
|
|
_hub.Clients.All.SendAsync("UsageUpdated", snapshot);
|
|
|
|
public Task WorkerLog(string message, WorkerLogLevel level, DateTime timestampUtc) =>
|
|
_hub.Clients.All.SendAsync("WorkerLog", message, level, timestampUtc);
|
|
|
|
// Phase of an in-flight single-task merge (see TaskMergeService.Phase*), so a client waiting
|
|
// on the MergeTask call can show what it is waiting for instead of a frozen button.
|
|
public Task MergeProgress(string taskId, string phase, int elapsedSeconds) =>
|
|
_hub.Clients.All.SendAsync("MergeProgress", taskId, phase, elapsedSeconds);
|
|
|
|
public Task PlanningMergeStarted(string planningTaskId, string targetBranch) =>
|
|
_hub.Clients.All.SendAsync("PlanningMergeStarted", planningTaskId, targetBranch);
|
|
|
|
public Task PlanningSubtaskMerged(string planningTaskId, string subtaskId) =>
|
|
_hub.Clients.All.SendAsync("PlanningSubtaskMerged", planningTaskId, subtaskId);
|
|
|
|
public Task PlanningMergeConflict(
|
|
string planningTaskId, string subtaskId, IReadOnlyList<string> files, bool externallyDriven) =>
|
|
_hub.Clients.All.SendAsync("PlanningMergeConflict", planningTaskId, subtaskId, files, externallyDriven);
|
|
|
|
public Task PlanningMergeAborted(string planningTaskId) =>
|
|
_hub.Clients.All.SendAsync("PlanningMergeAborted", planningTaskId);
|
|
|
|
public Task PlanningCompleted(string planningTaskId) =>
|
|
_hub.Clients.All.SendAsync("PlanningCompleted", planningTaskId);
|
|
|
|
public Task PrimeFired(Guid scheduleId, bool success, string message, DateTimeOffset firedAt) =>
|
|
_hub.Clients.All.SendAsync("PrimeFired", scheduleId, success, message, firedAt);
|
|
|
|
Task IPrimeBroadcaster.PrimeFiredAsync(Guid scheduleId, bool success, string message, DateTimeOffset firedAt) =>
|
|
PrimeFired(scheduleId, success, message, firedAt);
|
|
|
|
public Task PrepStarted() => _hub.Clients.All.SendAsync("PrepStarted");
|
|
public Task PrepLine(string line) => _hub.Clients.All.SendAsync("PrepLine", line);
|
|
public Task PrepFinished(bool success) => _hub.Clients.All.SendAsync("PrepFinished", success);
|
|
|
|
Task IPrimeBroadcaster.PrepStartedAsync() => PrepStarted();
|
|
Task IPrimeBroadcaster.PrepLineAsync(string line) => PrepLine(line);
|
|
Task IPrimeBroadcaster.PrepFinishedAsync(bool success) => PrepFinished(success);
|
|
|
|
public Task RefineStarted(string taskId) => _hub.Clients.All.SendAsync("RefineStarted", taskId);
|
|
public Task RefineFinished(string taskId, bool success, string? error) =>
|
|
_hub.Clients.All.SendAsync("RefineFinished", taskId, success, error);
|
|
|
|
Task IRefineBroadcaster.RefineStartedAsync(string taskId) => RefineStarted(taskId);
|
|
Task IRefineBroadcaster.RefineFinishedAsync(string taskId, bool success, string? error) =>
|
|
RefineFinished(taskId, success, error);
|
|
}
|