diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index e1edeb01..a3b1760c 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -263,6 +263,7 @@ "detach": "Abdocken", "redock": "Andocken", "windowTitle": "Mission Control", + "newSession": "Neue Sitzung", "clearFinished": "Erledigte entfernen", "empty": "Keine laufenden Aufgaben", "settings": "Einstellungen", diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 4be89921..6220203c 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -263,6 +263,7 @@ "detach": "Detach", "redock": "Re-dock", "windowTitle": "Mission Control", + "newSession": "New session", "clearFinished": "Clear finished", "empty": "No running tasks", "settings": "Settings", diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs index ec6a9cca..3ae6ebef 100644 --- a/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs @@ -7,14 +7,15 @@ using ClaudeDo.Ui.Services; namespace ClaudeDo.Ui.ViewModels.MissionControl; /// -/// Command Center pane hosting an embedded ConPTY terminal for one task's interactive Claude -/// session (task-based only — no ad-hoc/free sessions yet). Distinct from the streamed-log +/// Command Center pane hosting an embedded ConPTY terminal for either one task's interactive +/// Claude session, or an ad-hoc/free session in a user-chosen directory (no task, +/// is null — ad-hoc panes are never deduped, unlike task-based ones). Distinct from the streamed-log /// pane; the two coexist until /// the streaming interactive stack is removed. /// public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControlPane, IDisposable { - public string TaskId { get; } + public string? TaskId { get; } [ObservableProperty] private string _displayTitle; @@ -26,7 +27,9 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl /// Set by the host (Mission Control) to remove this pane from its collection. public Action? CloseRequested { get; set; } - public ConPtyPaneViewModel(string taskId, string displayTitle, TerminalLaunchDescriptor descriptor) + /// Task-based pane — dedup'd by . Pass null for an ad-hoc pane + /// (no task, never deduped); prefer at ad-hoc call sites. + public ConPtyPaneViewModel(string? taskId, string displayTitle, TerminalLaunchDescriptor descriptor) { TaskId = taskId; _displayTitle = displayTitle; @@ -34,6 +37,10 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl Terminal.Start(descriptor); } + /// Ad-hoc pane — no task, no dedup. + public static ConPtyPaneViewModel CreateAdHoc(string displayTitle, TerminalLaunchDescriptor descriptor) + => new(null, displayTitle, descriptor); + private void OnTerminalPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(InteractiveTerminalViewModel.StartError) && Terminal.StartError is { Length: > 0 } error) diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs index dd2e2e16..3e16f985 100644 --- a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.IO; using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -265,6 +266,30 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable } } + // Ad-hoc (task-less) ConPTY session in a user-chosen directory. Never deduped — every call + // opens a fresh pane, unlike the task-based OpenConPtySessionAsync above. + public async System.Threading.Tasks.Task OpenAdHocConPtySessionAsync(string directory) + { + if (string.IsNullOrEmpty(directory)) return; + + var title = Path.GetFileName(directory.TrimEnd('\\', '/')); + if (string.IsNullOrEmpty(title)) title = directory; + + try + { + var spec = await _worker.GetAdHocLaunchSpecAsync(directory); + var descriptor = new TerminalLaunchDescriptor(spec.Cwd, spec.Exe, spec.Args, spec.Env); + var pane = ConPtyPaneViewModel.CreateAdHoc(title, descriptor); + pane.ErrorReported += OnConPtyPaneError; + pane.CloseRequested += CloseConPtySession; + ConPtySessions.Add(pane); + } + catch (Exception ex) + { + ErrorReported?.Invoke(Loc.T("missionControl.conptyLaunchFailed", ex.Message)); + } + } + private void OnConPtyPaneError(string message) => ErrorReported?.Invoke(message); private void CloseConPtySession(ConPtyPaneViewModel pane) diff --git a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml index e2862888..0292abeb 100644 --- a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml +++ b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml @@ -32,6 +32,12 @@ LetterSpacing="1.4" VerticalAlignment="Center" /> +