agent-device 0.20.5 → 0.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +2 -0
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.5.apk → agent-device-android-ime-helper-0.20.6.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.6.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.5.manifest.json → agent-device-android-ime-helper-0.20.6.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.5.apk → agent-device-android-snapshot-helper-0.20.6.apk} +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.6.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.5.manifest.json → agent-device-android-snapshot-helper-0.20.6.manifest.json} +6 -6
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +28 -0
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +41 -1
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +298 -92
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +126 -187
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.h +32 -0
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.m +212 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +118 -8
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +134 -27
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +23 -5
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +10 -0
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +0 -67
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +11 -1
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +2 -22
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +48 -11
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +0 -20
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +177 -0
  25. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +105 -448
  26. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryPolicyTests.swift +4 -0
  27. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +587 -0
  28. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +18 -0
  29. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h +72 -0
  30. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.m +89 -0
  31. package/dist/src/agent-device-client.js +2 -2
  32. package/dist/src/android.js +1 -1
  33. package/dist/src/app-log-request-scope.js +1 -1
  34. package/dist/src/app-resolution.js +1 -1
  35. package/dist/src/apps.js +5 -5
  36. package/dist/src/cli-config.js +1 -1
  37. package/dist/src/cli-help.js +34 -24
  38. package/dist/src/cli-runner.js +1 -1
  39. package/dist/src/cli.js +15 -15
  40. package/dist/src/command-metadata.js +1 -1
  41. package/dist/src/command-schema.js +3 -3
  42. package/dist/src/context.js +1 -1
  43. package/dist/src/debug-symbols.js +1 -1
  44. package/dist/src/find.js +1 -1
  45. package/dist/src/finders.js +1 -1
  46. package/dist/src/generic-settle.js +1 -0
  47. package/dist/src/generic.js +1 -1
  48. package/dist/src/ime-lifecycle.js +3 -3
  49. package/dist/src/index.d.ts +27 -7
  50. package/dist/src/input-actions.js +4 -4
  51. package/dist/src/interaction-runtime.js +1 -0
  52. package/dist/src/interaction-snapshot.js +1 -1
  53. package/dist/src/interaction-touch-reference-frame.js +1 -0
  54. package/dist/src/interaction.js +1 -1
  55. package/dist/src/interactor.js +2 -2
  56. package/dist/src/interactor2.js +1 -1
  57. package/dist/src/internal/bin.js +2 -2
  58. package/dist/src/internal/daemon.js +15 -15
  59. package/dist/src/lease-context.js +1 -1
  60. package/dist/src/lease.js +1 -1
  61. package/dist/src/limrun.d.ts +23 -2
  62. package/dist/src/linux.js +1 -1
  63. package/dist/src/process-exit.js +1 -0
  64. package/dist/src/react-native.js +1 -1
  65. package/dist/src/record-trace.js +1 -1
  66. package/dist/src/register-builtins.js +1 -1
  67. package/dist/src/registry.js +25 -24
  68. package/dist/src/remote-config.js +1 -1
  69. package/dist/src/remote-config2.js +1 -1
  70. package/dist/src/reporting.js +5 -5
  71. package/dist/src/runner-client.js +1 -1
  72. package/dist/src/runner-disposal.js +1 -1
  73. package/dist/src/runner-lease.js +1 -1
  74. package/dist/src/runtime.js +3 -3
  75. package/dist/src/screenshot-geometry.js +1 -1
  76. package/dist/src/screenshot-result.js +4 -4
  77. package/dist/src/sdk-android-adb.d.ts +20 -2
  78. package/dist/src/sdk-android-adb.js +1 -1
  79. package/dist/src/sdk-batch-runner.js +3 -3
  80. package/dist/src/sdk-contracts.d.ts +17 -2
  81. package/dist/src/sdk-finders.d.ts +4 -20
  82. package/dist/src/sdk-finders.js +1 -1
  83. package/dist/src/{interaction.d.ts → sdk-selectors.d.ts} +98 -3
  84. package/dist/src/sdk-selectors.js +1 -1
  85. package/dist/src/selector-runtime.js +1 -1
  86. package/dist/src/selector-vocabulary.js +1 -1
  87. package/dist/src/selectors.d.ts +1 -46
  88. package/dist/src/selectors.js +1 -1
  89. package/dist/src/server.js +3 -3
  90. package/dist/src/session-target-evidence.js +1 -1
  91. package/dist/src/session.js +9 -9
  92. package/dist/src/snapshot2.js +1 -1
  93. package/dist/src/text-surface.js +1 -0
  94. package/dist/src/viewport-dimension.js +13 -12
  95. package/dist/src/web.js +1 -1
  96. package/package.json +41 -35
  97. package/server.json +3 -3
  98. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.5.apk.sha256 +0 -1
  99. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.5.apk.sha256 +0 -1
  100. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -225
@@ -144,6 +144,41 @@ extension RunnerTests {
144
144
  return pending
145
145
  }
146
146
 
147
+ /// The pre-seeded first-failure a penalized plan stamps into its verdict. The deferred case
148
+ /// uses the dedicated 'deferred' code: the breaker pre-selected the backend, nothing new
149
+ /// degraded on this capture, and the daemon keys warning suppression and the settle budget
150
+ /// reset off exactly that distinction. The bounded probe keeps 'budget': its short XCTest
151
+ /// slice genuinely constrains what this capture could read.
152
+ static func xcTestChannelStateFirstFailure(
153
+ _ state: SnapshotXCTestChannelPlanState
154
+ ) -> (reason: String, code: String)? {
155
+ switch state {
156
+ case .normal:
157
+ return nil
158
+ case .deferredToIndependentBackend:
159
+ return (
160
+ "XCTest-backed snapshot tiers were deferred after recent slow accessibility work on this screen",
161
+ "deferred"
162
+ )
163
+ case .boundedXCTestProbe:
164
+ return (
165
+ "XCTest-backed snapshot tiers are running with a short recovery probe after recent slow accessibility work on this screen",
166
+ "budget"
167
+ )
168
+ }
169
+ }
170
+
171
+ /// Pure gate: a capture is planned as penalized when the channel penalty is
172
+ /// active OR the daemon pinned the private-AX backend (same-backend evidence
173
+ /// probe) — both mean "do not enter XCTest tree work first, and stamp the
174
+ /// pre-selection as 'deferred' rather than a degradation".
175
+ static func snapshotXCTestChannelTreatedAsPenalized(
176
+ penalized: Bool,
177
+ preferredBackend: String?
178
+ ) -> Bool {
179
+ penalized || preferredBackend == SnapshotBackendKind.privateAX.rawValue
180
+ }
181
+
147
182
  /// Pure plan-reorder rule: a penalized XCTest accessibility channel uses independent backends
148
183
  /// when the platform has one, otherwise it keeps XCTest work on a short probe. The raw
149
184
  /// diagnostic plan keeps tree-first errors, and unknown plans are left untouched.
@@ -198,9 +233,15 @@ extension RunnerTests {
198
233
  // Reorder is iOS-only because hostile screens can make XCTest tree/query work grind while
199
234
  // the app remains visually responsive. Simulators can avoid that channel through private AX;
200
235
  // physical devices have no independent semantic backend yet, so they use a bounded probe.
236
+ // A daemon-preferred private-AX capture (same-backend evidence probe) takes the exact
237
+ // penalized route: privateAX-first plan, 'deferred' verdict — the backend was pre-selected
238
+ // deliberately, so no degradation warning should render for it.
201
239
  var xCTestChannelPenalized = false
202
240
  #if os(iOS)
203
- xCTestChannelPenalized = isSnapshotXCTestChannelPenalized(bundleId: currentBundleId)
241
+ xCTestChannelPenalized = Self.snapshotXCTestChannelTreatedAsPenalized(
242
+ penalized: isSnapshotXCTestChannelPenalized(bundleId: currentBundleId),
243
+ preferredBackend: options.preferredBackend
244
+ )
204
245
  #endif
205
246
  let effective = Self.effectiveSnapshotCapturePlan(
206
247
  plan,
@@ -208,20 +249,13 @@ extension RunnerTests {
208
249
  availableBackends: Set(SnapshotBackendKind.allCases.filter(\.isAvailableOnCurrentPlatform))
209
250
  )
210
251
  let effectivePlan = effective.plan
252
+ firstFailure = Self.xcTestChannelStateFirstFailure(effective.xCTestChannelState)
211
253
  switch effective.xCTestChannelState {
212
254
  case .normal:
213
255
  break
214
256
  case .deferredToIndependentBackend:
215
- firstFailure = (
216
- "XCTest-backed snapshot tiers were deferred after recent slow accessibility work on this screen",
217
- "budget"
218
- )
219
257
  NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_XCTEST_CHANNEL_DEFERRED bundle=%@", currentBundleId ?? "")
220
258
  case .boundedXCTestProbe:
221
- firstFailure = (
222
- "XCTest-backed snapshot tiers are running with a short recovery probe after recent slow accessibility work on this screen",
223
- "budget"
224
- )
225
259
  NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_XCTEST_CHANNEL_PROBE_BOUNDED bundle=%@", currentBundleId ?? "")
226
260
  }
227
261
 
@@ -519,7 +553,7 @@ extension RunnerTests {
519
553
  guard quality.state != "healthy" || quality.collapsedLeafIndexes != nil else { return nil }
520
554
  var parts: [String] = []
521
555
  if quality.state == "recovered" {
522
- let meaning = quality.reasonCode == "budget"
556
+ let meaning = quality.reasonCode == "budget" || quality.reasonCode == "deferred"
523
557
  ? " The primary capture ran out of its time budget (busy app or simulator); the recovered tree is authoritative for this screen."
524
558
  : " This usually means the app publishes an unhealthy accessibility tree — fixing the app's accessibility is the real cure. Treat screenshot as visual truth when this warning appears."
525
559
  parts.append(
@@ -536,8 +570,11 @@ extension RunnerTests {
536
570
  )
537
571
  }
538
572
  if let depth = quality.effectiveDepth {
573
+ // No --depth remedy here: an explicit --depth capture disables the
574
+ // frontier extension, so following it would return strictly less than
575
+ // this capture did. A plain re-run retries with a fresh extension budget.
539
576
  parts.append(
540
- "The accessibility server rejected deeper requests; this tree is capped at depth \(depth) — re-run with --depth \(depth) --scope <container> for deeper content."
577
+ "The accessibility server rejected deeper requests; content below depth \(depth) may be missing — re-run snapshot to retry deeper content."
541
578
  )
542
579
  }
543
580
  return parts.isEmpty ? nil : parts.joined(separator: " ")
@@ -1,24 +1,4 @@
1
1
  import XCTest
2
2
 
3
3
  extension RunnerTests {
4
- func testSnapshotTraversalIdentityPreservesSameOriginNodesWithDifferentBounds() {
5
- let wrapper = Self.snapshotTraversalIdentity(
6
- elementType: .other,
7
- label: "Article",
8
- identifier: "",
9
- frame: CGRect(x: 0, y: 97, width: 393, height: 48)
10
- )
11
- let leaf = Self.snapshotTraversalIdentity(
12
- elementType: .other,
13
- label: "Article",
14
- identifier: "",
15
- frame: CGRect(x: 0, y: 97, width: 131, height: 48)
16
- )
17
-
18
- #if os(iOS)
19
- XCTAssertNotEqual(wrapper, leaf)
20
- #else
21
- XCTAssertEqual(wrapper, leaf)
22
- #endif
23
- }
24
4
  }
@@ -0,0 +1,177 @@
1
+ import XCTest
2
+
3
+ // AX-independent text synthesis: private-XCTest boundary, pacing plan, and route policy.
4
+ // The verified XCUIElement path remains in TextEntry/TextTyping.
5
+ extension RunnerTests {
6
+ enum SynthesizedReplacementRouteOutcome {
7
+ case notApplicable
8
+ case completed(TextEntryResult)
9
+ case fallback(TextEntryTarget)
10
+ }
11
+
12
+ struct SynthesizedReplacementRequest {
13
+ let app: XCUIApplication
14
+ let target: TextEntryTarget
15
+ let text: String
16
+ let delaySeconds: Double
17
+ let synthesizer: any TextEntrySynthesizing
18
+ let commandId: String?
19
+ let startedAt: Date
20
+ }
21
+
22
+ enum SynthesizedTextEntryAction: Equatable {
23
+ case continueTyping
24
+ case fallback
25
+ case raise(String?)
26
+ }
27
+
28
+ protocol TextEntrySynthesizing {
29
+ func enterText(
30
+ app: XCUIApplication,
31
+ text: String,
32
+ replacingExistingText: Bool
33
+ ) -> SynthesizedTextEntryAction
34
+ }
35
+
36
+ struct PrivateXCTestTextEntrySynthesizer: TextEntrySynthesizing {
37
+ func enterText(
38
+ app: XCUIApplication,
39
+ text: String,
40
+ replacingExistingText: Bool
41
+ ) -> SynthesizedTextEntryAction {
42
+ #if os(iOS)
43
+ let result = replacingExistingText
44
+ ? RunnerSynthesizedTextEntry.replaceText(withApplication: app, text: text)
45
+ : RunnerSynthesizedTextEntry.synthesizeText(withApplication: app, text: text)
46
+ return Self.action(status: result.status, message: result.message)
47
+ #else
48
+ return .fallback
49
+ #endif
50
+ }
51
+
52
+ #if os(iOS)
53
+ static func action(
54
+ status: RunnerSynthesizedTextEntryStatus,
55
+ message: String?
56
+ ) -> SynthesizedTextEntryAction {
57
+ switch status {
58
+ case .succeeded:
59
+ return .continueTyping
60
+ case .unavailable:
61
+ return .fallback
62
+ case .failed:
63
+ return .raise(message)
64
+ @unknown default:
65
+ return .raise(message)
66
+ }
67
+ }
68
+ #endif
69
+ }
70
+
71
+ struct SynthesizedReplacementStep: Equatable {
72
+ let text: String
73
+ let replacesExistingText: Bool
74
+ }
75
+
76
+ static func synthesizedReplacementSteps(
77
+ text: String,
78
+ delaySeconds: Double
79
+ ) -> [SynthesizedReplacementStep] {
80
+ let characters = Array(text)
81
+ guard delaySeconds > 0, characters.count > 1 else {
82
+ return [SynthesizedReplacementStep(text: text, replacesExistingText: true)]
83
+ }
84
+ return characters.enumerated().map { index, character in
85
+ SynthesizedReplacementStep(
86
+ text: String(character),
87
+ replacesExistingText: index == 0
88
+ )
89
+ }
90
+ }
91
+
92
+ func runSynthesizedReplacementRoute(
93
+ _ request: SynthesizedReplacementRequest
94
+ ) -> SynthesizedReplacementRouteOutcome {
95
+ #if os(iOS)
96
+ NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder-replacement")
97
+ let steps = Self.synthesizedReplacementSteps(
98
+ text: request.text,
99
+ delaySeconds: request.delaySeconds
100
+ )
101
+ for (index, step) in steps.enumerated() {
102
+ switch request.synthesizer.enterText(
103
+ app: request.app,
104
+ text: step.text,
105
+ replacingExistingText: step.replacesExistingText
106
+ ) {
107
+ case .fallback:
108
+ NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
109
+ guard let point = request.target.refreshPoint else { return .notApplicable }
110
+ return .fallback(
111
+ focusTextInputForTextEntry(app: request.app, x: point.x, y: point.y)
112
+ )
113
+ case .raise(let message):
114
+ NSException(
115
+ name: NSExceptionName.internalInconsistencyException,
116
+ reason: message ?? "private XCTest text synthesis failed"
117
+ ).raise()
118
+ case .continueTyping:
119
+ break
120
+ }
121
+ if index + 1 < steps.count {
122
+ sleepFor(request.delaySeconds)
123
+ }
124
+ }
125
+ logTextEntryPhase(
126
+ commandId: request.commandId,
127
+ phase: "total",
128
+ startedAt: request.startedAt,
129
+ chars: request.text.count,
130
+ mode: .replacement
131
+ )
132
+ return .completed(
133
+ TextEntryResult(
134
+ verified: nil,
135
+ repaired: false,
136
+ expectedText: request.text,
137
+ observedText: nil,
138
+ textEntryRoute: "synthesized-first-responder-replacement"
139
+ )
140
+ )
141
+ #else
142
+ return .notApplicable
143
+ #endif
144
+ }
145
+
146
+ static func shouldUseSynthesizedFirstResponderReplacement(
147
+ hasResolvedElement: Bool,
148
+ hasRefreshPoint: Bool,
149
+ xCTestChannelPenalized: Bool
150
+ ) -> Bool {
151
+ !hasResolvedElement && hasRefreshPoint && xCTestChannelPenalized
152
+ }
153
+
154
+ static func shouldUseSynthesizedFirstResponderType(
155
+ repairMode: TextTypingRepairMode,
156
+ fromTapWitness: Bool,
157
+ softwareKeyboardVisible: Bool
158
+ ) -> Bool {
159
+ repairMode == .none && fromTapWitness && !softwareKeyboardVisible
160
+ }
161
+
162
+ static func shouldUseResolvedCoordinateTextEntryRoute(
163
+ repairMode: TextTypingRepairMode,
164
+ hasX: Bool,
165
+ hasY: Bool,
166
+ xCTestChannelPenalized: Bool
167
+ ) -> Bool {
168
+ repairMode == .replacement && hasX && hasY && xCTestChannelPenalized
169
+ }
170
+
171
+ static func shouldFallbackFromSynthesizedTextEntryFocus(
172
+ _ outcome: RunnerInteractionOutcome
173
+ ) -> Bool {
174
+ if case .unsupported = outcome { return true }
175
+ return false
176
+ }
177
+ }