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,325 @@
1
+ """A phone's live picture for the Mac app, whatever the connection."""
2
+
3
+ import struct
4
+ import subprocess
5
+ import threading
6
+ import time
7
+
8
+ from .video import (
9
+ DEFAULT_QUALITY,
10
+ HEARTBEAT_SECONDS,
11
+ IDLE_GRACE_SECONDS,
12
+ MAX_FRAME_BYTES,
13
+ MAX_VIEWERS,
14
+ PEEK_WAIT_SECONDS,
15
+ VIDEO_DETAIL,
16
+ VideoUnavailable,
17
+ WdaVideo,
18
+ mjpeg_settings,
19
+ )
20
+
21
+ # A tile in the grid of phones: small and slow; the focused phone gets the chosen width and rate.
22
+ TILE_WIDTH = 196
23
+ TILE_FPS = 4
24
+ TILE_QUALITY = .5
25
+ # The full picture's width per detail level, as a share of FULL_WIDTH (the connection's own frame width).
26
+ FULL_WIDTH = 590
27
+ DETAIL_SCALE = {"low": .5, "medium": .75, "high": 1.0}
28
+ DETAIL_QUALITY = {detail: VIDEO_DETAIL[detail]["mjpegServerScreenshotQuality"] / 100 for detail in VIDEO_DETAIL}
29
+ # After a failed pull, wait this long before the next.
30
+ RETRY_SECONDS = 1.0
31
+ # A helper process that exits is started again after this long while someone watches.
32
+ RESTART_SECONDS = 1.0
33
+ # The frame width a helper process is asked for at each detail level (its JPEG quality is DETAIL_QUALITY).
34
+ DETAIL_WIDTH = {"low": 360, "medium": 480, "high": 720}
35
+
36
+
37
+ def streams(video):
38
+ """Whether ``video`` serves the live stream and tiles (WdaVideo, or a FrameRelay)."""
39
+ return isinstance(video, (WdaVideo, FrameRelay))
40
+
41
+
42
+ class FrameRelay:
43
+ """WdaVideo's frame source interface over ``pull(width, quality, after)``: (jpeg, key), or None when unchanged."""
44
+
45
+ kind = "relay"
46
+
47
+ def __init__(self, pull=None, *, source="relay", quality=DEFAULT_QUALITY, clock=time.monotonic,
48
+ sleep=time.sleep):
49
+ if pull is not None:
50
+ self.pull = pull
51
+ self.source_name = source
52
+ self.clock, self._sleep_for = clock, sleep
53
+ self.condition = threading.Condition()
54
+ self.viewers = set()
55
+ self.focused = set()
56
+ self.frame = None
57
+ self.sequence = 0
58
+ self.key = None
59
+ self.error = None
60
+ self.worker = None
61
+ self.closed = False
62
+ self.idle_since = None
63
+ self.frame_times = []
64
+ self.pulls = 0
65
+ self.set_quality(*quality)
66
+
67
+ def pull(self, width, quality, after):
68
+ raise NotImplementedError
69
+
70
+ def set_quality(self, fps, detail):
71
+ mjpeg_settings(fps, detail) # validates both, as WDA's do
72
+ with self.condition:
73
+ self.target_fps = fps
74
+ self.width = int(FULL_WIDTH * DETAIL_SCALE[detail])
75
+ self.quality = DETAIL_QUALITY[detail]
76
+
77
+ def reduced_fps(self):
78
+ return None
79
+
80
+ def mode(self):
81
+ """(width, quality, fps) to pull at now: the chosen ones while a stream is focused, a tile's otherwise."""
82
+ with self.condition:
83
+ if self.focused:
84
+ return self.width, self.quality, self.target_fps
85
+ return TILE_WIDTH, TILE_QUALITY, TILE_FPS
86
+
87
+ def status(self):
88
+ with self.condition:
89
+ live = self.frame is not None and self.clock() - self.frame[4] < 3
90
+ return {"status": "live" if live else "starting" if self.viewers else "idle", "kind": self.kind,
91
+ "sourceKind": self.source_name, "error": self.error, "viewers": len(self.viewers),
92
+ "fps": round(self.fps(), 1), "targetFps": self.target_fps, "reducedFps": None, "linkLoad": None,
93
+ "capturedAt": self.frame[3] if self.frame else None, "stream": "/api/device/stream"}
94
+
95
+ def fps(self):
96
+ now = self.clock()
97
+ return len([t for t in self.frame_times if now - t <= 2]) / 2
98
+
99
+ def subscribe(self, focus=True):
100
+ """A viewer. ``focus`` (a live stream) asks for the full picture; a peek is a tile."""
101
+ with self.condition:
102
+ if self.closed:
103
+ raise VideoUnavailable("Live video is shutting down")
104
+ if len(self.viewers) >= MAX_VIEWERS:
105
+ raise VideoUnavailable("Too many live viewers")
106
+ viewer = object()
107
+ self.viewers.add(viewer)
108
+ if focus:
109
+ self.focused.add(viewer)
110
+ self.idle_since = None
111
+ if self.worker is None or not self.worker.is_alive():
112
+ self.worker = threading.Thread(target=self._run, name=f"mobster-{self.kind}-video", daemon=True)
113
+ self.worker.start()
114
+ self.condition.notify_all()
115
+ return viewer
116
+
117
+ def unsubscribe(self, viewer):
118
+ with self.condition:
119
+ self.viewers.discard(viewer)
120
+ self.focused.discard(viewer)
121
+ if not self.viewers:
122
+ self.idle_since = self.clock()
123
+ self.condition.notify_all()
124
+
125
+ next_frame = WdaVideo.next_frame
126
+
127
+ def latest(self):
128
+ with self.condition:
129
+ return self.frame
130
+
131
+ def peek(self, wait=PEEK_WAIT_SECONDS):
132
+ """A current still picture (a tile): starts a stopped source at tile size and waits up to ``wait``."""
133
+ try:
134
+ viewer = self.subscribe(focus=False)
135
+ except VideoUnavailable:
136
+ return self.latest()
137
+ try:
138
+ deadline = self.clock() + wait
139
+ with self.condition:
140
+ while not self.closed and (self.frame is None or self.clock() - self.frame[4] > IDLE_GRACE_SECONDS):
141
+ remaining = deadline - self.clock()
142
+ if remaining <= 0:
143
+ break
144
+ self.condition.wait(remaining)
145
+ return self.frame
146
+ finally:
147
+ self.unsubscribe(viewer)
148
+
149
+ def paced_frames(self, viewer, rate=lambda: None, heartbeat=HEARTBEAT_SECONDS, timeout=10.0):
150
+ """WdaVideo.paced_frames: new frames at most ``rate()`` a second, the same one again every ``heartbeat``."""
151
+ sequence, sent_at, looked_at = 0, None, None
152
+ while True:
153
+ fps = rate()
154
+ if isinstance(fps, (int, float)) and fps > 0 and looked_at is not None:
155
+ wait = looked_at + 1 / fps - self.clock()
156
+ if wait > 0:
157
+ self._sleep_for(wait)
158
+ with self.condition:
159
+ if self.closed or viewer not in self.viewers:
160
+ return
161
+ frame = self.next_frame(viewer, sequence, timeout=min(timeout, heartbeat))
162
+ looked_at = self.clock()
163
+ if frame is None:
164
+ with self.condition:
165
+ if self.closed or viewer not in self.viewers:
166
+ return
167
+ frame = self.frame
168
+ if frame is None or (sent_at is not None and looked_at - sent_at < heartbeat):
169
+ continue
170
+ sequence, sent_at = frame[0], looked_at
171
+ yield frame
172
+
173
+ def close(self):
174
+ with self.condition:
175
+ self.closed = True
176
+ self.condition.notify_all()
177
+
178
+ def _should_run(self):
179
+ with self.condition:
180
+ if self.closed:
181
+ return False
182
+ if self.viewers:
183
+ return True
184
+ return self.idle_since is not None and self.clock() - self.idle_since < IDLE_GRACE_SECONDS
185
+
186
+ def publish(self, data, key, content_type="image/jpeg"):
187
+ now = self.clock()
188
+ with self.condition:
189
+ self.sequence += 1
190
+ self.key = key
191
+ self.frame = (self.sequence, content_type, data, int(time.time() * 1000), now, self.source_name)
192
+ self.error = None
193
+ self.frame_times = [t for t in self.frame_times if now - t <= 2] + [now]
194
+ self.condition.notify_all()
195
+
196
+ def _run(self):
197
+ last_mode = None
198
+ while self._should_run():
199
+ width, quality, fps = self.mode()
200
+ started = self.clock()
201
+ # A change of size needs a new picture even when the screen is still.
202
+ after = self.key if (width, quality) == last_mode else None
203
+ try:
204
+ pulled = self.pull(width, quality, after)
205
+ self.pulls += 1
206
+ except Exception as error: # noqa: BLE001 -- reported in status; the viewer sees no new frame
207
+ with self.condition:
208
+ self.error = str(error) or type(error).__name__
209
+ self._sleep_for(RETRY_SECONDS)
210
+ continue
211
+ last_mode = (width, quality)
212
+ if pulled is not None:
213
+ self.publish(*pulled)
214
+ else:
215
+ with self.condition: # the same picture: it is still current
216
+ if self.frame is not None:
217
+ self.frame = self.frame[:4] + (self.clock(),) + self.frame[5:]
218
+ wait = 1 / fps - (self.clock() - started)
219
+ if wait > 0:
220
+ self._sleep_for(wait)
221
+
222
+
223
+ def read_exact(stream, size):
224
+ """``size`` bytes from ``stream``, or None at its end."""
225
+ chunks, left = [], size
226
+ while left:
227
+ chunk = stream.read(left)
228
+ if not chunk:
229
+ return None
230
+ chunks.append(chunk)
231
+ left -= len(chunk)
232
+ return b"".join(chunks)
233
+
234
+
235
+ def read_frames(stream):
236
+ """The JPEGs a helper writes: 4-byte big-endian length, then the bytes. Ends at the stream's end or a bad length."""
237
+ while True:
238
+ header = read_exact(stream, 4)
239
+ if header is None:
240
+ return
241
+ (length,) = struct.unpack(">I", header)
242
+ if not 0 < length <= MAX_FRAME_BYTES:
243
+ return
244
+ data = read_exact(stream, length)
245
+ if data is None:
246
+ return
247
+ yield data
248
+
249
+
250
+ class ProcessFrameSource(WdaVideo):
251
+ """A frame source fed by a helper process. ``command_for(fps, detail)``: the helper's argv for a quality."""
252
+
253
+ kind = "process"
254
+
255
+ def __init__(self, command_for, *, source="process", on_activity=None, clock=time.monotonic,
256
+ quality=DEFAULT_QUALITY, spawn=subprocess.Popen):
257
+ super().__init__("http://127.0.0.1:0", mjpeg_url=None, session=None, on_activity=on_activity, clock=clock,
258
+ quality=quality, screenshot_fallback=False)
259
+ self.command_for = command_for
260
+ self.source_name = source
261
+ self.quality = tuple(quality)
262
+ self.spawn = spawn
263
+ self.process = None
264
+
265
+ def status(self):
266
+ value = super().status()
267
+ value.update(kind=self.kind, stream="/api/device/stream")
268
+ return value
269
+
270
+ def set_quality(self, fps, detail):
271
+ settings = mjpeg_settings(fps, detail) # validates
272
+ with self.condition:
273
+ self.settings, self.quality = settings, (fps, detail)
274
+ self._stop_process() # the loop starts it again at the new quality
275
+
276
+ def _configure(self):
277
+ return True
278
+
279
+ def _stop_process(self):
280
+ process, self.process = self.process, None
281
+ if process is None:
282
+ return
283
+ try:
284
+ process.terminate()
285
+ process.wait(timeout=2)
286
+ except Exception: # noqa: BLE001 -- it is going away either way
287
+ try:
288
+ process.kill()
289
+ except Exception: # noqa: BLE001
290
+ pass
291
+
292
+ def _run(self):
293
+ try:
294
+ while self._should_run():
295
+ try:
296
+ fps, detail = self.quality
297
+ self.process = self.spawn(list(self.command_for(fps, detail)), stdin=subprocess.PIPE,
298
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
299
+ process = self.process
300
+ for data in read_frames(process.stdout):
301
+ self._publish("image/jpeg", data, self.source_name)
302
+ if not self._should_run():
303
+ break
304
+ code = process.poll()
305
+ with self.condition:
306
+ if code not in (None, 0, -15):
307
+ self.error = f"exited {code}"
308
+ except Exception as error: # noqa: BLE001 -- reported in status, retried while watched
309
+ with self.condition:
310
+ self.error = type(error).__name__
311
+ finally:
312
+ self._stop_process()
313
+ deadline = self.clock() + RESTART_SECONDS
314
+ while self._should_run() and self.clock() < deadline:
315
+ time.sleep(.1)
316
+ finally:
317
+ with self.condition:
318
+ self.worker = None
319
+ self.frame = None
320
+ self.frame_times = []
321
+ self.on_activity(False)
322
+
323
+ def close(self):
324
+ super().close()
325
+ threading.Thread(target=self._stop_process, name="mobster-frames-stop", daemon=True).start()
@@ -0,0 +1,115 @@
1
+ """Screens a run points at, kept beside the journal."""
2
+
3
+ import io
4
+ import os
5
+ import re
6
+ import shutil
7
+ import threading
8
+ import uuid
9
+ from collections import OrderedDict
10
+ from concurrent.futures import ThreadPoolExecutor
11
+ from pathlib import Path
12
+
13
+ from ..paths import write_private
14
+
15
+ FRAME_ID = re.compile(r"f[0-9a-f]{10}")
16
+ RUN_ID = re.compile(r"[a-f0-9]{12}")
17
+
18
+
19
+ class FrameStore:
20
+ """Screens a run points at (steps, receipts, proof), as JPEGs no more than MAX_SIDE px on their long
21
+ side. A frame is kept in memory at once and downscaled, then written beside the journal, on one worker
22
+ thread: the agent never waits for an image. Without durable history frames live in memory only."""
23
+
24
+ MAX_SIDE = 480
25
+ MAX_PER_RUN = 400
26
+ MAX_MEMORY_RUNS = 4
27
+
28
+ def __init__(self, root=None):
29
+ self.root = Path(root) if root else None
30
+ self.memory = OrderedDict() # run id -> {frame id: bytes (raw until processed)}
31
+ self.lock = threading.Lock()
32
+ self.worker = ThreadPoolExecutor(max_workers=1, thread_name_prefix="mobster-frames")
33
+
34
+ def put(self, run_id, data):
35
+ """Keep ``data`` (JPEG bytes) for ``run_id``; returns its frame id, or None when the run is full."""
36
+ frame_id = "f" + uuid.uuid4().hex[:10]
37
+ with self.lock:
38
+ frames = self.memory.setdefault(run_id, {})
39
+ self.memory.move_to_end(run_id)
40
+ if len(frames) >= self.MAX_PER_RUN:
41
+ return None
42
+ frames[frame_id] = bytes(data)
43
+ # Older runs' frames are on disk (or, without durable history, gone with the oldest runs).
44
+ while len(self.memory) > (self.MAX_MEMORY_RUNS if self.root is not None else 20):
45
+ self.memory.popitem(last=False)
46
+ self.worker.submit(self._process, run_id, frame_id)
47
+ return frame_id
48
+
49
+ @classmethod
50
+ def downscale(cls, data):
51
+ try:
52
+ from PIL import Image
53
+ image = Image.open(io.BytesIO(data))
54
+ if max(image.size) <= cls.MAX_SIDE and image.format == "JPEG":
55
+ return data
56
+ image = image.convert("RGB")
57
+ image.thumbnail((cls.MAX_SIDE, cls.MAX_SIDE))
58
+ out = io.BytesIO()
59
+ image.save(out, "JPEG", quality=72)
60
+ return out.getvalue()
61
+ except Exception:
62
+ return None
63
+
64
+ def _process(self, run_id, frame_id):
65
+ with self.lock:
66
+ raw = self.memory.get(run_id, {}).get(frame_id)
67
+ small = self.downscale(raw) if raw is not None else None
68
+ if small is None:
69
+ with self.lock:
70
+ self.memory.get(run_id, {}).pop(frame_id, None)
71
+ return
72
+ with self.lock:
73
+ if frame_id in self.memory.get(run_id, {}):
74
+ self.memory[run_id][frame_id] = small
75
+ if self.root is not None:
76
+ try:
77
+ # frames/ itself is 0700 too: mkdir's mode covers only the folder it names, not the parents it makes.
78
+ self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
79
+ os.chmod(self.root, 0o700)
80
+ folder = self.root / run_id
81
+ folder.mkdir(exist_ok=True, mode=0o700)
82
+ write_private(folder / f"{frame_id}.jpg", small, binary=True)
83
+ except OSError:
84
+ pass
85
+
86
+ def get(self, run_id, frame_id):
87
+ if not FRAME_ID.fullmatch(frame_id or ""):
88
+ return None
89
+ with self.lock:
90
+ data = self.memory.get(run_id, {}).get(frame_id)
91
+ if data is not None:
92
+ small = self.downscale(data)
93
+ return small
94
+ if self.root is not None:
95
+ try:
96
+ return (self.root / run_id / f"{frame_id}.jpg").read_bytes()
97
+ except OSError:
98
+ return None
99
+ return None
100
+
101
+ def folder(self, run_id):
102
+ """Where a run's files are kept (its frames, its tape), or None without durable history or for an id that
103
+ is not a run's."""
104
+ return self.root / run_id if self.root is not None and RUN_ID.fullmatch(run_id or "") else None
105
+
106
+ def delete(self, run_id):
107
+ with self.lock:
108
+ self.memory.pop(run_id, None)
109
+ folder = self.folder(run_id)
110
+ if folder is not None:
111
+ # An in-flight downscale may still write its frame. Remove the directory after that worker finishes.
112
+ self.worker.submit(shutil.rmtree, folder, ignore_errors=True).result()
113
+
114
+ def close(self):
115
+ self.worker.shutdown(wait=True, cancel_futures=False)
@@ -0,0 +1,163 @@
1
+ """WebDriverAgent patches: one gate for screen and accessibility captures, on for simulators and off for iPhones.
2
+
3
+ WDA's MJPEG broadcaster asks XCTest for a screenshot on its own queue while a request asks for the
4
+ app's accessibility snapshot. On iOSWorld simulators an app stopped answering snapshots after a
5
+ scroll with the stream running, and stayed stuck until it was closed (scorezone-003, 10 Oct 2026).
6
+ The gate keeps the two apart, unless the runner starts with ``MOBSTER_CAPTURE_GATE=off`` in its
7
+ environment (``TEST_RUNNER_`` prefix through xcodebuild):
8
+
9
+ - every accessibility snapshot (``-[XCAXClient_iOS requestSnapshotForElement:...]``, which /source,
10
+ element queries and attribute reads all go through) holds it, waiting at most
11
+ ``FBMobsterSnapshotGateWait`` seconds for it, then running without it, as upstream does;
12
+ - every screenshot (``+[FBScreenshot takeInOriginalResolutionWithScreenID:...]``) holds it;
13
+ - the MJPEG broadcaster waits for it at most one frame interval, then skips that frame.
14
+
15
+ With ``off`` nothing is locked, and the runner behaves as upstream's.
16
+
17
+ Measured cost, 10 Oct 2026, "Frontier Bench 1" simulator, 4 other simulators busy, ABBA, 8 rounds of
18
+ 60 back-to-back source reads each with the stream at 30 fps:
19
+ - read p50 went from 66.5 to 93 ms (median of rounds), and p90 from 82.5 to 127.5 ms;
20
+ - the stream went from 26.6 to 18.7 frames a second while reads ran.
21
+ A broadcaster that skipped a busy frame at once, instead of waiting a frame interval, sent 1.9 frames
22
+ a second instead of 9.6 (stream at 10 fps).
23
+
24
+ Measured effect on the hang, 11 Oct 2026: the tuning loop's 4-simulator scripted harness (iOSWorld
25
+ scorezone-003, a swipe then done, no model), off and on interleaved ABBA, 4 runs per simulator a round.
26
+ Off, 10 of 64 runs hung (15.6%, 95% CI 8.7-26.4%). On, 2 of 64 did (3.1%, 0.9-10.7%; Fisher p = 0.03).
27
+ On those simulators, reads cost 61.5 -> 80.5 ms at p50 and the stream ran at 22.6 instead of 27.4 fps.
28
+ Upstream's own guard (the ``accessibilityDeadline`` session setting, 3 s) hung none of 32 runs the same way,
29
+ but 4 failed at 79 s instead: the stuck app's main run loop did not answer, so its reads failed sooner.
30
+
31
+ Measured on an iPhone, 11 Oct 2026: an iPhone 15 Pro on iOS 26.0.1 over USB, off and on interleaved ABBA.
32
+ - 8 rounds of 2 x 60 back-to-back reads of Settings > General, the stream at 30 fps: read p50 went from
33
+ 160 to 241.5 ms, p90 from 193.5 to 290 ms; the stream went from 19.2 to 14.7 frames a second.
34
+ - A scripted swipe then read on YouTube Shorts, 40 runs a side: no read stalled either way (0 of 40,
35
+ 95% CI 0-8.8%). With the gate off, 2 runs failed at once with a transport error (cause not recorded).
36
+ Read p50 went from 86 to 168 ms, and the swipe's from 1025 to 1509 ms.
37
+ - 10 short tasks on Smart, 20 runs off and 12 on: 19 of 20 passed off, 10 of 10 graded on; the median
38
+ per-task difference was +0.29 s with the gate on.
39
+ The phone paid the cost and showed no hang to prevent, so ``runner_env`` starts iPhone runners with
40
+ the gate off and simulator runners with it on. ``MOBSTER_CAPTURE_GATE=on`` or ``off`` in Mobster's own
41
+ environment overrides that for both.
42
+
43
+ Applied with ``patches.WDA_PATCHES`` (anchored, idempotent edits on the pinned release).
44
+ """
45
+
46
+ import os
47
+
48
+ # What the runner reads (its environment, through xcodebuild's TEST_RUNNER_ prefix), and the override.
49
+ RUNNER_VARIABLE = "TEST_RUNNER_MOBSTER_CAPTURE_GATE"
50
+ OVERRIDE = "MOBSTER_CAPTURE_GATE"
51
+ # Per device kind: on for simulators (10 of 64 scripted runs hung without it), off for iPhones (no hang in
52
+ # 40 runs either way, 81.5 ms more per read with it). Measurements above.
53
+ DEFAULTS = {"simulator": "on", "phone": "off"}
54
+ WORDS = {"on": "on", "1": "on", "true": "on", "yes": "on", "off": "off", "0": "off", "false": "off", "no": "off"}
55
+
56
+
57
+ def gate_for(kind, environ=None):
58
+ if kind not in DEFAULTS:
59
+ raise ValueError(f"A runner's device kind is one of {', '.join(sorted(DEFAULTS))}, not {kind!r}")
60
+ environ = os.environ if environ is None else environ
61
+ for name in (OVERRIDE, RUNNER_VARIABLE):
62
+ said = WORDS.get(str(environ.get(name) or "").strip().casefold())
63
+ if said:
64
+ return said
65
+ return DEFAULTS[kind]
66
+
67
+
68
+ def runner_env(kind, environ=None):
69
+ """The runner's gate as xcodebuild passes it on; merge it last into every runner's environment."""
70
+ return {RUNNER_VARIABLE: gate_for(kind, environ)}
71
+
72
+ SNAPSHOT_FILE = "WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m"
73
+ SCREENSHOT_FILE = "WebDriverAgentLib/Utilities/FBScreenshot.m"
74
+ MJPEG_FILE = "WebDriverAgentLib/Utilities/FBMjpegServer.m"
75
+
76
+ GATE_DECLARATION = (
77
+ "// Mobster: one gate for screen and accessibility captures (XCAXClient_iOS+FBSnapshotReqParams.m).\n"
78
+ "NSRecursiveLock *FBMobsterCaptureGate(void);\n")
79
+
80
+ GATE_DEFINITION = """// Mobster: one gate for screen and accessibility captures, unless the runner's environment has
81
+ // MOBSTER_CAPTURE_GATE=off (then nil: nothing is locked). A snapshot holds it, waiting at most
82
+ // FBMobsterSnapshotGateWait seconds, then running without it; a screenshot holds it; the MJPEG
83
+ // broadcaster waits at most one frame interval for it, then skips that frame.
84
+ static const NSTimeInterval FBMobsterSnapshotGateWait = 2.0;
85
+
86
+ NSRecursiveLock *FBMobsterCaptureGate(void)
87
+ {
88
+ static NSRecursiveLock *gate;
89
+ static dispatch_once_t once;
90
+ dispatch_once(&once, ^{
91
+ if (![NSProcessInfo.processInfo.environment[@"MOBSTER_CAPTURE_GATE"] isEqualToString:@"off"]) {
92
+ gate = [NSRecursiveLock new];
93
+ gate.name = @"mobster.capture-gate";
94
+ }
95
+ [FBLogger logFmt:@"Mobster capture gate: %@", nil == gate ? @"off" : @"on"];
96
+ });
97
+ return gate;
98
+ }
99
+
100
+ static id swizzledRequestSnapshotForElement(id self, SEL _cmd, id element, id attributes,
101
+ id parameters, NSError **error)
102
+ {
103
+ NSRecursiveLock *gate = FBMobsterCaptureGate();
104
+ BOOL held = nil != gate
105
+ && [gate lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:FBMobsterSnapshotGateWait]];
106
+ if (nil != gate && !held) {
107
+ [FBLogger log:@"Mobster: a snapshot ran without the capture gate after waiting for a screenshot"];
108
+ }
109
+ @try {
110
+ return FBMobsterUngatedSnapshot(self, _cmd, element, attributes, parameters, error);
111
+ } @finally {
112
+ if (held) {
113
+ [gate unlock];
114
+ }
115
+ }
116
+ }
117
+
118
+ """
119
+
120
+ PATCHES = (
121
+ # Snapshots: upstream's swizzle is renamed and wrapped by a gated one with its old name.
122
+ (SNAPSHOT_FILE,
123
+ "static id swizzledRequestSnapshotForElement(id self, SEL _cmd, id element, id attributes, id parameters, "
124
+ "NSError **error)\n{\n",
125
+ "static id FBMobsterUngatedSnapshot(id self, SEL _cmd, id element, id attributes, id parameters, "
126
+ "NSError **error)\n{\n"),
127
+ (SNAPSHOT_FILE,
128
+ "@implementation XCAXClient_iOS (FBSnapshotReqParams)\n",
129
+ GATE_DEFINITION + "@implementation XCAXClient_iOS (FBSnapshotReqParams)\n"),
130
+ # Screenshots hold the gate from the request until its reply or timeout.
131
+ (SCREENSHOT_FILE,
132
+ "#import \"XCUIScreen.h\"\n\nstatic const NSTimeInterval SCREENSHOT_TIMEOUT = 20.;\n",
133
+ "#import \"XCUIScreen.h\"\n\n" + GATE_DECLARATION + "\nstatic const NSTimeInterval SCREENSHOT_TIMEOUT = 20.;\n"),
134
+ (SCREENSHOT_FILE,
135
+ " [proxy _XCT_requestScreenshot:screnshotRequest\n",
136
+ " NSRecursiveLock *gate = FBMobsterCaptureGate();\n"
137
+ " [gate lock];\n"
138
+ " [proxy _XCT_requestScreenshot:screnshotRequest\n"),
139
+ (SCREENSHOT_FILE,
140
+ " if (nil != error && nil != innerError) {\n *error = innerError;\n }\n return screenshotData;\n",
141
+ " [gate unlock];\n"
142
+ " if (nil != error && nil != innerError) {\n *error = innerError;\n }\n return screenshotData;\n"),
143
+ # The broadcaster waits one frame interval for a snapshot in flight, then skips the frame.
144
+ (MJPEG_FILE,
145
+ "static const char *QUEUE_NAME = \"JPEG Screenshots Provider Queue\";\n",
146
+ "static const char *QUEUE_NAME = \"JPEG Screenshots Provider Queue\";\n" + GATE_DECLARATION),
147
+ (MJPEG_FILE,
148
+ " NSData *screenshotData = [FBScreenshot takeInOriginalResolutionWithScreenID:self.mainScreenID\n",
149
+ " // Mobster: a frame waits for a snapshot in flight at most one frame interval, then is skipped.\n"
150
+ " NSRecursiveLock *captureGate = FBMobsterCaptureGate();\n"
151
+ " if (nil != captureGate && ![captureGate lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:\n"
152
+ " (NSTimeInterval)timerInterval / NSEC_PER_SEC]]) {\n"
153
+ " [self scheduleNextScreenshotWithInterval:timerInterval timeStarted:timeStarted];\n"
154
+ " return;\n"
155
+ " }\n"
156
+ " NSData *screenshotData = [FBScreenshot takeInOriginalResolutionWithScreenID:self.mainScreenID\n"),
157
+ (MJPEG_FILE,
158
+ " timeout:FRAME_TIMEOUT\n"
159
+ " error:&error];\n",
160
+ " timeout:FRAME_TIMEOUT\n"
161
+ " error:&error];\n"
162
+ " [captureGate unlock];\n"),
163
+ )