diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json
index 2d079d85..bd3283a9 100644
--- a/src/ClaudeDo.Localization/locales/de.json
+++ b/src/ClaudeDo.Localization/locales/de.json
@@ -785,6 +785,7 @@
"checkForUpdates": "Nach Updates suchen",
"restartWorker": "Worker neu starten",
"updateClaudeCli": "Claude CLI aktualisieren",
+ "shutdown": "Beenden",
"worktrees": "Worktrees…",
"weeklyReport": "Wochenbericht…",
"usageMonitor": "Usage Monitor…",
@@ -810,7 +811,7 @@
},
"vm": {
"connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" },
- "shell": { "unexpectedError": "Unerwarteter Fehler: {0}" },
+ "shell": { "unexpectedError": "Unerwarteter Fehler: {0}", "shutdownConfirm": "Dadurch wird der ClaudeDo-Worker beendet (laufende Task-Runs und interaktive Sitzungen brechen ab) und die App geschlossen. Fortfahren?" },
"mainWindow": { "closeConfirm": { "message": "{0} Mission-Control-Sitzung(en) laufen noch. Beim Beenden werden sie abgebrochen. Trotzdem schließen?" } },
"agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "review": "Prüfung", "children": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
"taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "waitingForChildren": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "parked": "Geparkt", "interactive": "Interaktiv", "waiting": "Wartet" },
diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json
index 8f71e486..0205a724 100644
--- a/src/ClaudeDo.Localization/locales/en.json
+++ b/src/ClaudeDo.Localization/locales/en.json
@@ -785,6 +785,7 @@
"checkForUpdates": "Check for updates",
"restartWorker": "Restart worker",
"updateClaudeCli": "Update Claude CLI",
+ "shutdown": "Shutdown",
"worktrees": "Worktrees…",
"weeklyReport": "Weekly Report…",
"usageMonitor": "Usage Monitor…",
@@ -810,7 +811,7 @@
},
"vm": {
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
- "shell": { "unexpectedError": "Unexpected error: {0}" },
+ "shell": { "unexpectedError": "Unexpected error: {0}", "shutdownConfirm": "This stops the ClaudeDo worker (any running task or interactive session is interrupted) and closes the app. Continue?" },
"mainWindow": { "closeConfirm": { "message": "{0} Mission Control session(s) are still running. Closing the app will end them. Close anyway?" } },
"agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "review": "Review", "children": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "waitingForChildren": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled", "parked": "Parked", "interactive": "Interactive", "waiting": "Waiting" },
diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
index 1ead9b8e..e879fd4e 100644
--- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
@@ -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;
diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml
index e5d54798..9aad5ade 100644
--- a/src/ClaudeDo.Ui/Views/MainWindow.axaml
+++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml
@@ -68,6 +68,9 @@
Command="{Binding OpenUsageMonitorCommand}"/>
+
+