feat(ui): answer a running task's question inline in Mission Control

TaskMonitorViewModel surfaces a pending AskUser question (TaskQuestionAsked /
TaskQuestionResolved events) with an AnswerDraft + SubmitAnswerCommand that calls
the new IWorkerClient.AnswerTaskQuestionAsync; MonitorPaneView shows an accent
question banner with an input box above the terminal. Pending question is cleared
on answer/resolve/finish and re-hydrated on attach via GetPendingQuestionAsync.
en/de localization for missionControl.question.*; test fakes updated.
This commit is contained in:
Mika Kuns
2026-06-25 22:53:46 +02:00
parent c7f8280106
commit 917301d61c
10 changed files with 224 additions and 2 deletions

View File

@@ -23,6 +23,8 @@ public abstract class StubWorkerClient : IWorkerClient
public event Action<string>? ListUpdatedEvent;
public event Action<string, string>? TaskMessageEvent;
public event Action<WorkerLogEntry>? WorkerLogReceivedEvent;
public event Action<string, string, string>? TaskQuestionAskedEvent;
public event Action<string, string>? TaskQuestionResolvedEvent;
public event Action? PrepStartedEvent;
public event Action<string>? PrepLineEvent;
public event Action<bool>? PrepFinishedEvent;
@@ -46,6 +48,8 @@ public abstract class StubWorkerClient : IWorkerClient
public void RaiseTaskMessage(string taskId, string line) => TaskMessageEvent?.Invoke(taskId, line);
public void RaiseTaskUpdated(string taskId) => TaskUpdatedEvent?.Invoke(taskId);
public void RaiseConnectionRestored() => ConnectionRestoredEvent?.Invoke();
public void RaiseTaskQuestionAsked(string taskId, string questionId, string question) => TaskQuestionAskedEvent?.Invoke(taskId, questionId, question);
public void RaiseTaskQuestionResolved(string taskId, string questionId) => TaskQuestionResolvedEvent?.Invoke(taskId, questionId);
public void RaisePrepStarted() => PrepStartedEvent?.Invoke();
public void RaisePrepLine(string line) => PrepLineEvent?.Invoke(line);
@@ -58,6 +62,14 @@ public abstract class StubWorkerClient : IWorkerClient
public virtual Task WakeQueueAsync() => Task.CompletedTask;
public virtual Task RunNowAsync(string taskId) => Task.CompletedTask;
public virtual Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask;
public (string TaskId, string QuestionId, string Answer)? LastAnswer { get; private set; }
public virtual Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer)
{
LastAnswer = (taskId, questionId, answer);
return Task.CompletedTask;
}
public PendingQuestionDto? PendingQuestion;
public virtual Task<PendingQuestionDto?> GetPendingQuestionAsync(string taskId) => Task.FromResult(PendingQuestion);
public virtual Task ResetTaskAsync(string taskId) => Task.CompletedTask;
public virtual Task CancelTaskAsync(string taskId) => Task.CompletedTask;
public virtual Task<List<AgentInfo>> GetAgentsAsync() => Task.FromResult(new List<AgentInfo>());