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(opt => opt.UseSqlite($"Data Source={cfg.DbPath}")); builder.Services.AddSingleton(cfg); builder.Services.AddHostedService(); builder.Services.AddSignalR().AddJsonProtocol(options => { options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); }); // Runner stack. builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); // 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>())); // QueueService: singleton + hosted service (same instance). builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // Planning session services. var planningSessionsDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".todo-app", "planning-sessions"); builder.Services.AddSingleton(sp => new PlanningSessionManager( sp.GetRequiredService>(), sp.GetRequiredService(), cfg, planningSessionsDir)); builder.Services.AddSingleton(sp => new WindowsTerminalPlanningLauncher("wt.exe", cfg.ClaudeBin)); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(sp => sp.GetRequiredService>().CreateDbContext()); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddMcpServer() .WithHttpTransport() .WithTools(); // 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()); } try { var seeder = app.Services.GetRequiredService(); 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(); app.MapHub("/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();