feat(worker): route standalone success to review and resume on re-queue

Standalone tasks now enter WaitingForReview on success; re-queued tasks
carrying reviewer feedback resume the prior Claude session with that
feedback as the next turn.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-01 17:15:57 +02:00
parent e8d018dd54
commit 9c1f20f2d9
5 changed files with 105 additions and 11 deletions

View File

@@ -3,6 +3,7 @@ using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Runner;
using ClaudeDo.Worker.State;
using Microsoft.EntityFrameworkCore;
namespace ClaudeDo.Worker.Queue;
@@ -16,6 +17,7 @@ public sealed class QueueService : BackgroundService
private readonly QueueWaker _waker;
private readonly IQueuePicker _picker;
private readonly OverrideSlotService _override;
private readonly ITaskStateService _state;
private readonly object _lock = new();
private readonly Dictionary<string, QueueSlotState> _queueSlots = new();
@@ -27,7 +29,8 @@ public sealed class QueueService : BackgroundService
ILogger<QueueService> logger,
QueueWaker waker,
IQueuePicker picker,
OverrideSlotService overrideSlot)
OverrideSlotService overrideSlot,
ITaskStateService state)
{
_dbFactory = dbFactory;
_runner = runner;
@@ -36,6 +39,7 @@ public sealed class QueueService : BackgroundService
_waker = waker;
_picker = picker;
_override = overrideSlot;
_state = state;
}
public IReadOnlyList<(string slot, string taskId, DateTime startedAt)> GetActive()
@@ -174,6 +178,29 @@ public sealed class QueueService : BackgroundService
?? throw new KeyNotFoundException($"Task '{taskId}' not found.");
}
// A task re-queued from review carries reviewer feedback. Resume the prior
// Claude session with that feedback as the next turn when a session exists;
// otherwise fall back to a fresh run with the feedback folded into the prompt.
if (!string.IsNullOrWhiteSpace(task.ReviewFeedback))
{
var feedback = task.ReviewFeedback!;
string? sessionId;
using (var context = _dbFactory.CreateDbContext())
sessionId = (await new TaskRunRepository(context).GetLatestByTaskIdAsync(taskId, ct))?.SessionId;
await _state.ClearReviewFeedbackAsync(taskId, ct);
if (sessionId is not null)
{
await _runner.ContinueAsync(taskId, feedback, "queue", ct);
return;
}
task.Description = string.IsNullOrWhiteSpace(task.Description)
? $"Reviewer feedback: {feedback}"
: $"{task.Description}\n\nReviewer feedback: {feedback}";
}
await _runner.RunAsync(task, "queue", ct);
}
catch (Exception ex)