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);
}
};