diff --git a/apps/android/app/src/main/java/ai/openclaw/android/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/android/MainViewModel.kt index 7076f09a292..176032d1fc2 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/MainViewModel.kt @@ -144,10 +144,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { runtime.setTalkEnabled(enabled) } - fun logGatewayDebugSnapshot(source: String = "manual") { - runtime.logGatewayDebugSnapshot(source) - } - fun refreshGatewayConnection() { runtime.refreshGatewayConnection() } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/android/NodeRuntime.kt index 3e804ec8a07..da27d9dbdbd 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/NodeRuntime.kt @@ -328,13 +328,20 @@ class NodeRuntime(context: Context) { private fun updateStatus() { _isConnected.value = operatorConnected + val operator = operatorStatusText.trim() + val node = nodeStatusText.trim() _statusText.value = when { operatorConnected && _nodeConnected.value -> "Connected" operatorConnected && !_nodeConnected.value -> "Connected (node offline)" - !operatorConnected && _nodeConnected.value -> "Connected (operator offline)" - operatorStatusText.isNotBlank() && operatorStatusText != "Offline" -> operatorStatusText - else -> nodeStatusText + !operatorConnected && _nodeConnected.value -> + if (operator.isNotEmpty() && operator != "Offline") { + "Connected (operator: $operator)" + } else { + "Connected (operator offline)" + } + operator.isNotBlank() && operator != "Offline" -> operator + else -> node } } @@ -614,17 +621,14 @@ class NodeRuntime(context: Context) { prefs.setTalkEnabled(value) } - fun logGatewayDebugSnapshot(source: String = "manual") { - val flowToken = gatewayToken.value.trim() - val loadedToken = prefs.loadGatewayToken().orEmpty() - Log.i( - "OpenClawGatewayDebug", - "source=$source manualEnabled=${manualEnabled.value} host=${manualHost.value} port=${manualPort.value} tls=${manualTls.value} flowTokenLen=${flowToken.length} loadTokenLen=${loadedToken.length} connected=${isConnected.value} status=${statusText.value}", - ) - } - fun refreshGatewayConnection() { - val endpoint = connectedEndpoint ?: return + val endpoint = + connectedEndpoint ?: run { + _statusText.value = "Failed: no cached gateway endpoint" + return + } + operatorStatusText = "Connecting…" + updateStatus() val token = prefs.loadGatewayToken() val password = prefs.loadGatewayPassword() val tls = connectionManager.resolveTlsParams(endpoint) diff --git a/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt b/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt index 4e210de8fb9..92acf968954 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt @@ -62,6 +62,11 @@ class GatewaySession( private val onInvoke: (suspend (InvokeRequest) -> InvokeResult)? = null, private val onTlsFingerprint: ((stableId: String, fingerprint: String) -> Unit)? = null, ) { + private companion object { + // Keep connect timeout above observed gateway unauthorized close on lower-end devices. + private const val CONNECT_RPC_TIMEOUT_MS = 12_000L + } + data class InvokeRequest( val id: String, val nodeId: String, @@ -302,26 +307,13 @@ class GatewaySession( val identity = identityStore.loadOrCreate() val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role) val trimmedToken = token?.trim().orEmpty() - val authToken = if (storedToken.isNullOrBlank()) trimmedToken else storedToken + // QR/setup/manual shared token must take precedence; stale role tokens can survive re-onboarding. + val authToken = if (trimmedToken.isNotBlank()) trimmedToken else storedToken.orEmpty() val payload = buildConnectParams(identity, connectNonce, authToken, password?.trim()) - var res = request("connect", payload, timeoutMs = 8_000) + val res = request("connect", payload, timeoutMs = CONNECT_RPC_TIMEOUT_MS) if (!res.ok) { val msg = res.error?.message ?: "connect failed" - val hasStoredToken = !storedToken.isNullOrBlank() - val canRetryWithShared = hasStoredToken && trimmedToken.isNotBlank() - if (canRetryWithShared) { - val sharedPayload = buildConnectParams(identity, connectNonce, trimmedToken, password?.trim()) - val sharedRes = request("connect", sharedPayload, timeoutMs = 8_000) - if (!sharedRes.ok) { - val retryMsg = sharedRes.error?.message ?: msg - throw IllegalStateException(retryMsg) - } - // Stored device token was bypassed successfully; clear stale token for future connects. - deviceAuthStore.clearToken(identity.deviceId, options.role) - res = sharedRes - } else { - throw IllegalStateException(msg) - } + throw IllegalStateException(msg) } handleConnectSuccess(res, identity.deviceId) connectDeferred.complete(Unit) diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/ConnectTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/ConnectTabScreen.kt index 9f7cf2211a1..875b82796d3 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/ConnectTabScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/ConnectTabScreen.kt @@ -168,6 +168,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) { validationText = null return@Button } + if (statusText.contains("operator offline", ignoreCase = true)) { + validationText = null + viewModel.refreshGatewayConnection() + return@Button + } val config = resolveGatewayConnectConfig( @@ -397,15 +402,6 @@ fun ConnectTabScreen(viewModel: MainViewModel) { HorizontalDivider(color = mobileBorder) - Text( - "Debug snapshot: mode=${if (inputMode == ConnectInputMode.SetupCode) "setup" else "manual"}, manualEnabled=$manualEnabled, tokenLen=${gatewayToken.trim().length}", - style = mobileCaption1, - color = mobileTextSecondary, - ) - TextButton(onClick = { viewModel.logGatewayDebugSnapshot(source = "connect_tab") }) { - Text("Log gateway debug snapshot", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent) - } - TextButton(onClick = { viewModel.setOnboardingCompleted(false) }) { Text("Run onboarding again", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent) }