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,446 @@
1
+ """Recorded runs in one shape: a Run of Turns of Actions, from either benchmark's folders. $0, no device.
2
+
3
+ Two sources:
4
+
5
+ - **iOSWorld task folders** (``bench/iosworld.py``): ``NNN-<task>/task.json`` (the request, the judge's verdict
6
+ and the run's ``mobster.events``) and ``prompts.jsonl`` (one line per model turn: the prompt text the model saw,
7
+ its reply and the screenshot it saw). A batch folder holds run folders (``A1``, ``ab7-a1``...) of task folders.
8
+ - **Phone and simulator benchmark runs** (``bench/runner.py``): ``records.jsonl`` (one line per attempt, with the
9
+ verdict) and ``artifacts/<repeat>_<task>_<agent>/`` holding ``events.jsonl`` and ``prompts.jsonl``.
10
+
11
+ A turn's screen is identified as ``iosworld_stats`` does it: a hash of the first 40 (role, label) rows of the
12
+ prompt's element list, so values typed into a field don't change it. Timing comes from the run's events:
13
+ ``frontier_decision`` (the model call) and ``frontier_action`` (refresh, exec and settle per action). The adapter
14
+ keeps only the last 400 events of a run, so a long run's early turns can lack timing (``Run.events_capped``).
15
+ """
16
+
17
+ import dataclasses
18
+ import glob
19
+ import hashlib
20
+ import json
21
+ import os
22
+ import pathlib
23
+ import re
24
+ import time
25
+
26
+ from ..iosworld_stats import EVENTS_CAP, NON_GUARD_REFUSALS, official_pass, rubric_score
27
+
28
+ ROW = re.compile(r'^(e\d+) (\w+) "([^"]*)"', re.M)
29
+ CURRENT_APP = re.compile(r"^Current app: (.*)$", re.M)
30
+ # The pooled simulators were rebuilt and isolated on 26 Sep 2026 (PR #17): runs before then shared app data
31
+ # between workers, so their outcomes are suspect even where their timings are fine.
32
+ CLEAN_SINCE = time.mktime((2026, 9, 26, 16, 40, 0, 0, 0, -1))
33
+ TYPED_OPS = frozenset({"TYPE", "TYPE_SUBMIT", "SET_TEXT"})
34
+ SWITCH_OPS = frozenset({"LAUNCH_APP", "HOME", "OPEN_URL"})
35
+ SCROLL_OPS = frozenset({"SWIPE_UP", "SWIPE_DOWN", "SWIPE_LEFT", "SWIPE_RIGHT", "SCROLL_TO", "READ_LIST"})
36
+ END_OPS = frozenset({"DONE", "BLOCKED"})
37
+ LONG_TEXT = 40 # typed text longer than this is free-form content (a note's body), compared only as "long text"
38
+ BACK_WORDS = frozenset({"back", "cancel", "close", "dismiss", "x", "×", "not now", "later"})
39
+
40
+
41
+ def norm(text):
42
+ return re.sub(r"\s+", " ", str(text or "")).strip().casefold()
43
+
44
+
45
+ def app_key(app):
46
+ """An app's comparable name: "CloudSheets" and "com.example.cloudsheets" are both "cloudsheets"."""
47
+ app = norm(app)
48
+ return app.rsplit(".", 1)[-1] if "." in app and " " not in app else app
49
+
50
+
51
+ def step_type(op):
52
+ """The kind of step an operation is: ground (tap an element), type, scroll (move or read a list), switch (change
53
+ app), end (DONE or BLOCKED), other (WAIT, DISMISS...)."""
54
+ if op in TYPED_OPS or op == "SUBMIT":
55
+ return "type"
56
+ if op in ("TAP", "LONG_PRESS", "TAP_XY"):
57
+ return "ground"
58
+ if op in SCROLL_OPS:
59
+ return "scroll"
60
+ if op in SWITCH_OPS:
61
+ return "switch"
62
+ if op in END_OPS:
63
+ return "end"
64
+ return "other"
65
+
66
+
67
+ @dataclasses.dataclass
68
+ class Action:
69
+ op: str
70
+ label: str = ""
71
+ target: str = None
72
+ text: str = None
73
+ app: str = None
74
+ changed: bool = None
75
+ refresh_ms: float = None
76
+ exec_ms: float = None
77
+ settle_ms: float = None
78
+ executed: bool = False
79
+
80
+ @property
81
+ def device_ms(self):
82
+ return sum(x or 0.0 for x in (self.refresh_ms, self.exec_ms, self.settle_ms))
83
+
84
+ @property
85
+ def is_back(self):
86
+ return self.op == "DISMISS" or (self.op in ("TAP", "TAP_XY") and norm(self.label) in BACK_WORDS)
87
+
88
+ def token(self):
89
+ """What the action did, comparable across runs of the same task: (app, operation, label, typed text)."""
90
+ if self.op in SWITCH_OPS:
91
+ return (app_key(self.app), self.op, app_key(self.text or self.label), "")
92
+ text = norm(self.text) if self.op in TYPED_OPS else ""
93
+ return (app_key(self.app), self.op, norm(self.label), text if len(text) <= LONG_TEXT else "<long text>")
94
+
95
+
96
+ @dataclasses.dataclass
97
+ class Turn:
98
+ step: int
99
+ app: str = None
100
+ screen: str = None
101
+ title: str = None
102
+ rows: int = 0
103
+ planned: list = dataclasses.field(default_factory=list)
104
+ actions: list = dataclasses.field(default_factory=list)
105
+ refused: list = dataclasses.field(default_factory=list)
106
+ failed: list = dataclasses.field(default_factory=list)
107
+ stops: list = dataclasses.field(default_factory=list)
108
+ route: str = None
109
+ model_ms: float = None
110
+ ttft_ms: float = None
111
+ gen_ms: float = None
112
+ prep_ms: float = None
113
+ in_tokens: int = None
114
+ out_tokens: int = None
115
+ cached_tokens: int = None
116
+ write_tokens: int = None
117
+ out_chars: int = None
118
+ notes_add: int = 0
119
+ checklist: list = dataclasses.field(default_factory=list)
120
+ answer: str = None
121
+ thought: str = None
122
+ image: str = None
123
+ has_prompt: bool = False
124
+ timed: bool = False
125
+
126
+ @property
127
+ def done(self):
128
+ return any(a.op in END_OPS for a in self.planned)
129
+
130
+ @property
131
+ def done_only(self):
132
+ return bool(self.planned) and all(a.op in END_OPS for a in self.planned)
133
+
134
+ @property
135
+ def gained(self):
136
+ """The turn wrote something down: a note, a checklist item found or done, an answer."""
137
+ return bool(self.notes_add or self.answer or any(
138
+ (c or {}).get("status") in ("found", "done") for c in self.checklist))
139
+
140
+ @property
141
+ def device_ms(self):
142
+ return sum(a.device_ms for a in self.actions)
143
+
144
+ @property
145
+ def seconds(self):
146
+ return ((self.model_ms or 0.0) + (self.prep_ms or 0.0) + self.device_ms) / 1000
147
+
148
+ @property
149
+ def kind(self):
150
+ ops = [a.op for a in (self.actions or self.planned)]
151
+ return step_type(ops[0]) if ops else "other"
152
+
153
+
154
+ @dataclasses.dataclass
155
+ class Run:
156
+ folder: str
157
+ suite: str
158
+ batch: str
159
+ arm: str
160
+ repeat: int
161
+ task: str
162
+ category: str = None
163
+ difficulty: str = None
164
+ apps: list = dataclasses.field(default_factory=list)
165
+ goal: str = ""
166
+ model: str = None
167
+ build: str = None
168
+ device: str = "sim"
169
+ clean: bool = True
170
+ judged: bool = False
171
+ passed: bool = None
172
+ rubric: float = None
173
+ criteria: list = dataclasses.field(default_factory=list)
174
+ status: str = None
175
+ reason: str = ""
176
+ answer: str = ""
177
+ agent_s: float = None
178
+ usd: float = None
179
+ calls: int = None
180
+ timing: dict = dataclasses.field(default_factory=dict)
181
+ events_capped: bool = False
182
+ started: float = None
183
+ turns: list = dataclasses.field(default_factory=list)
184
+
185
+ @property
186
+ def id(self):
187
+ return f"{self.batch}/{self.arm}{self.repeat}/{self.task}"
188
+
189
+ @property
190
+ def actions(self):
191
+ return [a for t in self.turns for a in (t.actions or t.planned) if a.op not in END_OPS]
192
+
193
+ @property
194
+ def timed(self):
195
+ return any(t.timed for t in self.turns)
196
+
197
+
198
+ def screen_rows(text):
199
+ """[(id, role, label)] of the prompt's element list ("Screen elements:" to the end of the text)."""
200
+ at = (text or "").find("Screen elements:")
201
+ return ROW.findall(text[at:]) if at >= 0 else []
202
+
203
+
204
+ def screen_hash(rows):
205
+ return hashlib.md5(json.dumps([[r, lab] for _, r, lab in rows[:40]]).encode()).hexdigest()[:12] if rows else None
206
+
207
+
208
+ def screen_title(rows):
209
+ for _, role, label in rows:
210
+ if role == "NavigationBar" and label:
211
+ return label
212
+ for _, role, label in rows[:6]:
213
+ if role in ("StaticText", "Other") and label:
214
+ return label
215
+ return None
216
+
217
+
218
+ def read_jsonl(path):
219
+ out = []
220
+ try:
221
+ lines = pathlib.Path(path).read_text(encoding="utf-8", errors="replace").splitlines()
222
+ except OSError:
223
+ return out
224
+ for line in lines:
225
+ try:
226
+ out.append(json.loads(line))
227
+ except ValueError:
228
+ pass
229
+ return out
230
+
231
+
232
+ def turn_from_prompt(prompt, folder=None):
233
+ text = prompt.get("text") or ""
234
+ rows = screen_rows(text)
235
+ labels = {eid: label for eid, _, label in rows}
236
+ match = CURRENT_APP.search(text)
237
+ app = match.group(1).strip() if match else None
238
+ out = prompt.get("out") or {}
239
+ planned = []
240
+ for a in out.get("actions") or ():
241
+ op = a.get("operation") or a.get("op") or ""
242
+ target = a.get("target")
243
+ label = a.get("target_label") or labels.get(target or "", "") or ""
244
+ if op == "LAUNCH_APP":
245
+ label = a.get("app") or a.get("text") or label
246
+ planned.append(Action(op=op, label=label, target=target, text=a.get("text"),
247
+ app=(a.get("app") if op == "LAUNCH_APP" else app)))
248
+ image = prompt.get("image")
249
+ return Turn(step=prompt.get("step"), app=app, screen=screen_hash(rows), title=screen_title(rows), rows=len(rows),
250
+ planned=planned, notes_add=len(out.get("notes_add") or ()),
251
+ checklist=[c for c in out.get("checklist_updates") or () if isinstance(c, dict)],
252
+ answer=out.get("answer"), thought=out.get("thought"),
253
+ out_chars=len(json.dumps(out, ensure_ascii=False)) if out else None,
254
+ image=str(pathlib.Path(folder) / image) if (folder and image) else image, has_prompt=True)
255
+
256
+
257
+ def attach_events(turns, events):
258
+ """Fold a run's frontier_* events into its turns (by step). Returns the turns, in step order."""
259
+ by_step = {t.step: t for t in turns}
260
+ executed = {}
261
+ for e in events:
262
+ kind, step = e.get("event"), e.get("step")
263
+ if not str(kind).startswith("frontier_") or step is None:
264
+ continue
265
+ turn = by_step.get(step)
266
+ if turn is None:
267
+ turn = by_step[step] = Turn(step=step)
268
+ if kind == "frontier_decision":
269
+ turn.timed = True
270
+ turn.route = e.get("source") or turn.route
271
+ if e.get("latency_ms") is not None:
272
+ turn.model_ms = (turn.model_ms or 0.0) + float(e["latency_ms"])
273
+ if e.get("prep_ms") is not None:
274
+ turn.prep_ms = (turn.prep_ms or 0.0) + float(e["prep_ms"])
275
+ tokens = e.get("tokens") or e.get("usage") or {}
276
+ if isinstance(tokens, dict):
277
+ turn.in_tokens = tokens.get("in", tokens.get("prompt_tokens", turn.in_tokens))
278
+ turn.out_tokens = tokens.get("out", tokens.get("completion_tokens", turn.out_tokens))
279
+ turn.cached_tokens = tokens.get("cached", tokens.get("cached_tokens", turn.cached_tokens))
280
+ turn.write_tokens = tokens.get("write", tokens.get("cache_write_tokens", turn.write_tokens))
281
+ if tokens.get("ttft_ms") is not None:
282
+ turn.ttft_ms = float(tokens["ttft_ms"])
283
+ if tokens.get("total_ms") is not None:
284
+ turn.gen_ms = float(tokens["total_ms"]) - turn.ttft_ms
285
+ if not turn.planned and e.get("ops"):
286
+ turn.planned = [Action(op=op, app=turn.app) for op in e["ops"]]
287
+ elif kind == "frontier_action":
288
+ turn.timed = True
289
+ op = e.get("operation") or ""
290
+ label = e.get("target_label") or ""
291
+ app = e.get("app_name") or turn.app
292
+ text = e.get("text") if isinstance(e.get("text"), str) else None
293
+ index = e.get("index")
294
+ if op == "LAUNCH_APP":
295
+ planned = turn.planned[index] if isinstance(index, int) and index < len(turn.planned) else None
296
+ label = label or (planned.label if planned else "")
297
+ app = (planned.app if planned else None) or label or app
298
+ executed.setdefault(step, []).append(Action(
299
+ op=op, label=label, target=None, text=text, app=app, changed=e.get("changed"),
300
+ refresh_ms=e.get("refresh_ms"), exec_ms=e.get("exec_ms"), settle_ms=e.get("settle_ms"), executed=True))
301
+ elif kind == "frontier_refused":
302
+ turn.refused.append(e.get("label") or "")
303
+ elif kind == "frontier_failed":
304
+ turn.failed.append({"op": e.get("operation"), "error": str(e.get("error") or e.get("detail") or "")[:160]})
305
+ elif kind == "frontier_chunk_stop":
306
+ turn.stops.append(e.get("reason") or "")
307
+ for step, actions in executed.items():
308
+ turn = by_step[step]
309
+ for a, p in zip(actions, turn.planned):
310
+ if not a.label and p.label:
311
+ a.label = p.label
312
+ if a.text is None and p.text is not None and a.op in TYPED_OPS:
313
+ a.text = p.text
314
+ turn.actions = actions
315
+ ordered = [by_step[k] for k in sorted(by_step, key=lambda s: (s is None, s))]
316
+ current = None # runs recorded without prompts: carry the app forward from the last switch
317
+ for turn in ordered:
318
+ if turn.app:
319
+ current = turn.app
320
+ else:
321
+ turn.app = current
322
+ for a in turn.actions or turn.planned:
323
+ if a.app is None:
324
+ a.app = turn.app
325
+ if a.op == "LAUNCH_APP" and (a.label or a.text):
326
+ current = a.label or a.text
327
+ return ordered
328
+
329
+
330
+ def guard_refusals(turn):
331
+ return [r for r in turn.refused if r not in NON_GUARD_REFUSALS]
332
+
333
+
334
+ def _batch_arm(folder):
335
+ """(batch, arm, repeat) from a task folder's place: <batch>/<A1>/<NNN-task> or <batch-a1>/<NNN-task>."""
336
+ run_dir = folder.parent
337
+ m = re.match(r"^([A-Za-z]+)(\d+)$", run_dir.name)
338
+ if m: # tuning batches: aa1/A1/...
339
+ return run_dir.parent.name, m.group(1), int(m.group(2))
340
+ m = re.match(r"^(.*?)-([a-z])(\d)$", run_dir.name)
341
+ if m: # ab7-a1, ab6-b2
342
+ return m.group(1), m.group(2).upper(), int(m.group(3))
343
+ m = re.match(r"^(.*?)-r(\d)$", run_dir.name)
344
+ if m: # model-gpt-6-sol-r1
345
+ return m.group(1), "A", int(m.group(2))
346
+ return run_dir.name, "A", 1
347
+
348
+
349
+ def load_iosworld(folder, clean_since=CLEAN_SINCE):
350
+ folder = pathlib.Path(folder)
351
+ record = json.loads((folder / "task.json").read_text(encoding="utf-8"))
352
+ mobster = record.get("mobster") or {}
353
+ evaluation = record.get("evaluation")
354
+ batch, arm, repeat = _batch_arm(folder)
355
+ prompts_path = folder / "prompts.jsonl"
356
+ try:
357
+ started = os.path.getmtime(prompts_path if prompts_path.exists() else folder / "task.json")
358
+ except OSError:
359
+ started = None
360
+ judged = bool(evaluation) and record.get("status") in ("ok", None)
361
+ events = mobster.get("events") or []
362
+ turns = [turn_from_prompt(p, folder) for p in read_jsonl(prompts_path)]
363
+ turns = attach_events(turns, events)
364
+ calls = mobster.get("model_calls")
365
+ return Run(folder=str(folder), suite="iosworld", batch=batch, arm=arm, repeat=repeat,
366
+ task=record.get("task") or folder.name.partition("-")[2], category=record.get("category"),
367
+ difficulty=record.get("difficulty"), apps=list(record.get("apps") or ()), goal=record.get("goal") or "",
368
+ model=mobster.get("model"), build=mobster.get("build"), device="sim",
369
+ clean=started is None or started >= clean_since, judged=judged,
370
+ passed=official_pass(evaluation) if judged else None,
371
+ rubric=rubric_score(evaluation) if judged else None,
372
+ criteria=[{"criterion": r.get("criterion"), "satisfied": r.get("satisfied")}
373
+ for r in (evaluation or {}).get("rubric_results") or ()],
374
+ status=mobster.get("status"), reason=str(mobster.get("reason") or ""),
375
+ answer=record.get("agent_answer") or "", agent_s=mobster.get("agent_seconds"),
376
+ usd=mobster.get("cost_usd"), calls=calls if isinstance(calls, int) else len(turns),
377
+ timing=mobster.get("timing") or {}, events_capped=len(events) >= EVENTS_CAP, started=started,
378
+ turns=turns)
379
+
380
+
381
+ def _artifact_name(record):
382
+ return re.sub(r"[:+]", "_", f"{record.get('repeat', 0)}:{record.get('task')}:{record.get('agent')}")
383
+
384
+
385
+ def load_bench(batch_dir):
386
+ """Runs from one benchmark batch folder (records.jsonl, plan.json, artifacts/)."""
387
+ batch_dir = pathlib.Path(batch_dir)
388
+ try:
389
+ plan = json.loads((batch_dir / "plan.json").read_text())
390
+ except (OSError, ValueError):
391
+ plan = {}
392
+ port = str(plan.get("wda_url") or "").rsplit(":", 1)[-1]
393
+ device = "phone" if port in ("8100", "") and "sim" not in batch_dir.name else "sim"
394
+ runs = []
395
+ for record in read_jsonl(batch_dir / "records.jsonl"):
396
+ folder = batch_dir / "artifacts" / _artifact_name(record)
397
+ if not folder.is_dir():
398
+ continue
399
+ verdict = record.get("verdict")
400
+ turns = [turn_from_prompt(p, folder) for p in read_jsonl(folder / "prompts.jsonl")]
401
+ events = read_jsonl(folder / "events.jsonl")
402
+ turns = attach_events(turns, events)
403
+ try:
404
+ summary = json.loads((folder / "summary.json").read_text())
405
+ except (OSError, ValueError):
406
+ summary = {}
407
+ config = record.get("config") or {}
408
+ runs.append(Run(folder=str(folder), suite="mobsterbench", batch=batch_dir.name,
409
+ arm=str(record.get("agent") or "A"), repeat=int(record.get("repeat") or 0) + 1,
410
+ task=str(record.get("task")), category=record.get("category"), goal="",
411
+ model=config.get("model") or summary.get("model"), device=device, clean=True,
412
+ judged=verdict in ("pass", "fail"), passed=(verdict == "pass") if verdict in ("pass", "fail")
413
+ else None, rubric=None, status=record.get("status"), reason=str(record.get("reason") or ""),
414
+ answer=json.dumps(record.get("answer")) if record.get("answer") is not None else "",
415
+ agent_s=(record.get("agent_ms") or 0) / 1000 or None, usd=record.get("cost_usd"),
416
+ calls=record.get("model_calls"), timing=summary.get("timing") or {},
417
+ events_capped=False, started=None, turns=turns))
418
+ return runs
419
+
420
+
421
+ def discover(roots, clean_since=CLEAN_SINCE, suites=("iosworld", "mobsterbench")):
422
+ """Every run under ``roots`` (batch folders or folders of them), each loaded once."""
423
+ runs, seen = [], set()
424
+ for root in roots:
425
+ root = pathlib.Path(root).expanduser()
426
+ if "iosworld" in suites:
427
+ pattern = [str(root / "task.json"), str(root / "*" / "task.json"), str(root / "*" / "*" / "task.json"),
428
+ str(root / "*" / "*" / "*" / "task.json")]
429
+ for task_json in sorted({p for pat in pattern for p in glob.glob(pat)}):
430
+ folder = pathlib.Path(task_json).parent.resolve()
431
+ if folder in seen or not re.match(r"^\d+-", folder.name):
432
+ continue
433
+ seen.add(folder)
434
+ try:
435
+ runs.append(load_iosworld(folder, clean_since))
436
+ except (OSError, ValueError, KeyError, TypeError):
437
+ continue
438
+ if "mobsterbench" in suites:
439
+ for records in sorted(set(glob.glob(str(root / "records.jsonl"))
440
+ + glob.glob(str(root / "*" / "records.jsonl")))):
441
+ batch_dir = pathlib.Path(records).parent.resolve()
442
+ if batch_dir in seen:
443
+ continue
444
+ seen.add(batch_dir)
445
+ runs.extend(load_bench(batch_dir))
446
+ return runs
@@ -0,0 +1,176 @@
1
+ """Device fixtures the operator sets up once (image sets and app records), with ground truth frozen in code."""
2
+
3
+ import hashlib
4
+ import json
5
+ import random
6
+ import shutil
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ VISION_DIR = Path(__file__).resolve().parent.parent / "evals" / "vision"
11
+ ROOT_FOLDER = "MobsterBench"
12
+ FILES_PATH = ("Browse", "On My iPhone", ROOT_FOLDER)
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ImageSetSpec:
17
+ key: str # e.g. "files.dogs12"
18
+ folder: str # folder under On My iPhone/MobsterBench
19
+ predicate: str # vision manifest predicate
20
+ picks: tuple # ((label, difficulty, count), ...)
21
+ album: str = "" # Photos album holding the same images, if any
22
+
23
+
24
+ IMAGE_SETS = (
25
+ ImageSetSpec("files.dogs12", "dogs12", "dog",
26
+ (("yes", "normal", 4), ("yes", "hard", 1), ("no", "normal", 4), ("no", "hard", 3)),
27
+ album="MobsterBench Dogs"),
28
+ ImageSetSpec("files.receipts12", "receipts12", "receipt",
29
+ (("yes", "normal", 5), ("yes", "hard", 1), ("no", "normal", 3), ("no", "hard", 3)),
30
+ album="MobsterBench Receipts"),
31
+ ImageSetSpec("files.sunglasses12", "sunglasses12", "sunglasses",
32
+ (("yes", "normal", 4), ("yes", "hard", 2), ("no", "normal", 3), ("no", "hard", 3))),
33
+ # 4 answerable eye colours and 4 where the only correct answer is "unsure".
34
+ ImageSetSpec("files.eyes8", "eyes8", "eye_colour",
35
+ (("blue", "normal", 1), ("brown", "normal", 2), ("green", "normal", 1),
36
+ ("unsure", "normal", 2), ("unsure", "hard", 2))),
37
+ )
38
+
39
+ # Typed in by the operator. Content is deliberately fictional (555-01xx numbers
40
+ # are reserved for fiction) and contains no send/buy/call style wording that a
41
+ # safety rule could confuse with an action.
42
+ RECORDS = {
43
+ "notes.bench": {
44
+ "app": "notes", "title": "MobsterBench Note",
45
+ "body": ["MobsterBench Note", "Locker code: 4817", "https://en.m.wikipedia.org/wiki/Alan_Turing"],
46
+ "code": "4817",
47
+ "verify": ("MobsterBench Note",),
48
+ "setup": "Notes: create a note in the default 'Notes' folder (iCloud or On My iPhone) whose three "
49
+ "lines are exactly the body lines. Do not pin or lock it.",
50
+ },
51
+ "contacts.bench": {
52
+ "app": "contacts", "first": "Bench", "last": "Tester", "company": "Mobster Bench Labs",
53
+ "phone": "(555) 010-4477", "city": "Cupertino",
54
+ "verify": ("Bench Tester",),
55
+ "setup": "Contacts: new contact First 'Bench', Last 'Tester', Company 'Mobster Bench Labs', "
56
+ "phone (mobile) '(555) 010-4477', address city 'Cupertino'.",
57
+ },
58
+ "reminders.bench": {
59
+ "app": "reminders", "list": "MobsterBench",
60
+ "items": ["Water the plants", "Return library books", "Pick up dry cleaning"],
61
+ "count": "3", "count_x7": "21",
62
+ "verify": ("Water the plants", "Return library books", "Pick up dry cleaning"),
63
+ "path": ("MobsterBench",),
64
+ "setup": "Reminders: new list 'MobsterBench' with exactly these three incomplete reminders, "
65
+ "no dates, no flags.",
66
+ },
67
+ "calendar.bench": {
68
+ "app": "calendar", "title": "MobsterBench Review", "date": "2026-10-15", "start": "14:00",
69
+ "end": "15:00", "location": "Room 42",
70
+ "verify": ("MobsterBench Review",),
71
+ "setup": "Calendar: event 'MobsterBench Review' on Thursday 15 October 2026, 14:00-15:00 local "
72
+ "time, location 'Room 42', no invitees, no alerts.",
73
+ },
74
+ }
75
+
76
+
77
+ def _vision_manifest():
78
+ return json.loads((VISION_DIR / "manifest.json").read_text())
79
+
80
+
81
+ def select(spec, manifest=None):
82
+ """Deterministic pick + order for one set. Items sharing an entity are avoided."""
83
+ manifest = manifest or _vision_manifest()
84
+ pool = sorted((item for item in manifest["items"]
85
+ if item["predicate"] == spec.predicate and item.get("entity") is None),
86
+ key=lambda item: item["id"])
87
+ chosen = []
88
+ for label, difficulty, count in spec.picks:
89
+ group = [item for item in pool if item["label"] == label and item["difficulty"] == difficulty]
90
+ if len(group) < count:
91
+ raise ValueError(f"{spec.key}: need {count} {label}/{difficulty}, have {len(group)}")
92
+ chosen.extend(group[:count])
93
+ random.Random(f"mobsterbench-v1:{spec.key}").shuffle(chosen)
94
+ rows = []
95
+ for index, item in enumerate(chosen, 1):
96
+ # A neutral file name, so the name on the phone never gives its label away.
97
+ rows.append({"file": f"{spec.folder}-{index:02d}.jpg", "source_id": item["id"],
98
+ "source_file": item["file"], "label": item["label"], "accept": list(item["accept"]),
99
+ "sha256": hashlib.sha256((VISION_DIR / item["file"]).read_bytes()).hexdigest()})
100
+ return rows
101
+
102
+
103
+ class Fixtures:
104
+ """Frozen fixture ground truth. ``value(key, part)`` feeds the checks."""
105
+
106
+ def __init__(self, manifest=None):
107
+ manifest = manifest or _vision_manifest()
108
+ self.sets = {spec.key: (spec, select(spec, manifest)) for spec in IMAGE_SETS}
109
+ self.records = RECORDS
110
+
111
+ def value(self, key, part):
112
+ if key in self.sets:
113
+ spec, rows = self.sets[key]
114
+ positive = {"dog": "yes", "receipt": "yes", "sunglasses": "yes"}.get(spec.predicate)
115
+ if part == "files":
116
+ return [row["file"] for row in rows]
117
+ if part == "positives":
118
+ return [row["file"] for row in rows if row["label"] == positive]
119
+ if part == "positive_count":
120
+ return sum(1 for row in rows if row["label"] == positive)
121
+ if part == "labels":
122
+ return {row["file"]: row["label"] for row in rows}
123
+ if part == "accept":
124
+ return {row["file"]: row["accept"] for row in rows}
125
+ if part.startswith("item:"):
126
+ _, index, *attribute = part.split(":")
127
+ row = rows[int(index) - 1]
128
+ return row[attribute[0]] if attribute else row
129
+ if key in self.records and part in self.records[key]:
130
+ return self.records[key][part]
131
+ raise KeyError(f"no fixture value {key}.{part}")
132
+
133
+ def frozen(self):
134
+ """Canonical description for the suite hash (labels + image digests + records)."""
135
+ return {"image_sets": {key: {"folder": spec.folder, "album": spec.album,
136
+ "rows": rows} for key, (spec, rows) in sorted(self.sets.items())},
137
+ "records": {key: {k: v for k, v in rec.items()} for key, rec in sorted(self.records.items())}}
138
+
139
+ def verify_path(self, key):
140
+ """Labels the harness taps (read-only navigation) to reach a fixture, and strings it expects."""
141
+ if key in self.sets:
142
+ spec, rows = self.sets[key]
143
+ return "files", (*FILES_PATH, spec.folder), [row["file"].rsplit(".", 1)[0] for row in rows]
144
+ if key.startswith("photos."):
145
+ spec = next(spec for spec, _ in self.sets.values() if spec.album and
146
+ spec.key.split(".", 1)[1] == key.split(".", 1)[1])
147
+ return "photos", (spec.album,), [spec.album]
148
+ record = self.records[key]
149
+ return record["app"], tuple(record.get("path", ())), list(record["verify"])
150
+
151
+ def build(self, out_dir):
152
+ """Write the folders to copy to the phone, plus a labels file kept OFF the phone."""
153
+ out = Path(out_dir)
154
+ phone_root = out / "copy-to-phone" / ROOT_FOLDER
155
+ for key, (spec, rows) in self.sets.items():
156
+ folder = phone_root / spec.folder
157
+ folder.mkdir(parents=True, exist_ok=True)
158
+ for row in rows:
159
+ shutil.copyfile(VISION_DIR / row["source_file"], folder / row["file"])
160
+ albums = {spec.album: spec.folder for spec, _ in self.sets.values() if spec.album}
161
+ (out / "labels-do-not-copy.json").write_text(json.dumps(self.frozen(), indent=1))
162
+ lines = ["MobsterBench fixture setup (see mobile_agent/bench/README.md)", "",
163
+ f"1. Copy the folder copy-to-phone/{ROOT_FOLDER} to Files > On My iPhone (AirDrop or Finder).",
164
+ "2. In Photos, create these albums from the same images (import order does not matter):"]
165
+ lines += [f" - {album}: every image in {ROOT_FOLDER}/{folder}" for album, folder in albums.items()]
166
+ lines += ["3. Create these records by hand:"]
167
+ for rec in self.records.values():
168
+ lines.append(f" - {rec['setup']}")
169
+ # The exact contents the setup sentence refers to.
170
+ for label, key in (("Note lines", "body"), ("Reminders", "items")):
171
+ if rec.get(key):
172
+ lines.append(f" {label}:")
173
+ lines += [f" {value}" for value in rec[key]]
174
+ lines += ["4. Never copy labels-do-not-copy.json to the phone: it is the answer key."]
175
+ (out / "SETUP.txt").write_text("\n".join(lines) + "\n")
176
+ return out