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

4.4 KiB

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 ButtonSubmitAnswerCommand. 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.