chore(claude-do): Mission Control: allgemeiner Session-Handoff an eine frische

## Ziel

Jede Mission-Control-Session (Ad-hoc UND Task-Session) soll ihre Arbeit an eine frische Session
uebergeben koennen: "gib das ab an eine neue Session". Die alte Kachel wird geschlossen, an
derselben Position oeffnet eine frische Session im selben cwd, die mit einem Brief startet, den
die abgebende Session selbst geschrieben hat.

Motivation: eine lange Session mit vollem/verrottetem Kontex

ClaudeDo-Task: 8b846deb-ad10-4243-9272-9aea2a01ec93
This commit is contained in:
Mika Kuns
2026-09-28 11:57:37 +02:00
parent e0a1448f44
commit 08d1797ead
16 changed files with 598 additions and 35 deletions
@@ -108,6 +108,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
_onHandoffRequested = (taskId, survivingTaskIds, nextPhase) => { _ = OpenMergeHelperHandoffConPtySessionAsync(taskId, survivingTaskIds, nextPhase); };
_worker.HandoffRequestedEvent += _onHandoffRequested;
_worker.SessionHandoffRequestedEvent += OpenSessionHandoff;
_onListHandlerStartRequested = (listId, taskIds) => { OpenRequested?.Invoke(); _ = OpenMergeHelperConPtySessionAsync(listId, taskIds); };
_worker.ListHandlerStartRequestedEvent += _onListHandlerStartRequested;
@@ -322,11 +323,31 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
var title = Path.GetFileName(directory.TrimEnd('\\', '/'));
if (string.IsNullOrEmpty(title)) title = directory;
AddConPtyPane(ConPtyPaneViewModel.CreateAdHoc(title,
() => DescribeAsync(() => _worker.GetAdHocLaunchSpecAsync(directory))));
// The key is minted here, not in the pane, because the launch spec needs it as the paneId.
var paneId = Guid.NewGuid().ToString("n");
AddConPtyPane(new ConPtyPaneViewModel(null, title,
() => DescribeAsync(() => _worker.GetAdHocLaunchSpecAsync(directory, paneId))) { IdentityKey = paneId });
return System.Threading.Tasks.Task.CompletedTask;
}
/// <summary>A session called handoff_session: replaces its tile (same slot, same IdentityKey, so
/// same colour) with a fresh session in the same cwd that starts from the brief. No matching tile
/// (closed meanwhile, or another app instance) = nothing to replace.</summary>
internal void OpenSessionHandoff(string paneId, string briefPath)
{
if (ConPtySessions.FirstOrDefault(s => s.IdentityKey == paneId) is not { Cwd: { } cwd } outgoing) return;
OpenRequested?.Invoke();
var slot = ConPtySessions.IndexOf(outgoing);
var taskId = outgoing.TaskId;
var title = outgoing.DisplayTitle;
CloseConPtySession(outgoing);
AddConPtyPane(new ConPtyPaneViewModel(taskId, title,
() => DescribeAsync(() => _worker.GetSessionHandoffLaunchSpecAsync(paneId, cwd, briefPath, taskId)))
{ IdentityKey = paneId }, slot);
}
// List-handler session over a hand-picked set of tasks ("Let Claude handle it"). Task-based:
// creates one new ClaudeDo task per run to own the session (title/diff/result), deduped by
// TaskId like OpenConPtySessionAsync. The handler still merges the tasks it handles itself
@@ -615,6 +636,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
_worker.TaskUpdatedEvent -= _onTaskUpdated;
_worker.ConnectionRestoredEvent -= _onConnectionRestored;
_worker.HandoffRequestedEvent -= _onHandoffRequested;
_worker.SessionHandoffRequestedEvent -= OpenSessionHandoff;
_worker.ListHandlerStartRequestedEvent -= _onListHandlerStartRequested;
_worker.HandlerUsageWarningEvent -= _onHandlerUsageWarning;
ConPtySessions.CollectionChanged -= OnConPtySessionsChanged;