feat(worker-mcp): raise wait_for_task_change timeout, expose queue slot state

MaxTimeoutSeconds was 170s against runs that take tens of minutes, forcing
a dozen full-context wait rounds per long-running batch. Raise it to 900s
and raise MCP_TOOL_TIMEOUT in lockstep (ClaudeProcess + every
InteractiveLaunchSpecService launch spec) to 930000ms so the client
connection actually stays open that long instead of aborting first.

Add get_queue_state (QueueStateMcpTools): configured vs. effective
parallel-slot count (via QueueService.GetSlotCountsAsync, extracted from
the former GetEffectiveMaxParallelAsync), active slots with taskId +
startedAt including the run_task_now override slot, and queued tasks in
pick order -- so a caller can observe queue occupancy instead of inferring
it from maxParallelExecutions.
This commit is contained in:
mika kuns
2026-08-05 20:47:57 +02:00
parent bdee731376
commit d43b5fcefc
10 changed files with 299 additions and 36 deletions
+24 -6
View File
@@ -1,8 +1,8 @@
# External MCP tool surface
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
> Last verified against commit `f6cb825` (2026-08-05).
> Drift check: `git log --oneline f6cb825..HEAD -- src/ClaudeDo.Worker/External`
> Last verified against commit `bdee731` (2026-08-05).
> Drift check: `git log --oneline bdee731..HEAD -- src/ClaudeDo.Worker/External`
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
Covers `src/ClaudeDo.Worker/External/` — the always-on MCP tools ClaudeDo exposes to general
@@ -57,6 +57,7 @@ Daily prep: `GetDailyPrepCandidates`, `SetMyDay`.
| `LifecycleMcpTools` | `ResetFailedTask` |
| `AppSettingsMcpTools` | `GetAppSettings` (read-only) |
| `TaskWaitMcpTools` | `WaitForTaskChange` |
| `QueueStateMcpTools` | `GetQueueState` |
| `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` |
## Per-tool behaviour worth knowing
@@ -110,12 +111,29 @@ failing item never aborts the rest — and rejects batches over **100 items**.
- Implemented as an **async DB poll** (short-lived `DbContext` per check, 500 ms delay, no
held connection, no busy loop) rather than hooking `HubBroadcaster` — deliberately isolated
so it can't regress the existing broadcast callers.
- `timeoutSeconds` is clamped server-side to `TaskWaitMcpTools.MaxTimeoutSeconds` (170 s),
comfortably under the list handler's `MCP_TOOL_TIMEOUT` (200 s, see
`InteractiveLaunchSpecService`), so the tool reports `timedOut: true` instead of racing the
client's own abort.
- `timeoutSeconds` is clamped server-side to `TaskWaitMcpTools.MaxTimeoutSeconds` (900 s),
comfortably under the `MCP_TOOL_TIMEOUT` (930 s) every ClaudeDo-owned claude launcher sets —
`ClaudeProcess` for headless queue runs, `InteractiveLaunchSpecService` for every embedded
ConPTY session (list handler, planning, interactive resume) — so the tool reports
`timedOut: true` instead of racing the client's own abort. A caller running claude with a
different (or default: 60 s) `MCP_TOOL_TIMEOUT` will still see its own client-side timeout
fire first; the server has no way to detect or compensate for that.
- Replaced the list handler's old "sleep + poll `get_task` in a loop" Phase 3 instruction.
**`GetQueueState()`** — read-only snapshot so a caller doesn't have to infer queue state from
`maxParallelExecutions` or repeated `wait_for_task_change` rounds:
`{ configuredSlots, effectiveSlots, activeSlots: [{ slot, taskId, startedAt }], waitingTaskIds }`.
- `configuredSlots`/`effectiveSlots` reuse `QueueService.GetSlotCountsAsync` — the same
configured-vs-throttled computation `QueueService.ExecuteAsync` uses each tick (see
[usage-monitoring](usage-monitoring.md) for the throttle staging) — so this tool can't drift
from the queue's actual refill decision.
- `activeSlots` reuses `QueueService.GetActive()` (already the source for the Hub's `GetActive`):
`slot` is `"queue"` for a normal queue slot or `"override"` for the single
`run_task_now`/`continue_task` slot.
- `waitingTaskIds` is a fresh read-only query mirroring `QueuePicker.ClaimNextAsync`'s
eligibility filter and order (`Queued`, unblocked, non-manual, due, `sort_order` then
`created_at`) — it does not claim or mutate anything.
**`AttachmentMcpTools`** — re-attaching the same `fileName` overwrites. Add/remove refuse on a
`Running` task.