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,88 @@
1
+ """Bounded, local-only MCP view of two exact saved test suites."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from .comparison import ComparisonError, compare_suites
7
+
8
+ NAME = "compare_test_suites"
9
+ MAX_CASES = 100
10
+ SUITE = r"^[0-9]{8}-[0-9]{6}-[0-9a-f]{4}$"
11
+ REF = re.compile(r"[a-f0-9]{12}\Z")
12
+
13
+
14
+ def definition():
15
+ return {"name": NAME,
16
+ "description": "Compare two exact completed local test suites. Returns both verdicts and bounded "
17
+ "case transitions; never treats a green run alone as proof of a fix.",
18
+ "inputSchema": {"type": "object", "properties": {
19
+ "base_suite_id": {"type": "string", "pattern": SUITE,
20
+ "description": "the saved suite before the proposed change"},
21
+ "candidate_suite_id": {"type": "string", "pattern": SUITE,
22
+ "description": "the saved suite after the proposed change"}},
23
+ "required": ["base_suite_id", "candidate_suite_id"],
24
+ "additionalProperties": False}}
25
+
26
+
27
+ def call(args, toolset, *, compare=compare_suites):
28
+ from ..mcp_server.protocol import ToolResult
29
+ from ..mcp_server.tools import ToolError
30
+
31
+ runs = Path(toolset.runs_dir).absolute()
32
+ private = runs.parent
33
+ results = private / "test-results"
34
+ if runs.name != "runs" or private.name != ".mobster":
35
+ raise ToolError("Start this MCP server in the test project without a custom --out to compare its suites.")
36
+ if runs.is_symlink() or private.is_symlink() or results.is_symlink():
37
+ raise ToolError("The project's test-results folder must not be a symlink.")
38
+ try:
39
+ answer = compare(results, args["base_suite_id"], args["candidate_suite_id"])
40
+ except (ComparisonError, OSError) as error:
41
+ raise ToolError(str(error)) from None
42
+ total = len(answer["cases"])
43
+ answer = {**answer, "cases": answer["cases"][:MAX_CASES], "casesTotal": total,
44
+ "casesTruncated": total > MAX_CASES}
45
+ proof = "yes, for controlled app state" if answer["verifiedFix"] else "no"
46
+ text = (f"Compared suites {args['base_suite_id']} and {args['candidate_suite_id']}: "
47
+ f"{'comparable' if answer['comparable'] else 'not comparable'}; verified fix: {proof}. "
48
+ f"Showing {len(answer['cases'])} of {total} case-target pairs.")
49
+ return ToolResult(text, answer)
50
+
51
+
52
+ def mask_comparison(mask, args, result):
53
+ """Preserve only validated opaque suite/case handles through the contextual secret mask."""
54
+ from ..mcp_server.tools import ToolError
55
+
56
+ if result.is_error:
57
+ return mask(result)
58
+ value = result.structured
59
+ if not isinstance(value, dict) or set(value) != {
60
+ "schema", "baseSuite", "candidateSuite", "baseVerdict", "candidateVerdict", "comparable",
61
+ "verifiedFix", "proof", "reasons", "cases", "casesTotal", "casesTruncated"} or \
62
+ value["schema"] != "mobster.test-compare/1" or \
63
+ value["baseSuite"] != args["base_suite_id"] or value["candidateSuite"] != args["candidate_suite_id"] or \
64
+ type(value["verifiedFix"]) is not bool or \
65
+ value["proof"] != {"scope": "controlled_app_state" if value["verifiedFix"] else None} or \
66
+ type(value["casesTotal"]) is not int or \
67
+ not isinstance(value["cases"], list) or len(value["cases"]) > MAX_CASES or \
68
+ type(value["casesTruncated"]) is not bool:
69
+ raise ToolError("Mobster returned an invalid local suite comparison.")
70
+ refs = []
71
+ for row in value["cases"]:
72
+ if not isinstance(row, dict) or type(row.get("verifiedFix")) is not bool or \
73
+ row.get("proof") != {"scope": "controlled_app_state" if row["verifiedFix"] else None} or \
74
+ not all(isinstance(row.get(key), str) and REF.fullmatch(row[key])
75
+ for key in ("caseRef", "targetRef")):
76
+ raise ToolError("Mobster returned invalid comparison case references.")
77
+ refs.append((row["caseRef"], row["targetRef"]))
78
+ result.structured = {**value, "schema": None, "baseSuite": None, "candidateSuite": None, "proof": None,
79
+ "cases": [{**row, "caseRef": None, "targetRef": None, "proof": None}
80
+ for row in value["cases"]]}
81
+ masked = mask(result)
82
+ masked.structured["schema"] = value["schema"]
83
+ masked.structured["baseSuite"] = value["baseSuite"]
84
+ masked.structured["candidateSuite"] = value["candidateSuite"]
85
+ masked.structured["proof"] = value["proof"]
86
+ for row, source, (case_ref, target_ref) in zip(masked.structured["cases"], value["cases"], refs):
87
+ row.update(caseRef=case_ref, targetRef=target_ref, proof=source["proof"])
88
+ return masked
@@ -0,0 +1,471 @@
1
+ """Find a smaller local reproducer by running bounded, isolated check variants.
2
+
3
+ Only steps change. The saved failure, app bytes, registered fixture, assertions and
4
+ resolved simulator stay fixed. A draft is never an approved check or a proof of
5
+ external backend isolation.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import re
11
+ import stat
12
+ import threading
13
+ import time
14
+ from contextlib import ExitStack, contextmanager
15
+ from dataclasses import replace
16
+ from pathlib import Path
17
+
18
+ from ..verify.checks import CheckError, dump_check
19
+ from . import capsule, comparison
20
+ from .api import Options, UsageError, execute, plan
21
+ from .discover import Entry
22
+ from .provenance import app_digest, case_provenance, require_case_provenance
23
+
24
+ SCHEMA = "mobster.test-minimize/1"
25
+ MAX_ATTEMPTS = 8
26
+ MAX_SECONDS = 1800
27
+ MAX_USD = 2
28
+ REF = re.compile(r"[0-9a-f]{12}\Z")
29
+ MAX_DRAFT_BYTES = 1_000_000
30
+
31
+
32
+ class MinimizationError(ValueError):
33
+ """The saved failure cannot safely enter a new reduction experiment."""
34
+
35
+
36
+ class _Draft:
37
+ def __init__(self, root, folder, root_chain, descriptors):
38
+ self.root = root
39
+ self.folder = folder
40
+ self.root_chain = root_chain
41
+ self.descriptors = descriptors
42
+ self.fd = descriptors[-1]
43
+
44
+ def attached(self):
45
+ try:
46
+ paths = (*self.root_chain, (".mobster", self.descriptors[0], self.descriptors[1]),
47
+ ("drafts", self.descriptors[1], self.descriptors[2]),
48
+ ("repro", self.descriptors[2], self.descriptors[3]),
49
+ (self.folder.name, self.descriptors[3], self.descriptors[4]))
50
+ for name, parent, held_fd in paths:
51
+ found = os.stat(name, dir_fd=parent, follow_symlinks=False)
52
+ held = os.fstat(held_fd)
53
+ if not stat.S_ISDIR(found.st_mode) or (found.st_dev, found.st_ino) != (held.st_dev, held.st_ino):
54
+ return False
55
+ except OSError:
56
+ return False
57
+ return True
58
+
59
+ def _bytes(self, data):
60
+ if len(data) > MAX_DRAFT_BYTES or not self.attached():
61
+ raise MinimizationError("The draft directory changed or its output is too large. Review this attempt.")
62
+ return data
63
+
64
+ def write_new(self, name, content):
65
+ try:
66
+ capsule._write_at(self.fd, name, self._bytes(content.encode("utf-8")))
67
+ os.fsync(self.fd)
68
+ except (OSError, capsule.CapsuleError) as error:
69
+ raise MinimizationError(
70
+ "The draft file already exists or its directory changed. Review this attempt."
71
+ ) from error
72
+
73
+ def save(self, value):
74
+ data = (json.dumps(value, sort_keys=True, indent=2, allow_nan=False) + "\n").encode("utf-8")
75
+ try:
76
+ capsule._atomic_bytes(self.fd, "result.json", self._bytes(data))
77
+ except (OSError, capsule.CapsuleError) as error:
78
+ raise MinimizationError("The draft result could not be saved. Review this attempt.") from error
79
+
80
+
81
+ @contextmanager
82
+ def _draft_directory(root, name):
83
+ folder = root / ".mobster" / "drafts" / "repro" / name
84
+ with ExitStack() as stack:
85
+ descriptors = []
86
+ root_chain = []
87
+
88
+ def opened(path, parent=None, create=False):
89
+ if create:
90
+ try:
91
+ os.mkdir(path, 0o700, dir_fd=parent)
92
+ except FileExistsError:
93
+ pass
94
+ fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent)
95
+ stack.callback(os.close, fd)
96
+ return fd
97
+
98
+ try:
99
+ root_fd = opened("/")
100
+ root_chain.append(("/", None, root_fd))
101
+ for part in root.parts[1:]:
102
+ child = opened(part, root_fd)
103
+ root_chain.append((part, root_fd, child))
104
+ root_fd = child
105
+ descriptors.append(root_fd)
106
+ mobster_fd = opened(".mobster", root_fd)
107
+ descriptors.append(mobster_fd)
108
+ drafts_fd = opened("drafts", mobster_fd, create=True)
109
+ descriptors.append(drafts_fd)
110
+ repro_fd = opened("repro", drafts_fd, create=True)
111
+ descriptors.append(repro_fd)
112
+ try:
113
+ os.mkdir(name, 0o700, dir_fd=repro_fd)
114
+ except FileExistsError as error:
115
+ raise MinimizationError("This reduction already started. Review its draft and suite results before "
116
+ "a new attempt.") from error
117
+ descriptors.append(opened(name, repro_fd))
118
+ for fd in descriptors[:-1]:
119
+ os.fsync(fd)
120
+ except OSError as error:
121
+ raise MinimizationError("The draft output directory must be a physical project directory.") from error
122
+ draft = _Draft(root, folder, root_chain, descriptors)
123
+ if not draft.attached():
124
+ raise MinimizationError("The draft directory changed before the experiment started.")
125
+ yield draft
126
+
127
+
128
+ def _selected(doc, case_ref, target_ref):
129
+ found = [(check, cell) for check in doc.get("checks", ()) if isinstance(check, dict)
130
+ for cell in (check.get("results") if isinstance(check.get("results"), list) else ())
131
+ if isinstance(cell, dict)
132
+ and comparison._ref(check.get("caseId") or check.get("file") or "") == case_ref
133
+ and comparison._ref(cell.get("deviceKey") or "") == target_ref]
134
+ if len(found) != 1:
135
+ raise MinimizationError("Select one exact case and simulator from each saved suite.")
136
+ return found[0]
137
+
138
+
139
+ def _app(root, supplied, expected):
140
+ try:
141
+ path = Path(supplied).expanduser()
142
+ path = (path if path.is_absolute() else root / path).resolve(strict=True)
143
+ path.relative_to(root)
144
+ if path.suffix != ".app" or not path.is_dir() or app_digest(path) != expected:
145
+ raise ValueError("app digest mismatch")
146
+ except (OSError, TypeError, ValueError) as error:
147
+ raise MinimizationError("Keep both exact saved .app builds under this project and pass their paths.") from error
148
+ return path
149
+
150
+
151
+ def _source(results_root, bad_suite_id, control_suite_id, case_ref, target_ref,
152
+ bad_app, control_app, isolated, max_attempts):
153
+ from .. import __version__
154
+ if isolated is not True:
155
+ raise MinimizationError("Pass --isolated only after resetting the external fixture yourself.")
156
+ if type(max_attempts) is not int or not 5 <= max_attempts <= MAX_ATTEMPTS:
157
+ raise MinimizationError("--max-attempts must be between 5 and 8 total suite runs.")
158
+ if not isinstance(case_ref, str) or not REF.fullmatch(case_ref) or \
159
+ not isinstance(target_ref, str) or not REF.fullmatch(target_ref):
160
+ raise MinimizationError("Choose the exact caseRef and targetRef from a comparison report.")
161
+ try:
162
+ bad = comparison._read(results_root, bad_suite_id)
163
+ control = comparison._read(results_root, control_suite_id)
164
+ except comparison.ComparisonError as error:
165
+ raise MinimizationError(str(error)) from error
166
+ paired = comparison.compare(bad, control)
167
+ selected = [row for row in paired["cases"] if row["caseRef"] == case_ref and row["targetRef"] == target_ref]
168
+ if len(selected) != 1 or not selected[0]["verifiedFix"] or \
169
+ selected[0]["proof"]["scope"] != "controlled_app_state":
170
+ raise MinimizationError("This exact bad/control case needs a controlled app-state verified comparison.")
171
+ if any(len(doc["checks"]) != 1 or len(doc["checks"][0]["results"]) != 1
172
+ for doc in (bad, control)):
173
+ raise MinimizationError("Use paired suites with one check on one simulator for this reduction.")
174
+ policy = comparison._policy(bad)
175
+ if (policy is None or policy["replay"] != "fresh" or policy["keyless"] or policy["retries"] != 0 or
176
+ policy["repeat"] != 1 or policy["mobster"] != __version__):
177
+ raise MinimizationError("Use paired single-attempt fresh Smart suites from this Mobster version.")
178
+ check_row, cell = _selected(bad, case_ref, target_ref)
179
+ control_row, control_cell = _selected(control, case_ref, target_ref)
180
+ proof = comparison._business_fields(check_row, cell)
181
+ env = comparison._environment(cell)
182
+ if (cell.get("status") != "failed" or comparison._reason(cell) != "business" or
183
+ not proof or not proof[1] or not env or env["kind"] != "simulator" or
184
+ cell.get("deviceKey") != f"sim:{env['model']}@{env['runtime']}" or
185
+ not comparison._digest(cell.get("appDigest")) or
186
+ cell["runs"][0].get("class") != "business" or cell.get("flaky") is not False or
187
+ cell.get("quarantined") is not False or cell.get("replayed") is not False or
188
+ cell.get("healed") != [] or cell.get("safetyStop") or
189
+ not comparison._supporting_evidence(cell, check_row.get("visualBaselineSha256") is not None) or
190
+ check_row.get("provenanceDrift") or not comparison._digest(check_row.get("checkSpecDigest"))):
191
+ raise MinimizationError("The source needs one clean, registered app-state assertion failure.")
192
+ relative = check_row.get("file")
193
+ results_root = Path(results_root)
194
+ if results_root.name != "test-results" or results_root.parent.name != ".mobster" or results_root.is_symlink():
195
+ raise MinimizationError("Choose this project's physical .mobster/test-results directory.")
196
+ root = results_root.resolve().parent.parent
197
+ checks = root / ".mobster" / "checks"
198
+ if not isinstance(relative, str) or Path(relative).is_absolute() or ".." in Path(relative).parts:
199
+ raise MinimizationError("The saved check path is not a project check.")
200
+ path = root / relative
201
+ options = bad.get("options")
202
+ control_options = control.get("options")
203
+ if (not isinstance(options, dict) or type(options.get("strict")) is not bool or
204
+ not isinstance(control_options, dict) or control_options.get("strict") is not options["strict"]):
205
+ raise MinimizationError("The paired suites need the same frozen action policy.")
206
+ source_options = Options(paths=(str(path),), sims=(f"{env['model']}@{env['runtime']}",),
207
+ retries=0, fresh=True, strict=options["strict"],
208
+ assert_timeout=policy["assertTimeout"])
209
+ try:
210
+ if checks.resolve(strict=True) != checks or path.resolve(strict=True) != path:
211
+ raise ValueError("source check is linked")
212
+ path.relative_to(checks)
213
+ source_plan = plan(source_options, cwd=root)
214
+ if source_plan.root != root or len(source_plan.entries) != 1 or len(source_plan.targets) != 1:
215
+ raise ValueError("unexpected source plan")
216
+ source_entry = source_plan.entries[0]
217
+ check = source_entry.check
218
+ if (check is None or source_entry.quarantined is not None or source_entry.file != relative or
219
+ source_entry.path.resolve() != path.resolve()):
220
+ raise ValueError("source refused")
221
+ frozen = case_provenance(source_entry, root)
222
+ if frozen["sourceDigest"] is None:
223
+ raise ValueError("source check unreadable")
224
+ except (OSError, ValueError, CheckError, UsageError) as error:
225
+ raise MinimizationError("The saved check or registered fixture is unavailable.") from error
226
+ identity = ("checkSpecDigest", "baselineDigest", "visualBaselineSha256", "fixtureDigest",
227
+ "businessBindingSha256")
228
+ if any(frozen[field] != check_row.get(field) for field in identity):
229
+ raise MinimizationError("The check, baseline or enrolled fixture changed since the saved failure.")
230
+ if (check.business is None or check.reset_level() not in ("data", "reinstall") or
231
+ not check.steps or
232
+ check.max_seconds * max_attempts > MAX_SECONDS or check.max_usd * max_attempts > MAX_USD):
233
+ raise MinimizationError("Use a resettable app check with steps and a bounded run budget.")
234
+ common = {field: frozen[field] for field in identity}
235
+ if common != {field: check_row[field] for field in common} or \
236
+ common != {field: control_row[field] for field in common}:
237
+ raise MinimizationError("The check, baseline or enrolled fixture changed since the paired suites.")
238
+ bad_path = _app(root, bad_app, cell["appDigest"])
239
+ control_path = _app(root, control_app, control_cell["appDigest"])
240
+ if bad_path == control_path:
241
+ raise MinimizationError("The bad and control builds must be distinct preserved app artifacts.")
242
+ if any(replace(check, app_path=str(path)).reset_level() != check.reset_level()
243
+ for path in (bad_path, control_path)):
244
+ raise MinimizationError("Adding a saved app build would change the check's reset policy.")
245
+ return root, check, frozen, env, proof, policy, options, (bad_path, cell["appDigest"]), \
246
+ (control_path, control_cell["appDigest"])
247
+
248
+
249
+ def _outcome(doc, expected, expected_policy, env, fields, failed, arm):
250
+ """True: expected bad failure or clean control pass; False: known mismatch; None: uncertain effect."""
251
+ if (not isinstance(doc, dict) or doc.get("schema") != "mobster.test/1" or
252
+ not isinstance(doc.get("suite"), str) or not capsule.SUITE_ID.fullmatch(doc["suite"]) or
253
+ type(doc.get("finishedAt")) is not int or doc.get("stopReason") or doc.get("stopped") or
254
+ type(doc.get("exitCode")) is not int or doc["exitCode"] not in (0, 1) or
255
+ comparison._policy(doc) != expected_policy or
256
+ not isinstance(doc.get("checks"), list) or len(doc["checks"]) != 1):
257
+ return None
258
+ row = doc["checks"][0]
259
+ cells = row.get("results") if isinstance(row, dict) else None
260
+ if (not isinstance(cells, list) or len(cells) != 1 or
261
+ any(row.get(field) != expected[field] for field in
262
+ ("checkSpecDigest", "fixtureDigest", "businessBindingSha256", "baselineDigest",
263
+ "visualBaselineSha256")) or row.get("provenanceDrift")):
264
+ return None
265
+ cell = cells[0]
266
+ if (not isinstance(cell, dict) or comparison._environment(cell) != env or
267
+ cell.get("deviceKey") != f"sim:{env['model']}@{env['runtime']}" or
268
+ cell.get("appDigest") != expected["appDigest"] or cell.get("status") not in ("failed", "passed") or
269
+ cell.get("flaky") is not False or cell.get("quarantined") is not False or
270
+ cell.get("replayed") is not False or cell.get("healed") != [] or cell.get("safetyStop")):
271
+ return None
272
+ if doc["exitCode"] != (1 if cell["status"] == "failed" else 0):
273
+ return None
274
+ if not comparison._supporting_evidence(cell, expected["visualBaselineSha256"] is not None):
275
+ return None
276
+ proof = comparison._business_fields(row, cell)
277
+ if not proof or proof[0] != fields:
278
+ return None
279
+ if cell["status"] == "failed":
280
+ if comparison._reason(cell) != "business" or cell["runs"][0].get("class") != "business":
281
+ return False
282
+ return arm == "bad" and set(proof[1]) == set(failed)
283
+ if cell["runs"][0].get("class") is not None or cell.get("reason") is not None or proof[1]:
284
+ return None
285
+ return arm == "control"
286
+
287
+
288
+ def _canonical_evaluate(root, path, options, key, stop, frozen):
289
+ planned = plan(options, cwd=root)
290
+ if (planned.root != root or len(planned.entries) != 1 or
291
+ planned.entries[0].path.resolve() != path.resolve() or
292
+ planned.entries[0].quarantined is not None or
293
+ case_provenance(planned.entries[0], root) != frozen):
294
+ raise MinimizationError("The trial check changed before it was admitted. Review the draft before retrying.")
295
+ planned.entries[0].check = replace(planned.entries[0].check, draft_trial=True)
296
+ doc, _ = execute(planned, cwd=root, key=key, stop=stop,
297
+ before_attempt=lambda entry, check, record: require_case_provenance(entry, root, frozen))
298
+ return doc
299
+
300
+
301
+ def minimize(results_root, bad_suite_id, control_suite_id, *, case_ref, target_ref, bad_app, control_app,
302
+ isolated=False, max_attempts=8, key=None, evaluator=None, stop=None):
303
+ """Run a finite experiment and save a separate draft; never retry an interrupted experiment."""
304
+ admission = _source(
305
+ results_root, bad_suite_id, control_suite_id, case_ref, target_ref,
306
+ bad_app, control_app, isolated, max_attempts)
307
+ name = f"{bad_suite_id}-{control_suite_id}-{case_ref}-{target_ref}"
308
+ with _draft_directory(admission[0], name) as draft:
309
+ return _run(admission, draft, bad_suite_id, control_suite_id, case_ref, target_ref,
310
+ max_attempts, key, evaluator, stop)
311
+
312
+
313
+ def _run(admission, draft, bad_suite_id, control_suite_id, case_ref, target_ref,
314
+ max_attempts, key, evaluator, stop):
315
+ root, check, frozen, env, proof, policy, source_options, bad_build, control_build = admission
316
+ folder = draft.folder
317
+ result = {"schema": SCHEMA, "badSuite": bad_suite_id, "controlSuite": control_suite_id,
318
+ "caseRef": case_ref, "targetRef": target_ref,
319
+ "status": "running", "originalSteps": len(check.steps), "candidateSteps": None,
320
+ "attemptsUsed": 0, "candidateCheck": None, "reason": None, "trials": []}
321
+ def save():
322
+ draft.save(result)
323
+
324
+ save() # Durable intent precedes the first action. An interrupted run is not retried automatically.
325
+ deadline = time.monotonic() + MAX_SECONDS
326
+ stop = stop or threading.Event()
327
+ options = Options(sims=(f"{env['model']}@{env['runtime']}",), retries=0, repeat=1,
328
+ fresh=True, keyless=False, strict=source_options["strict"],
329
+ assert_timeout=policy["assertTimeout"])
330
+
331
+ def trial(steps, arm):
332
+ if stop.is_set():
333
+ result.update(status="stopped", reason="Stopped before the next trial started.")
334
+ save()
335
+ return None
336
+ if result["attemptsUsed"] >= max_attempts or time.monotonic() + check.max_seconds > deadline:
337
+ result.update(status="budget_exhausted", reason="The configured trial budget ended before dispatch.")
338
+ save()
339
+ return None
340
+ source_entry = Entry(Path(check.source), Path(check.source).relative_to(root).as_posix(), check)
341
+ current = case_provenance(source_entry, root)
342
+ if any(current[field] != frozen[field] for field in frozen):
343
+ result.update(status="stopped_unknown", reason="The source check or admitted assets changed.")
344
+ save()
345
+ return None
346
+ app_path, expected_app_digest = bad_build if arm == "bad" else control_build
347
+ try:
348
+ if app_digest(app_path) != expected_app_digest:
349
+ raise ValueError("build changed")
350
+ except (OSError, ValueError):
351
+ result.update(status="stopped_unknown", reason="A preserved app build changed.")
352
+ save()
353
+ return None
354
+ number = result["attemptsUsed"] + 1
355
+ path = folder / f"trial-{number}.yaml"
356
+ trial_file = path.relative_to(root).as_posix()
357
+ intended = replace(check, steps=tuple(steps), app_path=str(app_path), source=str(path),
358
+ a11y_scope=trial_file)
359
+ draft.write_new(path.name, dump_check(intended, project_root=root))
360
+ trial_frozen = case_provenance(Entry(path, trial_file, intended), root)
361
+ if (not comparison._digest(trial_frozen["checkSpecDigest"]) or
362
+ not comparison._digest(trial_frozen["sourceDigest"]) or
363
+ any(trial_frozen[field] != frozen[field] for field in
364
+ ("baselineDigest", "visualBaselineSha256", "fixtureDigest", "businessBindingSha256")) or
365
+ trial_frozen["appDigest"] != expected_app_digest):
366
+ result.update(status="stopped_unknown", reason="The draft's admitted assets changed.")
367
+ save()
368
+ return None
369
+ options.paths = (str(path),)
370
+ result["attemptsUsed"] = number
371
+ result["trials"].append({"suite": None, "arm": arm, "steps": len(steps), "outcome": "unknown"})
372
+ save()
373
+ if not draft.attached():
374
+ raise MinimizationError("The draft directory changed before trial admission. Review this attempt.")
375
+ doc = evaluator(path, options) if evaluator else _canonical_evaluate(
376
+ root, path, options, key, stop, trial_frozen)
377
+ outcome = _outcome(doc, trial_frozen, policy, env, proof[0], proof[1], arm)
378
+ snapshot = doc["checks"][0]["results"][0]["business"]["sourceSha256"] if outcome is not None else None
379
+ result["trials"][-1].update({"suite": doc.get("suite") if isinstance(doc, dict) else None,
380
+ "snapshotSha256": snapshot,
381
+ "outcome": (("same_failure" if arm == "bad" else "clean_pass")
382
+ if outcome is True else
383
+ "different" if outcome is False else "unknown")})
384
+ save()
385
+ return outcome
386
+
387
+ try:
388
+ bad_original = trial(check.steps, "bad")
389
+ if bad_original is None:
390
+ if result["status"] == "running":
391
+ result.update(status="stopped_unknown", reason="The original bad run has an unknown outcome.")
392
+ elif bad_original is False:
393
+ result.update(status="original_not_reproduced", reason="The saved bad failure did not recur.")
394
+ else:
395
+ control_original = trial(check.steps, "control")
396
+ if control_original is None:
397
+ if result["status"] == "running":
398
+ result.update(status="stopped_unknown", reason="The original control run has an unknown outcome.")
399
+ elif control_original is False:
400
+ result.update(status="control_not_reproduced", reason="The saved clean control did not pass.")
401
+ elif result["trials"][-2]["snapshotSha256"] == result["trials"][-1]["snapshotSha256"]:
402
+ result.update(status="stopped_unknown", reason="Bad and control reported identical app-state bytes.")
403
+ else:
404
+ steps = list(check.steps)
405
+ index = 0
406
+ while index < len(steps) and result["attemptsUsed"] + 3 <= max_attempts:
407
+ smaller = steps[:index] + steps[index + 1:]
408
+ bad_first = trial(smaller, "bad")
409
+ if bad_first is None:
410
+ if result["status"] == "running":
411
+ result.update(status="stopped_unknown", reason="A bad trial has an unknown outcome.")
412
+ break
413
+ if bad_first is False:
414
+ index += 1
415
+ continue
416
+ clean = trial(smaller, "control")
417
+ if clean is None:
418
+ if result["status"] == "running":
419
+ result.update(status="stopped_unknown", reason="A control trial has an unknown outcome.")
420
+ break
421
+ if clean is False:
422
+ index += 1
423
+ continue
424
+ bad_second = trial(smaller, "bad")
425
+ if bad_second is None:
426
+ if result["status"] == "running":
427
+ result.update(status="stopped_unknown", reason="A confirming bad trial is unknown.")
428
+ break
429
+ if result["trials"][-2]["snapshotSha256"] in (
430
+ result["trials"][-3]["snapshotSha256"], result["trials"][-1]["snapshotSha256"]):
431
+ result.update(
432
+ status="stopped_unknown",
433
+ reason="Bad and control reported identical app-state bytes.",
434
+ )
435
+ break
436
+ if bad_second:
437
+ steps = smaller
438
+ else:
439
+ index += 1
440
+ if result["status"] in ("running", "budget_exhausted"):
441
+ if control_original is True and len(steps) < len(check.steps):
442
+ source_entry = Entry(Path(check.source), Path(check.source).relative_to(root).as_posix(), check)
443
+ current = case_provenance(source_entry, root)
444
+ if any(current[field] != frozen[field] for field in frozen):
445
+ result.update(status="stopped_unknown", reason="The source check or admitted assets changed.")
446
+ save()
447
+ return result
448
+ try:
449
+ builds_stable = all(app_digest(path) == digest
450
+ for path, digest in (bad_build, control_build))
451
+ except (OSError, ValueError):
452
+ builds_stable = False
453
+ if not builds_stable:
454
+ result.update(status="stopped_unknown", reason="A preserved app build changed.")
455
+ save()
456
+ return result
457
+ candidate = folder / "candidate.yaml"
458
+ draft.write_new(candidate.name, dump_check(replace(check, steps=tuple(steps),
459
+ app_path=str(bad_build[0])), project_root=root))
460
+ result.update(status="smaller_reproducer", candidateSteps=len(steps),
461
+ candidateCheck=candidate.relative_to(root).as_posix(),
462
+ reason=("The configured budget ended after this confirmed reduction."
463
+ if result["status"] == "budget_exhausted" else None))
464
+ elif control_original is True and result["status"] == "running":
465
+ result.update(status="no_smaller_reproducer",
466
+ reason="No tested deletion kept the bad failure and clean control within the budget.")
467
+ except BaseException:
468
+ # The result remains 'running' with the in-flight trial marked unknown for manual reconciliation.
469
+ raise
470
+ save()
471
+ return result
@@ -0,0 +1,48 @@
1
+ """Run one bounded local reduction from an exact saved failure."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from ..verify.checks import project_root
8
+ from .minimize import MinimizationError, minimize
9
+
10
+
11
+ def run(args, operands, *, out=None, err=None, reduce=minimize):
12
+ out, err = out or sys.stdout, err or sys.stderr
13
+
14
+ def refuse(message):
15
+ if getattr(args, "json", False):
16
+ print(json.dumps({"ok": False, "error": message}), file=out)
17
+ else:
18
+ print(f"mobster test minimize: {message}", file=err)
19
+ return 3
20
+
21
+ if len(operands) != 4:
22
+ return refuse("name exact bad and control suites, caseRef and targetRef: "
23
+ "mobster test minimize BAD_SUITE_ID CONTROL_SUITE_ID CASE_REF TARGET_REF "
24
+ "--bad-app PATH --control-app PATH --isolated")
25
+ if not getattr(args, "minimize_isolated", False):
26
+ return refuse("pass --isolated only after resetting the external fixture yourself")
27
+ if not getattr(args, "minimize_bad_app", None) or not getattr(args, "minimize_control_app", None):
28
+ return refuse("name both exact saved builds with --bad-app and --control-app")
29
+ private = project_root(Path.cwd()) / ".mobster"
30
+ results = private / "test-results"
31
+ if private.is_symlink() or results.is_symlink():
32
+ return refuse("the project's test-results folder must not be a symlink")
33
+ try:
34
+ result = reduce(results, operands[0], operands[1], case_ref=operands[2], target_ref=operands[3],
35
+ bad_app=args.minimize_bad_app, control_app=args.minimize_control_app, isolated=True,
36
+ max_attempts=getattr(args, "minimize_max_attempts", None) or 8)
37
+ except (MinimizationError, OSError) as error:
38
+ return refuse(str(error))
39
+ if getattr(args, "json", False):
40
+ print(json.dumps(result, ensure_ascii=False, allow_nan=False), file=out)
41
+ else:
42
+ print(f"Reduction {result['status'].replace('_', ' ')}: {result['attemptsUsed']} trial suites; "
43
+ f"{result['originalSteps']} original steps.", file=out)
44
+ if result.get("candidateCheck"):
45
+ print(f"Review {result['candidateCheck']} ({result['candidateSteps']} steps).", file=out)
46
+ elif result.get("reason"):
47
+ print(result["reason"], file=out)
48
+ return 130 if result["status"] == "stopped" else 3 if result["status"] == "stopped_unknown" else 0