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,549 @@
1
+ """Runtime's task start: the readiness checks, Runtime.create and the results that need no phone."""
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+ import threading
7
+
8
+ from . import ai_plans, engine_health, engines, harness_api, orchestrate, ownership, permissions, project_env
9
+ from .api_errors import APIError
10
+ from .journal import JournalError, Lease
11
+ from .lockscreen import PHONE_LOCKED
12
+ from .output_contract import validate_output
13
+ from .owner_presence import OWNER_ACTIVE_REFUSAL
14
+ from .pool import NoDeviceAvailable
15
+ from .run import Run
16
+ from .runtime_driver import PooledLease, waits_for_unlock
17
+ from .runtime_limits import TASK_LIMIT_RANGE
18
+ from .state import validate_bundle_id
19
+ from .wda import watchdog as wda_watchdog
20
+
21
+ # A run that calls no model needs a ready phone and live runs on, but no model key.
22
+ NO_MODEL = "no_model"
23
+
24
+ ANY_APP = {"id": "any", "name": "Any app", "bundleId": None, "available": True}
25
+
26
+
27
+ class TaskStartMixin:
28
+ def _replay(self, idempotency_key, fingerprint):
29
+ # Call with the lock held. A key reused for another task, or whose task left history, is refused.
30
+ prior = self.journal.lookup(idempotency_key) if idempotency_key else None
31
+ if not prior:
32
+ return None
33
+ if prior[1] != fingerprint:
34
+ raise APIError("This request key belongs to a different task", code="idempotency_conflict")
35
+ if prior[0] not in self.runs:
36
+ raise APIError("This previous task has expired from history; it was not restarted", 410, "run_expired")
37
+ return self.runs[prior[0]], True
38
+
39
+ def check_ready(self, phone, primary, chosen, origin, idempotency_key):
40
+ # Admission asks it, and a queued request asks it again when its turn comes.
41
+ if self.owner_blocks_start(phone, origin, idempotency_key):
42
+ raise APIError(OWNER_ACTIVE_REFUSAL, 409, "owner_active", device=getattr(phone, "id", None))
43
+ if primary:
44
+ status = self.status()
45
+ state = status.get("engines", {}).get(chosen) or {"available": True, "reason": None}
46
+ if not state["available"]:
47
+ # Never another engine instead: the user chose this one (or it is their default).
48
+ raise APIError(state["reason"] or "This engine can't run right now", 409, "engine_unavailable",
49
+ engine=chosen)
50
+ usable = status["live_enabled"] if chosen == engines.SMART else (
51
+ status.get("device_ready") and self.config.enable_live and not self.closing)
52
+ if not usable:
53
+ locked = (self.config.enable_live and not self.closing
54
+ and engine_health.cause_of(status["engine"], "phone:primary") == "locked")
55
+ if locked and not waits_for_unlock(origin, idempotency_key):
56
+ # The device manager's probe saw the lock: say so, not "not ready" (PLAN §3.9).
57
+ raise APIError(PHONE_LOCKED, 503, "device_unavailable", stop="phone_locked")
58
+ if not locked:
59
+ raise APIError(self.not_ready_message(status), 503, "device_unavailable")
60
+ # Locked and nothing else: a person started this task, so it waits for the unlock at its preflight.
61
+ else:
62
+ state = self.engines_status().get(chosen) or {"available": True, "reason": None}
63
+ if not state["available"]:
64
+ raise APIError(state["reason"] or "This engine can't run right now", 409, "engine_unavailable",
65
+ engine=chosen)
66
+ if not self.config.enable_live:
67
+ raise APIError("Live runs are off; enable them to act on the phone", 503, "device_unavailable")
68
+ target = phone.target_status()
69
+ if not (target["ready"] and target["can_act"]):
70
+ locked = engine_health.cause_of(self.engine(), f"phone:{phone.id}") == "locked"
71
+ if locked and not waits_for_unlock(origin, idempotency_key):
72
+ raise APIError(f"{phone.name or 'This iPhone'} is locked. Unlock it and try again. "
73
+ "No action was taken.", 503, "device_unavailable", device=phone.id,
74
+ stop="phone_locked")
75
+ if not (locked and target["can_act"]):
76
+ raise APIError(f"{phone.name or 'This device'} is not ready for live tasks. "
77
+ + (target.get("health") or "Try again when it is available."), 503,
78
+ "device_unavailable", device=phone.id)
79
+ # Locked only: waits for the unlock at its preflight, like the first iPhone's.
80
+
81
+ def create(self, app_id, goal, mode, idempotency_key=None, output_schema=None, output_format="auto",
82
+ allowed_bundles=None, engine=None, device=None, default_device="last_used",
83
+ extras=None, origin="api", group_id=None, pinned_apps=None, cap_usd=None, queue=False,
84
+ _resume_run=None, _workflow_id=None):
85
+ if self.plan_gate is not None:
86
+ self.plan_gate.require()
87
+ engine = engines.read_engine(engine)
88
+ if cap_usd is not None and (isinstance(cap_usd, bool) or not isinstance(cap_usd, (int, float))
89
+ or not 0 < cap_usd <= TASK_LIMIT_RANGE[1]):
90
+ raise ValueError("cap_usd is a positive amount in USD")
91
+ if engine is not None and engine not in engines.ENGINES and not ai_plans.is_agent(engine):
92
+ raise ValueError("engine must be smart, or codex, claude_code or gemini for your own coding agent")
93
+ if origin not in harness_api.ORIGINS:
94
+ raise ValueError("Unknown task origin")
95
+ if origin in harness_api.CLAIMED_ORIGINS:
96
+ queue = False # the claim admits it only as it starts now
97
+ source = None
98
+ if _resume_run is not None:
99
+ source = self.runs.get(getattr(_resume_run, "id", None))
100
+ if (source is not _resume_run or not isinstance(extras, dict)
101
+ or extras.get("resumeFrom") != source.id or source.finished_at is None):
102
+ raise ValueError("This task cannot reuse a saved run context")
103
+ if "account" in extras and extras["account"] != source.extras.get("account"):
104
+ raise ValueError("A picked-up task keeps its original profile")
105
+ _workflow_id = source.workflow_id
106
+ elif _workflow_id is not None:
107
+ if (not isinstance(_workflow_id, str) or not isinstance(idempotency_key, str)
108
+ or not self.workflows.owns_run_request(_workflow_id, idempotency_key)):
109
+ raise ValueError("This task has no claimed saved workflow")
110
+ # A canonical pickup carries its original private snapshot even if the saved profile was edited or removed.
111
+ if source is not None and isinstance(source.extras.get("account"), dict):
112
+ if source.account_context is None:
113
+ raise ValueError("This task has no saved profile context")
114
+ extras = harness_api.clean_extras({k: v for k, v in extras.items() if k != "account"}, self)
115
+ extras["account"] = source.extras["account"]
116
+ else:
117
+ extras = harness_api.clean_extras(extras, self)
118
+ predecessor = self.runs.get(extras.get("resumeFrom")) if extras.get("resumeFrom") else None
119
+ if source is None and predecessor is not None and (predecessor.extras.get("account")
120
+ or predecessor.workflow_id):
121
+ raise ValueError("Pick up this task from its saved run so its profile and workflow stay fixed")
122
+ if harness_api.calls_no_model(extras) and engine not in (None, engines.SMART):
123
+ raise ValueError("This task calls no model: leave the engine out")
124
+ if extras.get("projectEnv") and isinstance(goal, str):
125
+ self._check_project_env(extras["projectEnv"], goal, engine, origin)
126
+ account = extras.get("account")
127
+ account_context = account_digest = None
128
+ if account is not None:
129
+ from .accounts import routing as account_routing
130
+ if source is not None:
131
+ account_context = source.account_context
132
+ account_digest = account_routing.digest(account_context)
133
+ else:
134
+ account_context, account_digest = account_routing.capture(self, account)
135
+ if account is not None and isinstance(goal, str):
136
+ goal, device = account_routing.route(self, account_context, goal, device)
137
+ if default_device not in ("last_used", "primary"):
138
+ raise ValueError("default_device must be last_used or primary")
139
+ fleet = getattr(self, "fleet", None)
140
+ phone = None
141
+ if device is not None and (fleet is None or self.pool is not None):
142
+ raise ValueError("This Mobster runs every task on its own device; leave device out")
143
+ if fleet is not None and self.pool is None:
144
+ # Resolved before the runtime's lock: naming a device reads the USB listing and the registries.
145
+ if device is not None:
146
+ phone = self.resolve_device(device)
147
+ elif self.one_target:
148
+ phone = fleet.primary
149
+ elif default_device == "primary":
150
+ phone = self.resolve_default(sticky=False)
151
+ else:
152
+ phone = self.resolve_device(None)
153
+ primary = phone is None or phone.primary
154
+ chosen = engine
155
+ if chosen is None:
156
+ chosen = engines.SMART
157
+ if account is not None and chosen != engines.SMART:
158
+ # Only Smart confirms the account on screen before it commits (accounts/screen.py).
159
+ from .accounts.routing import SMART_ONLY
160
+ raise ValueError(SMART_ONLY)
161
+ if pinned_apps is not None:
162
+ if (not isinstance(pinned_apps, list) or not 1 <= len(pinned_apps) <= orchestrate.PINNED_APPS
163
+ or not all(isinstance(item, str) and 0 < len(item) <= 160 for item in pinned_apps)
164
+ or len(set(pinned_apps)) != len(pinned_apps)):
165
+ raise ValueError(f"pinnedApps is 1-{orchestrate.PINNED_APPS} different app ids")
166
+ if app_id is None:
167
+ app_id = pinned_apps[0]
168
+ any_app = chosen == engines.SMART or ai_plans.is_agent(chosen)
169
+ if app_id is None and any_app:
170
+ app_id = ANY_APP["id"]
171
+ if (not isinstance(app_id, str) or not isinstance(goal, str) or not 1 <= len(goal.strip()) <= 4000
172
+ or mode != "live"):
173
+ raise ValueError("Choose an app, a task (1–4000 characters), and live mode")
174
+ if idempotency_key is not None and not re.fullmatch(r"[A-Za-z0-9_.:-]{1,128}", idempotency_key):
175
+ raise ValueError("Invalid Idempotency-Key")
176
+ output_schema = validate_output(output_format, output_schema)
177
+ if allowed_bundles is not None:
178
+ if not isinstance(allowed_bundles, list) or not allowed_bundles or len(allowed_bundles) > 31:
179
+ raise ValueError("allowedBundles must be 1-31 bundle identifiers")
180
+ try:
181
+ extra_bundles = frozenset(validate_bundle_id(bundle) for bundle in allowed_bundles)
182
+ except ValueError:
183
+ raise ValueError("allowedBundles must be valid bundle identifiers") from None
184
+ else:
185
+ extra_bundles = None
186
+ identity = [app_id, goal.strip(), mode, output_schema]
187
+ # Preserve existing JSON request tombstones; another format is a distinct request.
188
+ if output_format != "json":
189
+ identity.append({"outputFormat": output_format})
190
+ if extra_bundles is not None:
191
+ identity.append({"allowedBundles": sorted(extra_bundles)})
192
+ if engine is not None:
193
+ identity.append({"engine": engine})
194
+ if device is not None and phone is not None:
195
+ identity.append({"device": phone.id})
196
+ if extras:
197
+ identity.append({"extras": extras})
198
+ if account_digest is not None:
199
+ identity.append({"accountContextSha256": account_digest})
200
+ if _workflow_id is not None:
201
+ identity.append({"workflowId": _workflow_id})
202
+ if group_id is not None:
203
+ if not isinstance(group_id, str) or not re.fullmatch(r"[a-f0-9]{12}", group_id):
204
+ raise ValueError("A group id is 12 lowercase hex characters")
205
+ identity.append({"groupId": group_id})
206
+ if pinned_apps:
207
+ identity.append({"pinnedApps": list(pinned_apps)})
208
+ fingerprint = hashlib.sha256(json.dumps(identity,
209
+ ensure_ascii=True, sort_keys=True, allow_nan=False).encode()).hexdigest()
210
+ if account is not None and phone is not None and getattr(fleet, "health", None) is not None:
211
+ # Refused before anything is claimed; a retry of a started request gets its task back first.
212
+ with self.lock:
213
+ replayed = None if self.closing else self._replay(idempotency_key, fingerprint)
214
+ if replayed is not None:
215
+ return replayed
216
+ fleet.health.preflight(account, phone)
217
+ if ai_plans.is_agent(chosen):
218
+ # Before the runtime's lock: a first check runs the CLI's own status command.
219
+ self.check_agent(chosen)
220
+ with self.lock:
221
+ if self.closing:
222
+ raise APIError("The service is shutting down", 503, "shutting_down")
223
+ if source is not None and self.runs.get(source.id) is not source:
224
+ raise APIError("The task being picked up left saved history. Nothing was restarted.", 410,
225
+ "run_expired")
226
+ replayed = self._replay(idempotency_key, fingerprint)
227
+ if replayed is not None:
228
+ return replayed
229
+ waits = None
230
+ line = self.queues.find(self.queue_key(phone))
231
+ in_line = line is not None and (len(line) > 0 or line.held())
232
+ if phone is not None:
233
+ busy = self.run_on(phone)
234
+ if busy is not None or in_line:
235
+ if not queue:
236
+ raise self.busy_error(phone, busy, primary)
237
+ waits = self.queue_key(phone)
238
+ elif not self.capacity() or in_line:
239
+ if not queue:
240
+ raise self.busy_error(None, self.active, primary)
241
+ waits = self.queue_key(None)
242
+ if waits is not None and len(self.queues.of(waits)) >= self.queues.limit:
243
+ raise APIError(ownership.QUEUE_FULL.format(count=len(self.queues.of(waits)),
244
+ phone=self.phone_name(phone)), 429, "queue_full")
245
+ phone_prefs = fleet.prefs(phone) if fleet is not None and phone is not None else {}
246
+ if phone is not None and fleet.is_disconnected(phone):
247
+ raise APIError(f"{phone.name or 'This iPhone'} is disconnected in Mobster. Connect it in Settings › "
248
+ "Phones to run tasks on it.", 409, "device_disconnected", device=phone.id)
249
+ phone_apps = frozenset(phone_prefs["apps"]) if phone_prefs.get("apps") else None
250
+ admitted = None
251
+ if origin in harness_api.CLAIMED_ORIGINS:
252
+ # A console run (teams/link.py) narrows the apps and limits; no claim registered, no run.
253
+ claim = harness_api.origin_claim(origin)
254
+ if claim is None or not set(claim[2]) <= set(extras):
255
+ raise APIError("That origin needs its own session on this Mac.", 403, "origin_refused")
256
+ admitted = claim[1](self, extras=extras, phone=phone, engine=chosen)
257
+ if admitted.apps is not None:
258
+ phone_apps = admitted.apps if phone_apps is None else phone_apps & admitted.apps
259
+ if admitted.cap_usd is not None:
260
+ cap_usd = admitted.cap_usd if cap_usd is None else min(cap_usd, admitted.cap_usd)
261
+ if admitted.ask_floor == permissions.ASK_ALL:
262
+ extras["permissionMode"] = permissions.ASK_ALL
263
+ if not admitted.long_tasks and extras.get("longform") is not None:
264
+ raise ValueError("A long task can't start from there. Start it in Mobster on this Mac")
265
+ catalog = self.apps_for(phone)
266
+ if account is not None and app_id == ANY_APP["id"]:
267
+ # A task as an account starts in the account's app, unless it named another one.
268
+ app_id = next((a["id"] for a in catalog if a.get("bundleId") == account["app"]["bundleId"]), app_id)
269
+ app = ANY_APP if app_id == ANY_APP["id"] and any_app else next(
270
+ (a for a in catalog if a["id"] == app_id), None)
271
+ if not app:
272
+ raise ValueError("Choose an app from your catalog")
273
+ pins = None
274
+ if pinned_apps:
275
+ pins = []
276
+ for pinned in pinned_apps:
277
+ found = next((a for a in catalog if a["id"] == pinned), None)
278
+ if found is None:
279
+ raise ValueError("Pin apps from your catalog")
280
+ if found.get("installed") is False:
281
+ raise ValueError(f"{found['name']} isn't installed on "
282
+ f"{(phone.name if phone is not None and phone.name else 'this iPhone')}")
283
+ pins.append({"id": found["id"], "name": found["name"], "bundleId": found.get("bundleId")})
284
+ app_policy = permissions.from_env().apps
285
+ outside = origin in permissions.policy.OUTSIDE_ORIGINS
286
+ if app.get("bundleId") and not app_policy.allows(app["bundleId"], outside=outside):
287
+ raise APIError(app_policy.blocked_message(app.get("name"), outside=outside and app_policy.allows(
288
+ app["bundleId"])), 403, "app_not_allowed")
289
+ long_task = None
290
+ if chosen == engines.SMART and not harness_api.calls_no_model(extras) and \
291
+ (admitted is None or admitted.long_tasks):
292
+ long_task = self.long_task(goal, app, extras, origin, pins)
293
+ elif extras.get("longform") is not None:
294
+ raise ValueError("Long tasks run on Mobster's agent")
295
+ if phone_apps is not None and app is not ANY_APP and app.get("bundleId") not in phone_apps:
296
+ raise APIError(f"{app['name']} isn't one of the apps Mobster may use on {phone.name or 'this phone'}. "
297
+ "Change it in Settings › Phones.", 400, "app_not_allowed", device=phone.id)
298
+ outside = [pin for pin in pins or () if phone_apps is not None and pin["bundleId"] not in phone_apps]
299
+ if outside:
300
+ # A pin is an app the task uses: the phone's app list limits it as it limits the start app.
301
+ raise APIError(f"{outside[0]['name']} isn't one of the apps Mobster may use on "
302
+ f"{phone.name or 'this phone'}. Change it in Settings › Phones.", 400, "app_not_allowed",
303
+ device=phone.id)
304
+ allowed = None
305
+ if extra_bundles is not None:
306
+ known = {a["bundleId"] for a in catalog if isinstance(a.get("bundleId"), str)}
307
+ unknown = sorted(extra_bundles - known)
308
+ if unknown:
309
+ raise ValueError("Unknown app bundle: " + ", ".join(unknown[:5]))
310
+ if phone_apps is not None and extra_bundles - phone_apps:
311
+ raise APIError("Some of those apps aren't ones Mobster may use on "
312
+ f"{phone.name or 'this phone'}: " + ", ".join(sorted(extra_bundles - phone_apps)[:5])
313
+ + ". Change it in Settings › Phones.", 400, "app_not_allowed", device=phone.id)
314
+ blocked = sorted(b for b in extra_bundles if not app_policy.allows(b))
315
+ if blocked:
316
+ names = {a["bundleId"]: a["name"] for a in catalog if a.get("bundleId")}
317
+ raise APIError(app_policy.blocked_message(names.get(blocked[0], blocked[0])), 403,
318
+ "app_not_allowed")
319
+ allowed = frozenset({app["bundleId"]} | extra_bundles)
320
+ self.check_ready(phone, primary, NO_MODEL if harness_api.calls_no_model(extras) else chosen, origin,
321
+ idempotency_key)
322
+ cap = None
323
+ limit = self.monthly_limit()
324
+ if limit is not None:
325
+ cap = self.monthly_cap(limit, raised=(long_task or {}).get("capUsd"))
326
+ if app is not ANY_APP:
327
+ if primary:
328
+ self.check_app(app)
329
+ elif app.get("installed") is False:
330
+ raise ValueError(f"{app['name']} isn't installed on {phone.name or 'this device'}")
331
+ self.teaching(None if primary else phone)
332
+ run = Run(app, goal.strip(), mode, idempotency_key=idempotency_key, output_schema=output_schema,
333
+ output_format=output_format, allowed_bundles=allowed, journal=self.journal,
334
+ phone=None if primary else phone,
335
+ device_id=phone.id if phone is not None else None,
336
+ device_name=phone.name if phone is not None else None,
337
+ engine=chosen, frames=getattr(self, "frames", None),
338
+ estimate_usd=self.estimate_for(chosen, goal.strip(), output_schema, output_format),
339
+ extras=extras, account_context=account_context, workflow_id=_workflow_id, origin=origin,
340
+ listeners=getattr(self, "listeners", None),
341
+ longform=long_task, group_id=group_id, phone_apps=phone_apps, pinned_apps=pins,
342
+ ask_override=phone_prefs.get("askBeforeActing"),
343
+ approvals_closed=self.approvals_closed if origin in harness_api.UNATTENDED_ORIGINS else None)
344
+ self.remembering(run)
345
+ run.client = ownership.clean_client(ownership.current_client()) if origin == "mcp" else None
346
+ self.apply_caps(run, cap, cap_usd)
347
+ if self.pool is None and waits is None:
348
+ try:
349
+ run.lease = Lease.device((self.config.wda_url if primary else phone.wda_url).rstrip("/"))
350
+ except JournalError:
351
+ raise APIError("Your iPhone is busy in another Mobster process" if primary else
352
+ f"{phone.name or 'This device'} is busy in another Mobster process",
353
+ code="device_busy") from None
354
+ device = None
355
+ if self.pool is not None and waits is None:
356
+ # At admission, not dispatch: a task that can't get a device is refused while the caller waits.
357
+ try:
358
+ device = self.pool.acquire(run.id, timeout=0)
359
+ except NoDeviceAvailable:
360
+ elsewhere = any("another Mobster process" in item["reason"] for item in self.pool.public())
361
+ raise APIError(
362
+ "Your iPhone is busy in another Mobster process" if elsewhere else
363
+ "Every assigned phone is busy; stop a run or wait for one to finish",
364
+ code="device_busy" if elsewhere else "run_active",
365
+ activeRunId=self.active) from None
366
+ run.device = device
367
+ run.lease = PooledLease(self.pool, run.id)
368
+ held_url = self.run_phone_url(run)
369
+ if waits is None:
370
+ try:
371
+ wda_watchdog.tracker.run_started(held_url, run.id) # refused while the helper restarts
372
+ except wda_watchdog.ActionBlocked:
373
+ if run.lease:
374
+ run.lease.close()
375
+ raise APIError("Mobster's helper on your iPhone is restarting. No action was taken. "
376
+ "Try again in a moment.", 503, "device_unavailable") from None
377
+ try:
378
+ expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
379
+ except Exception:
380
+ if waits is None:
381
+ wda_watchdog.tracker.run_ended(held_url, run.id)
382
+ if run.lease:
383
+ run.lease.close()
384
+ raise
385
+ self._retain_run(run, expired)
386
+ thread = None
387
+ try:
388
+ if waits is not None:
389
+ # Held runs are durable, listed and cancellable, with no phone, lease or worker.
390
+ self.enqueue(run, phone, waits, queue_cap=cap_usd, chosen=chosen, primary=primary)
391
+ else:
392
+ self.active_runs[run.id] = device.id if device else phone.key if phone is not None else None
393
+ if run.listeners is not None:
394
+ run.listeners.post("on_created", run, self)
395
+ self.publish("runs", {"event": "run_created", "runId": run.id,
396
+ **({"threadId": extras["threadId"]} if extras.get("threadId") else {}),
397
+ **({"device": run.device_id} if run.device_id else {})})
398
+ if waits is not None:
399
+ self.publish("devices", {"event": "queue_changed", "device": run.device_id})
400
+ return (run, False) if idempotency_key else run
401
+ thread = threading.Thread(target=self.work, args=(run,), daemon=True)
402
+ self.threads.add(thread)
403
+ thread.start()
404
+ except Exception:
405
+ self.threads.discard(thread)
406
+ self.active_runs.pop(run.id, None)
407
+ if waits is not None:
408
+ self.unqueue(run.id)
409
+ if waits is None:
410
+ wda_watchdog.tracker.run_ended(held_url, run.id)
411
+ if run.lease:
412
+ run.lease.close()
413
+ run.finish({"status": "interrupted", "independently_verified": False,
414
+ "reason": "The task worker could not start. No agent action was dispatched."})
415
+ raise APIError("The task could not start; check its saved status before retrying", 503,
416
+ "start_failed") from None
417
+ if phone is not None:
418
+ fleet.mark_used(phone)
419
+ return (run, False) if idempotency_key else run
420
+
421
+ def _retain_run(self, run, expired):
422
+ # Call under self.lock after journal.create succeeds; its expired ids own history retention.
423
+ for identifier in expired:
424
+ self.runs.pop(identifier, None)
425
+ if getattr(self, "frames", None) is not None:
426
+ self.frames.delete(identifier)
427
+ self.runs[run.id] = run
428
+
429
+ def compose_result(self, *, app, goal, summary, idempotency_key, extras=None, origin="workflow",
430
+ device=(None, None)):
431
+ # No phone, no model call, acts on nothing; Run.finish validates its blocks like any task's.
432
+ if self.plan_gate is not None:
433
+ self.plan_gate.require()
434
+ if origin not in harness_api.ORIGINS:
435
+ raise ValueError("Unknown task origin")
436
+ if not isinstance(app, dict) or not isinstance(app.get("id"), str):
437
+ raise ValueError("A result names the app its tasks ran in")
438
+ extras = harness_api.clean_extras(extras, self)
439
+ fingerprint = hashlib.sha256(json.dumps(["compose", app["id"], goal, extras], ensure_ascii=True,
440
+ sort_keys=True, default=str).encode()).hexdigest()
441
+ with self.lock:
442
+ if self.closing:
443
+ raise APIError("The service is shutting down", 503, "shutting_down")
444
+ prior = self.journal.lookup(idempotency_key)
445
+ if prior and prior[0] in self.runs:
446
+ return self.runs[prior[0]]
447
+ if prior:
448
+ raise APIError("This result has expired from history", 410, "run_expired")
449
+ run = Run(dict(app), goal.strip()[:4000], "live", idempotency_key=idempotency_key, journal=self.journal,
450
+ frames=getattr(self, "frames", None), device_id=device[0], device_name=device[1],
451
+ extras=extras, origin=origin, listeners=getattr(self, "listeners", None))
452
+ expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
453
+ self._retain_run(run, expired)
454
+ if run.listeners is not None:
455
+ run.listeners.post("on_created", run, self)
456
+ self.publish("runs", {"event": "run_created", "runId": run.id,
457
+ **({"threadId": extras["threadId"]} if extras.get("threadId") else {})})
458
+ run.finish(dict(summary))
459
+ self.finished(run)
460
+ return run
461
+
462
+ def model_result(self, *, app, goal, call, idempotency_key, extras=None, origin="workflow"):
463
+ # The call is metered into the run's events, so the usage ledger and the monthly limit count it.
464
+ if self.plan_gate is not None:
465
+ self.plan_gate.require()
466
+ if origin not in harness_api.ORIGINS or origin in harness_api.CLAIMED_ORIGINS:
467
+ # A claimed origin's runs are admitted in create(); this path has no admission, so none comes here.
468
+ raise ValueError("Unknown task origin")
469
+ if not isinstance(app, dict) or not isinstance(app.get("id"), str):
470
+ raise ValueError("A result names the app its tasks ran in")
471
+ extras = harness_api.clean_extras(extras, self)
472
+ fingerprint = hashlib.sha256(json.dumps(["model", app["id"], goal, extras], ensure_ascii=True,
473
+ sort_keys=True, default=str).encode()).hexdigest()
474
+ with self.lock:
475
+ if self.closing:
476
+ raise APIError("The service is shutting down", 503, "shutting_down")
477
+ prior = self.journal.lookup(idempotency_key)
478
+ if prior and prior[0] in self.runs:
479
+ return self.runs[prior[0]]
480
+ if prior:
481
+ raise APIError("This result has expired from history", 410, "run_expired")
482
+ limit = self.monthly_limit()
483
+ if limit is not None:
484
+ self.monthly_cap(limit) # refuses when this month has nothing left
485
+ run = Run(dict(app), goal.strip()[:4000], "live", idempotency_key=idempotency_key, journal=self.journal,
486
+ frames=getattr(self, "frames", None), extras=extras, origin=origin,
487
+ listeners=getattr(self, "listeners", None))
488
+ expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
489
+ self._retain_run(run, expired)
490
+ if run.listeners is not None:
491
+ run.listeners.post("on_created", run, self)
492
+ self.publish("runs", {"event": "run_created", "runId": run.id,
493
+ **({"threadId": extras["threadId"]} if extras.get("threadId") else {})})
494
+ try:
495
+ summary = call(run.emit)
496
+ except Exception as error: # the call failed or its answer didn't fit: the run says so; nothing acted
497
+ summary = {"event": "result", "status": "error", "outcome": "couldnt_finish", "engine": None,
498
+ "answer": None, "reason": f"The model call didn't finish: {type(error).__name__}.",
499
+ "actions": 0, "decisions": 1, "elapsed_ms": 0, "model": None,
500
+ "independently_verified": False, "proof": []}
501
+ run.finish(dict(summary))
502
+ self.finished(run)
503
+ return run
504
+
505
+ def frame(self, run_id, frame_id):
506
+ frames = getattr(self, "frames", None)
507
+ run = self.runs.get(run_id)
508
+ if frames is None or run is None:
509
+ return None
510
+ data = frames.get(run_id, frame_id)
511
+ if data is not None:
512
+ return data
513
+ brief = (run.summary or {}).get("brief") if isinstance(run.summary, dict) else None
514
+ for source in (brief or {}).get("runs") or () if isinstance(brief, dict) else ():
515
+ if isinstance(source, str) and source in self.runs:
516
+ data = frames.get(source, frame_id)
517
+ if data is not None:
518
+ return data
519
+ return None
520
+
521
+ def start_group(self, phones, app_id, goal, idempotency_key=None, fingerprint=None, thread_id=None, **create):
522
+ # Without a plan no phone's run is admitted, so the group isn't made either.
523
+ if self.plan_gate is not None:
524
+ self.plan_gate.require()
525
+ if self.closing:
526
+ raise APIError("The service is shutting down", 503, "shutting_down")
527
+ return self.fanout.start(phones, app_id, goal, idempotency_key=idempotency_key, fingerprint=fingerprint,
528
+ thread_id=thread_id, **create)
529
+
530
+ @staticmethod
531
+ def _check_project_env(root, goal, engine, origin):
532
+ # Only Mobster's agent masks the values, and a task never starts with a blank or a literal ${NAME}.
533
+ if not project_env.refs(goal):
534
+ return
535
+ if origin not in {"app", "cli", "tui", "workflow", "schedule"} or engine not in (None, engines.SMART):
536
+ raise ValueError("A task that uses project secrets (${NAME}) runs on Mobster's agent from the app, the "
537
+ "terminal or a saved workflow. Remove the ${NAME}s to run it here.")
538
+ try:
539
+ project_env.for_task(root, goal)
540
+ except project_env.EnvError as error:
541
+ raise ValueError(str(error)) from None
542
+
543
+ def start_profiles(self, profiles, app_id, goal, idempotency_key=None, fingerprint=None, thread_id=None, **create):
544
+ if self.plan_gate is not None:
545
+ self.plan_gate.require()
546
+ if self.closing:
547
+ raise APIError("The service is shutting down", 503, "shutting_down")
548
+ return self.fanout.start_profiles(profiles, app_id, goal, idempotency_key=idempotency_key,
549
+ fingerprint=fingerprint, thread_id=thread_id, **create)