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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -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};