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,223 @@
1
+ """The evidence read: the app's accessibility tree as WebDriverAgent reports it, with each node's visibility."""
2
+
3
+ import hashlib
4
+ import json
5
+ import math
6
+ import time
7
+ from dataclasses import dataclass
8
+
9
+ from ..state import _parse_wda
10
+ from ..wda.observe import WDA_RICH_SOURCE_PATH
11
+
12
+ # Keeps ``visible`` and ``traits``: p50 0.76-1.09 s on Settings (28 Sep, n=20), under the spec's 1.5 s.
13
+ EVIDENCE_SOURCE_PATH = "/source?format=xml&excluded_attributes=accessible,index"
14
+ # No ``visible``: 0.14-0.5 s against 1.4-1.9 s (17 Oct). It says whether the screen moved; it never decides.
15
+ GLANCE_SOURCE_PATH = WDA_RICH_SOURCE_PATH
16
+ MAX_DEPTH = 64
17
+ MAX_NODES = 5000
18
+ TEXT_LIMIT = 4000
19
+ STRUCTURAL = frozenset({"Application", "Window"})
20
+ # Containers WDA reports not visible while their content shows: hidden_by_ancestor leaves them out.
21
+ SCROLL_CONTAINERS = frozenset({"CollectionView", "Table", "ScrollView", "WebView"})
22
+ TOGGLES = frozenset({"Switch", "Toggle"})
23
+ RETRY_PAUSE = .75 # seconds before the evidence read's one retry
24
+
25
+
26
+ class TreeError(ValueError):
27
+ """The source could not be read as an accessibility tree (too large, too deep, unsafe or malformed)."""
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class AXNode:
32
+ path: str
33
+ role: str # the XCUIElementType name without its prefix: "Button", "StaticText"
34
+ identifier: str # the accessibility identifier: WDA's name when it differs from the label
35
+ label: str
36
+ value: str
37
+ placeholder: str
38
+ rect: tuple # points: x, y, w, h
39
+ enabled: bool
40
+ selected: bool
41
+ visible: bool # WDA's own visible attribute (True when the read leaves it out)
42
+ hidden_by_ancestor: bool = False # an ancestor with area, not a scroll container, is visible="false" (data only)
43
+ inner_toggle: bool = False # an unlabelled Switch with no identifier inside a Switch: its knob (never shown)
44
+
45
+
46
+ def _number(value):
47
+ try:
48
+ number = float(value)
49
+ except (TypeError, ValueError):
50
+ return 0.0
51
+ return number if math.isfinite(number) else 0.0
52
+
53
+
54
+ def _text(value):
55
+ value = value or ""
56
+ if len(value) > TEXT_LIMIT:
57
+ raise TreeError(f"The accessibility tree has text longer than {TEXT_LIMIT:,} characters")
58
+ return value
59
+
60
+
61
+ class AXTree:
62
+ """One read of the app's tree: ``nodes`` in document order."""
63
+
64
+ def __init__(self, bundle_id, size, nodes):
65
+ self.bundle_id = bundle_id
66
+ self.size = tuple(size)
67
+ self.nodes = tuple(nodes)
68
+ self._shown = None
69
+ self.glance = False # read without ``visible``: it may order a re-read, never decide a verdict
70
+
71
+ def on_screen(self, node):
72
+ """The node's frame overlaps the app's frame with positive area."""
73
+ x, y, w, h = node.rect
74
+ width, height = self.size
75
+ return min(width, x + w) - max(0.0, x) > 0 and min(height, y + h) - max(0.0, y) > 0
76
+
77
+ def is_laid_out(self, node):
78
+ """Every ``shown`` rule except WDA's own ``visible``."""
79
+ return node.role not in STRUCTURAL and not node.inner_toggle and self.on_screen(node)
80
+
81
+ def is_shown(self, node):
82
+ # The node's own ``visible``: WDA calls containers not visible while their content is on screen.
83
+ return node.visible and self.is_laid_out(node)
84
+
85
+ def shown(self):
86
+ if self._shown is None:
87
+ self._shown = [node for node in self.nodes if self.is_shown(node)]
88
+ return list(self._shown)
89
+
90
+ def select(self, selector):
91
+ """Shown nodes matching ``selector`` (an ``assertions.Selector``, or a selector object)."""
92
+ from .assertions import Selector, parse_selector
93
+ if not isinstance(selector, Selector):
94
+ selector = parse_selector(selector)
95
+ return [node for node in self.shown() if selector.matches(node)]
96
+
97
+ def by_path(self, path):
98
+ return next((node for node in self.nodes if node.path == path), None)
99
+
100
+ def _digest(self, nodes):
101
+ rows = [self.bundle_id, self.size, [
102
+ [node.path, node.role, node.identifier, node.label, node.value, node.placeholder,
103
+ node.enabled, node.selected, [round(node.rect[0]), round(node.rect[1]), *node.rect[2:]]]
104
+ for node in nodes]]
105
+ return hashlib.sha256(json.dumps(rows, ensure_ascii=False).encode()).hexdigest()
106
+
107
+ def _evidence_digest(self, nodes):
108
+ # These are the complete AXNode fields consumed by selectors and screen audits, including exact dimensions.
109
+ rows = [self.bundle_id, self.size, [vars(node) for node in nodes]]
110
+ return hashlib.sha256(json.dumps(rows, ensure_ascii=False).encode()).hexdigest()
111
+
112
+ def fingerprint(self):
113
+ """Identity of the shown assertion inputs on a full evidence read."""
114
+ return self._evidence_digest(self.shown())
115
+
116
+ def audit_fingerprint(self):
117
+ """Identity of every input a screen audit may read, including offscreen nodes."""
118
+ return self._evidence_digest(self.nodes)
119
+
120
+ def layout(self):
121
+ """The fingerprint over the nodes that pass every ``shown`` rule except WDA's ``visible``."""
122
+ return self._digest([node for node in self.nodes if self.is_laid_out(node)])
123
+
124
+
125
+ def parse_tree(xml):
126
+ """The evidence read as an AXTree."""
127
+ try:
128
+ root = _parse_wda(xml)
129
+ except ValueError as error:
130
+ raise TreeError(f"The accessibility tree could not be read: {error}") from None
131
+ except Exception as error: # xml.etree.ElementTree.ParseError and friends
132
+ raise TreeError(f"The accessibility tree could not be read: {type(error).__name__}") from None
133
+ app = next((node for node in root.iter() if node.tag == "XCUIElementTypeApplication"), None)
134
+ if app is None:
135
+ raise TreeError("The accessibility tree has no application")
136
+ width, height = _number(app.attrib.get("width")), _number(app.attrib.get("height"))
137
+ if width <= 0 or height <= 0:
138
+ raise TreeError("The accessibility tree's application has no size")
139
+ nodes = []
140
+
141
+ def walk(node, path, depth, hidden, parent_role):
142
+ if depth > MAX_DEPTH:
143
+ raise TreeError(f"The accessibility tree is deeper than {MAX_DEPTH} levels")
144
+ if len(nodes) >= MAX_NODES:
145
+ raise TreeError(f"The accessibility tree has more than {MAX_NODES:,} elements")
146
+ a = node.attrib
147
+ role = node.tag.removeprefix("XCUIElementType")
148
+ x, y = _number(a.get("x")), _number(a.get("y"))
149
+ w, h = _number(a.get("width")), _number(a.get("height"))
150
+ visible = a.get("visible", "true") != "false"
151
+ label = _text(a.get("label"))
152
+ name = _text(a.get("name"))
153
+ traits = [trait.strip() for trait in (a.get("traits") or "").split(",")]
154
+ # A SwiftUI Toggle reads as a labelled Switch around an unlabelled knob (iOS 26.4): the knob is inner_toggle.
155
+ nodes.append(AXNode(
156
+ path=path, role=role, identifier=name if name and name != label else "", label=label,
157
+ value=_text(a.get("value")), placeholder=_text(a.get("placeholderValue")),
158
+ rect=(x, y, w, h), enabled=a.get("enabled", "true") != "false",
159
+ selected="Selected" in traits or a.get("selected") == "true", visible=visible,
160
+ hidden_by_ancestor=hidden,
161
+ inner_toggle=role in TOGGLES and parent_role in TOGGLES and not label.strip() and not name.strip()))
162
+ hides = not visible and w * h > 0 and role not in SCROLL_CONTAINERS # for hidden_by_ancestor only
163
+ counts = {}
164
+ for child in node:
165
+ counts[child.tag] = counts.get(child.tag, 0) + 1
166
+ walk(child, f"{path}/{child.tag}[{counts[child.tag]}]", depth + 1, hidden or hides, role)
167
+
168
+ walk(root, f"/{root.tag}", 0, False, None)
169
+ return AXTree(app.attrib.get("bundleId", "") or "", (width, height), nodes)
170
+
171
+
172
+ def read_source(driver, timeout=30, pause=None, retry_slow=True, path=EVIDENCE_SOURCE_PATH):
173
+ # Right after a relaunch WDA can answer "not running" for a moment, so the one retry waits RETRY_PAUSE. A read that
174
+ # used all of ``timeout`` was slow, not caught in a moment: with retry_slow False it is not tried again.
175
+ own = getattr(driver, "evidence_source", None) # a connection with its own tree read (Android)
176
+ if own is not None:
177
+ return own(timeout)
178
+ last = None
179
+ for attempt in range(2):
180
+ if attempt:
181
+ (pause or time.sleep)(RETRY_PAUSE)
182
+ started = time.monotonic()
183
+ try:
184
+ xml = driver.call("GET", path, timeout=timeout)
185
+ if not isinstance(xml, str):
186
+ raise TreeError("WebDriverAgent did not return the accessibility tree")
187
+ return xml
188
+ except TreeError:
189
+ raise
190
+ except Exception as error: # transport errors, timeouts, a runner that went away
191
+ last = error
192
+ if not retry_slow and time.monotonic() - started >= timeout - .1:
193
+ break
194
+ raise last
195
+
196
+
197
+ def read_tree(driver, timeout=30, pause=None, retry_slow=True, path=EVIDENCE_SOURCE_PATH):
198
+ return parse_tree(read_source(driver, timeout=timeout, pause=pause, retry_slow=retry_slow, path=path))
199
+
200
+
201
+ def tree_from_pixels(snapshot):
202
+ # Paths start with /pixels, which no Element locator does, so a tap lands on the frame and never on another read's.
203
+ width, height = snapshot.width, snapshot.height
204
+ nodes = [AXNode(path=f"/pixels/{element.role}[{index}]", role=element.role, identifier="", label=element.label,
205
+ value=element.value, placeholder=element.placeholder,
206
+ rect=(element.rect[0] * width, element.rect[1] * height,
207
+ element.rect[2] * width, element.rect[3] * height),
208
+ enabled=element.enabled, selected=element.selected, visible=True)
209
+ for index, element in enumerate(snapshot.elements, 1)]
210
+ return AXTree(snapshot.bundle_id, (width, height), nodes)
211
+
212
+
213
+ def screen_title(tree):
214
+ """The title a screen shows: its navigation bar's, else the top-most text in the upper third."""
215
+ shown = tree.shown()
216
+ for node in shown:
217
+ if node.role == "NavigationBar":
218
+ title = (node.label or node.identifier).strip()
219
+ if title:
220
+ return title[:120]
221
+ top = tree.size[1] / 3
222
+ texts = [node for node in shown if node.role == "StaticText" and node.label.strip() and 0 <= node.rect[1] < top]
223
+ return min(texts, key=lambda node: (node.rect[1], node.rect[0])).label.strip()[:120] if texts else ""
@@ -0,0 +1,377 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import time
5
+ import traceback
6
+ from pathlib import Path
7
+
8
+ from .. import project_env
9
+ from . import perf, proof, snapshot_run
10
+ from .checks import dump_check, project_root
11
+ from .smart import VIEW_SERVICE, is_real_device
12
+ from .tree import read_tree as read_tree_from
13
+ from .tree import screen_title
14
+
15
+ SCHEMA = "mobster.verify/1"
16
+ EXIT_CODES = {"passed": 0, "failed": 1, "needs_review": 2, "couldnt_run": 3}
17
+ # Smart statuses that end the flow early without an error: an assertion failing after one is class "blocked".
18
+ BLOCKING = frozenset({"blocked", "timeout", "max_steps", "approval_denied", "approval_timeout"})
19
+ STEP_FRAME_CAP, STEP_FRAMES_HEAD, STEP_FRAMES_TAIL = 60, 10, 10
20
+ # couldnt_run classes a crash of the app explains: the run then failed, class app_crashed (the app's failure, not
21
+ # the machine's). A stop, a usage error, a budget or an unknown outcome stays what it is.
22
+ CRASH_EXPLAINS = frozenset({"wda", "launch", "replay"})
23
+ HELD_BEFORE_FLOW = ("Every expectation already held before the flow ran, so this run doesn't show the flow works. "
24
+ "Add an expectation that only holds after it.")
25
+
26
+
27
+ class CouldntRun(Exception):
28
+ def __init__(self, klass, message, fix=""):
29
+ super().__init__(message)
30
+ self.klass, self.message, self.fix = klass, message, fix or ""
31
+
32
+
33
+ def mobster_version():
34
+ from .. import __version__
35
+ return __version__
36
+
37
+
38
+ def _shell_quote(text):
39
+ return text if re.fullmatch(r"[A-Za-z0-9_@%+=:,./-]+", text) else "'" + text.replace("'", "'\\''") + "'"
40
+
41
+
42
+ class Verdict:
43
+ def _decide(self):
44
+ """{verdict, klass, message, fix, results, tree, stable, alert, foreground} by the first rule that applies."""
45
+ if self._ready_at is not None and "flow" not in self.timing and self.mode == "keyless":
46
+ self.timing["flow"] = round(time.monotonic() - self._ready_at, 2)
47
+ self._verdict_ms = self._ms()
48
+ self.perf = self._perf()
49
+ if self.error is not None and self.error["klass"] == "unknown_outcome":
50
+ if not self._left_app:
51
+ self._verdict_frames(None, ())
52
+ return None
53
+ action_failure = self._action_failure()
54
+ if action_failure is not None:
55
+ raise action_failure
56
+ if self.error is not None:
57
+ if not self._left_app:
58
+ self._verdict_frames(None, ())
59
+ crash = self._crash(suspect=True) if self.error["klass"] in CRASH_EXPLAINS else None
60
+ return self._crashed(crash) if crash else None
61
+ failure = self._smart_failure()
62
+ if failure is not None:
63
+ if not self._left_app: # another app is in front on someone's phone: no frame of it
64
+ self._verdict_frames(None, ())
65
+ crash = self._crash(suspect=True) if failure.klass in CRASH_EXPLAINS else None
66
+ if crash:
67
+ return self._crashed(crash)
68
+ raise failure
69
+ from .system_surfaces.play import note as surfaces_note
70
+ started = time.monotonic()
71
+ expect = self.check.expect
72
+ results, tree, stable = self._settle(expect, self.assert_timeout)
73
+ if self._wire is not None:
74
+ results = [*results, *self._wire.judge(self._conditions.net if self._conditions else None)]
75
+ self.timing["assert"] = round(time.monotonic() - started, 2)
76
+ frame = self._verdict_frames(tree, results)
77
+ self._observe_screen(self._relative(frame), tree)
78
+ self._check_pending_screens()
79
+ if self._a11y is not None and self._a11y.screens == 0:
80
+ self._incomplete_screen("No accessibility screen was audited")
81
+ static_findings = self._audit_findings()
82
+ snapshot_found = []
83
+ if self.check.snapshot is not None: # the screen against its baseline, outside the coverage lock
84
+ self.snapshot, snapshot_found = snapshot_run.take(self, tree, self._relative(frame))
85
+ with self._screen_coverage_lock:
86
+ self.findings.extend(self._missing_somewhere())
87
+ if self._a11y is not None:
88
+ self.findings.extend(self._a11y.findings())
89
+ if self._l10n is not None:
90
+ self.findings.extend(self._l10n.findings())
91
+ self.findings.extend(static_findings)
92
+ self.findings.extend(snapshot_found)
93
+ self._screen_verdict_audited = True
94
+ coverage = self._finalize_screen_coverage()
95
+ decided = {"results": results, "tree": tree, "stable": stable, "frame": frame, "alert": self._alert()}
96
+ foreground = self._foreground()
97
+ # A crash anywhere in the run fails it, even when the app came back and the expectations held.
98
+ crash = self._crash(suspect=foreground != self.app.bundle_id)
99
+ if crash:
100
+ return self._crashed(crash, decided)
101
+ failed = [result for result in results if not result.ok]
102
+ status = self._smart_status
103
+ still = "; the screen was still changing" if not stable else ""
104
+ if status == "budget" and failed:
105
+ return {**decided, "verdict": "couldnt_run", "klass": "budget",
106
+ "message": f"Smart reached its ${self.check.max_usd:.2f} spend cap and {len(failed)} of "
107
+ f"{len(results)} expectations failed{still}.",
108
+ "fix": "Raise --max-usd (at most 1.00), then run again."}
109
+ if self._replay_drift:
110
+ return {**decided, "verdict": "failed", "klass": "drift", "message": self._replay_drift,
111
+ "fix": "If the app changed on purpose, run `mobster test --fresh` to record the flow again; "
112
+ "without --no-heal, Smart heals the step and you review the diff."}
113
+ if not foreground:
114
+ return {**decided, "verdict": "couldnt_run", "klass": "wda",
115
+ "message": "WebDriverAgent could not confirm which app was in front at the end.",
116
+ "fix": "Check WebDriverAgent, then run the check again."}
117
+ if foreground and foreground != self.app.bundle_id:
118
+ alert = f"; an alert says \"{decided['alert']}\"" if decided["alert"] else ""
119
+ return {**decided, "verdict": "failed", "klass": "app_not_running",
120
+ "message": f"{self.app.name} wasn't in front at the end: {foreground} was{alert}.",
121
+ "fix": None, "foreground": foreground}
122
+ unavailable = self._observe_business(decided)
123
+ if unavailable is not None:
124
+ return unavailable
125
+ if failed:
126
+ message = f"{len(failed)} of {len(results)} expectations failed{still}"
127
+ if status in BLOCKING:
128
+ note = (self.agent or {}).get("note")
129
+ return {**decided, "verdict": "failed", "klass": "blocked",
130
+ "message": f"Smart ended {status}: " + (note or "no note") + f". {message}", "fix": None}
131
+ return {**decided, "verdict": "failed", "klass": "assertion", "message": message, "fix": None}
132
+ over, unmeasured = perf.judge(self.check.perf_budget, self.perf)
133
+ if over:
134
+ return {**decided, "verdict": "failed", "klass": "budget_exceeded",
135
+ "message": " ".join(item.message for item in over),
136
+ "fix": "Find what the app does while it launches or runs, or raise the budget if the change "
137
+ "is on purpose. The report shows the numbers over the last runs."}
138
+ broken = [item for item in self.findings if item["level"] == "fail"]
139
+ if broken:
140
+ return {**decided, "verdict": "failed", "klass": "finding",
141
+ "message": f"{len(broken)} finding{'' if len(broken) == 1 else 's'}: {broken[0]['message']}",
142
+ "fix": None}
143
+ business_count = len(self.business_binding.spec.expect) if self.business_binding is not None else 0
144
+ business_failed = [item for item in (self.business_result or {}).get("assertions", ()) if not item["ok"]]
145
+ if business_failed:
146
+ return {**decided, "verdict": "failed", "klass": "business",
147
+ "message": f"{len(business_failed)} of {business_count} app-state expectations failed.",
148
+ "fix": None}
149
+ if not coverage["complete"] and (self.check.throughout or self._a11y is not None
150
+ and self._a11y.level == "strict"):
151
+ return {**decided, "verdict": "needs_review", "klass": "screen_coverage",
152
+ "message": f"The run did not observe every required screen: {self._screen_coverage_issue}.",
153
+ "fix": "Check WebDriverAgent and run the check again."}
154
+ unsure = [item for item in self.findings if item["level"] == "review"]
155
+ if unsure:
156
+ return {**decided, "verdict": "needs_review", "klass": "finding",
157
+ "message": f"{len(unsure)} finding{'' if len(unsure) == 1 else 's'} for a person to judge: "
158
+ f"{unsure[0]['message']}", "fix": None}
159
+ if unmeasured:
160
+ return {**decided, "verdict": "needs_review", "klass": "budget_unmeasured",
161
+ "message": " ".join(item.message for item in unmeasured),
162
+ "fix": "Run the check on a simulator, or drop the budget key this run cannot measure."
163
+ if is_real_device(self.manager) else "Run the check again: the simulator gave no reading this "
164
+ "time, which says nothing about the budget."}
165
+ if not (expect or self.check.wire or self.check.throughout or self.check.somewhere or self.check.audit
166
+ or self.check.a11y or self.check.surfaces or business_count
167
+ or self.check.snapshot):
168
+ return {**decided, "verdict": "needs_review", "klass": "no_assertions",
169
+ "message": "The check has no expectations, so nothing decided the run. The frames show what "
170
+ "happened.", "fix": "Add expect: what must be true at the end."}
171
+ held_ax = all(self.baseline.get("held") or [False]) if expect else True
172
+ held_business = (all(self.baseline.get("businessHeld") or [False])
173
+ if business_count else True)
174
+ if (self.check.steps and not self.check.wire and self.baseline.get("taken") and (expect or business_count)
175
+ and held_ax and held_business):
176
+ return {**decided, "verdict": "needs_review", "klass": "held_before_flow", "message": HELD_BEFORE_FLOW,
177
+ "fix": None}
178
+ return {**decided, "verdict": "passed", "klass": None,
179
+ "message": f"All {len(results) + business_count} expectations held{still}{surfaces_note(self)}",
180
+ "fix": None}
181
+
182
+ def _alert(self):
183
+ try:
184
+ text = self.driver.call("GET", "/alert/text", timeout=3)
185
+ return text[:300] if isinstance(text, str) and text.strip() else None
186
+ except Exception:
187
+ return None
188
+
189
+ def _verdict_frames(self, tree, results):
190
+ """NN-verdict (the screen) and NN-verdict-ax (the same with the accessibility overlay)."""
191
+ if self.driver is None or self.target is None:
192
+ return None
193
+ if is_real_device(self.manager):
194
+ foreground = self._foreground()
195
+ if not foreground or (foreground != self.app.bundle_id and not VIEW_SERVICE.fullmatch(foreground)):
196
+ self.log("no verdict frame: the check's app is not confirmed in front")
197
+ return None
198
+ plain = self.frame("verdict")
199
+ if plain is None:
200
+ return None
201
+ self._give_after_frame(plain)
202
+ try:
203
+ if tree is None:
204
+ tree = read_tree_from(self.driver, pause=self._sleep)
205
+ from .overlay import draw_overlay
206
+ asserted = [(result, list(result.paths)) for result in results]
207
+ path = self._frame_path("verdict-ax")
208
+ draw_overlay(plain, tree, asserted, path)
209
+ self._register_frame(path, "verdict-ax")
210
+ except Exception as error:
211
+ self.log(f"no overlay frame: {type(error).__name__}: {error}")
212
+ return plain
213
+
214
+ def _cap_frames(self):
215
+ """At most 60 step frames: the first 10, the last 10, and an even spread of the rest."""
216
+ frames = self.step_frames
217
+ if len(frames) <= STEP_FRAME_CAP:
218
+ return
219
+ head, tail, middle = frames[:STEP_FRAMES_HEAD], frames[-STEP_FRAMES_TAIL:], \
220
+ frames[STEP_FRAMES_HEAD:-STEP_FRAMES_TAIL]
221
+ room = STEP_FRAME_CAP - len(head) - len(tail)
222
+ stride = 2
223
+ while -(-len(middle) // stride) > room:
224
+ stride += 1
225
+ keep = set(head) | set(tail) | set(middle[::stride])
226
+ dropped = set(frames) - keep
227
+ for relative in dropped:
228
+ try:
229
+ (self.run_dir / relative).unlink()
230
+ except OSError:
231
+ pass
232
+ self.frames = [frame for frame in self.frames if frame not in dropped]
233
+ self.step_frames = [frame for frame in frames if frame in keep]
234
+ for step in self.steps:
235
+ if step.get("frame") in dropped:
236
+ step["frame"] = None
237
+
238
+ def _compose(self, decided):
239
+ coverage = self._finalize_screen_coverage()
240
+ check, app, target = self.check, self.app, self.target
241
+ self._cap_frames()
242
+ if decided is None:
243
+ error = self.error or {"klass": "internal", "message": "The run ended without a verdict.", "fix": ""}
244
+ verdict, klass, message, fix = "couldnt_run", error["klass"], error["message"], error.get("fix") or None
245
+ results, stable, alert, frame = [], None, None, None
246
+ summary = message
247
+ else:
248
+ verdict, klass, message, fix = decided["verdict"], decided["klass"], decided["message"], decided["fix"]
249
+ results, stable, alert, frame = decided["results"], decided["stable"], decided["alert"], decided["frame"]
250
+ failed = [result for result in results if not result.ok]
251
+ if verdict == "passed":
252
+ total = len(results) + len(check.business.expect if check.business is not None else ())
253
+ summary = f"{total} of {total} expectations held"
254
+ elif klass == "finding":
255
+ summary = message
256
+ elif klass in ("assertion", "blocked", "budget"):
257
+ summary = f"{failed[0].text} failed: {failed[0].observed}"
258
+ else:
259
+ summary = message
260
+ frame_rel = Path(frame).relative_to(self.run_dir).as_posix() if frame else None
261
+ assertions = [{"index": index, "assertion": assertion.to_dict(), "text": result.text, "ok": result.ok,
262
+ "observed": result.observed, "matches": [dict(match) for match in result.matches],
263
+ "frame": frame_rel}
264
+ for index, (assertion, result) in enumerate(zip((*check.expect, *check.wire), results))]
265
+ draft = self.run_dir / "check.yaml"
266
+ ax = next((f for f in reversed(self.frames) if f.endswith("-verdict-ax.jpg")), None)
267
+ plain = frame_rel or next((f for f in reversed(self.frames) if f.endswith("-verdict.jpg")), None)
268
+ last_step = self.step_frames[-1] if self.step_frames else None
269
+ proof = [str(self.run_dir / relative) for relative in (ax, plain, last_step) if relative]
270
+ result = {
271
+ "schema": SCHEMA,
272
+ "run_id": self.run_id,
273
+ "verdict": verdict,
274
+ "exit_code": EXIT_CODES[verdict],
275
+ "summary": self._scrub(summary),
276
+ "reason": {"class": klass, "message": self._scrub(message), "fix": fix},
277
+ "mode": self.mode,
278
+ "check": {"name": check.name, "steps": list(check.steps),
279
+ "expect": [assertion.to_dict() for assertion in (*check.expect, *check.wire)]
280
+ + ([check.snapshot.to_dict()] if check.snapshot else []),
281
+ **({"throughout": [assertion.to_dict() for assertion in check.throughout]}
282
+ if check.throughout else {}),
283
+ **({"somewhere": [assertion.to_dict() for assertion in check.somewhere]}
284
+ if check.somewhere else {}),
285
+ **({"audit": list(check.audit)} if check.audit else {}),
286
+ **({"a11y": check.a11y} if check.a11y else {}), "source": check.source},
287
+ "app": ({"bundle_id": app.bundle_id, "name": app.name, "version": app.version,
288
+ "path": str(Path(app.path).resolve()) if app.path else check.app_path}
289
+ if app is not None else {"bundle_id": check.bundle_id, "name": None, "version": None,
290
+ "path": check.app_path}),
291
+ "device": ({"name": target.name, "udid": target.udid, "type": target.device_type,
292
+ "runtime": target.runtime} if target is not None
293
+ else {"name": None, "udid": None, "type": check.device, "runtime": check.runtime}),
294
+ "assertions": assertions,
295
+ **({"screen": {"title": screen_title(decided["tree"])}} if decided and decided.get("tree") else {}),
296
+ "findings": [dict(item) for item in self.findings],
297
+ **({"snapshot": dict(self.snapshot)} if self.snapshot else {}),
298
+ "screenCoverage": coverage,
299
+ **({"conditions": {**self._conditions.report(), "cleanup": self.conditions_cleanup}}
300
+ if self._conditions is not None else {}),
301
+ "perf": self.perf,
302
+ "baseline": dict(self.baseline),
303
+ "stable": stable,
304
+ "steps": [dict(step) for step in self.steps],
305
+ "agent": dict(self.agent) if self.mode == "smart" and self.agent is not None else None,
306
+ "frames": list(self.frames),
307
+ "proof": proof,
308
+ "alert": alert,
309
+ "report": str(self.run_dir / "report.html"),
310
+ "draft_check": str(draft),
311
+ "run_dir": str(self.run_dir),
312
+ "repro": f"mobster verify --check {_shell_quote(str(draft))}",
313
+ "seconds": round(time.monotonic() - self._t0, 1),
314
+ "timing": {key: self.timing[key] for key in ("simulator", "install", "launch", "flow", "assert", "report")
315
+ if key in self.timing},
316
+ "cost_usd": self.cost_usd if self.mode == "smart" else 0,
317
+ "mobster": {"version": mobster_version()},
318
+ }
319
+ evidence = self._evidence_summary(verdict)
320
+ if check.business is not None:
321
+ result["check"]["business"] = check.business.to_dict()
322
+ result["business"] = dict(self.business_result or {})
323
+ if evidence is not None:
324
+ result["evidence"] = evidence
325
+ step_proof = self._step_proof(verdict, klass, stable, decided, plain, ax)
326
+ if step_proof is not None:
327
+ result["step_proof"] = step_proof
328
+ return project_env.mask_deep(result, self.secrets)
329
+
330
+ def _step_proof(self, verdict, klass, stable, decided, plain, ax):
331
+ """Each step's log lines and network rows, the frame and tree the verdict used, and the cause line."""
332
+ end = self._failed_ms if verdict == "couldnt_run" and self._failed_ms is not None else self._verdict_ms
333
+ end = self._ms() if end is None else end
334
+ try:
335
+ lines = self.evidence.lines() if self.evidence is not None else []
336
+ return proof.build(verdict=verdict, klass=klass, stable=stable, steps=self.steps, lines=lines, end_ms=end,
337
+ frame=plain, ax_frame=ax, tree=decided.get("tree") if decided else None,
338
+ verdict_ms=self._verdict_ms)
339
+ except Exception as error:
340
+ self.log(f"step proof: none ({type(error).__name__}: {error})")
341
+ return None
342
+
343
+ def _evidence_summary(self, verdict):
344
+ if self.evidence is None:
345
+ return None
346
+ center = self._failed_ms if verdict == "couldnt_run" and self._failed_ms is not None else self._verdict_ms
347
+ try:
348
+ return self.evidence.summary(center_ms=center if center is not None else self._ms(), steps=self.steps,
349
+ failed=verdict != "passed")
350
+ except Exception as error:
351
+ self.log(f"evidence: no summary ({type(error).__name__}: {error})")
352
+ return None
353
+
354
+ def _write(self, result):
355
+ """check.yaml, result.json and report.html."""
356
+ started = time.monotonic()
357
+ root = project_root(self.run_dir) if self.runs_dir.parent.name == ".mobster" else None
358
+ try:
359
+ (self.run_dir / "check.yaml").write_text(dump_check(self.check, project_root=root), encoding="utf-8")
360
+ except Exception as error:
361
+ self.log(f"check.yaml not written: {type(error).__name__}: {error}")
362
+ from .report import write_report
363
+ try:
364
+ write_report(self.run_dir, result, scrub=self._scrub)
365
+ except Exception as error:
366
+ self.log("".join(traceback.format_exception(type(error), error, error.__traceback__)))
367
+ result["timing"]["report"] = round(time.monotonic() - started, 2)
368
+ result["seconds"] = round(time.monotonic() - self._t0, 1)
369
+ self._write_json(result)
370
+ if self.secrets:
371
+ project_env.sweep_files(self.run_dir, self.secrets)
372
+
373
+ def _write_json(self, result):
374
+ text = self._scrub(json.dumps(result, indent=1, ensure_ascii=False, allow_nan=False))
375
+ temporary = self.run_dir / "result.json.tmp"
376
+ temporary.write_text(text + "\n", encoding="utf-8")
377
+ os.replace(temporary, self.run_dir / "result.json")