mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 00:52:10 -06:00
feat(macos): Computer Use provider readiness checklist (#124093)
* feat(macos): show computer provider readiness * refactor(macos): drop the status string superseded by the readiness checklist * chore(macos): refresh i18n baseline after removing the superseded status string
This commit is contained in:
committed by
GitHub
parent
2c0de878c6
commit
a4f61d135a
Generated
+128
-35
@@ -20552,6 +20552,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.1f848d6b8b8c9a5b",
|
||||
"source": "Accessibility",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.dad3a39ff4502cb0",
|
||||
"source": "Accessibility access is required to attach text from \\(appName).",
|
||||
@@ -20563,17 +20574,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.c8aea204934bc72d",
|
||||
"source": "Accessibility grant may be stale",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.8749684a8c17f426",
|
||||
"source": "Accessibility permission is required to paste.",
|
||||
@@ -22703,6 +22703,10 @@
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ChannelsSettings+View.swift"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -23174,6 +23178,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.1b206a7a4565f60b",
|
||||
"source": "CUA daemon",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.cfbd34b9f70ab403",
|
||||
"source": "Cache",
|
||||
@@ -24646,17 +24661,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.173d1e07a2d80eb6",
|
||||
"source": "Computer Control access",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.7b46ae17b22676bf",
|
||||
"source": "Computer Control provider",
|
||||
@@ -30875,14 +30879,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.a32c9e42458c9b1c",
|
||||
"source": "Grant OpenClaw in System Settings → Privacy & Security → Accessibility, then reopen it.",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call-multiline",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.cdb8cc9716727acf",
|
||||
"source": "Grant OpenClaw in System Settings → Privacy & Security → Screen Recording, then reopen it.",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call-multiline",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.a2b0395080861914",
|
||||
"source": "Granted",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift"
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
@@ -31974,6 +32000,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.cff202e57912738d",
|
||||
"source": "Keep OpenClaw open while it checks the embedded CUA daemon.",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.b2a590882480d15e",
|
||||
"source": "Keep OpenClaw visible in the Dock. When off, windows still show the Dock icon while open.",
|
||||
@@ -33892,17 +33929,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.2f563e685fcd2e50",
|
||||
"source": "Missing permission",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.b8b79ae74c5aa5ca",
|
||||
"source": "Missing: %@",
|
||||
@@ -35747,6 +35773,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.e0a495b711628049",
|
||||
"source": "Not granted",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.6141dea69ca38164",
|
||||
"source": "Not listening",
|
||||
@@ -35799,6 +35836,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.b4f3bc6fe500677b",
|
||||
"source": "Not ready",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.59d1d878b3170094",
|
||||
"source": "Not selected",
|
||||
@@ -40781,6 +40829,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.bb3054116d83e716",
|
||||
"source": "Reopen OpenClaw to retry the embedded CUA daemon.",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.d103d60717e1d702",
|
||||
"source": "Repairing the OpenClaw Gateway update…",
|
||||
@@ -42019,6 +42078,10 @@
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/ios/WatchApp/Sources/WatchInboxView.swift"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift"
|
||||
@@ -42557,6 +42620,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.9c4ba1b5e037c818",
|
||||
"source": "Screen Recording",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.f7fb49bcff01c4bb",
|
||||
"source": "Screenshot: \\(label)",
|
||||
@@ -47830,6 +47904,10 @@
|
||||
"kind": "ui-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/AboutSettings.swift"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/VoiceWakeSettings.swift"
|
||||
@@ -47868,6 +47946,10 @@
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/ios/Sources/Design/SettingsProTabSupport.swift"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"path": "apps/macos/Sources/OpenClaw/DebugSettings.swift"
|
||||
@@ -48275,6 +48357,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.9fdb78560dae9056",
|
||||
"source": "Use a packaged OpenClaw build that includes the CUA driver.",
|
||||
"surface": "apple",
|
||||
"sites": [
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"path": "apps/macos/Sources/OpenClaw/ComputerControlReadinessView.swift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "native.apple.927cfaa2ccf2e93d",
|
||||
"source": "Use a secure wss:// or Tailscale Serve Gateway, then scan a full-access setup code from the Control UI or openclaw qr and reconnect to enable settings and upgrades.",
|
||||
|
||||
@@ -341,14 +341,6 @@ struct ComputerControlPermissionSnapshot: Equatable, Sendable {
|
||||
case missing([Bucket])
|
||||
case accessibilityGrantMayBeStale
|
||||
|
||||
var statusText: String {
|
||||
switch self {
|
||||
case .granted: "Granted"
|
||||
case .missing: "Missing permission"
|
||||
case .accessibilityGrantMayBeStale: "Accessibility grant may be stale"
|
||||
}
|
||||
}
|
||||
|
||||
var detailText: String {
|
||||
switch self {
|
||||
case .granted:
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import AppKit
|
||||
import OpenClawIPC
|
||||
import SwiftUI
|
||||
|
||||
struct ComputerControlReadinessRow: Equatable, Identifiable {
|
||||
enum ID: Hashable {
|
||||
case provider
|
||||
case accessibility
|
||||
case screenRecording
|
||||
case cuaDaemon
|
||||
}
|
||||
|
||||
enum Status: Equatable {
|
||||
case available
|
||||
case granted
|
||||
case running
|
||||
case unavailable
|
||||
case notGranted
|
||||
case notReady
|
||||
case unknown
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .available: String(localized: "Available")
|
||||
case .granted: String(localized: "Granted")
|
||||
case .running: String(localized: "Running")
|
||||
case .unavailable: String(localized: "Unavailable")
|
||||
case .notGranted: String(localized: "Not granted")
|
||||
case .notReady: String(localized: "Not ready")
|
||||
case .unknown: String(localized: "Unknown")
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .available, .granted, .running: "checkmark.circle"
|
||||
case .unavailable, .notGranted, .notReady: "exclamationmark.circle"
|
||||
case .unknown: "questionmark.circle"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .available, .granted, .running: .green
|
||||
case .unavailable, .notGranted, .notReady: .orange
|
||||
case .unknown: .secondary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id: ID
|
||||
let title: String
|
||||
let status: Status
|
||||
let nextStep: String?
|
||||
}
|
||||
|
||||
enum ComputerControlReadinessPresentation {
|
||||
static func rows(
|
||||
provider: ComputerControlProvider,
|
||||
cuaDriverAvailable: Bool,
|
||||
permissions: [Capability: CapabilityAuthorizationStatus],
|
||||
cuaDaemonReady: Bool?) -> [ComputerControlReadinessRow]
|
||||
{
|
||||
let providerAvailable = provider == .peekaboo || cuaDriverAvailable
|
||||
var rows = [
|
||||
ComputerControlReadinessRow(
|
||||
id: .provider,
|
||||
title: provider.displayName,
|
||||
status: providerAvailable ? .available : .unavailable,
|
||||
nextStep: providerAvailable
|
||||
? nil
|
||||
: String(localized: "Use a packaged OpenClaw build that includes the CUA driver.")),
|
||||
self.permissionRow(
|
||||
id: .accessibility,
|
||||
title: String(localized: "Accessibility"),
|
||||
status: permissions[.accessibility],
|
||||
nextStep: String(
|
||||
localized: """
|
||||
Grant OpenClaw in System Settings → Privacy & Security → Accessibility, then reopen it.
|
||||
""")),
|
||||
self.permissionRow(
|
||||
id: .screenRecording,
|
||||
title: String(localized: "Screen Recording"),
|
||||
status: permissions[.screenRecording],
|
||||
nextStep: String(
|
||||
localized: """
|
||||
Grant OpenClaw in System Settings → Privacy & Security → Screen Recording, then reopen it.
|
||||
""")),
|
||||
]
|
||||
|
||||
if provider == .cua {
|
||||
let status: ComputerControlReadinessRow.Status = if !cuaDriverAvailable {
|
||||
.unavailable
|
||||
} else if let cuaDaemonReady {
|
||||
cuaDaemonReady ? .running : .notReady
|
||||
} else {
|
||||
.unknown
|
||||
}
|
||||
let nextStep: String? = switch status {
|
||||
case .running:
|
||||
nil
|
||||
case .unavailable:
|
||||
String(localized: "Use a packaged OpenClaw build that includes the CUA driver.")
|
||||
case .notReady:
|
||||
String(localized: "Reopen OpenClaw to retry the embedded CUA daemon.")
|
||||
case .unknown:
|
||||
String(localized: "Keep OpenClaw open while it checks the embedded CUA daemon.")
|
||||
case .available, .granted, .notGranted:
|
||||
nil
|
||||
}
|
||||
rows.append(ComputerControlReadinessRow(
|
||||
id: .cuaDaemon,
|
||||
title: String(localized: "CUA daemon"),
|
||||
status: status,
|
||||
nextStep: nextStep))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
private static func permissionRow(
|
||||
id: ComputerControlReadinessRow.ID,
|
||||
title: String,
|
||||
status: CapabilityAuthorizationStatus?,
|
||||
nextStep: String) -> ComputerControlReadinessRow
|
||||
{
|
||||
switch status ?? .unknown {
|
||||
case .granted:
|
||||
ComputerControlReadinessRow(id: id, title: title, status: .granted, nextStep: nil)
|
||||
case .notGranted:
|
||||
ComputerControlReadinessRow(id: id, title: title, status: .notGranted, nextStep: nextStep)
|
||||
case .unknown:
|
||||
ComputerControlReadinessRow(id: id, title: title, status: .unknown, nextStep: nextStep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct ComputerControlReadinessView: View {
|
||||
let provider: ComputerControlProvider
|
||||
let cuaDriverAvailable: Bool
|
||||
|
||||
@State private var permissions: [Capability: CapabilityAuthorizationStatus] = [:]
|
||||
@State private var cuaDaemonReady: Bool?
|
||||
|
||||
init(provider: ComputerControlProvider, cuaDriverAvailable: Bool) {
|
||||
self.provider = provider
|
||||
self.cuaDriverAvailable = cuaDriverAvailable
|
||||
self._cuaDaemonReady = State(initialValue: Self.cuaDaemonReadiness(
|
||||
provider: provider,
|
||||
cuaDriverAvailable: cuaDriverAvailable))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ForEach(self.rows) { row in
|
||||
SettingsCardRow(
|
||||
title: .verbatim(row.title),
|
||||
subtitle: row.nextStep.map(SettingsTextValue.verbatim))
|
||||
{
|
||||
Label {
|
||||
Text(verbatim: row.status.text)
|
||||
} icon: {
|
||||
Image(systemName: row.status.icon)
|
||||
}
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(row.status.color)
|
||||
}
|
||||
}
|
||||
.task(id: self.provider) {
|
||||
await self.refreshPermissions()
|
||||
self.refreshCuaDaemonReadiness()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
Task { await self.refreshPermissions() }
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .openclawPermissionsChanged)) { _ in
|
||||
Task { await self.refreshPermissions() }
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .openclawCuaDriverAvailabilityChanged)) { _ in
|
||||
self.refreshCuaDaemonReadiness()
|
||||
}
|
||||
}
|
||||
|
||||
private var rows: [ComputerControlReadinessRow] {
|
||||
ComputerControlReadinessPresentation.rows(
|
||||
provider: self.provider,
|
||||
cuaDriverAvailable: self.cuaDriverAvailable,
|
||||
permissions: self.permissions,
|
||||
cuaDaemonReady: self.cuaDaemonReady)
|
||||
}
|
||||
|
||||
private func refreshPermissions() async {
|
||||
self.permissions = await PermissionManager.authorizationStatus([.accessibility, .screenRecording])
|
||||
}
|
||||
|
||||
private func refreshCuaDaemonReadiness() {
|
||||
self.cuaDaemonReady = Self.cuaDaemonReadiness(
|
||||
provider: self.provider,
|
||||
cuaDriverAvailable: self.cuaDriverAvailable)
|
||||
}
|
||||
|
||||
private static func cuaDaemonReadiness(
|
||||
provider: ComputerControlProvider,
|
||||
cuaDriverAvailable: Bool) -> Bool?
|
||||
{
|
||||
guard provider == .cua, cuaDriverAvailable else { return nil }
|
||||
return CuaDriverHostCoordinator.shared.workerEndpoint != nil
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ struct GeneralSettings: View {
|
||||
@State private var gatewayStatus: GatewayEnvironmentStatus = .checking
|
||||
@State private var remoteStatus: RemoteStatus = .idle
|
||||
@State private var showRemoteAdvanced = false
|
||||
@State private var computerControlPermissions = ComputerControlPermissionSnapshot.probe()
|
||||
@State private var cookieSyncManager = CookieSyncManager.shared
|
||||
private let isPreview = ProcessInfo.processInfo.isPreview
|
||||
private var isNixMode: Bool {
|
||||
@@ -74,12 +73,6 @@ struct GeneralSettings: View {
|
||||
.onChange(of: self.computerControlProviderRaw) { _, _ in
|
||||
self.state.applyComputerControlHostState()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
self.refreshComputerControlPermissions()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .openclawPermissionsChanged)) { _ in
|
||||
self.refreshComputerControlPermissions()
|
||||
}
|
||||
.onDisappear { self.gatewayDiscovery.stop() }
|
||||
}
|
||||
|
||||
@@ -148,17 +141,10 @@ struct GeneralSettings: View {
|
||||
|
||||
self.computerControlProviderRow
|
||||
|
||||
SettingsCardRow(
|
||||
title: "Computer Control access",
|
||||
subtitle: .verbatim(self.computerControlPermissions.diagnostic.detailText))
|
||||
{
|
||||
Label {
|
||||
Text(verbatim: self.computerControlPermissions.diagnostic.statusText)
|
||||
} icon: {
|
||||
Image(systemName: self.computerControlPermissionIcon)
|
||||
}
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(self.computerControlPermissionColor)
|
||||
if self.computerControlEnabled {
|
||||
ComputerControlReadinessView(
|
||||
provider: self.selectedComputerControlProvider,
|
||||
cuaDriverAvailable: self.cuaDriverBundled)
|
||||
}
|
||||
|
||||
SettingsCardToggleRow(
|
||||
@@ -377,7 +363,6 @@ struct GeneralSettings: View {
|
||||
private func updateActiveWork(active: Bool) {
|
||||
guard !self.isPreview else { return }
|
||||
if active {
|
||||
self.refreshComputerControlPermissions()
|
||||
self.refreshGatewayStatus()
|
||||
if self.page == .connection {
|
||||
self.gatewayDiscovery.start()
|
||||
@@ -387,26 +372,6 @@ struct GeneralSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshComputerControlPermissions() {
|
||||
guard self.page == .general, self.isActive, !self.isPreview else { return }
|
||||
self.computerControlPermissions = .probe()
|
||||
}
|
||||
|
||||
private var computerControlPermissionIcon: String {
|
||||
switch self.computerControlPermissions.diagnostic {
|
||||
case .granted: "checkmark.circle.fill"
|
||||
case .missing: "exclamationmark.circle.fill"
|
||||
case .accessibilityGrantMayBeStale: "exclamationmark.triangle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
private var computerControlPermissionColor: Color {
|
||||
switch self.computerControlPermissions.diagnostic {
|
||||
case .granted: .green
|
||||
case .missing, .accessibilityGrantMayBeStale: .orange
|
||||
}
|
||||
}
|
||||
|
||||
private var connectionStatusPanel: some View {
|
||||
HStack(alignment: .center, spacing: 14) {
|
||||
ZStack {
|
||||
@@ -906,12 +871,13 @@ extension GeneralSettings {
|
||||
CuaDriverArtifact.bundledExecutableURL != nil
|
||||
}
|
||||
|
||||
private var selectedComputerControlProvider: ComputerControlProvider {
|
||||
ComputerControlProvider.current()
|
||||
}
|
||||
|
||||
private var computerControlProviderBinding: Binding<ComputerControlProvider> {
|
||||
Binding(
|
||||
get: {
|
||||
let selected = ComputerControlProvider(rawValue: self.computerControlProviderRaw) ?? .peekaboo
|
||||
return selected == .cua && !self.cuaDriverBundled ? .peekaboo : selected
|
||||
},
|
||||
get: { self.selectedComputerControlProvider },
|
||||
set: { provider in
|
||||
guard provider != .cua || self.cuaDriverBundled else { return }
|
||||
self.computerControlProviderRaw = provider.rawValue
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import OpenClawIPC
|
||||
import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
@@ -62,4 +63,68 @@ struct ComputerControlSettingsTests {
|
||||
#expect(decoded["socketPath"] as? String == endpoint.socketPath)
|
||||
#expect(decoded["binaryPath"] as? String == endpoint.binaryPath)
|
||||
}
|
||||
|
||||
@Test func `readiness rows never promote unknown or unavailable state`() {
|
||||
struct Scenario {
|
||||
let provider: ComputerControlProvider
|
||||
let cuaDriverAvailable: Bool
|
||||
let permissions: [Capability: CapabilityAuthorizationStatus]
|
||||
let cuaDaemonReady: Bool?
|
||||
let expectedStatuses: [ComputerControlReadinessRow.Status]
|
||||
}
|
||||
|
||||
let granted: [Capability: CapabilityAuthorizationStatus] = [
|
||||
.accessibility: .granted,
|
||||
.screenRecording: .granted,
|
||||
]
|
||||
let scenarios = [
|
||||
Scenario(
|
||||
provider: .peekaboo,
|
||||
cuaDriverAvailable: false,
|
||||
permissions: granted,
|
||||
cuaDaemonReady: nil,
|
||||
expectedStatuses: [.available, .granted, .granted]),
|
||||
Scenario(
|
||||
provider: .cua,
|
||||
cuaDriverAvailable: true,
|
||||
permissions: granted,
|
||||
cuaDaemonReady: true,
|
||||
expectedStatuses: [.available, .granted, .granted, .running]),
|
||||
Scenario(
|
||||
provider: .cua,
|
||||
cuaDriverAvailable: true,
|
||||
permissions: [:],
|
||||
cuaDaemonReady: nil,
|
||||
expectedStatuses: [.available, .unknown, .unknown, .unknown]),
|
||||
Scenario(
|
||||
provider: .cua,
|
||||
cuaDriverAvailable: true,
|
||||
permissions: [.accessibility: .notGranted, .screenRecording: .notGranted],
|
||||
cuaDaemonReady: false,
|
||||
expectedStatuses: [.available, .notGranted, .notGranted, .notReady]),
|
||||
Scenario(
|
||||
provider: .cua,
|
||||
cuaDriverAvailable: false,
|
||||
permissions: granted,
|
||||
cuaDaemonReady: true,
|
||||
expectedStatuses: [.unavailable, .granted, .granted, .unavailable]),
|
||||
]
|
||||
|
||||
for scenario in scenarios {
|
||||
let rows = ComputerControlReadinessPresentation.rows(
|
||||
provider: scenario.provider,
|
||||
cuaDriverAvailable: scenario.cuaDriverAvailable,
|
||||
permissions: scenario.permissions,
|
||||
cuaDaemonReady: scenario.cuaDaemonReady)
|
||||
#expect(rows.map(\.status) == scenario.expectedStatuses)
|
||||
for row in rows {
|
||||
switch row.status {
|
||||
case .available, .granted, .running:
|
||||
#expect(row.nextStep == nil)
|
||||
case .unavailable, .notGranted, .notReady, .unknown:
|
||||
#expect(row.nextStep?.isEmpty == false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user