fix(mcp): keep wait_for_task_change waiting through a planning-chain block

This commit is contained in:
mika kuns
2026-08-11 08:32:52 +02:00
parent f31b1b4fb2
commit 7106bf754f
3 changed files with 19 additions and 21 deletions
+11 -11
View File
@@ -44,10 +44,11 @@ public sealed class TaskWaitMcpTools
[McpServerTool, Description(
"Blocks until at least one of the given tasks leaves Queued/Running -- use this instead of " +
"polling get_task in a loop. Returns immediately if a task is already outside Queued/Running " +
"(an unknown id reports status \"NotFound\" and counts as changed). A Queued task the picker " +
"will not claim yet (a planning-chain predecessor, or a depends_on link whose target isn't " +
"Done) also reports immediately as status \"Blocked\" with blockedReason set, instead of " +
"silently waiting out the full timeout. Pitfall: a planning parent goes Running -> " +
"(an unknown id reports status \"NotFound\" and counts as changed). A Queued task held by a " +
"depends_on link whose target isn't Done reports immediately as status \"Blocked\" with " +
"blockedReason set, instead of silently waiting out the full timeout -- that link never " +
"resolves on its own. A planning-chain block does NOT report that way: it clears by itself " +
"when the predecessor finishes, so the wait simply continues. Pitfall: a planning parent goes Running -> " +
"WaitingForChildren while its children are still working, so by default waiting on a parent " +
"returns early; see treatWaitingForChildrenAsBusy. Sends MCP progress " +
"pings every 30s while waiting so a long wait survives the calling client's own idle-silence " +
@@ -135,14 +136,13 @@ public sealed class TaskWaitMcpTools
continue;
}
if (row.Status == TaskStatus.Queued)
// A planning-chain BlockedByTaskId is deliberately NOT reported as "Blocked": it
// resolves on its own the moment the predecessor reaches any terminal state
// (PlanningChainCoordinator), so it's exactly what a caller waiting on a queued
// fan-out wants to keep waiting through. Reporting it would return instantly for
// every chained child and turn the wait into a one-turn-per-poll busy loop.
if (row.Status == TaskStatus.Queued && row.BlockedByTaskId is null)
{
if (row.BlockedByTaskId is not null)
{
result.Add(new TaskStatusChangeDto(id, "Blocked",
$"Blocked by planning-chain predecessor {row.BlockedByTaskId}."));
continue;
}
if (row.DependsOnTaskId is not null)
{
var known = dependencyStatuses.TryGetValue(row.DependsOnTaskId, out var depStatus);