Skip to content

Commit 5a7b5c4

Browse files
committed
fix(android): preserve retryable pairing guidance
1 parent b8b61a8 commit 5a7b5c4

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

apps/.i18n/native-source.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,31 @@
6767
},
6868
{
6969
"kind": "ui-state-text",
70-
"line": 353,
70+
"line": 360,
7171
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
7272
"source": "Offline",
7373
"surface": "android",
7474
"id": "native.android.c65b61de70a063e7"
7575
},
7676
{
7777
"kind": "conditional-branch",
78-
"line": 1757,
78+
"line": 1764,
7979
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
8080
"source": "Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.",
8181
"surface": "android",
8282
"id": "native.android.81009c40eed2216d"
8383
},
8484
{
8585
"kind": "conditional-branch",
86-
"line": 1759,
86+
"line": 1766,
8787
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
8888
"source": "Failed: secure endpoint reached, but TLS fingerprint verification timed out. Check Tailscale Serve or gateway TLS and retry.",
8989
"surface": "android",
9090
"id": "native.android.467899bb510b8e34"
9191
},
9292
{
9393
"kind": "conditional-branch",
94-
"line": 1761,
94+
"line": 1768,
9595
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
9696
"source": "Failed: couldn't reach the secure gateway endpoint for this host.",
9797
"surface": "android",

apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ data class GatewayConnectionDisplay(
112112
val problem: GatewayConnectionProblem?,
113113
)
114114

115+
private fun gatewayProblemAfterDisconnect(
116+
problem: GatewayConnectionProblem?,
117+
statusText: String,
118+
): GatewayConnectionProblem? =
119+
// Automatic bootstrap pairing retries need their approval guidance until success or a different failure.
120+
problem?.takeIf { statusText == "Reconnecting…" && it.canAutoRetry }
121+
115122
internal fun gatewayConnectionDisplay(
116123
operatorConnected: Boolean,
117124
nodeConnected: Boolean,
@@ -547,7 +554,7 @@ class NodeRuntime(
547554
updateStatus {
548555
operatorConnected = false
549556
operatorStatusText = message
550-
operatorConnectionProblem = null
557+
operatorConnectionProblem = gatewayProblemAfterDisconnect(operatorConnectionProblem, message)
551558
}
552559
micCapture.onGatewayConnectionChanged(false)
553560
},
@@ -603,7 +610,7 @@ class NodeRuntime(
603610
updateStatus {
604611
_nodeConnected.value = false
605612
nodeStatusText = message
606-
nodeConnectionProblem = null
613+
nodeConnectionProblem = gatewayProblemAfterDisconnect(nodeConnectionProblem, message)
607614
}
608615
showLocalCanvasOnDisconnect()
609616
},

apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,42 @@ class GatewayBootstrapAuthTest {
8383
assertNull(runtime.gatewayConnectionDisplay.value.problem)
8484
}
8585

86+
@Test
87+
fun retryableNodePairingProblemSurvivesReconnectStatus() {
88+
val runtime = NodeRuntime(RuntimeEnvironment.getApplication())
89+
val session = readField<GatewaySession>(runtime, "nodeSession")
90+
val onDisconnected = readField<(String) -> Unit>(session, "onDisconnected")
91+
val onConnectFailure = readField<(GatewaySession.ErrorShape, Boolean) -> Unit>(session, "onConnectFailure")
92+
93+
onDisconnected("Gateway error: pairing required")
94+
onConnectFailure(
95+
GatewaySession.ErrorShape(
96+
code = "NOT_PAIRED",
97+
message = "pairing required",
98+
details =
99+
GatewayConnectErrorDetails(
100+
code = "PAIRING_REQUIRED",
101+
canRetryWithDeviceToken = false,
102+
recommendedNextStep = "wait_then_retry",
103+
reason = "not-paired",
104+
requestId = "request-1",
105+
retryable = true,
106+
),
107+
),
108+
false,
109+
)
110+
111+
onDisconnected("Reconnecting…")
112+
113+
val reconnectDisplay = runtime.gatewayConnectionDisplay.value
114+
assertEquals("Reconnecting…", reconnectDisplay.statusText)
115+
assertEquals("PAIRING_REQUIRED", reconnectDisplay.problem?.code)
116+
assertEquals("request-1", reconnectDisplay.problem?.requestId)
117+
118+
onDisconnected("Gateway error: timeout")
119+
assertNull(runtime.gatewayConnectionDisplay.value.problem)
120+
}
121+
86122
@Test
87123
fun doesNotConnectOperatorSessionWhenOnlyBootstrapAuthExists() {
88124
assertFalse(

0 commit comments

Comments
 (0)