agent-device 0.14.8 → 0.14.9
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.
- package/README.md +1 -2
- package/android-snapshot-helper/README.md +4 -2
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.apk → agent-device-android-snapshot-helper-0.14.9.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.9.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.manifest.json → agent-device-android-snapshot-helper-0.14.9.manifest.json} +6 -6
- package/dist/src/6108.js +17 -17
- package/dist/src/7462.js +1 -1
- package/dist/src/9542.js +1 -1
- package/dist/src/9639.js +2 -2
- package/dist/src/9818.js +1 -1
- package/dist/src/android-adb.d.ts +11 -2
- package/dist/src/android-snapshot-helper.d.ts +12 -2
- package/dist/src/cli.js +46 -46
- package/dist/src/command-schema.js +1 -0
- package/dist/src/contracts.d.ts +1 -0
- package/dist/src/finders.d.ts +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/internal/daemon.js +20 -20
- package/dist/src/selectors.d.ts +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +86 -13
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +160 -93
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +3 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +15 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +185 -0
- package/package.json +1 -1
- package/server.json +3 -3
- package/skills/agent-device/SKILL.md +11 -1
- package/skills/dogfood/SKILL.md +3 -1
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.8.apk.sha256 +0 -1
- package/skills/react-devtools/SKILL.md +0 -48
|
@@ -28,6 +28,7 @@ extension RunnerTests {
|
|
|
28
28
|
let identifier: String
|
|
29
29
|
let valueText: String?
|
|
30
30
|
let hittable: Bool
|
|
31
|
+
let focused: Bool
|
|
31
32
|
let visible: Bool
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -341,6 +342,7 @@ extension RunnerTests {
|
|
|
341
342
|
identifier: identifier,
|
|
342
343
|
valueText: valueText,
|
|
343
344
|
hittable: computedSnapshotHittable(snapshot, viewport: context.viewport, laterNodes: laterNodes),
|
|
345
|
+
focused: snapshotHasFocus(snapshot),
|
|
344
346
|
visible: isVisibleInViewport(snapshot.frame, context.viewport)
|
|
345
347
|
)
|
|
346
348
|
}
|
|
@@ -360,6 +362,7 @@ extension RunnerTests {
|
|
|
360
362
|
value: evaluation.valueText,
|
|
361
363
|
rect: snapshotRect(from: snapshot.frame),
|
|
362
364
|
enabled: snapshot.isEnabled,
|
|
365
|
+
focused: evaluation.focused ? true : nil,
|
|
363
366
|
hittable: evaluation.hittable,
|
|
364
367
|
depth: depth,
|
|
365
368
|
parentIndex: parentIndex,
|
|
@@ -525,6 +528,7 @@ extension RunnerTests {
|
|
|
525
528
|
value: node.value,
|
|
526
529
|
rect: node.rect,
|
|
527
530
|
enabled: node.enabled,
|
|
531
|
+
focused: node.focused,
|
|
528
532
|
hittable: node.hittable,
|
|
529
533
|
depth: depth,
|
|
530
534
|
parentIndex: parentIndex,
|
|
@@ -575,6 +579,7 @@ extension RunnerTests {
|
|
|
575
579
|
value: valueText,
|
|
576
580
|
rect: snapshotRect(from: frame),
|
|
577
581
|
enabled: element.isEnabled,
|
|
582
|
+
focused: elementHasFocus(element) ? true : nil,
|
|
578
583
|
hittable: element.isHittable,
|
|
579
584
|
depth: 0,
|
|
580
585
|
parentIndex: nil,
|
|
@@ -592,6 +597,16 @@ extension RunnerTests {
|
|
|
592
597
|
return node
|
|
593
598
|
}
|
|
594
599
|
|
|
600
|
+
private func snapshotHasFocus(_ snapshot: XCUIElementSnapshot) -> Bool {
|
|
601
|
+
var focused = false
|
|
602
|
+
_ = RunnerObjCExceptionCatcher.catchException({
|
|
603
|
+
if let value = (snapshot as! NSObject).value(forKey: "hasFocus") as? Bool {
|
|
604
|
+
focused = value
|
|
605
|
+
}
|
|
606
|
+
})
|
|
607
|
+
return focused
|
|
608
|
+
}
|
|
609
|
+
|
|
595
610
|
private func shouldExpandCollapsedTabContainer(_ snapshot: XCUIElementSnapshot) -> Bool {
|
|
596
611
|
let frame = snapshot.frame
|
|
597
612
|
if frame.isNull || frame.isEmpty { return false }
|
|
@@ -186,6 +186,7 @@ extension RunnerTests {
|
|
|
186
186
|
value: nil,
|
|
187
187
|
rect: snapshotRect(from: element.frame),
|
|
188
188
|
enabled: element.isEnabled,
|
|
189
|
+
focused: elementHasFocus(element) ? true : nil,
|
|
189
190
|
hittable: hittableOverride ?? element.isHittable,
|
|
190
191
|
depth: depth,
|
|
191
192
|
parentIndex: nil,
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
enum RunnerInteractionOutcome {
|
|
4
|
+
case performed
|
|
5
|
+
case unsupported(String)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
enum TvRemoteButton {
|
|
9
|
+
case select
|
|
10
|
+
case menu
|
|
11
|
+
case home
|
|
12
|
+
case up
|
|
13
|
+
case down
|
|
14
|
+
case left
|
|
15
|
+
case right
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
extension RunnerTests {
|
|
19
|
+
func resolveTvRemoteDoublePressDelay() -> TimeInterval {
|
|
20
|
+
guard
|
|
21
|
+
let raw = ProcessInfo.processInfo.environment["AGENT_DEVICE_TV_REMOTE_DOUBLE_PRESS_DELAY_MS"],
|
|
22
|
+
!raw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
23
|
+
else {
|
|
24
|
+
return tvRemoteDoublePressDelayDefault
|
|
25
|
+
}
|
|
26
|
+
guard let parsedMs = Double(raw), parsedMs >= 0 else {
|
|
27
|
+
return tvRemoteDoublePressDelayDefault
|
|
28
|
+
}
|
|
29
|
+
return min(parsedMs, 1000) / 1000.0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@discardableResult
|
|
33
|
+
func pressTvRemote(_ button: TvRemoteButton, duration: TimeInterval? = nil) -> Bool {
|
|
34
|
+
#if os(tvOS)
|
|
35
|
+
let remoteButton = xcuiRemoteButton(button)
|
|
36
|
+
if let duration, duration > 0 {
|
|
37
|
+
XCUIRemote.shared.press(remoteButton, forDuration: duration)
|
|
38
|
+
} else {
|
|
39
|
+
XCUIRemote.shared.press(remoteButton)
|
|
40
|
+
}
|
|
41
|
+
return true
|
|
42
|
+
#else
|
|
43
|
+
return false
|
|
44
|
+
#endif
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func tvRemoteButton(from raw: String?) -> TvRemoteButton? {
|
|
48
|
+
switch raw?.lowercased() {
|
|
49
|
+
case "select":
|
|
50
|
+
return .select
|
|
51
|
+
case "menu":
|
|
52
|
+
return .menu
|
|
53
|
+
case "home":
|
|
54
|
+
return .home
|
|
55
|
+
case "up":
|
|
56
|
+
return .up
|
|
57
|
+
case "down":
|
|
58
|
+
return .down
|
|
59
|
+
case "left":
|
|
60
|
+
return .left
|
|
61
|
+
case "right":
|
|
62
|
+
return .right
|
|
63
|
+
default:
|
|
64
|
+
return nil
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func elementHasFocus(_ element: XCUIElement) -> Bool {
|
|
69
|
+
var focused = false
|
|
70
|
+
_ = RunnerObjCExceptionCatcher.catchException({
|
|
71
|
+
if let value = (element as NSObject).value(forKey: "hasFocus") as? Bool {
|
|
72
|
+
focused = value
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
return focused
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func activateElement(app: XCUIApplication, element: XCUIElement, action: String) -> RunnerInteractionOutcome {
|
|
79
|
+
if let outcome = selectFocusedTvElement(app: app, element: element, action: action) {
|
|
80
|
+
return outcome
|
|
81
|
+
}
|
|
82
|
+
return performElementTap(element)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func selectFocusedTvElement(app: XCUIApplication, point: CGPoint, action: String) -> RunnerInteractionOutcome? {
|
|
86
|
+
#if os(tvOS)
|
|
87
|
+
guard let focused = focusedTvElement(app: app), !focused.frame.isEmpty, focused.frame.contains(point) else {
|
|
88
|
+
return .unsupported("\(action) is supported on tvOS only when the requested point is inside the focused element")
|
|
89
|
+
}
|
|
90
|
+
_ = pressTvRemote(.select)
|
|
91
|
+
return .performed
|
|
92
|
+
#else
|
|
93
|
+
return nil
|
|
94
|
+
#endif
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func longSelectFocusedTvElement(app: XCUIApplication, point: CGPoint, duration: TimeInterval) -> RunnerInteractionOutcome? {
|
|
98
|
+
#if os(tvOS)
|
|
99
|
+
guard let focused = focusedTvElement(app: app), !focused.frame.isEmpty, focused.frame.contains(point) else {
|
|
100
|
+
return .unsupported("long press is supported on tvOS only when the requested point is inside the focused element")
|
|
101
|
+
}
|
|
102
|
+
_ = pressTvRemote(.select, duration: duration)
|
|
103
|
+
return .performed
|
|
104
|
+
#else
|
|
105
|
+
return nil
|
|
106
|
+
#endif
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private func performElementTap(_ element: XCUIElement) -> RunnerInteractionOutcome {
|
|
110
|
+
#if os(tvOS)
|
|
111
|
+
return .unsupported("element tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element")
|
|
112
|
+
#else
|
|
113
|
+
element.tap()
|
|
114
|
+
return .performed
|
|
115
|
+
#endif
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private func selectFocusedTvElement(app: XCUIApplication, element: XCUIElement, action: String) -> RunnerInteractionOutcome? {
|
|
119
|
+
#if os(tvOS)
|
|
120
|
+
guard tvFocusedElementMatches(app: app, target: element) else {
|
|
121
|
+
return .unsupported("\(action) is supported on tvOS only when the requested element is focused")
|
|
122
|
+
}
|
|
123
|
+
_ = pressTvRemote(.select)
|
|
124
|
+
return .performed
|
|
125
|
+
#else
|
|
126
|
+
return nil
|
|
127
|
+
#endif
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func tvFocusedElementMatches(app: XCUIApplication, target: XCUIElement) -> Bool {
|
|
131
|
+
#if os(tvOS)
|
|
132
|
+
if target.hasFocus {
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
guard let focused = focusedTvElement(app: app) else {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
let targetFrame = target.frame
|
|
139
|
+
let focusedFrame = focused.frame
|
|
140
|
+
guard !targetFrame.isEmpty && !focusedFrame.isEmpty else {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
let focusedCenter = CGPoint(x: focusedFrame.midX, y: focusedFrame.midY)
|
|
144
|
+
let targetCenter = CGPoint(x: targetFrame.midX, y: targetFrame.midY)
|
|
145
|
+
return targetFrame.contains(focusedCenter)
|
|
146
|
+
|| focusedFrame.contains(targetCenter)
|
|
147
|
+
|| targetFrame.intersects(focusedFrame)
|
|
148
|
+
#else
|
|
149
|
+
return false
|
|
150
|
+
#endif
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func focusedTvElement(app: XCUIApplication) -> XCUIElement? {
|
|
154
|
+
#if os(tvOS)
|
|
155
|
+
let focused = app
|
|
156
|
+
.descendants(matching: .any)
|
|
157
|
+
.matching(NSPredicate(format: "hasFocus == true"))
|
|
158
|
+
.firstMatch
|
|
159
|
+
return focused.exists ? focused : nil
|
|
160
|
+
#else
|
|
161
|
+
return nil
|
|
162
|
+
#endif
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#if os(tvOS)
|
|
166
|
+
private func xcuiRemoteButton(_ button: TvRemoteButton) -> XCUIRemote.Button {
|
|
167
|
+
switch button {
|
|
168
|
+
case .select:
|
|
169
|
+
return .select
|
|
170
|
+
case .menu:
|
|
171
|
+
return .menu
|
|
172
|
+
case .home:
|
|
173
|
+
return .home
|
|
174
|
+
case .up:
|
|
175
|
+
return .up
|
|
176
|
+
case .down:
|
|
177
|
+
return .down
|
|
178
|
+
case .left:
|
|
179
|
+
return .left
|
|
180
|
+
case .right:
|
|
181
|
+
return .right
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
#endif
|
|
185
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-device",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.9",
|
|
4
4
|
"description": "Agent-native CLI for AI mobile testing and app automation across iOS, Android, tvOS, Android TV, macOS, and Linux.",
|
|
5
5
|
"mcpName": "io.github.callstackincubator/agent-device",
|
|
6
6
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.callstackincubator/agent-device",
|
|
4
4
|
"title": "agent-device",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Discovery router for the agent-device CLI with status, install, help, prompts, and resources.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/callstackincubator/agent-device",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.14.
|
|
10
|
+
"version": "0.14.9",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "agent-device",
|
|
15
|
-
"version": "0.14.
|
|
15
|
+
"version": "0.14.9",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -11,7 +11,15 @@ Router only. Private setup before using this skill:
|
|
|
11
11
|
agent-device --version
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
If that fails but the user installed `agent-device` globally, try the user's login shell before using `npx`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
zsh -lic 'command -v agent-device'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If it prints a path, run that absolute path instead of `agent-device`. For non-zsh shells, use the equivalent login-shell command.
|
|
21
|
+
|
|
22
|
+
Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, stop and tell the user to upgrade the trusted install or approve an exact-version npm command. Do not run `npm install -g agent-device@latest` or `npx -y agent-device@latest` autonomously, and do not include version/upgrade commands in final plans.
|
|
15
23
|
|
|
16
24
|
Before your first agent-device command or plan, read the version-matched CLI guide:
|
|
17
25
|
|
|
@@ -32,3 +40,5 @@ agent-device help dogfood
|
|
|
32
40
|
Default loop: `open -> snapshot/-i -> get/is/find or press/fill/scroll/wait -> verify -> close`.
|
|
33
41
|
|
|
34
42
|
Use this skill only to route into version-matched CLI help. Let `help workflow` provide exact command shapes, platform limits, and current workflow guidance.
|
|
43
|
+
|
|
44
|
+
For precise location workflows, read the installed `settings` help before planning so coordinate support and platform limits come from the active CLI version.
|
package/skills/dogfood/SKILL.md
CHANGED
|
@@ -12,7 +12,9 @@ Router for exploratory QA. Private setup before using this skill:
|
|
|
12
12
|
agent-device --version
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
If that fails, stop and tell the user to expose a trusted `agent-device` binary on PATH or approve an exact-version npm command. This skill intentionally keeps allowed tools restricted to `agent-device` and `npx agent-device`.
|
|
16
|
+
|
|
17
|
+
Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, stop and tell the user to upgrade the trusted install or approve an exact-version npm command. Do not run `npm install -g agent-device@latest` or `npx -y agent-device@latest` autonomously, and do not include version/upgrade commands in final plans.
|
|
16
18
|
|
|
17
19
|
Read current CLI guidance:
|
|
18
20
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0669bbeb4c3b549a9084dc3d75e4afa8f055408424c40c2fd9db4b75eb1f6e53 agent-device-android-snapshot-helper-0.14.8.apk
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: react-devtools
|
|
3
|
-
description: Inspect and profile React Native component trees from agent-device. Use for React Native performance, profiling, props, state, hooks, render causes, slow components, excessive rerenders, or questions like why a component rerendered.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# react-devtools
|
|
7
|
-
|
|
8
|
-
Router for React Native internals. Private setup before using this skill:
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
agent-device --version
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Require `agent-device >= 0.14.0`; older CLIs lack these help topics. If older, run `npm install -g agent-device@latest`, recheck, then continue. If you cannot upgrade, stop and tell the user. Do not include version/upgrade commands in final plans.
|
|
15
|
-
|
|
16
|
-
Read current CLI guidance:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
agent-device help react-devtools
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Use `agent-device react-devtools ...` for component tree, props, state, hooks, render ownership, performance profiling, slow components, or rerenders. It dynamically runs pinned `agent-react-devtools@0.4.0`. Use normal `agent-device` commands for visible UI, refs, screenshots, logs, network, or device-level perf.
|
|
23
|
-
|
|
24
|
-
Core loop:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
agent-device react-devtools status
|
|
28
|
-
agent-device react-devtools wait --connected
|
|
29
|
-
agent-device react-devtools get tree --depth 3
|
|
30
|
-
agent-device react-devtools profile start
|
|
31
|
-
# perform the interaction with normal agent-device commands
|
|
32
|
-
agent-device react-devtools profile stop
|
|
33
|
-
agent-device react-devtools profile slow --limit 5
|
|
34
|
-
agent-device react-devtools profile rerenders --limit 5
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Remote iOS bridge order:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
agent-device open <bundle-id> --platform ios --relaunch
|
|
41
|
-
agent-device react-devtools start
|
|
42
|
-
agent-device open <bundle-id> --platform ios --relaunch
|
|
43
|
-
agent-device react-devtools wait --connected
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Rules:
|
|
47
|
-
|
|
48
|
-
Keep reads bounded with `--depth`/`find`, treat `@c` refs as reload-local, profile only the investigated interaction, and run the same command in remote Android sessions; the CLI manages the needed local service tunnel. For remote iOS, relaunch after `react-devtools start` because React Native opens the legacy DevTools websocket during JavaScript startup.
|