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:
mika kuns
2026-08-21 16:51:18 +02:00
parent 36720d33ae
commit ea2271cc3e
9 changed files with 325 additions and 76 deletions
+16 -6
View File
@@ -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.