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" />
+