using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Runner; using Microsoft.EntityFrameworkCore; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Services; public sealed class TaskResetService { private readonly IDbContextFactory _dbFactory; private readonly WorktreeManager _wtManager; private readonly HubBroadcaster _broadcaster; private readonly ILogger _logger; public TaskResetService( IDbContextFactory dbFactory, WorktreeManager wtManager, HubBroadcaster broadcaster, ILogger logger) { _dbFactory = dbFactory; _wtManager = wtManager; _broadcaster = broadcaster; _logger = logger; } public async Task ResetAsync(string taskId, CancellationToken ct) { TaskEntity task; ListEntity list; WorktreeEntity? wt; using (var ctx = _dbFactory.CreateDbContext()) { task = await new TaskRepository(ctx).GetByIdAsync(taskId, ct) ?? throw new KeyNotFoundException($"Task '{taskId}' not found."); if (task.Status == TaskStatus.Running) throw new InvalidOperationException("Cannot reset a running task. Cancel it first."); list = await new ListRepository(ctx).GetByIdAsync(task.ListId, ct) ?? throw new InvalidOperationException("List not found."); wt = await new WorktreeRepository(ctx).GetByTaskIdAsync(taskId, ct); } bool worktreeChanged = false; if (wt is not null && wt.State == WorktreeState.Active && list.WorkingDir is not null) { await _wtManager.DiscardAsync(wt, list.WorkingDir, ct); worktreeChanged = true; } using (var ctx = _dbFactory.CreateDbContext()) { await new TaskRepository(ctx).ResetToManualAsync(taskId, ct); } await _broadcaster.TaskUpdated(taskId); if (worktreeChanged) await _broadcaster.WorktreeUpdated(taskId); _logger.LogInformation("Reset task {TaskId} to Manual (worktree discarded: {Discarded})", taskId, worktreeChanged); } }