Files
claude-code-haha/native/cu-helper/Tests/CuHelperTests/OffScreenTargetTests.swift
T
程序员阿江(Relakkes) b8a90626ce feat(computer-use): native macOS engine, on top of main and nothing else
Rebuilt against main so the branch carries the Computer Use work and no other
divergence. Three unrelated efforts had been sitting uncommitted in this
worktree and were swept into an earlier commit; they are preserved on
cu-worktree-full-backup and belong on their own branches — adapter control
credentials, Electron asar sealing, and the sidecar code-loading audit. Every
file outside Computer Use now matches main exactly.

The engine
  A Swift helper drives apps through the accessibility tree, with coordinate
  actuation for the Chromium and Electron apps whose tree is a bare window
  frame. Ten primitives matching the shape Codex uses, so an app's guidance and
  the model's habits transfer.

  Coordinate actions resolve their target window once and refuse when none can
  be named. The unbound event they used to fall back to is discarded by custom
  renderers, so a minimized target produced a whole session of "Action
  completed" with nothing behind it.

  Input acceptance is established for typing and key presses as well as clicks:
  each MCP call is seconds apart, so the keyboard cannot inherit the focus a
  click established. The synthetic focus notification is gated on the target
  not already being active — sent unconditionally it names window 0 at an app
  that already owns a key window, and nine window-bound clicks were discarded
  with the traffic lights fully lit.

State the model can trust
  An off-screen target says so, and says which tools still reach it: element
  actions need no on-screen geometry, so an app with a real tree can still be
  driven from the Dock. A fully covered window is recovered once, then left
  alone — burying it again is the user wanting their screen back. A repeated
  capture is reported with the cause that actually applies rather than both,
  because coverage is something we compute.

Signing
  The helper is signed under a stable identity before electron-builder sees it,
  and excluded from re-signing: macOS ties Accessibility and Screen Recording
  grants to the signing identity, so rotating it drops both on every update.

Discoverability
  The desktop slash menu falls back to a directory scan while a session's CLI
  has not started, which is when the menu is first opened. Built-ins and
  bundled skills live in the binary, so /computer-use was absent until after
  the first message.
2026-08-05 22:06:23 +08:00

145 lines
5.9 KiB
Swift

import CoreGraphics
import XCTest
@testable import cc_haha_computer_use
/// A minimized app produced a real screenshot, a real accessibility tree, and
/// "Action completed" for every click and keystroke of an entire session, while
/// nothing whatsoever happened. Two separate defects combined to hide it:
/// capture accepts off-screen windows, actuation cannot use them, and the gap
/// between the two was a silent fallback to an unbound event that custom
/// renderers discard.
///
/// These tests hold both halves of the fix in place: the refusal, and the fact
/// that the model is told why.
final class OffScreenTargetTests: XCTestCase {
private let point = CGPoint(x: 410, y: 50)
private func windowList(
pid: pid_t,
bounds: CGRect,
layer: Int = 0
) -> () -> [[CFString: Any]]? {
{
[[
kCGWindowLayer: layer,
kCGWindowNumber: 4242,
kCGWindowOwnerPID: pid,
kCGWindowBounds: [
"X": bounds.minX,
"Y": bounds.minY,
"Width": bounds.width,
"Height": bounds.height,
] as [String: CGFloat],
]]
}
}
// MARK: - The distinction that was missing
func testAnAppWithNoOnScreenWindowIsReportedAsOffScreen() {
// The whole failure: the window server lists on-screen windows only, so
// a minimized target simply is not there. Reported as "your coordinate
// missed", the model retries other coordinates forever.
let result = WindowGeometry.binding(at: point, pid: 501, windowList: { [] })
XCTAssertEqual(result, .failure(.noWindowOnScreen))
}
func testAMissedCoordinateIsNotReportedAsOffScreen() {
// The app IS on screen; the point just falls outside its window. Here
// re-reading coordinates is exactly the right move, so the two cases
// must not collapse into one message.
let result = WindowGeometry.binding(
at: point,
pid: 501,
windowList: windowList(pid: 501, bounds: CGRect(x: 800, y: 400, width: 300, height: 200))
)
XCTAssertEqual(result, .failure(.pointOutsideWindows))
}
func testAWindowUnderThePointBinds() {
let result = WindowGeometry.binding(
at: point,
pid: 501,
windowList: windowList(pid: 501, bounds: CGRect(x: 0, y: 0, width: 1000, height: 800))
)
XCTAssertEqual(try? result.get().id, 4242)
}
func testAnotherAppsWindowUnderThePointDoesNotCount() {
// Binding an event to a window the target does not own addresses it to
// the wrong process, which is worse than refusing.
let result = WindowGeometry.binding(
at: point,
pid: 501,
windowList: windowList(pid: 999, bounds: CGRect(x: 0, y: 0, width: 1000, height: 800))
)
XCTAssertEqual(result, .failure(.noWindowOnScreen))
}
func testOverlayLayersDoNotMakeAnAppLookReachable() {
// Layer 0 only. An app whose only surface is a panel or overlay cannot
// receive an app-directed click, so it is off screen for our purposes.
XCTAssertFalse(
WindowGeometry.hasWindowOnScreen(
pid: 501,
windowList: windowList(
pid: 501,
bounds: CGRect(x: 0, y: 0, width: 100, height: 100),
layer: 25
)
)
)
}
func testAZeroSizedWindowDoesNotCountAsOnScreen() {
XCTAssertFalse(
WindowGeometry.hasWindowOnScreen(
pid: 501,
windowList: windowList(pid: 501, bounds: .zero)
)
)
}
// MARK: - What the model is told
func testTheModelIsToldWhenTheTargetCannotBeActedOn() {
let notice = OffScreenTargetAdvice.noticeIfUnreachable(hasWindowOnScreen: false)
XCTAssertNotNil(notice)
// The model's instinct on a dead click is to try different coordinates.
// Against an off-screen window that is an infinite loop, so the notice
// has to close it explicitly rather than merely describe the state.
XCTAssertTrue(notice!.contains("no window on screen"))
XCTAssertTrue(notice!.contains("Do not retry with different coordinates"))
// And it must name the way out, or the model has nothing to do next.
XCTAssertTrue(notice!.contains("ask the user to bring it"))
}
func testTheNoticeKeepsElementActionsOpen() {
// Only coordinate actuation needs on-screen geometry — it is the only
// half that hit-tests. Element actions address a node directly and work
// on a window in the Dock, which IS the background automation this
// feature exists to provide.
//
// The first draft of this notice said "NOTHING can be clicked or typed".
// That reads as a tidy warning and is a capability switch: the model
// would stop at the exact moment it should reach for element_index.
let notice = try! XCTUnwrap(
OffScreenTargetAdvice.noticeIfUnreachable(hasWindowOnScreen: false)
)
XCTAssertTrue(notice.contains("Element actions still work"))
for tool in ["element_index", "set_value", "select_text", "perform_secondary_action"] {
XCTAssertTrue(notice.contains(tool), "\(tool) must be named as still usable")
}
// And it must not overclaim in the other direction either: a shell tree
// has no elements to address, so that case needs its own sentence.
XCTAssertTrue(notice.contains("bare shell"))
}
func testNothingIsSaidWhenTheTargetIsReachable() {
// Every get_app_state carries this check; a notice on the normal path
// would be noise in every single turn.
XCTAssertNil(OffScreenTargetAdvice.noticeIfUnreachable(hasWindowOnScreen: true))
}
}