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,104 @@
1
+ """`mobster workflow show NAME`: print a saved workflow's graph from the running Mobster (client.py)."""
2
+
3
+ import json
4
+ import sys
5
+
6
+ from ..graph import chain as graph_chain
7
+ from ..graph.catalog import TYPES
8
+ from .client import Client, NoService, ServiceError
9
+
10
+ WORDS = ("text", "question", "instruction", "message") # the field that says what a step does, in the person's words
11
+ WORDS_SHOWN = 70
12
+ NOT_RUNNING = "Mobster isn't running here. Open Mobster or run `mobster serve`, then try again."
13
+
14
+
15
+ def add_arguments(parser, helpers):
16
+ subs = parser.add_subparsers(dest="workflow_command", metavar="<subcommand>", required=True)
17
+ show = subs.add_parser("show", help="print a workflow's steps in the order they run",
18
+ formatter_class=parser.formatter_class,
19
+ description="Print a saved workflow's steps in the order they run: each one's id, type, "
20
+ "title and the steps it waits for, with a loop's steps under it. It shows "
21
+ "the published version unless you add --draft, and changes nothing.",
22
+ epilog='examples:\n mobster workflow show "Answer DMs"\n'
23
+ " mobster workflow show 3f9c2a1b7d0e --draft --json")
24
+ show.add_argument("name", metavar="NAME", help="the workflow's name or id")
25
+ show.add_argument("--draft", action="store_true", help="the draft you're editing, not the published version")
26
+ show.add_argument("--json", action="store_true", help="print the steps as JSON")
27
+ show.add_argument("--url", metavar="URL", help="Mobster's address (default: $MOBSTER_URL, else the running Mac "
28
+ "app's, else http://127.0.0.1:8765)")
29
+
30
+
31
+ def steps(graph):
32
+ """[{id, type, title, after: [{node, port}], parent, depth, words}] in the order they run (graph/chain.ordered)."""
33
+ nodes = {node["id"]: node for node in graph.get("nodes") or ()}
34
+ into = {}
35
+ for edge in graph.get("edges") or ():
36
+ into.setdefault(edge["target"], []).append({"node": edge["source"], "port": edge.get("sourcePort") or "out"})
37
+
38
+ def depth(node):
39
+ level, parent = 0, node.get("parent")
40
+ while parent in nodes and level < 10:
41
+ level, parent = level + 1, nodes[parent].get("parent")
42
+ return level
43
+ out = []
44
+ for node in graph_chain.ordered(graph):
45
+ kind, config = node["type"], node.get("config") or {}
46
+ words = next((config[field] for field in WORDS if isinstance(config.get(field), str)), None)
47
+ out.append({"id": node["id"], "type": kind, "title": node.get("title")
48
+ or getattr(TYPES.get(kind), "label", kind),
49
+ "after": into.get(node["id"], []), "parent": node.get("parent"), "depth": depth(node),
50
+ "words": " ".join(words.split()) if words else None})
51
+ return out
52
+
53
+
54
+ def lines(workflow, revision, shown):
55
+ """The text `show` prints: a heading, then one line per step."""
56
+ which = f"published revision {revision}" if revision else "the draft"
57
+ head = f"“{workflow.get('name')}” ({workflow.get('id')}): {which}, {len(shown)} steps"
58
+ width = max((len(step["id"]) + 2 * step["depth"] for step in shown), default=0)
59
+ out = [head]
60
+ for step in shown:
61
+ waits = ", ".join(a["node"] + (f" ({a['port']})" if a["port"] != "out" else "") for a in step["after"])
62
+ line = f" {' ' * step['depth']}{step['id']:<{width - 2 * step['depth']}} {step['title']} ({step['type']})"
63
+ if waits:
64
+ line += f", after {waits}"
65
+ if step["words"]:
66
+ words = step["words"]
67
+ line += f": “{words[:WORDS_SHOWN]}{'…' if len(words) > WORDS_SHOWN else ''}”"
68
+ out.append(line)
69
+ return out
70
+
71
+
72
+ def run(args):
73
+ if args.workflow_command != "show":
74
+ return 2
75
+ client = Client(getattr(args, "url", None))
76
+ try:
77
+ try:
78
+ workflow = client.find_workflow(args.name)
79
+ except LookupError as error:
80
+ return _fail(str(error), 1)
81
+ revision = None if args.draft else (workflow.get("published") or {}).get("revision")
82
+ graph = workflow.get("graph") or {}
83
+ if revision:
84
+ graph = (client.call("GET", f"/api/workflows/{workflow['id']}/versions/{revision}").get("workflow")
85
+ or {}).get("graph") or {}
86
+ except NoService:
87
+ return _fail(NOT_RUNNING, 3)
88
+ except ServiceError as error:
89
+ return _fail(str(error), 1)
90
+ shown = steps(graph)
91
+ try:
92
+ if args.json:
93
+ print(json.dumps({"workflowId": workflow.get("id"), "name": workflow.get("name"), "revision": revision,
94
+ "draft": revision is None, "steps": shown}, ensure_ascii=False), flush=True)
95
+ else:
96
+ print("\n".join(lines(workflow, revision, shown)), flush=True)
97
+ except BrokenPipeError:
98
+ pass
99
+ return 0
100
+
101
+
102
+ def _fail(message, code):
103
+ print(f"mobster workflow: {message}", file=sys.stderr, flush=True)
104
+ return code
mobile_agent/tls.py ADDED
@@ -0,0 +1,36 @@
1
+ """Where HTTPS certificates are verified from: macOS's bundle for the frozen app or a Python missing its own."""
2
+
3
+ import os
4
+ import ssl
5
+ import sys
6
+
7
+ SYSTEM_BUNDLE = "/etc/ssl/cert.pem"
8
+
9
+
10
+ def _own_paths_exist():
11
+ paths = ssl.get_default_verify_paths()
12
+ if paths.cafile and os.path.isfile(paths.cafile):
13
+ return True
14
+ return bool(paths.capath and os.path.isdir(paths.capath) and os.listdir(paths.capath))
15
+
16
+
17
+ def ensure_ca_bundle(environ=None, frozen=None, bundle=SYSTEM_BUNDLE):
18
+ """Point OpenSSL at macOS's bundle when this Python's own can't be trusted to exist."""
19
+ environ = os.environ if environ is None else environ
20
+ if environ.get("SSL_CERT_FILE") or environ.get("SSL_CERT_DIR"):
21
+ return None
22
+ frozen = getattr(sys, "frozen", False) if frozen is None else frozen
23
+ if not frozen and _own_paths_exist():
24
+ return None
25
+ if not os.path.isfile(bundle):
26
+ return None
27
+ environ["SSL_CERT_FILE"] = bundle
28
+ return bundle
29
+
30
+
31
+ def report():
32
+ """For `mobster version --json`: the CA file TLS verifies against and how many CAs load from it."""
33
+ context = ssl.create_default_context()
34
+ return {"caFile": ssl.get_default_verify_paths().cafile,
35
+ "caCerts": context.cert_store_stats().get("x509_ca", 0)}
36
+
mobile_agent/traces.py ADDED
@@ -0,0 +1,271 @@
1
+ """Traces: a run's tape while it runs, and its bundle after.
2
+
3
+ ``MOBSTER_EXPERIMENTAL_TRACES=1`` turns traces on. A run in the app or the terminal then records a tape
4
+ (bench/tape.py): the request, every screen the agent read as data, every model reply and every action, in a private
5
+ file beside the run's frames. The secret filter masks each entry as it is written. Without durable history there is
6
+ nowhere to keep it, so nothing is recorded.
7
+
8
+ ``mobster export ID --trace`` packs one finished run into a bundle (a zip):
9
+
10
+ manifest.json the run's facts: task, app, status, outcome, cost, model, counts, and what the bundle holds
11
+ events.jsonl the run's journal, one event a line: decisions, actions, refusals, cost and latency per call
12
+ tape.jsonl.gz the tape, when the run recorded one: replay it with bench/tape_replay.py
13
+ screens/*.jpg the run's screenshots, only with --screens
14
+
15
+ Every string in the bundle goes through ``secret_filter.redact``; the pictures cannot, so they stay out unless asked
16
+ for. A bundle holds task text and screen text. Nothing here uploads it.
17
+ """
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ import shutil
23
+ import tempfile
24
+ import time
25
+ from pathlib import Path
26
+
27
+ from . import __version__, failure_line, secret_filter
28
+
29
+ ENV_FLAG = "MOBSTER_EXPERIMENTAL_TRACES"
30
+ FORMAT = "mobster-trace/1"
31
+ TAPE = "tape.jsonl.gz" # bench/tape.py's NAME, gzipped when the run ends
32
+ BLOB = 4096 # a longer string with no space in it is a picture or a dump: never kept
33
+ MAX_DEPTH = 64
34
+ MAX_SCREENS_BYTES = 40_000_000
35
+ # What the bundle keeps of a run's summary: how it ended and what it cost, never its answer data or result cards.
36
+ SUMMARY_KEYS = ("status", "outcome", "reason", "answer", "code", "stop_code", "actions", "decisions", "elapsed_ms",
37
+ "costUsd", "model", "engine", "timing", "deviceKind", "failure")
38
+ RUN_KEYS = ("id", "goal", "appName", "mode", "engine", "status", "createdAt", "finishedAt", "costUsd", "origin")
39
+
40
+
41
+ def enabled(env=None):
42
+ return (os.environ if env is None else env).get(ENV_FLAG, "").strip().casefold() in ("1", "true", "yes", "on")
43
+
44
+
45
+ # -- masking --------------------------------------------------------------------------------------------------------
46
+
47
+ def mask(value, depth=0):
48
+ """``value`` (JSON-shaped) with the secret filter applied to every string and every key. Unlike
49
+ ``secret_filter.redact_deep``, nothing passes unfiltered: a long unbroken string is dropped, and so is
50
+ everything nested deeper than MAX_DEPTH."""
51
+ if isinstance(value, str):
52
+ if len(value) > BLOB and " " not in value[:BLOB]:
53
+ return f"[{len(value)} characters left out]"
54
+ return secret_filter.redact(value)
55
+ if isinstance(value, dict):
56
+ return {secret_filter.redact(str(key)): mask(item, depth + 1) for key, item in value.items()} \
57
+ if depth < MAX_DEPTH else {}
58
+ if isinstance(value, (list, tuple)):
59
+ return [mask(item, depth + 1) for item in value] if depth < MAX_DEPTH else []
60
+ return value
61
+
62
+
63
+ # -- recording ------------------------------------------------------------------------------------------------------
64
+
65
+ class Recording:
66
+ """One run's tape. ``attach`` is what ``engines.build_frontier`` calls with the pair the agent is built on: it
67
+ returns the same pair recording to a tape in the run's folder when traces are on and the run has a place to keep
68
+ it, and the pair unchanged otherwise. ``finish`` ends the tape with the run's outcome, once."""
69
+
70
+ def __init__(self, frames, run_id, request):
71
+ self.frames, self.run_id, self.request, self.tape = frames, run_id, request, None
72
+
73
+ def attach(self, driver, client, *, apps, frontier, hooks=()):
74
+ folder = self.frames.folder(self.run_id) if self.frames is not None and enabled() else None
75
+ if folder is None:
76
+ return driver, client
77
+ from .bench import tape as tape_module
78
+ try:
79
+ folder.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
80
+ os.chmod(folder.parent, 0o700)
81
+ folder.mkdir(exist_ok=True, mode=0o700)
82
+ except OSError:
83
+ return driver, client
84
+ tape = tape_module.Tape(folder / tape_module.NAME, scrub=mask)
85
+ tape.header(request=self.request, apps=apps, model=getattr(client, "model", None), frontier=frontier,
86
+ driver=driver, hooks=hooks)
87
+ self.tape = tape
88
+ return tape_module.TapeDriver(driver, tape), tape_module.TapeClient(client, tape)
89
+
90
+ def finish(self, outcome=None):
91
+ """End the tape; a run that ended without an outcome (an exception) ends it as an error. A tape that stopped
92
+ early (past its size, a write that failed) can't replay anything: it is removed."""
93
+ tape, self.tape = self.tape, None
94
+ if tape is None:
95
+ return
96
+ tape.end(outcome or {"status": "error"})
97
+ if tape.broken:
98
+ tape.path.unlink(missing_ok=True)
99
+ Path(str(tape.path) + ".gz").unlink(missing_ok=True)
100
+
101
+
102
+ # -- the bundle -----------------------------------------------------------------------------------------------------
103
+
104
+ def _frame_ids(value, found):
105
+ if isinstance(value, dict):
106
+ for key, item in value.items():
107
+ if key == "frameId" and isinstance(item, str):
108
+ found.setdefault(item)
109
+ else:
110
+ _frame_ids(item, found)
111
+ elif isinstance(value, list):
112
+ for item in value:
113
+ _frame_ids(item, found)
114
+ return found
115
+
116
+
117
+ def _facts(summary):
118
+ return {k: v for k, v in summary.items() if k in SUMMARY_KEYS}
119
+
120
+
121
+ def _lean(event):
122
+ """A run's last two events carry its whole summary (answer data, result cards): the bundle keeps the facts."""
123
+ if event.get("event") == "run_finished" and isinstance(event.get("summary"), dict):
124
+ return {**event, "summary": _facts(event["summary"])}
125
+ if event.get("event") == "result":
126
+ return {k: v for k, v in event.items() if k in SUMMARY_KEYS or k in ("event", "seq", "timestamp")}
127
+ return event
128
+
129
+
130
+ def masked_tape(path):
131
+ """The tape at ``path`` with every entry masked again, as gzip bytes, and its entry count; None when it can't
132
+ be read whole (a bundle never carries a tape it could not mask)."""
133
+ import gzip # export only: a run's start never loads gzip or zipfile (scripts/perf-budgets.json)
134
+ try:
135
+ text = gzip.decompress(Path(path).read_bytes()).decode("utf-8")
136
+ entries = [mask(json.loads(line)) for line in text.splitlines() if line.strip()]
137
+ except (OSError, ValueError, EOFError):
138
+ return None
139
+ body = "".join(json.dumps(entry, ensure_ascii=False, separators=(",", ":")) + "\n" for entry in entries)
140
+ return gzip.compress(body.encode("utf-8"), 6, mtime=0), len(entries)
141
+
142
+
143
+ def _jsonl(rows):
144
+ return "".join(json.dumps(row, ensure_ascii=False, separators=(",", ":")) + "\n" for row in rows).encode("utf-8")
145
+
146
+
147
+ def bundle(record_, folder, *, read_frame=None):
148
+ """(zip bytes, manifest) for a finished run. ``record_``: the run's journal row with its ``events``; ``folder``:
149
+ where the run's tape is (its frames folder), or None; ``read_frame(frame_id)``: bytes or None, given only when
150
+ the screenshots are wanted."""
151
+ import io
152
+ import zipfile
153
+ events = [mask(_lean(event)) for event in record_.get("events") or () if isinstance(event, dict)]
154
+ summary = record_.get("summary") if isinstance(record_.get("summary"), dict) else {}
155
+ facts = {k: record_.get(k) for k in RUN_KEYS}
156
+ facts["app"] = (record_.get("app") or {}).get("bundleId") if isinstance(record_.get("app"), dict) else None
157
+ facts["summary"] = _facts(summary)
158
+ members = {"events.jsonl": _jsonl(events)}
159
+ tape_file = Path(folder) / TAPE if folder is not None else None
160
+ packed = masked_tape(tape_file) if tape_file is not None and tape_file.is_file() else None
161
+ if packed is not None:
162
+ members[TAPE], entries = packed
163
+ tape_note = {"included": True, "entries": entries}
164
+ elif tape_file is not None and tape_file.exists():
165
+ tape_note = {"included": False, "why": "the tape could not be read whole, so it was left out"}
166
+ else:
167
+ tape_note = {"included": False, "why": f"this run recorded no tape (set {ENV_FLAG}=1 before it runs)"}
168
+ screens = {"included": False}
169
+ if read_frame is not None:
170
+ taken, size = {}, 0
171
+ for frame_id in _frame_ids(record_.get("events") or (), {}):
172
+ data = read_frame(frame_id)
173
+ if data and size + len(data) <= MAX_SCREENS_BYTES:
174
+ taken[f"screens/{frame_id}.jpg"] = bytes(data)
175
+ size += len(data)
176
+ members.update(taken)
177
+ screens = {"included": True, "count": len(taken)}
178
+ manifest = {"format": FORMAT, "run": mask(facts), "events": len(events), "tape": tape_note, "screens": screens,
179
+ "masked": "secret_filter"}
180
+ members = {"manifest.json": json.dumps(manifest, ensure_ascii=False, indent=1).encode("utf-8") + b"\n", **members}
181
+ out = io.BytesIO()
182
+ with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as archive:
183
+ for name, data in members.items():
184
+ info = zipfile.ZipInfo(name) # the 1980 date: nothing of this Mac's clock rides along
185
+ info.compress_type, info.external_attr = zipfile.ZIP_DEFLATED, 0o600 << 16
186
+ archive.writestr(info, data)
187
+ return out.getvalue(), manifest
188
+
189
+
190
+ def save(data, folder, stem):
191
+ """Write ``data`` as ``folder``/``stem``.zip, private, and never over a file that is there ("stem (1).zip"...).
192
+ The saved path; OSError when the folder can't be written."""
193
+ folder = Path(folder)
194
+ folder.mkdir(parents=True, exist_ok=True)
195
+ for index in range(1000):
196
+ target = folder / (f"{stem}.zip" if index == 0 else f"{stem} ({index}).zip")
197
+ try:
198
+ descriptor = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
199
+ except FileExistsError:
200
+ continue
201
+ with os.fdopen(descriptor, "wb") as stream:
202
+ stream.write(data)
203
+ return target
204
+ raise OSError("Too many files with that name")
205
+
206
+
207
+ # -- the local eval set ---------------------------------------------------------------------------------------------
208
+
209
+ class EvalError(Exception):
210
+ """A run that can't become an eval. ``code``: running, no_tape or unreadable; the message says what happened and
211
+ what to do."""
212
+
213
+ def __init__(self, code, message):
214
+ super().__init__(message)
215
+ self.code = code
216
+
217
+
218
+ def _slug(text):
219
+ return re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", str(text).lower())).strip("-")[:40].strip("-")
220
+
221
+
222
+ def add_to_evals(record_, folder, evals_dir):
223
+ """Keep a finished run as a regression task in the eval set at ``evals_dir`` (bench/trace_evals.py says what a
224
+ task is). ``record_``: the run's row with its ``events``; ``folder``: where its tape is. A run that failed expects
225
+ a replay that changes; one that didn't, a replay that holds. Adding the same run again changes nothing.
226
+ Returns {"id", "path", "added", "expected", "recorded"}; EvalError when the run is still going or has no tape."""
227
+ from .bench import trace_evals
228
+ if record_.get("status") in ("queued", "running") or record_.get("finishedAt") is None:
229
+ raise EvalError("running", "This task is still running. Add it to your evals once it ends.")
230
+ tape_file = Path(folder) / TAPE if folder is not None else None
231
+ if tape_file is None or not tape_file.is_file():
232
+ raise EvalError("no_tape", f"This task recorded no tape, so it can't be replayed. Set {ENV_FLAG}=1 and run "
233
+ "it again.")
234
+ packed = masked_tape(tape_file)
235
+ if packed is None:
236
+ raise EvalError("unreadable", "This task's tape could not be read whole. Run it again to record a new one.")
237
+ events = [event for event in record_.get("events") or () if isinstance(event, dict)]
238
+ summary = record_.get("summary") if isinstance(record_.get("summary"), dict) else {}
239
+ failure = summary.get("failure") or failure_line.failure_line(summary, events)
240
+ recorded = mask({"status": record_.get("status"), "outcome": summary.get("outcome"),
241
+ "steps": summary.get("decisions"), "actions": summary.get("actions"),
242
+ "costUsd": summary.get("costUsd"), "failure": failure})
243
+ expected = {"status": "completed" if failure else record_.get("status"),
244
+ "replay": "changes" if failure else "holds"}
245
+ goal = mask(record_.get("goal") or "")
246
+ task_id = f"{_slug(goal) or 'task'}-{record_['id']}"
247
+ task = {"format": trace_evals.FORMAT, "id": task_id, "added": time.strftime("%Y-%m-%d"), "goal": goal,
248
+ "app": (record_.get("app") or {}).get("bundleId") if isinstance(record_.get("app"), dict) else None,
249
+ "source": {"run": record_["id"], "version": __version__}, "recorded": recorded, "expected": expected}
250
+ target = Path(evals_dir) / task_id
251
+ result = {"id": task_id, "path": str(target), "expected": expected, "recorded": recorded}
252
+ if target.exists():
253
+ return {**result, "added": False}
254
+ Path(evals_dir).mkdir(parents=True, exist_ok=True, mode=0o700)
255
+ os.chmod(evals_dir, 0o700)
256
+ building = Path(tempfile.mkdtemp(prefix=f".{task_id}.", dir=evals_dir)) # a task appears whole or not at all
257
+ try:
258
+ for name, data in ((trace_evals.TASK, (json.dumps(task, ensure_ascii=False, indent=1) + "\n").encode()),
259
+ (trace_evals.TAPE, packed[0])):
260
+ descriptor = os.open(building / name, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
261
+ with os.fdopen(descriptor, "wb") as stream:
262
+ stream.write(data)
263
+ try:
264
+ os.rename(building, target)
265
+ except OSError:
266
+ if not target.exists():
267
+ raise
268
+ return {**result, "added": False} # the same run, added at the same moment
269
+ finally:
270
+ shutil.rmtree(building, ignore_errors=True)
271
+ return {**result, "added": True}
mobile_agent/tracks.py ADDED
@@ -0,0 +1,235 @@
1
+ """Static registration of the SOTA tracks (seam S1). Static imports so PyInstaller follows them.
2
+
3
+ ``load()`` imports each track's ``register`` module and calls ``register(api)`` (api = harness_api) inside
4
+ ``_staged()``: every seam registry (harness_api's, api_routes, journal.SCHEMAS, mcp_server.registry) is
5
+ snapshotted first and restored if register raises, so a track that raises leaves nothing registered. It is
6
+ recorded in STATUS and skipped: Mobster still starts. ``load`` does no I/O beyond imports.
7
+
8
+ Callers: server.Runtime.__init__ (first line), mcp_server.cli at startup, and each new CLI command.
9
+ """
10
+
11
+ import threading
12
+ from contextlib import contextmanager
13
+
14
+ from . import harness_api as api
15
+
16
+ TRACKS = ("harness", "threads", "memory", "attachments", "wireless", "testkit", "remote", "accounts", "learned",
17
+ "longform", "outlets", "connectors", "plugins", "permissions", "packs", "app_api", "waiting", "netxray",
18
+ "content_queue", "feedback", "ios", "teams", "xray", "triggers", "projects", "corpus")
19
+ STATUS: dict = {}
20
+ _loaded = False
21
+ _lock = threading.Lock()
22
+
23
+
24
+ def _importers():
25
+ """(track, importer) pairs. Each importer is a plain ``from .x import register`` (PyInstaller follows it)."""
26
+ def harness():
27
+ from .harness import register
28
+ return register
29
+
30
+ def threads():
31
+ from .threads import register
32
+ return register
33
+
34
+ def memory():
35
+ from .memory import register
36
+ return register
37
+
38
+ def attachments():
39
+ from .attachments import register
40
+ return register
41
+
42
+ def wireless():
43
+ from .wireless import register
44
+ return register
45
+
46
+ def testkit():
47
+ from .testkit import register
48
+ return register
49
+
50
+ def remote():
51
+ from .remote import register
52
+ return register
53
+
54
+ def accounts():
55
+ from .accounts import register
56
+ return register
57
+
58
+ def learned():
59
+ from .learned import register
60
+ return register
61
+
62
+ def longform():
63
+ from .longform import register
64
+ return register
65
+
66
+ def outlets():
67
+ from .outlets import register
68
+ return register
69
+
70
+ def connectors():
71
+ from .connectors import register
72
+ return register
73
+
74
+ def plugins():
75
+ from .plugins import register
76
+ return register
77
+
78
+ def permissions():
79
+ from .permissions import register
80
+ return register
81
+
82
+ def packs():
83
+ from .packs import register
84
+ return register
85
+
86
+ def app_api():
87
+ from .app_api import register
88
+ return register
89
+
90
+ def waiting():
91
+ from .waiting import register
92
+ return register
93
+
94
+ def netxray():
95
+ from .netxray import register
96
+ return register
97
+
98
+ def content_queue():
99
+ from .content_queue import register
100
+ return register
101
+
102
+ def feedback():
103
+ from .feedback import register
104
+ return register
105
+
106
+ def ios():
107
+ from .ios import register
108
+ return register
109
+
110
+ def triggers():
111
+ from .triggers import register
112
+ return register
113
+
114
+ def teams():
115
+ from .teams import register
116
+ return register
117
+
118
+ def xray():
119
+ from .xray import register
120
+ return register
121
+
122
+ def projects():
123
+ from .projects import register
124
+ return register
125
+
126
+ def corpus():
127
+ from .corpus import register
128
+ return register
129
+
130
+ return (("harness", harness), ("threads", threads), ("memory", memory), ("attachments", attachments),
131
+ ("wireless", wireless), ("testkit", testkit), ("remote", remote), ("accounts", accounts),
132
+ ("learned", learned), ("longform", longform), ("outlets", outlets),
133
+ ("connectors", connectors), ("plugins", plugins),
134
+ ("permissions", permissions), ("packs", packs), ("app_api", app_api),
135
+ ("waiting", waiting), ("netxray", netxray),
136
+ ("content_queue", content_queue), ("feedback", feedback), ("ios", ios), ("teams", teams), ("xray", xray),
137
+ ("triggers", triggers), ("projects", projects), ("corpus", corpus))
138
+
139
+
140
+ def _registries():
141
+ """(snapshot, restore) pairs of every seam registry a track's register() may touch."""
142
+ from . import api_routes, journal
143
+ from .mcp_server import registry as mcp_registry
144
+ pairs = [(api._snapshot, api._restore)]
145
+ pairs.append((lambda: list(api_routes._routes), lambda saved: _replace(api_routes._routes, saved)))
146
+ pairs.append((lambda: dict(journal.SCHEMAS), lambda saved: _replace(journal.SCHEMAS, saved)))
147
+ pairs.append((lambda: dict(journal.SCHEMA_OWNERS), lambda saved: _replace(journal.SCHEMA_OWNERS, saved)))
148
+ pairs.append((lambda: list(mcp_registry._providers), lambda saved: _replace(mcp_registry._providers, saved)))
149
+ return pairs
150
+
151
+
152
+ def _replace(target, saved):
153
+ target.clear()
154
+ if isinstance(target, dict):
155
+ target.update(saved)
156
+ else:
157
+ target.extend(saved)
158
+
159
+
160
+ @contextmanager
161
+ def _staged(track):
162
+ """Registrations inside belong to ``track``; if the body raises, every seam registry goes back to how it was."""
163
+ pairs = _registries()
164
+ saved = [(snapshot(), restore) for snapshot, restore in pairs]
165
+ try:
166
+ with api.owned_by(track):
167
+ yield
168
+ except BaseException:
169
+ for value, restore in saved:
170
+ restore(value)
171
+ raise
172
+
173
+
174
+ def register_one(track, register):
175
+ """Run one track's ``register(api)`` staged; record STATUS. Returns True when it registered."""
176
+ try:
177
+ with _staged(track):
178
+ register(api)
179
+ except Exception as error: # noqa: BLE001 -- a track never stops Mobster from starting
180
+ STATUS[track] = f"error: {type(error).__name__}"
181
+ return False
182
+ STATUS[track] = "ok"
183
+ return True
184
+
185
+
186
+ def load() -> None:
187
+ """Idempotent: register every track once per process."""
188
+ global _loaded
189
+ with _lock:
190
+ if _loaded:
191
+ return
192
+ _loaded = True
193
+ for track, importer in _importers():
194
+ try:
195
+ module = importer()
196
+ except ModuleNotFoundError as error:
197
+ # A track left out of this build (Remote isn't in the open-source tree) is absent, not broken.
198
+ own = error.name == f"{__package__}.{track}"
199
+ STATUS[track] = "absent" if own else f"error: {type(error).__name__}"
200
+ continue
201
+ except Exception as error: # noqa: BLE001
202
+ STATUS[track] = f"error: {type(error).__name__}"
203
+ continue
204
+ register_one(track, module.register)
205
+ try: # GET /api/access/full-disk belongs to no track, so it never answers 503 (full_disk_access.py)
206
+ from .full_disk_access import register_route
207
+ register_route()
208
+ except Exception: # noqa: BLE001 -- the route is an extra; Mobster still starts
209
+ pass
210
+ try: # /api/project/env belongs to no track either
211
+ from .project_env_routes import register_routes
212
+ register_routes()
213
+ except Exception: # noqa: BLE001
214
+ pass
215
+
216
+
217
+ def disable(track, reason) -> None:
218
+ """Turn ``track`` off (journal.register_schema: "error: newer data" or "error: migration")."""
219
+ STATUS[track] = reason
220
+ api.disable(track)
221
+
222
+
223
+ def reset_for_tests() -> None:
224
+ """Forget every registration and allow ``load`` again."""
225
+ global _loaded
226
+ from . import api_routes, journal
227
+ from .mcp_server import registry as mcp_registry
228
+ with _lock:
229
+ _loaded = False
230
+ STATUS.clear()
231
+ api.reset_for_tests()
232
+ api_routes._routes.clear()
233
+ journal.SCHEMAS.clear()
234
+ journal.SCHEMA_OWNERS.clear()
235
+ mcp_registry._providers.clear()