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,26 @@
1
+ """The replay core: run the model once, record the exact action path, replay it with no model, heal as a diff.
2
+
3
+ One engine, two front ends (FEATURE-BACKLOG v2, item 1):
4
+
5
+ - **Checks** (`mobster test`): a passing Smart run records its actions with their tree anchors; later runs replay
6
+ them through WebDriverAgent with no model call. A step whose screen or target changed is healed by Smart for that
7
+ step only, and the change is written as a reviewable diff (``<check>.heal.yaml`` and ``healed[]`` in
8
+ results.json), never silently. A heal never changes an assertion: the verdict is still read from the tree.
9
+ - **Saved workflows** (the Mac app's Workflows): after the first good run a workflow is *learned* and runs without
10
+ the model. Typed text is never replayed blind (it comes from the workflow's own words, or the model writes it),
11
+ and sends, posts and deletes still stop for approval.
12
+
13
+ Modules:
14
+
15
+ - ``path.py``: the recorded path (steps with anchors) and its validation;
16
+ - ``recorder.py``: records a Smart run's executed actions (the frontier's step observer, plus the screens it read);
17
+ - ``engine.py``: replays a path on a driver: preconditions, actions, approvals, heals and the circuit breaker;
18
+ - ``store.py``: the per-machine cache, keyed by what the path replays (the check or workflow), the app version and
19
+ the iOS major version;
20
+ - ``heal.py``: heal entries, ``.heal.yaml`` files, Accept and Keep failing;
21
+ - ``checks.py``: the check front end (verify/runner.py calls it).
22
+
23
+ The workflow front end is part of Mobster for Mac: a pre-run hook and a run listener (harness_api) on this engine.
24
+
25
+ Nothing here calls a model itself: a heal is a callback the front end supplies.
26
+ """
@@ -0,0 +1,594 @@
1
+ """The check front end: `mobster test` replays a check's recorded path, heals it, and keeps the cache.
2
+
3
+ verify/runner.py's VerifyRun calls three things when a suite runs it with replay on:
4
+
5
+ - ``CheckReplay.recorder()`` while Smart runs: the frontier's actions become a path;
6
+ - ``CheckReplay.run(cancelled)`` instead of Smart when a path is cached: the replay, with Smart healing a step that
7
+ changed (for that step only) unless the run is strict (``--no-heal``);
8
+ - ``CheckReplay.after(result)`` once the verdict is in: what results.json says about the replay (``replay`` and
9
+ ``healed``), and what is kept: a passing run's path goes to the cache, its heals to ``<check>.heal.yaml``.
10
+
11
+ Modes: ``auto`` replays and heals (the default of `mobster test`), ``strict`` replays and fails on a change
12
+ (``--no-heal``), ``fresh`` runs Smart and records (``--fresh``), ``off`` neither replays nor records.
13
+
14
+ The verdict never changes: replayed or not, healed or not, the assertions are read from the tree after the flow.
15
+ """
16
+
17
+ import hashlib
18
+ import time
19
+ from pathlib import Path
20
+
21
+ from .engine import STRONG, Heal, Replayer, match, screen_likeness
22
+ from .heal import HealError
23
+ from .path import describe, new_path, replayable_count
24
+ from .recorder import Recorder, literal_text
25
+ from .store import Cache, env_of, revision
26
+
27
+ MODES = ("auto", "strict", "fresh", "off")
28
+ HEAL_TURNS = 6
29
+ LOOKAHEAD = 4
30
+ # The same recorded step healed this often in one run stops the replay: the screen keeps undoing the heal.
31
+ HEALS_PER_STEP = 2
32
+ NOTICE = ("Mobster is replaying this check's recorded steps with no model. Recorded step {n} was: {action}. On this "
33
+ "screen {why}. Do what that step was for on the screen you see, then carry on with the check. The replay "
34
+ "takes over again as soon as the screen matches a later recorded step.")
35
+ LIVE_NOTICE = ("Mobster is replaying this check's recorded steps with no model. Recorded step {n} ({action}) is one "
36
+ "you do each run: the text it types isn't in the check's steps. Do it now, then carry on with the "
37
+ "check. The replay takes over again as soon as the screen matches the next recorded step.")
38
+
39
+
40
+ def identity(check, binding=None, asset_proof=None):
41
+ """What a check's path depends on: its steps, its app and how it starts; never its expectations."""
42
+ out = {"kind": "check", "steps": list(check.steps), "bundle": check.bundle_id,
43
+ "app": (check.app_path or "").rsplit("/", 1)[-1] or None, "args": list(check.launch_args),
44
+ "env": dict(sorted(check.launch_env.items())), "url": check.open_url, "reset": check.reset}
45
+ business = getattr(check, "business", None)
46
+ if business is not None:
47
+ if binding is None:
48
+ from ..verify.business import load_business_binding
49
+ from ..verify.checks import project_root
50
+ binding = load_business_binding(check, project_root(check.source))
51
+ out["business"] = {"adapter": business.adapter, "version": "mobster.business/1",
52
+ "fixture": business.fixture, "seedSha256": binding.action_digest}
53
+ if getattr(check, "conditions", None):
54
+ out["conditions"] = check.conditions
55
+ seed = getattr(check, "seed", None)
56
+ if getattr(check, "fixture", ()):
57
+ out["fixture"] = {"names": list(check.fixture), "keychain": getattr(seed, "keychain", False),
58
+ "pasteboard": getattr(seed, "pasteboard", "")}
59
+ if asset_proof is not None and (getattr(check, "fixture", ()) or getattr(check, "conditions", None)):
60
+ # Assertion baselines and expected business values do not change the action path.
61
+ out["evidence"] = {name: asset_proof[name] for name in ("seedSha256", "mediaSha256", "pushSha256")}
62
+ return out
63
+
64
+
65
+ def _source_hash(source):
66
+ try:
67
+ return hashlib.sha256(Path(source).read_bytes()).hexdigest() if source else None
68
+ except OSError:
69
+ return None
70
+
71
+
72
+ def _proof(check):
73
+ """Freeze the effective check, including fixture-flattened steps and the current check's assertions."""
74
+ from dataclasses import replace
75
+
76
+ from ..verify.checks import check_to_dict
77
+ seed = getattr(check, "seed", None)
78
+ return {"check": check_to_dict(replace(check, fixture_origin=None)),
79
+ "seed": {"files": [(str(source), destination) for source, destination in
80
+ getattr(seed, "files", ())],
81
+ "keychain": getattr(seed, "keychain", False),
82
+ "pasteboard": getattr(seed, "pasteboard", ""),
83
+ "risky": list(getattr(seed, "risky", ()))},
84
+ "l10n": getattr(check, "l10n", False), "a11yScope": getattr(check, "a11y_scope", "")}
85
+
86
+
87
+ def _saved_check(source):
88
+ from dataclasses import replace
89
+
90
+ from ..devkit import links
91
+ from ..verify.checks import CheckError, load_check, project_root
92
+ from ..verify.fixtures import FixtureError, with_fixtures
93
+ root = project_root(source)
94
+ try:
95
+ check = load_check(source)
96
+ if any(links.OPEN_STEP.match(step) for step in check.steps):
97
+ check = replace(check, steps=links.expand_steps(check.steps, links.discover(root, deep=False)))
98
+ return with_fixtures(check, root)
99
+ except (CheckError, FixtureError, links.LinksError) as error:
100
+ raise ValueError(str(error)) from None
101
+
102
+
103
+ def _same_assertions(actual, saved):
104
+ """Planning may add audits, but cannot remove an assertion from the saved check."""
105
+ return (actual.expect == saved.expect and actual.throughout == saved.throughout
106
+ and actual.somewhere == saved.somewhere and actual.business == saved.business
107
+ and set(saved.audit).issubset(actual.audit)
108
+ and (saved.a11y is None or actual.a11y == saved.a11y or
109
+ (saved.a11y == "warn" and actual.a11y == "strict")))
110
+
111
+
112
+ def cache_dir_for(runs_dir):
113
+ """`.mobster/cache/replay` beside `.mobster/runs` (runs_dir's sibling ``cache/replay``)."""
114
+ from pathlib import Path
115
+ return Path(runs_dir).resolve().parent / "cache" / "replay"
116
+
117
+
118
+ def text_sources(check):
119
+ return list(check.steps) + list(check.launch_args) + list(check.launch_env.values()) + [check.open_url or ""]
120
+
121
+
122
+ def history_line(number, step):
123
+ target = step.get("target") or {}
124
+ what = target.get("label") or step.get("app") or ""
125
+ return f"{number}.0: {step.get('op')} {what[:60]!r}" + (" text=…" if step.get("text") or step.get("param")
126
+ else "") + " -> screen changed (replayed)"
127
+
128
+
129
+ class CheckReplay:
130
+ """Replay state for one VerifyRun."""
131
+
132
+ def __init__(self, run, mode):
133
+ if mode not in MODES:
134
+ raise ValueError(f"replay must be one of {', '.join(MODES)}")
135
+ self.run, self.mode = run, mode
136
+ self.cache = Cache(cache_dir_for(run.runs_dir))
137
+ self.identity = None
138
+ self.path = None
139
+ self.reason = None # why this run didn't replay, in a sentence
140
+ self.result = None
141
+ self.guard = None
142
+ self._recorder = None
143
+ self.heal_cost = 0.0
144
+ self.heal_file = None
145
+ self._looked = False
146
+ self.base_revision = ""
147
+ self.pin = None
148
+ self._pin_error = None
149
+ self._pin_refused = False
150
+ self._pending_heals = 0
151
+ self._legacy_pending = False
152
+ self._source_hash = None
153
+ self._proof = None
154
+ self._run_proof = None
155
+ self._asset_proof = None
156
+
157
+ def env(self):
158
+ from ..verify.smart import is_real_device
159
+ run = self.run
160
+ real = is_real_device(run.manager)
161
+ model = getattr(run.target, "device_type", None)
162
+ if real and model == "iPhone":
163
+ model = None # WDA's fallback is not a physical model identity.
164
+ return env_of(getattr(run.app, "version", None), getattr(run.target, "runtime", None),
165
+ "iphone" if real else "simulator", device_type=model)
166
+
167
+ def refuse_pin(self, reason):
168
+ """Report pre-action repeat refusal even when asset admission precedes cache lookup."""
169
+ self._looked = True
170
+ self._pin_refused = True
171
+ self._pin_error = self.reason = reason
172
+ from ..verify.runner import CouldntRun
173
+ raise CouldntRun("replay", reason)
174
+
175
+ def lookup(self):
176
+ """The cached path to replay, or None (``reason`` says why not). Looked up once per run."""
177
+ if self._looked:
178
+ if self._pin_error:
179
+ from ..verify.runner import CouldntRun
180
+ raise CouldntRun("replay", self._pin_error)
181
+ return self.path
182
+ if self.mode == "off":
183
+ return None
184
+ binding = getattr(self.run, "business_binding", None)
185
+ if getattr(self.run.check, "business", None) is not None:
186
+ from ..verify.business import load_business_binding
187
+ from ..verify.checks import CheckError, project_root
188
+ from ..verify.runner import CouldntRun
189
+ if not self.run.check.source:
190
+ raise CouldntRun("business", "An app-state check must be a saved project check file.")
191
+ try:
192
+ current = load_business_binding(self.run.check, project_root(self.run.check.source))
193
+ except CheckError as error:
194
+ raise CouldntRun("business", str(error),
195
+ "Fix the enrolled app-state fixture, then run again.") from None
196
+ if binding is not None and binding.digest != current.digest:
197
+ raise CouldntRun("business", "The enrolled app-state fixture changed before replay admission.")
198
+ binding = self.run.business_binding = current
199
+ self._source_hash = _source_hash(self.run.check.source)
200
+ self._run_proof = _proof(self.run.check)
201
+ if self.run.check.source:
202
+ try:
203
+ saved = _saved_check(self.run.check.source)
204
+ if not _same_assertions(self.run.check, saved):
205
+ raise ValueError("the saved check's assertions changed after admission")
206
+ self._proof = _proof(saved)
207
+ except (OSError, ValueError) as error:
208
+ self._looked = True
209
+ self.reason = str(error)
210
+ if self.mode == "strict" or self.pin is not None:
211
+ self.refuse_pin(self.reason)
212
+ return None
213
+ else:
214
+ self._proof = self._run_proof
215
+ from . import heal as heals
216
+ try:
217
+ self._asset_proof = heals.proof_assets(self.run.check)
218
+ except heals.HealError as error:
219
+ self._looked = True
220
+ self.reason = f"the check's evidence could not be frozen: {error}"
221
+ if self.mode == "strict" or self.pin is not None:
222
+ self.refuse_pin(self.reason)
223
+ return None
224
+ self.identity = identity(self.run.check, binding, self._asset_proof)
225
+ self._looked = True
226
+ env = self.env()
227
+ path = self.cache.load(self.identity, env)
228
+ self._legacy_pending = bool(path and path.get("pending"))
229
+ self.base_revision = revision(path) if path else ""
230
+ if self.pin is not None:
231
+ pin_env = {**env, "runtime": getattr(self.run.target, "runtime", None)}
232
+ self._pin_error = self.pin.bind(self.identity, pin_env,
233
+ "" if self._legacy_pending else self.base_revision)
234
+ if self._pin_error:
235
+ self.refuse_pin(self._pin_error)
236
+ source = self.run.check.source
237
+ if source:
238
+ from . import heal as heals
239
+ try:
240
+ document = heals.read(source)
241
+ self._pending_heals = len(document["heals"]) if document is not None else 0
242
+ except heals.HealError:
243
+ self._pending_heals = 1
244
+ if self.mode == "fresh":
245
+ self.reason = "--fresh"
246
+ return None
247
+ if self._legacy_pending:
248
+ self.reason = "an older unreviewed heal is in the cache; record again with --fresh"
249
+ return None
250
+ if env["model"] is None:
251
+ self.reason = "the device model is unknown"
252
+ return None
253
+ if path is None:
254
+ others = self.cache.others(self.identity)
255
+ if others:
256
+ was = others[0]
257
+ if was.get("app_version") != env["app_version"]:
258
+ self.reason = (f"the app changed (recorded on {was.get('app_version') or 'an unknown version'}, "
259
+ f"this is {env['app_version'] or 'an unknown version'})")
260
+ elif was.get("ios") != env["ios"]:
261
+ self.reason = (f"iOS changed (recorded on iOS {was.get('ios') or '?'}, this is iOS "
262
+ f"{env['ios'] or '?'})")
263
+ elif was.get("device") != env["device"]:
264
+ self.reason = "it was recorded on another kind of device"
265
+ elif was.get("model") != env["model"]:
266
+ self.reason = (f"the device model changed (recorded on {was.get('model') or 'an unknown model'}, "
267
+ f"this is {env['model'] or 'an unknown model'})")
268
+ else:
269
+ self.reason = "no matching recorded run"
270
+ else:
271
+ self.reason = "no recorded run yet"
272
+ return None
273
+ if self.mode == "strict" or self.pin is not None:
274
+ live = next(((index, step) for index, step in enumerate(path["steps"], 1) if step.get("live")), None)
275
+ if live is not None:
276
+ index, step = live
277
+ self.reason = f"recorded step {index} ({describe(step)}) needs Smart"
278
+ if self.pin is not None:
279
+ self.refuse_pin(f"A fixed replay cannot run: {self.reason}.")
280
+ return None
281
+ if not replayable_count(path):
282
+ self.reason = "every recorded step needs the model"
283
+ return None
284
+ self.path = path
285
+ return path
286
+
287
+ def recorder(self):
288
+ if not self._looked:
289
+ self.lookup() # capture the approved base before Smart spends time acting
290
+ self._recorder = Recorder(text_policy=literal_text(text_sources(self.run.check)))
291
+ return self._recorder
292
+
293
+ def replay(self, cancelled=None):
294
+ """Replay the cached path on the run's driver. Returns engine.Result."""
295
+ run = self.run
296
+ from .. import engines
297
+ from ..verify.smart import check_approver, check_guard
298
+ # Without a key nothing can heal: a changed step fails the check, as with --no-heal.
299
+ self.can_heal = self.mode != "strict" and self.pin is None and not self._pending_heals \
300
+ and bool(run.key or engines.smart_key())
301
+ heal = self._heal if self.can_heal else None
302
+ guard = self.guard = check_guard(run.manager, run.app.bundle_id)
303
+ names = {run.app.bundle_id: run.app.name}
304
+ def emit(event):
305
+ run.event(event)
306
+ if event.get("event") == "replay_step":
307
+ run.record_step(op=event["op"], text=f"Replayed: {event['action']}",
308
+ target={"id": None, "label": event.get("label"), "role": event.get("role")},
309
+ typed=event.get("typed"), changed=True)
310
+ elif event.get("event") == "replay_drift":
311
+ run.progress(f" Step {event['step']} changed: {event['why']}")
312
+
313
+ replayer = Replayer(run.driver, approve=check_approver(run.manager), heal=heal,
314
+ live=None if self.mode == "strict" or self.pin is not None else self._live,
315
+ emit=emit, cancelled=lambda: run._stop.is_set() or bool(cancelled and cancelled()),
316
+ frame=lambda label: run.frame(label), on_commit="deny", app_names=names,
317
+ allows_app=guard.allows_app if guard is not None else None,
318
+ dialogs=self._responder(emit))
319
+ self._steps = self.path["steps"]
320
+ self._healed_at = {}
321
+ try:
322
+ result = replayer.run(self.path)
323
+ finally:
324
+ if guard is not None and guard.left:
325
+ run._left_app = guard.left
326
+ if result.status == "drift" and self._pending_heals:
327
+ result.reason += " Review the pending heal with mobster test heal before healing another change."
328
+ if guard is not None:
329
+ # The final action can itself open another app. Check that screen too, before VerifyRun reads its
330
+ # evidence or captures a verdict frame (there may be no next replay step to check it).
331
+ bundle = getattr(result.snapshot, "bundle_id", None)
332
+ if bundle and bundle != "com.apple.springboard":
333
+ guard.allows_app(bundle)
334
+ run._left_app = guard.left
335
+ self.result = result
336
+ return result
337
+
338
+ def _responder(self, emit):
339
+ """Answers the dialogs a replay meets from the check's ``dialogs``; None when the run has no policy."""
340
+ run = self.run
341
+ if getattr(run, "dialogs", None) is None:
342
+ return None
343
+ from ..verify.dialogs import Responder
344
+ return Responder(run.dialogs, run.driver, emit=emit, log=run.log)
345
+
346
+ def _heal(self, index, step, snapshot, why, done_steps, limit=None):
347
+ self._healed_at[index] = self._healed_at.get(index, 0) + 1
348
+ if self._healed_at[index] > HEALS_PER_STEP:
349
+ return Heal(False, reason=f"step {index + 1} changed again after it healed")
350
+ feedback = NOTICE.format(n=index + 1, action=describe(step), why=why)
351
+ return self._smart(index, done_steps, feedback, start=index, limit=limit)
352
+
353
+ def _live(self, index, step, snapshot, done_steps):
354
+ feedback = LIVE_NOTICE.format(n=index + 1, action=describe(step))
355
+ return self._smart(index, done_steps, feedback, start=index + 1)
356
+
357
+ def _smart(self, index, done_steps, feedback, *, start, limit=None):
358
+ from .. import engines, harness_api, smart_events
359
+ from ..model_clients import cost_usd
360
+ from ..verify.smart import check_approver, smart_request
361
+ run = self.run
362
+ steps = self._steps
363
+ key = run.key or engines.smart_key()
364
+ if not key:
365
+ return Heal(False, reason="Smart has no key, so the step couldn't heal")
366
+ spent = run.cost_usd + self.heal_cost
367
+ budget = float(run.check.max_usd) - spent
368
+ if budget <= 0:
369
+ return Heal(False, reason=f"the check's ${run.check.max_usd:.2f} spend cap was reached", status="budget")
370
+ recorder = Recorder(text_policy=literal_text(text_sources(run.check)))
371
+ rejoin = {}
372
+ candidates = [j for j in range(start, min(len(steps), start + LOOKAHEAD + 1))
373
+ if not steps[j].get("live") and steps[j].get("op") not in ("LAUNCH_APP", "HOME", "WAIT")]
374
+
375
+ def observer(record):
376
+ recorder.observe_step(record)
377
+ screen = recorder.latest
378
+ if screen is None or "at" in rejoin or "breaker" in rejoin:
379
+ return
380
+ for j in candidates:
381
+ ok, _, _ = match(steps[j], screen)
382
+ if ok and screen_likeness(steps[j], screen) >= STRONG:
383
+ rejoin["at"] = j
384
+ return
385
+ if limit is not None and len(recorder.steps) >= limit:
386
+ rejoin["breaker"] = True # the next action would heal more than half the path
387
+
388
+ def stopping():
389
+ return "at" in rejoin or "breaker" in rejoin or run._stop.is_set()
390
+
391
+ client = engines.build_client(key=key)
392
+ events = []
393
+ unknown = []
394
+
395
+ def on_event(event):
396
+ run.event({**event, "heal": index + 1} if isinstance(event, dict) else event)
397
+ if (isinstance(event, dict) and event.get("event") == "frontier_failed"
398
+ and event.get("outcome") == "unknown"):
399
+ unknown.append(event) # an action that may have landed (frontier/act.py:_execute)
400
+ if isinstance(event, dict) and event.get("event") == "frontier_action":
401
+ events.append(event)
402
+ op, label = event.get("operation") or "", event.get("target_label") or ""
403
+ run.record_step(op=op,
404
+ text=f"Healed: {smart_events.step_text(op, label, {run.app.bundle_id: run.app.name},
405
+ text=event.get('text'))}",
406
+ target={"id": None, "label": label or None, "role": None},
407
+ typed=event.get("text") if op in smart_events.TYPING else None,
408
+ changed=event.get("changed"))
409
+
410
+ try:
411
+ agent = engines.build_frontier(recorder.wrap(run.driver), client, apps={run.app.bundle_id: run.app.name},
412
+ emit=on_event, max_cost_usd=budget, approve=check_approver(run.manager),
413
+ cancelled=stopping, guard=self.guard, dialogs=True,
414
+ dialog_policy=getattr(run, "dialogs", None),
415
+ step_observer=observer, max_steps=HEAL_TURNS,
416
+ max_seconds=max(20.0, float(run.check.max_seconds) - (time.monotonic()
417
+ - run._t0)))
418
+ initial = harness_api.InitialState(
419
+ history=tuple(history_line(n, s) for n, s in enumerate(done_steps, 1))[-20:], feedback=feedback,
420
+ reason="a replayed check step changed on screen")
421
+ outcome = agent.run(smart_request(run.app.name, run.check.steps), initial=initial)
422
+ finally:
423
+ if self.guard is not None and self.guard.left:
424
+ run._left_app = self.guard.left
425
+ engines.release_client(client)
426
+ cost = cost_usd(getattr(client, "model", ""), outcome.get("usage") or getattr(client, "usage", {}) or {}) or 0
427
+ self.heal_cost += cost
428
+ status = outcome.get("status")
429
+ if unknown and status != "done" and "at" not in rejoin:
430
+ # An action's outcome is unknown and the heal never settled it: stop, so no retry sends it again.
431
+ reason = str(outcome.get("reason") or outcome.get("answer") or status or "")[:300]
432
+ return Heal(False, steps=list(recorder.steps), cost_usd=cost, status="unknown_outcome", reason=reason)
433
+ if "breaker" in rejoin:
434
+ return Heal(False, steps=list(recorder.steps), cost_usd=cost, status="breaker",
435
+ reason="more than half the recorded steps would heal")
436
+ if "at" in rejoin:
437
+ return Heal(True, steps=list(recorder.steps), resume_at=rejoin["at"], cost_usd=cost)
438
+ if status == "done":
439
+ return Heal(True, steps=list(recorder.steps), done=True, cost_usd=cost, status=status)
440
+ reason = str(outcome.get("reason") or outcome.get("answer") or status or "")[:300]
441
+ return Heal(False, steps=list(recorder.steps), cost_usd=cost, status=status,
442
+ reason=f"Smart couldn't heal step {index + 1} ({status}{': ' + reason if reason else ''})")
443
+
444
+ # -- after the verdict -------------------------------------------------------------------------------------
445
+
446
+ def summary(self):
447
+ """results.json's ``replay`` for this run."""
448
+ result = self.result
449
+ recorded = self._recorder.steps if self._recorder is not None else []
450
+ approved = self.base_revision if self.path is not None else None
451
+ fixed = bool(self.pin is not None and approved and self.mode != "fresh")
452
+ if result is None:
453
+ if (self.mode == "strict" or self._pin_refused) and self._looked and self.path is None:
454
+ return {"mode": "unavailable", "status": "unavailable", "reason": self.reason, "steps": 0,
455
+ "replayed": 0, "healed": 0, "live": 0, "costUsd": 0, "saved": False, "cache": None,
456
+ "pendingHeals": 0, "approvedRevision": None, "fixed": False}
457
+ return {"mode": "live", "reason": self.reason, "steps": len(recorded), "replayed": 0, "healed": 0,
458
+ "live": len(recorded), "costUsd": None, "saved": False, "cache": None,
459
+ "pendingHeals": 0, "approvedRevision": None, "fixed": False}
460
+ mode = "healed" if result.heals or result.live else "replayed"
461
+ return {"mode": mode, "status": result.status, "reason": result.reason or None,
462
+ "steps": len(self.path["steps"]), "replayed": result.replayed, "healed": len(result.heals),
463
+ "live": result.live, "seconds": result.seconds, "costUsd": round(result.cost_usd, 5),
464
+ "saved": False, "cache": None, "pendingHeals": self._pending_heals,
465
+ "approvedRevision": approved, "fixed": fixed}
466
+
467
+ def after(self, result):
468
+ """Add ``replay`` and ``healed`` to the run's ``result``, and keep what a passing run proved."""
469
+ info = self.summary()
470
+ healed = [dict(heal) for heal in (self.result.heals if self.result is not None else ())]
471
+ passed = result.get("verdict") == "passed"
472
+ refreshing_a11y = bool(getattr(self.run.check, "a11y_refresh", False))
473
+ # A trial with the saved check's steps and app name shares its cache file, so saving replaces the approved path.
474
+ draft_trial = bool(getattr(self.run.check, "draft_trial", False))
475
+ # A new heal cannot become an approved path during baseline replacement, even when the only failed
476
+ # assertion is a new accessibility finding (which baseline refresh otherwise accepts).
477
+ unstaged = bool(refreshing_a11y and self.result is not None and self.result.status == "done" and healed
478
+ and result.get("verdict") in ("passed", "failed"))
479
+ try:
480
+ if self.result is None:
481
+ if passed and not refreshing_a11y and not draft_trial and self.mode != "off" \
482
+ and self._recorder is not None and self._recorder.steps:
483
+ info.update(self._save_new())
484
+ elif self.result.status == "done" and passed:
485
+ if not refreshing_a11y:
486
+ info.update(self._save_replayed(healed))
487
+ unstaged = bool(healed and not info.get("pendingHeals"))
488
+ # A failed or uncertain action never revokes the approved path.
489
+ except Exception as error:
490
+ self.run.log(f"replay cache not written: {type(error).__name__}: {error}")
491
+ unstaged = unstaged or bool(passed and healed)
492
+ if unstaged:
493
+ from ..verify.verdict import EXIT_CODES
494
+ message = ("The check healed during accessibility baseline refresh; its approved path and baseline were "
495
+ "kept." if refreshing_a11y else
496
+ "The check's assertions held, but its healed path could not be staged for review.")
497
+ fix = ("Run mobster test without --update-a11y-baseline to review the heal, then refresh the baseline "
498
+ "in a separate run." if refreshing_a11y else
499
+ "Inspect the check and replay cache, then run mobster test --fresh.")
500
+ info.update(status="proposal_unavailable", reason=message, saved=False, pendingHeals=0)
501
+ result.update(verdict="couldnt_run", exit_code=EXIT_CODES["couldnt_run"], summary=message,
502
+ reason={"class": "replay", "message": message, "fix": fix})
503
+ result["replay"] = info
504
+ result["healed"] = healed
505
+ if self.heal_file is not None:
506
+ result["healDiff"] = str(self.heal_file)
507
+ return result
508
+
509
+ def _save_new(self):
510
+ if self.pin is not None:
511
+ return {} # Repetitions observe one frozen baseline; they do not publish a new one.
512
+ if not self._looked:
513
+ self.lookup()
514
+ if self.identity is None or self._asset_proof is None:
515
+ self.run.log("the check's evidence was not frozen; the replay path was not saved")
516
+ return {}
517
+ steps = self._recorder.steps
518
+ env = self.env()
519
+ if env["model"] is None:
520
+ return {} # Without a model this recording cannot safely replay on the next acquired device.
521
+ path = new_path(steps, kind="check", identity=self.identity, env=env, source_run=self.run.run_id)
522
+ with self.cache.locked():
523
+ current = self.cache.load(self.identity, env)
524
+ if (revision(current) if current else "") != self.base_revision:
525
+ self.run.log("replay cache changed during Smart; the new path was not saved")
526
+ return {}
527
+ if self._legacy_pending and self.mode != "fresh":
528
+ self.run.log("an older unreviewed heal remains; use --fresh to replace its path")
529
+ return {}
530
+ file = self.cache.save(path)
531
+ self._drop_superseded_heals(file.name)
532
+ return {"saved": True, "cache": file.name, "steps": len(steps)}
533
+
534
+ def _drop_superseded_heals(self, cache_name):
535
+ """A new recording replaces the steps a waiting heal diff described: that diff goes with them."""
536
+ source = self.run.check.source
537
+ if not source:
538
+ return
539
+ from . import heal as heals
540
+ if heals.discard(source, self.cache.directory, cache_name):
541
+ self.run.log(f"{heals.heal_file(source).name} described steps recorded again; removed it")
542
+
543
+ def _save_replayed(self, healed):
544
+ if not healed or self.pin is not None:
545
+ return {}
546
+ old = self.path
547
+ path = dict(old)
548
+ path["steps"] = list(self.result.steps)
549
+ path["strict"] = list(self.result.strict)
550
+ source = self.run.check.source
551
+ if not source:
552
+ self.run.log("the healed path has no check file for review; approved steps were kept")
553
+ return {}
554
+ try:
555
+ current_check = _saved_check(source)
556
+ except (OSError, ValueError):
557
+ current_check = None
558
+ if (_source_hash(source) != self._source_hash or current_check is None
559
+ or _proof(self.run.check) != self._run_proof
560
+ or _proof(current_check) != self._proof):
561
+ self.run.log("the check changed during healing; the approved path was kept without staging a proposal")
562
+ return {}
563
+ from . import heal as heals
564
+ proof = self._asset_proof
565
+ if proof is None:
566
+ self.run.log("the check's evidence was not frozen before replay; proposal was not staged")
567
+ return {}
568
+ try:
569
+ current_proof = heals.proof_assets(current_check, include_a11y=proof["a11ySha256"] is not None)
570
+ except HealError:
571
+ current_proof = None
572
+ if current_proof != proof:
573
+ self.run.log("the check's evidence changed during healing; proposal was not staged")
574
+ return {}
575
+ with self.cache.locked():
576
+ current = self.cache.load(self.identity, self.env())
577
+ if current is None or revision(current) != self.base_revision:
578
+ self.run.log("approved replay changed during healing; proposal was not staged")
579
+ return {}
580
+ app = self.run.app
581
+ self.heal_file = heals.record(source, healed, run_id=self.run.run_id,
582
+ cache_file=self.cache._file(self.identity, self.env()),
583
+ candidate=path, base_revision=self.base_revision,
584
+ check_sha256=self._source_hash,
585
+ effective_sha256=heals.effective_digest(current_check), proof=proof,
586
+ app={"bundle": app.bundle_id, "version": app.version or None},
587
+ ios=self.env()["ios"], device=self.env()["device"])
588
+ for item in healed:
589
+ item["diff"] = str(self.heal_file)
590
+ return {"saved": False, "cache": self.cache._file(self.identity, self.env()).name,
591
+ "pendingHeals": len(healed)}
592
+
593
+
594
+ __all__ = ["CheckReplay", "identity", "cache_dir_for", "MODES"]