diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt index f99154c67ad..b60e9ed837c 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt @@ -204,6 +204,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { prefs.setOnboardingCompleted(value) } + fun hasStoredNodeDeviceToken(): Boolean { + return ensureRuntime().hasStoredNodeDeviceToken() + } + fun setCanvasDebugStatusEnabled(value: Boolean) { prefs.setCanvasDebugStatusEnabled(value) } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index 1ad8d6ad107..4ef9041b1e9 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -535,6 +535,10 @@ class NodeRuntime( fun setGatewayBootstrapToken(value: String) = prefs.setGatewayBootstrapToken(value) fun setGatewayPassword(value: String) = prefs.setGatewayPassword(value) fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value) + fun hasStoredNodeDeviceToken(): Boolean { + val deviceId = identityStore.loadOrCreate().deviceId + return !deviceAuthStore.loadToken(deviceId, "node").isNullOrBlank() + } val lastDiscoveredStableId: StateFlow = prefs.lastDiscoveredStableId val canvasDebugStatusEnabled: StateFlow = prefs.canvasDebugStatusEnabled val notificationForwardingEnabled: StateFlow = prefs.notificationForwardingEnabled diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt index d8f0d0c21ed..4781d33986f 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt @@ -228,7 +228,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { var manualTls by rememberSaveable { mutableStateOf(false) } var gatewayError by rememberSaveable { mutableStateOf(null) } var attemptedConnect by rememberSaveable { mutableStateOf(false) } - val canFinishOnboarding = isConnected || (gatewayInputMode == GatewayInputMode.SetupCode && isNodeConnected) + val canRecoverBootstrapRetry = + gatewayInputMode == GatewayInputMode.SetupCode && + attemptedConnect && + statusText.contains("bootstrap token invalid or expired", ignoreCase = true) && + viewModel.hasStoredNodeDeviceToken() + val canFinishOnboarding = + isConnected || (gatewayInputMode == GatewayInputMode.SetupCode && (isNodeConnected || canRecoverBootstrapRetry)) val lifecycleOwner = LocalLifecycleOwner.current val qrScannerOptions =