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,124 @@
1
+ """Model calls for the L1 runners: one JSON-answering interface over Claude, OpenAI and Jev, timed and priced, under a
2
+ spend cap kept in a ledger file. Keys come from env files and are never printed or written anywhere.
3
+
4
+ ``ask(model, system, parts, schema)`` returns ``Reply(out, usd, seconds, usage, error)``. ``parts``: text strings or
5
+ ``("image", path)`` tuples. ``Spend`` refuses a call once the ledger's total plus this run's spend reaches the cap.
6
+ """
7
+
8
+ import dataclasses
9
+ import json
10
+ import os
11
+ import pathlib
12
+ import threading
13
+ import time
14
+
15
+ from ... import frontier, model_clients
16
+
17
+
18
+ @dataclasses.dataclass
19
+ class Reply:
20
+ out: dict = None
21
+ usd: float = 0.0
22
+ seconds: float = 0.0
23
+ usage: dict = None
24
+ error: str = None
25
+
26
+
27
+ class OverBudget(RuntimeError):
28
+ pass
29
+
30
+
31
+ class Spend:
32
+ """A run's spend, checked against ``cap`` together with every earlier run in ``ledger`` (JSONL of {"usd": x})."""
33
+
34
+ def __init__(self, ledger, cap, label):
35
+ self.ledger, self.cap, self.label = pathlib.Path(ledger).expanduser() if ledger else None, cap, label
36
+ self.before = self._total()
37
+ self.usd = 0.0
38
+ self.lock = threading.Lock()
39
+
40
+ def _total(self):
41
+ if not self.ledger or not self.ledger.exists():
42
+ return 0.0
43
+ total = 0.0
44
+ for line in self.ledger.read_text().splitlines():
45
+ try:
46
+ total += float(json.loads(line).get("usd") or 0)
47
+ except ValueError:
48
+ pass
49
+ return total
50
+
51
+ def check(self):
52
+ if self.cap is not None and self.before + self.usd >= self.cap:
53
+ raise OverBudget(f"spend cap ${self.cap:.2f} reached (${self.before + self.usd:.2f} so far)")
54
+
55
+ def add(self, usd):
56
+ with self.lock:
57
+ self.usd += usd or 0.0
58
+
59
+ def close(self, extra=None):
60
+ if self.ledger and self.usd:
61
+ self.ledger.parent.mkdir(parents=True, exist_ok=True)
62
+ with open(self.ledger, "a") as handle:
63
+ handle.write(json.dumps({"at": time.strftime("%Y-%m-%dT%H:%M:%S"), "what": self.label,
64
+ "usd": round(self.usd, 5), **(extra or {})}) + "\n")
65
+
66
+
67
+ def load_keys(paths):
68
+ """Load env files into this process (values are never echoed)."""
69
+ from ...config import load_env_file
70
+ for path in paths or ():
71
+ path = os.path.expanduser(path)
72
+ if os.path.exists(path):
73
+ load_env_file(path)
74
+
75
+
76
+ _clients, _lock = {}, threading.Lock()
77
+
78
+
79
+ def client(model, reasoning="low"):
80
+ key = (model, reasoning)
81
+ with _lock:
82
+ if key not in _clients:
83
+ _clients[key] = model_clients.chat_client(model, reasoning=reasoning)
84
+ return _clients[key]
85
+
86
+
87
+ def _image(path):
88
+ data = pathlib.Path(path).read_bytes()
89
+ mime = "image/png" if str(path).lower().endswith(".png") else "image/jpeg"
90
+ return frontier.image_part(mime, data)
91
+
92
+
93
+ def messages(system, parts):
94
+ content = []
95
+ for part in parts:
96
+ if isinstance(part, tuple) and part[0] == "image":
97
+ if part[1]:
98
+ content.append(_image(part[1]))
99
+ elif isinstance(part, dict):
100
+ content.append(part)
101
+ else:
102
+ content.append({"type": "text", "text": str(part)})
103
+ return [{"role": "system", "content": system}, {"role": "user", "content": content}]
104
+
105
+
106
+ def ask(model, system, parts, schema, spend=None, reasoning="low", timeout=120):
107
+ """One call. The model answers JSON matching ``schema``; ``spend`` is charged its list-price cost."""
108
+ if spend is not None:
109
+ spend.check()
110
+ started = time.monotonic()
111
+ try:
112
+ out, usage = client(model, reasoning).complete(messages(system, parts), schema, timeout=timeout)
113
+ except Exception as exc: # a failed call is a row, not a crash: the scorer counts it
114
+ return Reply(error=f"{type(exc).__name__}: {str(exc)[:200]}", seconds=time.monotonic() - started)
115
+ seconds = time.monotonic() - started
116
+ usd = model_clients.cost_usd(model, usage) or 0.0
117
+ if spend is not None:
118
+ spend.add(usd)
119
+ return Reply(out=out if isinstance(out, dict) else {"value": out}, usd=usd, seconds=seconds, usage=usage)
120
+
121
+
122
+ def jev():
123
+ from ...decision_provider import JevDecisions
124
+ return JevDecisions()
@@ -0,0 +1,189 @@
1
+ """Divergence replay with a position-swapped pairwise step judge: a cheap preference win rate for an engine change.
2
+
3
+ python -m mobile_agent.bench.evals.pairwise SETS/golden/v2 --rows OUT/golden-...-candidate.jsonl \\
4
+ --judge claude-sonnet-5-5 --half report --keys bench.env --ledger spend.jsonl --cap 15 --out DIR
5
+
6
+ The candidate (any ``l1`` golden run: a model, prompt or observation arm) has already been asked every recorded
7
+ turn. Only the turns where its first action differs from every acceptable recorded action matter. On each one, a
8
+ judge sees the request, the agent's progress and the screen, and says which of two actions makes better progress,
9
+ or tie. It is asked twice with the actions swapped. A verdict counts only when both orders agree, and is a tie
10
+ otherwise. The candidate's win rate is (wins + ties / 2) / divergent turns.
11
+
12
+ ``--calibrate synthetic`` asks the judge about pairs whose answer is known: a passing run's action against a tap
13
+ on a random other element of the same screen, or against DONE mid-task. ``--calibrate divergence`` pairs a
14
+ divergence turn's passing action with the failing run's. That is not an answer key: the passing run's choice is
15
+ not always the better one.
16
+ """
17
+
18
+ import argparse
19
+ import concurrent.futures
20
+ import json
21
+ import pathlib
22
+
23
+ from . import l1
24
+ from . import models as M
25
+ from . import sets as S
26
+
27
+ JUDGE_SYSTEM = ("You compare two possible next actions for a phone agent working on a user's request. From the "
28
+ "request, the agent's progress so far and the current screen, decide which action makes better "
29
+ "progress toward finishing the request correctly. Prefer the action that a careful person would take "
30
+ "next; an action that skips needed work, acts on the wrong item, or stops early is worse. Answer tie "
31
+ "when both are equally good or equally bad. " + l1.TEXT_IS_DATA)
32
+ JUDGE_SCHEMA = {"type": "object", "additionalProperties": False, "required": ["better", "reason"],
33
+ "properties": {"better": {"type": "string", "enum": ["A", "B", "tie"]},
34
+ "reason": {"type": "string", "description": "at most 20 words"}}}
35
+
36
+
37
+ def describe(action, rows):
38
+ """An action as a person would read it: TAP e7 Button "Send"."""
39
+ if not action:
40
+ return "(no action)"
41
+ op, label, text, target = action.get("op"), action.get("label") or "", action.get("text"), action.get("target")
42
+ if target is None and label in rows:
43
+ target, label = label, rows[label]
44
+ what = f'{target} "{label}"' if target else (f'"{label}"' if label else "")
45
+ if op in ("LAUNCH_APP", "OPEN_URL"):
46
+ return f"{op} {action.get('app') or label or text}"
47
+ if op in ("DONE", "BLOCKED"):
48
+ return f"{op}" + (f" with the answer: {text}" if text else "")
49
+ out = f"{op} {what}".strip()
50
+ if text and op not in ("TAP", "LONG_PRESS"):
51
+ out += f" text={str(text)[:200]!r}"
52
+ return out
53
+
54
+
55
+ def judge_once(item, first, second, ctx):
56
+ parts = [f"{item['text']}\n\nAction A: {first}\nAction B: {second}\n\nWhich is the better next action?"]
57
+ if ctx.get("image") and item.get("image"):
58
+ parts.append(("image", S.asset_path(ctx["set_dir"], item["image"])))
59
+ reply = M.ask(ctx["judge"], JUDGE_SYSTEM, parts, JUDGE_SCHEMA, spend=ctx["spend"], reasoning="low")
60
+ return (reply.out or {}).get("better") if not reply.error else None, reply
61
+
62
+
63
+ def judge_pair(item, candidate, recorded, ctx):
64
+ """'candidate' | 'recorded' | 'tie' from two orders, with each order's raw answer and the $."""
65
+ rows = l1.rows_of(item["text"])
66
+ a, b = describe(candidate, rows), describe(recorded, rows)
67
+ v1, r1 = judge_once(item, a, b, ctx) # candidate is A
68
+ v2, r2 = judge_once(item, b, a, ctx) # candidate is B
69
+ first = {"A": "candidate", "B": "recorded", "tie": "tie"}.get(v1)
70
+ second = {"A": "recorded", "B": "candidate", "tie": "tie"}.get(v2)
71
+ verdict = first if first == second and first is not None else "tie"
72
+ return {"verdict": verdict, "orders": [v1, v2], "consistent": first == second and first is not None,
73
+ "candidate": a, "recorded": b, "usd": r1.usd + r2.usd, "seconds": r1.seconds + r2.seconds,
74
+ "error": r1.error or r2.error}
75
+
76
+
77
+ def synthetic_pairs(items, n, seed=20261010):
78
+ """Pairs whose answer is known: a passing run's recorded action against a tap on a random other element of the
79
+ same screen, or against DONE in the middle of the task. The judge should prefer the recorded action."""
80
+ import random
81
+ rng = random.Random(seed)
82
+ pool = [i for i in items if i["kind"] == "path"]
83
+ rng.shuffle(pool)
84
+ out = []
85
+ for item in pool:
86
+ if len(out) >= n:
87
+ break
88
+ rows = l1.rows_of(item["text"])
89
+ if len(out) % 2 == 0:
90
+ taken = {item["gold"].get("target")} | {a[4] for a in item.get("alternatives") or () if len(a) > 4}
91
+ taken |= {eid for eid, label in rows.items() if T_norm(label) in {T_norm(item["gold"]["label"])} |
92
+ {a[1] for a in item.get("alternatives") or ()}}
93
+ others = [eid for eid, label in rows.items() if eid not in taken and label]
94
+ if not others:
95
+ continue
96
+ eid = rng.choice(others)
97
+ bad = {"op": "TAP", "label": rows[eid], "text": None, "app": None, "target": eid}
98
+ else:
99
+ bad = {"op": "DONE", "label": "", "text": "Done.", "app": None, "target": None}
100
+ out.append((item, bad, item["gold"]))
101
+ return out
102
+
103
+
104
+ def T_norm(text):
105
+ from .traces import norm
106
+ return norm(text)
107
+
108
+
109
+ def replay(set_dir, rows_path, judge, half="report", workers=4, ledger=None, cap=None, image=True, calibrate=None,
110
+ calibrate_n=20):
111
+ set_dir = pathlib.Path(set_dir).expanduser()
112
+ _, items = S.load(set_dir, half=half if half != "all" else None)
113
+ by = {i["id"]: i for i in items}
114
+ spend = M.Spend(ledger, cap, f"pairwise {judge} {'calibrate' if calibrate else pathlib.Path(rows_path).name}")
115
+ ctx = {"set_dir": set_dir, "judge": judge, "spend": spend, "image": image}
116
+ jobs = []
117
+ if calibrate == "divergence":
118
+ for item in items:
119
+ if item.get("wrong"):
120
+ jobs.append((item, item["wrong"], item["gold"])) # the "candidate" is the failing run's action
121
+ elif calibrate:
122
+ jobs = synthetic_pairs(items, calibrate_n)
123
+ else:
124
+ for line in pathlib.Path(rows_path).expanduser().read_text().splitlines():
125
+ row = json.loads(line)
126
+ item = by.get(row["id"])
127
+ if item is None or row.get("error") or row.get("skipped"):
128
+ continue
129
+ if not l1.golden_match(row, item)["alt"]:
130
+ jobs.append((item, row.get("pred"), item["gold"]))
131
+ try:
132
+ with concurrent.futures.ThreadPoolExecutor(workers) as pool:
133
+ results = list(pool.map(lambda job: {"id": job[0]["id"], "step_type": job[0]["step_type"],
134
+ "kind": job[0]["kind"], **judge_pair(job[0], job[1], job[2], ctx)},
135
+ jobs))
136
+ finally:
137
+ spend.close({"pairs": len(jobs)})
138
+ n = len(results)
139
+ wins = sum(r["verdict"] == "candidate" for r in results)
140
+ losses = sum(r["verdict"] == "recorded" for r in results)
141
+ ties = n - wins - losses
142
+ summary = {"rows": str(rows_path) if not calibrate else "calibrate", "judge": judge, "image": image, "half": half,
143
+ "divergent": n, "candidate_wins": wins, "recorded_wins": losses, "ties": ties,
144
+ "win_rate": round((wins + ties / 2) / n, 3) if n else None,
145
+ "consistent": round(sum(r["consistent"] for r in results) / n, 3) if n else None,
146
+ "usd": round(sum(r["usd"] for r in results), 4), "errors": sum(bool(r["error"]) for r in results)}
147
+ if not calibrate:
148
+ scored = sum(1 for line in pathlib.Path(rows_path).expanduser().read_text().splitlines()
149
+ if json.loads(line)["id"] in by and not json.loads(line).get("skipped"))
150
+ summary["turns"] = scored
151
+ summary["divergence_rate"] = round(n / scored, 3) if scored else None
152
+ return summary, results
153
+
154
+
155
+ def main(argv=None):
156
+ parser = argparse.ArgumentParser(prog="python -m mobile_agent.bench.evals.pairwise",
157
+ description=__doc__.split("\n\n")[0])
158
+ parser.add_argument("set_dir")
159
+ parser.add_argument("--rows", help="a golden run's rows (the candidate)")
160
+ parser.add_argument("--calibrate", choices=("synthetic", "divergence"),
161
+ help="judge known pairs: synthetic (the recorded action against a random other tap or an early "
162
+ "DONE) or divergence (a passing run's action against the failing run's)")
163
+ parser.add_argument("--calibrate-n", type=int, default=20)
164
+ parser.add_argument("--judge", default="claude-sonnet-5-5")
165
+ parser.add_argument("--no-image", action="store_true")
166
+ parser.add_argument("--half", default="report", choices=("tune", "report", "all"))
167
+ parser.add_argument("--workers", type=int, default=4)
168
+ parser.add_argument("--keys", nargs="*", default=())
169
+ parser.add_argument("--ledger")
170
+ parser.add_argument("--cap", type=float)
171
+ parser.add_argument("--out", required=True)
172
+ args = parser.parse_args(argv)
173
+ if not args.rows and not args.calibrate:
174
+ parser.error("give --rows or --calibrate")
175
+ M.load_keys(args.keys)
176
+ summary, results = replay(args.set_dir, args.rows, args.judge, args.half, args.workers, args.ledger, args.cap,
177
+ image=not args.no_image, calibrate=args.calibrate, calibrate_n=args.calibrate_n)
178
+ out = pathlib.Path(args.out).expanduser()
179
+ out.mkdir(parents=True, exist_ok=True)
180
+ name = f"calibrate-{args.calibrate}" if args.calibrate else pathlib.Path(args.rows).stem
181
+ (out / f"pairwise-{name}-{l1.slug(args.judge)}.jsonl").write_text("".join(json.dumps(r) + "\n" for r in results))
182
+ with open(out / "pairwise-summary.jsonl", "a") as handle:
183
+ handle.write(json.dumps(summary) + "\n")
184
+ print(json.dumps(summary))
185
+ return 0
186
+
187
+
188
+ if __name__ == "__main__":
189
+ raise SystemExit(main())
@@ -0,0 +1,90 @@
1
+ """Proxy validity: does a cheap eval predict the paired A/B? One file of hypotheses, one rank correlation per proxy.
2
+
3
+ python -m mobile_agent.bench.evals.proxy validity.jsonl [--outcome pass_delta|rubric_delta] [--min 4]
4
+
5
+ Each line of the file is one hypothesis that reached (or was stopped before) the paired A/B:
6
+
7
+ {"hypothesis": "...", "change": "PR or branch", "date": "2026-10-10",
8
+ "gates": {"l1": "written before the run", "l2": "..."},
9
+ "proxies": {"<level>:<set>:<metric>": value, ...}, # the change's delta on each proxy, sign: + is better
10
+ "l3": {"batch": "ac1", "pass_delta": -0.125, "pass_ci": [-0.25, -0.025], "rubric_delta": -0.033}}
11
+
12
+ A hypothesis with no "l3" was stopped by a cheaper level, and is listed but not correlated. For every proxy with
13
+ at least ``--min`` hypotheses that have an L3 result, it prints Spearman's rank correlation with the outcome and how
14
+ often the signs agree. A proxy that doesn't predict gets dropped or fixed.
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import pathlib
20
+
21
+
22
+ def ranks(xs):
23
+ order = sorted(range(len(xs)), key=lambda i: xs[i])
24
+ out = [0.0] * len(xs)
25
+ i = 0
26
+ while i < len(order):
27
+ j = i
28
+ while j + 1 < len(order) and xs[order[j + 1]] == xs[order[i]]:
29
+ j += 1
30
+ for k in range(i, j + 1):
31
+ out[order[k]] = (i + j) / 2 + 1
32
+ i = j + 1
33
+ return out
34
+
35
+
36
+ def spearman(xs, ys):
37
+ if len(xs) < 3:
38
+ return None
39
+ rx, ry = ranks(xs), ranks(ys)
40
+ mx, my = sum(rx) / len(rx), sum(ry) / len(ry)
41
+ sxy = sum((a - mx) * (b - my) for a, b in zip(rx, ry))
42
+ sxx = sum((a - mx) ** 2 for a in rx) ** .5
43
+ syy = sum((b - my) ** 2 for b in ry) ** .5
44
+ return round(sxy / (sxx * syy), 3) if sxx and syy else None
45
+
46
+
47
+ def validity(entries, outcome="pass_delta", minimum=4):
48
+ done = [e for e in entries if (e.get("l3") or {}).get(outcome) is not None]
49
+ keys = sorted({k for e in done for k in (e.get("proxies") or {})})
50
+ rows = []
51
+ for key in keys:
52
+ pairs = [(e["proxies"][key], e["l3"][outcome]) for e in done if (e.get("proxies") or {}).get(key) is not None]
53
+ row = {"proxy": key, "n": len(pairs)}
54
+ if len(pairs) >= minimum:
55
+ xs, ys = zip(*pairs)
56
+ row["spearman"] = spearman(list(xs), list(ys))
57
+ row["sign_agreement"] = round(sum((x > 0) == (y > 0) for x, y in pairs if x and y) /
58
+ max(1, sum(1 for x, y in pairs if x and y)), 3)
59
+ rows.append(row)
60
+ return {"hypotheses": len(entries), "with_l3": len(done), "stopped_early": len(entries) - len(done),
61
+ "outcome": outcome, "proxies": rows}
62
+
63
+
64
+ def main(argv=None):
65
+ parser = argparse.ArgumentParser(prog="python -m mobile_agent.bench.evals.proxy",
66
+ description=__doc__.split("\n\n")[0])
67
+ parser.add_argument("file")
68
+ parser.add_argument("--outcome", default="pass_delta")
69
+ parser.add_argument("--min", type=int, default=4)
70
+ args = parser.parse_args(argv)
71
+ entries = [json.loads(line) for line in pathlib.Path(args.file).expanduser().read_text().splitlines()
72
+ if line.strip()]
73
+ report = validity(entries, args.outcome, args.min)
74
+ print(f"{report['hypotheses']} hypotheses: {report['with_l3']} with a paired A/B, {report['stopped_early']} "
75
+ f"stopped earlier. Outcome: {args.outcome}.")
76
+ for e in entries:
77
+ l3 = e.get("l3") or {}
78
+ print(f"- {e['hypothesis']}: " + (f"L3 {l3.get('batch')} {args.outcome} {l3.get(args.outcome):+.3f}" if
79
+ l3.get(args.outcome) is not None
80
+ else "no L3 (" + e.get("status", "stopped") + ")"))
81
+ for row in report["proxies"]:
82
+ if "spearman" in row:
83
+ print(f" {row['proxy']}: n {row['n']}, Spearman {row['spearman']}, signs agree {row['sign_agreement']}")
84
+ else:
85
+ print(f" {row['proxy']}: n {row['n']} (needs {args.min} with a paired A/B)")
86
+ return 0
87
+
88
+
89
+ if __name__ == "__main__":
90
+ raise SystemExit(main())
@@ -0,0 +1,149 @@
1
+ """Frozen L1 sets: versioned, hashed, split by task into a tune half and a report half.
2
+
3
+ A set lives in its own folder, outside the repository (its items hold task text and screens):
4
+
5
+ <root>/<name>/<version>/items.jsonl one item per line, sorted by id; each has "id", "task", "half"
6
+ <root>/<name>/<version>/assets/ the screenshots items name, by content hash
7
+ <root>/<name>/<version>/MANIFEST.json counts, sources, the builder's commit, and "sha256"
8
+
9
+ ``sha256`` covers items.jsonl and every asset's bytes. ``load`` refuses a set whose files no longer match it, so a
10
+ number reported on a set is a number on exactly that set. A change tuned on the tune half is reported on the report
11
+ half; the halves split tasks, not items, so no task's screens leak from one half into the other.
12
+
13
+ The halves come from one task-to-half map shared by every set (``make_halves``): tasks are dealt alternately within
14
+ strata (core or not, category) in the order of a seeded hash. Tasks in a split named in ``forbidden`` (val, test)
15
+ never enter a set.
16
+ """
17
+
18
+ import datetime
19
+ import hashlib
20
+ import json
21
+ import pathlib
22
+ import subprocess
23
+
24
+ SEED = "20261010"
25
+ HALVES = ("tune", "report")
26
+
27
+
28
+ def _h(text):
29
+ return hashlib.sha256(text.encode()).hexdigest()
30
+
31
+
32
+ def make_halves(strata, seed=SEED):
33
+ """{task: "tune" | "report"} from {task: stratum}: within each stratum, tasks in seeded-hash order alternate."""
34
+ by = {}
35
+ for task, stratum in strata.items():
36
+ by.setdefault(str(stratum), []).append(task)
37
+ out = {}
38
+ for stratum in sorted(by):
39
+ for i, task in enumerate(sorted(by[stratum], key=lambda t: _h(f"{seed}:{stratum}:{t}"))):
40
+ out[task] = HALVES[i % 2]
41
+ return out
42
+
43
+
44
+ def halves_from_splits(splits, extra=None, seed=SEED):
45
+ """The halves of every dev task in a tuning splits file (core and category strata), plus ``extra``
46
+ {task: stratum} for tasks outside it (another benchmark's)."""
47
+ dev = splits["splits"]["dev"]
48
+ core = set(splits["splits"].get("dev_core") or ())
49
+ tasks = splits.get("tasks") or {}
50
+ strata = {t: f"{'core' if t in core else 'dev'}:{(tasks.get(t) or {}).get('category')}" for t in dev}
51
+ strata.update({t: f"extra:{s}" for t, s in (extra or {}).items()})
52
+ return make_halves(strata, seed)
53
+
54
+
55
+ def forbidden_tasks(splits, names=("val", "test")):
56
+ return {t for name in names for t in (splits["splits"].get(name) or ())}
57
+
58
+
59
+ def _git_head():
60
+ try:
61
+ here = pathlib.Path(__file__).resolve().parent
62
+ return subprocess.run(["git", "-C", str(here), "rev-parse", "--short=12", "HEAD"], capture_output=True,
63
+ text=True, timeout=5).stdout.strip() or None
64
+ except (OSError, subprocess.SubprocessError):
65
+ return None
66
+
67
+
68
+ class SetWriter:
69
+ """Collects items and assets, then writes a frozen set. ``asset(path)`` copies a file in and returns its name."""
70
+
71
+ def __init__(self, root, name, version, halves, forbidden=()):
72
+ self.dir = pathlib.Path(root).expanduser() / name / version
73
+ if (self.dir / "MANIFEST.json").exists():
74
+ raise FileExistsError(f"{self.dir} is frozen; build a new version instead")
75
+ self.name, self.version, self.halves, self.forbidden = name, version, halves, set(forbidden)
76
+ self.items, self.assets, self.skipped = [], {}, {"forbidden": 0, "no_half": 0}
77
+
78
+ def asset(self, path):
79
+ if not path:
80
+ return None
81
+ path = pathlib.Path(path)
82
+ try:
83
+ data = path.read_bytes()
84
+ except OSError:
85
+ return None
86
+ name = hashlib.sha256(data).hexdigest()[:20] + path.suffix.lower()
87
+ self.assets[name] = data
88
+ return f"assets/{name}"
89
+
90
+ def add(self, item):
91
+ task = item["task"]
92
+ if task in self.forbidden:
93
+ self.skipped["forbidden"] += 1
94
+ return False
95
+ half = self.halves.get(task)
96
+ if half is None:
97
+ self.skipped["no_half"] += 1
98
+ return False
99
+ self.items.append({**item, "half": half})
100
+ return True
101
+
102
+ def write(self, sources=(), notes=""):
103
+ ids = [i["id"] for i in self.items]
104
+ if len(ids) != len(set(ids)):
105
+ raise ValueError("duplicate item ids")
106
+ self.dir.mkdir(parents=True, exist_ok=True)
107
+ tmp = self.dir / "assets"
108
+ tmp.mkdir(exist_ok=True)
109
+ for name, data in self.assets.items():
110
+ (tmp / name).write_bytes(data)
111
+ lines = "".join(json.dumps(i, ensure_ascii=False, sort_keys=True) + "\n" for i in sorted(self.items,
112
+ key=lambda i: i["id"]))
113
+ (self.dir / "items.jsonl").write_text(lines, encoding="utf-8")
114
+ manifest = {"name": self.name, "version": self.version,
115
+ "created": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
116
+ "builder_commit": _git_head(), "items": len(self.items),
117
+ "halves": {h: sum(i["half"] == h for i in self.items) for h in HALVES},
118
+ "tasks": {h: len({i["task"] for i in self.items if i["half"] == h}) for h in HALVES},
119
+ "assets": len(self.assets), "skipped": self.skipped, "sources": list(sources), "notes": notes,
120
+ "sha256": digest(self.dir)}
121
+ (self.dir / "MANIFEST.json").write_text(json.dumps(manifest, indent=1))
122
+ return manifest
123
+
124
+
125
+ def digest(set_dir):
126
+ """sha256 over items.jsonl and every asset's name and bytes."""
127
+ set_dir = pathlib.Path(set_dir)
128
+ h = hashlib.sha256((set_dir / "items.jsonl").read_bytes())
129
+ assets = set_dir / "assets"
130
+ for path in sorted(assets.iterdir()) if assets.is_dir() else ():
131
+ h.update(path.name.encode())
132
+ h.update(path.read_bytes())
133
+ return h.hexdigest()
134
+
135
+
136
+ def load(set_dir, half=None, verify=True):
137
+ """(manifest, items) of a frozen set, refusing one whose files no longer match its hash."""
138
+ set_dir = pathlib.Path(set_dir).expanduser()
139
+ manifest = json.loads((set_dir / "MANIFEST.json").read_text())
140
+ if verify and digest(set_dir) != manifest["sha256"]:
141
+ raise ValueError(f"{set_dir} no longer matches its MANIFEST sha256: rebuild it as a new version")
142
+ items = [json.loads(line) for line in (set_dir / "items.jsonl").read_text(encoding="utf-8").splitlines() if line]
143
+ if half:
144
+ items = [i for i in items if i["half"] == half]
145
+ return manifest, items
146
+
147
+
148
+ def asset_path(set_dir, name):
149
+ return pathlib.Path(set_dir).expanduser() / name if name else None