Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/OpenClaw.Tray.WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args)
ToastNotificationManagerCompat.OnActivated += OnToastActivated;

_sshTunnelService = new SshTunnelService(new AppLogger());
_sshTunnelService.TunnelExited += OnSshTunnelExited;

// First-run check
if (string.IsNullOrWhiteSpace(_settings.Token))
Expand Down Expand Up @@ -2262,6 +2263,35 @@ _settings.SshTunnelRemotePort is < 1 or > 65535 ||
return true;
}

private async void OnSshTunnelExited(object? sender, EventArgs e)
{
if (_isExiting || _settings?.UseSshTunnel != true) return;

// Attempt to restart the SSH tunnel with bounded exponential backoff.
// The gateway client's built-in reconnect loop will pick up automatically
// once the tunnel port is available again.
int[] retryDelays = [1000, 2000, 5000, 10000, 30000];
for (int i = 0; i < retryDelays.Length; i++)
{
await Task.Delay(retryDelays[i]);
if (_isExiting || _settings?.UseSshTunnel != true) return;
try
{
if (EnsureSshTunnelConfigured())
{
Logger.Info("SSH tunnel successfully restarted after unexpected exit");
return;
}
}
catch (Exception ex)
{
Logger.Warn($"SSH tunnel restart attempt {i + 1} failed: {ex.Message}");
}
}

Logger.Error("SSH tunnel could not be restarted after all retry attempts");
}

#endregion

private Microsoft.UI.Dispatching.DispatcherQueue? AppDispatcherQueue =>
Expand Down
9 changes: 9 additions & 0 deletions src/OpenClaw.Tray.WinUI/Services/SshTunnelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public SshTunnelService(IOpenClawLogger logger)
_logger = logger;
}

/// <summary>
/// Raised when the SSH tunnel process exits unexpectedly (i.e., not as a result of <see cref="Stop"/>).
/// </summary>
public event EventHandler? TunnelExited;

public bool IsRunning => _process is { HasExited: false };

public void EnsureStarted(SettingsManager settings)
Expand Down Expand Up @@ -130,6 +135,10 @@ private void StartProcess(string user, string host, int remotePort, int localPor
else
{
_logger.Warn($"SSH tunnel exited unexpectedly (code {exitCode})");
_process = null;
_lastSpec = null;
try { process.Dispose(); } catch (Exception disposeEx) { _logger.Warn($"SSH tunnel process dispose failed: {disposeEx.Message}"); }
TunnelExited?.Invoke(this, EventArgs.Empty);
}
};

Expand Down