fix: signature fallback retries connect, SSH tunnel auto-restart (closes #131, #132)

#131: Store challenge nonce and re-send connect message after advancing
signature mode on 'device signature invalid' rejection. Previously the
client got stuck in Connecting state after mode fallback.

#132: Clean up process/spec on unexpected SSH tunnel exit, raise
TunnelExited event. App.xaml.cs subscribes and auto-restarts after 3s
delay. Previously the tunnel stayed dead until manual intervention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Scott Hanselman 2026-04-02 09:46:04 -07:00
parent 4ffd028a02
commit 8f43544fa0
3 changed files with 30 additions and 1 deletions

View File

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

View File

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

View File

@ -16,6 +16,9 @@ public sealed class SshTunnelService : IDisposable
private string? _lastSpec;
private bool _stopping;
/// <summary>Raised when the SSH tunnel exits unexpectedly (not during shutdown).</summary>
public event EventHandler<int>? 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);
}
};