Files
ClaudeDo/src/ClaudeDo.Worker/Program.cs
mika kuns 16e1ddd129 feat(worker): add PlanningChainCoordinator for sequential subtask execution
Coordinates Waiting -> Queued transitions between sibling subtasks: when a child finishes Done, the next Waiting sibling is promoted to Queued. WorkerHub.QueuePlanningSubtasksAsync exposes this to the UI; TaskRunner advances the chain on completion. Also tightens the planning-session prompt: planner must use MCP tools, not direct edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:36:01 +02:00

112 lines
4.1 KiB
C#

using ClaudeDo.Data;
using ClaudeDo.Data.Git;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Planning;
using ClaudeDo.Worker.Runner;
using ClaudeDo.Worker.Services;
using Microsoft.EntityFrameworkCore;
var cfg = WorkerConfig.Load();
var builder = WebApplication.CreateBuilder(args);
// When launched by the Windows SCM, speak the Service Control Protocol so SCM
// doesn't think we crashed (~30s timeout). No-op when running interactively.
builder.Host.UseWindowsService(o => o.ServiceName = "ClaudeDoWorker");
builder.Services.AddDbContextFactory<ClaudeDoDbContext>(opt =>
opt.UseSqlite($"Data Source={cfg.DbPath}"));
builder.Services.AddSingleton(cfg);
builder.Services.AddHostedService<StaleTaskRecovery>();
builder.Services.AddSignalR().AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// Runner stack.
builder.Services.AddSingleton<IClaudeProcess, ClaudeProcess>();
builder.Services.AddSingleton<HubBroadcaster>();
builder.Services.AddSingleton<GitService>();
builder.Services.AddSingleton<WorktreeManager>();
builder.Services.AddSingleton<ClaudeArgsBuilder>();
builder.Services.AddSingleton<TaskRunner>();
builder.Services.AddSingleton<WorktreeMaintenanceService>();
builder.Services.AddSingleton<TaskResetService>();
builder.Services.AddSingleton<TaskMergeService>();
builder.Services.AddSingleton<PlanningAggregator>();
builder.Services.AddSingleton<PlanningMergeOrchestrator>();
builder.Services.AddSingleton<PlanningChainCoordinator>();
// Agent file management.
var agentsDir = Path.Combine(ClaudeDo.Data.Paths.AppDataRoot(), "agents");
Directory.CreateDirectory(agentsDir);
builder.Services.AddSingleton(new AgentFileService(agentsDir));
var defaultAgentsBundleDir = Path.Combine(AppContext.BaseDirectory, "DefaultAgents");
builder.Services.AddSingleton(sp => new DefaultAgentSeeder(
defaultAgentsBundleDir,
agentsDir,
sp.GetService<Microsoft.Extensions.Logging.ILogger<DefaultAgentSeeder>>()));
// QueueService: singleton + hosted service (same instance).
builder.Services.AddSingleton<QueueService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<QueueService>());
// Planning session services.
var planningSessionsDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".todo-app", "planning-sessions");
builder.Services.AddSingleton(sp =>
new PlanningSessionManager(
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
sp.GetRequiredService<GitService>(),
cfg,
planningSessionsDir));
builder.Services.AddSingleton<IPlanningTerminalLauncher>(sp =>
new WindowsTerminalPlanningLauncher("wt.exe", cfg.ClaudeBin));
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<PlanningMcpContextAccessor>();
builder.Services.AddScoped<ClaudeDoDbContext>(sp =>
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>().CreateDbContext());
builder.Services.AddScoped<TaskRepository>();
builder.Services.AddScoped<PlanningMcpService>();
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<PlanningMcpService>();
// Loopback-only bind. Firewall is irrelevant for 127.0.0.1.
builder.WebHost.UseUrls($"http://127.0.0.1:{cfg.SignalRPort}");
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
ClaudeDoDbContext.MigrateAndConfigure(
scope.ServiceProvider.GetRequiredService<ClaudeDoDbContext>());
}
try
{
var seeder = app.Services.GetRequiredService<DefaultAgentSeeder>();
var seedResult = await seeder.SeedMissingAsync();
app.Logger.LogInformation(
"Default agents seeded: {Copied} copied, {Skipped} already present",
seedResult.Copied, seedResult.Skipped);
}
catch (Exception ex)
{
app.Logger.LogWarning(ex, "Default agent seeding failed");
}
app.UseMiddleware<PlanningTokenAuthMiddleware>();
app.MapHub<WorkerHub>("/hub");
app.MapMcp("/mcp");
app.Logger.LogInformation("ClaudeDo.Worker listening on http://127.0.0.1:{Port} (db: {Db})",
cfg.SignalRPort, cfg.DbPath);
app.Run();