mobster-cli 0.4.3__py3-none-any.whl

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 (1009) hide show
  1. mobile_agent/__init__.py +4 -0
  2. mobile_agent/__main__.py +598 -0
  3. mobile_agent/accounts/__init__.py +1 -0
  4. mobile_agent/accounts/context.py +80 -0
  5. mobile_agent/accounts/mcp.py +55 -0
  6. mobile_agent/accounts/pacing.py +318 -0
  7. mobile_agent/accounts/register.py +78 -0
  8. mobile_agent/accounts/routes.py +212 -0
  9. mobile_agent/accounts/routing.py +69 -0
  10. mobile_agent/accounts/screen.py +273 -0
  11. mobile_agent/accounts/stats.py +54 -0
  12. mobile_agent/accounts/store.py +394 -0
  13. mobile_agent/agent_hooks.py +123 -0
  14. mobile_agent/ai_plans/__init__.py +7 -0
  15. mobile_agent/ai_plans/agents.py +96 -0
  16. mobile_agent/ai_plans/claude_code.py +208 -0
  17. mobile_agent/ai_plans/cli.py +400 -0
  18. mobile_agent/ai_plans/codex.py +258 -0
  19. mobile_agent/ai_plans/compat_chat.py +307 -0
  20. mobile_agent/ai_plans/device_login.py +164 -0
  21. mobile_agent/ai_plans/draft_client.py +68 -0
  22. mobile_agent/ai_plans/gemini.py +137 -0
  23. mobile_agent/ai_plans/instructions.py +65 -0
  24. mobile_agent/ai_plans/mcp_http.py +160 -0
  25. mobile_agent/ai_plans/phone_tools.py +892 -0
  26. mobile_agent/ai_plans/providers.py +235 -0
  27. mobile_agent/ai_plans/routes.py +160 -0
  28. mobile_agent/ai_plans/runner.py +400 -0
  29. mobile_agent/ai_plans/vault.py +114 -0
  30. mobile_agent/alerts.py +337 -0
  31. mobile_agent/android/__init__.py +18 -0
  32. mobile_agent/android/adb.py +268 -0
  33. mobile_agent/android/conditions.py +128 -0
  34. mobile_agent/android/doctor.py +54 -0
  35. mobile_agent/android/driver.py +137 -0
  36. mobile_agent/android/emulator.py +216 -0
  37. mobile_agent/android/logs.py +85 -0
  38. mobile_agent/android/manager.py +225 -0
  39. mobile_agent/android/records.py +31 -0
  40. mobile_agent/android/tree.py +96 -0
  41. mobile_agent/answer_shapes.py +230 -0
  42. mobile_agent/api_devices.py +333 -0
  43. mobile_agent/api_errors.py +17 -0
  44. mobile_agent/api_get.py +249 -0
  45. mobile_agent/api_http.py +467 -0
  46. mobile_agent/api_post.py +453 -0
  47. mobile_agent/api_routes.py +173 -0
  48. mobile_agent/app_api/__init__.py +4 -0
  49. mobile_agent/app_api/cli.py +151 -0
  50. mobile_agent/app_api/endpoints.py +193 -0
  51. mobile_agent/app_api/mcp.py +54 -0
  52. mobile_agent/app_api/openapi.py +146 -0
  53. mobile_agent/app_api/reading.py +172 -0
  54. mobile_agent/app_api/register.py +10 -0
  55. mobile_agent/app_api/screen.py +216 -0
  56. mobile_agent/app_api/service.py +382 -0
  57. mobile_agent/app_api/sessions.py +404 -0
  58. mobile_agent/app_api/tokens.py +167 -0
  59. mobile_agent/app_choice.py +131 -0
  60. mobile_agent/app_icons.py +183 -0
  61. mobile_agent/app_killer.py +69 -0
  62. mobile_agent/artifacts.py +606 -0
  63. mobile_agent/attach/__init__.py +13 -0
  64. mobile_agent/attach/cli.py +46 -0
  65. mobile_agent/attach/flutter.py +28 -0
  66. mobile_agent/attach/score.py +119 -0
  67. mobile_agent/attach/web.py +13 -0
  68. mobile_agent/attachments/__init__.py +1 -0
  69. mobile_agent/attachments/agent.py +418 -0
  70. mobile_agent/attachments/extract.py +217 -0
  71. mobile_agent/attachments/mcp.py +104 -0
  72. mobile_agent/attachments/register.py +73 -0
  73. mobile_agent/attachments/routes.py +326 -0
  74. mobile_agent/attachments/service.py +170 -0
  75. mobile_agent/attachments/sniff.py +152 -0
  76. mobile_agent/attachments/store.py +441 -0
  77. mobile_agent/audit_verify.py +436 -0
  78. mobile_agent/bench/README.md +410 -0
  79. mobile_agent/bench/__init__.py +0 -0
  80. mobile_agent/bench/__main__.py +394 -0
  81. mobile_agent/bench/ab.py +168 -0
  82. mobile_agent/bench/actuation.py +368 -0
  83. mobile_agent/bench/agents/__init__.py +9 -0
  84. mobile_agent/bench/agents/base.py +105 -0
  85. mobile_agent/bench/agents/plan_clients.py +245 -0
  86. mobile_agent/bench/agents/smart.py +533 -0
  87. mobile_agent/bench/checks.py +358 -0
  88. mobile_agent/bench/decide_gate.py +214 -0
  89. mobile_agent/bench/diagnose.py +181 -0
  90. mobile_agent/bench/evals/__init__.py +14 -0
  91. mobile_agent/bench/evals/build.py +565 -0
  92. mobile_agent/bench/evals/checkpoint.py +214 -0
  93. mobile_agent/bench/evals/irt.py +219 -0
  94. mobile_agent/bench/evals/l0.py +647 -0
  95. mobile_agent/bench/evals/l1.py +866 -0
  96. mobile_agent/bench/evals/models.py +124 -0
  97. mobile_agent/bench/evals/pairwise.py +189 -0
  98. mobile_agent/bench/evals/proxy.py +90 -0
  99. mobile_agent/bench/evals/sets.py +149 -0
  100. mobile_agent/bench/evals/traces.py +446 -0
  101. mobile_agent/bench/fixtures.py +176 -0
  102. mobile_agent/bench/guard_replay.py +129 -0
  103. mobile_agent/bench/h2-manifest.json +20 -0
  104. mobile_agent/bench/h3-manifest.json +20 -0
  105. mobile_agent/bench/heldout-manifest.json +32 -0
  106. mobile_agent/bench/heldout.py +375 -0
  107. mobile_agent/bench/iosworld.py +901 -0
  108. mobile_agent/bench/iosworld_stats.py +510 -0
  109. mobile_agent/bench/llm_units.py +116 -0
  110. mobile_agent/bench/manifest.json +93 -0
  111. mobile_agent/bench/metrics.py +66 -0
  112. mobile_agent/bench/phone.py +44 -0
  113. mobile_agent/bench/replay.py +172 -0
  114. mobile_agent/bench/report.py +207 -0
  115. mobile_agent/bench/runner.py +456 -0
  116. mobile_agent/bench/safety.py +310 -0
  117. mobile_agent/bench/stream_timing.py +213 -0
  118. mobile_agent/bench/suite.py +444 -0
  119. mobile_agent/bench/sweep.py +176 -0
  120. mobile_agent/bench/tape.py +798 -0
  121. mobile_agent/bench/tape_replay.py +88 -0
  122. mobile_agent/bench/trace_evals.py +128 -0
  123. mobile_agent/bench/trace_view.py +81 -0
  124. mobile_agent/bench/truth/iphone15pro.json +138 -0
  125. mobile_agent/bench/truth.py +340 -0
  126. mobile_agent/bench/tuning.py +543 -0
  127. mobile_agent/bench/tuning_analysis.py +656 -0
  128. mobile_agent/bench/vague-heldout-manifest.json +12 -0
  129. mobile_agent/bench/vague_rubric.py +135 -0
  130. mobile_agent/bench/vague_run.py +243 -0
  131. mobile_agent/brand.py +454 -0
  132. mobile_agent/brand_fonts.py +756 -0
  133. mobile_agent/build_identity.py +21 -0
  134. mobile_agent/cable_bt.py +623 -0
  135. mobile_agent/cable_bt_runtime.py +128 -0
  136. mobile_agent/catalog.py +69 -0
  137. mobile_agent/chart_svg.py +415 -0
  138. mobile_agent/chat_here.py +189 -0
  139. mobile_agent/cli_constants.py +5 -0
  140. mobile_agent/cli_parser.py +611 -0
  141. mobile_agent/cli_run.py +115 -0
  142. mobile_agent/compiled_read/__init__.py +21 -0
  143. mobile_agent/compiled_read/delta.py +89 -0
  144. mobile_agent/compiled_read/extractors.py +202 -0
  145. mobile_agent/compiled_read/routes.py +56 -0
  146. mobile_agent/completion.py +267 -0
  147. mobile_agent/compose.py +48 -0
  148. mobile_agent/config.py +114 -0
  149. mobile_agent/connectors/__init__.py +1 -0
  150. mobile_agent/connectors/api_session.py +76 -0
  151. mobile_agent/connectors/callback_page.py +146 -0
  152. mobile_agent/connectors/client.py +582 -0
  153. mobile_agent/connectors/github_app.py +55 -0
  154. mobile_agent/connectors/graph_api.py +396 -0
  155. mobile_agent/connectors/imports.py +170 -0
  156. mobile_agent/connectors/issue.py +66 -0
  157. mobile_agent/connectors/keystore.py +85 -0
  158. mobile_agent/connectors/marks.py +222 -0
  159. mobile_agent/connectors/oauth.py +476 -0
  160. mobile_agent/connectors/picker.py +228 -0
  161. mobile_agent/connectors/presets.py +591 -0
  162. mobile_agent/connectors/register.py +48 -0
  163. mobile_agent/connectors/routes.py +153 -0
  164. mobile_agent/connectors/service.py +966 -0
  165. mobile_agent/connectors/shared.py +136 -0
  166. mobile_agent/connectors/skill.py +446 -0
  167. mobile_agent/connectors/slack_api.py +269 -0
  168. mobile_agent/connectors/steps.py +92 -0
  169. mobile_agent/connectors/store.py +144 -0
  170. mobile_agent/connectors/vault.py +72 -0
  171. mobile_agent/console.py +178 -0
  172. mobile_agent/content_queue/__init__.py +1 -0
  173. mobile_agent/content_queue/client.py +263 -0
  174. mobile_agent/content_queue/importing.py +153 -0
  175. mobile_agent/content_queue/media_check.py +224 -0
  176. mobile_agent/content_queue/prompt.py +64 -0
  177. mobile_agent/content_queue/register.py +91 -0
  178. mobile_agent/content_queue/routes.py +370 -0
  179. mobile_agent/content_queue/runner.py +475 -0
  180. mobile_agent/content_queue/store.py +521 -0
  181. mobile_agent/contract.py +996 -0
  182. mobile_agent/contract_asks.py +248 -0
  183. mobile_agent/contract_prompt.py +45 -0
  184. mobile_agent/contract_screen.py +981 -0
  185. mobile_agent/contract_view.py +84 -0
  186. mobile_agent/corpus/__init__.py +26 -0
  187. mobile_agent/corpus/cli.py +63 -0
  188. mobile_agent/corpus/consent.py +55 -0
  189. mobile_agent/corpus/listener.py +76 -0
  190. mobile_agent/corpus/register.py +9 -0
  191. mobile_agent/corpus/skeleton.py +72 -0
  192. mobile_agent/corpus/store.py +143 -0
  193. mobile_agent/costs.py +161 -0
  194. mobile_agent/decider_scope.py +40 -0
  195. mobile_agent/decision_provider.py +189 -0
  196. mobile_agent/deeplinks.py +352 -0
  197. mobile_agent/device_info.py +116 -0
  198. mobile_agent/device_manager.py +978 -0
  199. mobile_agent/device_status.py +195 -0
  200. mobile_agent/device_targets.py +217 -0
  201. mobile_agent/devices.py +676 -0
  202. mobile_agent/devkit/__init__.py +64 -0
  203. mobile_agent/devkit/account_cli.py +90 -0
  204. mobile_agent/devkit/asc.py +562 -0
  205. mobile_agent/devkit/asc_cli.py +214 -0
  206. mobile_agent/devkit/ci.py +737 -0
  207. mobile_agent/devkit/ci_cli.py +196 -0
  208. mobile_agent/devkit/ci_hosts.py +194 -0
  209. mobile_agent/devkit/es256.py +197 -0
  210. mobile_agent/devkit/expo.py +440 -0
  211. mobile_agent/devkit/expo_cli.py +405 -0
  212. mobile_agent/devkit/expo_dev.py +203 -0
  213. mobile_agent/devkit/expo_routes.py +90 -0
  214. mobile_agent/devkit/fixtures_cli.py +133 -0
  215. mobile_agent/devkit/fixtures_routes.py +82 -0
  216. mobile_agent/devkit/links.py +375 -0
  217. mobile_agent/devkit/links_cli.py +123 -0
  218. mobile_agent/devkit/links_routes.py +83 -0
  219. mobile_agent/devkit/nightly.py +152 -0
  220. mobile_agent/devkit/shots.py +203 -0
  221. mobile_agent/devkit/testflight.py +155 -0
  222. mobile_agent/devkit/testflight_cli.py +127 -0
  223. mobile_agent/devkit/testflight_routes.py +111 -0
  224. mobile_agent/devkit/xcode.py +236 -0
  225. mobile_agent/devkit/xcode_cli.py +101 -0
  226. mobile_agent/devtools.py +419 -0
  227. mobile_agent/doctor.py +509 -0
  228. mobile_agent/doctor_fix.py +467 -0
  229. mobile_agent/downloads.py +55 -0
  230. mobile_agent/drivers.py +793 -0
  231. mobile_agent/dry_run.py +102 -0
  232. mobile_agent/effect_ledger.py +83 -0
  233. mobile_agent/engine_health.py +378 -0
  234. mobile_agent/engine_hooks.py +96 -0
  235. mobile_agent/engines.py +408 -0
  236. mobile_agent/env_file.py +63 -0
  237. mobile_agent/env_secrets.py +149 -0
  238. mobile_agent/errors.py +15 -0
  239. mobile_agent/evals/__init__.py +3 -0
  240. mobile_agent/evals/app_survey.py +149 -0
  241. mobile_agent/evals/development/__init__.py +1 -0
  242. mobile_agent/evals/development/score.py +276 -0
  243. mobile_agent/evals/latency_report.py +312 -0
  244. mobile_agent/evals/offline_report.py +374 -0
  245. mobile_agent/evals/oracles.py +298 -0
  246. mobile_agent/evals/redteam/__init__.py +15 -0
  247. mobile_agent/evals/redteam/__main__.py +72 -0
  248. mobile_agent/evals/redteam/cases.py +730 -0
  249. mobile_agent/evals/redteam/harness.py +604 -0
  250. mobile_agent/evals/redteam/live.py +139 -0
  251. mobile_agent/evals/redteam/plain.py +85 -0
  252. mobile_agent/evals/vision/__init__.py +0 -0
  253. mobile_agent/evals/vision/images/dog_001.jpg +0 -0
  254. mobile_agent/evals/vision/images/dog_002.jpg +0 -0
  255. mobile_agent/evals/vision/images/dog_003.jpg +0 -0
  256. mobile_agent/evals/vision/images/dog_004.jpg +0 -0
  257. mobile_agent/evals/vision/images/dog_005.jpg +0 -0
  258. mobile_agent/evals/vision/images/dog_006.jpg +0 -0
  259. mobile_agent/evals/vision/images/dog_007.jpg +0 -0
  260. mobile_agent/evals/vision/images/dog_010.jpg +0 -0
  261. mobile_agent/evals/vision/images/dog_011.jpg +0 -0
  262. mobile_agent/evals/vision/images/dog_013.jpg +0 -0
  263. mobile_agent/evals/vision/images/dog_014.jpg +0 -0
  264. mobile_agent/evals/vision/images/dog_015.jpg +0 -0
  265. mobile_agent/evals/vision/images/dog_016.jpg +0 -0
  266. mobile_agent/evals/vision/images/dog_017.jpg +0 -0
  267. mobile_agent/evals/vision/images/dog_019.jpg +0 -0
  268. mobile_agent/evals/vision/images/dog_020.jpg +0 -0
  269. mobile_agent/evals/vision/images/dog_021.jpg +0 -0
  270. mobile_agent/evals/vision/images/dog_022.jpg +0 -0
  271. mobile_agent/evals/vision/images/dog_023.jpg +0 -0
  272. mobile_agent/evals/vision/images/dog_024.jpg +0 -0
  273. mobile_agent/evals/vision/images/dog_026.jpg +0 -0
  274. mobile_agent/evals/vision/images/dog_027.jpg +0 -0
  275. mobile_agent/evals/vision/images/dog_034.jpg +0 -0
  276. mobile_agent/evals/vision/images/dog_035.jpg +0 -0
  277. mobile_agent/evals/vision/images/dog_038.jpg +0 -0
  278. mobile_agent/evals/vision/images/dog_042.jpg +0 -0
  279. mobile_agent/evals/vision/images/dog_047.jpg +0 -0
  280. mobile_agent/evals/vision/images/dog_052.jpg +0 -0
  281. mobile_agent/evals/vision/images/dog_058.jpg +0 -0
  282. mobile_agent/evals/vision/images/dog_062.jpg +0 -0
  283. mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
  284. mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
  285. mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
  286. mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
  287. mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
  288. mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
  289. mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
  290. mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
  291. mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
  292. mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
  293. mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
  294. mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
  295. mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
  296. mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
  297. mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
  298. mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
  299. mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
  300. mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
  301. mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
  302. mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
  303. mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
  304. mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
  305. mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
  306. mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
  307. mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
  308. mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
  309. mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
  310. mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
  311. mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
  312. mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
  313. mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
  314. mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
  315. mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
  316. mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
  317. mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
  318. mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
  319. mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
  320. mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
  321. mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
  322. mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
  323. mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
  324. mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
  325. mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
  326. mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
  327. mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
  328. mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
  329. mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
  330. mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
  331. mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
  332. mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
  333. mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
  334. mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
  335. mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
  336. mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
  337. mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
  338. mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
  339. mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
  340. mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
  341. mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
  342. mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
  343. mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
  344. mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
  345. mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
  346. mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
  347. mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
  348. mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
  349. mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
  350. mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
  351. mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
  352. mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
  353. mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
  354. mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
  355. mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
  356. mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
  357. mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
  358. mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
  359. mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
  360. mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
  361. mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
  362. mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
  363. mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
  364. mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
  365. mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
  366. mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
  367. mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
  368. mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
  369. mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
  370. mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
  371. mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
  372. mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
  373. mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
  374. mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
  375. mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
  376. mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
  377. mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
  378. mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
  379. mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
  380. mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
  381. mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
  382. mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
  383. mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
  384. mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
  385. mobile_agent/evals/vision/manifest.json +2355 -0
  386. mobile_agent/eventbus.py +110 -0
  387. mobile_agent/explain.py +34 -0
  388. mobile_agent/failure_line.py +197 -0
  389. mobile_agent/fanout.py +663 -0
  390. mobile_agent/feedback/__init__.py +2 -0
  391. mobile_agent/feedback/bundle.py +204 -0
  392. mobile_agent/feedback/outbox.py +196 -0
  393. mobile_agent/feedback/register.py +9 -0
  394. mobile_agent/feedback/routes.py +177 -0
  395. mobile_agent/fleet.py +841 -0
  396. mobile_agent/fleet_health.py +729 -0
  397. mobile_agent/fleet_phone.py +186 -0
  398. mobile_agent/frontier/__init__.py +79 -0
  399. mobile_agent/frontier/act.py +380 -0
  400. mobile_agent/frontier/actions.py +70 -0
  401. mobile_agent/frontier/agent.py +256 -0
  402. mobile_agent/frontier/approvals.py +366 -0
  403. mobile_agent/frontier/hooks.py +199 -0
  404. mobile_agent/frontier/interrupts.py +125 -0
  405. mobile_agent/frontier/observe.py +331 -0
  406. mobile_agent/frontier/phases.py +381 -0
  407. mobile_agent/frontier/pipeline.py +61 -0
  408. mobile_agent/frontier/prompt.py +384 -0
  409. mobile_agent/frontier/protocol.py +91 -0
  410. mobile_agent/frontier/safety.py +467 -0
  411. mobile_agent/frontier/saved.py +375 -0
  412. mobile_agent/frontier/screen.py +419 -0
  413. mobile_agent/frontier/skills.py +147 -0
  414. mobile_agent/frontier/stages/__init__.py +1 -0
  415. mobile_agent/frontier/stages/after.py +192 -0
  416. mobile_agent/frontier/stages/approve.py +138 -0
  417. mobile_agent/frontier/stages/controls.py +115 -0
  418. mobile_agent/frontier/stages/ends.py +102 -0
  419. mobile_agent/frontier/stages/gate.py +92 -0
  420. mobile_agent/frontier/stages/guards.py +132 -0
  421. mobile_agent/frontier/stages/points.py +155 -0
  422. mobile_agent/frontier/stages/send.py +177 -0
  423. mobile_agent/frontier/turn.py +151 -0
  424. mobile_agent/full_disk_access.py +83 -0
  425. mobile_agent/gemini.py +69 -0
  426. mobile_agent/genuine.py +120 -0
  427. mobile_agent/graph/__init__.py +4 -0
  428. mobile_agent/graph/catalog.py +789 -0
  429. mobile_agent/graph/chain.py +306 -0
  430. mobile_agent/graph/dispatch.py +98 -0
  431. mobile_agent/graph/draft.py +373 -0
  432. mobile_agent/graph/edit.py +76 -0
  433. mobile_agent/graph/estimate.py +189 -0
  434. mobile_agent/graph/expr.py +562 -0
  435. mobile_agent/graph/functions.py +271 -0
  436. mobile_agent/graph/infer.py +144 -0
  437. mobile_agent/graph/nodes.py +936 -0
  438. mobile_agent/graph/phone_steps.py +178 -0
  439. mobile_agent/graph/rerun.py +327 -0
  440. mobile_agent/graph/run.py +899 -0
  441. mobile_agent/graph/schema.py +842 -0
  442. mobile_agent/graph/state.py +161 -0
  443. mobile_agent/graph/values.py +198 -0
  444. mobile_agent/harness/__init__.py +2 -0
  445. mobile_agent/harness/checkpoints.py +468 -0
  446. mobile_agent/harness/control.py +101 -0
  447. mobile_agent/harness/prewarm.py +213 -0
  448. mobile_agent/harness/register.py +43 -0
  449. mobile_agent/harness/routes.py +114 -0
  450. mobile_agent/harness/settings.py +66 -0
  451. mobile_agent/harness/tools/__init__.py +67 -0
  452. mobile_agent/harness/tools/ask_user.py +102 -0
  453. mobile_agent/harness/tools/watch.py +120 -0
  454. mobile_agent/harness_api.py +815 -0
  455. mobile_agent/hedge.py +203 -0
  456. mobile_agent/help_topics.py +305 -0
  457. mobile_agent/hints.py +248 -0
  458. mobile_agent/history.py +69 -0
  459. mobile_agent/home.py +214 -0
  460. mobile_agent/http_pool.py +175 -0
  461. mobile_agent/image_policy.py +139 -0
  462. mobile_agent/inbox.py +148 -0
  463. mobile_agent/installed_apps.py +344 -0
  464. mobile_agent/integrations/__init__.py +1 -0
  465. mobile_agent/integrations/cli.py +472 -0
  466. mobile_agent/integrations/client_files.py +65 -0
  467. mobile_agent/integrations/clients.py +998 -0
  468. mobile_agent/integrations/doctor.py +185 -0
  469. mobile_agent/integrations/jsonc.py +383 -0
  470. mobile_agent/integrations/routes.py +135 -0
  471. mobile_agent/integrations/rules.py +112 -0
  472. mobile_agent/integrations/server_options.py +46 -0
  473. mobile_agent/integrations/skill.py +152 -0
  474. mobile_agent/integrations/skill_files.py +10 -0
  475. mobile_agent/integrations/textedit.py +309 -0
  476. mobile_agent/intents/__init__.py +17 -0
  477. mobile_agent/intents/atlas.py +216 -0
  478. mobile_agent/intents/callback.py +44 -0
  479. mobile_agent/intents/cli.py +55 -0
  480. mobile_agent/intents/harvest.py +91 -0
  481. mobile_agent/intents/plan.py +93 -0
  482. mobile_agent/ios/__init__.py +1 -0
  483. mobile_agent/ios/guide.py +16 -0
  484. mobile_agent/ios/kb.py +212 -0
  485. mobile_agent/ios/kb_data.py +1588 -0
  486. mobile_agent/ios/mcp.py +44 -0
  487. mobile_agent/ios/register.py +8 -0
  488. mobile_agent/ios/system.py +369 -0
  489. mobile_agent/journal.py +531 -0
  490. mobile_agent/keychain_acl.py +374 -0
  491. mobile_agent/keys.py +395 -0
  492. mobile_agent/latency_overlap.py +36 -0
  493. mobile_agent/latency_trace.py +170 -0
  494. mobile_agent/learned/__init__.py +15 -0
  495. mobile_agent/learned/app_path.py +160 -0
  496. mobile_agent/learned/code_read.py +38 -0
  497. mobile_agent/learned/parts.py +546 -0
  498. mobile_agent/learned/programs.py +691 -0
  499. mobile_agent/learned/register.py +171 -0
  500. mobile_agent/learned/routes.py +61 -0
  501. mobile_agent/learned/taught.py +329 -0
  502. mobile_agent/learned/teach.py +538 -0
  503. mobile_agent/learned/workflows.py +647 -0
  504. mobile_agent/learned/zero.py +41 -0
  505. mobile_agent/links.py +37 -0
  506. mobile_agent/listen.py +607 -0
  507. mobile_agent/lock_spike.py +278 -0
  508. mobile_agent/lockscreen.py +526 -0
  509. mobile_agent/login.py +354 -0
  510. mobile_agent/longform/__init__.py +15 -0
  511. mobile_agent/longform/ads.py +191 -0
  512. mobile_agent/longform/blocks.py +117 -0
  513. mobile_agent/longform/budget.py +265 -0
  514. mobile_agent/longform/context.py +285 -0
  515. mobile_agent/longform/costs.py +93 -0
  516. mobile_agent/longform/digest.py +145 -0
  517. mobile_agent/longform/extract.py +508 -0
  518. mobile_agent/longform/feed.py +505 -0
  519. mobile_agent/longform/followup.py +168 -0
  520. mobile_agent/longform/frames.py +82 -0
  521. mobile_agent/longform/identity.py +87 -0
  522. mobile_agent/longform/inbox.py +221 -0
  523. mobile_agent/longform/items.py +425 -0
  524. mobile_agent/longform/models.py +115 -0
  525. mobile_agent/longform/notebook.py +329 -0
  526. mobile_agent/longform/plan.py +614 -0
  527. mobile_agent/longform/recall.py +195 -0
  528. mobile_agent/longform/receipts.py +81 -0
  529. mobile_agent/longform/register.py +124 -0
  530. mobile_agent/longform/report.py +343 -0
  531. mobile_agent/longform/routes.py +102 -0
  532. mobile_agent/longform/session.py +644 -0
  533. mobile_agent/longform/store.py +146 -0
  534. mobile_agent/longform/synthesis.py +347 -0
  535. mobile_agent/mac_tools.py +223 -0
  536. mobile_agent/manual_control.py +143 -0
  537. mobile_agent/maps.py +149 -0
  538. mobile_agent/marks.py +92 -0
  539. mobile_agent/mcp_server/__init__.py +3 -0
  540. mobile_agent/mcp_server/__main__.py +29 -0
  541. mobile_agent/mcp_server/action_journal.py +24 -0
  542. mobile_agent/mcp_server/action_tools.py +638 -0
  543. mobile_agent/mcp_server/approvals.py +219 -0
  544. mobile_agent/mcp_server/catalog.py +92 -0
  545. mobile_agent/mcp_server/cli.py +194 -0
  546. mobile_agent/mcp_server/codes.py +147 -0
  547. mobile_agent/mcp_server/device_tools.py +306 -0
  548. mobile_agent/mcp_server/direct.py +272 -0
  549. mobile_agent/mcp_server/expo_tools.py +105 -0
  550. mobile_agent/mcp_server/phone_tools.py +510 -0
  551. mobile_agent/mcp_server/protocol.py +364 -0
  552. mobile_agent/mcp_server/registry.py +55 -0
  553. mobile_agent/mcp_server/run_tools.py +531 -0
  554. mobile_agent/mcp_server/schemas.py +425 -0
  555. mobile_agent/mcp_server/screen_tools.py +168 -0
  556. mobile_agent/mcp_server/session.py +469 -0
  557. mobile_agent/mcp_server/support.py +145 -0
  558. mobile_agent/mcp_server/tools.py +493 -0
  559. mobile_agent/memory/__init__.py +9 -0
  560. mobile_agent/memory/cli.py +207 -0
  561. mobile_agent/memory/extract.py +191 -0
  562. mobile_agent/memory/listener.py +100 -0
  563. mobile_agent/memory/model.py +144 -0
  564. mobile_agent/memory/project.py +96 -0
  565. mobile_agent/memory/provider.py +127 -0
  566. mobile_agent/memory/register.py +21 -0
  567. mobile_agent/memory/retrieval.py +172 -0
  568. mobile_agent/memory/routes.py +216 -0
  569. mobile_agent/memory/runs.py +124 -0
  570. mobile_agent/memory/store.py +806 -0
  571. mobile_agent/memory/writer.py +278 -0
  572. mobile_agent/mirroring.py +585 -0
  573. mobile_agent/mirroring_runtime.py +221 -0
  574. mobile_agent/model_clients.py +604 -0
  575. mobile_agent/narrate.py +813 -0
  576. mobile_agent/native_helpers.py +84 -0
  577. mobile_agent/netxray/__init__.py +1 -0
  578. mobile_agent/netxray/analyze.py +322 -0
  579. mobile_agent/netxray/attribution.py +62 -0
  580. mobile_agent/netxray/capture.py +88 -0
  581. mobile_agent/netxray/cli.py +142 -0
  582. mobile_agent/netxray/job.py +219 -0
  583. mobile_agent/netxray/mobster-pcap.c +560 -0
  584. mobile_agent/netxray/packets.py +410 -0
  585. mobile_agent/netxray/pcapng.py +223 -0
  586. mobile_agent/netxray/register.py +93 -0
  587. mobile_agent/netxray/report.py +214 -0
  588. mobile_agent/netxray/store.py +48 -0
  589. mobile_agent/netxray/summary.py +157 -0
  590. mobile_agent/netxray/target.py +67 -0
  591. mobile_agent/netxray/tool.py +66 -0
  592. mobile_agent/netxray/trackers.py +194 -0
  593. mobile_agent/notify_triggers/__init__.py +1 -0
  594. mobile_agent/notify_triggers/rules.py +39 -0
  595. mobile_agent/notify_triggers/sources.py +315 -0
  596. mobile_agent/notify_triggers/watch.py +135 -0
  597. mobile_agent/offers.py +86 -0
  598. mobile_agent/orchestrate.py +215 -0
  599. mobile_agent/outlets/__init__.py +1 -0
  600. mobile_agent/outlets/connect.py +249 -0
  601. mobile_agent/outlets/folder.py +180 -0
  602. mobile_agent/outlets/hooks.py +453 -0
  603. mobile_agent/outlets/inbound.py +472 -0
  604. mobile_agent/outlets/inbound_reply.py +57 -0
  605. mobile_agent/outlets/inbound_routes.py +81 -0
  606. mobile_agent/outlets/kinds.py +308 -0
  607. mobile_agent/outlets/mail.py +110 -0
  608. mobile_agent/outlets/net.py +86 -0
  609. mobile_agent/outlets/notion.py +244 -0
  610. mobile_agent/outlets/register.py +56 -0
  611. mobile_agent/outlets/routes.py +135 -0
  612. mobile_agent/outlets/service.py +580 -0
  613. mobile_agent/outlets/sheets.py +169 -0
  614. mobile_agent/outlets/slack.py +177 -0
  615. mobile_agent/outlets/store.py +194 -0
  616. mobile_agent/outlets/vault.py +80 -0
  617. mobile_agent/outlets/webhook.py +316 -0
  618. mobile_agent/output_contract.py +141 -0
  619. mobile_agent/owner_presence.py +211 -0
  620. mobile_agent/ownership.py +219 -0
  621. mobile_agent/packs/__init__.py +1 -0
  622. mobile_agent/packs/calendar.py +243 -0
  623. mobile_agent/packs/context.py +68 -0
  624. mobile_agent/packs/core.py +757 -0
  625. mobile_agent/packs/front_door.py +316 -0
  626. mobile_agent/packs/heal.py +214 -0
  627. mobile_agent/packs/health.py +122 -0
  628. mobile_agent/packs/insights.py +76 -0
  629. mobile_agent/packs/instagram.py +640 -0
  630. mobile_agent/packs/linkedin.py +106 -0
  631. mobile_agent/packs/maps.py +196 -0
  632. mobile_agent/packs/mcp.py +149 -0
  633. mobile_agent/packs/messages.py +270 -0
  634. mobile_agent/packs/notes.py +226 -0
  635. mobile_agent/packs/photos.py +166 -0
  636. mobile_agent/packs/pins.py +47 -0
  637. mobile_agent/packs/reddit.py +110 -0
  638. mobile_agent/packs/register.py +276 -0
  639. mobile_agent/packs/rows.py +108 -0
  640. mobile_agent/packs/safari.py +143 -0
  641. mobile_agent/packs/settings.py +253 -0
  642. mobile_agent/packs/social.py +300 -0
  643. mobile_agent/packs/threads.py +97 -0
  644. mobile_agent/packs/tiktok.py +455 -0
  645. mobile_agent/packs/tools.py +290 -0
  646. mobile_agent/packs/x.py +127 -0
  647. mobile_agent/packs/youtube.py +199 -0
  648. mobile_agent/parity.py +270 -0
  649. mobile_agent/paths.py +99 -0
  650. mobile_agent/perception.py +470 -0
  651. mobile_agent/permissions/__init__.py +32 -0
  652. mobile_agent/permissions/consequence.py +535 -0
  653. mobile_agent/permissions/exceptions.py +99 -0
  654. mobile_agent/permissions/gate.py +316 -0
  655. mobile_agent/permissions/outside.py +316 -0
  656. mobile_agent/permissions/policy.py +288 -0
  657. mobile_agent/permissions/project_policy.py +411 -0
  658. mobile_agent/permissions/register.py +148 -0
  659. mobile_agent/permissions/rules.py +291 -0
  660. mobile_agent/phone_io/__init__.py +146 -0
  661. mobile_agent/phone_io/cli.py +427 -0
  662. mobile_agent/phone_io/clipboard.py +78 -0
  663. mobile_agent/phone_io/controls.py +78 -0
  664. mobile_agent/phone_io/files.py +289 -0
  665. mobile_agent/phone_io/handoff.py +123 -0
  666. mobile_agent/phone_io/icloud.py +118 -0
  667. mobile_agent/phone_io/install.py +188 -0
  668. mobile_agent/phone_io/network_capture.py +188 -0
  669. mobile_agent/phone_io/network_cli.py +138 -0
  670. mobile_agent/phone_io/network_profiles.py +138 -0
  671. mobile_agent/phone_io/photos.py +512 -0
  672. mobile_agent/phone_io/recording.py +115 -0
  673. mobile_agent/phone_io/secrets.py +146 -0
  674. mobile_agent/phone_io/transfer.py +895 -0
  675. mobile_agent/phone_io/tunnel.py +129 -0
  676. mobile_agent/phone_ready.py +91 -0
  677. mobile_agent/pinning.py +158 -0
  678. mobile_agent/plan_gate.py +807 -0
  679. mobile_agent/plugins/__init__.py +3 -0
  680. mobile_agent/plugins/install.py +163 -0
  681. mobile_agent/plugins/manifest.py +242 -0
  682. mobile_agent/plugins/register.py +15 -0
  683. mobile_agent/plugins/routes.py +82 -0
  684. mobile_agent/plugins/service.py +322 -0
  685. mobile_agent/plugins/skills.py +76 -0
  686. mobile_agent/plugins/store.py +79 -0
  687. mobile_agent/pool.py +285 -0
  688. mobile_agent/power_watch.py +275 -0
  689. mobile_agent/premium_model.py +22 -0
  690. mobile_agent/project_env.py +291 -0
  691. mobile_agent/project_env_cli.py +74 -0
  692. mobile_agent/project_env_routes.py +55 -0
  693. mobile_agent/projects/__init__.py +27 -0
  694. mobile_agent/projects/ci.py +332 -0
  695. mobile_agent/projects/clone.py +339 -0
  696. mobile_agent/projects/drafts.py +158 -0
  697. mobile_agent/projects/folder.py +436 -0
  698. mobile_agent/projects/github.py +131 -0
  699. mobile_agent/projects/register.py +31 -0
  700. mobile_agent/projects/revisions.py +253 -0
  701. mobile_agent/projects/routes.py +234 -0
  702. mobile_agent/projects/runs.py +297 -0
  703. mobile_agent/projects/service.py +695 -0
  704. mobile_agent/projects/setup.py +84 -0
  705. mobile_agent/projects/store.py +379 -0
  706. mobile_agent/projects/sync.py +380 -0
  707. mobile_agent/projects/workflow_files.py +270 -0
  708. mobile_agent/replay_core/__init__.py +26 -0
  709. mobile_agent/replay_core/checks.py +594 -0
  710. mobile_agent/replay_core/engine.py +596 -0
  711. mobile_agent/replay_core/heal.py +364 -0
  712. mobile_agent/replay_core/path.py +240 -0
  713. mobile_agent/replay_core/recorder.py +206 -0
  714. mobile_agent/replay_core/store.py +171 -0
  715. mobile_agent/result_blocks.py +941 -0
  716. mobile_agent/run.py +642 -0
  717. mobile_agent/run_delivery.py +123 -0
  718. mobile_agent/run_executor.py +200 -0
  719. mobile_agent/run_export.py +284 -0
  720. mobile_agent/run_gif.py +891 -0
  721. mobile_agent/runner_hints.py +86 -0
  722. mobile_agent/runner_renewal.py +205 -0
  723. mobile_agent/runtime_create.py +549 -0
  724. mobile_agent/runtime_driver.py +458 -0
  725. mobile_agent/runtime_limits.py +319 -0
  726. mobile_agent/runtime_queue.py +394 -0
  727. mobile_agent/runtime_runs.py +383 -0
  728. mobile_agent/runtime_status.py +181 -0
  729. mobile_agent/runtime_teach.py +295 -0
  730. mobile_agent/runtime_work.py +599 -0
  731. mobile_agent/screen_text.py +101 -0
  732. mobile_agent/secret_filter.py +113 -0
  733. mobile_agent/serve.py +160 -0
  734. mobile_agent/server.py +363 -0
  735. mobile_agent/service_support.py +42 -0
  736. mobile_agent/settle/__init__.py +1 -0
  737. mobile_agent/settle/predict.py +102 -0
  738. mobile_agent/settle/replay.py +92 -0
  739. mobile_agent/settle/store.py +93 -0
  740. mobile_agent/settle/stream.py +43 -0
  741. mobile_agent/setup_cli.py +225 -0
  742. mobile_agent/setup_errors.py +87 -0
  743. mobile_agent/setup_service.py +527 -0
  744. mobile_agent/share.py +104 -0
  745. mobile_agent/signing.py +184 -0
  746. mobile_agent/sim/__init__.py +6 -0
  747. mobile_agent/sim/__main__.py +26 -0
  748. mobile_agent/sim/api.py +952 -0
  749. mobile_agent/sim/apps.py +172 -0
  750. mobile_agent/sim/biometrics.py +84 -0
  751. mobile_agent/sim/cli.py +271 -0
  752. mobile_agent/sim/doctor.py +293 -0
  753. mobile_agent/sim/governor.py +90 -0
  754. mobile_agent/sim/holders.py +88 -0
  755. mobile_agent/sim/models.py +62 -0
  756. mobile_agent/sim/registry.py +161 -0
  757. mobile_agent/sim/simctl.py +269 -0
  758. mobile_agent/sim/wda.py +618 -0
  759. mobile_agent/sim/xcode.py +74 -0
  760. mobile_agent/skills/__init__.py +12 -0
  761. mobile_agent/skills/codes.py +597 -0
  762. mobile_agent/skills/notifications.py +281 -0
  763. mobile_agent/skills/open_url.py +127 -0
  764. mobile_agent/smart_events.py +588 -0
  765. mobile_agent/smart_result.py +490 -0
  766. mobile_agent/smart_run.py +413 -0
  767. mobile_agent/state.py +662 -0
  768. mobile_agent/states.py +217 -0
  769. mobile_agent/steering.py +70 -0
  770. mobile_agent/style.py +178 -0
  771. mobile_agent/supervised.py +234 -0
  772. mobile_agent/swift/helper_bt_hid.swift +534 -0
  773. mobile_agent/swift/helper_doc_text.swift +184 -0
  774. mobile_agent/swift/helper_draft_reply.swift +170 -0
  775. mobile_agent/swift/helper_map.swift +110 -0
  776. mobile_agent/swift/helper_mirroring.swift +1099 -0
  777. mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
  778. mobile_agent/swift/helper_pdf.swift +100 -0
  779. mobile_agent/swift/helper_phone_audio.swift +993 -0
  780. mobile_agent/swift/helper_phone_screen.swift +416 -0
  781. mobile_agent/swift/helper_screen_text.swift +24 -0
  782. mobile_agent/swift/helper_transfer.swift +246 -0
  783. mobile_agent/swift/ocr.swift +22 -0
  784. mobile_agent/task_policy.py +337 -0
  785. mobile_agent/team_signing/__init__.py +34 -0
  786. mobile_agent/team_signing/apple_ca.py +37 -0
  787. mobile_agent/team_signing/bundle.py +136 -0
  788. mobile_agent/team_signing/cli.py +208 -0
  789. mobile_agent/team_signing/identity.py +248 -0
  790. mobile_agent/team_signing/ledger.py +105 -0
  791. mobile_agent/team_signing/prebuilt.py +61 -0
  792. mobile_agent/team_signing/resign.py +256 -0
  793. mobile_agent/team_signing/service.py +382 -0
  794. mobile_agent/team_signing/tooling.py +243 -0
  795. mobile_agent/team_signing/writes.py +268 -0
  796. mobile_agent/terminal_image.py +81 -0
  797. mobile_agent/testkit/__init__.py +1 -0
  798. mobile_agent/testkit/a11y_baseline.py +132 -0
  799. mobile_agent/testkit/api.py +403 -0
  800. mobile_agent/testkit/capsule.py +400 -0
  801. mobile_agent/testkit/capsule_cli.py +57 -0
  802. mobile_agent/testkit/cli.py +948 -0
  803. mobile_agent/testkit/cli_arguments.py +193 -0
  804. mobile_agent/testkit/comparison.py +288 -0
  805. mobile_agent/testkit/comparison_cli.py +41 -0
  806. mobile_agent/testkit/discover.py +257 -0
  807. mobile_agent/testkit/editor.py +243 -0
  808. mobile_agent/testkit/execute.py +515 -0
  809. mobile_agent/testkit/github_checks.py +248 -0
  810. mobile_agent/testkit/gitref.py +66 -0
  811. mobile_agent/testkit/heal_review.py +176 -0
  812. mobile_agent/testkit/health.py +480 -0
  813. mobile_agent/testkit/history.py +255 -0
  814. mobile_agent/testkit/hosted_results.py +321 -0
  815. mobile_agent/testkit/input_files.py +173 -0
  816. mobile_agent/testkit/jobs.py +285 -0
  817. mobile_agent/testkit/junit_verdict.py +33 -0
  818. mobile_agent/testkit/maestro.py +802 -0
  819. mobile_agent/testkit/mcp.py +562 -0
  820. mobile_agent/testkit/mcp_comparison.py +88 -0
  821. mobile_agent/testkit/minimize.py +471 -0
  822. mobile_agent/testkit/minimizer_cli.py +48 -0
  823. mobile_agent/testkit/notify.py +212 -0
  824. mobile_agent/testkit/phone_video.py +191 -0
  825. mobile_agent/testkit/pr.py +491 -0
  826. mobile_agent/testkit/pr_report.py +562 -0
  827. mobile_agent/testkit/prbuild.py +148 -0
  828. mobile_agent/testkit/proof_report.py +55 -0
  829. mobile_agent/testkit/provenance.py +119 -0
  830. mobile_agent/testkit/prrun.py +244 -0
  831. mobile_agent/testkit/recent.py +166 -0
  832. mobile_agent/testkit/recipes.py +146 -0
  833. mobile_agent/testkit/record.py +204 -0
  834. mobile_agent/testkit/register.py +288 -0
  835. mobile_agent/testkit/reports.py +998 -0
  836. mobile_agent/testkit/repro.py +432 -0
  837. mobile_agent/testkit/scenario.py +440 -0
  838. mobile_agent/testkit/scenario_cli.py +37 -0
  839. mobile_agent/testkit/snapshots_cli.py +60 -0
  840. mobile_agent/testkit/suite.py +670 -0
  841. mobile_agent/testkit/targets.py +497 -0
  842. mobile_agent/testkit/xctest.py +378 -0
  843. mobile_agent/threads/__init__.py +1 -0
  844. mobile_agent/threads/chat_once.py +264 -0
  845. mobile_agent/threads/cli.py +740 -0
  846. mobile_agent/threads/client.py +326 -0
  847. mobile_agent/threads/context.py +246 -0
  848. mobile_agent/threads/mcp.py +739 -0
  849. mobile_agent/threads/recent.py +34 -0
  850. mobile_agent/threads/register.py +54 -0
  851. mobile_agent/threads/routes.py +188 -0
  852. mobile_agent/threads/service.py +975 -0
  853. mobile_agent/threads/store.py +361 -0
  854. mobile_agent/threads/summary.py +117 -0
  855. mobile_agent/threads/term.py +101 -0
  856. mobile_agent/threads/workflow_cli.py +104 -0
  857. mobile_agent/tls.py +36 -0
  858. mobile_agent/traces.py +271 -0
  859. mobile_agent/tracks.py +235 -0
  860. mobile_agent/transport.py +271 -0
  861. mobile_agent/triggers/__init__.py +1 -0
  862. mobile_agent/triggers/actions.py +204 -0
  863. mobile_agent/triggers/model.py +146 -0
  864. mobile_agent/triggers/register.py +29 -0
  865. mobile_agent/triggers/routes.py +101 -0
  866. mobile_agent/triggers/service.py +387 -0
  867. mobile_agent/triggers/store.py +142 -0
  868. mobile_agent/tui/__init__.py +89 -0
  869. mobile_agent/tui/app.py +582 -0
  870. mobile_agent/tui/chrome.py +321 -0
  871. mobile_agent/tui/commands.py +375 -0
  872. mobile_agent/tui/demo_phone.py +369 -0
  873. mobile_agent/tui/images.py +29 -0
  874. mobile_agent/tui/keys.py +64 -0
  875. mobile_agent/tui/prompt.py +195 -0
  876. mobile_agent/tui/render.py +395 -0
  877. mobile_agent/tui/screens.py +180 -0
  878. mobile_agent/tui/session.py +308 -0
  879. mobile_agent/tui/tasks.py +287 -0
  880. mobile_agent/tui/theme.py +118 -0
  881. mobile_agent/tui/widgets.py +272 -0
  882. mobile_agent/update_check.py +213 -0
  883. mobile_agent/usage.py +184 -0
  884. mobile_agent/verify/__init__.py +0 -0
  885. mobile_agent/verify/__main__.py +41 -0
  886. mobile_agent/verify/a11y.py +189 -0
  887. mobile_agent/verify/actions.py +290 -0
  888. mobile_agent/verify/assertions.py +570 -0
  889. mobile_agent/verify/audit.py +312 -0
  890. mobile_agent/verify/business.py +221 -0
  891. mobile_agent/verify/business_run.py +131 -0
  892. mobile_agent/verify/business_state.py +168 -0
  893. mobile_agent/verify/check_schema.py +398 -0
  894. mobile_agent/verify/checks.py +614 -0
  895. mobile_agent/verify/cli.py +423 -0
  896. mobile_agent/verify/conditions.py +483 -0
  897. mobile_agent/verify/crashes.py +321 -0
  898. mobile_agent/verify/device_audit.py +246 -0
  899. mobile_agent/verify/dialogs.py +287 -0
  900. mobile_agent/verify/evidence.py +705 -0
  901. mobile_agent/verify/fixtures.py +305 -0
  902. mobile_agent/verify/flow.py +213 -0
  903. mobile_agent/verify/l10n.py +141 -0
  904. mobile_agent/verify/netcerts.py +67 -0
  905. mobile_agent/verify/netproxy.py +356 -0
  906. mobile_agent/verify/netrun.py +81 -0
  907. mobile_agent/verify/netshim.m +28 -0
  908. mobile_agent/verify/netshim.py +48 -0
  909. mobile_agent/verify/netstubs.py +132 -0
  910. mobile_agent/verify/overlay.py +174 -0
  911. mobile_agent/verify/perf.py +239 -0
  912. mobile_agent/verify/proof.py +192 -0
  913. mobile_agent/verify/replay_admission.py +48 -0
  914. mobile_agent/verify/report.py +952 -0
  915. mobile_agent/verify/run_cleanup.py +67 -0
  916. mobile_agent/verify/run_links.py +48 -0
  917. mobile_agent/verify/run_perf.py +44 -0
  918. mobile_agent/verify/run_secrets.py +39 -0
  919. mobile_agent/verify/runner.py +976 -0
  920. mobile_agent/verify/runs.py +57 -0
  921. mobile_agent/verify/screen_coverage.py +181 -0
  922. mobile_agent/verify/smart.py +106 -0
  923. mobile_agent/verify/snapshot_run.py +114 -0
  924. mobile_agent/verify/snapshots.py +306 -0
  925. mobile_agent/verify/sound.py +335 -0
  926. mobile_agent/verify/source_observation.py +130 -0
  927. mobile_agent/verify/system_surfaces/__init__.py +7 -0
  928. mobile_agent/verify/system_surfaces/play.py +106 -0
  929. mobile_agent/verify/system_surfaces/spec.py +139 -0
  930. mobile_agent/verify/system_surfaces/stage.py +138 -0
  931. mobile_agent/verify/system_surfaces/surfaces.py +121 -0
  932. mobile_agent/verify/tree.py +223 -0
  933. mobile_agent/verify/verdict.py +377 -0
  934. mobile_agent/verify/wire.py +392 -0
  935. mobile_agent/verify/wirestore.py +176 -0
  936. mobile_agent/waiting/__init__.py +1 -0
  937. mobile_agent/waiting/contacts.py +73 -0
  938. mobile_agent/waiting/drafts.py +136 -0
  939. mobile_agent/waiting/job.py +145 -0
  940. mobile_agent/waiting/messages.py +174 -0
  941. mobile_agent/waiting/rank.py +115 -0
  942. mobile_agent/waiting/register.py +26 -0
  943. mobile_agent/watch.py +502 -0
  944. mobile_agent/wda/__init__.py +2 -0
  945. mobile_agent/wda/frame_clock.py +646 -0
  946. mobile_agent/wda/frame_source.py +325 -0
  947. mobile_agent/wda/frame_store.py +115 -0
  948. mobile_agent/wda/gate.py +163 -0
  949. mobile_agent/wda/gestures.py +291 -0
  950. mobile_agent/wda/guards.py +94 -0
  951. mobile_agent/wda/identity.py +55 -0
  952. mobile_agent/wda/observe.py +884 -0
  953. mobile_agent/wda/patches.py +856 -0
  954. mobile_agent/wda/point_gestures.py +150 -0
  955. mobile_agent/wda/source.py +32 -0
  956. mobile_agent/wda/system_dialogs.py +571 -0
  957. mobile_agent/wda/text.py +694 -0
  958. mobile_agent/wda/timeouts.py +77 -0
  959. mobile_agent/wda/video.py +505 -0
  960. mobile_agent/wda/watchdog.py +243 -0
  961. mobile_agent/wireless/__init__.py +15 -0
  962. mobile_agent/wireless/api.py +354 -0
  963. mobile_agent/wireless/cli.py +287 -0
  964. mobile_agent/wireless/devicectl.py +229 -0
  965. mobile_agent/wireless/exposure.py +33 -0
  966. mobile_agent/wireless/mobster-wifi-pair.c +122 -0
  967. mobile_agent/wireless/monitor.py +332 -0
  968. mobile_agent/wireless/network.py +334 -0
  969. mobile_agent/wireless/onboarding.py +293 -0
  970. mobile_agent/wireless/pair.py +124 -0
  971. mobile_agent/wireless/probe.py +322 -0
  972. mobile_agent/wireless/register.py +8 -0
  973. mobile_agent/wireless/relay.py +179 -0
  974. mobile_agent/wireless/result.py +87 -0
  975. mobile_agent/wireless/store.py +85 -0
  976. mobile_agent/wireless/switch.py +77 -0
  977. mobile_agent/wireless/transport.py +118 -0
  978. mobile_agent/wireless/words.py +81 -0
  979. mobile_agent/wireless/xctestrun.py +83 -0
  980. mobile_agent/workflow_tools.py +59 -0
  981. mobile_agent/workflows/__init__.py +2 -0
  982. mobile_agent/workflows/brief.py +201 -0
  983. mobile_agent/workflows/cron.py +110 -0
  984. mobile_agent/workflows/health.py +257 -0
  985. mobile_agent/workflows/history.py +126 -0
  986. mobile_agent/workflows/records.py +442 -0
  987. mobile_agent/workflows/runs.py +1000 -0
  988. mobile_agent/workflows/scheduler.py +134 -0
  989. mobile_agent/workflows/service.py +973 -0
  990. mobile_agent/workflows/starters.py +285 -0
  991. mobile_agent/workflows/triggers.py +248 -0
  992. mobile_agent/xray/__init__.py +1 -0
  993. mobile_agent/xray/app_cli.py +116 -0
  994. mobile_agent/xray/cli.py +21 -0
  995. mobile_agent/xray/crawl.py +754 -0
  996. mobile_agent/xray/diff.py +171 -0
  997. mobile_agent/xray/diff_cli.py +43 -0
  998. mobile_agent/xray/job.py +184 -0
  999. mobile_agent/xray/paywall.py +188 -0
  1000. mobile_agent/xray/policy.py +305 -0
  1001. mobile_agent/xray/register.py +43 -0
  1002. mobile_agent/xray/report.py +283 -0
  1003. mobile_agent/xray/screens.py +117 -0
  1004. mobster_cli-0.4.3.dist-info/METADATA +93 -0
  1005. mobster_cli-0.4.3.dist-info/RECORD +1009 -0
  1006. mobster_cli-0.4.3.dist-info/WHEEL +5 -0
  1007. mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
  1008. mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
  1009. mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,186 @@
1
+ """One device as the server drives it (SPEC §3): ``Phone``, and ``PrimaryPhone``, the runtime's own device."""
2
+
3
+ import threading
4
+ import time
5
+
6
+
7
+ class Phone:
8
+ """One device as the server drives it."""
9
+
10
+ primary = False
11
+
12
+ def __init__(self, fleet, *, id, kind, wda_url=None, mjpeg_url=None, udid=None, name=None, model=None, ios=None,
13
+ manager=None, setup=None):
14
+ self.fleet, self.runtime = fleet, fleet.runtime
15
+ self.id, self.kind, self.udid = id, kind, udid
16
+ self._wda_url, self.mjpeg_url = (wda_url or "").rstrip("/") or None, mjpeg_url
17
+ self.name, self.model, self.ios = name, model, ios
18
+ self.manager, self.setup = manager, setup
19
+ self.session_id, self.session_lock = None, threading.Lock()
20
+ self.health_at, self.health_ready = 0.0, False
21
+ self.health_lock = threading.Lock()
22
+ self._video = self._control = None
23
+ self.installed_apps = self.device_info = None
24
+ self.objects_lock = threading.Lock()
25
+ # A simulator's start (boot and WebDriverAgent) runs in the background: {"state", "error"}.
26
+ self.start_state = {"state": "idle", "error": None}
27
+
28
+ @property
29
+ def key(self):
30
+ """What ``Runtime.active_runs`` records for a run on this device."""
31
+ return self.id
32
+
33
+ @property
34
+ def wda_url(self):
35
+ return self._wda_url
36
+
37
+ # -- WebDriverAgent ---------------------------------------------------------------------------------
38
+
39
+ def wda_session(self):
40
+ from .drivers import resolve_wda_session
41
+ with self.session_lock:
42
+ self.session_id = resolve_wda_session(self.wda_url, preferred=self.session_id)
43
+ return self.session_id
44
+
45
+ def current_session(self):
46
+ try:
47
+ return self.wda_session()
48
+ except Exception:
49
+ return None
50
+
51
+ def target_status(self):
52
+ """The runtime's target_status for this device: whether WDA answers and reaches the phone's UI."""
53
+ ready, health = False, None
54
+ if self.wda_url:
55
+ with self.health_lock:
56
+ if not self.health_at or time.monotonic() - self.health_at > 5:
57
+ from .transport import HTTP
58
+ client = None
59
+ try:
60
+ client = HTTP(self.wda_url)
61
+ response = client.request("GET", "/status", timeout=2)
62
+ self.health_ready = response.get("value", {}).get("ready") is True
63
+ if isinstance(response.get("sessionId"), str) and response["sessionId"]:
64
+ self.session_id = response["sessionId"]
65
+ except Exception:
66
+ self.health_ready = False
67
+ finally:
68
+ if client:
69
+ client.close()
70
+ self.health_at = time.monotonic()
71
+ ready = self.health_ready
72
+ health = self.manager.health_error() if self.manager is not None else None
73
+ if ready and health:
74
+ ready = False
75
+ return {"device": bool(self.wda_url), "ready": ready, "health": health, "can_act": bool(self.wda_url),
76
+ "driver": "wda", "screen_reading": False}
77
+
78
+ # -- per-device objects -----------------------------------------------------------------------------
79
+
80
+ @property
81
+ def video(self):
82
+ """This device's live view (and the frame clock's stream), made on first use."""
83
+ if self._video is None and self.wda_url:
84
+ from .wda.video import WdaVideo
85
+ with self.objects_lock:
86
+ if self._video is None:
87
+ self._video = WdaVideo(self.wda_url, mjpeg_url=self.mjpeg_url, session=self.current_session,
88
+ quality=self.runtime.video_quality())
89
+ return self._video
90
+
91
+ @property
92
+ def control(self):
93
+ if self._control is None and self.wda_url:
94
+ from .manual_control import ManualControl
95
+ with self.objects_lock:
96
+ if self._control is None:
97
+ self._control = ManualControl(self.wda_url, self.wda_session)
98
+ return self._control
99
+
100
+ def apps(self):
101
+ """The apps a task on this device may name: the catalog, then the apps found on the phone."""
102
+ from .catalog import APPS
103
+ from .installed_apps import merge as merge_installed
104
+ inventory = self.installed_apps.snapshot() if self.installed_apps else None
105
+ return merge_installed(APPS, inventory)
106
+
107
+ def info(self):
108
+ if self.device_info is not None:
109
+ return self.device_info.snapshot()
110
+ return {"modelIdentifier": self.model if self.kind == "usb" else None,
111
+ "modelName": self.model if self.kind == "simulator" else None, "iosVersion": self.ios,
112
+ "buildVersion": None, "virtual": self.kind != "usb", "source": self.kind,
113
+ "verifiedAt": None, "stale": False}
114
+
115
+ def close(self, keep_runner=False):
116
+ for item in (self._video, self._control):
117
+ if item is not None:
118
+ try:
119
+ item.close()
120
+ except Exception:
121
+ pass
122
+ if self.manager is not None:
123
+ if keep_runner:
124
+ self.manager.watching.set()
125
+ else:
126
+ self.manager.close()
127
+
128
+
129
+ class PrimaryPhone(Phone):
130
+ """The runtime's own device: everything is the runtime's, looked up when used."""
131
+
132
+ primary = True
133
+
134
+ def __init__(self, fleet):
135
+ super().__init__(fleet, id=None, kind="usb")
136
+
137
+ @property
138
+ def key(self):
139
+ return "@primary"
140
+
141
+ @property
142
+ def wda_url(self):
143
+ url = getattr(self.runtime.config, "wda_url", None)
144
+ return url.rstrip("/") if isinstance(url, str) and url else None
145
+
146
+ @property
147
+ def manager(self):
148
+ return getattr(self.runtime, "manager", None)
149
+
150
+ @manager.setter
151
+ def manager(self, value):
152
+ pass
153
+
154
+ @property
155
+ def setup(self):
156
+ return getattr(self.runtime, "setup", None)
157
+
158
+ @setup.setter
159
+ def setup(self, value):
160
+ pass
161
+
162
+ @property
163
+ def video(self):
164
+ return self.runtime.video
165
+
166
+ @property
167
+ def control(self):
168
+ return getattr(self.runtime, "control", None)
169
+
170
+ def wda_session(self):
171
+ return self.runtime.wda_session()
172
+
173
+ def current_session(self):
174
+ return self.runtime.current_wda_session()
175
+
176
+ def target_status(self):
177
+ return self.runtime.target_status()
178
+
179
+ def apps(self):
180
+ return self.runtime.apps()
181
+
182
+ def info(self):
183
+ return self.runtime.device_info.snapshot()
184
+
185
+ def close(self, keep_runner=False):
186
+ pass # Runtime.close closes the runtime's own objects
@@ -0,0 +1,79 @@
1
+ """The turn pipeline: observe, decide, verify and act, with a frontier model choosing each step."""
2
+
3
+ from ..agent_hooks import RESUME_AFTER_SECONDS
4
+ from .act import DISMISS_POINTS, MACRO_SWIPES, READ_LIST_ROWS, READ_LIST_SECONDS, SCROLL_TO_SEARCHABLE_SWIPES
5
+ from .actions import Action, parse_reply
6
+ from .agent import FrontierAgent, screen_goal
7
+ from .hooks import BATTERY_PAUSED_FEEDBACK, BATTERY_TOO_LONG, PAUSED_FEEDBACK, STEER_FEEDBACK, STOPPED_AFTER, _redacted
8
+ from .observe import RECOVERY_BUDGET_SECONDS, STALL_BUDGET_SECONDS, STUCK_APP_FEEDBACK
9
+ from .phases import MAX_ACTIONS, MAX_NOTES
10
+ from .prompt import (
11
+ DONE_CHECK,
12
+ FENCE_CLOSE,
13
+ FENCE_OPEN,
14
+ LISTING_HEAD,
15
+ MODEL_RETRY_WAITS,
16
+ PASSED_HEAD,
17
+ STEERING_MARK,
18
+ SYSTEM,
19
+ image_part,
20
+ prompt_messages,
21
+ prompt_text,
22
+ system_prompt,
23
+ )
24
+ from .protocol import ELEMENT_OPS, OPERATIONS, STEP_FIELD, _schema, normalize_reply, output_instruction
25
+ from .safety import (
26
+ CHANGED_WHILE_ASKING,
27
+ CONNECTION_LOST,
28
+ KEYPAD_REFUSAL,
29
+ MAX_COMMITS_PER_RUN,
30
+ PENDING_REASON,
31
+ PHONE_LOCKED,
32
+ SAME_FAILURES_TO_STOP,
33
+ UNKNOWN_BLOCKS,
34
+ UNKNOWN_REASON,
35
+ GuardStop,
36
+ binding,
37
+ failure_feedback,
38
+ guard,
39
+ revealed_actions,
40
+ same_binding,
41
+ )
42
+ from .saved import START_BACKS, back_control
43
+ from .screen import (
44
+ KEYBOARD_ROW,
45
+ PROVENANCE_MARK,
46
+ SCREENSHOT_SIDE,
47
+ UNDER_BAR_MARK,
48
+ Provenance,
49
+ content_rows,
50
+ kept_list,
51
+ lean_skip,
52
+ list_rows,
53
+ point_crop,
54
+ readback,
55
+ reads_back,
56
+ receipt_for,
57
+ screen_rows,
58
+ screenshot_part,
59
+ text_status,
60
+ video_frame,
61
+ view_of,
62
+ )
63
+ from .skills import default_skills, usable_skills
64
+
65
+ __all__ = ["Action", "BATTERY_PAUSED_FEEDBACK", "BATTERY_TOO_LONG", "CHANGED_WHILE_ASKING", "CONNECTION_LOST",
66
+ "DISMISS_POINTS",
67
+ "DONE_CHECK", "ELEMENT_OPS", "FENCE_CLOSE", "FENCE_OPEN", "FrontierAgent", "GuardStop", "KEYBOARD_ROW",
68
+ "KEYPAD_REFUSAL", "LISTING_HEAD", "MACRO_SWIPES", "MAX_ACTIONS", "MAX_COMMITS_PER_RUN", "MAX_NOTES",
69
+ "MODEL_RETRY_WAITS", "OPERATIONS", "PASSED_HEAD", "PAUSED_FEEDBACK", "PENDING_REASON", "PHONE_LOCKED",
70
+ "PROVENANCE_MARK", "Provenance", "READ_LIST_ROWS", "READ_LIST_SECONDS", "RECOVERY_BUDGET_SECONDS",
71
+ "RESUME_AFTER_SECONDS", "SAME_FAILURES_TO_STOP", "SCREENSHOT_SIDE", "SCROLL_TO_SEARCHABLE_SWIPES",
72
+ "STALL_BUDGET_SECONDS", "START_BACKS", "STEERING_MARK", "STEER_FEEDBACK", "STEP_FIELD", "STOPPED_AFTER",
73
+ "STUCK_APP_FEEDBACK", "SYSTEM", "UNDER_BAR_MARK", "UNKNOWN_BLOCKS", "UNKNOWN_REASON", "_redacted", "_schema",
74
+ "back_control", "binding", "content_rows", "default_skills", "failure_feedback", "guard",
75
+ "image_part", "kept_list", "lean_skip", "list_rows", "normalize_reply", "output_instruction",
76
+ "parse_reply", "point_crop", "prompt_messages", "prompt_text", "readback", "reads_back", "receipt_for",
77
+ "revealed_actions",
78
+ "same_binding", "screen_goal", "screen_rows", "screenshot_part", "system_prompt", "text_status",
79
+ "usable_skills", "video_frame", "view_of"]
@@ -0,0 +1,380 @@
1
+ """Acting on the phone: one action through the driver, the list macros, typed text and finding targets."""
2
+
3
+ import time
4
+
5
+ from ..state import ADJUSTABLE_ROLES, TEXT_OPERATIONS, typing_timeout
6
+ from ..task_policy import same_words
7
+ from ..wda.guards import DriverRejection
8
+ from ..wda.point_gestures import GESTURE_OPS
9
+ from .protocol import TYPED_OPS
10
+ from .safety import _commits, consequential
11
+ from .screen import editable_in, looks_like_field, receipt_for, text_difference
12
+
13
+ MACRO_SWIPES = 8
14
+ SCROLL_TO_SEARCHABLE_SWIPES = 3
15
+ READ_LIST_SECONDS = 8.0
16
+ READ_LIST_ROWS = 60
17
+ MACRO_PAUSE_SECONDS = .35
18
+ # After a glide (no momentum) the list is still at release.
19
+ MACRO_GLIDE_PAUSE_SECONDS = .1
20
+ # A tap at the status bar scrolls to top instead of closing a menu, so each DISMISS tries the next free point.
21
+ DISMISS_POINTS = ((.04, .5), (.5, .7), (.96, .5), (.5, .97), (.04, .2), (.96, .2), (.5, .035))
22
+ # Actions that leave the screen: before one, a value the turn recorded under a bottom bar is scrolled into view.
23
+ LEAVING_OPS = frozenset({"LAUNCH_APP", "HOME", "OPEN_URL"})
24
+
25
+
26
+ def is_address_field(element):
27
+ label = " ".join((getattr(element, "label", "") or "").split()).casefold()
28
+ placeholder = " ".join((getattr(element, "placeholder", "") or "").split()).casefold()
29
+ return bool(getattr(element, "editable", False)) and (
30
+ label in ("address", "url") or "website" in placeholder or "enter website" in label)
31
+
32
+
33
+ # A tap soon after a search's Return waits for two agreeing reads: results still arriving move under it.
34
+ SUBMIT_WINDOW_SECONDS = 4.0
35
+ SUBMIT_SETTLE_GAP = .35
36
+ SUBMIT_SETTLE_SECONDS = 3.0
37
+
38
+
39
+ # Never one a tap could commit: the model chooses those.
40
+ def opens_field(element, snapshot):
41
+ return (element is not None and not element.editable and element.role not in ADJUSTABLE_ROLES
42
+ and (looks_like_field(element) or element.role == "Button"
43
+ and " ".join((element.label or "").split()).casefold() == "search")
44
+ and editable_in(snapshot, element) is None and not consequential("TAP", element, snapshot))
45
+
46
+
47
+ # A chained tap on a tile's name goes to the tile: on the name, Files opens a rename.
48
+ CONTAINER_ROLES = frozenset({"Cell", "Button"})
49
+ TEXT_ROLES = frozenset({"StaticText", "Text", "Other", "Image", "Icon", "NavigationBar"})
50
+
51
+
52
+ def _container(snapshot, element, want):
53
+ if element is None or element.role != "StaticText":
54
+ return element
55
+ x, y = element.center
56
+ for other in snapshot.elements:
57
+ if other is element or other.role not in CONTAINER_ROLES:
58
+ continue
59
+ label = " ".join((other.label or "").split()).casefold()
60
+ ox, oy, ow, oh = other.rect
61
+ if (label.startswith(want) and len(label) > len(want) and label[len(want)] in ",: ("
62
+ and ox <= x <= ox + ow and oy <= y <= oy + oh):
63
+ return other
64
+ return element
65
+
66
+
67
+ class ActMixin:
68
+ def _read_list(self, snapshot, needle=None):
69
+ from ..state import redundant_ids
70
+ lines, seen, current = [], set(), snapshot
71
+ want = " ".join(str(needle or "").split()).casefold()
72
+ found = []
73
+ begun, last_swipe = time.monotonic(), 0.0
74
+ app = getattr(snapshot, "bundle_id", None)
75
+
76
+ def texts(screen):
77
+ masked = self._masked(screen)
78
+ skip = redundant_ids(masked.public().get("elements") or ())
79
+ for element in masked.elements:
80
+ if element.editable or element.id in skip:
81
+ continue
82
+ value = element.value if element.value and element.value != element.label else ""
83
+ text = " ".join(f"{element.label or ''} {value}".split())
84
+ if text:
85
+ yield text
86
+
87
+ if self.run_state.list_read and self.run_state.list_read[0] == app and (
88
+ self.run_state.list_read[1] == snapshot.content_fingerprint
89
+ or set(texts(snapshot)) <= self.run_state.list_read[2]):
90
+ # Read afresh, the same screen filled the cap again and the list never moved.
91
+ seen = set(self.run_state.list_read[2])
92
+ self.run_state.list_read = None
93
+
94
+ def harvest(screen):
95
+ new = 0
96
+ for text in texts(screen):
97
+ if text not in seen:
98
+ seen.add(text)
99
+ lines.append(text[:300])
100
+ new += 1
101
+ if want and want in text.casefold():
102
+ found.append(text)
103
+ return new
104
+ ended = False
105
+ for swipe in range(MACRO_SWIPES + 1):
106
+ new = harvest(current)
107
+ ended = bool(swipe and not new)
108
+ if (found or len(lines) >= READ_LIST_ROWS or swipe == MACRO_SWIPES or ended or self._stopping()
109
+ or time.monotonic() - begun + last_swipe > READ_LIST_SECONDS):
110
+ break
111
+ t_swipe = time.monotonic()
112
+ self._drag_next() # XCTest's drag even where SWIPE_UP glides
113
+ self.driver.execute("SWIPE_UP", None, current, timeout=10)
114
+ current = self._macro_read()
115
+ last_swipe = time.monotonic() - t_swipe
116
+ if found:
117
+ self.run_state.macro_feedback = f"READ_LIST stopped at the first row containing {needle!r}."
118
+ elif not ended and (len(lines) >= READ_LIST_ROWS or time.monotonic() - begun + last_swipe > READ_LIST_SECONDS):
119
+ self.run_state.macro_feedback = (f"READ_LIST stopped after {min(len(lines), READ_LIST_ROWS)} rows and "
120
+ f"{time.monotonic() - begun:.0f} s, before the end of the list. If you need more, "
121
+ "READ_LIST again from here; if the answer is in these rows, use them.")
122
+ self.run_state.list_read = (app, current.content_fingerprint,
123
+ (seen - set(lines)) | set(lines[:READ_LIST_ROWS]))
124
+ return lines[:READ_LIST_ROWS]
125
+
126
+ def _drag_next(self):
127
+ drag_next = getattr(self.driver, "drag_next", None)
128
+ return bool(callable(drag_next) and drag_next())
129
+
130
+ def _macro_read(self):
131
+ glided = getattr(self.driver, "last_swipe_glided", False) is True
132
+ time.sleep(MACRO_GLIDE_PAUSE_SECONDS if glided else MACRO_PAUSE_SECONDS)
133
+ return self._read_once()
134
+
135
+ def _scroll_to(self, snapshot, text):
136
+ want, current = " ".join(text.split()).casefold(), snapshot
137
+
138
+ def shown(screen):
139
+ return any(want in " ".join((e.label or "").split()).casefold() and .08 < e.center[1] < .9
140
+ for e in screen.elements)
141
+ search = next((e for e in snapshot.elements if e.role == "SearchField"), None)
142
+ for swipe in range(MACRO_SWIPES):
143
+ if shown(current):
144
+ return True
145
+ if self._stopping():
146
+ break
147
+ if search is not None and swipe >= SCROLL_TO_SEARCHABLE_SWIPES:
148
+ # A list with its own search field: a name further down is found faster by searching.
149
+ label = (search.label or search.value or "Search")[:40]
150
+ self.run_state.macro_feedback = (
151
+ f"SCROLL_TO: {text!r} is not in the first {swipe + 1} screens of this list, "
152
+ f"and the list has a search field ({label!r}): search for it there.")
153
+ return False
154
+ self.driver.execute("SWIPE_UP", None, current, timeout=10)
155
+ after = self._macro_read()
156
+ if after.content_fingerprint == current.content_fingerprint and self._drag_next():
157
+ self.driver.execute("SWIPE_UP", None, current, timeout=10) # as in _read_list: once, a drag
158
+ after = self._macro_read()
159
+ if after.content_fingerprint == current.content_fingerprint:
160
+ break
161
+ current = after
162
+ return shown(current)
163
+
164
+ def _reveal_found(self, state):
165
+ """One code swipe up, once a turn, before the turn leaves its screen or finishes, when a value it recorded sits
166
+ only in rows a bottom bar covers or the screen's foot cuts (Contract.hidden_row), so the run's frames show it.
167
+ Journaled before it is sent; no model call. iOSWorld dev, 22 Oct: +0.074 rubric score over 34 paired runs."""
168
+ if self._contract is None or self.run_state.revealed == state.step or self._stopping():
169
+ return
170
+ self.run_state.revealed = state.step
171
+ row = self._contract.hidden_row(self._masked(state.snapshot).elements, state.step)
172
+ if row is None:
173
+ return
174
+ event = {"event": "frontier_cleanup", "step": state.step, "operation": "SWIPE_UP",
175
+ "label": self._mask(row.label)[:40], "reason": "reveal_value"}
176
+ t_reveal, before = time.monotonic(), state.snapshot
177
+ self._emit({**event, "state": "sending"})
178
+ try:
179
+ self._drag_next() # XCTest's drag, a measured half screen: a glide's 0.68 can carry the row under a top bar
180
+ self.driver.execute("SWIPE_UP", None, before, timeout=10)
181
+ except Exception as error:
182
+ self._emit({**event, "state": "failed", "detail": type(error).__name__})
183
+ state.snapshot, state.unproven = self._observe(), False # it may have moved: never sent again
184
+ return
185
+ state.snapshot, state.unproven = self._settle(before), False
186
+ self._spend("reveal", t_reveal)
187
+ self._emit({**event, "state": "sent"})
188
+ self._contract.observe(self._masked(state.snapshot), self._app_name(state.snapshot), state.step)
189
+
190
+ def _exact_text(self):
191
+ return callable(getattr(self.driver, "write_text", None))
192
+
193
+ def _write(self, op, element, text, snapshot):
194
+ # A search, an address and a submit into text this task didn't write replace what the field holds.
195
+ append = op in TEXT_OPERATIONS and element.role != "SearchField" and not is_address_field(element) and not (
196
+ op == "TYPE_SUBMIT" and self._foreign_text(element))
197
+ receipt = self.driver.write_text(element, snapshot, text or "", append=append, submit=op == "TYPE_SUBMIT",
198
+ timeout=typing_timeout(text) + 20)
199
+ self.run_state.receipts[element.locator or element.label] = receipt
200
+ self.run_state.last_receipt = receipt
201
+ if receipt.get("value") is not None and not receipt.get("matches"):
202
+ self.run_state.macro_feedback = (
203
+ "The field does not hold what you wrote ("
204
+ + ("it was cleared and written once more" if receipt.get("repaired") else
205
+ "what it held before was kept, and nothing was rewritten: the app may have formatted it")
206
+ + "): " + text_difference(receipt["value"], receipt.get("expected") or "")
207
+ + ". Do not retype blindly: see the field's status on this screen.")
208
+ if op == "TYPE_SUBMIT" and receipt.get("submitted") is False:
209
+ said = self.run_state.macro_feedback
210
+ self.run_state.macro_feedback = ((said + " ") if said else "") + (
211
+ "Return was not pressed, because the field does not hold exactly what you wrote: fix it (SET_TEXT "
212
+ "replaces the field's text), then SUBMIT.")
213
+
214
+ def _appends(self, op, element):
215
+ if not self._exact_text():
216
+ return True
217
+ return element.role != "SearchField" and not is_address_field(element) and not (
218
+ op == "TYPE_SUBMIT" and self._foreign_text(element))
219
+
220
+ def _foreign_text(self, element):
221
+ shown = "" if element.placeholder else (element.value or "")
222
+ if not shown.strip() or shown.strip() == (element.label or "").strip():
223
+ return False
224
+ return receipt_for(self.run_state.receipts, element) is None
225
+
226
+ def _gesture(self, gesture, snapshot):
227
+ send = getattr(self.driver, gesture.kind + "_point", None)
228
+ if not callable(send):
229
+ raise DriverRejection("unsupported_or_stale_target")
230
+ options = {"duration": gesture.duration, "timeout": 15 if gesture.kind == "hold" else 10}
231
+ if gesture.kind == "drag":
232
+ options["hold_seconds"] = gesture.hold_seconds
233
+ send(*(coordinate for location in gesture.points for coordinate in location), snapshot, **options)
234
+
235
+ # Fails closed: unsure means no tap.
236
+ def _still_after_submit(self, element):
237
+ deadline = time.monotonic() + SUBMIT_SETTLE_SECONDS
238
+ previous = self._read_once()
239
+ while True:
240
+ time.sleep(SUBMIT_SETTLE_GAP)
241
+ current = self._read_once()
242
+ same = current.content_fingerprint == previous.content_fingerprint
243
+ if same and element is not None:
244
+ before = self._find(previous, element.label, element.role, element.locator)
245
+ after = self._find(current, element.label, element.role, element.locator)
246
+ if before is None and after is None:
247
+ return current, None
248
+ if before is not None and after is not None and tuple(before.rect) == tuple(after.rect):
249
+ return current, after
250
+ elif same:
251
+ return current, None
252
+ if time.monotonic() >= deadline:
253
+ return None, None
254
+ previous = current
255
+
256
+ def _execute(self, op, element, app, text, snapshot, point=None):
257
+ if op != "READ_LIST":
258
+ self.run_state.list_read = None
259
+ if op == "TAP_XY":
260
+ self.driver.tap_point(point[0], point[1], snapshot, timeout=10, purpose="tap_xy")
261
+ elif op in GESTURE_OPS:
262
+ self._gesture(point, snapshot)
263
+ elif op == "LAUNCH_APP":
264
+ # A start reset's reopen, in the turn that chose it only: never a later LAUNCH_APP.
265
+ fresh, self.run_state.fresh_launch = self.run_state.fresh_launch == (app, self.run_state.calls), None
266
+ relaunch = getattr(self.driver, "relaunch_app", None)
267
+ if not (fresh and callable(relaunch) and relaunch(app, timeout=40)):
268
+ self.driver.execute("LAUNCH_APP", app, snapshot, timeout=20)
269
+ elif op in ("SWIPE_LEFT", "SWIPE_RIGHT") and element is not None:
270
+ self.driver.execute(op, element, snapshot, timeout=10) # through the targeted row (B7)
271
+ elif op == "HOME" or op.startswith("SWIPE_"):
272
+ self.driver.execute(op, None, snapshot, timeout=10)
273
+ elif op == "LONG_PRESS":
274
+ self.driver.long_press(element, snapshot, timeout=10)
275
+ elif op == "READ_LIST":
276
+ self.run_state.listing = self._read_list(snapshot, text)
277
+ elif op == "SCROLL_TO":
278
+ if not self._scroll_to(snapshot, text) and not self.run_state.macro_feedback:
279
+ self.run_state.macro_feedback = (
280
+ f"SCROLL_TO found no item containing {text!r} down to the end of the list.")
281
+ elif op == "DISMISS":
282
+ free = [(px, py) for px, py in DISMISS_POINTS if not any(
283
+ ex <= px <= ex + ew and ey <= py <= ey + eh for ex, ey, ew, eh in
284
+ (e.rect for e in snapshot.elements))] or list(DISMISS_POINTS)
285
+ screen, tries = self.run_state.dismissed
286
+ tries = tries + 1 if screen == snapshot.content_fingerprint else 0
287
+ self.run_state.dismissed = (snapshot.content_fingerprint, tries)
288
+ x, y = free[tries % len(free)]
289
+ # A harness that checks actions sees a DISMISS; a phone driver taps the point.
290
+ tap = getattr(self.driver, "dismiss_at", None)
291
+ (tap if callable(tap) else self.driver.tap_point)(x, y, snapshot, timeout=10)
292
+ elif op == "SET_TEXT" and element.role in ADJUSTABLE_ROLES:
293
+ self.driver.set_value(element, text, timeout=30) # a wheel may be stepped item by item
294
+ elif op in TYPED_OPS and self._exact_text():
295
+ self._write(op, element, text, snapshot)
296
+ elif op == "SET_TEXT" and not text:
297
+ clear = getattr(self.driver, "clear_text", None)
298
+ if not callable(clear):
299
+ raise ValueError("this driver cannot empty a field")
300
+ clear(element, timeout=10)
301
+ elif op == "SET_TEXT" and callable(getattr(self.driver, "replace_text", None)) and self.driver.replace_text(
302
+ element, text, timeout=typing_timeout(text)):
303
+ pass # cleared and typed through one element reference, no read between
304
+ elif op == "SET_TEXT":
305
+ clear = getattr(self.driver, "clear_text", None)
306
+ if callable(clear):
307
+ clear(element, timeout=10)
308
+ # One read: the field is empty when the clear returns.
309
+ try:
310
+ snapshot = self.driver.observe(timeout=15)
311
+ except Exception:
312
+ snapshot = self._observe()
313
+ element = next((e for e in snapshot.elements if e.locator == element.locator), element)
314
+ self.driver.execute("TYPE", element, snapshot, text=text, timeout=typing_timeout(text))
315
+ elif op == "SUBMIT" and "SUBMIT" not in element.actions:
316
+ # Return needs a focused field.
317
+ self.driver.execute("TAP", element, snapshot, timeout=10)
318
+ snapshot = self._observe()
319
+ element = self._find(snapshot, element.label, element.role, element.locator) or element
320
+ self.driver.execute("SUBMIT", element, snapshot, timeout=10)
321
+ else:
322
+ self.driver.execute(op, element, snapshot, text=text, timeout=typing_timeout(text))
323
+ return snapshot
324
+
325
+ @staticmethod
326
+ def _find(snapshot, label, role=None, locator=None):
327
+ want = " ".join((label or "").split()).casefold()
328
+ if not want or not want.strip():
329
+ # A labelled element is found by its label: paths shift as the screen changes (splitpay-001).
330
+ if locator:
331
+ return next((e for e in snapshot.elements if e.locator == locator
332
+ and (role is None or e.role == role)), None)
333
+ return None
334
+ exact = [e for e in snapshot.elements if " ".join((e.label or "").split()).casefold() == want]
335
+ if len(exact) > 1 and locator:
336
+ same = [e for e in exact if e.locator == locator]
337
+ if same:
338
+ return same[0]
339
+ # An unlabelled field is named by its placeholder value ("Search restaurants").
340
+ norm = [(e, " ".join((e.label or (e.value if e.editable else "") or "").split()).casefold())
341
+ for e in snapshot.elements]
342
+ found = [e for e, text in norm if text == want]
343
+ if not found:
344
+ # The model names later targets from memory ("Save" for "Save Note"): a unique partial match is it.
345
+ found = [e for e, text in norm if text and len(want) >= 3 and (want in text or text in want)]
346
+ if len(found) > 1:
347
+ found = [e for e in found if role and e.role == role] if role else []
348
+ if len(found) != 1:
349
+ return None
350
+ if role is None and len(found) > 1:
351
+ # By label alone a control beats text that reads the same: Calculator's display shows the 4 just typed.
352
+ controls = [e for e in found if e.role not in TEXT_ROLES]
353
+ found = controls or found
354
+ return next((e for e in found if role and e.role == role), found[0] if found else None)
355
+
356
+ def _target(self, act, index, aliases, snapshot, after=None):
357
+ element = None
358
+ if act.locator:
359
+ # The Return of a split TYPE_SUBMIT goes to the very field just typed into.
360
+ element = self._find(snapshot, act.target_label, act.role, act.locator)
361
+ if element is not None:
362
+ return element
363
+ if act.target in aliases:
364
+ # Typing changes the screen: later actions re-find an id by its path or label.
365
+ element = aliases[act.target]
366
+ element = self._find(snapshot, element.label, element.role, element.locator) if index else element
367
+ if element is None and act.target_label:
368
+ element = self._find(snapshot, act.target_label)
369
+ if act.op == "TAP":
370
+ element = _container(snapshot, element, " ".join(act.target_label.split()).casefold())
371
+ if element is not None and not same_words(element.label, act.target_label) and _commits(element):
372
+ # A commit is found by its exact label only: "Delete" never finds "Delete Account".
373
+ element = None
374
+ op = act.op
375
+ if element is None and index and (after == "TAP" and op in TYPED_OPS | {"SUBMIT"}
376
+ or after in TYPED_OPS and op == "SUBMIT"):
377
+ # A tap that opened a search screen leaves one field, under another name: the typing goes there.
378
+ fields = [e for e in snapshot.elements if e.editable and (op != "SUBMIT" or "SUBMIT" in e.actions)]
379
+ element = fields[0] if len(fields) == 1 else None
380
+ return element