Merge branch 'claudedo/75514ee68371402b814638290af47cca'
This commit is contained in:
@@ -109,6 +109,13 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
// only — see RefreshStaleWorkerCheckAsync.
|
||||
[ObservableProperty] private bool _isStaleWorkerBannerVisible;
|
||||
|
||||
// planningTaskId -> subtaskId, for unit-merge conflicts an MCP session (not the UI) started.
|
||||
// Kept as a dictionary so a later conflict on the same planning task updates in place instead
|
||||
// of piling up duplicate entries.
|
||||
private readonly Dictionary<string, string> _externalMergeConflicts = new();
|
||||
[ObservableProperty] private bool _isExternalMergeBannerVisible;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private double _windowWidth = 1280;
|
||||
|
||||
@@ -185,13 +192,59 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
_primeStatusTimer.Start();
|
||||
}
|
||||
|
||||
private void OnPlanningMergeConflict(string planningTaskId, string subtaskId, IReadOnlyList<string> conflictedFiles)
|
||||
public void OnPlanningMergeConflict(
|
||||
string planningTaskId, string subtaskId, IReadOnlyList<string> conflictedFiles, bool externallyDriven)
|
||||
{
|
||||
// Already on UI thread (WorkerClient dispatches via Dispatcher.UIThread.Post).
|
||||
if (externallyDriven)
|
||||
{
|
||||
// An MCP session (review_task/continue_merge) is driving this merge — it owns
|
||||
// resolution. Auto-opening the resolver here raced with the session's own writes
|
||||
// (two parties editing the same shared checkout at once); show a banner instead and
|
||||
// leave the resolver reachable only via a deliberate click.
|
||||
_externalMergeConflicts[planningTaskId] = subtaskId;
|
||||
IsExternalMergeBannerVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// A unit-merge conflict resolves in the same in-app 3-way editor as a single-task merge.
|
||||
_ = OpenPlanningConflictAsync(planningTaskId, subtaskId);
|
||||
}
|
||||
|
||||
public void OnPlanningMergeAborted(string planningTaskId) => ClearExternalMergeConflict(planningTaskId);
|
||||
public void OnPlanningMergeCompleted(string planningTaskId) => ClearExternalMergeConflict(planningTaskId);
|
||||
|
||||
private void ClearExternalMergeConflict(string planningTaskId)
|
||||
{
|
||||
_externalMergeConflicts.Remove(planningTaskId);
|
||||
IsExternalMergeBannerVisible = _externalMergeConflicts.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>Re-syncs the external-merge banner from the worker on (re)connect — the
|
||||
/// one-shot PlanningMergeConflict broadcast isn't replayed after a UI restart, so this is
|
||||
/// the recovery path. The worker checks MERGE_HEAD before reporting a conflict as active,
|
||||
/// so a session that died mid-merge without cleaning up doesn't leave a stale banner up.</summary>
|
||||
private async Task RefreshExternalMergeConflictsAsync()
|
||||
{
|
||||
if (Worker is null) return;
|
||||
IReadOnlyList<PlanningMergeConflictStateDto> active;
|
||||
try { active = await Worker.GetActiveExternalPlanningMergeConflictsAsync(); }
|
||||
catch { return; }
|
||||
|
||||
_externalMergeConflicts.Clear();
|
||||
foreach (var c in active)
|
||||
_externalMergeConflicts[c.PlanningTaskId] = c.SubtaskId;
|
||||
IsExternalMergeBannerVisible = _externalMergeConflicts.Count > 0;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task OpenExternalMergeConflictAsync()
|
||||
{
|
||||
if (_externalMergeConflicts.Count == 0) return Task.CompletedTask;
|
||||
var (planningTaskId, subtaskId) = _externalMergeConflicts.First();
|
||||
return OpenPlanningConflictAsync(planningTaskId, subtaskId);
|
||||
}
|
||||
|
||||
private async Task OpenPlanningConflictAsync(string planningTaskId, string subtaskId)
|
||||
{
|
||||
if (ConflictResolverFactory is null || Dialogs is null) return;
|
||||
@@ -298,6 +351,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
Worker.WorkerLogReceivedEvent += OnWorkerLogReceived;
|
||||
Worker.ConnectionRestoredEvent += () => _ = RefreshStaleWorkerCheckAsync();
|
||||
Worker.PlanningMergeConflictEvent += OnPlanningMergeConflict;
|
||||
Worker.PlanningMergeAbortedEvent += OnPlanningMergeAborted;
|
||||
Worker.PlanningCompletedEvent += OnPlanningMergeCompleted;
|
||||
Worker.ConnectionRestoredEvent += () => _ = RefreshExternalMergeConflictsAsync();
|
||||
Worker.PrimeFired += OnPrimeFired;
|
||||
_clearTimer.Elapsed += (_, _) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user