diff --git a/src/OpenClaw.Shared/OpenClawGatewayClient.cs b/src/OpenClaw.Shared/OpenClawGatewayClient.cs index ebe8e34..05189f5 100644 --- a/src/OpenClaw.Shared/OpenClawGatewayClient.cs +++ b/src/OpenClaw.Shared/OpenClawGatewayClient.cs @@ -53,6 +53,7 @@ public class OpenClawGatewayClient : WebSocketClientBase private string _connectAuthToken; private SignatureTokenMode _signatureTokenMode = SignatureTokenMode.V3AuthToken; private long? _challengeTimestampMs; + private string? _currentChallengeNonce; private bool _usageStatusUnsupported; private bool _usageCostUnsupported; private bool _sessionPreviewUnsupported; @@ -788,6 +789,7 @@ public class OpenClawGatewayClient : WebSocketClientBase if (_signatureTokenMode != previousMode) { _logger.Warn($"Gateway rejected device signature with mode {previousMode}; retrying with mode {_signatureTokenMode}"); + _ = SendConnectMessageAsync(_currentChallengeNonce); return; } @@ -1125,6 +1127,7 @@ public class OpenClawGatewayClient : WebSocketClientBase } _challengeTimestampMs = ts; + _currentChallengeNonce = nonce; _logger.Info($"Received challenge, nonce: {nonce}"); _ = SendConnectMessageAsync(nonce); diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index b895961..04f053d 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -270,6 +270,7 @@ public partial class App : Application ToastNotificationManagerCompat.OnActivated += OnToastActivated; _sshTunnelService = new SshTunnelService(new AppLogger()); + _sshTunnelService.TunnelExited += OnSshTunnelExited; // First-run check if (string.IsNullOrWhiteSpace(_settings.Token)) @@ -2264,7 +2265,25 @@ public partial class App : Application #endregion - private Microsoft.UI.Dispatching.DispatcherQueue? AppDispatcherQueue => + private async void OnSshTunnelExited(object? sender, int exitCode) + { + Logger.Warn($"SSH tunnel exited unexpectedly (code {exitCode}); restarting in 3s..."); + await Task.Delay(3000); + if (_sshTunnelService != null && _settings?.UseSshTunnel == true) + { + try + { + _sshTunnelService.EnsureStarted(_settings); + Logger.Info("SSH tunnel restarted successfully"); + } + catch (Exception ex) + { + Logger.Error($"SSH tunnel restart failed: {ex.Message}"); + } + } + } + + private Microsoft.UI.Dispatching.DispatcherQueue? AppDispatcherQueue => Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); } diff --git a/src/OpenClaw.Tray.WinUI/Services/SshTunnelService.cs b/src/OpenClaw.Tray.WinUI/Services/SshTunnelService.cs index 99ee08e..b983354 100644 --- a/src/OpenClaw.Tray.WinUI/Services/SshTunnelService.cs +++ b/src/OpenClaw.Tray.WinUI/Services/SshTunnelService.cs @@ -16,6 +16,9 @@ public sealed class SshTunnelService : IDisposable private string? _lastSpec; private bool _stopping; + /// Raised when the SSH tunnel exits unexpectedly (not during shutdown). + public event EventHandler? TunnelExited; + public SshTunnelService(IOpenClawLogger logger) { _logger = logger; @@ -130,6 +133,10 @@ public sealed class SshTunnelService : IDisposable else { _logger.Warn($"SSH tunnel exited unexpectedly (code {exitCode})"); + try { process.Dispose(); } catch { } + _process = null; + _lastSpec = null; + TunnelExited?.Invoke(this, exitCode); } };