diff --git a/docs/open.md b/docs/open.md index 50528e63..7bea8891 100644 --- a/docs/open.md +++ b/docs/open.md @@ -17,13 +17,7 @@ Kein Code-Aufwand, nur Durchspielen mit explizit notiertem Pass-Kriterium. Der G - **UI-Sichtprüfung (neu, 2026-06-10, nach Refactoring-Merges):** Detail-Insel komplett durchklicken (Output/Git/Session-Tabs, Merge-Sektion, Agent-Settings-Overrides, Prep-Panel) — `DetailsIslandViewModel` wurde in Sektions-VMs aufgeteilt, Bindings angepasst. Außerdem: DiffModal-Fehler-State „Diff nicht mehr verfügbar" (Commit-Range ohne aufgezeichnete Commits) und der In-App-Konflikt-Resolver (Hub-Methoden umbenannt). - **UI-Sichtprüfung (neu, 2026-06-19, Rider-Style 3-Pane Merge-Editor):** Echten Konflikt auslösen (Single-Task-Approve mit Konflikt **und** Planning-Unit-Merge) und prüfen: drei Panes (Ours read-only | Result editierbar | Theirs read-only), Konfliktblöcke rot / aufgelöst grün in allen Panes, Inline-Accept `›`/`‹` in den Zwischen-Guttern landen die jeweilige Seite im Result, nur Konfliktregionen im Result editierbar (Stable read-only), synchrones vertikales Scrollen, File-Switcher bei mehreren Dateien, `M conflicts · K resolved`-Readout, Continue erst bei allen Konflikten gelöst, Binär-Guard. **Bekannte Kanten:** (1) Konflikt mit leerer Ours-Seite → Result-Region ist null-lang (Gutter via 1-Zeichen-Probe positioniert, Accept funktioniert; nur Hand-Tippen in die leere Region ist fummelig). (2) Gutter-Y nutzt `TranslatePoint` vom Result-`TextView` — bei sehr hohen Fenstern / großen Scrollständen die Ausrichtung gegenprüfen. (3) Blöcke richten sich nur über Stable-Text aus; nach einem Konflikt mit unterschiedlicher Zeilenzahl je Seite driften nachfolgende Blöcke vertikal (aligned/virtual-space Scroll ist bewusst zurückgestellt). - **Worker-Autostart am Gerät:** Logoff/Logon-Autostart, Update-Pfad, Uninstall entfernt die Startup-`.lnk`. -- **In-App Interactive Sessions (neu, 2026-06-26):** ersetzt den externen `wt`-„Run interactively"-Launch durch einen In-App-Streaming-Chat (`StreamingClaudeSession`, `claude --input-format stream-json`). Real-CLI-Smoke (kein xUnit, kein Claude in Tests): - - Task rechtsklick → „Run interactively" startet **keinen** Terminal mehr; der Stream erscheint im Detail-Output-Tab des (selektierten) Tasks und als Monitor in Mission Control. - - Composer: Nachricht tippen + Enter/Send → erscheint sofort als `log-user`-Zeile in **Akzentfarbe** (via `LogKindForegroundConverter`, lokale Bindung schlägt den dim Style), Claude antwortet im selben Prozess. - - **Senden während Claude arbeitet = Queue (Default):** die Nachricht wird gepuffert und beim `result` des laufenden Turns abgeschickt (kein Interrupt). Mehrere Queue-Nachrichten FIFO, eine pro Turn. Gequeute Nachrichten erscheinen in einem **Pending-Streifen über der Eingabezeile** (⧗-Liste, via `InteractiveQueueChanged`); eine Nachricht landet erst im Transkript (`log-user`-Zeile via `InteractiveMessageSent`), wenn sie tatsächlich an Claude zugestellt wird. Der seeded Erst-Prompt erscheint als erste User-Zeile. Jede gequeute Zeile hat ein **✕ zum Entfernen** (`RemoveQueuedInteractiveMessage`, by-text first-match; Worker re-broadcastet die Queue). - - **Interrupt opt-in:** der kleine ■-Stop-Button neben Send unterbricht den laufenden Turn (`control_request`/`interrupt`, verifiziert mit CLI 2.1.191; Abbruch-`result` = `error_during_execution`, als Turn-Ende behandelt) — danach flusht die ggf. gequeute Nachricht im selben Prozess mit erhaltenem Kontext. Stop-Button ist immer sichtbar solange live (Interrupt im Idle ist ein No-op; Turn-in-flight wird nicht in die UI gebroadcastet). - - Session-Ende: Prozess-Exit/Stop → `InteractiveSessionEnded`, Composer verschwindet, Monitor wird „done". - - **Sicht-Konsistenz:** Mission-Control-Composer (SessionTerminalView-Bottom-Row mit Send-Button) vs. Detail-Composer (WorkConsole-Shell-Prompt `❯ … [Send]`) sehen unterschiedlich aus — ggf. angleichen. +- **In-App Interactive Sessions (2026-06-26, REMOVED 2026-07-23):** der In-App-Streaming-Chat (`StreamingClaudeSession`, Composer/Queue auf `TaskMonitorViewModel`/`SessionTerminalView`) wurde komplett entfernt und durch die **embedded ConPTY**-Sessions ersetzt (echte `claude`-TUI im UI-Prozess, siehe `docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md`). Kein offener Punkt mehr — nur zur Historie. - **Pick up in terminal (neu, 2026-07-01):** neue Aktion, die die Claude-Session einer Task per `claude --resume ` in einem **echten** `wt`-Terminal fortsetzt (echte TUI: Permission-Prompts/Fragen inklusive) — bewusst NICHT der In-App-Streaming-Chat. Real-CLI-Smoke (kein Claude in Tests): - Kontextmenü einer Task in **WaitingForReview** oder **Failed** → „Pick up in terminal" sowie der Terminal-Button (Icon `ArrowOut`) im Detail-Header sind sichtbar; bei anderen Status (Idle/Running/Queued/Done) NICHT. - Klick → neues Windows-Terminal im Worktree-Verzeichnis der Task, Claude nimmt die letzte Session mit erhaltenem Kontext wieder auf. diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index a3b1760c..b43c8771 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -128,7 +128,6 @@ "ctxMarkAs": "Markieren als", "ctxMarkDone": "Erledigt", "ctxMarkCancelled": "Abgebrochen", - "ctxRunInteractively": "Interaktiv ausführen", "ctxOpenConPtySession": "ConPTY-Sitzung öffnen", "ctxPickUpInTerminal": "Im Terminal fortsetzen", "ctxOpenPlanningSession": "Planungssitzung öffnen", @@ -247,15 +246,7 @@ "chipDone": "FERTIG", "chipFailed": "FEHLGESCHLAGEN", "reviewContinueTip": "Dieses Feedback senden und die Aufgabe erneut ausführen", - "reviewResetTip": "Alle Änderungen verwerfen und die Aufgabe auf Leerlauf zurücksetzen", - "composer": { - "placeholder": "Nachricht an die Sitzung…", - "send": "Senden", - "stop": "Sitzung beenden", - "interrupt": "Aktuellen Zug unterbrechen", - "queued": "Wartet — wird nach dem aktuellen Zug gesendet", - "unqueue": "Aus Warteschlange entfernen" - } + "reviewResetTip": "Alle Änderungen verwerfen und die Aufgabe auf Leerlauf zurücksetzen" }, "missionControl": { "openInApp": "In App öffnen", @@ -513,7 +504,7 @@ "taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "waitingForChildren": "Wartet auf Verbesserungen", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "parked": "Geparkt" }, "planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" }, "taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" }, - "tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "runInteractiveFailed": "Interaktiv ausführen fehlgeschlagen: {0}", "planningOpenFailed": "Planungssitzung konnte nicht geöffnet werden: {0}", "pickUpInTerminalFailed": "Im Terminal fortsetzen fehlgeschlagen: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}" }, + "tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "planningOpenFailed": "Planungssitzung konnte nicht geöffnet werden: {0}", "pickUpInTerminalFailed": "Im Terminal fortsetzen fehlgeschlagen: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}" }, "diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen.", "unavailable": "Diff nicht mehr verfügbar — Commit-Bereich unvollständig." }, "planningDiff": { "hubError": "Kombinierte Vorschau konnte nicht erstellt werden (Hub-Fehler).", "conflict": "Kombinierte Vorschau nicht möglich: Teilaufgabe {0} steht im Konflikt mit einer früheren Teilaufgabe ({1} Dateien)." }, "merge": { "commitMessage": "Merge-Aufgabe: {0}", "workerOfflineBranches": "Worker offline — Branches können nicht aufgelistet werden.", "loadBranchesFailed": "Branches konnten nicht geladen werden: {0}", "merged": "Zusammengeführt.", "conflict": "Merge-Konflikt — Ziel-Branch wiederhergestellt. Manuell oder über Fortsetzen lösen, dann erneut versuchen.", "blocked": "Blockiert: {0}", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" }, diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 6220203c..033c96a0 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -128,7 +128,6 @@ "ctxMarkAs": "Mark as", "ctxMarkDone": "Done", "ctxMarkCancelled": "Cancelled", - "ctxRunInteractively": "Run interactively", "ctxOpenConPtySession": "Open ConPTY session", "ctxPickUpInTerminal": "Pick up in terminal", "ctxOpenPlanningSession": "Open planning Session", @@ -247,15 +246,7 @@ "chipDone": "DONE", "chipFailed": "FAILED", "reviewContinueTip": "Send this feedback and re-run the task", - "reviewResetTip": "Discard all changes and reset the task to Idle", - "composer": { - "placeholder": "Message the session…", - "send": "Send", - "stop": "Stop session", - "interrupt": "Interrupt current turn", - "queued": "Queued — sends after the current turn", - "unqueue": "Remove from queue" - } + "reviewResetTip": "Discard all changes and reset the task to Idle" }, "missionControl": { "openInApp": "Open in app", @@ -513,7 +504,7 @@ "taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "waitingForChildren": "Waiting for Improvements", "done": "Done", "failed": "Failed", "cancelled": "Cancelled", "parked": "Parked" }, "planningBadge": { "active": "PLANNING", "finalized": "PLANNED" }, "taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" }, - "tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "runInteractiveFailed": "Run interactively failed: {0}", "planningOpenFailed": "Couldn't open planning session: {0}", "pickUpInTerminalFailed": "Pick up in terminal failed: {0}", "approveFailed": "Approve & merge failed: {0}" }, + "tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "planningOpenFailed": "Couldn't open planning session: {0}", "pickUpInTerminalFailed": "Pick up in terminal failed: {0}", "approveFailed": "Approve & merge failed: {0}" }, "diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show.", "unavailable": "Diff no longer available — commit range incomplete." }, "planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files)." }, "merge": { "commitMessage": "Merge task: {0}", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "conflict": "Merge conflict — target branch restored. Resolve manually or via Continue, then retry.", "blocked": "Blocked: {0}", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" }, diff --git a/src/ClaudeDo.Ui/CLAUDE.md b/src/ClaudeDo.Ui/CLAUDE.md index 8635425f..0208ca8b 100644 --- a/src/ClaudeDo.Ui/CLAUDE.md +++ b/src/ClaudeDo.Ui/CLAUDE.md @@ -35,7 +35,7 @@ Design/ — Tokens.axaml (design tokens; merged before styles) + IslandStyle - **IslandsShellViewModel** — root coordinator; owns the three island VMs and the `WorkerClient`, wires cross-island events (selection, notes/prep mode, conflict resolution), owns connection state, the update banner, the inline worker-log strip (clickable → Log Visualizer overlay via `OpenLogVisualizerCommand`; `FlashFooterError` surfaces UI-action failures + the worker's Serilog Warn/Error there), responsive-layout flags (`ShowLists`/`ShowDetails` by window width), `PrimeStatus` flash, and the modal openers (About, RepoImport, WeeklyReport, WorktreesOverview, WorkerConnection help, LogVisualizer) plus `RestartWorkerAsync`/`CheckForUpdatesAsync`. Hosts `UpdateCheckService`. - **ListsIslandViewModel** — smart lists (My Day, Important, Planned, virtual queued/running/review), user lists, selection, list CRUD, drag-reorder, badge counts, opens list settings / repo import / worktrees overview, `OpenInExplorer`/`OpenInTerminal`. -- **TasksIslandViewModel** — open/overdue/completed groups for the selected list with hierarchy-aware regrouping; task CRUD, drag-reorder, toggle done/star, schedule, enqueue/dequeue, cancel; review actions (approve, reject-rerun, reject-park, cancel); planning session lifecycle (open/resume/discard/finalize, `QueuePlanningSubtasksAsync`); `RunInteractivelyAsync`, `RefineTask`; MyDay extras (`IsMyDayList`, `ClearDayCommand`, `ShowPrepLogCommand`) and the pinned Notes pseudo-row (`ShowNotesRow`, `OpenNotesCommand`). Raises `NotesRequested`/`PrepRequested` events consumed by the shell. +- **TasksIslandViewModel** — open/overdue/completed groups for the selected list with hierarchy-aware regrouping; task CRUD, drag-reorder, toggle done/star, schedule, enqueue/dequeue, cancel; review actions (approve, reject-rerun, reject-park, cancel); planning session lifecycle (open/resume/discard/finalize, `QueuePlanningSubtasksAsync`); `RefineTask`, `OpenConPtySessionRequested` (embedded ConPTY terminal), `PickUpInTerminalAsync`; MyDay extras (`IsMyDayList`, `ClearDayCommand`, `ShowPrepLogCommand`) and the pinned Notes pseudo-row (`ShowNotesRow`, `OpenNotesCommand`). Raises `NotesRequested`/`PrepRequested` events consumed by the shell. - **DetailsIslandViewModel** — the detail pane for a bound `TaskRowViewModel`. Owns live-log streaming (`Log` via `StreamLineFormatter`), debounced title/description editing, subtasks, session-outcome/roadblock split (splits `Result` at the roadblock marker into two cards), the three-tab work console (`output`/`git`/`session`), child surfacing (`ChildOutcomes` rows plus `ChildrenNeedingAttention`/`HasChildrenNeedingAttention` — children that failed, were cancelled, await review, or reported roadblocks — drive an attention band on the Session tab, which is only visible when `HasChildOutcomes`), and the modes: `IsNotesMode` (hosts `NotesEditorViewModel`), `IsPrepMode`, computed `IsTaskDetailVisible = !IsNotesMode && !IsPrepMode`. Three concerns are extracted into section VMs exposed as properties: **AgentConfigEditorViewModel** (scope=Task; per-task Model/MaxTurns/AgentPath overrides with `InheritedBadge` + `InheritanceResolver`, additive SystemPrompt, debounced auto-save; exposed as `AgentSettings`), **MergeSectionViewModel** (merge-target selection, mergeability indicator via `MergePreviewPresenter` over `PreviewMergeAsync`, `OpenDiffAsync` and `ReviewCombinedDiffCommand` — both build a `DiffViewerViewModel` and call `ShowDiffViewer`), **PrepPanelViewModel** (daily-prep panel: `PrepLog`, `PlanDayCommand` → `RunDailyPrepNowAsync`, persisted last run via `GetLastPrepLogAsync`). Attachments: `Attachments` (`ObservableCollection`), `IsDragOver`, `DropStatus`, `CanAcceptDrop`, `AddFilesAsync`, `RemoveAttachmentCommand`; loads on task change; `ComposedPreview` includes attachment paths. Writes directly via `new AttachmentStore()` + `new TaskAttachmentRepository(ctx)`. Helper rows (`ChildOutcomeRowViewModel`, `SubtaskRowViewModel`, `LogLineViewModel`, `AttachmentRowViewModel`) live in the same file. - **TaskRowViewModel** / **ListNavItemViewModel** — lightweight display VMs (task row: status, planning phase, parent/blocked links, roadblock count, computed `IsDraft`/`IsPlanned`/`IsChild`/`IsPlanningParent`/`CanRefine`; list row: kind Smart/Virtual/User, count, icon/dot keys, drop hints). - **NotesEditorViewModel** — day navigator + bullet CRUD for daily notes via `INotesApi`. @@ -45,7 +45,7 @@ Design/ — Tokens.axaml (design tokens; merged before styles) + IslandStyle ## Services -- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface tracks `WorkerHub` (see `src/ClaudeDo.Worker/CLAUDE.md` for the canonical method/event list); groups: task execution (RunNow/Cancel/Continue/Reset/SetTaskStatus), review (`ApproveReviewAsync(taskId, targetBranch) -> MergeResultDto`, reject-to-queue/idle, cancel review, `PreviewMergeAsync -> MergePreviewDto`), planning sessions (start/resume/discard/finalize, queue subtasks, pending draft count, interactive terminal, refine), planning aggregate/integration-branch diffs, unit-merge continue/abort, single-task conflict resolving (start/get-conflict-documents/write-resolution/continue/abort), worktrees (overview, set state, force remove, cleanup, reset all), agents, app settings, lists/config, weekly report, daily notes, daily prep (`RunDailyPrepNowAsync`, `ClearMyDayAsync`, `GetLastPrepLogAsync`), prime schedules, recent worker logs (`GetRecentLogsAsync`). Events mirror `HubBroadcaster` (task/worktree/list/run updates, prep events, planning-merge events, refine events, worker log). Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`. +- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface tracks `WorkerHub` (see `src/ClaudeDo.Worker/CLAUDE.md` for the canonical method/event list); groups: task execution (RunNow/Cancel/Continue/Reset/SetTaskStatus), review (`ApproveReviewAsync(taskId, targetBranch) -> MergeResultDto`, reject-to-queue/idle, cancel review, `PreviewMergeAsync -> MergePreviewDto`), planning sessions (start/resume/discard/finalize, queue subtasks, pending draft count, refine), pick-up-in-terminal + embedded ConPTY launch specs (`GetInteractiveLaunchSpecAsync`/`GetAdHocLaunchSpecAsync`), planning aggregate/integration-branch diffs, unit-merge continue/abort, single-task conflict resolving (start/get-conflict-documents/write-resolution/continue/abort), worktrees (overview, set state, force remove, cleanup, reset all), agents, app settings, lists/config, weekly report, daily notes, daily prep (`RunDailyPrepNowAsync`, `ClearMyDayAsync`, `GetLastPrepLogAsync`), prime schedules, recent worker logs (`GetRecentLogsAsync`). Events mirror `HubBroadcaster` (task/worktree/list/run updates, prep events, planning-merge events, refine events, worker log). Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`. - **INotesApi** / **WorkerNotesApi** — daily-note CRUD (`ListAsync(day)`, `AddAsync`, `UpdateAsync`, `DeleteAsync`); UI DTO `DailyNoteDto(Id, Date, Text, SortOrder)`. - **IPrimeScheduleApi** — prime-schedule CRUD (`ListAsync`, `UpsertAsync`, `DeleteAsync`). - **UpdateCheckService** — polls releases, exposes `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` (feeds the shell's update banner). diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs index bf031d36..64050c2a 100644 --- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs @@ -25,11 +25,6 @@ public interface IWorkerClient : INotifyPropertyChanged /// A pending question was answered, timed out, or the run ended: (taskId, questionId). event Action? TaskQuestionResolvedEvent; - event Action? InteractiveSessionStartedEvent; - event Action? InteractiveSessionEndedEvent; - event Action>? InteractiveQueueChangedEvent; - event Action? InteractiveMessageSentEvent; - event Action? PrepStartedEvent; event Action? PrepLineEvent; event Action? PrepFinishedEvent; @@ -51,10 +46,6 @@ public interface IWorkerClient : INotifyPropertyChanged Task ContinueTaskAsync(string taskId, string followUpPrompt); /// Answer a question a running task raised via AskUser. Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer); - Task SendInteractiveMessageAsync(string taskId, string text); - Task RemoveQueuedInteractiveMessageAsync(string taskId, string text); - Task StopInteractiveSessionAsync(string taskId); - Task InterruptInteractiveSessionAsync(string taskId); /// The question a running task is currently blocked on, if any (for re-attach). Task GetPendingQuestionAsync(string taskId); Task ResetTaskAsync(string taskId); @@ -83,9 +74,7 @@ public interface IWorkerClient : INotifyPropertyChanged Task ContinueConflictMergeAsync(string taskId); Task AbortConflictMergeAsync(string taskId); Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default); - Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default); - // Picks up a task's Claude session in a real terminal window (--resume) — distinct - // from OpenInteractiveTerminalAsync (the in-app streaming session). + // Picks up a task's Claude session in a real terminal window (--resume). Task ResumeTaskInTerminalAsync(string taskId, CancellationToken ct = default); /// Launch spec for an embedded ConPTY terminal to open an interactive session /// in a task's worktree (same worktree prep as an autonomous run). diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs index afaa3188..acccd4c1 100644 --- a/src/ClaudeDo.Ui/Services/WorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs @@ -49,10 +49,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC public event Action? TaskUpdatedEvent; public event Action? TaskQuestionAskedEvent; public event Action? TaskQuestionResolvedEvent; - public event Action? InteractiveSessionStartedEvent; - public event Action? InteractiveSessionEndedEvent; - public event Action>? InteractiveQueueChangedEvent; - public event Action? InteractiveMessageSentEvent; public event Action? ConnectionRestoredEvent; public event Action? WorktreeUpdatedEvent; public event Action? ListUpdatedEvent; @@ -152,26 +148,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC Dispatcher.UIThread.Post(() => TaskQuestionResolvedEvent?.Invoke(taskId, questionId)); }); - _hub.On("InteractiveSessionStarted", taskId => - { - Dispatcher.UIThread.Post(() => InteractiveSessionStartedEvent?.Invoke(taskId)); - }); - - _hub.On("InteractiveSessionEnded", taskId => - { - Dispatcher.UIThread.Post(() => InteractiveSessionEndedEvent?.Invoke(taskId)); - }); - - _hub.On>("InteractiveQueueChanged", (taskId, pending) => - { - Dispatcher.UIThread.Post(() => InteractiveQueueChangedEvent?.Invoke(taskId, pending)); - }); - - _hub.On("InteractiveMessageSent", (taskId, text) => - { - Dispatcher.UIThread.Post(() => InteractiveMessageSentEvent?.Invoke(taskId, text)); - }); - _hub.On("WorktreeUpdated", taskId => { Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId)); @@ -303,30 +279,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC catch { /* offline or already resolved — the UI clears optimistically */ } } - public async Task SendInteractiveMessageAsync(string taskId, string text) - { - try { await _hub.InvokeAsync("SendInteractiveMessage", taskId, text); } - catch { /* offline or session already ended */ } - } - - public async Task RemoveQueuedInteractiveMessageAsync(string taskId, string text) - { - try { await _hub.InvokeAsync("RemoveQueuedInteractiveMessage", taskId, text); } - catch { /* offline or session already ended */ } - } - - public async Task StopInteractiveSessionAsync(string taskId) - { - try { await _hub.InvokeAsync("StopInteractiveSession", taskId); } - catch { /* offline */ } - } - - public async Task InterruptInteractiveSessionAsync(string taskId) - { - try { await _hub.InvokeAsync("InterruptInteractiveSession", taskId); } - catch { /* offline */ } - } - public Task GetPendingQuestionAsync(string taskId) => TryInvokeAsync("GetPendingQuestion", taskId); @@ -558,9 +510,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC public async Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("ResumePlanningSessionAsync", taskId, ct); - public async Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) - => await _hub.InvokeAsync("OpenInteractiveTerminalAsync", taskId, ct); - public async Task ResumeTaskInTerminalAsync(string taskId, CancellationToken ct = default) => await _hub.InvokeAsync("ResumeTaskInTerminal", taskId, ct); diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs index 612c2657..35b39472 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs @@ -71,9 +71,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro private const string RoadblockMarker = "Roadblocks reported during the run:"; - public ObservableCollection QueuedMessages { get; } = new(); - public bool HasQueuedMessages => QueuedMessages.Count > 0; - // Captured handler delegates for disposal private readonly Action _onTaskMessage; private readonly Action _onTaskStarted; @@ -81,19 +78,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro private readonly Action _onTaskUpdated; private readonly Action _onTaskQuestionAsked; private readonly Action _onTaskQuestionResolved; - private readonly Action _onInteractiveStarted; - private readonly Action _onInteractiveEnded; - private readonly Action> _onInteractiveQueueChanged; - private readonly Action _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; } -} diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs index b4c5f522..c82feb1a 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs @@ -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? OpenConPtySessionRequested; [RelayCommand] diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs index 3e16f985..c65a8032 100644 --- a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs @@ -22,7 +22,6 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable private readonly Action _onTaskFinished; private readonly Action _onTaskUpdated; private readonly Action _onConnectionRestored; - private readonly Action _onInteractiveStarted; public ObservableCollection 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; diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml index 24bb82ab..0e6e2314 100644 --- a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml @@ -263,83 +263,6 @@ Command="{Binding RejectReviewCommand}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -