feat(ui): worker-menu Shutdown ends worker + app after confirm
Adds a Shutdown entry after the Worker menu's Restart/Update Claude CLI items. Confirms via IDialogService.ConfirmAsync, then kills this installation's ClaudeDo.Worker process(es) (shared kill loop, extracted from RestartWorkerService) and force-closes the window through the same _forceClose path MainWindow already uses for the Mission-Control close confirm, so no second confirmation dialog shows.
This commit is contained in:
@@ -658,8 +658,17 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
var exe = _workerLocator.Find();
|
||||
if (exe is null) throw new InvalidOperationException("Worker executable not found.");
|
||||
|
||||
// Only kill the worker belonging to THIS installation — not any other
|
||||
// ClaudeDo.Worker on the machine (e.g. a second install).
|
||||
KillMatchingWorkerProcesses(exe);
|
||||
|
||||
var started = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true });
|
||||
if (started is not null && started.WaitForExit(RestartSurvivalCheckMs))
|
||||
throw new InvalidOperationException($"worker process exited immediately (code {started.ExitCode})");
|
||||
}
|
||||
|
||||
// Only kill the worker belonging to THIS installation — not any other
|
||||
// ClaudeDo.Worker on the machine (e.g. a second install).
|
||||
private static void KillMatchingWorkerProcesses(string exe)
|
||||
{
|
||||
var exeFull = System.IO.Path.GetFullPath(exe);
|
||||
foreach (var p in System.Diagnostics.Process.GetProcessesByName("ClaudeDo.Worker"))
|
||||
{
|
||||
@@ -675,10 +684,29 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
catch { /* may have exited or be inaccessible */ }
|
||||
finally { p.Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
var started = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true });
|
||||
if (started is not null && started.WaitForExit(RestartSurvivalCheckMs))
|
||||
throw new InvalidOperationException($"worker process exited immediately (code {started.ExitCode})");
|
||||
// Set by MainWindow: force-closes the window without re-triggering its own close
|
||||
// confirmation (the shutdown dialog above already confirmed).
|
||||
public Action? CloseWindow { get; set; }
|
||||
|
||||
// Test seam: overrides the real kill-worker + close-window body.
|
||||
internal Action? ShutdownOperation { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ShutdownAsync()
|
||||
{
|
||||
if (Dialogs is null) return;
|
||||
var confirmed = await Dialogs.ConfirmAsync(Loc.T("vm.shell.shutdownConfirm"));
|
||||
if (!confirmed) return;
|
||||
(ShutdownOperation ?? ShutdownWorkerAndCloseApp)();
|
||||
}
|
||||
|
||||
private void ShutdownWorkerAndCloseApp()
|
||||
{
|
||||
var exe = _workerLocator.Find();
|
||||
if (exe is not null) KillMatchingWorkerProcesses(exe);
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
|
||||
private bool _updatingClaudeCli;
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
Command="{Binding OpenUsageMonitorCommand}"/>
|
||||
<MenuItem Header="{loc:Tr shell.menu.updateClaudeCli}"
|
||||
Command="{Binding UpdateClaudeCliCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{loc:Tr shell.menu.shutdown}"
|
||||
Command="{Binding ShutdownCommand}"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="{loc:Tr shell.menu.repositories}"
|
||||
FontSize="{StaticResource FontSizeMono}"
|
||||
|
||||
@@ -80,6 +80,11 @@ public partial class MainWindow : Window
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
};
|
||||
vm.CloseWindow = () =>
|
||||
{
|
||||
_forceClose = true;
|
||||
Close();
|
||||
};
|
||||
if (vm.Tasks is not null)
|
||||
vm.Tasks.IsWindowVisible = WindowState != WindowState.Minimized;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user