Teil A: update_task bekommt einen optionalen isManual-Parameter (null = unveraendert); TaskDto/TaskRefDto spiegeln IsManual zurueck. Teil B: neuer submit_task_for_review MCP-Tool (LifecycleMcpTools) laesst einen Handler-Task selbst auf WaitingForReview gehen -- fuer einen worktree-losen Task wird HandlerHeadCommit vom aktuellen HEAD gestempelt, sonst werden offene Aenderungen committet. Die Submit-Logik ist aus WorkerHub.SubmitTaskForReview in InteractiveReviewSubmissionService extrahiert; der Hub ruft sie jetzt nur noch auf. Der Merge-Prompt-Default weist die Endzweige (merge ohne Rerun, merge_final) an, den eigenen Handler-Task nach der Summary einzureichen.
71 lines
3.5 KiB
C#
71 lines
3.5 KiB
C#
using System.ComponentModel;
|
|
using ClaudeDo.Data.Repositories;
|
|
using ClaudeDo.Worker.Lifecycle;
|
|
using ModelContextProtocol.Server;
|
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
|
|
|
namespace ClaudeDo.Worker.External;
|
|
|
|
public sealed record ResetFailedTaskResult(bool Reset, string TaskId, int Number);
|
|
|
|
[McpServerToolType]
|
|
public sealed class LifecycleMcpTools
|
|
{
|
|
private readonly TaskRepository _tasks;
|
|
private readonly TaskResetService _reset;
|
|
private readonly ExternalMcpService _svc;
|
|
private readonly InteractiveReviewSubmissionService _reviewSubmission;
|
|
|
|
public LifecycleMcpTools(
|
|
TaskRepository tasks, TaskResetService reset, ExternalMcpService svc,
|
|
InteractiveReviewSubmissionService reviewSubmission)
|
|
{
|
|
_tasks = tasks;
|
|
_reset = reset;
|
|
_svc = svc;
|
|
_reviewSubmission = reviewSubmission;
|
|
}
|
|
|
|
[McpServerTool, Description(
|
|
"Discards a failed task's worktree (and all uncommitted work in it) and resets it to Idle for a fresh " +
|
|
"run. Only tasks with Status=Failed are accepted; other statuses throw. Check failureReason from " +
|
|
"get_task/batch_get_tasks first: for failureReason=\"max_turns\" the worktree's work is usually still " +
|
|
"good and continue_task (resume the session, keep the worktree) is almost always the right call instead " +
|
|
"— reach for this tool only for a real error, not a task that just ran out of turns.")]
|
|
public async Task<ResetFailedTaskResult> ResetFailedTask(string taskId, CancellationToken cancellationToken)
|
|
{
|
|
taskId = await TaskIdResolver.ResolveAsync(_tasks, taskId, cancellationToken);
|
|
var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
|
|
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
|
if (task.Status != TaskStatus.Failed)
|
|
throw new InvalidOperationException($"Task {taskId} is {task.Status}, not Failed. Only failed tasks can be reset via this tool.");
|
|
|
|
await _reset.ResetAsync(taskId, cancellationToken);
|
|
return new ResetFailedTaskResult(true, taskId, task.Number);
|
|
}
|
|
|
|
[McpServerTool, Description(
|
|
"Submit a task into the review pipeline directly, without a headless agent run — the way a list " +
|
|
"handler ('Let Claude handle it') submits its OWN handler task once its run is done, since " +
|
|
"update_task_status doesn't reach WaitingForReview. Only Idle or Failed tasks are accepted; a " +
|
|
"Running/Queued/WaitingForReview/WaitingForChildren task throws. For a worktree-less handler task this " +
|
|
"stamps the review range's HandlerHeadCommit from the list's working dir HEAD (nothing to commit — the " +
|
|
"handler already committed straight to the list's working dir); for a task with an active worktree it " +
|
|
"commits any uncommitted changes there first. Either way the task then transitions to WaitingForReview " +
|
|
"and its diff becomes reviewable via review_task/get_task_diff." + McpToolDocs.LeanTaskRef + McpToolDocs.TaskNumberHint)]
|
|
public async Task<TaskRefDto> SubmitTaskForReview(string taskId, CancellationToken cancellationToken)
|
|
{
|
|
taskId = await TaskIdResolver.ResolveAsync(_tasks, taskId, cancellationToken);
|
|
try
|
|
{
|
|
await _reviewSubmission.SubmitAsync(taskId, cancellationToken);
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
throw new InvalidOperationException($"Task {taskId} not found.");
|
|
}
|
|
|
|
return await _svc.GetTaskRefAsync(taskId, cancellationToken);
|
|
}
|
|
}
|