feat(worker): add TaskResetService for discard + reset flow
Orchestrates worktree discard, task reset to Manual, and SignalR broadcast. Includes integration tests (happy path + running-task rejection). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
68
src/ClaudeDo.Worker/Services/TaskResetService.cs
Normal file
68
src/ClaudeDo.Worker/Services/TaskResetService.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
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<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly WorktreeManager _wtManager;
|
||||
private readonly HubBroadcaster _broadcaster;
|
||||
private readonly ILogger<TaskResetService> _logger;
|
||||
|
||||
public TaskResetService(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||
WorktreeManager wtManager,
|
||||
HubBroadcaster broadcaster,
|
||||
ILogger<TaskResetService> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user