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
mobile_agent/server.py ADDED
@@ -0,0 +1,363 @@
1
+ """Loopback-only dashboard API with SSE, cancellation, and one device owner at a time."""
2
+
3
+ import threading
4
+ import time
5
+ from http.server import ThreadingHTTPServer
6
+ from pathlib import Path
7
+
8
+ from . import engine_health, engines, eventbus, harness_api, ownership, plan_gate, tracks
9
+ from .api_devices import DeviceRoutesMixin
10
+ from .api_get import GetRoutesMixin
11
+ from .api_http import ApiBase
12
+ from .api_post import WriteRoutesMixin
13
+ from .app_icons import AppIcons
14
+ from .cli_constants import DEFAULT_WDA_URL
15
+ from .costs import NANODOLLARS
16
+ from .device_info import DeviceInfo
17
+ from .device_manager import DeviceManager
18
+ from .fanout import GROUP_ID, Fanout
19
+ from .fleet import Fleet
20
+ from .installed_apps import InstalledApps, InventoryCache
21
+ from .journal import Journal, JournalError
22
+ from .manual_control import ManualControl
23
+ from .paths import user_data_dir
24
+ from .pool import default_mjpeg_url
25
+ from .power_watch import PowerWatch
26
+ from .run import Run
27
+ from .runtime_create import TaskStartMixin
28
+ from .runtime_driver import PhoneDriverMixin
29
+ from .runtime_limits import LimitsMixin
30
+ from .runtime_queue import PhoneQueueMixin
31
+ from .runtime_runs import RunActionsMixin
32
+ from .runtime_status import StatusMixin
33
+ from .runtime_teach import LiveViewMixin
34
+ from .runtime_work import TaskWorkMixin
35
+ from .setup_service import SetupService
36
+ from .wda.frame_store import FrameStore
37
+ from .wda.video import WdaVideo
38
+ from .workflows.service import Workflows
39
+
40
+ DONE_ACTION_EVENTS = frozenset({"action_acknowledged", "frontier_action"})
41
+ ACTING_EVENTS = frozenset({"action_started", "app_launch_started", "frontier_action", "frontier_failed",
42
+ "skill_started",
43
+ "frontier_app_relaunched", "frontier_cleanup"})
44
+ IDLE_OPERATIONS = frozenset({None, "DONE", "BLOCKED", "WAIT"})
45
+
46
+
47
+ def may_have_acted(event):
48
+ # Fails closed: a decision is journaled before it is sent, and one whose ops can't be read counts as acting.
49
+ kind = event.get("event")
50
+ if kind in ACTING_EVENTS:
51
+ return True
52
+ if kind != "frontier_decision":
53
+ return False
54
+ ops = event.get("ops")
55
+ return not isinstance(ops, list) or any(op not in IDLE_OPERATIONS for op in ops)
56
+
57
+
58
+ class Runtime(PhoneQueueMixin, PhoneDriverMixin, LiveViewMixin, LimitsMixin, StatusMixin, TaskStartMixin, TaskWorkMixin,
59
+ RunActionsMixin):
60
+ # `mobster run` and the terminal UI: a task that names no device runs on the one target, without the device list.
61
+ one_target = False
62
+ plan_gate = None
63
+ # The demo (a scripted phone and model) never acts on a phone, so it needs no plan.
64
+ needs_plan = True
65
+ # The sentence a yes to an outside agent's approval is refused with, or None: the hosted review demo
66
+ # (remote/review.py) sets it, and Run.answer_approval then takes no yes from the Mac window, a link or the console.
67
+ approvals_closed = None
68
+ # The device list's class (fleet.Fleet); a scripted world of several phones gives its own (tests/fleet_world.py).
69
+ fleet_class = Fleet
70
+
71
+ def __init__(self, config):
72
+ # The tracks register first: their schemas migrate when the journal opens below.
73
+ tracks.load()
74
+ self.config = config
75
+ self.runs = {}
76
+ self.lock = threading.Lock()
77
+ self._conversation_apps = {}
78
+ self.listeners = harness_api.ListenerQueue()
79
+ self.bus = eventbus.bus
80
+ # run id -> device id: one live run per device.
81
+ self.active_runs = {}
82
+ self.queues = ownership.Queues(ttl=ownership.ttl_seconds())
83
+ self.queued = {}
84
+ self.queue_timers = {}
85
+ self.pool = self.make_pool(config)
86
+ self.inventory = {}
87
+ self.inventory_at = 0
88
+ self.inventory_lock = threading.Lock()
89
+ self.inventory_ok = False
90
+ self.wda_health_at, self.wda_ready = 0, False
91
+ # Re-resolved from WDA itself: a runner restart replaces the session, so a fixed --session would fail.
92
+ self.wda_session_id, self.wda_session_lock = getattr(config, "session", None), threading.Lock()
93
+ self.closing = False
94
+ self.threads = set()
95
+ self.streams = threading.BoundedSemaphore(8)
96
+ # Set by ``serve`` once it listens; Mobster Remote's bridge waits on ``serving``.
97
+ self.local_api = None
98
+ self.serving = threading.Event()
99
+ self.device_info = self.make_device_info(config)
100
+ self.awake = self.make_awake_lease()
101
+ self.power = PowerWatch(self)
102
+ # WDA's MJPEG port is paired with its own (8100 -> 9100, 8203 -> 9203), never another device's stream.
103
+ self.video = (WdaVideo(config.wda_url, mjpeg_url=default_mjpeg_url(config.wda_url),
104
+ session=self.current_wda_session,
105
+ on_activity=self.video_activity, quality=self.video_quality())
106
+ if getattr(config, "wda_url", None) else self.make_video(config))
107
+ self.control = ManualControl(config.wda_url, self.wda_session) if getattr(config, "wda_url", None) else None
108
+ self.controlled_at = {}
109
+ self.lessons, self.lessons_lock = {}, threading.Lock()
110
+ self.journal = Journal(getattr(config, "state_db", None))
111
+ state_db = getattr(config, "state_db", None)
112
+ self.frames = FrameStore(Path(state_db).parent / "frames" if state_db else None)
113
+ self.model_reach = engines.ModelReach()
114
+ self.scheduler = None
115
+ self.manager = self.setup = None
116
+ self.installed_apps = None
117
+ data_dir = Path(getattr(config, "data_dir", None) or user_data_dir())
118
+ self.plan_gate = plan_gate.for_runtime(config, data_dir) if self.needs_plan else None
119
+ self.icons = AppIcons(data_dir / "app-icons", allowed=self.icon_bundles)
120
+ if getattr(config, "manage_device", False):
121
+ env_file = getattr(config, "env_file", None) or data_dir / "agent.env"
122
+ self.manager = DeviceManager(data_dir, config.wda_url or DEFAULT_WDA_URL)
123
+ self.setup = SetupService(self, self.manager, env_file)
124
+ self.device_info = DeviceInfo(manager=self.manager)
125
+ threading.Thread(target=self.manager.watch, args=(self.manager.watching,), name="mobster-device-watch",
126
+ daemon=True).start()
127
+ # --socket exists only with the optional extension, which the signed app doesn't ship.
128
+ if not getattr(config, "socket", None):
129
+ self.installed_apps = InstalledApps(self.manager.device,
130
+ cache=InventoryCache(data_dir / "installed-apps"),
131
+ over_wifi=self.manager.wifi_udid)
132
+ self.fleet = self.fleet_class(self)
133
+ self.engine_health = engine_health.EngineHealth(publish=self.publish, boot=self.bus.boot)
134
+ self.engine_sampler = engine_health.Sampler(self)
135
+ try:
136
+ self.workflows = Workflows(self)
137
+ for item in self.journal.load():
138
+ run = Run(item["app"], item["goal"], item["mode"], id=item["id"],
139
+ status=item["status"], created_at=item["createdAt"] / 1000,
140
+ finished_at=item["finishedAt"] / 1000 if item["finishedAt"] is not None else None,
141
+ events=item["events"], summary=item["summary"],
142
+ idempotency_key=item.get("idempotencyKey"), output_schema=item.get("outputSchema"),
143
+ output_format=item.get("outputFormat", "json"),
144
+ allowed_bundles=(frozenset(item["allowedBundles"]) if item.get("allowedBundles") else None),
145
+ journal=self.journal, engine=item.get("engine"), estimate_usd=item.get("estimateUsd"),
146
+ reviewed_ok=item.get("reviewedOk") is True, frames=self.frames,
147
+ device_id=item.get("device"), device_name=item.get("deviceName"),
148
+ group_id=item["groupId"] if isinstance(item.get("groupId"), str)
149
+ and GROUP_ID.fullmatch(item["groupId"]) else None,
150
+ extras=dict(item.get("extras") or {}),
151
+ origin=item.get("origin") if item.get("origin") in harness_api.ORIGINS else "api",
152
+ pinned_apps=item.get("pinnedApps") if isinstance(item.get("pinnedApps"), list) else None,
153
+ block_state=dict(item["blockState"]) if isinstance(item.get("blockState"), dict) else {},
154
+ shares=[dict(x) for x in item["shares"] if isinstance(x, dict)][:50]
155
+ if isinstance(item.get("shares"), list) else [],
156
+ issues=[dict(x) for x in item["issues"] if isinstance(x, dict)][:50]
157
+ if isinstance(item.get("issues"), list) else [])
158
+ if isinstance(item.get("costUsd"), (int, float)):
159
+ run.cost_nanodollars, run.cost_known = round(item["costUsd"] * NANODOLLARS), True
160
+ self.runs[run.id] = run
161
+ if run.finished_at is None:
162
+ # A task that waited for a held phone never started a worker: it acted on nothing.
163
+ waited = not any(e.get("event") == "run_started" or may_have_acted(e) for e in run.events)
164
+ run.finish({"event": "result", "status": "interrupted", "independently_verified": False,
165
+ "reason": ownership.QUEUE_RESTART if waited else
166
+ "The service restarted. An in-flight action may have run; this task was not resumed.",
167
+ "actions": sum(e["event"] in DONE_ACTION_EVENTS for e in run.events),
168
+ "actions_may_have_run": any(may_have_acted(e) for e in run.events)})
169
+ if run.summary.get("persistence_failed"):
170
+ raise JournalError("Interrupted task recovery could not be saved")
171
+ except Exception:
172
+ self.journal.close()
173
+ raise
174
+ self.fanout = Fanout(self)
175
+ self.start_services()
176
+
177
+ def start_services(self):
178
+ self._services = list(harness_api.services())
179
+ for start, _close in self._services:
180
+ try:
181
+ start(self)
182
+ except Exception: # noqa: BLE001 -- a track's service never stops Mobster from starting
183
+ harness_api.log.exception("a service failed to start")
184
+
185
+ def close_services(self):
186
+ for _start, close in reversed(getattr(self, "_services", ())):
187
+ try:
188
+ close(self)
189
+ except Exception: # noqa: BLE001
190
+ harness_api.log.exception("a service failed to close")
191
+ self._services = []
192
+
193
+ def scripted_models(self):
194
+ return type(self).smart_client is not Runtime.smart_client
195
+
196
+ def device_busy(self, device_id):
197
+ with self.lock:
198
+ return any(value == device_id for value in self.active_runs.values()) or any(
199
+ getattr(run, "device_id", None) == device_id and run.id in self.active_runs
200
+ for run in self.runs.values())
201
+
202
+ def run_context(self, run):
203
+ phone = self.phone_for(run) if getattr(self, "fleet", None) is not None else None
204
+ kind = getattr(phone, "kind", None) if phone is not None else None
205
+ if kind is None and getattr(self.config, "wda_url", None):
206
+ kind = "usb" if getattr(self, "manager", None) is not None else "wda"
207
+ state_db = getattr(self.config, "state_db", None)
208
+ return harness_api.RunContext(
209
+ run_id=run.id, goal=run.goal, engine=run.engine or engines.SMART, origin=run.origin,
210
+ app_bundle=run.app.get("bundleId"), device_id=run.device_id, device_kind=kind,
211
+ extras=harness_api.frozen_mapping(run.extras), data_dir=Path(state_db).parent if state_db else None,
212
+ emit=run.emit, clarify=harness_api.clarify_only(run.request_approval), cancelled=run.stop.is_set,
213
+ runtime=self, account_context=run.account_context, workflow_id=run.workflow_id)
214
+
215
+ def current_wda_session(self):
216
+ # Without a session the stream stays at full size and framerate (measured: 1178x2556 at ~9.5 fps on an
217
+ # iPhone 15 Pro). WDA keeps MJPEG settings server-wide, so making one here changes nothing a run relies on.
218
+ try:
219
+ return self.wda_session()
220
+ except Exception:
221
+ return None
222
+
223
+ def video_activity(self, active):
224
+ self.awake.acquire("video") if active else self.awake.release("video")
225
+
226
+ @property
227
+ def active(self):
228
+ return next(iter(self.active_runs), None)
229
+
230
+ def capacity(self):
231
+ if self.pool is None:
232
+ fleet = getattr(self, "fleet", None)
233
+ if fleet is None:
234
+ return 0 if self.active_runs else 1
235
+ return 0 if self.run_on(fleet.primary) else 1
236
+ free = self.pool.position()
237
+ return max(0, free["healthy"] - free["busy"])
238
+
239
+ def publish(self, topic, event):
240
+ try:
241
+ (getattr(self, "bus", None) or eventbus.bus).publish(topic, event)
242
+ except Exception: # noqa: BLE001
243
+ harness_api.log.exception("event bus publish failed")
244
+
245
+ def close(self, timeout=25):
246
+ deadline = time.monotonic() + timeout
247
+ self.engine_sampler.close()
248
+ # Stop every task before the runners and relays go: quitting the Mac app gives this process only SIDECAR_GRACE
249
+ # (desktop/src-tauri/src/sidecar.rs).
250
+ with self.lock:
251
+ self.closing = True
252
+ waiting = self.drop_queues()
253
+ for run in self.runs.values():
254
+ if run.finished_at is None:
255
+ with run.condition:
256
+ if not run.stop.is_set():
257
+ run.quitting = True
258
+ run.stop.set()
259
+ run.condition.notify_all()
260
+ scheduled_checks = getattr(self, "test_schedules", None)
261
+ if scheduled_checks is not None and not scheduled_checks.close(max(0.0, deadline - time.monotonic())):
262
+ return False # Its suite may still dispatch: keep the phone and journal owned.
263
+ from .projects import service as project_service
264
+ projects = project_service.service_for(self)
265
+ if projects is not None:
266
+ if projects.begin_close() is False:
267
+ return False
268
+ if not projects.join(max(0.0, deadline - time.monotonic())):
269
+ return False # An accepted project check may still dispatch or write its report.
270
+ for timer in list(self.queue_timers.values()):
271
+ timer.cancel()
272
+ for run in waiting:
273
+ try:
274
+ self.finished(run)
275
+ except Exception: # noqa: BLE001 -- closing goes on
276
+ harness_api.log.exception("run_finished listeners failed")
277
+ if not getattr(self.config, "keep_runner", False):
278
+ phones = list(getattr(getattr(self, "fleet", None), "phones", {}).values())
279
+ for manager in [self.manager] + [getattr(phone, "manager", None) for phone in phones]:
280
+ begin = getattr(manager, "begin_close", None)
281
+ if callable(begin):
282
+ try:
283
+ begin()
284
+ except Exception: # noqa: BLE001 -- closing goes on; close() below stops it the slow way
285
+ pass
286
+ workflows = getattr(self, "workflows", None)
287
+ if workflows is not None and hasattr(workflows, "close"):
288
+ if workflows.close() is False:
289
+ return False # a batch may still write its ending to the journal
290
+ if self.scheduler and not self.scheduler.close(max(0.0, deadline - time.monotonic())):
291
+ return False # a scheduler dispatch may still be in flight
292
+ self.video.close()
293
+ if getattr(self, "control", None) is not None:
294
+ self.control.close()
295
+ if getattr(self, "fleet", None) is not None:
296
+ self.fleet.close(keep_runner=getattr(self.config, "keep_runner", False))
297
+ if not getattr(self.config, "keep_runner", False):
298
+ from .sim import wda as sim_wda
299
+ sim_wda.stop_started() # a simulator's runner this process started goes with it
300
+ if self.manager is not None:
301
+ if getattr(self.config, "keep_runner", False):
302
+ # A development reload: the runner and relay outlive this process and the next server adopts them.
303
+ self.manager.watching.set()
304
+ else:
305
+ self.manager.close()
306
+ with self.lock:
307
+ self.closing = True
308
+ threads = list(self.threads)
309
+ for run in self.runs.values():
310
+ if run.finished_at is None:
311
+ run.stop.set()
312
+ self.awake.close()
313
+ self.power.close()
314
+ for thread in threads:
315
+ thread.join(max(0, deadline - time.monotonic()))
316
+ if any(thread.is_alive() for thread in threads):
317
+ # Keep the leases and journal until exit: an old worker might still dispatch.
318
+ return False
319
+ if getattr(self, "fanout", None) is not None:
320
+ self.fanout.close()
321
+ if getattr(self, "listeners", None) is not None:
322
+ if not self.listeners.close(timeout=max(0.0, deadline - time.monotonic())):
323
+ return False
324
+ self.close_services()
325
+ if getattr(self, "frames", None) is not None:
326
+ self.frames.close()
327
+ self.journal.close()
328
+ return True
329
+
330
+
331
+ class ApiHandler(GetRoutesMixin, WriteRoutesMixin, DeviceRoutesMixin, ApiBase):
332
+ pass
333
+
334
+
335
+ def make_handler(runtime, token=None, app_session=None, setup_token=None):
336
+ # token None (tests only) leaves requests unauthenticated.
337
+ return type("Handler", (ApiHandler,), {"runtime": runtime, "token": token, "app_session": app_session,
338
+ "setup_token": setup_token})
339
+
340
+
341
+ class BoundedServer(ThreadingHTTPServer):
342
+ daemon_threads = True
343
+ request_queue_size = 32
344
+
345
+ def __init__(self, *args, **kwargs):
346
+ self.capacity = threading.BoundedSemaphore(32)
347
+ super().__init__(*args, **kwargs)
348
+
349
+ def process_request(self, request, address):
350
+ if not self.capacity.acquire(blocking=False):
351
+ self.shutdown_request(request)
352
+ return
353
+ try:
354
+ super().process_request(request, address)
355
+ except Exception:
356
+ self.capacity.release()
357
+ raise
358
+
359
+ def process_request_thread(self, request, address):
360
+ try:
361
+ super().process_request_thread(request, address)
362
+ finally:
363
+ self.capacity.release()
@@ -0,0 +1,42 @@
1
+ """What the per-runtime services (connectors, outlets, plugins) share: their registry, ``ms`` and the app-only check."""
2
+
3
+ import threading
4
+
5
+ from .api_errors import APIError
6
+
7
+ APP_ONLY = "Do this in the Mobster app."
8
+
9
+
10
+ class ServiceRegistry:
11
+ """One service per Runtime, keyed by the runtime object."""
12
+
13
+ def __init__(self):
14
+ self._items = {}
15
+ self._lock = threading.Lock()
16
+
17
+ def get(self, runtime):
18
+ with self._lock:
19
+ return self._items.get(id(runtime))
20
+
21
+ def add(self, runtime, service):
22
+ with self._lock:
23
+ self._items[id(runtime)] = service
24
+
25
+ def pop(self, runtime):
26
+ with self._lock:
27
+ return self._items.pop(id(runtime), None)
28
+
29
+ def all(self):
30
+ with self._lock:
31
+ return list(self._items.values())
32
+
33
+
34
+ def ms(seconds):
35
+ return round(seconds * 1000) if isinstance(seconds, (int, float)) else None
36
+
37
+
38
+ def needs_app(request, message=APP_ONLY):
39
+ """403 app_only when this agent has a Mac app session and the request lacks it."""
40
+ local = getattr(request.runtime, "local_api", None) or {}
41
+ if local.get("app_session") and not request.app_session:
42
+ raise APIError(message, 403, "app_only")
@@ -0,0 +1 @@
1
+ """Predictive settle and screen streams (MOBSTER_EXPERIMENTAL_SETTLE). See predict.py and docs/waiting.mdx."""
@@ -0,0 +1,102 @@
1
+ """Predictive settle: wait the learned p90, read once, let the next verify read prove it.
2
+
3
+ Polling is the floor: the settle loop reads until two reads agree for 0.3 to 0.5 s. When an app's transition has
4
+ settled in X ms at least nine times in ten, a step can sleep X ms, take one read and go on. The next observation (the
5
+ frontier's verify read, taken while the model thinks) proves the read, the same contract as ``single_read``; so it
6
+ runs only for callers that verify (``single_read`` or ``early``), and only with ``MOBSTER_EXPERIMENTAL_SETTLE``.
7
+
8
+ Nothing here decides a step. A prediction that is missing, a read that still shows the screen before the action, or
9
+ pixels that moved during the read fall back to the polling loop, unchanged. Each settle row records what was
10
+ predicted and what happened (``predicted``: ms, ``outcome``: hit, miss reason or none; ``arrival_ms``).
11
+ """
12
+
13
+ import threading
14
+ import time
15
+
16
+ from .store import SettleStore, enabled, shape_of
17
+ from .stream import WdaMjpegObserver
18
+
19
+ _store = None
20
+ _store_lock = threading.Lock()
21
+ STABLE_SECONDS = .18 # frame_clock.STABLE_SECONDS: the stream must be still this long before the read ended
22
+ READ_MARGIN_SECONDS = .35 # wda.observe.WDA_READ_MARGIN_SECONDS
23
+
24
+
25
+ def default_store():
26
+ global _store
27
+ with _store_lock:
28
+ if _store is None:
29
+ _store = SettleStore()
30
+ return _store
31
+
32
+
33
+ def reset_default_store():
34
+ global _store
35
+ with _store_lock:
36
+ _store = None
37
+
38
+
39
+ def arrival_ms(started, reads, before):
40
+ """When the final screen first showed, in ms after ``started``: the first read of the trailing run of agreeing
41
+ reads, or None when the run is shorter than two reads or shows the screen before the action."""
42
+ if len(reads) < 2 or reads[-1][2] == before:
43
+ return None
44
+ first = len(reads) - 1
45
+ while first > 0 and reads[first - 1][2] == reads[-1][2]:
46
+ first -= 1
47
+ if len(reads) - first < 2:
48
+ return None
49
+ return max(0.0, (reads[first][0] - started) * 1000)
50
+
51
+
52
+ def predicted_settle(driver, snapshot, action, watch, clock, deadline, started, cap, reads, info,
53
+ store=None):
54
+ """A changed read after the learned wait, or None (``info`` says why). Reads happen at most once."""
55
+ if not enabled() or not action:
56
+ return None
57
+ store = store or default_store()
58
+ app, shape = snapshot.bundle_id, shape_of(action.get("op"))
59
+ wait = store.p90(app, shape)
60
+ if wait is None:
61
+ info["outcome"] = "none"
62
+ return None
63
+ info["predicted"], info["shape"] = round(wait, 1), shape
64
+ # The wait is counted from the dispatch's end, which is when ``started`` was taken.
65
+ pause = min(started + wait / 1000, cap - READ_MARGIN_SECONDS) - time.monotonic()
66
+ if pause > 0:
67
+ time.sleep(pause)
68
+ read_started = time.monotonic()
69
+ if deadline.end - read_started <= READ_MARGIN_SECONDS:
70
+ info["outcome"] = "no_time"
71
+ return None
72
+ try:
73
+ current = driver.observe(timeout=deadline.remaining())
74
+ except Exception: # TransportError or TimeoutError: the polling loop owns the failure.
75
+ info["outcome"] = "read_failed"
76
+ return None
77
+ now = time.monotonic()
78
+ reads.append((read_started, now, current.content_fingerprint))
79
+ if current.content_fingerprint == snapshot.content_fingerprint:
80
+ info["outcome"] = "unchanged"
81
+ return None
82
+ if WdaMjpegObserver(clock, watch).still_between(read_started - STABLE_SECONDS, now) is False:
83
+ info["outcome"] = "moved"
84
+ return None
85
+ info["outcome"] = "hit"
86
+ return current
87
+
88
+
89
+ def annotate(driver, entry, action, snapshot, result, started, reads, info):
90
+ """Put the prediction on the settle row, remember it on the driver (``predicted_settle``) and learn from the
91
+ settle. Learns only from settles that ended on agreeing reads; a hit is one read, so it teaches nothing."""
92
+ if not enabled():
93
+ return
94
+ arrival = arrival_ms(started, reads, snapshot.content_fingerprint)
95
+ shape = shape_of((action or {}).get("op"))
96
+ if arrival is not None:
97
+ entry["arrival_ms"] = round(arrival, 1)
98
+ default_store().add(snapshot.bundle_id, shape, arrival)
99
+ if info is not None and "outcome" in info:
100
+ entry.update({key: info[key] for key in ("predicted", "outcome", "shape") if key in info})
101
+ driver.predicted_settle = {key: entry.get(key) for key in ("shape", "predicted", "outcome", "arrival_ms")
102
+ if entry.get(key) is not None} or None
@@ -0,0 +1,92 @@
1
+ """Offline replay of recorded settles: what predictive settle would have done on frameclock.jsonl logs.
2
+
3
+ python -m mobile_agent.settle.replay LOG [LOG ...]
4
+
5
+ Reads ``settle`` rows (``MOBSTER_FRAME_CLOCK_LOG`` files) in time order, learns each key's arrival the way a run does,
6
+ and for every settle that polled (path ax, frame or frame_fallback) asks whether the learned p90 would have been
7
+ enough. A row records no arrival unless it was logged with the switch on (``arrival_ms``), so the replay estimates it:
8
+ ``stable_ms`` when the row has it, else ``settle_ms`` minus the polling loop's quiet period (0.5 s on AX alone, 0.3 s
9
+ with the pixels). The wall time of a hit is the wait plus one read; of a miss, the wait, one read and then the
10
+ whole polled settle (an upper bound). These are projections from recordings, not a measured effect of the switch.
11
+ """
12
+
13
+ import json
14
+ import sys
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+ from .store import SettleStore, shape_of
19
+
20
+ POLLED = {"ax", "frame", "frame_fallback"}
21
+ QUIET_AX_MS, QUIET_PIXELS_MS, READ_MS = 500.0, 300.0, 200.0
22
+
23
+
24
+ def estimate_arrival(row):
25
+ if row.get("arrival_ms") is not None:
26
+ return float(row["arrival_ms"])
27
+ if row.get("stable_ms") is not None:
28
+ return float(row["stable_ms"])
29
+ quiet = QUIET_PIXELS_MS if row.get("path") == "frame" else QUIET_AX_MS
30
+ return max(0.0, float(row["settle_ms"]) - quiet)
31
+
32
+
33
+ def load(paths):
34
+ rows = []
35
+ for path in paths:
36
+ with open(path, encoding="utf-8") as handle:
37
+ for line in handle:
38
+ try:
39
+ row = json.loads(line)
40
+ except ValueError:
41
+ continue
42
+ if row.get("kind") == "settle" and row.get("changed") and "settle_ms" in row:
43
+ rows.append(row)
44
+ return sorted(rows, key=lambda row: row.get("at", 0))
45
+
46
+
47
+ def replay(rows):
48
+ with tempfile.TemporaryDirectory() as folder:
49
+ store = SettleStore(Path(folder) / "durations.json")
50
+ totals = {"settles": 0, "predicted": 0, "hits": 0, "misses": 0, "polled_ms": 0.0, "with_ms": 0.0,
51
+ "polled_reads": 0, "with_reads": 0, "no_prediction": 0}
52
+ for row in rows:
53
+ if row.get("path") not in POLLED:
54
+ continue
55
+ app, shape = row.get("bundle") or "", shape_of(row.get("op"))
56
+ arrival, wall, reads = estimate_arrival(row), float(row["settle_ms"]), int(row.get("reads", 1))
57
+ totals["settles"] += 1
58
+ totals["polled_ms"] += wall
59
+ totals["polled_reads"] += reads
60
+ wait = store.p90(app, shape)
61
+ if wait is None:
62
+ totals["no_prediction"] += 1
63
+ totals["with_ms"], totals["with_reads"] = totals["with_ms"] + wall, totals["with_reads"] + reads
64
+ store.add(app, shape, arrival)
65
+ elif wait >= arrival:
66
+ totals["predicted"] += 1
67
+ totals["hits"] += 1
68
+ totals["with_ms"] += wait + READ_MS
69
+ totals["with_reads"] += 1
70
+ else:
71
+ totals["predicted"] += 1
72
+ totals["misses"] += 1
73
+ totals["with_ms"] += wait + READ_MS + wall
74
+ totals["with_reads"] += 1 + reads
75
+ store.add(app, shape, arrival)
76
+ return totals
77
+
78
+
79
+ def report(totals):
80
+ n = totals["settles"] or 1
81
+ lines = [f"{totals['settles']} polled settles; {totals['no_prediction']} had no prediction yet, "
82
+ f"{totals['hits']} hits, {totals['misses']} misses ({totals['predicted']} predicted)",
83
+ f"step settle wall: {totals['polled_ms'] / n:.0f} ms polled -> {totals['with_ms'] / n:.0f} ms predicted "
84
+ f"(mean per settle)",
85
+ f"AX reads per settle: {totals['polled_reads'] / n:.2f} -> {totals['with_reads'] / n:.2f}"]
86
+ if totals["predicted"]:
87
+ lines.append(f"hit rate when predicted: {totals['hits'] / totals['predicted']:.0%}")
88
+ return "\n".join(lines)
89
+
90
+
91
+ if __name__ == "__main__":
92
+ print(report(replay(load(sys.argv[1:]))))