agent-device 0.7.0 → 0.7.2
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 +57 -4
- package/dist/src/678.js +3 -0
- package/dist/src/bin.js +53 -53
- package/dist/src/daemon.js +32 -28
- package/package.json +1 -1
- package/skills/agent-device/SKILL.md +51 -1
- package/skills/agent-device/references/remote-tenancy.md +77 -0
- package/skills/agent-device/references/session-management.md +24 -0
- package/dist/src/735.js +0 -3
package/package.json
CHANGED
|
@@ -25,6 +25,8 @@ Use this skill as a router, not a full manual.
|
|
|
25
25
|
- Normal UI task: `open` -> `snapshot -i` -> `press/fill` -> `diff snapshot -i` -> `close`
|
|
26
26
|
- Debug/crash: `open <app>` -> `logs clear --restart` -> reproduce -> `network dump` -> `logs path` -> targeted `grep`
|
|
27
27
|
- Replay drift: `replay -u <path>` -> verify updated selectors
|
|
28
|
+
- Remote multi-tenant run: allocate lease -> run commands with tenant isolation flags -> heartbeat/release lease
|
|
29
|
+
- Device-scope isolation run: set iOS simulator set / Android allowlist -> run selectors within scope only
|
|
28
30
|
|
|
29
31
|
## Canonical Flows
|
|
30
32
|
|
|
@@ -57,12 +59,42 @@ Logging is off by default. Enable only for debugging windows.
|
|
|
57
59
|
agent-device replay -u ./session.ad
|
|
58
60
|
```
|
|
59
61
|
|
|
62
|
+
### 4) Remote Tenant Lease Flow (HTTP JSON-RPC)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Allocate lease
|
|
66
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
67
|
+
-H "content-type: application/json" \
|
|
68
|
+
-H "Authorization: Bearer <token>" \
|
|
69
|
+
-d '{"jsonrpc":"2.0","id":"alloc-1","method":"agent_device.lease.allocate","params":{"runId":"run-123","tenantId":"acme","ttlMs":60000}}'
|
|
70
|
+
|
|
71
|
+
# Use lease in tenant-isolated command execution
|
|
72
|
+
agent-device --daemon-transport http \
|
|
73
|
+
--tenant acme \
|
|
74
|
+
--session-isolation tenant \
|
|
75
|
+
--run-id run-123 \
|
|
76
|
+
--lease-id <lease-id> \
|
|
77
|
+
session list --json
|
|
78
|
+
|
|
79
|
+
# Heartbeat and release
|
|
80
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
81
|
+
-H "content-type: application/json" \
|
|
82
|
+
-H "Authorization: Bearer <token>" \
|
|
83
|
+
-d '{"jsonrpc":"2.0","id":"hb-1","method":"agent_device.lease.heartbeat","params":{"leaseId":"<lease-id>","ttlMs":60000}}'
|
|
84
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
85
|
+
-H "content-type: application/json" \
|
|
86
|
+
-H "Authorization: Bearer <token>" \
|
|
87
|
+
-d '{"jsonrpc":"2.0","id":"rel-1","method":"agent_device.lease.release","params":{"leaseId":"<lease-id>"}}'
|
|
88
|
+
```
|
|
89
|
+
|
|
60
90
|
## Command Skeleton (Minimal)
|
|
61
91
|
|
|
62
92
|
### Session and navigation
|
|
63
93
|
|
|
64
94
|
```bash
|
|
65
95
|
agent-device devices
|
|
96
|
+
agent-device devices --platform ios --ios-simulator-device-set /tmp/tenant-a/simulators
|
|
97
|
+
agent-device devices --platform android --android-device-allowlist emulator-5554,device-1234
|
|
66
98
|
agent-device open [app|url] [url]
|
|
67
99
|
agent-device open [app] --relaunch
|
|
68
100
|
agent-device close [app]
|
|
@@ -70,8 +102,16 @@ agent-device session list
|
|
|
70
102
|
```
|
|
71
103
|
|
|
72
104
|
Use `boot` only as fallback when `open` cannot find/connect to a ready target.
|
|
105
|
+
For Android emulators by AVD name, use `boot --platform android --device <avd-name>`.
|
|
106
|
+
For Android emulators without GUI, add `--headless`.
|
|
73
107
|
Use `--target mobile|tv` with `--platform` (required) to pick phone/tablet vs TV targets (AndroidTV/tvOS).
|
|
74
108
|
|
|
109
|
+
Isolation scoping quick reference:
|
|
110
|
+
- `--ios-simulator-device-set <path>` scopes iOS simulator discovery + command execution to one simulator set.
|
|
111
|
+
- `--android-device-allowlist <serials>` scopes Android discovery/selection to comma/space separated serials.
|
|
112
|
+
- Scope is applied before selectors (`--device`, `--udid`, `--serial`); out-of-scope selectors fail with `DEVICE_NOT_FOUND`.
|
|
113
|
+
- With iOS simulator-set scope enabled, iOS physical devices are not enumerated.
|
|
114
|
+
|
|
75
115
|
TV quick reference:
|
|
76
116
|
- AndroidTV: `open`/`apps` use TV launcher discovery automatically.
|
|
77
117
|
- TV target selection works on emulators/simulators and connected physical devices (AndroidTV + AppleTV).
|
|
@@ -101,6 +141,7 @@ agent-device clipboard write "token"
|
|
|
101
141
|
agent-device perf --json
|
|
102
142
|
agent-device network dump [limit] [summary|headers|body|all]
|
|
103
143
|
agent-device push <bundle|package> <payload.json|inline-json>
|
|
144
|
+
agent-device trigger-app-event screenshot_taken '{"source":"qa"}'
|
|
104
145
|
agent-device get text @e1
|
|
105
146
|
agent-device screenshot out.png
|
|
106
147
|
agent-device settings permission grant notifications
|
|
@@ -129,16 +170,23 @@ agent-device batch --steps-file /tmp/batch-steps.json --json
|
|
|
129
170
|
- iOS `appstate` is session-scoped; Android `appstate` is live foreground state.
|
|
130
171
|
- Clipboard helpers: `clipboard read` / `clipboard write <text>` are supported on Android and iOS simulators; iOS physical devices are not supported yet.
|
|
131
172
|
- `network dump` is best-effort and parses HTTP(s) entries from the session app log file.
|
|
132
|
-
-
|
|
173
|
+
- Biometric settings: iOS simulator supports `settings faceid|touchid <match|nonmatch|enroll|unenroll>`; Android supports `settings fingerprint <match|nonmatch>` where runtime tooling is available.
|
|
133
174
|
- For AndroidTV/tvOS selection, always pair `--target` with `--platform` (`ios`, `android`, or `apple` alias); target-only selection is invalid.
|
|
134
175
|
- `push` simulates notification delivery:
|
|
135
176
|
- iOS simulator uses APNs-style payload JSON.
|
|
136
177
|
- Android uses broadcast action + typed extras (string/boolean/number).
|
|
178
|
+
- `trigger-app-event` requires app-defined deep-link hooks and URL template configuration (`AGENT_DEVICE_APP_EVENT_URL_TEMPLATE` or platform-specific variants).
|
|
179
|
+
- `trigger-app-event` requires an active session or explicit selectors (`--platform`, `--device`, `--udid`, `--serial`); on iOS physical devices, custom-scheme triggers require active app context.
|
|
180
|
+
- Canonical trigger behavior and caveats are documented in [`website/docs/docs/commands.md`](../../website/docs/docs/commands.md) under **App event triggers**.
|
|
137
181
|
- Permission settings are app-scoped and require an active session app:
|
|
138
182
|
`settings permission <grant|deny|reset> <camera|microphone|photos|contacts|notifications> [full|limited]`
|
|
139
183
|
- `full|limited` mode applies only to iOS `photos`; other targets reject mode.
|
|
140
184
|
- On Android, non-ASCII `fill/type` may require an ADB keyboard IME on some system images; only install IME APKs from trusted sources and verify checksum/signature.
|
|
141
185
|
- If using `--save-script`, prefer explicit path syntax (`--save-script=flow.ad` or `./flow.ad`).
|
|
186
|
+
- For tenant-isolated remote runs, always pass `--tenant`, `--session-isolation tenant`, `--run-id`, and `--lease-id` together.
|
|
187
|
+
- Use short lease TTLs and heartbeat only while work is active; release leases immediately after run completion/failure.
|
|
188
|
+
- Env equivalents for scoped runs: `AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET` (compat `IOS_SIMULATOR_DEVICE_SET`) and
|
|
189
|
+
`AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST` (compat `ANDROID_DEVICE_ALLOWLIST`).
|
|
142
190
|
|
|
143
191
|
## Security and Trust Notes
|
|
144
192
|
|
|
@@ -146,6 +194,7 @@ agent-device batch --steps-file /tmp/batch-steps.json --json
|
|
|
146
194
|
- If install is required, pin an exact version (for example: `npx --yes agent-device@<exact-version> --help`).
|
|
147
195
|
- Signing/provisioning environment variables are optional, sensitive, and only for iOS physical-device setup.
|
|
148
196
|
- Logs/artifacts are written under `~/.agent-device`; replay scripts write to explicit paths you provide.
|
|
197
|
+
- For remote daemon mode, prefer `AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual` with `AGENT_DEVICE_HTTP_AUTH_HOOK` and tenant-scoped lease admission.
|
|
149
198
|
- Keep logging off unless debugging and use least-privilege/isolated environments for autonomous runs.
|
|
150
199
|
|
|
151
200
|
## Common Mistakes
|
|
@@ -165,3 +214,4 @@ agent-device batch --steps-file /tmp/batch-steps.json --json
|
|
|
165
214
|
- [references/coordinate-system.md](references/coordinate-system.md)
|
|
166
215
|
- [references/batching.md](references/batching.md)
|
|
167
216
|
- [references/perf-metrics.md](references/perf-metrics.md)
|
|
217
|
+
- [references/remote-tenancy.md](references/remote-tenancy.md)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Remote Tenancy and Lease Admission
|
|
2
|
+
|
|
3
|
+
Use this reference for remote daemon HTTP flows that require explicit
|
|
4
|
+
tenant/run admission control.
|
|
5
|
+
|
|
6
|
+
## Transport prerequisites
|
|
7
|
+
|
|
8
|
+
- Start daemon in HTTP mode (`AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual`).
|
|
9
|
+
- Use a token from daemon metadata or `Authorization: Bearer <token>`.
|
|
10
|
+
- Prefer an auth hook (`AGENT_DEVICE_HTTP_AUTH_HOOK`) for caller validation and
|
|
11
|
+
tenant injection.
|
|
12
|
+
|
|
13
|
+
## Lease lifecycle (JSON-RPC)
|
|
14
|
+
|
|
15
|
+
Use `POST /rpc` with JSON-RPC 2.0 methods:
|
|
16
|
+
|
|
17
|
+
- `agent_device.lease.allocate`
|
|
18
|
+
- `agent_device.lease.heartbeat`
|
|
19
|
+
- `agent_device.lease.release`
|
|
20
|
+
|
|
21
|
+
Example allocate:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
25
|
+
-H "content-type: application/json" \
|
|
26
|
+
-H "Authorization: Bearer <token>" \
|
|
27
|
+
-d '{"jsonrpc":"2.0","id":"alloc-1","method":"agent_device.lease.allocate","params":{"tenantId":"acme","runId":"run-123","ttlMs":60000}}'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Example heartbeat:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
34
|
+
-H "content-type: application/json" \
|
|
35
|
+
-H "Authorization: Bearer <token>" \
|
|
36
|
+
-d '{"jsonrpc":"2.0","id":"hb-1","method":"agent_device.lease.heartbeat","params":{"leaseId":"<lease-id>","ttlMs":60000}}'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Example release:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -sS http://127.0.0.1:${AGENT_DEVICE_DAEMON_HTTP_PORT}/rpc \
|
|
43
|
+
-H "content-type: application/json" \
|
|
44
|
+
-H "Authorization: Bearer <token>" \
|
|
45
|
+
-d '{"jsonrpc":"2.0","id":"rel-1","method":"agent_device.lease.release","params":{"leaseId":"<lease-id>"}}'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Command admission contract
|
|
49
|
+
|
|
50
|
+
For tenant-isolated command execution, pass all four flags:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-device --daemon-transport http \
|
|
54
|
+
--tenant acme \
|
|
55
|
+
--session-isolation tenant \
|
|
56
|
+
--run-id run-123 \
|
|
57
|
+
--lease-id <lease-id> \
|
|
58
|
+
session list --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Admission checks require tenant/run/lease scope alignment.
|
|
62
|
+
|
|
63
|
+
## Failure semantics
|
|
64
|
+
|
|
65
|
+
- Missing tenant/run/lease fields in tenant isolation mode: `INVALID_ARGS`
|
|
66
|
+
- Lease not active or wrong scope: `UNAUTHORIZED`
|
|
67
|
+
- Method mismatch: JSON-RPC `-32601` (HTTP 404)
|
|
68
|
+
|
|
69
|
+
## Operational guidance
|
|
70
|
+
|
|
71
|
+
- Keep TTL short and heartbeat only while a run is active.
|
|
72
|
+
- Release lease immediately on run completion/error paths.
|
|
73
|
+
- For bounded hosts, configure:
|
|
74
|
+
- `AGENT_DEVICE_MAX_SIMULATOR_LEASES`
|
|
75
|
+
- `AGENT_DEVICE_LEASE_TTL_MS`
|
|
76
|
+
- `AGENT_DEVICE_LEASE_MIN_TTL_MS`
|
|
77
|
+
- `AGENT_DEVICE_LEASE_MAX_TTL_MS`
|
|
@@ -14,6 +14,8 @@ Sessions isolate device context. A device can only be held by one session at a t
|
|
|
14
14
|
- Name sessions semantically.
|
|
15
15
|
- Close sessions when done.
|
|
16
16
|
- Use separate sessions for parallel work.
|
|
17
|
+
- For remote tenant-scoped automation, run commands with:
|
|
18
|
+
`--tenant <id> --session-isolation tenant --run-id <id> --lease-id <id>`
|
|
17
19
|
- In iOS sessions, use `open <app>`. `open <url>` opens deep links; on devices `http(s)://` opens Safari when no app is active, and custom schemes require an active app in the session.
|
|
18
20
|
- In iOS sessions, `open <app> <url>` opens a deep link.
|
|
19
21
|
- On iOS, `appstate` is session-scoped and requires a matching active session on the target device.
|
|
@@ -23,6 +25,22 @@ Sessions isolate device context. A device can only be held by one session at a t
|
|
|
23
25
|
- For deterministic replay scripts, prefer selector-based actions and assertions.
|
|
24
26
|
- Use `replay -u` to update selector drift during maintenance.
|
|
25
27
|
|
|
28
|
+
## Scoped device isolation
|
|
29
|
+
|
|
30
|
+
Use scoped discovery when sessions must not see host-global device lists.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agent-device devices --platform ios --ios-simulator-device-set /tmp/tenant-a/simulators
|
|
34
|
+
agent-device devices --platform android --android-device-allowlist emulator-5554,device-1234
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Scope is applied before selectors (`--device`, `--udid`, `--serial`).
|
|
38
|
+
- If selector target is outside scope, resolution fails with `DEVICE_NOT_FOUND`.
|
|
39
|
+
- With iOS simulator-set scope enabled, iOS physical devices are not enumerated.
|
|
40
|
+
- Environment equivalents:
|
|
41
|
+
- `AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET` (compat: `IOS_SIMULATOR_DEVICE_SET`)
|
|
42
|
+
- `AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST` (compat: `ANDROID_DEVICE_ALLOWLIST`)
|
|
43
|
+
|
|
26
44
|
## Listing sessions
|
|
27
45
|
|
|
28
46
|
```bash
|
|
@@ -35,3 +53,9 @@ agent-device session list
|
|
|
35
53
|
agent-device replay ./session.ad --session auth
|
|
36
54
|
agent-device replay -u ./session.ad --session auth
|
|
37
55
|
```
|
|
56
|
+
|
|
57
|
+
## Tenant isolation note
|
|
58
|
+
|
|
59
|
+
When session isolation is set to tenant mode, session namespace is scoped as
|
|
60
|
+
`<tenant>:<session>`. For remote runs, allocate and maintain an active lease
|
|
61
|
+
for the same tenant/run scope before executing tenant-isolated commands.
|
package/dist/src/735.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{AsyncLocalStorage as e}from"node:async_hooks";import t from"node:crypto";import r,{promises as n}from"node:fs";import o from"node:os";import i from"node:path";import{fileURLToPath as a,pathToFileURL as s}from"node:url";import{spawn as c,spawnSync as l}from"node:child_process";let d=new e,u=/(token|secret|password|authorization|cookie|api[_-]?key|access[_-]?key|private[_-]?key)/i,f=/(bearer\s+[a-z0-9._-]+|(?:api[_-]?key|token|secret|password)\s*[=:]\s*\S+)/i;function m(){return t.randomBytes(8).toString("hex")}async function p(e,r){let n={...e,diagnosticId:`${Date.now().toString(36)}-${t.randomBytes(4).toString("hex")}`,events:[]};return await d.run(n,r)}function h(){let e=d.getStore();return e?{diagnosticId:e.diagnosticId,requestId:e.requestId,session:e.session,command:e.command,debug:e.debug}:{}}function g(e){let t=d.getStore();if(!t)return;let n={ts:new Date().toISOString(),level:e.level??"info",phase:e.phase,session:t.session,requestId:t.requestId,command:t.command,durationMs:e.durationMs,data:e.data?y(e.data):void 0};if(t.events.push(n),!t.debug)return;let o=`[agent-device][diag] ${JSON.stringify(n)}
|
|
2
|
-
`;try{t.logPath&&r.appendFile(t.logPath,o,()=>{}),t.traceLogPath&&r.appendFile(t.traceLogPath,o,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(o)}catch{}}async function w(e,t,r){let n=Date.now();try{let o=await t();return g({level:"info",phase:e,durationMs:Date.now()-n,data:r}),o}catch(t){throw g({level:"error",phase:e,durationMs:Date.now()-n,data:{...r??{},error:t instanceof Error?t.message:String(t)}}),t}}function S(e={}){let t=d.getStore();if(!t||!e.force&&!t.debug||0===t.events.length)return null;try{let e=(t.session??"default").replace(/[^a-zA-Z0-9._-]/g,"_"),n=new Date().toISOString().slice(0,10),a=i.join(o.homedir(),".agent-device","logs",e,n);r.mkdirSync(a,{recursive:!0});let s=new Date().toISOString().replace(/[:.]/g,"-"),c=i.join(a,`${s}-${t.diagnosticId}.ndjson`),l=t.events.map(e=>JSON.stringify(y(e)));return r.writeFileSync(c,`${l.join("\n")}
|
|
3
|
-
`),t.events=[],c}catch{return null}}function y(e){return function e(t,r,n){if(null==t)return t;if("string"==typeof t){var o=t,i=n;let e=o.trim();if(!e)return o;if(i&&u.test(i)||f.test(e))return"[REDACTED]";let r=function(e){try{let t=new URL(e);return t.search&&(t.search="?REDACTED"),(t.username||t.password)&&(t.username="REDACTED",t.password="REDACTED"),t.toString()}catch{return null}}(e);return r||(e.length>400?`${e.slice(0,200)}...<truncated>`:e)}if("object"!=typeof t)return t;if(r.has(t))return"[Circular]";if(r.add(t),Array.isArray(t))return t.map(t=>e(t,r));let a={};for(let[n,o]of Object.entries(t)){if(u.test(n)){a[n]="[REDACTED]";continue}a[n]=e(o,r,n)}return a}(e,new WeakSet)}class A extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function I(e){return e instanceof A?e:e instanceof Error?new A("UNKNOWN",e.message,void 0,e):new A("UNKNOWN","Unknown error",{err:e})}function E(e,t={}){let r=I(e),n=r.details?y(r.details):void 0,o=n&&"string"==typeof n.hint?n.hint:void 0,i=(n&&"string"==typeof n.diagnosticId?n.diagnosticId:void 0)??t.diagnosticId,a=(n&&"string"==typeof n.logPath?n.logPath:void 0)??t.logPath,s=o??function(e){switch(e){case"INVALID_ARGS":return"Check command arguments and run --help for usage examples.";case"SESSION_NOT_FOUND":return"Run open first or pass an explicit device selector.";case"TOOL_MISSING":return"Install required platform tooling and ensure it is available in PATH.";case"DEVICE_NOT_FOUND":return"Verify the target device is booted/connected and selectors match.";case"UNSUPPORTED_OPERATION":return"This command is not available for the selected platform/device.";case"COMMAND_FAILED":default:return"Retry with --debug and inspect diagnostics log for details.";case"UNAUTHORIZED":return"Refresh daemon metadata and retry the command."}}(r.code),c=function(e){if(!e)return;let t={...e};return delete t.hint,delete t.diagnosticId,delete t.logPath,Object.keys(t).length>0?t:void 0}(n),l=function(e,t,r){if("COMMAND_FAILED"!==e||r?.processExitError!==!0)return t;let n=function(e){let t=[/^an error was encountered processing the command/i,/^underlying error\b/i,/^simulator device failed to complete the requested operation/i];for(let r of e.split("\n")){let e=r.trim();if(e&&!t.some(t=>t.test(e)))return e.length>200?`${e.slice(0,200)}...`:e}return null}("string"==typeof r?.stderr?r.stderr:"");return n||t}(r.code,r.message,n);return{code:r.code,message:l,hint:s,diagnosticId:i,logPath:a,details:c}}let v="<wifi|airplane|location> <on|off>",D="appearance <light|dark|toggle>",b="faceid <match|nonmatch|enroll|unenroll>",N="permission <grant|deny|reset> <camera|microphone|photos|contacts|contacts-limited|notifications|calendar|location|location-always|media-library|motion|reminders|siri> [full|limited]",$=`settings ${v} | settings ${D} | settings ${b} | settings ${N}`,_=`settings requires ${v}, ${D}, ${b}, or ${N}`;function M(e){let t=[],r=[];for(let n of e){let e=n.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let o=n.label?.trim()||n.value?.trim()||n.identifier?.trim()||"",i=L(n.type??"Element"),a="group"===i&&!o;a&&t.push(e);let s=a?e:Math.max(0,e-t.length);r.push({node:n,depth:s,type:i,text:O(n,s,a,i)})}return r}function O(e,t,r,n){let o=n??L(e.type??"Element"),i=T(e,o),a=" ".repeat(t),s=e.ref?`@${e.ref}`:"",c=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),l=c?` [${c}]`:"",d=i?` "${i}"`:"";return r?`${a}${s} [${o}]${l}`.trimEnd():`${a}${s} [${o}]${d}${l}`.trimEnd()}function T(e,t){var r,n;let o=e.label?.trim(),i=e.value?.trim();if("text-field"===(r=t)||"text-view"===r||"search"===r){if(i)return i;if(o)return o}else if(o)return o;if(i)return i;let a=e.identifier?.trim();return!a||(n=a,/^[\w.]+:id\/[\w.-]+$/i.test(n)&&("group"===t||"image"===t||"list"===t||"collection"===t))?"":a}function L(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase(),r=e.includes(".")&&(e.startsWith("android.")||e.startsWith("androidx.")||e.startsWith("com."));switch(t.includes(".")&&(t=t.replace(/^android\.widget\./,"").replace(/^android\.view\./,"").replace(/^android\.webkit\./,"").replace(/^androidx\./,"").replace(/^com\.google\.android\./,"").replace(/^com\.android\./,"")),t){case"application":return"application";case"navigationbar":return"navigation-bar";case"tabbar":return"tab-bar";case"button":case"imagebutton":return"button";case"link":return"link";case"cell":return"cell";case"statictext":case"checkedtextview":return"text";case"textfield":case"edittext":return"text-field";case"textview":return r?"text":"text-view";case"textarea":return"text-view";case"switch":return"switch";case"slider":return"slider";case"image":case"imageview":return"image";case"webview":return"webview";case"framelayout":case"linearlayout":case"relativelayout":case"constraintlayout":case"viewgroup":case"view":case"group":return"group";case"listview":case"recyclerview":return"list";case"collectionview":return"collection";case"searchfield":return"search";case"segmentedcontrol":return"segmented-control";case"window":return"window";case"checkbox":return"checkbox";case"radio":return"radio";case"menuitem":return"menu-item";case"toolbar":return"toolbar";case"scrollarea":case"scrollview":case"nestedscrollview":return"scroll-area";case"table":return"table";default:return t||"element"}}function x(){try{let e=C();return JSON.parse(r.readFileSync(i.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}function C(){let e=i.dirname(a(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=i.join(t,"package.json");if(r.existsSync(e))return t;t=i.dirname(t)}return e}async function R(e,t,r={}){return new Promise((n,o)=>{let i=c(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached}),a="",s=r.binaryStdout?Buffer.alloc(0):void 0,l="",d=!1,u=j(r.timeoutMs),f=u?setTimeout(()=>{d=!0,i.kill("SIGKILL")},u):null;r.binaryStdout||i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),void 0!==r.stdin&&i.stdin.write(r.stdin),i.stdin.end(),i.stdout.on("data",e=>{r.binaryStdout?s=Buffer.concat([s??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):a+=e}),i.stderr.on("data",e=>{l+=e}),i.on("error",r=>{(f&&clearTimeout(f),"ENOENT"===r.code)?o(new A("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new A("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),i.on("close",i=>{f&&clearTimeout(f);let c=i??1;d&&u?o(new A("COMMAND_FAILED",`${e} timed out after ${u}ms`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:c,timeoutMs:u})):0===c||r.allowFailure?n({stdout:a,stderr:l,exitCode:c,stdoutBuffer:s}):o(new A("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:c,processExitError:!0}))})})}async function F(e){try{var t;let{shell:r,args:n}=(t=e,"win32"===process.platform?{shell:"cmd.exe",args:["/c","where",t]}:{shell:"bash",args:["-lc",`command -v ${t}`]}),o=await R(r,n,{allowFailure:!0});return 0===o.exitCode&&o.stdout.trim().length>0}catch{return!1}}function P(e,t,r={}){let n=l(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:j(r.timeoutMs)});if(n.error){let o=n.error.code;if("ETIMEDOUT"===o)throw new A("COMMAND_FAILED",`${e} timed out after ${j(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:j(r.timeoutMs)},n.error);if("ENOENT"===o)throw new A("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new A("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},n.error)}let o=r.binaryStdout?Buffer.isBuffer(n.stdout)?n.stdout:Buffer.from(n.stdout??""):void 0,i=r.binaryStdout?"":"string"==typeof n.stdout?n.stdout:(n.stdout??"").toString(),a="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),s=n.status??1;if(0!==s&&!r.allowFailure)throw new A("COMMAND_FAILED",`${e} exited with code ${s}`,{cmd:e,args:t,stdout:i,stderr:a,exitCode:s,processExitError:!0});return{stdout:i,stderr:a,exitCode:s,stdoutBuffer:o}}function k(e,t,r={}){c(e,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}async function B(e,t,r={}){return new Promise((n,o)=>{let i=c(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached});r.onSpawn?.(i);let a="",s="",l=r.binaryStdout?Buffer.alloc(0):void 0;r.binaryStdout||i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),void 0!==r.stdin&&i.stdin.write(r.stdin),i.stdin.end(),i.stdout.on("data",e=>{if(r.binaryStdout){l=Buffer.concat([l??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]);return}let t=String(e);a+=t,r.onStdoutChunk?.(t)}),i.stderr.on("data",e=>{let t=String(e);s+=t,r.onStderrChunk?.(t)}),i.on("error",r=>{"ENOENT"===r.code?o(new A("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new A("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),i.on("close",i=>{let c=i??1;0===c||r.allowFailure?n({stdout:a,stderr:s,exitCode:c,stdoutBuffer:l}):o(new A("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:a,stderr:s,exitCode:c,processExitError:!0}))})})}function G(e,t,r={}){let n=c(e,t,{cwd:r.cwd,env:r.env,stdio:["ignore","pipe","pipe"],detached:r.detached}),o="",i="";n.stdout.setEncoding("utf8"),n.stderr.setEncoding("utf8"),n.stdout.on("data",e=>{o+=e}),n.stderr.on("data",e=>{i+=e});let a=new Promise((a,s)=>{n.on("error",r=>{"ENOENT"===r.code?s(new A("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):s(new A("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),n.on("close",n=>{let c=n??1;0===c||r.allowFailure?a({stdout:o,stderr:i,exitCode:c}):s(new A("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:o,stderr:i,exitCode:c,processExitError:!0}))})});return{child:n,wait:a}}function j(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}let U=[/(^|[\/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[\/\s"'=])src\/daemon\.ts($|[\s"'])/];function V(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function q(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=P("ps",["-p",String(e),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function H(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=P("ps",["-p",String(e),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function J(e,t){let r;if(!V(e))return!1;if(t){let r=q(e);if(!r||r!==t)return!1}let n=H(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&U.some(e=>e.test(r))}function W(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function z(e,t){if(!V(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!V(e))return!0;return!V(e)}async function K(e,t){!J(e,t.expectedStartTime)||!W(e,"SIGTERM")||await z(e,t.termTimeoutMs)||W(e,"SIGKILL")&&await z(e,t.killTimeoutMs)}let X=100,Z=new Set(["batch","replay"]);function Q(e){let t;try{t=JSON.parse(e)}catch{throw new A("INVALID_ARGS","Batch steps must be valid JSON.")}if(!Array.isArray(t)||0===t.length)throw new A("INVALID_ARGS","Batch steps must be a non-empty JSON array.");return t}function Y(e,t){if(!Array.isArray(e)||0===e.length)throw new A("INVALID_ARGS","batch requires a non-empty batchSteps array.");if(e.length>t)throw new A("INVALID_ARGS",`batch has ${e.length} steps; max allowed is ${t}.`);let r=[];for(let t=0;t<e.length;t+=1){let n=e[t];if(!n||"object"!=typeof n)throw new A("INVALID_ARGS",`Invalid batch step at index ${t}.`);let o="string"==typeof n.command?n.command.trim().toLowerCase():"";if(!o)throw new A("INVALID_ARGS",`Batch step ${t+1} requires command.`);if(Z.has(o))throw new A("INVALID_ARGS",`Batch step ${t+1} cannot run ${o}.`);if(void 0!==n.positionals&&!Array.isArray(n.positionals))throw new A("INVALID_ARGS",`Batch step ${t+1} positionals must be an array.`);let i=n.positionals??[];if(i.some(e=>"string"!=typeof e))throw new A("INVALID_ARGS",`Batch step ${t+1} positionals must contain only strings.`);if(void 0!==n.flags&&("object"!=typeof n.flags||Array.isArray(n.flags)||!n.flags))throw new A("INVALID_ARGS",`Batch step ${t+1} flags must be an object.`);r.push({command:o,positionals:i,flags:n.flags??{}})}return r}export{default as node_net}from"node:net";export{A as AppError,X as DEFAULT_BATCH_MAX_STEPS,_ as SETTINGS_INVALID_ARGS_MESSAGE,$ as SETTINGS_USAGE_OVERRIDE,I as asAppError,M as buildSnapshotDisplayLines,m as createRequestId,T as displayLabel,g as emitDiagnostic,a as fileURLToPath,C as findProjectRoot,S as flushDiagnosticsToSessionFile,L as formatRole,O as formatSnapshotLine,h as getDiagnosticsMeta,J as isAgentDeviceDaemonProcess,V as isProcessAlive,t as node_crypto,r as node_fs,o as node_os,i as node_path,E as normalizeError,Q as parseBatchStepsJson,s as pathToFileURL,n as promises,H as readProcessCommand,q as readProcessStartTime,x as readVersion,R as runCmd,G as runCmdBackground,k as runCmdDetached,B as runCmdStreaming,P as runCmdSync,c as spawn,K as stopProcessForTakeover,Y as validateAndNormalizeBatchSteps,F as whichCmd,w as withDiagnosticTimer,p as withDiagnosticsScope};
|