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,596 @@
1
+ """Replay a recorded path on a driver, with no model.
2
+
3
+ Each step waits for its precondition, then acts through the same driver calls the frontier makes:
4
+
5
+ - **The app**: the screen is in the app the step was recorded in (LAUNCH_APP and HOME need nothing).
6
+ - **The screen**: the same structural screen (its hash), or one at least 80% alike by role/label pairs
7
+ (Jaccard overlap). While the screen settles only those count; once the wait is over, a screen at least 50%
8
+ alike counts too when the step's target is there exactly once.
9
+ - **The target**: exactly one element with the recorded label and role (the recorded tree path breaks a tie). Never
10
+ a partial label: a heal is cheaper than a tap on the wrong control.
11
+
12
+ When a precondition doesn't hold within the wait, the step *drifted*. Then, in order:
13
+
14
+ 1. a step the reviewer chose to keep failing (``strict``) stops the replay: status "drift";
15
+ 2. with no healer (``--no-heal``) the replay stops: status "drift";
16
+ 3. the circuit breaker: when healing this step would make more than half the path healed, the replay stops: status
17
+ "drift", reason "breaker";
18
+ 4. else the healer (the model, for this step only) acts until the screen matches a later recorded step again, or
19
+ finishes the flow. Its actions replace the drifted steps, and the change is returned as a heal entry for review.
20
+
21
+ A step that sends, posts, deletes, buys or submits (its recorded commit act, or harness_api.commit_act on the step
22
+ now) asks ``approve`` first, exactly as a live run asks. With no approver the replay hands that step to the model
23
+ (``on_commit="handoff"``) or stops (``"deny"``): it never commits on its own.
24
+
25
+ Typed text is never invented: a step types its recorded literal, or the value of its blank from ``params``. A step
26
+ the model must do each run (``live``) goes to ``live``, or hands off.
27
+ """
28
+
29
+ import inspect
30
+ import logging
31
+ import time
32
+ from dataclasses import dataclass, field
33
+ from typing import Optional
34
+
35
+ from .. import harness_api
36
+ from ..wda.system_dialogs import DialogStop
37
+ from .path import ELEMENT_OPS, POINT_OPS, SWIPES, TEXT_OPS, anchor, describe, signature_hash
38
+
39
+ STRONG, WEAK = .8, .5
40
+ SEEK_SWIPES = 4
41
+ WAIT_SECONDS = 3.0
42
+ POLL_SECONDS = .15
43
+ MIN_WAIT, STILL_READS = 1.0, 2
44
+ BREAKER = .5
45
+ DIALOG_ROUNDS = 3 # times a step that doesn't match looks for an iOS dialog in front of it before it drifts
46
+
47
+
48
+ @dataclass
49
+ class Heal:
50
+ """What a healer did."""
51
+ ok: bool
52
+ steps: list = field(default_factory=list)
53
+ resume_at: Optional[int] = None
54
+ done: bool = False
55
+ cost_usd: float = 0.0
56
+ reason: str = ""
57
+ status: Optional[str] = None
58
+
59
+
60
+ @dataclass
61
+ class Result:
62
+ status: str # done | drift | stopped | declined | handoff | heal_failed | error | dialog
63
+ steps: list
64
+ strict: list = field(default_factory=list) # protected positions in the resulting path
65
+ completed: int = 0
66
+ replayed: int = 0
67
+ live: int = 0
68
+ heals: list = field(default_factory=list)
69
+ reason: str = ""
70
+ cost_usd: float = 0.0
71
+ seconds: float = 0.0
72
+ at: Optional[int] = None
73
+ answer: Optional[str] = None
74
+ snapshot: object = None
75
+ model_status: Optional[str] = None
76
+
77
+
78
+ def plain(text):
79
+ return " ".join(str(text or "").split()).casefold()
80
+
81
+
82
+ def find_target(step, snapshot):
83
+ """The one element ``step`` acts on in ``snapshot``, or None."""
84
+ target = step.get("target")
85
+ if not isinstance(target, dict):
86
+ return None
87
+ label, role, path = plain(target.get("label")), target.get("role") or "", target.get("id")
88
+ elements = list(getattr(snapshot, "elements", ()) or ())
89
+ if not label:
90
+ found = [e for e in elements if path and e.locator == path and (not role or e.role == role)]
91
+ return found[0] if len(found) == 1 else None
92
+ found = [e for e in elements if plain(e.label) == label and (not role or e.role == role)]
93
+ if len(found) > 1 and path:
94
+ found = [e for e in found if e.locator == path] or found
95
+ return found[0] if len(found) == 1 else None
96
+
97
+
98
+ def screen_likeness(step, snapshot):
99
+ from ..state import signature
100
+ screen = step.get("screen") or {}
101
+ pairs = screen.get("signature")
102
+ if not pairs and not screen.get("hash") and not screen.get("shape"):
103
+ return 0.0
104
+ now = signature(snapshot)
105
+ if screen.get("hash") and signature_hash(now) == screen["hash"]:
106
+ return 1.0
107
+ if not pairs and screen.get("shape"):
108
+ # Kept as hashed pairs (a saved program keeps no screen text): the same overlap measure on the hashes.
109
+ from .path import screen_shape
110
+ ours, theirs = set(screen["shape"]), set(screen_shape(now))
111
+ union = ours | theirs
112
+ return len(ours & theirs) / len(union) if union else 1.0
113
+ if not pairs:
114
+ return 0.0
115
+ ours, theirs = {tuple(pair) for pair in pairs}, {tuple(pair) for pair in now}
116
+ union = ours | theirs
117
+ return len(ours & theirs) / len(union) if union else 1.0
118
+
119
+
120
+ def match(step, snapshot, *, settled=False):
121
+ """(ok, element, why) for ``step`` on ``snapshot``."""
122
+ op = step.get("op")
123
+ if op in ("LAUNCH_APP", "HOME", "WAIT"):
124
+ return True, None, ""
125
+ app, now = step.get("app"), getattr(snapshot, "bundle_id", "") or ""
126
+ if app and app != now:
127
+ return False, None, f"{now or 'an unknown app'} is in front, not {app}"
128
+ if op in POINT_OPS:
129
+ proof = (step.get("screen") or {}).get("fingerprint")
130
+ if not proof:
131
+ return False, None, "the recorded point has no screen geometry proof"
132
+ if proof != getattr(snapshot, "content_fingerprint", None):
133
+ return False, None, "the screen geometry changed around the recorded point"
134
+ likeness = screen_likeness(step, snapshot)
135
+ element = None
136
+ if op in ELEMENT_OPS or (op in SWIPES and step.get("target")):
137
+ element = find_target(step, snapshot)
138
+ if element is None and op in ELEMENT_OPS:
139
+ label = (step.get("target") or {}).get("label")
140
+ return False, None, (f'"{label}" isn\'t on the screen' if label else "the field isn't on the screen")
141
+ if likeness >= STRONG or (settled and element is not None and likeness >= WEAK):
142
+ return True, element, ""
143
+ if settled and element is not None and not (step.get("screen") or {}).get("signature") \
144
+ and not (step.get("screen") or {}).get("shape"):
145
+ # A step learned from a live run's step records has only its screen's hash: pinned by its target alone.
146
+ return True, element, ""
147
+ return False, None, f"the screen is different ({round(likeness * 100)}% alike)"
148
+
149
+
150
+ class Replayer:
151
+ """Replays one path on ``driver``; a recorded step never opens or acts in an app the run says no to."""
152
+
153
+ def __init__(self, driver, *, approve=None, heal=None, live=None, emit=None, cancelled=None, params=None,
154
+ frame=None, on_commit="handoff", wait_seconds=WAIT_SECONDS, breaker=BREAKER, sleep=time.sleep,
155
+ clock=time.monotonic, app_names=None, allows_app=None, decide=None, dialogs=None):
156
+ self.driver = driver
157
+ # dialogs: a verify.dialogs.Responder. iOS's own dialogs (permission alerts, Allow Paste, the purchase sheet)
158
+ # are answered from the check's policy between steps, and are neither a step nor a heal. None: the model's job.
159
+ self.dialogs = dialogs
160
+ # decide(step, element, snapshot, text) -> permissions.gate.Decision: the run's permission gate, so a replayed
161
+ # step asks what the same step asks live (ask_all's changes, a like or follow, an amount, a one-time code). None
162
+ # (replay_core.checks on simulators): the recorded commit act alone.
163
+ self.decide = decide
164
+ # allows_app(bundle) -> bool: whether the run may use an app (its first-use card asks). None: every app.
165
+ self.allows_app = allows_app
166
+ self.approve, self.heal, self.live = approve, heal, live
167
+ self.emit = emit or (lambda event: None)
168
+ self.cancelled = cancelled or (lambda: False)
169
+ self.params = dict(params or {})
170
+ self.frame = frame
171
+ self.on_commit = on_commit
172
+ self.wait_seconds, self.breaker = wait_seconds, breaker
173
+ self.sleep, self.clock = sleep, clock
174
+ self.app_names = dict(app_names or {})
175
+
176
+ def read(self):
177
+ ready = getattr(self.driver, "observe_ready", None)
178
+ return ready(timeout=15) if callable(ready) else self.driver.observe(timeout=15)
179
+
180
+ def settle(self, before, op):
181
+ if op in TEXT_OPS:
182
+ return self.driver.observe(timeout=15)
183
+ native = getattr(self.driver, "wait_for_change", None)
184
+ if callable(native):
185
+ try:
186
+ return native(before, wait_seconds=.6, timeout=2.1)
187
+ except Exception:
188
+ pass
189
+ return self.read()
190
+
191
+ def wait_for(self, step, snapshot):
192
+ """(ok, element, snapshot, why) once the precondition holds; a weak match goes only once two reads agree."""
193
+ started = self.clock()
194
+ deadline = started + self.wait_seconds
195
+ still, last = 0, getattr(snapshot, "content_fingerprint", None)
196
+ while True:
197
+ ok, element, why = match(step, snapshot)
198
+ if ok:
199
+ return True, element, snapshot, ""
200
+ if still >= 1:
201
+ ok, element, why = match(step, snapshot, settled=True)
202
+ if ok:
203
+ return True, element, snapshot, ""
204
+ now = self.clock()
205
+ if now >= deadline or (still >= STILL_READS and now - started >= MIN_WAIT) or self.cancelled():
206
+ ok, element, why = match(step, snapshot, settled=True)
207
+ return ok, element, snapshot, why
208
+ self.sleep(POLL_SECONDS)
209
+ snapshot = self.read()
210
+ key = getattr(snapshot, "content_fingerprint", None)
211
+ still = still + 1 if key is not None and key == last else 0
212
+ last = key
213
+
214
+ def run(self, path):
215
+ steps = list(path.get("steps") or ())
216
+ strict = {int(n) for n in path.get("strict") or ()}
217
+ started = self.clock()
218
+ out = Result(status="done", steps=[])
219
+ total = len(steps)
220
+ healed_over = 0
221
+ index = 0
222
+ attempted = False # an action or a model callback may have acted: an exception from here on is unknown
223
+ snapshot = self.read()
224
+
225
+ def answer_dialogs(current):
226
+ """The screen after iOS's dialogs in front were answered (verify.dialogs); a stop becomes the result."""
227
+ if self.dialogs is None:
228
+ return current, 0
229
+ return self.dialogs.clear(current, thorough=True, read=self.read)
230
+
231
+ def finish(status, reason="", at=None, **extra):
232
+ out.status, out.reason, out.at = status, reason, at
233
+ out.seconds = round(self.clock() - started, 3)
234
+ out.snapshot = snapshot
235
+ for key, value in extra.items():
236
+ setattr(out, key, value)
237
+ if status != "done":
238
+ out.steps.extend(steps[index:]) # the rest stays as recorded
239
+ try:
240
+ self.emit({"event": "replay_finished", "status": status, "reason": reason, "replayed": out.replayed,
241
+ "healed": len(out.heals), "live": out.live, "seconds": out.seconds})
242
+ except Exception: # an observer never costs the run its result, an unknown outcome included
243
+ logging.getLogger(__name__).exception("replay completion observer failed")
244
+ return out
245
+
246
+ self.emit({"event": "replay_started", "steps": total})
247
+ asked_at, rounds = -1, 0
248
+ try:
249
+ while index < total:
250
+ if self.cancelled():
251
+ return finish("stopped", "The run was stopped.", index)
252
+ step = steps[index]
253
+ if asked_at != index:
254
+ asked_at, rounds = index, 0
255
+ try:
256
+ snapshot, _ = answer_dialogs(snapshot)
257
+ except DialogStop as stop:
258
+ return finish("dialog", str(stop), index)
259
+ if step.get("live"):
260
+ if self.live is None:
261
+ return finish("handoff", f"step {index + 1} ({describe(step)}) needs the model", index)
262
+ attempted = True
263
+ outcome = self.live(index, step, snapshot, list(out.steps))
264
+ out.cost_usd += outcome.cost_usd
265
+ if not outcome.ok:
266
+ return finish("heal_failed", outcome.reason or f"the model couldn't do step {index + 1}", index,
267
+ model_status=outcome.status)
268
+ resume = outcome.resume_at if outcome.resume_at is not None and outcome.resume_at > index \
269
+ else index + 1
270
+ if outcome.done:
271
+ resume = total
272
+ if any(index + 1 < number <= resume for number in strict):
273
+ return finish("drift", f"Step {index + 1} ({describe(step)}): the model crossed a step you "
274
+ "chose to keep failing. Review this check before running it again.",
275
+ index)
276
+ out.live += 1
277
+ out.steps.append(step) # a live step stays live
278
+ if index + 1 in strict:
279
+ out.strict.append(len(out.steps))
280
+ out.completed = index + 1
281
+ if outcome.done:
282
+ index = total
283
+ snapshot = self.read()
284
+ break
285
+ index = resume
286
+ snapshot = self.read()
287
+ continue
288
+ refused = self._app_refused(step, snapshot)
289
+ if refused: # before anything in that app, a seek's swipes included
290
+ return finish("handoff", refused, index)
291
+ if step.get("seek"):
292
+ try:
293
+ snapshot = self.seek(step, snapshot)
294
+ except Exception as error:
295
+ # A swipe may have landed before the failure: stop, never swipe again.
296
+ return finish("error", f"Step {index + 1} ({describe(step)}) failed while seeking: "
297
+ f"{type(error).__name__}: {str(error)[:160]}", index,
298
+ model_status="unknown_outcome")
299
+ ok, element, snapshot, why = self.wait_for(step, snapshot)
300
+ if self.cancelled():
301
+ return finish("stopped", "The run was stopped.", index)
302
+ refused = self._app_refused(step, snapshot)
303
+ if refused:
304
+ return finish("handoff", refused, index)
305
+ if not ok and self.dialogs is not None and rounds < DIALOG_ROUNDS:
306
+ # A dialog that came up while the step waited: answer it, then look at the step again.
307
+ rounds += 1
308
+ try:
309
+ snapshot, answered = answer_dialogs(snapshot)
310
+ except DialogStop as stop:
311
+ return finish("dialog", str(stop), index)
312
+ if answered:
313
+ continue
314
+ if not ok:
315
+ self.emit({"event": "replay_drift", "step": index + 1, "action": describe(step), "why": why})
316
+ if index + 1 in strict:
317
+ return finish("drift", f"Step {index + 1} ({describe(step)}): {why}. You chose to keep this "
318
+ "check failing when this step changes.", index)
319
+ if self.heal is None:
320
+ return finish("drift", f"Step {index + 1} ({describe(step)}): {why}.", index)
321
+ if (healed_over + 1) > total * self.breaker:
322
+ return finish("drift",
323
+ f"More than half the recorded steps would heal (step {index + 1}: {why}), "
324
+ "so Mobster stopped instead of healing.", index, model_status="breaker")
325
+ before = self._frame("heal-before")
326
+ limit = max(1, int(total * self.breaker) - healed_over)
327
+ attempted = True
328
+ outcome = self.heal(index, step, snapshot, why, list(out.steps), limit)
329
+ out.cost_usd += outcome.cost_usd
330
+ breaker = (outcome.status == "breaker" or (outcome.ok and healed_over + len(
331
+ steps[index:total if outcome.done else max(outcome.resume_at or index + 1, index)])
332
+ > total * self.breaker))
333
+ if breaker:
334
+ snapshot = self.read()
335
+ return finish("drift", f"More than half the recorded steps would heal (from step {index + 1}: "
336
+ f"{why}), so Mobster stopped instead of healing.", index,
337
+ model_status="breaker")
338
+ if not outcome.ok:
339
+ return finish("heal_failed", outcome.reason or f"Smart couldn't heal step {index + 1}", index,
340
+ model_status=outcome.status)
341
+ snapshot = self.read()
342
+ refused = self._app_refused(step, snapshot)
343
+ if refused:
344
+ return finish("handoff", refused, index)
345
+ after = self._frame("heal-after")
346
+ resume = total if outcome.done else outcome.resume_at
347
+ if resume is None or resume < index or (resume == index and not outcome.steps):
348
+ resume = index + 1 # resume == index with new steps: the heal added steps before this one
349
+ if any(index + 1 <= number <= resume for number in strict):
350
+ return finish("drift", f"Step {index + 1} ({describe(step)}): the heal crossed a step you "
351
+ "chose to keep failing. Review this check before running it again.",
352
+ index)
353
+ old = steps[index:resume]
354
+ healed_over += len(old)
355
+ out.heals.append(heal_entry(index, old, outcome.steps, why, before, after))
356
+ out.steps.extend(outcome.steps)
357
+ out.completed = resume
358
+ self.emit({"event": "replay_healed", "step": index + 1, "from": describe(step),
359
+ "to": describe(outcome.steps[0]) if outcome.steps else "nothing", "resume": resume + 1})
360
+ if outcome.done:
361
+ out.model_status = outcome.status
362
+ index = total
363
+ break
364
+ index = resume
365
+ continue
366
+ gate = self._commit(step, element, snapshot, index)
367
+ act = None
368
+ if gate is not None:
369
+ act, refusal, approved_screen = gate
370
+ if refusal is not None:
371
+ status, answer = refusal
372
+ if status == "handoff":
373
+ return finish("handoff", f"step {index + 1} ({describe(step)}) commits and asks a person "
374
+ "through the model", index)
375
+ return finish("declined", f"{describe(step)} wasn't approved ({answer}).", index,
376
+ answer=answer)
377
+ if self.cancelled(): # stopped while an approval waited
378
+ return finish("stopped", "The run was stopped.", index)
379
+ current = self.read()
380
+ if current.fingerprint != approved_screen:
381
+ return finish("drift", f"Step {index + 1} ({describe(step)}): the screen changed while "
382
+ "approval was waiting.", index)
383
+ refused = self._app_refused(step, current)
384
+ if refused:
385
+ return finish("handoff", refused, index)
386
+ ok, element, why = match(step, current, settled=True)
387
+ if not ok:
388
+ return finish("drift", f"Step {index + 1} ({describe(step)}): {why} after approval.", index)
389
+ snapshot = current
390
+ if self.cancelled():
391
+ return finish("stopped", "The run was stopped.", index)
392
+ attempted = True
393
+ try:
394
+ self.act(step, element, snapshot)
395
+ except Exception as error:
396
+ # The action may have landed: report an unknown outcome and never send it again.
397
+ return finish("error", f"Step {index + 1} ({describe(step)}) failed: {type(error).__name__}: "
398
+ f"{str(error)[:160]}", index, model_status="unknown_outcome")
399
+ out.replayed += 1
400
+ executed = refreshed(step, element, snapshot)
401
+ if act:
402
+ executed["commit"] = act
403
+ out.steps.append(executed)
404
+ if index + 1 in strict:
405
+ out.strict.append(len(out.steps))
406
+ out.completed = index + 1
407
+ self.emit({"event": "replay_step", "step": index + 1, "op": step["op"], "action": describe(step),
408
+ "label": (step.get("target") or {}).get("label"),
409
+ "role": (step.get("target") or {}).get("role"),
410
+ "typed": self.text_for(step) if step["op"] in TEXT_OPS else None, "app": step.get("app")})
411
+ snapshot = self.settle(snapshot, step["op"])
412
+ index += 1
413
+ except Exception as error:
414
+ if not attempted:
415
+ raise # nothing was sent: the caller can hand the run on
416
+ # Something may have acted, even before ``completed`` advanced (a model callback raising after its
417
+ # tap): stop with what is known and never run it again.
418
+ index = max(index, out.completed)
419
+ detail = f"{type(error).__name__}: {str(error)[:160]}"
420
+ reason = (f"Step {index} ({describe(steps[index - 1])}) ran, then Mobster lost the screen: {detail}"
421
+ if out.completed else f"Step {index + 1} ({describe(steps[index])}) failed: {detail}")
422
+ return finish("error", reason, index, model_status="unknown_outcome")
423
+ try:
424
+ snapshot, _ = answer_dialogs(snapshot) # a dialog the last step brought up would hide the screen it reads
425
+ except DialogStop as stop:
426
+ return finish("dialog", str(stop), index)
427
+ return finish("done")
428
+
429
+ def seek(self, step, snapshot):
430
+ for _ in range(SEEK_SWIPES):
431
+ if find_target(step, snapshot) is not None or self.cancelled() or self._app_refused(step, snapshot):
432
+ return snapshot
433
+ app, now = step.get("app"), getattr(snapshot, "bundle_id", "") or ""
434
+ if app and app != now:
435
+ return snapshot
436
+ before = snapshot
437
+ self.driver.execute("SWIPE_UP", None, snapshot, timeout=10)
438
+ snapshot = self.settle(before, "SWIPE_UP")
439
+ self.emit({"event": "replay_seek", "label": (step.get("target") or {}).get("label")})
440
+ if getattr(snapshot, "content_fingerprint", None) == getattr(before, "content_fingerprint", object()):
441
+ return snapshot
442
+ return snapshot
443
+
444
+ def _app_refused(self, step, snapshot):
445
+ """Why step ``step`` may not run here (the app it opens, or the one in front, isn't allowed), else ""."""
446
+ if self.allows_app is None:
447
+ return ""
448
+ apps = [getattr(snapshot, "bundle_id", "") or ""]
449
+ if step["op"] == "LAUNCH_APP":
450
+ apps.append(step.get("app") or "")
451
+ for bundle in apps:
452
+ if not bundle or bundle == "com.apple.springboard":
453
+ continue
454
+ try:
455
+ allowed = bool(self.allows_app(bundle))
456
+ except Exception: # noqa: BLE001 -- fails closed
457
+ allowed = False
458
+ if not allowed:
459
+ return f"{self.app_names.get(bundle) or bundle} isn't allowed yet"
460
+ return ""
461
+
462
+ def _frame(self, label):
463
+ if self.frame is None:
464
+ return None
465
+ try:
466
+ return self.frame(label)
467
+ except Exception:
468
+ return None
469
+
470
+ def _commit(self, step, element, snapshot, index):
471
+ """None for a plain step; otherwise (effective act, refusal, approved screen fingerprint)."""
472
+ label = element.label if element is not None else (step.get("target") or {}).get("label")
473
+ act = step.get("commit") or harness_api.commit_act(step["op"], label, None,
474
+ getattr(snapshot, "bundle_id", None) or step.get("app"))
475
+ if self.decide is not None:
476
+ try:
477
+ decision = self.decide(step, element, snapshot, self.text_for(step))
478
+ except Exception: # noqa: BLE001 -- an unreadable step asks
479
+ decision = None
480
+ # Asks when either says so: the recorded act, or today's rules for this step as it looks now.
481
+ if decision is None:
482
+ act = act or "act"
483
+ elif decision.asks:
484
+ act = act or decision.act or decision.kind
485
+ if not act:
486
+ return None
487
+ if self.approve is None:
488
+ refusal = ("handoff", None) if self.on_commit == "handoff" else ("declined", "no one to ask")
489
+ return act, refusal, None
490
+ x, y, w, h = element.rect if element is not None else (0, 0, 0, 0)
491
+ app = getattr(snapshot, "bundle_id", None) or step.get("app")
492
+ request = {"kind": "commit", "step": index + 1, "operation": step["op"], "label": (label or "")[:200],
493
+ "role": element.role if element is not None else None, "text": self.text_for(step),
494
+ "title": f"{describe(step)}?", "act": act, "app_name": self.app_names.get(app) or None,
495
+ "target": {"x": round(x, 4), "y": round(y, 4), "w": round(w, 4), "h": round(h, 4)},
496
+ "replayed": True}
497
+ approved_screen = snapshot.fingerprint # capture before an approval callback can mutate the snapshot
498
+ answer = self.approve(request)
499
+ self.emit({"event": "replay_approval", "step": index + 1, "act": act,
500
+ "answer": answer.split(":", 1)[0] if isinstance(answer, str) else None})
501
+ if answer == "approved":
502
+ return act, None, approved_screen
503
+ return act, ("declined", str(answer or "denied")), None
504
+
505
+ def text_for(self, step):
506
+ if step.get("param"):
507
+ return self.params.get(step["param"])
508
+ return step.get("text")
509
+
510
+ def act(self, step, element, snapshot):
511
+ """One action through the driver, the way frontier._execute sends it."""
512
+ from ..state import ADJUSTABLE_ROLES, TEXT_OPERATIONS, typing_timeout
513
+ driver, op = self.driver, step["op"]
514
+ if op == "LAUNCH_APP":
515
+ driver.execute("LAUNCH_APP", step["app"], snapshot, timeout=20)
516
+ elif op == "HOME":
517
+ driver.execute("HOME", None, snapshot, timeout=10)
518
+ elif op == "WAIT":
519
+ self.sleep(1.0)
520
+ elif op in POINT_OPS:
521
+ x, y = step["point"]
522
+ tap = driver.tap_point
523
+ try: # bind first: a TypeError raised inside the tap must never send it a second time
524
+ inspect.signature(tap).bind(x, y, snapshot, timeout=10, purpose=op.casefold())
525
+ except TypeError:
526
+ tap(x, y, snapshot, timeout=10)
527
+ else:
528
+ tap(x, y, snapshot, timeout=10, purpose=op.casefold())
529
+ elif op in SWIPES:
530
+ driver.execute(op, element if op in ("SWIPE_LEFT", "SWIPE_RIGHT") else None, snapshot, timeout=10)
531
+ elif op == "LONG_PRESS":
532
+ driver.long_press(element, snapshot, timeout=10)
533
+ elif op == "TAP":
534
+ driver.execute("TAP", element, snapshot, timeout=10)
535
+ elif op == "SUBMIT":
536
+ if "SUBMIT" not in element.actions:
537
+ driver.execute("TAP", element, snapshot, timeout=10)
538
+ snapshot = self.read()
539
+ element = find_target(step, snapshot) or element
540
+ driver.execute("SUBMIT", element, snapshot, timeout=10)
541
+ elif op in TEXT_OPS:
542
+ text = self.text_for(step)
543
+ if not isinstance(text, str):
544
+ raise ValueError("no text for this step")
545
+ if op == "SET_TEXT" and element.role in ADJUSTABLE_ROLES:
546
+ driver.set_value(element, text, timeout=10)
547
+ elif callable(getattr(driver, "write_text", None)):
548
+ append = op in TEXT_OPERATIONS and element.role != "SearchField"
549
+ receipt = driver.write_text(element, snapshot, text, append=append, submit=op == "TYPE_SUBMIT",
550
+ timeout=typing_timeout(text) + 20)
551
+ if not receipt.get("matches") or op == "TYPE_SUBMIT" and not receipt.get("submitted"):
552
+ raise ValueError("The driver did not confirm the recorded text or its submission; inspect "
553
+ "the field before trying again")
554
+ elif op == "SET_TEXT" and callable(getattr(driver, "replace_text", None)) and driver.replace_text(
555
+ element, text, timeout=typing_timeout(text)):
556
+ pass
557
+ elif op == "SET_TEXT":
558
+ clear = getattr(driver, "clear_text", None)
559
+ if callable(clear):
560
+ clear(element, timeout=10)
561
+ driver.execute("TYPE", element, snapshot, text=text, timeout=typing_timeout(text))
562
+ else:
563
+ driver.execute(op, element, snapshot, text=text, timeout=typing_timeout(text))
564
+ else:
565
+ raise ValueError(f"{op} can't be replayed")
566
+
567
+
568
+ def refreshed(step, element, snapshot):
569
+ """``step`` with its anchors brought up to date; the label and role are what matched, so this is no heal."""
570
+ new = dict(step)
571
+ if step["op"] in POINT_OPS:
572
+ return new # the point stays bound to its recorded pre-action geometry
573
+ from ..state import signature
574
+ if element is not None and isinstance(step.get("target"), dict):
575
+ new["target"] = {**step["target"], "id": element.locator or step["target"].get("id"),
576
+ "frame": [round(float(v), 4) for v in element.rect]}
577
+ try:
578
+ pairs = signature(snapshot)
579
+ new["screen"] = {"hash": signature_hash(pairs), "signature": pairs}
580
+ except Exception:
581
+ pass
582
+ return new
583
+
584
+
585
+ def heal_entry(index, old, new, why, before=None, after=None):
586
+ """A heal as results.json's ``healed[]`` and the .heal.yaml file carry it."""
587
+ return {"step": index + 1, "text": describe(old[0]) if old else "",
588
+ "from": describe(old[0]) if old else "nothing",
589
+ "to": "; ".join(describe(step) for step in new[:3]) + ("; …" if len(new) > 3 else "") if new
590
+ else "nothing (the screen it needed came by itself)",
591
+ "why": why, "old": [anchor(step) for step in old], "new": [anchor(step) for step in new],
592
+ "replaced": len(old), "frames": {"before": before and str(before), "after": after and str(after)},
593
+ "status": "pending"}
594
+
595
+
596
+ __all__ = ["Replayer", "Result", "Heal", "match", "find_target", "heal_entry", "describe"]