agent-device 0.5.0 → 0.5.1
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 +53 -2
- package/dist/src/50.js +1 -0
- package/dist/src/bin.js +31 -30
- package/dist/src/daemon.js +17 -16
- package/package.json +1 -1
- package/skills/agent-device/SKILL.md +45 -0
- package/skills/agent-device/references/batching.md +79 -0
- package/dist/src/797.js +0 -1
package/package.json
CHANGED
|
@@ -155,6 +155,50 @@ agent-device replay -u ./session.ad # Update selector drift and rewrite .ad sc
|
|
|
155
155
|
`--save-script` path is a file path; parent directories are created automatically.
|
|
156
156
|
For ambiguous bare values, use `--save-script=workflow.ad` or `./workflow.ad`.
|
|
157
157
|
|
|
158
|
+
### Fast batching (JSON steps)
|
|
159
|
+
|
|
160
|
+
Use `batch` when an agent already has a known short sequence and wants fewer orchestration round trips.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
agent-device batch \
|
|
164
|
+
--session sim \
|
|
165
|
+
--platform ios \
|
|
166
|
+
--udid 00008150-001849640CF8401C \
|
|
167
|
+
--steps-file /tmp/batch-steps.json \
|
|
168
|
+
--json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Inline JSON works for small payloads:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
agent-device batch --steps '[{"command":"open","positionals":["settings"]},{"command":"wait","positionals":["100"]}]'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Step format:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
[
|
|
181
|
+
{ "command": "open", "positionals": ["settings"], "flags": {} },
|
|
182
|
+
{ "command": "wait", "positionals": ["label=\"Privacy & Security\"", "3000"], "flags": {} },
|
|
183
|
+
{ "command": "click", "positionals": ["label=\"Privacy & Security\""], "flags": {} },
|
|
184
|
+
{ "command": "get", "positionals": ["text", "label=\"Tracking\""], "flags": {} }
|
|
185
|
+
]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Batch best practices:
|
|
189
|
+
|
|
190
|
+
- Batch one screen-local flow at a time.
|
|
191
|
+
- Add sync guards (`wait`, `is exists`) after mutating steps (`open`, `click`, `fill`, `swipe`).
|
|
192
|
+
- Treat prior refs/snapshot assumptions as stale after UI mutations.
|
|
193
|
+
- Prefer `--steps-file` over inline JSON.
|
|
194
|
+
- Keep batches moderate (about 5-20 steps).
|
|
195
|
+
- Use failure context (`step`, `partialResults`) to replan from the failed step.
|
|
196
|
+
|
|
197
|
+
Stale accessibility tree note:
|
|
198
|
+
|
|
199
|
+
- Rapid mutations can outrun accessibility tree updates.
|
|
200
|
+
- Mitigate with explicit waits and phase splitting (navigate, verify/extract, cleanup).
|
|
201
|
+
|
|
158
202
|
### Trace logs (XCTest)
|
|
159
203
|
|
|
160
204
|
```bash
|
|
@@ -208,3 +252,4 @@ agent-device apps --platform android --user-installed
|
|
|
208
252
|
- [references/permissions.md](references/permissions.md)
|
|
209
253
|
- [references/video-recording.md](references/video-recording.md)
|
|
210
254
|
- [references/coordinate-system.md](references/coordinate-system.md)
|
|
255
|
+
- [references/batching.md](references/batching.md)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Batching
|
|
2
|
+
|
|
3
|
+
## When to use batch
|
|
4
|
+
|
|
5
|
+
- The agent already knows a short sequence of commands.
|
|
6
|
+
- Steps belong to one logical screen flow.
|
|
7
|
+
- You want one result object with per-step timing and failure context.
|
|
8
|
+
|
|
9
|
+
## When not to use batch
|
|
10
|
+
|
|
11
|
+
- Flows are unrelated and should be retried independently.
|
|
12
|
+
- The workflow is highly dynamic and requires replanning after each step.
|
|
13
|
+
- You need human approvals between steps.
|
|
14
|
+
|
|
15
|
+
## CLI patterns
|
|
16
|
+
|
|
17
|
+
From file:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
agent-device batch --session sim --platform ios --steps-file /tmp/batch-steps.json --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Inline (small payloads only):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
agent-device batch --steps '[{"command":"open","positionals":["settings"]}]'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Step payload contract
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
[
|
|
33
|
+
{ "command": "open", "positionals": ["settings"], "flags": {} },
|
|
34
|
+
{ "command": "wait", "positionals": ["label=\"Privacy & Security\"", "3000"], "flags": {} },
|
|
35
|
+
{ "command": "click", "positionals": ["label=\"Privacy & Security\""], "flags": {} },
|
|
36
|
+
{ "command": "get", "positionals": ["text", "label=\"Tracking\""], "flags": {} }
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Rules:
|
|
41
|
+
|
|
42
|
+
- `positionals` optional, defaults to `[]`.
|
|
43
|
+
- `flags` optional, defaults to `{}`.
|
|
44
|
+
- nested `batch` and `replay` are rejected.
|
|
45
|
+
- stop-on-first-error is the supported mode (`--on-error stop`).
|
|
46
|
+
|
|
47
|
+
## Response handling
|
|
48
|
+
|
|
49
|
+
Success includes:
|
|
50
|
+
|
|
51
|
+
- `total`, `executed`, `totalDurationMs`
|
|
52
|
+
- `results[]` entries with `step`, `command`, `durationMs`, and optional `data`
|
|
53
|
+
|
|
54
|
+
Failure includes:
|
|
55
|
+
|
|
56
|
+
- `details.step`
|
|
57
|
+
- `details.command`
|
|
58
|
+
- `details.executed`
|
|
59
|
+
- `details.partialResults`
|
|
60
|
+
|
|
61
|
+
Use these fields to replan from the first failing step.
|
|
62
|
+
|
|
63
|
+
## Common error categories and agent actions
|
|
64
|
+
|
|
65
|
+
- `INVALID_ARGS`: payload/step shape issue; fix payload and retry.
|
|
66
|
+
- `SESSION_NOT_FOUND`: open or select the correct session, then retry.
|
|
67
|
+
- `UNSUPPORTED_OPERATION`: switch command/target to supported operation.
|
|
68
|
+
- `AMBIGUOUS_MATCH`: refine selector/locator, then retry failed step.
|
|
69
|
+
- `COMMAND_FAILED`: add sync guard (`wait`, `is exists`) and retry from failed step.
|
|
70
|
+
|
|
71
|
+
## Reliability guardrails
|
|
72
|
+
|
|
73
|
+
- Add sync guards after mutating steps.
|
|
74
|
+
- Assume snapshot/ref drift after navigation.
|
|
75
|
+
- Keep batch size moderate (about 5-20 steps).
|
|
76
|
+
- Split long workflows into phases:
|
|
77
|
+
1. navigate
|
|
78
|
+
2. verify/extract
|
|
79
|
+
3. cleanup
|
package/dist/src/797.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e,{promises as t}from"node:fs";import r from"node:path";import{fileURLToPath as n,pathToFileURL as o}from"node:url";import{spawn as i,spawnSync as u}from"node:child_process";class s extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function d(e){return e instanceof s?e:e instanceof Error?new s("UNKNOWN",e.message,void 0,e):new s("UNKNOWN","Unknown error",{err:e})}function a(){try{let t=l();return JSON.parse(e.readFileSync(r.join(t,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}function l(){let t=r.dirname(n(import.meta.url)),o=t;for(let t=0;t<6;t+=1){let t=r.join(o,"package.json");if(e.existsSync(t))return o;o=r.dirname(o)}return t}async function c(e,t,r={}){return new Promise((n,o)=>{let u=i(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"]}),d="",a=r.binaryStdout?Buffer.alloc(0):void 0,l="",c=!1,f=h(r.timeoutMs),m=f?setTimeout(()=>{c=!0,u.kill("SIGKILL")},f):null;r.binaryStdout||u.stdout.setEncoding("utf8"),u.stderr.setEncoding("utf8"),void 0!==r.stdin&&u.stdin.write(r.stdin),u.stdin.end(),u.stdout.on("data",e=>{r.binaryStdout?a=Buffer.concat([a??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):d+=e}),u.stderr.on("data",e=>{l+=e}),u.on("error",r=>{(m&&clearTimeout(m),"ENOENT"===r.code)?o(new s("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new s("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),u.on("close",i=>{m&&clearTimeout(m);let u=i??1;c&&f?o(new s("COMMAND_FAILED",`${e} timed out after ${f}ms`,{cmd:e,args:t,stdout:d,stderr:l,exitCode:u,timeoutMs:f})):0===u||r.allowFailure?n({stdout:d,stderr:l,exitCode:u,stdoutBuffer:a}):o(new s("COMMAND_FAILED",`${e} exited with code ${u}`,{cmd:e,args:t,stdout:d,stderr:l,exitCode:u}))})})}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 c(r,n,{allowFailure:!0});return 0===o.exitCode&&o.stdout.trim().length>0}catch{return!1}}function m(e,t,r={}){let n=u(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:h(r.timeoutMs)});if(n.error){let o=n.error.code;if("ETIMEDOUT"===o)throw new s("COMMAND_FAILED",`${e} timed out after ${h(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:h(r.timeoutMs)},n.error);if("ENOENT"===o)throw new s("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new s("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(),d="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),a=n.status??1;if(0!==a&&!r.allowFailure)throw new s("COMMAND_FAILED",`${e} exited with code ${a}`,{cmd:e,args:t,stdout:i,stderr:d,exitCode:a});return{stdout:i,stderr:d,exitCode:a,stdoutBuffer:o}}function w(e,t,r={}){i(e,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}async function p(e,t,r={}){return new Promise((n,o)=>{let u=i(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"]}),d="",a="",l=r.binaryStdout?Buffer.alloc(0):void 0;r.binaryStdout||u.stdout.setEncoding("utf8"),u.stderr.setEncoding("utf8"),void 0!==r.stdin&&u.stdin.write(r.stdin),u.stdin.end(),u.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);d+=t,r.onStdoutChunk?.(t)}),u.stderr.on("data",e=>{let t=String(e);a+=t,r.onStderrChunk?.(t)}),u.on("error",r=>{"ENOENT"===r.code?o(new s("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new s("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),u.on("close",i=>{let u=i??1;0===u||r.allowFailure?n({stdout:d,stderr:a,exitCode:u,stdoutBuffer:l}):o(new s("COMMAND_FAILED",`${e} exited with code ${u}`,{cmd:e,args:t,stdout:d,stderr:a,exitCode:u}))})})}function M(e,t,r={}){let n=i(e,t,{cwd:r.cwd,env:r.env,stdio:["ignore","pipe","pipe"]}),o="",u="";n.stdout.setEncoding("utf8"),n.stderr.setEncoding("utf8"),n.stdout.on("data",e=>{o+=e}),n.stderr.on("data",e=>{u+=e});let d=new Promise((i,d)=>{n.on("error",r=>{"ENOENT"===r.code?d(new s("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):d(new s("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),n.on("close",n=>{let a=n??1;0===a||r.allowFailure?i({stdout:o,stderr:u,exitCode:a}):d(new s("COMMAND_FAILED",`${e} exited with code ${a}`,{cmd:e,args:t,stdout:o,stderr:u,exitCode:a}))})});return{child:n,wait:d}}function h(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}let E=[/(^|[\/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[\/\s"'=])src\/daemon\.ts($|[\s"'])/];function S(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function N(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=m("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 g(e,t){let r;if(!S(e))return!1;if(t){let r=N(e);if(!r||r!==t)return!1}let n=function(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=m("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}}(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&E.some(e=>e.test(r))}function A(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 I(e,t){if(!S(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!S(e))return!0;return!S(e)}async function D(e,t){!g(e,t.expectedStartTime)||!A(e,"SIGTERM")||await I(e,t.termTimeoutMs)||A(e,"SIGKILL")&&await I(e,t.killTimeoutMs)}export{default as node_net}from"node:net";export{default as node_os}from"node:os";export{s as AppError,d as asAppError,n as fileURLToPath,l as findProjectRoot,g as isAgentDeviceDaemonProcess,S as isProcessAlive,e as node_fs,r as node_path,o as pathToFileURL,t as promises,N as readProcessStartTime,a as readVersion,c as runCmd,M as runCmdBackground,w as runCmdDetached,p as runCmdStreaming,D as stopProcessForTakeover,f as whichCmd};
|