Files
ClaudeDo/docs/superpowers/plans/2026-06-25-interactive-ask-user.md

57 lines
4.4 KiB
Markdown

# Plan — Interactive "Answer Claude's Questions"
Spec: `docs/superpowers/specs/2026-06-25-interactive-ask-user-design.md`
Implement on the shared main tree. Commit explicit paths per task (never `git add -A`).
Build with `-c Release` (running Worker locks Debug). No real-Claude tests.
## Task 1 — PendingQuestionRegistry (worker, new file)
- `src/ClaudeDo.Worker/Runner/PendingQuestionRegistry.cs`: singleton; `record PendingQuestion(TaskId, QuestionId, Question)`.
- `(string QuestionId, Task<string> Answer) Register(taskId, question)` — overwrites any stale entry, `RunContinuationsAsynchronously`.
- `bool TryAnswer(taskId, questionId, answer)`; `PendingQuestion? Get(taskId)`; `void Remove(taskId, questionId)`.
- Test: `tests/ClaudeDo.Worker.Tests/Runner/PendingQuestionRegistryTests.cs` — register→answer resolves the task; wrong questionId no-ops; Get reflects state; second Register overwrites.
## Task 2 — AskUser MCP tool (worker)
- `TaskRunMcpService.cs`: inject `PendingQuestionRegistry`; add
`[McpServerTool] async Task<string> AskUser(string question, CancellationToken ct)`:
- caller id from `_ctx.Current.CallerTaskId`; register; broadcast `TaskQuestionAsked`.
- await answer via `Task<string>.WaitAsync` with a 3-min linked-CTS; on timeout return the fallback string; on request-cancel rethrow.
- `finally`: `Remove` + broadcast `TaskQuestionResolved`.
- `[Description]`: when to use (only when a wrong guess is costly/irreversible; otherwise proceed).
- Test: `tests/ClaudeDo.Worker.Tests/Runner/AskUserToolTests.cs` — answer path returns the answer; timeout path returns fallback (inject a short timeout or a seam) with a fake broadcaster + stub context accessor.
## Task 3 — Wire MCP for all runs + timeout env (worker)
- `TaskRunner.RunAsync`: move MCP-identity setup out of the `standalone` gate so every run gets `claudedo_run`; `AllowedTools` = `mcp__claudedo_run__AskUser` always, append `,mcp__claudedo_run__SuggestImprovement` when standalone. Keep token cleanup in `finally`.
- `ClaudeProcess.cs`: `psi.Environment["MCP_TOOL_TIMEOUT"] = "200000";`.
- System prompt file (PromptKind.System default): add one guidance line about `AskUser`.
## Task 4 — Hub + Broadcaster (worker)
- `HubBroadcaster.cs`: `TaskQuestionAsked(taskId, questionId, question)`, `TaskQuestionResolved(taskId, questionId)`.
- `WorkerHub.cs`: inject registry; `bool AnswerTaskQuestion(taskId, questionId, answer)`; `PendingQuestionDto? GetPendingQuestion(taskId)`; `record PendingQuestionDto(...)`.
- `Program.cs`: register `PendingQuestionRegistry` as singleton.
## Task 5 — UI client (IWorkerClient/WorkerClient + fakes)
- `IWorkerClient`: `Task AnswerTaskQuestionAsync(taskId, questionId, answer)`, `Task<PendingQuestionDto?> GetPendingQuestionAsync(taskId)`, events `Action<string,string,string>? TaskQuestionAskedEvent`, `Action<string,string>? TaskQuestionResolvedEvent`; UI DTO record.
- `WorkerClient`: implement invokes + `On<...>` handlers raising the events.
- Update hand-rolled `IWorkerClient` fakes in Ui.Tests (and Worker.Tests if present).
## Task 6 — TaskMonitorViewModel (hot file)
- Subscribe both events (filter by `_subscribedTaskId`); dispose handlers.
- Props: `PendingQuestionId`, `PendingQuestion`, `HasPendingQuestion`, `AnswerDraft`, `IsWaitingForInput`.
- `SubmitAnswerCommand` (CanExecute: non-empty draft + HasPendingQuestion) → `AnswerTaskQuestionAsync`; clear draft.
- Clear pending on `TaskFinished` for this task and in `Reset()`.
- Test: `TaskMonitorViewModelTests` — asked event surfaces question; submit invokes client + clears; resolved/finished clears.
## Task 7 — Hydrate on attach (MissionControlViewModel)
- In `HydrateAsync`, after `ApplyState`, call `GetPendingQuestionAsync(taskId)`; if present, set the monitor's pending question (re-attach case).
## Task 8 — View banner (hot file, additive)
- `MonitorPaneView.axaml`: a `Border DockPanel.Dock="Top"` above `SessionTerminalView`, `IsVisible="{Binding HasPendingQuestion}"`, showing the question text, a `TextBox` bound to `AnswerDraft` (Enter submits), and a Send `Button``SubmitAnswerCommand`. Mirror the roadblock-banner styling.
## Task 9 — Localization
- `en.json` + `de.json`: `missionControl.question.title`, `.placeholder`, `.send`. Keep parity (Localization.Tests).
## Task 10 — Build + test + verify
- `dotnet build` App + Worker `-c Release`; run Worker.Tests, Ui.Tests, Localization.Tests.
- Self-review diffs. Flag the two manual verification gaps to Mika. Do not push.