feat(ui): add roadblock reply field to the ROADBLOCK card
A task that reports a roadblock but finishes successfully (Done/WaitingForReview/ Failed/Cancelled) had no way to answer it short of a full reset-and-rerun, losing the run's context. Adds a reply textbox + Send button to the existing ROADBLOCK card, modeled on the AskUser question card, that resumes the session via ContinueTaskAsync with the user's own text. Gated on LatestRunSessionId (disabled with a hint when there's nothing to resume); failures surface through the footer error strip instead of a modal.
This commit is contained in:
@@ -191,8 +191,24 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
|
||||
[NotifyPropertyChangedFor(nameof(CanReplyToRoadblock))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendRoadblockReplyCommand))]
|
||||
private string? _latestRunSessionId;
|
||||
|
||||
// A resumable session to reply to a reported roadblock with (via ContinueAsync's
|
||||
// transport). Distinct from ContinueCommand: that one is Failed/Cancelled-only and
|
||||
// reruns with a fixed prompt, this one answers a roadblock reported on any terminal
|
||||
// state (see ShowRoadblockCard) with the user's own text.
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendRoadblockReplyCommand))]
|
||||
private string _roadblockReplyDraft = string.Empty;
|
||||
|
||||
public bool CanReplyToRoadblock => !string.IsNullOrEmpty(LatestRunSessionId);
|
||||
|
||||
// Surfaces a UI-originated failure (e.g. override slot busy) to the shell's footer
|
||||
// error strip — mirrors TasksIslandViewModel.ErrorReported.
|
||||
public event Action<string>? ErrorReported;
|
||||
|
||||
[ObservableProperty] private string? _model;
|
||||
|
||||
[ObservableProperty] private string? _worktreePath;
|
||||
@@ -333,6 +349,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
DequeueCommand.NotifyCanExecuteChanged();
|
||||
ResetAndRetryCommand.NotifyCanExecuteChanged();
|
||||
ContinueCommand.NotifyCanExecuteChanged();
|
||||
SendRoadblockReplyCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
};
|
||||
_worker.PropertyChanged += _workerPropertyChangedHandler;
|
||||
@@ -524,6 +541,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
Task = row;
|
||||
OnPropertyChanged(nameof(TaskIdBadge));
|
||||
Monitor.Reset();
|
||||
RoadblockReplyDraft = string.Empty;
|
||||
Subtasks.Clear();
|
||||
ChildOutcomes.Clear();
|
||||
Attachments.Clear();
|
||||
@@ -1025,6 +1043,26 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
private bool CanContinue() =>
|
||||
Task != null && _worker.IsConnected && ShowContinue && !string.IsNullOrEmpty(LatestRunSessionId);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanSendRoadblockReply))]
|
||||
private async System.Threading.Tasks.Task SendRoadblockReplyAsync()
|
||||
{
|
||||
if (Task == null) return;
|
||||
var text = RoadblockReplyDraft;
|
||||
if (string.IsNullOrWhiteSpace(text)) return;
|
||||
try
|
||||
{
|
||||
await _worker.ContinueTaskAsync(Task.Id, text);
|
||||
RoadblockReplyDraft = string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("details.roadblockReply.failed", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanSendRoadblockReply() =>
|
||||
Task != null && _worker.IsConnected && CanReplyToRoadblock && !string.IsNullOrWhiteSpace(RoadblockReplyDraft);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanResetAndRetry))]
|
||||
private async System.Threading.Tasks.Task ResetAndRetryAsync()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user