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.
This commit is contained in:
+16
-6
@@ -61,7 +61,8 @@ public sealed record TaskDto(
|
||||
// True only while Status is Queued and the picker will not claim this task yet -- either a
|
||||
// planning-chain predecessor or DependsOnTaskId hasn't reached Done. See BlockedReason.
|
||||
bool Blocked = false,
|
||||
string? BlockedReason = null);
|
||||
string? BlockedReason = null,
|
||||
bool IsManual = false);
|
||||
|
||||
// Lean counterpart to TaskDto for writing/status-changing tools: echoes back what changed
|
||||
// without re-sending Description/Result, which the caller just sent or already has.
|
||||
@@ -83,7 +84,8 @@ public sealed record TaskRefDto(
|
||||
string? DependsOnTaskId = null,
|
||||
bool Blocked = false,
|
||||
string? BlockedReason = null,
|
||||
DirtyBaseWarning? BaseDirty = null);
|
||||
DirtyBaseWarning? BaseDirty = null,
|
||||
bool IsManual = false);
|
||||
|
||||
// tasks is populated when includeDescription=false (the default): lean references, no
|
||||
// Description/Result. tasksFull is populated when includeDescription=true: full tasks incl.
|
||||
@@ -495,8 +497,8 @@ public sealed class ExternalMcpService
|
||||
}
|
||||
|
||||
[McpServerTool, Description(
|
||||
"Update an existing task's title, description, commit type, and/or dependsOn link. Pass null to leave a " +
|
||||
"field unchanged." + McpToolDocs.NotWhileRunning + McpToolDocs.LeanTaskRef)]
|
||||
"Update an existing task's title, description, commit type, dependsOn link, and/or manual flag. Pass " +
|
||||
"null to leave a field unchanged." + McpToolDocs.NotWhileRunning + McpToolDocs.LeanTaskRef)]
|
||||
public async Task<TaskRefDto> UpdateTask(
|
||||
string taskId,
|
||||
string? title = null,
|
||||
@@ -506,6 +508,10 @@ public sealed class ExternalMcpService
|
||||
"string to clear an existing link; null leaves it unchanged. Rejected if it doesn't exist, " +
|
||||
"is this task's own id, or would create a dependency cycle.")]
|
||||
string? dependsOnTaskId = null,
|
||||
[Description("true: mark this task as a manual reminder only the user can complete — the queue picker, " +
|
||||
"daily prep, and the list handler all skip it (a ConPTY session may still touch it). false: " +
|
||||
"clear the flag. null (default): leave unchanged.")]
|
||||
bool? isManual = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
taskId = await TaskIdResolver.ResolveAsync(_tasks, taskId, cancellationToken);
|
||||
@@ -517,6 +523,7 @@ public sealed class ExternalMcpService
|
||||
if (title is not null) task.Title = title;
|
||||
if (description is not null) task.Description = description;
|
||||
if (commitType is not null) task.CommitType = commitType;
|
||||
if (isManual is not null) task.IsManual = isManual.Value;
|
||||
await _tasks.UpdateAsync(task, cancellationToken);
|
||||
|
||||
if (dependsOnTaskId is not null)
|
||||
@@ -1743,7 +1750,8 @@ public sealed class ExternalMcpService
|
||||
t.Status == TaskStatus.Failed ? t.FailureMaxTurns : null,
|
||||
t.DependsOnTaskId,
|
||||
blocked,
|
||||
blockedReason);
|
||||
blockedReason,
|
||||
t.IsManual);
|
||||
|
||||
private static TaskRefDto ToRefDto(TaskEntity t, bool blocked = false, string? blockedReason = null) => new(
|
||||
t.Id,
|
||||
@@ -1759,7 +1767,9 @@ public sealed class ExternalMcpService
|
||||
t.Status == TaskStatus.Failed ? t.FailureMaxTurns : null,
|
||||
t.DependsOnTaskId,
|
||||
blocked,
|
||||
blockedReason);
|
||||
blockedReason,
|
||||
BaseDirty: null,
|
||||
IsManual: t.IsManual);
|
||||
|
||||
// "unknown" covers a Failed task that predates this field (never got a classified reason
|
||||
// stamped) — a defined value rather than null so callers don't have to special-case it.
|
||||
|
||||
+31
-1
@@ -13,11 +13,17 @@ 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)
|
||||
public LifecycleMcpTools(
|
||||
TaskRepository tasks, TaskResetService reset, ExternalMcpService svc,
|
||||
InteractiveReviewSubmissionService reviewSubmission)
|
||||
{
|
||||
_tasks = tasks;
|
||||
_reset = reset;
|
||||
_svc = svc;
|
||||
_reviewSubmission = reviewSubmission;
|
||||
}
|
||||
|
||||
[McpServerTool, Description(
|
||||
@@ -37,4 +43,28 @@ public sealed class LifecycleMcpTools
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user