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,383 @@
1
+ """Runtime's actions on a finished run: stop, delete, export, share, review and block decisions."""
2
+
3
+ import base64
4
+ import hashlib
5
+ import re
6
+ import threading
7
+ import time
8
+ from pathlib import Path
9
+
10
+ from . import harness_api, ownership, result_blocks, traces
11
+ from .api_errors import APIError
12
+ from .downloads import save_export_bytes
13
+ from .journal import JournalError
14
+
15
+ # One render at a time: a GIF takes a second or two of CPU, which a running task shares.
16
+ GIF_RENDERS = threading.BoundedSemaphore(1)
17
+
18
+ # One at a time: each is a WebKit page off screen.
19
+ ARTIFACT_RENDERS = threading.BoundedSemaphore(1)
20
+
21
+ # A result that stays on this Mac (Network X-ray's report) is never shared, sent or read but by the Mac app.
22
+ STAYS_ON_MAC = "This result stays on this Mac, so Mobster doesn't share it. Export it as a file to keep a copy."
23
+
24
+
25
+ class RunActionsMixin:
26
+ def stop_run(self, identifier):
27
+ announce = None
28
+ with self.lock:
29
+ run = self.runs.get(identifier)
30
+ if not run:
31
+ raise APIError("Task not found", 404, "not_found")
32
+ item = self.unqueue(identifier) if identifier in self.queued else None
33
+ with run.condition:
34
+ if run.finished_at is None and not run.stop.is_set():
35
+ run.stop.set()
36
+ run.emit({"event": "stop_requested"})
37
+ if item is not None:
38
+ # It never had the phone: it leaves the line and ends, doing nothing.
39
+ self.end_queued(run, "stopped", ownership.QUEUE_CANCELLED.format(phone=self.phone_name(item["phone"])))
40
+ announce = run
41
+ if announce is not None:
42
+ self.finished(announce)
43
+ self.publish("devices", {"event": "queue_changed", "device": announce.device_id})
44
+ return run
45
+
46
+ def delete_run(self, identifier):
47
+ with self.lock:
48
+ run = self.runs.get(identifier)
49
+ if not run:
50
+ raise APIError("Task not found", 404, "not_found")
51
+ if run.finished_at is None:
52
+ raise APIError("Stop this task before deleting it", 409, "run_active")
53
+ self.journal.delete(identifier)
54
+ self.runs.pop(identifier, None)
55
+ if getattr(self, "frames", None) is not None:
56
+ self.frames.delete(identifier)
57
+ self.publish("runs", {"event": "run_deleted", "runId": identifier})
58
+
59
+ def save_run_gif(self, identifier, theme="paper", redact=False, mp4=False, directory=None):
60
+ from . import run_gif
61
+ run = self._finished_run(identifier, "This task is still running. Save it as a GIF once it ends.")
62
+ frames = getattr(self, "frames", None)
63
+
64
+ def read(frame_id):
65
+ return frames.get(run.id, frame_id) if frames is not None else None
66
+
67
+ if not GIF_RENDERS.acquire(blocking=False):
68
+ raise APIError("Mobster is saving another GIF. Try again in a moment.", 429, "export_busy")
69
+ try:
70
+ public = run.public()
71
+ try:
72
+ clip = run_gif.build_clip(public, read, theme=theme, redact=redact)
73
+ except ValueError as error:
74
+ if str(error) == run_gif.NO_SCREENS:
75
+ raise APIError(str(error), 404, "no_frames") from None
76
+ raise APIError(str(error), 422, "gif_too_long") from None
77
+ stem = run_gif.file_stem(public.get("goal"))
78
+ saved = save_export_bytes(stem, "gif", clip.gif, directory)
79
+ result = {**saved, "frames": clip.frames, "bytes": len(clip.gif), "seconds": round(clip.seconds, 1),
80
+ "mp4": None}
81
+ if mp4:
82
+ data = run_gif.build_mp4(public, read, theme=theme, redact=redact)
83
+ if data is not None:
84
+ # The GIF's own name, so the two sit together ("… (1).gif" and "… (1).mp4").
85
+ video = save_export_bytes(Path(saved["name"]).stem, "mp4", data, directory)
86
+ result["mp4"] = {**video, "bytes": len(data)}
87
+ return result
88
+ finally:
89
+ GIF_RENDERS.release()
90
+
91
+ def add_to_evals(self, identifier):
92
+ """A finished task into the local eval set, beside the journal (traces.add_to_evals): {id, path, added,
93
+ expected, recorded}. APIError 503 traces_off without MOBSTER_EXPERIMENTAL_TRACES, 409 run_active while it
94
+ runs, 422 no_tape or unreadable when it can't be replayed."""
95
+ run = self._finished_run(identifier, "This task is still running. Add it to your evals once it ends.")
96
+ state_db = getattr(self.config, "state_db", None)
97
+ if not traces.enabled():
98
+ raise APIError("Traces are off. Set MOBSTER_EXPERIMENTAL_TRACES=1 to keep tasks as evals.", 503,
99
+ "traces_off")
100
+ frames = getattr(self, "frames", None)
101
+ try:
102
+ return traces.add_to_evals({**run.public(), "app": run.app}, frames.folder(run.id) if frames else None,
103
+ Path(state_db).parent / "evals" if state_db else None)
104
+ except traces.EvalError as error:
105
+ raise APIError(str(error), 409 if error.code == "running" else 422, error.code) from None
106
+
107
+ def save_artifact(self, identifier, kind, blocks=None, directory=None):
108
+ from . import artifacts
109
+ if kind not in ("pdf", "png", "md"):
110
+ raise ValueError("format is pdf, png or md")
111
+ if blocks is not None and (not isinstance(blocks, list) or not 1 <= len(blocks) <= result_blocks.MAX_BLOCKS
112
+ or not all(isinstance(b, str) for b in blocks)):
113
+ raise ValueError("blocks is a list of 1 to 8 block ids")
114
+ if kind == "png" and (blocks is None or len(blocks) != 1):
115
+ raise ValueError("An image is one block: name it in blocks")
116
+ run = self._finished_run(identifier, "This task is still running. Save it once it ends.")
117
+ public = run.public(include_events=False)
118
+ envelope = (public.get("summary") or {}).get("blocks")
119
+ known = {b.get("id"): b for b in (envelope or {}).get("blocks") or ()} if isinstance(envelope, dict) else {}
120
+ if blocks is not None and any(b not in known for b in blocks):
121
+ raise APIError("That card isn't in this task's results anymore.", 404, "block_not_found")
122
+ frames = getattr(self, "frames", None)
123
+
124
+ def read(frame_id):
125
+ return self.frame(run.id, frame_id) if frames is not None else None
126
+
127
+ first = known[blocks[0]] if blocks else None
128
+ stem = (first.get("title") if first and first.get("title") else run.goal or "Mobster result")
129
+ stem = " ".join(str(stem).split())[:80]
130
+ if kind == "md":
131
+ data = artifacts.markdown(public, blocks=blocks).encode("utf-8")
132
+ else:
133
+ if not ARTIFACT_RENDERS.acquire(blocking=False):
134
+ raise APIError("Mobster is making another file. Try again in a moment.", 429, "export_busy")
135
+ try:
136
+ page = artifacts.document(public, blocks=blocks, frames=read, card=kind == "png",
137
+ report=kind == "pdf", draw_map=self._map_drawer())
138
+ data = artifacts.render(page, kind, title=stem)
139
+ except artifacts.RenderError as error:
140
+ raise APIError(str(error), 503, "artifact_unavailable") from None
141
+ finally:
142
+ ARTIFACT_RENDERS.release()
143
+ saved = save_export_bytes(stem, kind, data, directory)
144
+ return {**saved, "bytes": len(data)}
145
+
146
+ @staticmethod
147
+ def _map_drawer():
148
+ from . import maps
149
+ return lambda block: maps.snapshot(maps.places_of(block), near=block.get("near"), width=720, height=340)
150
+
151
+ def block_map(self, identifier, block_id, dark=False, width=720, height=360):
152
+ from . import maps
153
+ run = self._finished_run(identifier)
154
+ envelope = ((run.public(include_events=False).get("summary") or {}).get("blocks"))
155
+ block, _ = result_blocks.find(envelope, block_id)
156
+ places = maps.places_of(block)
157
+ if not places:
158
+ raise APIError("That card has no places to show on a map.", 404, "block_not_found")
159
+ try:
160
+ drawn = maps.snapshot(places, near=(block or {}).get("near"), dark=dark, width=width, height=height)
161
+ except maps.MapError as error:
162
+ raise APIError(str(error), 503, "map_unavailable") from None
163
+ return {**{k: v for k, v in drawn.items() if k != "image"},
164
+ "image": "data:image/png;base64," + base64.b64encode(drawn["image"]).decode()}
165
+
166
+ def _shareable(self, identifier, people, screens, title):
167
+ from . import share
168
+ if people not in ("mask", "show") or type(screens) is not bool:
169
+ raise ValueError("people is mask or show, and screens is true or false")
170
+ if title is not None and (not isinstance(title, str) or not 1 <= len(title.strip()) <= 120):
171
+ raise ValueError("title is 1 to 120 characters")
172
+ run = self._finished_run(identifier, "This task is still running. Share it once it ends.")
173
+ if harness_api.stays_on_mac(run.extras):
174
+ raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
175
+ frames = getattr(self, "frames", None)
176
+ public = run.public(include_events=False)
177
+ html = share.page(public, frames=(lambda f: self.frame(run.id, f)) if frames is not None else None,
178
+ people=people, screens=screens, title=title)
179
+ if len(html.encode("utf-8")) > share.MAX_PAGE_BYTES:
180
+ raise APIError("That page is over 4 MB. Leave out the screens.", 413, "too_large")
181
+ return run, public, html
182
+
183
+ def share_preview(self, identifier, people="mask", screens=False, title=None):
184
+ from . import artifacts
185
+ run, _public, html = self._shareable(identifier, people, screens, title)
186
+ if not ARTIFACT_RENDERS.acquire(blocking=False):
187
+ raise APIError("Mobster is making another file. Try again in a moment.", 429, "export_busy")
188
+ try:
189
+ image = artifacts.render(html, "png", width=640)
190
+ except artifacts.RenderError as error:
191
+ raise APIError(str(error), 503, "artifact_unavailable") from None
192
+ finally:
193
+ ARTIFACT_RENDERS.release()
194
+ return {"image": "data:image/png;base64," + base64.b64encode(image).decode(),
195
+ "bytes": len(html.encode("utf-8"))}
196
+
197
+ def share_run(self, identifier, access_token, people="mask", screens=False, title=None, expires_days=30):
198
+ # The account's access token is used for this request only.
199
+ from . import share
200
+ if not isinstance(access_token, str) or not 16 <= len(access_token) <= 8192:
201
+ raise APIError("Sign in to Mobster to share.", 401, "session")
202
+ if expires_days is not None and expires_days not in share.EXPIRY_DAYS:
203
+ raise ValueError("expiresDays is 1, 7, 30, 90 or null")
204
+ run, public, html = self._shareable(identifier, people, screens, title)
205
+ heading = " ".join(str(title or public.get("goal") or "Mobster result").split())[:120]
206
+ try:
207
+ made = share.create(access_token, title=heading, html=html, expires_days=expires_days)
208
+ except share.ShareError as error:
209
+ raise APIError(str(error), error.status, error.code) from None
210
+ finally:
211
+ access_token = None
212
+ note = {"id": made["id"], "url": made["url"], "title": made.get("title") or heading,
213
+ "createdAt": made.get("createdAt"), "expiresAt": made.get("expiresAt"), "revokedAt": None,
214
+ "people": people, "screens": screens}
215
+ self._note_share(run, lambda shares: [note, *shares][:50])
216
+ return {"share": note}
217
+
218
+ def unshare_run(self, identifier, share_id, access_token):
219
+ from . import share
220
+ if not isinstance(share_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]{22}", share_id):
221
+ raise APIError("That link isn't one of this task's.", 404, "not_found")
222
+ if not isinstance(access_token, str) or not 16 <= len(access_token) <= 8192:
223
+ raise APIError("Sign in to Mobster to turn the link off.", 401, "session")
224
+ with self.lock:
225
+ run = self.runs.get(identifier)
226
+ if not run or not any(item.get("id") == share_id for item in run.shares):
227
+ raise APIError("That link isn't one of this task's.", 404, "not_found")
228
+ try:
229
+ gone = share.revoke(access_token, share_id)
230
+ except share.ShareError as error:
231
+ raise APIError(str(error), error.status, error.code) from None
232
+ finally:
233
+ access_token = None
234
+ revoked = gone.get("revokedAt") or time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
235
+ self._note_share(run, lambda shares: [{**item, "revokedAt": item.get("revokedAt") or revoked}
236
+ if item.get("id") == share_id else item for item in shares])
237
+ return {"share": next(item for item in run.shares if item.get("id") == share_id)}
238
+
239
+ def _note_share(self, run, change):
240
+ with run.condition:
241
+ updated = change([dict(item) for item in run.shares])
242
+ if run.journal:
243
+ try:
244
+ run.journal.annotate(run.id, {"shares": updated})
245
+ except JournalError:
246
+ pass # the link exists either way; only this Mac's note of it is lost
247
+ run.shares = updated
248
+ self.publish("runs", {"event": "run_shared", "runId": run.id})
249
+
250
+ def review_run(self, identifier, ok):
251
+ run = self._finished_run(identifier, "This task is still running")
252
+ with run.condition:
253
+ if run.journal:
254
+ run.journal.annotate(run.id, {"reviewedOk": ok})
255
+ run.reviewed_ok = ok
256
+ self.publish("runs", {"event": "run_reviewed", "runId": run.id, "reviewedOk": ok})
257
+ return run
258
+
259
+ def decide_block_item(self, identifier, block_id, item_id, decision, text=None):
260
+ if decision not in ("approve", "skip", "reopen"):
261
+ raise ValueError("decision is approve, skip or reopen")
262
+ run = self._finished_run(identifier, "This task is still running")
263
+ blocks = (run.summary or {}).get("blocks")
264
+ block, item = result_blocks.find(blocks, block_id, item_id)
265
+ if block is None:
266
+ raise APIError("That item isn't in this task's results anymore.", 404, "block_item_not_found")
267
+ source = self._brief_source(run, block_id)
268
+ if source is not None:
269
+ # A brief's card is its task's: the item is decided once wherever it shows, and _block_state mirrors it.
270
+ answer = self.decide_block_item(source[0], source[1], item_id, decision, text)
271
+ with run.condition:
272
+ return {**answer, "blockState": dict(run.block_state)}
273
+ key = f"{block_id}/{item_id}"
274
+ with run.condition:
275
+ current = dict(run.block_state.get(key) or {})
276
+ if current.get("state") == "approved":
277
+ if decision == "approve" and current.get("runId"):
278
+ return {"state": "approved", "runId": current["runId"], "replayed": True,
279
+ "blockState": dict(run.block_state)}
280
+ raise APIError("You already approved this one, so it can't change now.", 409, "block_item_approved",
281
+ runId=current.get("runId"))
282
+ if decision in ("skip", "reopen"):
283
+ if decision == "reopen" and current.get("state") != "skipped":
284
+ raise APIError("Only a skipped item can go back in the queue.", 409, "block_item_not_skipped")
285
+ state = {"state": "skipped", "at": time.time() * 1000} if decision == "skip" else None
286
+ return self._block_state(run, key, state)
287
+ if block.get("type") != "review_queue":
288
+ raise APIError("Only a draft in a review queue can be approved.", 409, "block_item_not_approvable")
289
+ try:
290
+ block, item, words = result_blocks.approval_item(blocks, block_id, item_id, text)
291
+ except result_blocks.BlockError as problem:
292
+ raise APIError(str(problem), 400, "block_item_invalid") from None
293
+ goal = result_blocks.follow_up_goal(block, item, words)
294
+ app_id = next((app["id"] for app in self.apps_for(None) if item.get("bundleId")
295
+ and app.get("bundleId") == item["bundleId"]), None)
296
+ # One task per item, ever: a retried Approve (a lost response, a double click) gets the task it started.
297
+ request_key = f"rb-{run.id}-{hashlib.sha256(key.encode()).hexdigest()[:16]}"
298
+ thread_id = (run.extras or {}).get("threadId") if isinstance(run.extras, dict) else None
299
+ follow = None
300
+ if thread_id:
301
+ from .threads.service import service_for
302
+ service = service_for(self)
303
+ if service is not None:
304
+ _status, response = service.message(thread_id, {"text": goal, "mode": "new_task",
305
+ **({"appId": app_id} if app_id else {})},
306
+ origin="app", key=request_key)
307
+ follow = self.runs.get(((response or {}).get("run") or {}).get("id") or "")
308
+ if follow is None:
309
+ same_phone = ({"device": run.device_id} if run.device_id and getattr(self, "fleet", None) is not None
310
+ and self.pool is None else {})
311
+ created = self.create(app_id, goal, "live", request_key, origin="app", **same_phone)
312
+ follow = created[0] if isinstance(created, tuple) else created
313
+ with follow.condition:
314
+ if not follow.preapproved and follow.finished_at is None:
315
+ follow.preapproved = [result_blocks.preapproval(block, item, words)]
316
+ return {**self._block_state(run, key, {"state": "approved", "runId": follow.id, "at": time.time() * 1000}),
317
+ "runId": follow.id}
318
+
319
+ def _brief_source(self, run, block_id):
320
+ brief = (run.summary or {}).get("brief") if isinstance(run.summary, dict) else None
321
+ found = ((brief or {}).get("sources") or {}).get(block_id) if isinstance(brief, dict) else None
322
+ if (isinstance(found, list) and len(found) == 2 and all(isinstance(part, str) for part in found)
323
+ and found[0] != run.id and found[0] in self.runs):
324
+ return found[0], found[1]
325
+ return None
326
+
327
+ def _mirror_briefs(self, run, key, state):
328
+ block_id, _, item_id = key.partition("/")
329
+ with self.lock:
330
+ briefs = [other for other in self.runs.values() if other is not run and isinstance(other.summary, dict)
331
+ and isinstance(other.summary.get("brief"), dict)]
332
+ for other in briefs:
333
+ for card, found in ((other.summary["brief"].get("sources") or {}).items()):
334
+ if found == [run.id, block_id]:
335
+ try:
336
+ self._block_state(other, f"{card}/{item_id}", state, mirror=False)
337
+ except APIError:
338
+ harness_api.log.warning("brief %s: couldn't save a copy of a decision", other.id)
339
+
340
+ def _block_state(self, run, key, state, mirror=True):
341
+ # Journaled before the app is told.
342
+ with run.condition:
343
+ updated = dict(run.block_state)
344
+ if state is None:
345
+ updated.pop(key, None)
346
+ else:
347
+ updated[key] = state
348
+ if run.journal:
349
+ try:
350
+ run.journal.annotate(run.id, {"blockState": updated})
351
+ except JournalError:
352
+ raise APIError("Mobster couldn't save that. Try again.", 503, "journal_unavailable") from None
353
+ run.block_state = updated
354
+ self.publish("runs", {"event": "run_block_decided", "runId": run.id, "item": key,
355
+ "state": (state or {}).get("state", "open")})
356
+ if mirror:
357
+ self._mirror_briefs(run, key, state)
358
+ return {"state": (state or {}).get("state", "open"), "blockState": dict(updated)}
359
+
360
+ def send_run(self, identifier, destination):
361
+ from . import run_delivery
362
+ run = self._finished_run(identifier, "This task is still running. Send it once it ends.")
363
+ if harness_api.stays_on_mac(run.extras):
364
+ raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
365
+ return run_delivery.send(self, run, destination)
366
+
367
+ def file_run_issue(self, identifier, body):
368
+ from . import run_delivery
369
+ run = self._finished_run(identifier, "This task is still running. File it once it ends.")
370
+ if harness_api.stays_on_mac(run.extras):
371
+ raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
372
+ return run_delivery.file_issue(self, run, body)
373
+
374
+ def inbox(self):
375
+ from . import inbox
376
+ with self.lock:
377
+ runs = list(self.runs.values())
378
+ try:
379
+ workflows = self.workflows.list_with_health() if getattr(self, "workflows", None) is not None else []
380
+ except JournalError:
381
+ workflows = []
382
+ items = inbox.collect(runs, workflows)
383
+ return {"items": items, "count": len(items)}
@@ -0,0 +1,181 @@
1
+ """Runtime's settings, keys and the status every surface shows."""
2
+
3
+ import os
4
+
5
+ from . import __version__, engines, listen, permissions, tracks
6
+ from .api_errors import APIError
7
+ from .devices import AmbiguousDevice, DeviceNotFound
8
+ from .env_secrets import update_env_file
9
+ from .gemini import configured as helper_configured
10
+ from .keys import Keys
11
+ from .power_watch import PAUSE_RANGE as BATTERY_PAUSE_RANGE
12
+ from .power_watch import phone_pause_threshold
13
+ from .runtime_limits import MONTH_LIMIT_RANGE, TASK_LIMIT_RANGE
14
+ from .wda import frame_source
15
+ from .wda.video import mjpeg_settings
16
+
17
+
18
+ class StatusMixin:
19
+ def settings(self):
20
+ value = {"askBeforeActing": self.ask_before_acting(), "taskCostLimitUsd": self.task_cost_limit(),
21
+ "monthlyLimitUsd": self.monthly_limit(), "permissionMode": permissions.from_env().mode,
22
+ "phoneBatteryPause": phone_pause_threshold(),
23
+ "hearPhone": listen.enabled()}
24
+ if frame_source.streams(getattr(self, "video", None)):
25
+ value["videoFps"], value["videoDetail"] = self.video_quality()
26
+ return value
27
+
28
+ @property
29
+ def env_file(self):
30
+ return self.setup.env_file if getattr(self, "setup", None) else getattr(self.config, "env_file", None)
31
+
32
+ @property
33
+ def keys(self):
34
+ return Keys(self.env_file)
35
+
36
+ def save_keys(self, body):
37
+ # Ask Smart's provider afresh: a project the user just fixed must not wait out ModelReach's cached refusal.
38
+ state = self.keys.update(body)
39
+ reach = getattr(self, "model_reach", None)
40
+ if reach is not None and {"openai", "anthropic", "helper"} & set(body):
41
+ reach.forget(engines.smart_key())
42
+ return state
43
+
44
+ def check_key(self, target):
45
+ model = engines.smart_model()
46
+ # The key the test is about to read, when it is the one Smart runs on.
47
+ key = engines.smart_key() if target == engines.model_provider(model) else None
48
+ result = self.keys.test(target)
49
+ reach = getattr(self, "model_reach", None)
50
+ # The test takes up to a minute: a key saved meanwhile was never tested, and the next status checks it.
51
+ if key and reach is not None and engines.smart_key() == key:
52
+ if result.get("ok"):
53
+ reach.record(key, True)
54
+ elif result.get("problem") == "no_credit":
55
+ reach.record(key, False, engines.no_credit(model))
56
+ return result
57
+
58
+ def update_settings(self, changes):
59
+ allowed = {"askBeforeActing", "videoFps", "videoDetail", "taskCostLimitUsd", "monthlyLimitUsd",
60
+ "phoneBatteryPause", "hearPhone"}
61
+ if not isinstance(changes, dict) or not changes or set(changes) - allowed:
62
+ raise ValueError("Unsupported setting")
63
+ saved = {}
64
+ if "askBeforeActing" in changes:
65
+ if type(changes["askBeforeActing"]) is not bool:
66
+ raise ValueError("askBeforeActing must be true or false")
67
+ saved["MOBSTER_ASK_BEFORE_ACTING"] = "1" if changes["askBeforeActing"] else "0"
68
+ # The older switch sets the mode: off is Don't ask; on keeps a mode that asks, else the default.
69
+ mode = permissions.from_env().mode
70
+ saved["MOBSTER_PERMISSION_MODE"] = permissions.policy.mode_for_ask(changes["askBeforeActing"], mode)
71
+ if "hearPhone" in changes:
72
+ if type(changes["hearPhone"]) is not bool:
73
+ raise ValueError("hearPhone must be true or false")
74
+ saved[listen.SETTING] = "on" if changes["hearPhone"] else "off"
75
+ if "phoneBatteryPause" in changes:
76
+ low, high = BATTERY_PAUSE_RANGE
77
+ percent = changes["phoneBatteryPause"]
78
+ if percent is not None and (type(percent) is not int or not low <= percent <= high):
79
+ raise ValueError(f"phoneBatteryPause must be null or a whole percent from {low} to {high}")
80
+ saved["MOBSTER_PHONE_BATTERY_PAUSE"] = "off" if percent is None else str(percent)
81
+ for name, env, (low, high) in (("taskCostLimitUsd", "MOBSTER_TASK_COST_LIMIT", TASK_LIMIT_RANGE),
82
+ ("monthlyLimitUsd", "MOBSTER_MONTHLY_LIMIT", MONTH_LIMIT_RANGE)):
83
+ if name not in changes:
84
+ continue
85
+ amount = changes[name]
86
+ if amount is None:
87
+ saved[env] = "off" if name == "taskCostLimitUsd" else ""
88
+ continue
89
+ if type(amount) not in (int, float) or not low <= amount <= high:
90
+ raise ValueError(f"{name} must be null or between ${low:g} and ${high:,g}")
91
+ saved[env] = f"{round(float(amount), 2):.2f}"
92
+ if {"videoFps", "videoDetail"} & set(changes):
93
+ if not frame_source.streams(getattr(self, "video", None)):
94
+ raise ValueError("Live video settings need a USB iPhone")
95
+ fps, detail = self.video_quality()
96
+ fps, detail = changes.get("videoFps", fps), changes.get("videoDetail", detail)
97
+ if type(fps) is not int:
98
+ raise ValueError("videoFps must be 15, 30 or 60")
99
+ mjpeg_settings(fps, detail) # validates both
100
+ saved.update(MOBSTER_VIDEO_FPS=str(fps), MOBSTER_VIDEO_DETAIL=detail)
101
+ if self.env_file:
102
+ update_env_file(self.env_file, saved)
103
+ os.environ.update(saved)
104
+ if "MOBSTER_VIDEO_FPS" in saved:
105
+ self.video.set_quality(*self.video_quality())
106
+ for video in (self.fleet.videos() if getattr(self, "fleet", None) is not None else ()):
107
+ video.set_quality(*self.video_quality())
108
+ return self.settings()
109
+
110
+ def device_status(self, phone=None):
111
+ if phone is None:
112
+ fleet = getattr(self, "fleet", None)
113
+ phone = fleet.primary if fleet is not None else None
114
+ return {"phone": self.power.phone(phone) if phone is not None else {},
115
+ "mac": {**self.power.mac(), **self.power.awake.status()},
116
+ "warnings": self.power.warnings(),
117
+ "phoneBatteryPause": phone_pause_threshold()}
118
+
119
+ def engines_status(self):
120
+ managed = getattr(self, "setup", None) is not None
121
+ key, model = engines.smart_key(), engines.smart_model()
122
+ reach = getattr(self, "model_reach", None)
123
+ if self.scripted_models():
124
+ smart = (True, None)
125
+ elif not key:
126
+ smart = (False, engines.no_key_reason(managed, model))
127
+ else:
128
+ smart = reach.state(key, model) if reach is not None else (True, None)
129
+ return {engines.SMART: {"available": smart[0], "reason": smart[1], "model": model,
130
+ "provider": engines.provider_name(model),
131
+ # Whose bill: a plan (Kimi Code, Grok ...) or a key (ai_plans/providers.py).
132
+ "billing": "plan" if engines.on_plan(model) else "key"}}
133
+
134
+ def engine(self):
135
+ return self.engine_sampler.snapshot()
136
+
137
+ def status(self):
138
+ target = self.target_status()
139
+ device, ready, health = target["device"], target["ready"], target["health"]
140
+ wda = bool(self.config.wda_url)
141
+ available = self.engines_status()
142
+ key = available[engines.SMART]["available"]
143
+ return {"api_version": "v1", "version": __version__, "helper_configured": helper_configured(),
144
+ "engines": available, "agents": self.agents_status(), "engine": self.engine(),
145
+ "live_enabled": bool(not self.closing and self.config.enable_live and key and ready
146
+ and target["can_act"]),
147
+ "device_ready": ready, "durable_history": getattr(self.config, "state_db", None) is not None,
148
+ "screen_reading": target["screen_reading"], "ask_before_acting": self.ask_before_acting(),
149
+ "spend_cap_usd": getattr(self.config, "spend_cap_usd", None),
150
+ "devices": self.pool.public() if self.pool else [],
151
+ "concurrency": {"capacity": self.pool.size if self.pool else self.device_count(),
152
+ "running": len(self.active_runs),
153
+ "available": self.capacity()},
154
+ "device": "Your iPhone" if device else None,
155
+ "driver": target["driver"],
156
+ "extensions": dict(tracks.STATUS),
157
+ "limitations": self.limitations(key=key, device=device, ready=ready, health=health, wda=wda,
158
+ can_act=target["can_act"],
159
+ engine_reason=available[engines.SMART]["reason"])}
160
+
161
+ def device_count(self):
162
+ fleet = getattr(self, "fleet", None)
163
+ if fleet is None:
164
+ return 1
165
+ with fleet.lock:
166
+ others = sum(1 for phone in fleet.phones.values() if phone.wda_url)
167
+ return 1 + others
168
+
169
+ def resolve_default(self, sticky=True):
170
+ try:
171
+ return self.fleet.default(sticky=sticky)
172
+ except LookupError as error:
173
+ raise APIError(str(error), 404, "device_not_found") from None
174
+
175
+ def resolve_device(self, device):
176
+ try:
177
+ return self.fleet.for_run(device)
178
+ except DeviceNotFound as error:
179
+ raise APIError(str(error), 404, "device_not_found") from None
180
+ except AmbiguousDevice as error:
181
+ raise APIError(str(error), 400, "device_ambiguous") from None