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,123 @@
1
+ """A finished result leaving Mobster by a person's click: "Send to" a saved destination, and "File as issue" in a
2
+ connected tracker. Both ride the code that already sends (outlets' deliveries, ConnectorService.call), and both are
3
+ the person's: the routes need the Mac app's session."""
4
+
5
+ import time
6
+ import uuid
7
+
8
+ from .api_errors import APIError
9
+ from .connectors import client as mcp
10
+ from .connectors import issue as tracker
11
+ from .connectors import service as connector_service
12
+ from .connectors import store as connector_store
13
+ from .connectors.skill import result_text
14
+ from .journal import JournalError
15
+ from .outlets import service as outlet_service
16
+ from .outlets import webhook
17
+ from .secret_filter import redact
18
+
19
+ ISSUE_KEEP = 50
20
+ TITLE_MAX = 120
21
+
22
+
23
+ def _refuse(code, message, status=409):
24
+ return APIError(message, status, code)
25
+
26
+
27
+ def send(runtime, run, destination):
28
+ """Queue ``run``'s result for the saved destination ``destination`` names:
29
+ the delivery, as the run view shows it."""
30
+ service = outlet_service.service_for(runtime)
31
+ if service is None:
32
+ raise _refuse("unavailable", "Sending results isn't available right now. Restart Mobster, then try again.",
33
+ 503)
34
+ if not service.store.destinations():
35
+ raise _refuse("no_destination", "No destination is saved yet. Add Slack, email or a webhook in Settings › "
36
+ "Connectors, then send the result.")
37
+ try:
38
+ row = service.find(destination)
39
+ except ValueError as error:
40
+ raise APIError(str(error), 404, "destination_not_found") from None
41
+ return {"delivery": service.public_delivery(service.enqueue(run.id, row["id"]))}
42
+
43
+
44
+ def _text(run):
45
+ """The issue's title and text from the run's masked result (the same body a webhook gets)."""
46
+ body = webhook.payload(run)
47
+ title = " ".join(f"{run.goal or 'Mobster task'}".split())[:TITLE_MAX]
48
+ lines = [body["text"], "", f"Task: {redact(run.goal or '')}".strip(), f"Status: {body['status']}"]
49
+ if body.get("reason"):
50
+ lines.append(f"Why: {body['reason']}")
51
+ if run.device_name:
52
+ lines.append(f"Device: {run.device_name}")
53
+ lines += ["", f"Mobster run {run.id}"]
54
+ return title, "\n".join(lines)
55
+
56
+
57
+ def _note(runtime, run, change):
58
+ with run.condition:
59
+ updated = change([dict(item) for item in run.issues])
60
+ if run.journal:
61
+ run.journal.annotate(run.id, {"issues": updated}) # JournalError: nothing is sent without this note
62
+ run.issues = updated
63
+ runtime.publish("runs", {"event": "run_issue", "runId": run.id})
64
+
65
+
66
+ def file_issue(runtime, run, body):
67
+ """File ``run``'s result as one issue: the entry the run keeps ({"id", "tracker", "status", "url", ...}).
68
+ The attempt is noted on the run before the tool is called, and an answer that never came is never asked again."""
69
+ if (not isinstance(body, dict) or set(body) - {"tracker", "target", "again"}
70
+ or type(body.get("again", False)) is not bool):
71
+ raise ValueError('Send {"tracker": "GitHub", "target": {"owner": "...", "repo": "..."}} '
72
+ '(tracker, target optional)')
73
+ target = tracker.clean_target(body.get("target"))
74
+ service = connector_service.service_for(runtime)
75
+ if service is None:
76
+ raise _refuse("unavailable", "Connectors aren't available right now. Restart Mobster, then try again.", 503)
77
+ row, refusal = tracker.pick(service.store.all(), body.get("tracker"))
78
+ if refusal:
79
+ raise _refuse(*refusal)
80
+ name = row["name"]
81
+ mine = [item for item in run.issues if item.get("connector") == row["id"]]
82
+ for item in mine:
83
+ if item["status"] in ("filing", "unknown"):
84
+ raise _refuse("outcome_unknown", f"Mobster doesn't know whether {name} got this issue. Check {name} "
85
+ "before filing it again; Mobster won't send it twice on its own.")
86
+ if mine and not body.get("again"):
87
+ raise _refuse("already_filed", f"This result is already filed in {name}. Send again with \"again\": true "
88
+ "to file another.")
89
+ title, text = _text(run)
90
+ chosen = tracker.tool_for(row, target, title, text)
91
+ if chosen is None:
92
+ raise _refuse("tool_missing", f"{name} has no tool Mobster can file an issue with. Test it in Settings › "
93
+ "Connectors, then try again.")
94
+ tool, arguments = chosen
95
+ if connector_store.mode_of(row, tool) == "off":
96
+ raise _refuse("tool_blocked", f"{name}: {tool} is blocked in Settings › Connectors, so nothing was filed.")
97
+ entry = {"id": uuid.uuid4().hex[:12], "connector": row["id"], "tracker": name, "tool": tool, "status": "filing",
98
+ "url": None, "error": None, "at": int(time.time() * 1000)}
99
+ try:
100
+ _note(runtime, run, lambda items: [entry, *items][:ISSUE_KEEP])
101
+ except JournalError:
102
+ raise _refuse("journal_unavailable", "Mobster couldn't save that. Try again.", 503) from None
103
+ status, url, error = "filed", None, None
104
+ try:
105
+ result = service.call(row, tool, arguments)
106
+ text_back = result_text(result)
107
+ if result.get("isError"):
108
+ status, error = "failed", redact(text_back)[:200]
109
+ else:
110
+ url = tracker.link_of(text_back)
111
+ except mcp.OutcomeUnknown:
112
+ status, error = "unknown", f"{name} didn't answer. The issue may exist, so Mobster won't send it again."
113
+ except mcp.ConnectorError as problem:
114
+ status, error = "failed", redact(problem.message)[:200]
115
+
116
+ def settle(items):
117
+ return [{**item, "status": status, "url": url, "error": error} if item["id"] == entry["id"] else item
118
+ for item in items]
119
+ try:
120
+ _note(runtime, run, settle)
121
+ except JournalError:
122
+ run.issues = settle(run.issues) # the answer stays in memory; the note on disk keeps "filing"
123
+ return {"issue": next(item for item in run.issues if item["id"] == entry["id"])}
@@ -0,0 +1,200 @@
1
+ """One owner for the threads a run starts: a cancel token and a bounded pool of named workers."""
2
+
3
+ import logging
4
+ import threading
5
+ import time
6
+ from collections import deque
7
+ from concurrent.futures import Future
8
+ from dataclasses import dataclass
9
+ from enum import StrEnum
10
+
11
+ log = logging.getLogger("mobster.run_executor")
12
+
13
+ KEPT = 32 # finished workers ``workers()`` still names, newest last
14
+
15
+
16
+ class CancelToken:
17
+ """Set once, never cleared. ``CancelToken(parent)`` is set when its parent is, and can be set alone. ``set``,
18
+ ``is_set`` and ``wait`` are threading.Event's, so a run's Stop (``Run.stop``) is one: every ``run.stop.set()``
19
+ reaches the tokens and executors made from it."""
20
+
21
+ def __init__(self, parent=None):
22
+ self._event = threading.Event()
23
+ self._lock = threading.Lock()
24
+ self._callbacks = []
25
+ if parent is not None:
26
+ parent.on_cancel(self.set)
27
+
28
+ def set(self):
29
+ with self._lock:
30
+ if self._event.is_set():
31
+ return
32
+ self._event.set()
33
+ callbacks, self._callbacks = self._callbacks, []
34
+ for fn in callbacks:
35
+ _call(fn)
36
+
37
+ def is_set(self):
38
+ return self._event.is_set()
39
+
40
+ def wait(self, timeout=None):
41
+ return self._event.wait(timeout)
42
+
43
+ def on_cancel(self, fn):
44
+ """Call ``fn()`` once when the token is set, or now when it already is. It runs on the thread that sets the
45
+ token, inside whatever locks that thread holds (Runtime.stop_run holds the runtime's and the run's), so it
46
+ must be short and never wait."""
47
+ with self._lock:
48
+ if not self._event.is_set():
49
+ self._callbacks.append(fn)
50
+ return
51
+ _call(fn)
52
+
53
+
54
+ def _call(fn):
55
+ try:
56
+ fn()
57
+ except Exception: # noqa: BLE001 -- a hook never reaches whoever pressed Stop
58
+ log.exception("a cancel hook failed")
59
+
60
+
61
+ class Cancelled(RuntimeError):
62
+ """``RunExecutor.submit`` after its token was set: the run is stopping, so nothing new starts."""
63
+
64
+
65
+ class State(StrEnum):
66
+ QUEUED = "queued"
67
+ RUNNING = "running"
68
+ DONE = "done"
69
+ FAILED = "failed"
70
+ CANCELLED = "cancelled"
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class Worker:
75
+ name: str
76
+ state: State
77
+ error: str = "" # a failed worker's exception type
78
+
79
+
80
+ class _Job:
81
+ __slots__ = ("name", "fn", "args", "future", "state", "thread")
82
+
83
+ def __init__(self, name, fn, args):
84
+ self.name, self.fn, self.args = name, fn, args
85
+ self.future, self.state, self.thread = Future(), State.QUEUED, None
86
+
87
+
88
+ class RunExecutor:
89
+ """The threads one run starts. At most ``max_workers`` named daemon workers run at once and the rest wait their
90
+ turn. They all read one ``token``, a child of ``parent`` (the run's Stop): ``cancel()`` sets it, drops the work
91
+ still waiting and refuses more, and ``join()`` waits for the work already running."""
92
+
93
+ def __init__(self, parent=None, *, max_workers=4):
94
+ if max_workers < 1:
95
+ raise ValueError("A run executor needs at least one worker")
96
+ self.token = CancelToken(parent)
97
+ self._max = max_workers
98
+ self._condition = threading.Condition()
99
+ self._live = [] # queued and running jobs, oldest first
100
+ self._finished = deque(maxlen=KEPT)
101
+ self.token.on_cancel(self._drop_queued)
102
+
103
+ def submit(self, name, fn, *args):
104
+ """Run ``fn(*args)`` on a daemon thread named ``name`` and return its Future. What ``fn`` raises never
105
+ reaches the caller: the Future holds it and ``workers()`` names the worker failed. Raises Cancelled once the
106
+ token is set, and the thread's own error when no thread can be started."""
107
+ job = _Job(name, fn, args)
108
+ with self._condition:
109
+ if self.token.is_set():
110
+ raise Cancelled(f"{name} was not started: this run is stopping")
111
+ self._live.append(job)
112
+ if self._running() < self._max:
113
+ try:
114
+ self._start(job)
115
+ except BaseException:
116
+ self._live.remove(job)
117
+ raise
118
+ return job.future
119
+
120
+ def cancel(self):
121
+ self.token.set()
122
+
123
+ def join(self, timeout=None):
124
+ """Wait until every worker has finished. False when one still runs after ``timeout`` (``workers()`` names
125
+ it), and at once on one of this executor's own workers, which can't wait for itself."""
126
+ deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
127
+ with self._condition:
128
+ while self._live:
129
+ left = None if deadline is None else deadline - time.monotonic()
130
+ if (left is not None and left <= 0) or self._owns(threading.current_thread()):
131
+ return False
132
+ self._condition.wait(left)
133
+ return True
134
+
135
+ def in_worker(self):
136
+ """True on one of this executor's worker threads."""
137
+ with self._condition:
138
+ return self._owns(threading.current_thread())
139
+
140
+ def workers(self):
141
+ """The last KEPT finished workers, then every queued and running one, oldest first."""
142
+ with self._condition:
143
+ return (*self._finished, *(Worker(job.name, job.state) for job in self._live))
144
+
145
+ def _running(self):
146
+ return sum(job.state is State.RUNNING for job in self._live)
147
+
148
+ def _owns(self, thread):
149
+ return any(job.thread is thread for job in self._live)
150
+
151
+ def _start(self, job):
152
+ job.state = State.RUNNING
153
+ job.thread = threading.Thread(target=self._work, args=(job,), name=job.name, daemon=True)
154
+ try:
155
+ job.thread.start()
156
+ except BaseException:
157
+ job.state, job.thread = State.QUEUED, None
158
+ raise
159
+
160
+ def _work(self, job):
161
+ state, error = State.CANCELLED, ""
162
+ if job.future.set_running_or_notify_cancel():
163
+ try:
164
+ value = job.fn(*job.args)
165
+ except BaseException as failure: # noqa: BLE001 -- kept on its Future, never raised into the submitter
166
+ log.warning("run worker %s failed", job.name, exc_info=True)
167
+ job.future.set_exception(failure)
168
+ state, error = State.FAILED, type(failure).__name__
169
+ else:
170
+ job.future.set_result(value)
171
+ state = State.DONE
172
+ with self._condition:
173
+ self._retire(job, state, error)
174
+ self._start_queued()
175
+ self._condition.notify_all()
176
+
177
+ def _start_queued(self):
178
+ for job in [job for job in self._live if job.state is State.QUEUED]:
179
+ if self.token.is_set() or self._running() >= self._max:
180
+ return
181
+ try:
182
+ self._start(job)
183
+ except Exception as failure: # noqa: BLE001 -- no thread to be had: this job fails, the next still waits
184
+ log.warning("run worker %s could not start", job.name, exc_info=True)
185
+ if job.future.set_running_or_notify_cancel():
186
+ job.future.set_exception(failure)
187
+ self._retire(job, State.FAILED, type(failure).__name__)
188
+
189
+ def _drop_queued(self):
190
+ with self._condition:
191
+ for job in [job for job in self._live if job.state is State.QUEUED]:
192
+ job.future.cancel()
193
+ self._retire(job, State.CANCELLED)
194
+ self._condition.notify_all()
195
+
196
+ def _retire(self, job, state, error=""):
197
+ """Move ``job`` to the finished list, dropping what it held: a parked worker must not keep a run alive."""
198
+ self._live.remove(job)
199
+ job.fn = job.args = job.thread = None
200
+ self._finished.append(Worker(job.name, state, error))
@@ -0,0 +1,284 @@
1
+ """`mobster export ID --gif [--mp4] [--redact] [-o PATH]`: save a finished task as a captioned GIF or MP4, or
2
+ `mobster export ID --trace [--screens]`: as a trace bundle (traces.py)."""
3
+
4
+ import json
5
+ import re
6
+ import sqlite3
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .paths import PACKAGE, source_checkout, user_data_dir
11
+
12
+ FRAME_ID = re.compile(r"f[0-9a-f]{10}")
13
+ RUN_ID = re.compile(r"[0-9a-f]{4,12}")
14
+ ACTIVE = ("queued", "running")
15
+ REDACT_HINT = "It shows every screen of the task. Check it before you post it, or save it again with --redact."
16
+
17
+
18
+ class ExportError(Exception):
19
+ pass
20
+
21
+
22
+ def journals():
23
+ folders = []
24
+ if source_checkout():
25
+ folders.append(PACKAGE / ".state")
26
+ folders.append(user_data_dir() / "state")
27
+ paths = [folder / name for folder in folders for name in ("terminal.sqlite3", "mobster.sqlite3")]
28
+ found = [path for path in dict.fromkeys(paths) if path.is_file()]
29
+ return sorted(found, key=lambda path: path.stat().st_mtime, reverse=True)
30
+
31
+
32
+ def _connect(path):
33
+ return sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=2)
34
+
35
+
36
+ def _events(connection, run_id):
37
+ events = []
38
+ for (data,) in connection.execute("SELECT data FROM events WHERE run_id=? ORDER BY seq", (run_id,)):
39
+ try:
40
+ event = json.loads(data)
41
+ except ValueError:
42
+ continue
43
+ if isinstance(event, dict):
44
+ events.append(event)
45
+ return events
46
+
47
+
48
+ def find_run(identifier, paths=None):
49
+ paths = journals() if paths is None else paths
50
+ identifier = (identifier or "").strip().lower()
51
+ latest = identifier == "latest"
52
+ if not latest and not RUN_ID.fullmatch(identifier):
53
+ raise ExportError(f"“{identifier}” isn’t a task ID. List your tasks with `mobster history`.")
54
+ matches = []
55
+ for path in paths:
56
+ try:
57
+ connection = _connect(path)
58
+ except sqlite3.Error:
59
+ continue
60
+ try:
61
+ if latest:
62
+ rows = connection.execute("SELECT id, data FROM runs ORDER BY rowid DESC LIMIT 50").fetchall()
63
+ else:
64
+ rows = connection.execute("SELECT id, data FROM runs WHERE substr(id, 1, ?) = ?",
65
+ (len(identifier), identifier)).fetchall()
66
+ for run_id, data in rows:
67
+ try:
68
+ record = json.loads(data)
69
+ except ValueError:
70
+ continue
71
+ if not isinstance(record, dict) or (latest and record.get("status") in ACTIVE):
72
+ continue
73
+ matches.append((record, path, run_id))
74
+ except sqlite3.Error:
75
+ continue
76
+ finally:
77
+ connection.close()
78
+ if latest:
79
+ matches.sort(key=lambda item: item[0].get("createdAt") or 0, reverse=True)
80
+ matches = matches[:1]
81
+ if not matches:
82
+ what = "No finished task" if latest else f"No task {identifier}"
83
+ raise ExportError(f"{what} in your history. List your tasks with `mobster history`.")
84
+ if len({run_id for _, _, run_id in matches}) > 1:
85
+ raise ExportError(f"Several tasks start with {identifier}. Give more of the ID.")
86
+ record, path, run_id = matches[0]
87
+ connection = _connect(path)
88
+ try:
89
+ record = {**record, "events": _events(connection, run_id)}
90
+ finally:
91
+ connection.close()
92
+ return record, path
93
+
94
+
95
+ def run_folder(journal, run_id):
96
+ """Where the run's frames and tape are, beside its journal."""
97
+ return Path(journal).parent / "frames" / run_id
98
+
99
+
100
+ def frame_reader(journal, run_id):
101
+ folder = run_folder(journal, run_id)
102
+
103
+ def read(frame_id):
104
+ if not FRAME_ID.fullmatch(frame_id or ""):
105
+ return None
106
+ try:
107
+ return (folder / f"{frame_id}.jpg").read_bytes()
108
+ except OSError:
109
+ return None
110
+ return read
111
+
112
+
113
+ def targets(output, stem, want_gif, want_mp4):
114
+ if output is None or output.is_dir() or str(output).endswith("/"):
115
+ folder = output if output is not None else Path.home() / "Downloads"
116
+ return {ext: (folder, stem, False) for ext, wanted in (("gif", want_gif), ("mp4", want_mp4)) if wanted}
117
+ base = output.with_suffix("") if output.suffix.lower() in (".gif", ".mp4") else output
118
+ return {ext: (base.parent, base.name, True) for ext, wanted in (("gif", want_gif), ("mp4", want_mp4)) if wanted}
119
+
120
+
121
+ def _write(folder, stem, extension, data, overwrite):
122
+ from .api_errors import APIError
123
+ from .downloads import save_export_bytes
124
+ if overwrite:
125
+ target = Path(folder) / f"{stem}.{extension}"
126
+ try:
127
+ target.parent.mkdir(parents=True, exist_ok=True)
128
+ target.write_bytes(data)
129
+ except OSError as error:
130
+ raise ExportError(f"Couldn’t write {target}: {error.strerror or error}.") from None
131
+ return {"name": target.name, "path": str(target)}
132
+ try:
133
+ return save_export_bytes(stem, extension, data, folder)
134
+ except APIError as error:
135
+ raise ExportError(str(error)) from None
136
+ except OSError as error:
137
+ raise ExportError(f"Couldn’t write to {folder}: {error.strerror or error}.") from None
138
+
139
+
140
+ def _size(count):
141
+ return f"{count / 1_000_000:.1f} MB" if count >= 100_000 else f"{max(1, round(count / 1000))} KB"
142
+
143
+
144
+ def _home(path):
145
+ home = str(Path.home())
146
+ return "~" + path[len(home):] if path.startswith(home + "/") else path
147
+
148
+
149
+ def to_eval(args, stream=None, errors=None):
150
+ """`mobster history --to-eval ID`: a finished task into the local eval set, beside its journal
151
+ (traces.add_to_evals).
152
+ Exit 0 added (or already there), 1 couldn't."""
153
+ from . import traces
154
+ stream, errors = stream or sys.stdout, errors or sys.stderr
155
+ try:
156
+ record, journal = find_run(args.to_eval)
157
+ try:
158
+ result = traces.add_to_evals(record, run_folder(journal, record.get("id") or ""),
159
+ Path(journal).parent / "evals")
160
+ except traces.EvalError as error:
161
+ raise ExportError(str(error)) from None
162
+ except OSError as error:
163
+ raise ExportError(f"Couldn’t write the eval: {error.strerror or error}.") from None
164
+ except ExportError as error:
165
+ errors.write(f"mobster history: {error}\n")
166
+ return 1
167
+ if args.json:
168
+ stream.write(json.dumps({"ok": True, **result}, ensure_ascii=False) + "\n")
169
+ return 0
170
+ expected = result["expected"]
171
+ stream.write(f"{'Added' if result['added'] else 'Already in your evals:'} {result['id']}\n")
172
+ stream.write("It failed, so a fix is a change in how it acts: `python -m mobile_agent.bench evals` replays it "
173
+ "offline.\n" if expected["replay"] == "changes" else
174
+ "It finished, so a change must keep it acting the same: `python -m mobile_agent.bench evals` "
175
+ "replays it offline.\n")
176
+ stream.write(f"The eval set is {_home(str(Path(result['path']).parent))}\n")
177
+ return 0
178
+
179
+
180
+ def export_trace(args, stream, errors):
181
+ """``--trace``: the run's bundle as a zip in -o (a folder, or a file name) or ~/Downloads, never over a file
182
+ that is there. Exit 0 saved, 1 couldn't."""
183
+ from . import traces
184
+ try:
185
+ record, journal = find_run(args.run_id)
186
+ if record.get("status") in ACTIVE:
187
+ raise ExportError("This task is still running. Save it once it ends.")
188
+ run_id = record.get("id") or ""
189
+ data, manifest = traces.bundle(record, run_folder(journal, run_id),
190
+ read_frame=frame_reader(journal, run_id) if args.screens else None)
191
+ output = args.output
192
+ try:
193
+ if output is not None and not output.is_dir() and not str(output).endswith("/"):
194
+ output.parent.mkdir(parents=True, exist_ok=True)
195
+ output.write_bytes(data)
196
+ saved = output
197
+ else:
198
+ saved = traces.save(data, output if output is not None else Path.home() / "Downloads",
199
+ f"mobster-trace-{run_id}")
200
+ except OSError as error:
201
+ raise ExportError(f"Couldn’t write the bundle: {error.strerror or error}.") from None
202
+ except ExportError as error:
203
+ if args.json:
204
+ stream.write(json.dumps({"ok": False, "error": str(error)}) + "\n")
205
+ else:
206
+ errors.write(f"mobster export: {error}\n")
207
+ return 1
208
+ result = {"ok": True, "id": run_id, "trace": {"path": str(saved), "bytes": len(data), "tape": manifest["tape"],
209
+ "screens": manifest["screens"]}}
210
+ if args.json:
211
+ stream.write(json.dumps(result, ensure_ascii=False) + "\n")
212
+ return 0
213
+ tape = "with its tape" if manifest["tape"]["included"] else "without a tape"
214
+ stream.write(f"Saved {saved.name} ({_size(len(data))}, {tape}) to {_home(str(saved.parent))}\n")
215
+ if not manifest["tape"]["included"]:
216
+ stream.write(manifest["tape"]["why"][0].upper() + manifest["tape"]["why"][1:] + ".\n")
217
+ stream.write(("It holds the task's text and the text on its screens, and the screenshots, which can't be masked. "
218
+ if args.screens else "It holds the task's text and the text on its screens. ")
219
+ + "Keys, codes and passwords are masked. Read it before you share it.\n")
220
+ return 0
221
+
222
+
223
+ def export(args, stream=None, errors=None):
224
+ from . import run_gif
225
+ stream, errors = stream or sys.stdout, errors or sys.stderr
226
+ if args.trace:
227
+ if args.gif or args.mp4:
228
+ errors.write("mobster export: --trace saves a bundle; save a GIF or an MP4 in another command.\n")
229
+ return 2
230
+ return export_trace(args, stream, errors)
231
+ if args.screens:
232
+ errors.write("mobster export: --screens goes with --trace.\n")
233
+ return 2
234
+ if not args.gif and not args.mp4:
235
+ errors.write("mobster export: say what to save: --gif, --mp4, --trace, or a GIF and an MP4. Example: "
236
+ "mobster export latest --gif\n")
237
+ return 2
238
+ try:
239
+ record, journal = find_run(args.run_id)
240
+ if record.get("status") in ACTIVE:
241
+ raise ExportError("This task is still running. Save it once it ends.")
242
+ read = frame_reader(journal, record.get("id") or "")
243
+ ffmpeg = run_gif.ffmpeg_path() if args.mp4 else None
244
+ if args.mp4 and ffmpeg is None:
245
+ raise ExportError("--mp4 needs ffmpeg. Install it with `brew install ffmpeg`, or save a GIF with --gif.")
246
+ stem = run_gif.file_stem(record.get("goal"))
247
+ places = targets(args.output, stem, args.gif, args.mp4)
248
+ result = {"ok": True, "id": record.get("id"), "gif": None, "mp4": None}
249
+ if args.gif:
250
+ try:
251
+ clip = run_gif.build_clip(record, read, theme=args.theme, redact=args.redact)
252
+ except ValueError as error:
253
+ raise ExportError(str(error)) from None
254
+ folder, name, overwrite = places["gif"]
255
+ saved = _write(folder, name, "gif", clip.gif, overwrite)
256
+ result["gif"] = {**saved, "bytes": len(clip.gif), "frames": clip.frames, "seconds": round(clip.seconds, 1)}
257
+ stem = Path(saved["name"]).stem
258
+ if args.mp4:
259
+ try:
260
+ data = run_gif.build_mp4(record, read, theme=args.theme, redact=args.redact, ffmpeg=ffmpeg)
261
+ except ValueError as error:
262
+ raise ExportError(str(error)) from None
263
+ except (RuntimeError, OSError) as error:
264
+ raise ExportError(str(error)) from None
265
+ folder, name, overwrite = places["mp4"]
266
+ saved = _write(folder, stem if args.gif and not overwrite else name, "mp4", data, overwrite)
267
+ result["mp4"] = {**saved, "bytes": len(data)}
268
+ except ExportError as error:
269
+ if args.json:
270
+ stream.write(json.dumps({"ok": False, "error": str(error)}) + "\n")
271
+ else:
272
+ errors.write(f"mobster export: {error}\n")
273
+ return 1
274
+ if args.json:
275
+ stream.write(json.dumps(result, ensure_ascii=False) + "\n")
276
+ return 0
277
+ for kind in ("gif", "mp4"):
278
+ saved = result[kind]
279
+ if saved:
280
+ facts = _size(saved["bytes"]) + (f", {saved['seconds']:g} s" if kind == "gif" else "")
281
+ stream.write(f"Saved {saved['name']} ({facts}) to {_home(str(Path(saved['path']).parent))}\n")
282
+ if not args.redact:
283
+ stream.write(REDACT_HINT + "\n")
284
+ return 0