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:
@@ -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)
|
||||
|
||||
@@ -322,10 +322,21 @@ public sealed class TaskRunner
|
||||
// Terminal DB write uses CancellationToken.None so the task status
|
||||
// is never left as 'running' because of a cancel that arrived
|
||||
// after the Claude run already succeeded.
|
||||
// Standalone tasks gate on review; planning children go straight to Done
|
||||
// so the sequential chain (which advances on terminal states) is unaffected.
|
||||
var finishedAt = DateTime.UtcNow;
|
||||
await _state.CompleteAsync(task.Id, finishedAt, result.ResultMarkdown, CancellationToken.None);
|
||||
await _broadcaster.WorkerLog($"Finished \"{task.Title}\" (done)", WorkerLogLevel.Success, DateTime.UtcNow);
|
||||
await _broadcaster.TaskFinished(slot, task.Id, "done", finishedAt);
|
||||
if (task.ParentTaskId is null)
|
||||
{
|
||||
await _state.SubmitForReviewAsync(task.Id, finishedAt, result.ResultMarkdown, CancellationToken.None);
|
||||
await _broadcaster.WorkerLog($"Finished \"{task.Title}\" (waiting for review)", WorkerLogLevel.Success, DateTime.UtcNow);
|
||||
await _broadcaster.TaskFinished(slot, task.Id, "waiting_for_review", finishedAt);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _state.CompleteAsync(task.Id, finishedAt, result.ResultMarkdown, CancellationToken.None);
|
||||
await _broadcaster.WorkerLog($"Finished \"{task.Title}\" (done)", WorkerLogLevel.Success, DateTime.UtcNow);
|
||||
await _broadcaster.TaskFinished(slot, task.Id, "done", finishedAt);
|
||||
}
|
||||
_logger.LogInformation("Task {TaskId} completed (turns={Turns}, tokens_in={In}, tokens_out={Out})",
|
||||
task.Id, result.TurnCount, result.TokensIn, result.TokensOut);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user