fix(macos): explain pairing approval in onboarding

This commit is contained in:
Nimrod Gutman
2026-03-11 13:37:19 +02:00
parent 2c98f194df
commit 00e2ad847b
3 changed files with 33 additions and 1 deletions

View File

@@ -510,6 +510,8 @@ extension OnboardingView {
return ("wrench.and.screwdriver.fill", .orange) return ("wrench.and.screwdriver.fill", .orange)
case .passwordRequired: case .passwordRequired:
return ("lock.slash.fill", .orange) return ("lock.slash.fill", .orange)
case .pairingRequired:
return ("link.badge.plus", .orange)
} }
} }

View File

@@ -7,6 +7,7 @@ enum RemoteGatewayAuthIssue: Equatable {
case tokenMismatch case tokenMismatch
case gatewayTokenNotConfigured case gatewayTokenNotConfigured
case passwordRequired case passwordRequired
case pairingRequired
init?(error: Error) { init?(error: Error) {
guard let authError = error as? GatewayConnectAuthError else { guard let authError = error as? GatewayConnectAuthError else {
@@ -21,6 +22,8 @@ enum RemoteGatewayAuthIssue: Equatable {
self = .gatewayTokenNotConfigured self = .gatewayTokenNotConfigured
case .authPasswordMissing, .authPasswordMismatch, .authPasswordNotConfigured: case .authPasswordMissing, .authPasswordMismatch, .authPasswordNotConfigured:
self = .passwordRequired self = .passwordRequired
case .pairingRequired:
self = .pairingRequired
default: default:
return nil return nil
} }
@@ -30,7 +33,7 @@ enum RemoteGatewayAuthIssue: Equatable {
switch self { switch self {
case .tokenRequired, .tokenMismatch: case .tokenRequired, .tokenMismatch:
true true
case .gatewayTokenNotConfigured, .passwordRequired: case .gatewayTokenNotConfigured, .passwordRequired, .pairingRequired:
false false
} }
} }
@@ -45,6 +48,8 @@ enum RemoteGatewayAuthIssue: Equatable {
"This gateway host needs token setup" "This gateway host needs token setup"
case .passwordRequired: case .passwordRequired:
"This gateway is using unsupported auth" "This gateway is using unsupported auth"
case .pairingRequired:
"This device needs pairing approval"
} }
} }
@@ -58,6 +63,8 @@ enum RemoteGatewayAuthIssue: Equatable {
"This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. If the gateway uses an environment variable instead, set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway." "This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. If the gateway uses an environment variable instead, set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway."
case .passwordRequired: case .passwordRequired:
"This onboarding flow does not support password auth yet. Reconfigure the gateway to use token auth, then retry." "This onboarding flow does not support password auth yet. Reconfigure the gateway to use token auth, then retry."
case .pairingRequired:
"Approve this device from an already-paired OpenClaw client. In your OpenClaw chat, run `/pair approve`, then click **Check connection** again."
} }
} }
@@ -65,6 +72,8 @@ enum RemoteGatewayAuthIssue: Equatable {
switch self { switch self {
case .tokenRequired, .gatewayTokenNotConfigured: case .tokenRequired, .gatewayTokenNotConfigured:
"No token yet? Generate one on the gateway host with `openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`." "No token yet? Generate one on the gateway host with `openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`."
case .pairingRequired:
"If you do not have another paired OpenClaw client yet, approve the pending request on the gateway host with `openclaw devices approve`."
case .tokenMismatch, .passwordRequired: case .tokenMismatch, .passwordRequired:
nil nil
} }
@@ -80,6 +89,8 @@ enum RemoteGatewayAuthIssue: Equatable {
"This gateway has token auth enabled, but no gateway.auth.token is configured on the host." "This gateway has token auth enabled, but no gateway.auth.token is configured on the host."
case .passwordRequired: case .passwordRequired:
"This gateway uses password auth. Remote onboarding on macOS cannot collect gateway passwords yet." "This gateway uses password auth. Remote onboarding on macOS cannot collect gateway passwords yet."
case .pairingRequired:
"Pairing required. In an already-paired OpenClaw client, run /pair approve, then check the connection again."
} }
} }
} }

View File

@@ -21,6 +21,10 @@ struct OnboardingRemoteAuthPromptTests {
message: "password missing", message: "password missing",
detailCode: GatewayConnectAuthDetailCode.authPasswordMissing.rawValue, detailCode: GatewayConnectAuthDetailCode.authPasswordMissing.rawValue,
canRetryWithDeviceToken: false) canRetryWithDeviceToken: false)
let pairingRequired = GatewayConnectAuthError(
message: "pairing required",
detailCode: GatewayConnectAuthDetailCode.pairingRequired.rawValue,
canRetryWithDeviceToken: false)
let unknown = GatewayConnectAuthError( let unknown = GatewayConnectAuthError(
message: "other", message: "other",
detailCode: "SOMETHING_ELSE", detailCode: "SOMETHING_ELSE",
@@ -30,6 +34,7 @@ struct OnboardingRemoteAuthPromptTests {
#expect(RemoteGatewayAuthIssue(error: tokenMismatch) == .tokenMismatch) #expect(RemoteGatewayAuthIssue(error: tokenMismatch) == .tokenMismatch)
#expect(RemoteGatewayAuthIssue(error: tokenNotConfigured) == .gatewayTokenNotConfigured) #expect(RemoteGatewayAuthIssue(error: tokenNotConfigured) == .gatewayTokenNotConfigured)
#expect(RemoteGatewayAuthIssue(error: passwordMissing) == .passwordRequired) #expect(RemoteGatewayAuthIssue(error: passwordMissing) == .passwordRequired)
#expect(RemoteGatewayAuthIssue(error: pairingRequired) == .pairingRequired)
#expect(RemoteGatewayAuthIssue(error: unknown) == nil) #expect(RemoteGatewayAuthIssue(error: unknown) == nil)
} }
@@ -83,6 +88,20 @@ struct OnboardingRemoteAuthPromptTests {
remoteToken: "", remoteToken: "",
remoteTokenUnsupported: false, remoteTokenUnsupported: false,
authIssue: .gatewayTokenNotConfigured) == false) authIssue: .gatewayTokenNotConfigured) == false)
#expect(OnboardingView.shouldShowRemoteTokenField(
showAdvancedConnection: false,
remoteToken: "",
remoteTokenUnsupported: false,
authIssue: .pairingRequired) == false)
}
@Test func `pairing required copy points users to pair approve`() {
let issue = RemoteGatewayAuthIssue.pairingRequired
#expect(issue.title == "This device needs pairing approval")
#expect(issue.body.contains("`/pair approve`"))
#expect(issue.statusMessage.contains("/pair approve"))
#expect(issue.footnote?.contains("`openclaw devices approve`") == true)
} }
@Test func `paired device success copy explains auth source`() { @Test func `paired device success copy explains auth source`() {