feat(worker): register planning services and add Merge-all hub methods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-24 18:28:38 +02:00
parent e58cac24e1
commit 3008c36921
4 changed files with 77 additions and 2 deletions

View File

@@ -46,6 +46,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
private readonly TaskMergeService _mergeService; private readonly TaskMergeService _mergeService;
private readonly PlanningSessionManager _planning; private readonly PlanningSessionManager _planning;
private readonly IPlanningTerminalLauncher _launcher; private readonly IPlanningTerminalLauncher _launcher;
private readonly PlanningAggregator _planningAggregator;
private readonly PlanningMergeOrchestrator _planningMergeOrchestrator;
public WorkerHub( public WorkerHub(
QueueService queue, QueueService queue,
@@ -57,7 +59,9 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
TaskResetService resetService, TaskResetService resetService,
TaskMergeService mergeService, TaskMergeService mergeService,
PlanningSessionManager planning, PlanningSessionManager planning,
IPlanningTerminalLauncher launcher) IPlanningTerminalLauncher launcher,
PlanningAggregator planningAggregator,
PlanningMergeOrchestrator planningMergeOrchestrator)
{ {
_queue = queue; _queue = queue;
_agentService = agentService; _agentService = agentService;
@@ -69,6 +73,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
_mergeService = mergeService; _mergeService = mergeService;
_planning = planning; _planning = planning;
_launcher = launcher; _launcher = launcher;
_planningAggregator = planningAggregator;
_planningMergeOrchestrator = planningMergeOrchestrator;
} }
public string Ping() => $"pong v{Version}"; public string Ping() => $"pong v{Version}";
@@ -330,5 +336,55 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
public Task<int> GetPendingDraftCountAsync(string taskId) public Task<int> GetPendingDraftCountAsync(string taskId)
=> _planning.GetPendingDraftCountAsync(taskId, Context.ConnectionAborted); => _planning.GetPendingDraftCountAsync(taskId, Context.ConnectionAborted);
public async Task<IReadOnlyList<SubtaskDiffDto>> GetPlanningAggregate(string planningTaskId)
{
try
{
var diffs = await _planningAggregator.GetAggregatedDiffAsync(planningTaskId, CancellationToken.None);
return diffs.Select(d => new SubtaskDiffDto(
d.SubtaskId, d.Title, d.BranchName, d.BaseCommit, d.HeadCommit, d.DiffStat, d.UnifiedDiff)).ToList();
}
catch (KeyNotFoundException) { throw new HubException("planning task not found"); }
catch (InvalidOperationException ex) { throw new HubException(ex.Message); }
}
public async Task<CombinedDiffResultDto> BuildPlanningIntegrationBranch(string planningTaskId, string targetBranch)
{
try
{
var result = await _planningAggregator.BuildIntegrationBranchAsync(
planningTaskId, targetBranch ?? "", CancellationToken.None);
return result switch
{
CombinedDiffResult.Ok ok => new CombinedDiffResultDto(
true, ok.Value.IntegrationBranch, ok.Value.UnifiedDiff, null, null),
CombinedDiffResult.Failed f => new CombinedDiffResultDto(
false, null, null, f.Value.FirstConflictSubtaskId, f.Value.ConflictedFiles),
_ => throw new InvalidOperationException("unknown result type"),
};
}
catch (KeyNotFoundException) { throw new HubException("planning task not found"); }
catch (InvalidOperationException ex) { throw new HubException(ex.Message); }
}
public async Task MergeAllPlanning(string planningTaskId, string targetBranch)
{
try { await _planningMergeOrchestrator.StartAsync(planningTaskId, targetBranch ?? "", CancellationToken.None); }
catch (KeyNotFoundException) { throw new HubException("planning task not found"); }
catch (InvalidOperationException ex) { throw new HubException(ex.Message); }
}
public async Task ContinuePlanningMerge(string planningTaskId)
{
try { await _planningMergeOrchestrator.ContinueAsync(planningTaskId, CancellationToken.None); }
catch (InvalidOperationException ex) { throw new HubException(ex.Message); }
}
public async Task AbortPlanningMerge(string planningTaskId)
{
try { await _planningMergeOrchestrator.AbortAsync(planningTaskId, CancellationToken.None); }
catch (InvalidOperationException ex) { throw new HubException(ex.Message); }
}
private static string? Nullify(string? s) => string.IsNullOrWhiteSpace(s) ? null : s; private static string? Nullify(string? s) => string.IsNullOrWhiteSpace(s) ? null : s;
} }

View File

@@ -0,0 +1,17 @@
namespace ClaudeDo.Worker.Planning;
public sealed record SubtaskDiffDto(
string SubtaskId,
string Title,
string BranchName,
string BaseCommit,
string HeadCommit,
string? DiffStat,
string UnifiedDiff);
public sealed record CombinedDiffResultDto(
bool Success,
string? IntegrationBranch,
string? UnifiedDiff,
string? FirstConflictSubtaskId,
IReadOnlyList<string>? ConflictedFiles);

View File

@@ -36,6 +36,8 @@ builder.Services.AddSingleton<TaskRunner>();
builder.Services.AddSingleton<WorktreeMaintenanceService>(); builder.Services.AddSingleton<WorktreeMaintenanceService>();
builder.Services.AddSingleton<TaskResetService>(); builder.Services.AddSingleton<TaskResetService>();
builder.Services.AddSingleton<TaskMergeService>(); builder.Services.AddSingleton<TaskMergeService>();
builder.Services.AddSingleton<PlanningAggregator>();
builder.Services.AddSingleton<PlanningMergeOrchestrator>();
// Agent file management. // Agent file management.
var agentsDir = Path.Combine(ClaudeDo.Data.Paths.AppDataRoot(), "agents"); var agentsDir = Path.Combine(ClaudeDo.Data.Paths.AppDataRoot(), "agents");

View File

@@ -50,7 +50,7 @@ public sealed class PlanningHubTests : IDisposable
{ {
var hub = new WorkerHub( var hub = new WorkerHub(
null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!,
_planning, _launcher); _planning, _launcher, null!, null!);
hub.Clients = new FakeHubCallerClients(_proxy); hub.Clients = new FakeHubCallerClients(_proxy);
hub.Context = new FakeHubCallerContext(); hub.Context = new FakeHubCallerContext();
return hub; return hub;