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,102 @@
1
+ """``mobster run --dry-run`` and ``"dryRun": true`` on POST /api/runs: what a task would do, read from its words alone.
2
+
3
+ Nothing starts: no run, no journal entry, no phone, no model call, no count against a policy's daily cap. The plan
4
+ names the apps the task would open, the outward acts its words ask for and whether Mobster would ask about each one
5
+ under the person's permission mode and the project's policy, and the estimated cost. The screens are not listed:
6
+ Mobster reads them from the phone as the task goes, and a dry run never touches it.
7
+ """
8
+
9
+ import re
10
+
11
+ from . import costs
12
+ from .app_choice import infer_app
13
+ from .permissions import gate as permission_gate
14
+ from .permissions import policy as P
15
+ from .permissions import project_policy
16
+ from .task_policy import COMMIT_CONTROL, asked_part
17
+
18
+ MAX_ACTS = 12
19
+ _NOUN_BEFORE = re.compile(r"\b(?:the|a|an|best|my|your|its|top|new|each|every|one-line|last|\d+)\s*$", re.I)
20
+
21
+
22
+ def _acts(goal):
23
+ """[(word, act)] the task's own words ask for, once each, in order; a prohibition ("don't send") asks for none."""
24
+ from .harness_api import commit_act
25
+ from .task_policy import PROHIBITION_SENTENCE
26
+ # "Draft a reply. Don't send anything." rules out every act of the kind it names.
27
+ ruled_out = {kind for sentence in PROHIBITION_SENTENCE.findall(goal) for found in COMMIT_CONTROL.finditer(sentence)
28
+ for kind in project_policy.kinds_of("commit", commit_act("TAP", found.group(0), None, None))}
29
+ found, seen = [], set()
30
+ asked = asked_part(goal)
31
+ for match in COMMIT_CONTROL.finditer(asked):
32
+ if _NOUN_BEFORE.search(asked[:match.start()]):
33
+ continue # "the best post", "needs a reply": the word names a thing
34
+ word = " ".join(match.group(0).casefold().split())
35
+ act = commit_act("TAP", word, None, None) or word
36
+ if act not in seen and not project_policy.kinds_of("commit", act) & ruled_out:
37
+ seen.add(act)
38
+ found.append((word, act))
39
+ return found[:MAX_ACTS]
40
+
41
+
42
+ def _apps(goal, apps):
43
+ """The apps the task would open: the one its words name, then any other app it names."""
44
+ chosen = infer_app(goal, apps)
45
+ named = [app for app in sorted(apps, key=lambda a: -len(a["name"]))
46
+ if len(app["name"]) >= 4 and app is not chosen and re.search(
47
+ rf"(?<![\w-]){re.escape(app['name'])}(?![\w-])", goal, re.I)]
48
+ return [app for app in [chosen, *named] if app]
49
+
50
+
51
+ def plan(goal, *, apps, mode=P.DEFAULT_MODE, policy=None, profile=None, origin=None, estimator=None, now=None):
52
+ """The dry run of ``goal`` as JSON. ``apps``: the phone's apps (catalog shape). ``estimator(goal, names)``: the cost
53
+ range, default ``costs.smart_estimate``. Reads the policy's counts and never writes them."""
54
+ goal = " ".join(str(goal or "").replace("’", "'").split())
55
+ chosen = _apps(goal, apps)
56
+ names = [app["name"] for app in apps]
57
+ cost = (estimator or costs.smart_estimate)(goal, names)
58
+ mode = P.effective_mode(mode, None, origin)
59
+ bundle = chosen[0]["bundleId"] if chosen else ""
60
+ asks = []
61
+ for word, act in _acts(goal):
62
+ kinds = project_policy.kinds_of("commit", act)
63
+ by_mode = permission_gate.decision_for("commit", mode, existing=True) == "ask"
64
+ by_policy = policy.asks(kinds, bundle, profile, now=now) if policy is not None else None
65
+ if by_mode or by_policy:
66
+ asks.append({"act": act, "words": word, "kinds": sorted(kinds), "bundleId": bundle or None,
67
+ "why": "your permission mode asks" if by_mode and not by_policy else by_policy,
68
+ "by": "policy" if by_policy and not by_mode else "mode"})
69
+ return {"goal": goal, "dryRun": True, "sent": False, "apps": [{"id": a["id"], "name": a["name"],
70
+ "bundleId": a["bundleId"]} for a in chosen],
71
+ "permissionMode": mode, "asks": asks, "policy": _policy(policy),
72
+ "cost": {"lowUsd": cost.get("lowUsd"), "highUsd": cost.get("highUsd"), "model": cost.get("model"),
73
+ "billing": cost.get("billing") or "api"},
74
+ "screens": "Mobster reads each screen from the phone as the task goes. A dry run never touches it."}
75
+
76
+
77
+ def _policy(policy):
78
+ if policy is None:
79
+ return None
80
+ return {"file": str(policy.path) if policy.path else None, "rules": len(policy.rules), "error": policy.error}
81
+
82
+
83
+ def lines(result):
84
+ """The plan as text for a terminal."""
85
+ out = [f"Dry run: {result['goal']}", "Nothing was sent to your iPhone."]
86
+ apps = ", ".join(a["name"] for a in result["apps"])
87
+ out.append(f"Apps it would open: {apps}." if apps else "It names no app, so it starts on the Home Screen.")
88
+ if result["asks"]:
89
+ out.append("It would ask before:")
90
+ out += [f" {a['act']}: {a['why']}" for a in result["asks"]]
91
+ else:
92
+ out.append("It would ask about nothing its words name. Mobster still asks about any step that sends, buys, "
93
+ "posts or deletes.")
94
+ cost = result["cost"]
95
+ if cost["billing"] == "plan":
96
+ out.append("Estimated cost: nothing per call, it runs on your plan.")
97
+ elif cost["lowUsd"] is not None:
98
+ out.append(f"Estimated cost: ${cost['lowUsd']:.2f} to ${cost['highUsd']:.2f}.")
99
+ if result["policy"] and result["policy"]["error"]:
100
+ out.append(f"Your project policy can't be read, so every act asks: {result['policy']['error']}")
101
+ out.append(result["screens"])
102
+ return out
@@ -0,0 +1,83 @@
1
+ """One run's ledger of side effects: one whose outcome is acknowledged or unknown is never sent again."""
2
+
3
+ # Operations that can leave a durable effect on the device.
4
+ PERSISTENT_OPERATIONS = frozenset({"TAP", "TYPE", "TYPE_SUBMIT", "SET_TEXT", "INCREMENT", "DECREMENT", "SUBMIT"})
5
+
6
+ # Proven refusals and outcomes settle/resolve reconciled; any other outcome, unfamiliar ones too, blocks a replay.
7
+ RELEASING_OUTCOMES = frozenset({
8
+ "not_dispatched",
9
+ "failed_pre_dispatch",
10
+ "observed_absent", # a fresh look after an unknown outcome showed the action did not happen (settle)
11
+ "resolved", # the same effect, sent again after that look or with the person's OK, came back answered
12
+ })
13
+
14
+ # A refusal before anything was sent. It releases a slot nothing was sent through, never one an earlier send holds.
15
+ PRE_DISPATCH_REFUSALS = frozenset({"not_dispatched", "failed_pre_dispatch"})
16
+
17
+ # Times a look showing nothing happened can release one unknown outcome; a retry unknown again stays held.
18
+ RETRY_LIMIT = 1
19
+
20
+
21
+ class EffectLedger:
22
+ def __init__(self):
23
+ self._effects = {}
24
+
25
+ @staticmethod
26
+ def _key(operation, target_id, text=None, effect=None):
27
+ if effect is not None:
28
+ return effect
29
+ return (operation, target_id, text)
30
+
31
+ def would_duplicate(self, operation, target_id, text=None, *, effect=None):
32
+ """Whether this effect was already attempted on this run with an acknowledged or unknown outcome."""
33
+ if operation not in PERSISTENT_OPERATIONS:
34
+ return False
35
+ record = self._effects.get(self._key(operation, target_id, text, effect))
36
+ return record is not None and record["outcome"] not in RELEASING_OUTCOMES
37
+
38
+ def record_outcome(self, operation, target_id, text=None, outcome="acknowledged", *,
39
+ effect=None, context=None):
40
+ """Record a dispatch outcome: only a pre-dispatch refusal releases the slot; unknown is never retried."""
41
+ if operation not in PERSISTENT_OPERATIONS:
42
+ return
43
+ if not isinstance(outcome, str) or not outcome:
44
+ raise ValueError("Effect outcome must be a non-empty string")
45
+ key = self._key(operation, target_id, text, effect)
46
+ record = self._effects.get(key)
47
+ if (record is not None and outcome in PRE_DISPATCH_REFUSALS
48
+ and record["outcome"] not in RELEASING_OUTCOMES):
49
+ return # a later refusal says nothing about the earlier attempt, which may have run
50
+ if record is None:
51
+ record = {"operation": operation, "target_id": target_id, "text": text}
52
+ self._effects[key] = record
53
+ record["outcome"] = outcome
54
+ if context is not None:
55
+ record["context"] = context
56
+
57
+ def lookup(self, operation, target_id, text=None, *, effect=None):
58
+ """A copy of this logical effect's record (operation, outcome, context...), or None."""
59
+ record = self._effects.get(self._key(operation, target_id, text, effect))
60
+ return dict(record) if record is not None else None
61
+
62
+ def held(self, match):
63
+ """Keys of the effects still held back (a blocking outcome) whose record ``match(key, record)`` accepts."""
64
+ return [key for key, record in self._effects.items()
65
+ if record["outcome"] not in RELEASING_OUTCOMES and match(key, dict(record))]
66
+
67
+ def settle(self, operation, target_id, text=None, *, effect=None, happened):
68
+ """Settle an unknown outcome from a look: happened holds it, not happened releases it RETRY_LIMIT times."""
69
+ record = self._effects.get(self._key(operation, target_id, text, effect))
70
+ if record is None or record["outcome"] != "unknown":
71
+ return record["outcome"] if record is not None else None
72
+ if happened:
73
+ record["outcome"] = "observed_change"
74
+ elif record.get("retries", 0) < RETRY_LIMIT:
75
+ record["outcome"] = "observed_absent"
76
+ record["retries"] = record.get("retries", 0) + 1
77
+ return record["outcome"]
78
+
79
+ def resolve(self, operation, target_id, text=None, *, effect=None):
80
+ """The same effect was sent again and came back answered: nothing is unknown, and its retries start over."""
81
+ record = self._effects.get(self._key(operation, target_id, text, effect))
82
+ if record is not None and record["outcome"] in ("unknown", "observed_absent"):
83
+ record["outcome"], record["retries"] = "resolved", 0
@@ -0,0 +1,378 @@
1
+ """One answer to "is the engine OK?": the state every surface shows. Its words and fix are a row of ``states.ROWS``."""
2
+
3
+ import threading
4
+ import time
5
+
6
+ from .states import (
7
+ DOWN,
8
+ FROZEN,
9
+ HELPER_DOWN,
10
+ LIVE_OFF,
11
+ MODEL_UNAVAILABLE,
12
+ NO_PHONE,
13
+ READY,
14
+ RESTARTING,
15
+ ROWS,
16
+ SLOW,
17
+ STOPPING,
18
+ WAITING_FOR_PHONE,
19
+ fix_public,
20
+ row,
21
+ words,
22
+ )
23
+
24
+ # The state said first when several stand, most blocking first. A state not in the list never outranks one that is.
25
+ PRIORITY = (STOPPING, MODEL_UNAVAILABLE, LIVE_OFF, NO_PHONE, HELPER_DOWN, WAITING_FOR_PHONE)
26
+
27
+ TOPIC = "engine"
28
+ EVENT = "engine_health"
29
+
30
+ # Worst-case detection (device_manager.PROBE_TICK + PROBE_INTERVAL + wda_ready + PROBE_TIMEOUT + SAMPLE_S) must stay <=
31
+ # NAMED_WITHIN_S; test_engine_health asserts it.
32
+ SAMPLE_S = 1.0
33
+ SLOW_AFTER_S = 3.0
34
+ FROZEN_AFTER_S = 8.0
35
+ NAMED_WITHIN_S = 10.0
36
+
37
+ ACTION_UNKNOWN_STEP = ("An action was on its way when the phone went away. Mobster looks at the screen before it goes "
38
+ "on and doesn't send that action again.")
39
+
40
+ # The causes a helper log line or a phone state can name (runner_hints.RUNNER_HINTS).
41
+ PHONE_CAUSES = frozenset(r.cause for r in ROWS if r.state == WAITING_FOR_PHONE and "app" in r.surfaces)
42
+ TASK_WAIT_CAUSES = {"lock_screen": "locked", "passcode_keypad": "locked", "unplugged": "unplugged",
43
+ "wifi_lost": "wifi_lost"}
44
+
45
+
46
+ def task_wait(kind, phone=None):
47
+ """(need, sentence) for what a task waits on (``handoff_waiting``'s kind), or None for a kind that has no words."""
48
+ cause = TASK_WAIT_CAUSES.get(kind)
49
+ if cause is None:
50
+ return None
51
+ _message, step, need = words(WAITING_FOR_PHONE, cause, phone)
52
+ return need, step
53
+
54
+
55
+ class Condition:
56
+ """One thing that is not as it should be."""
57
+
58
+ __slots__ = ("state", "cause", "message", "next_step", "need", "fix", "since", "device", "device_name", "task",
59
+ "midrun", "action_unknown", "seconds")
60
+
61
+ def __init__(self, state, cause, *, since, phone=None, message=None, device=None, task=None, midrun=False,
62
+ action_unknown=False, seconds=None):
63
+ self.state, self.cause, self.since = state, cause, since
64
+ self.message, self.next_step, self.need = words(state, cause, phone, message)
65
+ self.fix = row(cause).fix
66
+ if action_unknown:
67
+ self.next_step = f"{self.next_step} {ACTION_UNKNOWN_STEP}"
68
+ self.device, self.device_name = device, phone
69
+ self.task, self.midrun, self.action_unknown, self.seconds = task, midrun, action_unknown, seconds
70
+
71
+ def public(self):
72
+ value = {"state": self.state, "cause": self.cause, "message": self.message, "next_step": self.next_step,
73
+ "fix": fix_public(self.fix), "since": self.since}
74
+ if self.need:
75
+ value["need"] = self.need
76
+ if self.device:
77
+ value["device"] = self.device
78
+ if self.device_name:
79
+ value["device_name"] = self.device_name
80
+ if self.task:
81
+ value.update(task=self.task, midrun=self.midrun)
82
+ if self.action_unknown:
83
+ value["action_unknown"] = True
84
+ if self.seconds:
85
+ value["wait_seconds"] = self.seconds
86
+ return value
87
+
88
+
89
+ class EngineHealth:
90
+ """The newest condition of each source, the one to say first, and a publish when it changes."""
91
+
92
+ def __init__(self, clock=time.time, publish=None, boot=None):
93
+ self.clock = clock
94
+ self.publish = publish
95
+ self.boot = boot
96
+ self.lock = threading.Lock()
97
+ self.conditions = {}
98
+ self.sampled_at = None
99
+ self._said = None
100
+
101
+ def report(self, source, state, cause, *, phone=None, message=None, device=None, task=None, midrun=False,
102
+ action_unknown=False, seconds=None):
103
+ """``source`` is in this state now, for this cause. The same state and cause again keeps their ``since``."""
104
+ with self.lock:
105
+ found = self.conditions.get(source)
106
+ same = found is not None and (found.state, found.cause) == (state, cause)
107
+ self.conditions[source] = Condition(
108
+ state, cause, since=found.since if same else self.clock(), phone=phone, message=message,
109
+ device=device, task=task, midrun=midrun, action_unknown=action_unknown, seconds=seconds)
110
+ self._publish_if_changed()
111
+
112
+ def clear(self, source):
113
+ """``source`` is fine now."""
114
+ with self.lock:
115
+ self.conditions.pop(source, None)
116
+ self._publish_if_changed()
117
+
118
+ def clear_prefix(self, prefix, keep=()):
119
+ """Every source starting with ``prefix`` is fine now."""
120
+ with self.lock:
121
+ for source in [s for s in self.conditions if s.startswith(prefix) and s not in keep]:
122
+ del self.conditions[source]
123
+ self._publish_if_changed()
124
+
125
+ def sampled(self):
126
+ """The sampler finished a pass (its time is the snapshot's ``observed_at``)."""
127
+ with self.lock:
128
+ self.sampled_at = self.clock()
129
+
130
+ def _ordered(self):
131
+ rank = {state: index for index, state in enumerate(PRIORITY)}
132
+ return sorted(self.conditions.items(), key=lambda item: (rank.get(item[1].state, len(rank)), item[1].since,
133
+ item[0]))
134
+
135
+ def snapshot(self):
136
+ """The state to say first (``ready`` when nothing is wrong), with every standing issue and the liveness."""
137
+ with self.lock:
138
+ ordered = self._ordered()
139
+ now = self.clock()
140
+ observed = self.sampled_at if self.sampled_at is not None else now
141
+ if ordered:
142
+ first = ordered[0][1].public()
143
+ else:
144
+ first = {"state": READY, "cause": None, "message": "Ready.", "next_step": None,
145
+ "fix": None, "since": observed}
146
+ return {**first, "observed_at": observed, "issues": [c.public() | {"source": s} for s, c in ordered],
147
+ "liveness": {"boot": self.boot, "sample_s": SAMPLE_S, "slow_after_s": SLOW_AFTER_S,
148
+ "frozen_after_s": FROZEN_AFTER_S}}
149
+
150
+ def _publish_if_changed(self):
151
+ with self.lock:
152
+ said = tuple((s, c.state, c.cause, c.action_unknown) for s, c in self._ordered())
153
+ if said == self._said:
154
+ return
155
+ self._said = said
156
+ if callable(self.publish):
157
+ self.publish(TOPIC, {"event": EVENT, **self.snapshot()})
158
+
159
+
160
+ def cause_of(snapshot, source):
161
+ """The cause ``source`` stands in in a snapshot (``phone:primary``, ``model``), or None when it is fine."""
162
+ return next((issue["cause"] for issue in snapshot.get("issues", ()) if issue.get("source") == source), None)
163
+
164
+
165
+ def classify_phone(reading):
166
+ """(state, cause) for what a phone's helper and link say, or None when ready; a None fact is unknown,
167
+ never a reason."""
168
+ if reading.get("exposed"):
169
+ return HELPER_DOWN, "exposed"
170
+ if reading.get("expired"):
171
+ return HELPER_DOWN, "signature_expired"
172
+ if not reading.get("chosen"):
173
+ return NO_PHONE, "none_chosen"
174
+ if reading.get("wifi"):
175
+ if reading.get("wifi_lost"):
176
+ return WAITING_FOR_PHONE, "wifi_lost"
177
+ elif reading.get("usb") is False:
178
+ return WAITING_FOR_PHONE, "unplugged"
179
+ if not reading.get("built"):
180
+ return HELPER_DOWN, "not_built"
181
+ runner, hint = reading.get("runner"), reading.get("hint")
182
+ if runner == "running":
183
+ if reading.get("locked") is True:
184
+ return WAITING_FOR_PHONE, "locked"
185
+ if reading.get("responsive") is False:
186
+ return WAITING_FOR_PHONE, "not_responding"
187
+ return None
188
+ if reading.get("holding") == "unlock":
189
+ return WAITING_FOR_PHONE, "locked"
190
+ if hint in PHONE_CAUSES:
191
+ return WAITING_FOR_PHONE, hint
192
+ if runner == "failed":
193
+ return HELPER_DOWN, "failing"
194
+ if runner == "starting":
195
+ return WAITING_FOR_PHONE, "helper_restarting"
196
+ return HELPER_DOWN, "stopped"
197
+
198
+
199
+ def classify_model(available, reason, credit_reasons=(), has_key=True):
200
+ """(state, cause, message) for whether Smart's model can run, or None when it can."""
201
+ if available:
202
+ return None
203
+ if not has_key:
204
+ return MODEL_UNAVAILABLE, "no_key", reason
205
+ return MODEL_UNAVAILABLE, "no_credit" if reason in credit_reasons else "key_rejected", reason
206
+
207
+
208
+ def action_in_flight(events):
209
+ """Whether the newest decision of a run sent an action that has no result yet; an unreadable decision counts."""
210
+ done = {"frontier_action", "frontier_failed"}
211
+ results = 0
212
+ for event in reversed(events[-400:]):
213
+ name = event.get("event")
214
+ if name in done:
215
+ results += 1
216
+ elif name == "frontier_decision":
217
+ ops = event.get("ops")
218
+ if not isinstance(ops, list):
219
+ return True
220
+ acting = sum(op not in (None, "DONE", "BLOCKED", "WAIT") for op in ops)
221
+ return acting > results
222
+ return False
223
+
224
+
225
+ def task_condition(run_id, wait, events, phone=None, device=None):
226
+ """The ``Condition`` arguments of a task waiting on the phone (``run.phone_wait``'s dict)."""
227
+ cause = TASK_WAIT_CAUSES.get(wait.get("kind"))
228
+ if cause is None:
229
+ return None
230
+ return dict(state=WAITING_FOR_PHONE, cause=cause, phone=phone, device=device, task=run_id,
231
+ midrun=wait.get("midrun") is True, action_unknown=wait.get("midrun") is True
232
+ and action_in_flight(events),
233
+ seconds=wait.get("seconds"))
234
+
235
+
236
+ def from_silence(age_s=None, *, refused=False, boot=None, seen_boot=None, thresholds=None):
237
+ """The state a process that can't say it for itself is in, from the asker's side."""
238
+ slow, frozen = (thresholds or (SLOW_AFTER_S, FROZEN_AFTER_S))
239
+ if boot is not None and seen_boot is not None and boot != seen_boot:
240
+ state, cause = RESTARTING, "restarted"
241
+ elif refused:
242
+ state, cause = DOWN, "not_running"
243
+ elif age_s is None or age_s < slow:
244
+ return None
245
+ elif age_s < frozen:
246
+ state, cause = SLOW, "slow_answer"
247
+ else:
248
+ state, cause = FROZEN, "no_answer"
249
+ message, step, _need = words(state, cause)
250
+ return {"state": state, "cause": cause, "message": message, "next_step": step, "fix": fix_public(row(cause).fix)}
251
+
252
+
253
+ def read_from_app(url=None, *, origin="cli", timeout=4.0, connect=None):
254
+ """The Mac app's engine state as an asker sees it: its snapshot, or what its silence is (``from_silence``: frozen
255
+ when it accepted the connection and never answered, down when the connection was refused). None when it can't be
256
+ asked (it refused the token). ``connect`` replaces ``Client.connect`` in tests."""
257
+ from .threads.client import Client, NoService, ServiceError
258
+ try:
259
+ return (connect or Client.connect)(url=url, origin=origin, timeout=timeout).status.get("engine")
260
+ except NoService as silent:
261
+ return from_silence(FROZEN_AFTER_S if silent.frozen else None, refused=not silent.frozen)
262
+ except ServiceError:
263
+ return None
264
+
265
+
266
+ class Sampler:
267
+ """Reads a ``Runtime``'s detectors (model, live runs, phones, waiting tasks) into its ``EngineHealth``."""
268
+
269
+ def __init__(self, runtime, interval=SAMPLE_S):
270
+ self.runtime, self.interval = runtime, interval
271
+ self.stop = threading.Event()
272
+ self.thread = None
273
+ self.pass_lock = threading.Lock()
274
+
275
+ @property
276
+ def health(self):
277
+ return self.runtime.engine_health
278
+
279
+ def start(self):
280
+ if self.thread is None:
281
+ self.thread = threading.Thread(target=self._loop, name="mobster-engine-health", daemon=True)
282
+ self.thread.start()
283
+
284
+ def close(self):
285
+ self.stop.set()
286
+ if self.thread is not None and self.thread is not threading.current_thread():
287
+ self.thread.join(2.0)
288
+
289
+ def _loop(self):
290
+ while not self.stop.is_set():
291
+ self.sample()
292
+ self.stop.wait(self.interval)
293
+
294
+ def snapshot(self):
295
+ if self.thread is None or not self.thread.is_alive():
296
+ self.sample()
297
+ return self.health.snapshot()
298
+
299
+ def sample(self):
300
+ """One pass. A detector that raises leaves its source as it was and is logged:
301
+ a failed read never says ready."""
302
+ if not self.pass_lock.acquire(blocking=False):
303
+ return # a pass is running (the thread's, or another asker's): its answer is the answer
304
+ try:
305
+ for part in (self._engine, self._model, self._phones, self._tasks):
306
+ try:
307
+ part()
308
+ except Exception: # noqa: BLE001 -- one detector's failure never hides the others
309
+ from . import harness_api
310
+ harness_api.log.exception("engine health: a detector failed")
311
+ self.health.sampled()
312
+ finally:
313
+ self.pass_lock.release()
314
+
315
+ def _set(self, source, found, **fields):
316
+ """Report ``found`` ((state, cause) or None) for ``source``."""
317
+ if found is None:
318
+ self.health.clear(source)
319
+ else:
320
+ self.health.report(source, found[0], found[1], **fields)
321
+
322
+ def _engine(self):
323
+ runtime = self.runtime
324
+ self._set("engine", (STOPPING, "closing") if runtime.closing else None)
325
+ self._set("live", None if getattr(runtime.config, "enable_live", True) else (LIVE_OFF, "disabled"))
326
+
327
+ def _model(self):
328
+ from . import engines
329
+ state = self.runtime.engines_status()[engines.SMART]
330
+ found = classify_model(state["available"], state["reason"], engines.CREDIT_REASONS,
331
+ has_key=bool(engines.smart_key()) or self.runtime.scripted_models())
332
+ if found is None:
333
+ self.health.clear("model")
334
+ else:
335
+ self.health.report("model", found[0], found[1], message=found[2])
336
+
337
+ def _phones(self):
338
+ runtime = self.runtime
339
+ if runtime.pool is not None:
340
+ return # a target with its own pool of phones says its own
341
+ fleet = getattr(runtime, "fleet", None)
342
+ seen = set()
343
+ primary_name = getattr(getattr(fleet, "primary", None), "name", None) or None
344
+ if runtime.manager is not None:
345
+ found = classify_phone(runtime.manager.engine_reading())
346
+ elif getattr(runtime.config, "wda_url", None):
347
+ found = None if runtime.target_status()["ready"] else (HELPER_DOWN, "not_answering")
348
+ else:
349
+ found = (NO_PHONE, "none_attached")
350
+ self._phone("phone:primary", found, primary_name, getattr(getattr(fleet, "primary", None), "id", None), seen)
351
+ for phone in list(getattr(fleet, "phones", {}).values()):
352
+ if phone.manager is not None:
353
+ found = classify_phone(phone.manager.engine_reading())
354
+ else:
355
+ found = None if phone.target_status()["ready"] else (HELPER_DOWN, "not_answering")
356
+ self._phone(f"phone:{phone.id}", found, phone.name, phone.id, seen)
357
+ self.health.clear_prefix("phone:", keep=seen)
358
+
359
+ def _phone(self, source, found, name, device, seen):
360
+ seen.add(source)
361
+ self._set(source, found, phone=name, device=device)
362
+
363
+ def _tasks(self):
364
+ from .run import phone_wait
365
+ seen = set()
366
+ for run in list(self.runtime.runs.values()):
367
+ if run.finished_at is not None:
368
+ continue
369
+ with run.condition:
370
+ events = list(run.events[-400:])
371
+ wait = phone_wait(events)
372
+ fields = task_condition(run.id, wait, events, run.device_name, run.device_id) if wait else None
373
+ if fields is None:
374
+ continue
375
+ source = f"task:{run.id}"
376
+ seen.add(source)
377
+ self.health.report(source, fields.pop("state"), fields.pop("cause"), **fields)
378
+ self.health.clear_prefix("task:", keep=seen)
@@ -0,0 +1,96 @@
1
+ """The harness's harness_api.EngineHooks: what the engine reaches in the full Mobster."""
2
+
3
+
4
+ class Harness:
5
+ def __init__(self):
6
+ self.target_driver, self.cli, self.runtime_for = None, [], None
7
+
8
+ def model_routes(self):
9
+ from .ai_plans import providers
10
+ return providers
11
+
12
+ def key(self, provider, env):
13
+ from . import keys
14
+ return (keys.anthropic_key if provider == "anthropic" else keys.openai_key)(env)
15
+
16
+ def tool_policy(self, skills, *, run_context, driver, guard):
17
+ from .harness.tools import offered
18
+ return offered(skills, run_context=run_context, driver=driver, guard=guard)
19
+
20
+ def first_screen(self, driver, run_context):
21
+ from .harness.prewarm import take
22
+ return take(driver, run_context)
23
+
24
+ def private_rects(self, elements, bundle_id):
25
+ from .run_gif import private_rects
26
+ return private_rects(elements, bundle_id)
27
+
28
+ def long_report(self):
29
+ from .longform import report
30
+ return report
31
+
32
+ def wifi_attached(self, udid):
33
+ from .wireless import transport
34
+ value = transport.attached(udid)
35
+ return (False, None) if value is transport.NOT_WIFI else (True, value)
36
+
37
+ def wifi_lost(self, udid):
38
+ from .wireless import transport
39
+ return transport.lost(udid)
40
+
41
+ def wifi_may_reach(self, udid):
42
+ from .paths import user_data_dir
43
+ from .wireless import store, transport, transport_on
44
+ return transport_on() and transport.link(udid) is None and store.enabled(user_data_dir(), udid)
45
+
46
+ def usb_attached(self, udid):
47
+ from .mac_tools import usb_attached
48
+ return usb_attached(udid)
49
+
50
+ def devicectl_json(self, args, timeout):
51
+ from .wireless import devicectl
52
+ return devicectl.run_json(args, timeout)
53
+
54
+ def coredevice_locked(self, udid):
55
+ from .wireless import devicectl
56
+ return devicectl.read_lock_state(udid)
57
+
58
+ def coredevice_apps(self, udid, kind):
59
+ from .wireless import devicectl
60
+ return devicectl.read_apps(udid, kind)
61
+
62
+ def track_disabled(self, track, reason):
63
+ from . import tracks
64
+ tracks.disable(track, reason)
65
+
66
+ def hedge(self):
67
+ from . import hedge
68
+ return hedge
69
+
70
+ def skills(self):
71
+ from .skills import default_skills
72
+ return default_skills()
73
+
74
+ def app_catalog(self):
75
+ from .catalog import APPS
76
+ return APPS
77
+
78
+ def image_policy(self):
79
+ from . import image_policy
80
+ return image_policy
81
+
82
+ def frame_clock(self):
83
+ from .wda import frame_clock
84
+ return frame_clock
85
+
86
+ def ios_slice(self, snapshot, request, driver):
87
+ from .ios.guide import turn_slice
88
+ return turn_slice(snapshot, request, driver)
89
+
90
+ def screen_text(self):
91
+ from .screen_text import reader
92
+ return reader()
93
+
94
+ def pixel_screen(self):
95
+ from . import perception
96
+ return perception