refactor(interactive): remove streaming interactive stack (superseded by ConPTY)

The embedded ConPTY terminal replaced the in-app streaming interactive session, so
delete the dead stack: StreamingClaudeSession, InteractiveSessionService,
ProcessClaudeStreamTransport, IClaudeStreamTransport, ILiveSession, LiveSessionRegistry,
IdleSessionReaper (+ WorkerConfig.InteractiveIdleTimeoutMinutes), the WorkerHub
interactive methods + HubBroadcaster events, IWorkerClient interactive members, the
TaskMonitorViewModel composer + SessionTerminalView composer markup, and the old
'Run interactively' entry. AskUser/PendingQuestionRegistry, the autonomous path,
planning, ResumeTaskInTerminal, and all ConPTY code are kept. Localization pruned.
This commit is contained in:
mika kuns
2026-07-23 16:47:16 +02:00
parent d8194ad57e
commit c412a84fdf
40 changed files with 23 additions and 2338 deletions
@@ -71,9 +71,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
private const string RoadblockMarker = "Roadblocks reported during the run:";
public ObservableCollection<QueuedMessageViewModel> QueuedMessages { get; } = new();
public bool HasQueuedMessages => QueuedMessages.Count > 0;
// Captured handler delegates for disposal
private readonly Action<string, string> _onTaskMessage;
private readonly Action<string, string, DateTime> _onTaskStarted;
@@ -81,19 +78,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
private readonly Action<string> _onTaskUpdated;
private readonly Action<string, string, string> _onTaskQuestionAsked;
private readonly Action<string, string> _onTaskQuestionResolved;
private readonly Action<string> _onInteractiveStarted;
private readonly Action<string> _onInteractiveEnded;
private readonly Action<string, IReadOnlyList<string>> _onInteractiveQueueChanged;
private readonly Action<string, string> _onInteractiveMessageSent;
// Interactive composer — active while the worker is in an interactive session.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SubmitComposerCommand))]
private bool _isInteractiveLive;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SubmitComposerCommand))]
private string _composerDraft = string.Empty;
// A question the running task raised via AskUser and is blocking on, plus the answer
// the user is typing. Ephemeral (in-memory + live events) — the task is still Running.
@@ -161,46 +145,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
ClearPendingQuestion();
};
_worker.TaskQuestionResolvedEvent += _onTaskQuestionResolved;
_onInteractiveStarted = taskId =>
{
if (taskId == _subscribedTaskId) { IsInteractiveLive = true; AgentState = "running"; }
};
_worker.InteractiveSessionStartedEvent += _onInteractiveStarted;
_onInteractiveEnded = taskId =>
{
if (taskId != _subscribedTaskId) return;
IsInteractiveLive = false;
AgentState = "done";
QueuedMessages.Clear();
OnPropertyChanged(nameof(HasQueuedMessages));
};
_worker.InteractiveSessionEndedEvent += _onInteractiveEnded;
_onInteractiveQueueChanged = (taskId, pending) =>
{
if (taskId != _subscribedTaskId) return;
QueuedMessages.Clear();
foreach (var m in pending)
{
var text = m;
QueuedMessages.Add(new QueuedMessageViewModel
{
Text = text,
RemoveCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(() => _ = RemoveQueuedAsync(text)),
});
}
OnPropertyChanged(nameof(HasQueuedMessages));
};
_worker.InteractiveQueueChangedEvent += _onInteractiveQueueChanged;
_onInteractiveMessageSent = (taskId, text) =>
{
if (taskId == _subscribedTaskId)
Log.Add(new LogLineViewModel { Kind = LogKind.User, Text = text });
};
_worker.InteractiveMessageSentEvent += _onInteractiveMessageSent;
}
// Surface a pending question (used by live event + re-attach hydration).
@@ -210,45 +154,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
PendingQuestion = question;
}
// Used by Mission Control when it creates the monitor after the started event already fired.
public void SetInteractiveLive(bool live)
{
IsInteractiveLive = live;
if (live) AgentState = "running";
}
[RelayCommand(CanExecute = nameof(CanSubmitComposer))]
private async System.Threading.Tasks.Task SubmitComposer()
{
if (string.IsNullOrEmpty(_subscribedTaskId)) return;
var text = ComposerDraft;
if (string.IsNullOrWhiteSpace(text)) return;
ComposerDraft = string.Empty;
await _worker.SendInteractiveMessageAsync(_subscribedTaskId, text);
}
private bool CanSubmitComposer() => IsInteractiveLive && !string.IsNullOrWhiteSpace(ComposerDraft);
[RelayCommand]
private async System.Threading.Tasks.Task StopInteractive()
{
if (!string.IsNullOrEmpty(_subscribedTaskId) && IsInteractiveLive)
await _worker.StopInteractiveSessionAsync(_subscribedTaskId);
}
[RelayCommand]
private async System.Threading.Tasks.Task InterruptInteractive()
{
if (!string.IsNullOrEmpty(_subscribedTaskId) && IsInteractiveLive)
await _worker.InterruptInteractiveSessionAsync(_subscribedTaskId);
}
private async System.Threading.Tasks.Task RemoveQueuedAsync(string text)
{
if (!string.IsNullOrEmpty(_subscribedTaskId))
await _worker.RemoveQueuedInteractiveMessageAsync(_subscribedTaskId, text);
}
private void ClearPendingQuestion()
{
PendingQuestionId = null;
@@ -297,10 +202,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
SessionOutcome = null;
Roadblocks = null;
ClearPendingQuestion();
IsInteractiveLive = false;
ComposerDraft = string.Empty;
QueuedMessages.Clear();
OnPropertyChanged(nameof(HasQueuedMessages));
}
[ObservableProperty]
@@ -524,15 +425,5 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
_worker.TaskUpdatedEvent -= _onTaskUpdated;
_worker.TaskQuestionAskedEvent -= _onTaskQuestionAsked;
_worker.TaskQuestionResolvedEvent -= _onTaskQuestionResolved;
_worker.InteractiveSessionStartedEvent -= _onInteractiveStarted;
_worker.InteractiveSessionEndedEvent -= _onInteractiveEnded;
_worker.InteractiveQueueChangedEvent -= _onInteractiveQueueChanged;
_worker.InteractiveMessageSentEvent -= _onInteractiveMessageSent;
}
}
public sealed class QueuedMessageViewModel
{
public required string Text { get; init; }
public required System.Windows.Input.ICommand RemoveCommand { get; init; }
}
@@ -810,18 +810,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.planningOpenFailed", ex.Message)); }
}
[RelayCommand]
private async Task RunInteractivelyAsync(TaskRowViewModel? row)
{
if (row is null || _worker is null) return;
ForegroundHelper.AllowAny();
try { await _worker.OpenInteractiveTerminalAsync(row.Id); }
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.runInteractiveFailed", ex.Message)); }
}
// Distinct from RunInteractivelyAsync (the old in-app streaming session) — this opens the
// task in an embedded ConPTY terminal pane in the Command Center. The shell owns the
// Mission Control view model, so this just raises an event for it to act on.
// Opens the task in an embedded ConPTY terminal pane in the Command Center. The shell owns
// the Mission Control view model, so this just raises an event for it to act on.
public event Action<string>? OpenConPtySessionRequested;
[RelayCommand]
@@ -22,7 +22,6 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
private readonly Action<string, string, string, DateTime> _onTaskFinished;
private readonly Action<string> _onTaskUpdated;
private readonly Action _onConnectionRestored;
private readonly Action<string> _onInteractiveStarted;
public ObservableCollection<TaskMonitorViewModel> Monitors { get; } = new();
@@ -94,14 +93,6 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
_onConnectionRestored = () => { SeedActive(); _ = RefreshQueueAsync(); };
_worker.ConnectionRestoredEvent += _onConnectionRestored;
_onInteractiveStarted = taskId =>
{
EnsureMonitor(taskId);
var m = Monitors.FirstOrDefault(x => x.SubscribedTaskId == taskId);
m?.SetInteractiveLive(true);
};
_worker.InteractiveSessionStartedEvent += _onInteractiveStarted;
SeedActive();
_ = RefreshQueueAsync();
}
@@ -231,8 +222,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
}
// Fetches the launch spec for a task's worktree and hosts an embedded ConPTY session as a
// Command Center pane (task-based only). A distinct entry point from RunInteractivelyAsync's
// streaming session — the two coexist until the streaming stack is removed.
// Command Center pane (task-based only).
public async System.Threading.Tasks.Task OpenConPtySessionAsync(string taskId)
{
if (string.IsNullOrEmpty(taskId)) return;
@@ -362,7 +352,6 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
_worker.TaskFinishedEvent -= _onTaskFinished;
_worker.TaskUpdatedEvent -= _onTaskUpdated;
_worker.ConnectionRestoredEvent -= _onConnectionRestored;
_worker.InteractiveSessionStartedEvent -= _onInteractiveStarted;
Monitors.CollectionChanged -= OnMonitorsChanged;
ConPtySessions.CollectionChanged -= OnConPtySessionsChanged;
Panes.CollectionChanged -= OnPanesChanged;