diff --git a/packages/experimental/cua/native/README.txt b/packages/experimental/cua/native/README.txt index 8ad7032663..7636fe0a50 100644 --- a/packages/experimental/cua/native/README.txt +++ b/packages/experimental/cua/native/README.txt @@ -30,7 +30,7 @@ Element clicks prefer advertised AX actions and otherwise use pointer delivery. The responsible host/helper needs macOS Accessibility permission for AX state and input, and Screen Recording permission for screenshots. Capability preflight and permission errors do not grant permissions. Capture refuses a window that moves or resizes while the image is being read. Unassociated system panels and accessory-only applications are not selectable. -Use `swift test --package-path packages/experimental/cua/native` for offline unit checks and `python3 packages/experimental/cua/native/tests/protocol_e2e.py` for JSONL admission, ordered replies and EOF shutdown without desktop operations. All Python native checks accept `--helper` to select a separately built comparison binary. +Use `python3 packages/experimental/cua/native/tests/test_swift.py` for offline unit checks with the selected Apple toolchain’s Swift Testing framework/runtime paths and `python3 packages/experimental/cua/native/tests/protocol_e2e.py` for JSONL admission, ordered replies and EOF shutdown without desktop operations. All Python native checks accept `--helper` to select a separately built comparison binary. Run `python3 packages/experimental/cua/native/tests/axless_e2e.py` after building and unlocking the Mac to verify an owned window whose application removes and later restores its AX roots. The check requires existing Accessibility/Screen Recording grants, rejects stale element indices, captures the AXless window, verifies a coordinate click in the fixture state file, and checks restoration. It never changes system permission settings. diff --git a/packages/experimental/cua/native/Sources/CuaNative/AXAccess.swift b/packages/experimental/cua/native/Sources/CuaNative/AXAccess.swift index e12a6d96b8..eeb8bc5f4b 100644 --- a/packages/experimental/cua/native/Sources/CuaNative/AXAccess.swift +++ b/packages/experimental/cua/native/Sources/CuaNative/AXAccess.swift @@ -61,7 +61,7 @@ final class AXAccess { } func elementValue(_ value: CFTypeRef, _ name: String) throws -> AXUIElement { guard CFGetTypeID(value) == AXUIElementGetTypeID() else { throw NativeFailure(code: "NATIVE_ERROR", message: "Accessibility \(name) is not an element") } - return unsafeBitCast(value, to: AXUIElement.self) + return unsafeDowncast(value, to: AXUIElement.self) } func element(_ element: AXUIElement, _ name: String) throws -> AXUIElement? { guard let value = try attribute(element, name) else { return nil } @@ -123,8 +123,8 @@ final class AXAccess { func bounds(_ element: AXUIElement) throws -> WindowBounds? { guard let position = try attribute(element, "AXPosition"), let size = try attribute(element, "AXSize") else { return nil } guard CFGetTypeID(position) == AXValueGetTypeID(), CFGetTypeID(size) == AXValueGetTypeID() else { return nil } - let positionValue = unsafeBitCast(position, to: AXValue.self) - let sizeValue = unsafeBitCast(size, to: AXValue.self) + let positionValue = unsafeDowncast(position, to: AXValue.self) + let sizeValue = unsafeDowncast(size, to: AXValue.self) var point = CGPoint.zero var dimensions = CGSize.zero guard AXValueGetValue(positionValue, .cgPoint, &point), AXValueGetValue(sizeValue, .cgSize, &dimensions), diff --git a/packages/experimental/cua/native/Sources/CuaNative/AXWorker.swift b/packages/experimental/cua/native/Sources/CuaNative/AXWorker.swift index e2e484ea97..d85461ccdb 100644 --- a/packages/experimental/cua/native/Sources/CuaNative/AXWorker.swift +++ b/packages/experimental/cua/native/Sources/CuaNative/AXWorker.swift @@ -183,9 +183,7 @@ actor AXWorker { try NativePlatform.requireAccessibility() if let window { _ = try NativePlatform.ownedWindow(identity.pid, window) } try cancellation.check() - let observedWindows: [WindowInfo] - if let window { observedWindows = [try NativePlatform.ownedWindow(identity.pid, window)] } - else { observedWindows = NativePlatform.windows().filter { $0.pid == identity.pid } } + let observedWindows = NativePlatform.windows().filter { $0.pid == identity.pid } let id = try identifier("snapshot") let scope = SnapshotScope(appHandle: handle, windowID: window) snapshots[scope] = NativeSnapshot(id: id, scope: scope, elements: projection.indexed) diff --git a/packages/experimental/cua/native/tests/test_swift.py b/packages/experimental/cua/native/tests/test_swift.py new file mode 100644 index 0000000000..b39e18b4e6 --- /dev/null +++ b/packages/experimental/cua/native/tests/test_swift.py @@ -0,0 +1,27 @@ +"""Run Swift Testing with the selected Apple toolchain's runtime search paths.""" +import subprocess +import sys +from pathlib import Path + + +def main(): + compiler = Path(subprocess.check_output(["xcrun", "--find", "swift"], text=True).strip()) + package = Path(__file__).resolve().parents[1] + command = [str(compiler), "test", "--package-path", str(package), + "--disable-xctest", "--enable-swift-testing"] + # Command Line Tools ships these libraries outside SwiftPM's default search + # paths. Full Xcode and other selected toolchains keep their normal lookup. + framework_root = compiler.parents[2] / "Library/Developer/Frameworks" + if (framework_root / "Testing.framework").is_dir(): + command += ["-Xswiftc", "-F", "-Xswiftc", str(framework_root), + "-Xlinker", "-F", "-Xlinker", str(framework_root), + "-Xlinker", "-rpath", "-Xlinker", str(framework_root)] + runtime = framework_root.parent / "usr/lib" + if (runtime / "lib_TestingInterop.dylib").is_file(): + command += ["-Xlinker", "-rpath", "-Xlinker", str(runtime)] + command += sys.argv[1:] + raise SystemExit(subprocess.call(command)) + + +if __name__ == "__main__": + main() diff --git a/packages/experimental/cua/package.json b/packages/experimental/cua/package.json index 37ebaa9883..a3d2c93a50 100644 --- a/packages/experimental/cua/package.json +++ b/packages/experimental/cua/package.json @@ -36,6 +36,7 @@ "native/Package.swift", "native/Sources", "native/tests/swift", + "native/tests/test_swift.py", "native/LICENSE.cua-driver", "native/upstream.json", "native/README.txt", diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index cea64bbe99..5fad97bb35 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -175,7 +175,7 @@ export function readWorkspaceManifests(repositoryRoot: string): WorkspaceManifes const packageFileExtras: Readonly> = { // CUA publishes its client API and attributed native source for local macOS builds. - '@deepseek-ai/dsh-experimental-cua': ['lib/api.js', 'lib/bridge.js', 'native/Package.swift', 'native/Sources', 'native/tests/swift', 'native/LICENSE.cua-driver', 'native/upstream.json', 'native/README.txt', 'native/protocol.schema.json'], + '@deepseek-ai/dsh-experimental-cua': ['lib/api.js', 'lib/bridge.js', 'native/Package.swift', 'native/Sources', 'native/tests/swift', 'native/tests/test_swift.py', 'native/LICENSE.cua-driver', 'native/upstream.json', 'native/README.txt', 'native/protocol.schema.json'], // Owned Worker bundles import this public bootstrap before their business entry. '@deepseek-ai/dsh-app-boot': ['lib/worker/profile-resolution-bootstrap.js'], // Statically linked client libraries keep their stylesheets next to the emitted