Files
ClaudeDo/src/ClaudeDo.Worker/External/LifecycleMcpTools.cs
T
mika kuns ea2271cc3e feat(mcp): update_task kann isManual setzen + List-Handler stellt seinen Task auf Review
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.
2026-08-21 16:51:18 +02:00

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);
}
}