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,178 @@
1
+ """Two workflow steps that act on the phone by code, with no model: "Open a link" (``phone.open_url``) and "Wait for a
2
+ notification" (``phone.notification``). Each rides a run field (``openUrl``, ``waitNotification``) that the step's task
3
+ carries, and a pre-run hook ends the run with the step's result, as the app pack read does (packs/register.py).
4
+
5
+ Open a link goes through the same rules as every other link (deeplinks.decide): a web address or an app Mobster knows
6
+ opens; a link that acts or writes to someone (tel:, sms:, a shortcut) asks, and with nobody to answer is not opened;
7
+ a scheme Mobster never opens is refused when the workflow is saved. The app it opens must be one the person allows.
8
+
9
+ Wait for a notification reads Notification Center every POLL_S seconds until a notification from the app (when named)
10
+ holds the words asked for, or the time is up. A match opens it when ``tap`` is on; no match in time is not a failure:
11
+ the step's ``timeout`` port runs. Notification text is data: codes are masked, and nothing in it instructs anything.
12
+ """
13
+
14
+ import time
15
+
16
+ from .. import deeplinks, harness_api
17
+
18
+ ORDER = 30 # after the front door and packs (2, 3); before the learned paths (20+)
19
+ POLL_S = 15.0
20
+ MAX_MINUTES = 240
21
+ URL_CHARS = 2000
22
+ MATCH_CHARS = 200
23
+
24
+
25
+ def validate_open_url(value, runtime):
26
+ """``openUrl``: {"url"}, a link Mobster may open (a link that asks is allowed here: the hook asks)."""
27
+ if not isinstance(value, dict) or set(value) != {"url"} or not isinstance(value["url"], str):
28
+ raise ValueError("openUrl is {url}")
29
+ url = value["url"].strip()
30
+ if not url or len(url) > URL_CHARS:
31
+ raise ValueError(f"openUrl's url is 1 to {URL_CHARS} characters")
32
+ deeplinks.risk_of(url) # LinkError (a ValueError): a scheme Mobster never opens
33
+ return {"url": url}
34
+
35
+
36
+ def validate_wait_notification(value, runtime):
37
+ """``waitNotification``: {"app": bundle id | None, "match": words | None, "timeoutMinutes", "tap"}."""
38
+ allowed = {"app", "match", "timeoutMinutes", "tap"}
39
+ if not isinstance(value, dict) or set(value) - allowed:
40
+ raise ValueError("waitNotification is {app, match, timeoutMinutes, tap}")
41
+ app, match = value.get("app"), value.get("match")
42
+ minutes, tap = value.get("timeoutMinutes", 10), value.get("tap", True)
43
+ if app is not None and (not isinstance(app, str) or not 1 <= len(app) <= 255):
44
+ raise ValueError("waitNotification's app is an app's bundle id")
45
+ if match is not None and (not isinstance(match, str) or len(match) > MATCH_CHARS):
46
+ raise ValueError(f"waitNotification's match is at most {MATCH_CHARS} characters")
47
+ if type(minutes) is not int or not 1 <= minutes <= MAX_MINUTES:
48
+ raise ValueError(f"waitNotification waits 1 to {MAX_MINUTES} minutes")
49
+ if type(tap) is not bool:
50
+ raise ValueError("waitNotification's tap is true or false")
51
+ return {"app": app, "match": " ".join(match.split()) if match and match.strip() else None,
52
+ "timeoutMinutes": minutes, "tap": tap}
53
+
54
+
55
+ def register():
56
+ """Idempotent."""
57
+ fields = harness_api.run_fields()
58
+ if "openUrl" not in fields:
59
+ harness_api.register_run_field("openUrl", validate_open_url, model=False)
60
+ harness_api.register_pre_run(open_url_hook, order=ORDER)
61
+ if "waitNotification" not in fields:
62
+ harness_api.register_run_field("waitNotification", validate_wait_notification, model=False)
63
+ harness_api.register_pre_run(notification_hook, order=ORDER + 1)
64
+
65
+
66
+ def _result(status, outcome, answer, reason=None, data=None, started=None, actions=1):
67
+ return harness_api.PreRun(result={
68
+ "event": "result", "status": status, "engine": "smart", "outcome": outcome, "answer": answer, "reason": reason,
69
+ "data": data, "actions": actions, "decisions": 0, "costUsd": 0.0, "model": None,
70
+ "elapsed_ms": round((time.monotonic() - started) * 1000) if started else 0,
71
+ "independently_verified": False, "proof": []})
72
+
73
+
74
+ def _refused(reason, started):
75
+ return _result("completion_not_confirmed", "check", None, reason, started=started, actions=0)
76
+
77
+
78
+ def _app_allowed(ctx, bundle):
79
+ allows = getattr(ctx.runtime, "allows_app", None)
80
+ return True if not bundle or ctx.runtime is None or not callable(allows) else bool(allows(ctx.run_id, bundle))
81
+
82
+
83
+ def open_url_hook(ctx, driver, approve, clock=time.monotonic):
84
+ spec = (ctx.extras or {}).get("openUrl")
85
+ if not spec or driver is None:
86
+ return None
87
+ started = clock()
88
+ url = spec["url"]
89
+ try:
90
+ decision = deeplinks.decide(url)
91
+ except deeplinks.LinkError as error:
92
+ return _refused(f"{error} Mobster didn't open it.", started)
93
+ # A web address with no app of its own opens in Safari; that is the app that must be allowed.
94
+ bundle = deeplinks.app_for(url) or ("com.apple.mobilesafari" if url.casefold().startswith(("http://", "https://"))
95
+ else None)
96
+ if not _app_allowed(ctx, bundle):
97
+ return _refused("That link opens an app you haven't allowed, so Mobster didn't open it. Allow the app, then "
98
+ "run the step again.", started)
99
+ if decision.action == "ask":
100
+ request = {"kind": "commit", "step": 1, "operation": "OPEN_URL", "label": f"Open {url[:180]}", "role": "Link",
101
+ "text": url[:500], "title": decision.title or "Open this link?", "act": "open link",
102
+ "app_name": decision.app_name or None}
103
+ answer = "denied"
104
+ if approve is not None:
105
+ try:
106
+ answer = str(approve(request) or "denied")
107
+ except Exception: # noqa: BLE001 -- no answer is no
108
+ answer = "denied"
109
+ if answer != "approved":
110
+ return _refused("That link acts or writes to someone, and nobody approved it, so Mobster didn't open it.",
111
+ started)
112
+ ctx.emit({"event": "action_started", "operation": "OPEN_URL", "target": url[:80]})
113
+ try:
114
+ opened = deeplinks.open_link(driver, url, bundle=bundle)
115
+ snapshot = driver.observe(timeout=10)
116
+ except Exception as error: # noqa: BLE001 -- the link may have opened: never opened again by this step
117
+ return _result("completion_not_confirmed", "check", None,
118
+ f"Mobster sent the link, but couldn't confirm it opened ({type(error).__name__}). It won't "
119
+ "send it again; check the phone.", started=started)
120
+ from ..contract_screen import screen_title
121
+ landed = getattr(snapshot, "bundle_id", "") or opened.get("bundleId") or ""
122
+ data = {"url": url, "landedApp": landed, "screen": (screen_title(snapshot) or "")[:120]}
123
+ return _result("completed", "done", f"Opened {url[:120]}.", data=data, started=started)
124
+
125
+
126
+ def _matches(row, app_bundle, words):
127
+ from ..catalog import app_name
128
+ if app_bundle:
129
+ name = (app_name(app_bundle) or "").casefold()
130
+ if not name or name not in str(row.app or "").casefold():
131
+ return False
132
+ return not words or words.casefold() in " ".join(str(row.text or "").split()).casefold()
133
+
134
+
135
+ def notification_hook(ctx, driver, approve, clock=time.monotonic, sleep=time.sleep, center=None):
136
+ spec = (ctx.extras or {}).get("waitNotification")
137
+ if not spec or driver is None:
138
+ return None
139
+ from ..skills.notifications import NotificationCenter
140
+ started = clock()
141
+ if not _app_allowed(ctx, spec.get("app")):
142
+ return _refused("That notification is from an app you haven't allowed, so Mobster didn't wait for it.", started)
143
+ center = center or NotificationCenter(driver)
144
+ deadline = started + spec["timeoutMinutes"] * 60
145
+ ctx.emit({"event": "action_started", "operation": "WAIT_NOTIFICATION", "target": (spec.get("app") or "")[:80]})
146
+ while True:
147
+ if ctx.cancelled():
148
+ return _refused("Stopped while waiting for a notification.", started)
149
+ try:
150
+ snapshot = driver.observe(timeout=10)
151
+ rows = center.read(origin=getattr(snapshot, "bundle_id", "") or "", size=snapshot, keep_partial=True)
152
+ except Exception as error: # noqa: BLE001 -- a read that fails is tried again until the time is up
153
+ rows = []
154
+ ctx.emit({"event": "notification_read_failed", "error": type(error).__name__})
155
+ found = next((row for row in rows if _matches(row, spec.get("app"), spec.get("match"))), None)
156
+ if found is not None:
157
+ public = found.public(now=time.time())
158
+ if spec["tap"]:
159
+ try:
160
+ tapped = center.open_notification(found.text, origin=getattr(snapshot, "bundle_id", "") or "",
161
+ size=snapshot)
162
+ except Exception as error: # noqa: BLE001 -- it may have opened: never tapped again by this step
163
+ return _result("completion_not_confirmed", "check", None,
164
+ f"Mobster found the notification but couldn't confirm it opened it "
165
+ f"({type(error).__name__}). It won't tap it again; check the phone.",
166
+ started=started)
167
+ if not tapped:
168
+ return _result("completion_not_confirmed", "check", None,
169
+ "Mobster found the notification, but it was gone before it could open it.",
170
+ started=started)
171
+ data = {"title": public["title"], "body": public["body"], "app": public["app"], "at": public["time"],
172
+ "tapped": bool(spec["tap"])}
173
+ return _result("completed", "done", f"Got a notification: {public['title'][:80]}.", data=data,
174
+ started=started)
175
+ if clock() + POLL_S >= deadline:
176
+ return _result("completed", "done", "No matching notification arrived in time.",
177
+ data={"timeout": True}, started=started, actions=0)
178
+ sleep(POLL_S)
@@ -0,0 +1,327 @@
1
+ """Test runs and re-runs from the editor (docs/workflows.mdx › Debug a workflow).
2
+
3
+ - **A test run** (``test``) runs the draft the canvas edits, and a node with a ``pin`` doesn't run: it is done at once
4
+ with its pinned output. Any other run runs the published graph and never reads pins.
5
+ - **From a node** (``{"from": {"batchId", "node", "input"?}}``): the node and everything after it run again; every other
6
+ node takes the result it had in that run. A node it needs that didn't finish there can't be reused, so the re-run is
7
+ refused and says which.
8
+ - **One round** (``{"from": {"batchId", "node": <loop>, "iteration": i}}``): that loop runs round ``i`` alone, with
9
+ the item it had then; nothing after the loop runs.
10
+ - **Only** (``{"only": {"node", "batchId"?}}``): that node runs on the input it had, and nothing after it.
11
+ - **Resume** (``{"resume": batchId}``): a run picks up where one stopped. Everything done is reused; what failed, was
12
+ blocked or was cancelled runs again, unless it may have acted: a step whose outcome is unknown, or one that failed
13
+ after acting outward (``acted``), is never run again and stays for you to review. ``preview`` says which is which.
14
+ - **Resume several** (``resume_all``): each of a list of runs, oldest first, one after another, on one thread.
15
+
16
+ A re-run is a new run with a new key: it never replays the earlier one's requests, and what it reuses it never acts on.
17
+ """
18
+
19
+ import re
20
+ import threading
21
+ import time
22
+
23
+ from .. import harness_api
24
+ from ..api_errors import APIError
25
+ from .catalog import TRIGGERS
26
+ from .run import Debug
27
+
28
+ MAX_RESUMES = 20
29
+ RESUME_WAIT_S = 24 * 3600 # the longest a bulk resume waits for one of its runs before it starts the next
30
+ RUN_ID = re.compile(r"[a-f0-9]{12}")
31
+
32
+ # Kinds that never act outward: one that failed may run again on a resume.
33
+ SAFE_KINDS = frozenset({"transform", "branch", "merge", "output", "wait", "phone.check", "app.read", "loop", "brief",
34
+ "ai.ask"})
35
+
36
+
37
+ def refuse(reason):
38
+ return APIError(reason, 409, "rerun_unavailable")
39
+
40
+
41
+ def _source(owner, workflow_id, batch_id):
42
+ record = owner.batches.get(batch_id) if isinstance(batch_id, str) else None
43
+ if record is None or record["workflowId"] != workflow_id:
44
+ raise refuse("That run isn't kept any more. Mobster keeps the newest 20 runs of each workflow.")
45
+ if record.get("graph") != 2:
46
+ raise refuse("That run is from before workflows were graphs, so its steps can't be reused. Run it again.")
47
+ if record.get("status") == "running":
48
+ raise refuse("That run is still going. Wait for it to end.")
49
+ return record, owner.batches.nodes_with_io(batch_id)
50
+
51
+
52
+ def _top(graph, ident):
53
+ nodes = {node["id"]: node for node in graph["nodes"]}
54
+ at = ident
55
+ while at in nodes and nodes[at].get("parent") is not None:
56
+ at = nodes[at]["parent"]
57
+ return at if at in nodes else None
58
+
59
+
60
+ def _lifted(graph):
61
+ """The top level's edges, an edge into a loop's body counted as one into the loop."""
62
+ out = set()
63
+ for edge in graph.get("edges") or []:
64
+ a, b = _top(graph, edge["source"]), _top(graph, edge["target"])
65
+ if a and b and a != b:
66
+ out.add((a, b))
67
+ return out
68
+
69
+
70
+ def _after(graph, ident):
71
+ edges, out, pending = _lifted(graph), set(), [ident]
72
+ while pending:
73
+ at = pending.pop()
74
+ for a, b in edges:
75
+ if a == at and b not in out:
76
+ out.add(b)
77
+ pending.append(b)
78
+ return out
79
+
80
+
81
+ def _before(graph, ident):
82
+ edges, out, pending = _lifted(graph), set(), [ident]
83
+ while pending:
84
+ at = pending.pop()
85
+ for a, b in edges:
86
+ if b == at and a not in out:
87
+ out.add(a)
88
+ pending.append(a)
89
+ return out
90
+
91
+
92
+ def _recorded(entry, io):
93
+ io = io or {}
94
+ return {"state": entry["state"], "output": io.get("output"), "input": io.get("input"), "error": io.get("error"),
95
+ "handled": bool(entry.get("handled")),
96
+ "port": entry.get("port"), "reason": entry.get("reason"), "status": entry.get("status"),
97
+ "runId": entry.get("runId"), "answer": entry.get("answer"), "startedAt": entry.get("startedAt"),
98
+ "items": io.get("items")}
99
+
100
+
101
+ def _cut(io):
102
+ return any(isinstance(value, dict) and value.get("cut") for value in (io or {}).values())
103
+
104
+
105
+ def _title(graph, ident):
106
+ node = next((n for n in graph["nodes"] if n["id"] == ident), None)
107
+ return f"“{(node or {}).get('title') or ident}”"
108
+
109
+
110
+ def _entry_of(graph, rows):
111
+ trigger = next(((entry, io) for entry, io in rows if entry.get("type") in TRIGGERS and entry["state"] == "done"),
112
+ None)
113
+ if trigger is None:
114
+ raise refuse("That run's trigger isn't kept, so it can't be picked up. Run it again.")
115
+ entry, io = trigger
116
+ if entry["type"] not in {node["type"] for node in graph["nodes"]}:
117
+ raise refuse("The workflow no longer has the trigger that run started from. Run it again.")
118
+ payload = (io or {}).get("output") or {}
119
+ return entry["type"], payload, (payload.get("values") if isinstance(payload, dict) else None) or {}
120
+
121
+
122
+ def plan(owner, workflow, graph, spec, *, test):
123
+ """(Debug, entry, payload, values, source) for a re-run, or Debug(test) with no source for a plain test run."""
124
+ if not spec:
125
+ return Debug(test=test), None, None, None, None
126
+ if not isinstance(spec, dict) or len(spec) != 1 or next(iter(spec)) not in ("from", "only", "resume"):
127
+ raise ValueError("A re-run is from, only or resume")
128
+ kind, value = next(iter(spec.items()))
129
+ if kind == "resume":
130
+ return resume(owner, workflow, graph, value, test=test)
131
+ if not isinstance(value, dict) or set(value) - {"batchId", "node", "iteration", "input"}:
132
+ raise ValueError(f"{kind} takes batchId, node" + (", iteration and input" if kind == "from" else " and input"))
133
+ ident = value.get("node")
134
+ nodes = {node["id"]: node for node in graph["nodes"]}
135
+ if ident not in nodes:
136
+ raise ValueError("Name a step of this workflow")
137
+ batch_id = value.get("batchId")
138
+ if batch_id is None:
139
+ if kind == "from":
140
+ raise ValueError("Name the run to re-run from (batchId)")
141
+ recent = [r for r in owner.batches.recent(workflow["id"]) if r.get("graph") == 2 and r["status"] != "running"]
142
+ if not recent:
143
+ raise refuse("This workflow has no run to take the step's input from. Run it first.")
144
+ batch_id = recent[0]["id"]
145
+ record, rows = _source(owner, workflow["id"], batch_id)
146
+ iteration = value.get("iteration")
147
+ if nodes[ident].get("parent") is not None:
148
+ raise refuse("That step is inside a loop. Re-run the loop, or one round of it (iteration).")
149
+ debug = Debug(test=test)
150
+ after = _after(graph, ident)
151
+ if iteration is not None:
152
+ if nodes[ident]["type"] != "loop" or kind != "from" or type(iteration) is not int or iteration < 0:
153
+ raise ValueError("iteration is a round of a loop: a whole number from 0")
154
+ loop = next(((entry, io) for entry, io in rows if entry["instance"] == ident), None)
155
+ items = (loop[1] or {}).get("items") if loop else None
156
+ rounds = 1 + max((e["iteration"][0] for e, _io in rows if (e.get("iteration") or []) and
157
+ e["instance"].split("@")[0] in nodes and _top(graph, e["instance"].split("@")[0]) == ident),
158
+ default=-1)
159
+ if iteration >= max(rounds, len(items) if isinstance(items, list) else 0):
160
+ raise refuse(f"That run has no round {iteration + 1} of {_title(graph, ident)}.")
161
+ item = items[iteration] if isinstance(items, list) and iteration < len(items) else iteration
162
+ debug.items[ident] = (iteration, item)
163
+ if kind == "only" or iteration is not None:
164
+ what = "Only one round of" if iteration is not None else "Only"
165
+ for later in after:
166
+ debug.skip[later] = f"{what} {_title(graph, ident)} ran in this run."
167
+ rerun = {ident} | after # these run again, or are skipped: never reused
168
+ needed = _before(graph, ident)
169
+ by_node = {}
170
+ for entry, io in rows:
171
+ node_id = entry["instance"].split("@")[0]
172
+ top = _top(graph, node_id)
173
+ if top is None or nodes.get(node_id, {}).get("type") != entry.get("type"):
174
+ continue
175
+ if top in rerun:
176
+ continue
177
+ by_node.setdefault(top, []).append((entry, io))
178
+ if entry["state"] in ("done", "skipped") or entry["state"] == "failed" and entry.get("handled"):
179
+ if top in needed and _cut(io):
180
+ raise refuse(f"{_title(graph, top)}'s result was too big to keep, so it can't be reused. "
181
+ "Run the workflow from the start.")
182
+ debug.reuse[entry["instance"]] = _recorded(entry, io)
183
+ elif top in needed:
184
+ raise refuse(f"{_title(graph, top)} didn't finish in that run, so its result can't be reused. "
185
+ "Re-run from it instead.")
186
+ else:
187
+ debug.reuse[entry["instance"]] = _recorded(entry, io)
188
+ missing = [top for top in needed if top not in by_node]
189
+ if missing:
190
+ raise refuse(f"{_title(graph, missing[0])} didn't run in that run. Run the workflow from the start.")
191
+ if "input" in value:
192
+ debug.inputs[ident] = value["input"]
193
+ entry, payload, values = _entry_of(graph, rows)
194
+ return debug, entry, payload, values, {"batchId": batch_id, kind: ident,
195
+ **({"iteration": iteration} if iteration is not None else {})}
196
+
197
+
198
+ def resume(owner, workflow, graph, batch_id, *, test):
199
+ record, rows = _source(owner, workflow["id"], batch_id)
200
+ debug = Debug(test=test)
201
+ for entry, io in rows:
202
+ verdict, _reason = judge(graph, entry)
203
+ if verdict in ("reuse", "keep"): # kept as it ended: what depends on it stays stopped
204
+ debug.reuse[entry["instance"]] = _recorded(entry, io)
205
+ entry, payload, values = _entry_of(graph, rows)
206
+ return debug, entry, payload, values, {"batchId": batch_id, "resume": True}
207
+
208
+
209
+ def judge(graph, entry):
210
+ """("reuse" | "run" | "keep", why); keep is an instance that may have acted, so what follows it stays stopped."""
211
+ node_id = entry["instance"].split("@")[0]
212
+ node = next((n for n in graph["nodes"] if n["id"] == node_id), None)
213
+ if node is None or node["type"] != entry.get("type"):
214
+ return "run", "It changed since that run."
215
+ state = entry["state"]
216
+ if state in ("done", "skipped") or state == "failed" and entry.get("handled"):
217
+ return "reuse", None
218
+ if state == "unknown":
219
+ return ("keep", "Its outcome is unknown, so Mobster won't run it again. Check it, then "
220
+ "re-run from the step after it.")
221
+ if state == "failed" and node["type"] not in SAFE_KINDS and entry.get("acted") is not False:
222
+ return ("keep", "It may have acted before it failed, so Mobster won't run it again. "
223
+ "Check it, then re-run from it.")
224
+ return "run", None
225
+
226
+
227
+ def preview(owner, workflow, graph, batch_id):
228
+ """GET /api/workflows/{id}/runs/{batchId}/resume: what a resume would reuse, run again and leave."""
229
+ _record, rows = _source(owner, workflow["id"], batch_id)
230
+ out = {"reuse": [], "run": [], "skip": []}
231
+ for entry, _io in rows:
232
+ verdict, reason = judge(graph, entry)
233
+ if verdict == "keep":
234
+ out["skip"].append({"instance": entry["instance"], "reason": reason})
235
+ else:
236
+ out[verdict].append(entry["instance"])
237
+ return out
238
+
239
+
240
+ def recorded_scope(owner, workflow, batch_id, node):
241
+ """The values an expression at ``node`` read in a past run, for POST /api/workflows/evaluate's preview."""
242
+ _record, rows = _source(owner, workflow["id"], batch_id)
243
+ done = {}
244
+ here = None
245
+ for entry, io in rows:
246
+ if entry["instance"] == node:
247
+ here = io or {}
248
+ if entry["state"] == "done" and "@" not in entry["instance"]:
249
+ done[entry["instance"]] = (io or {}).get("output")
250
+ trigger = next((io for entry, io in rows if entry.get("type") in TRIGGERS and entry["state"] == "done"), {}) or {}
251
+ payload = trigger.get("output") if isinstance(trigger.get("output"), dict) else {}
252
+ return {"input": (here or {}).get("input"), "inputs": payload.get("values") or {},
253
+ "vars": workflow["graph"].get("vars") or {}, "node": done,
254
+ "run": {"id": batch_id, "workflowId": workflow["id"], "trigger": None, "startedAt": None, "test": False}}
255
+
256
+
257
+ def resume_all(owner, identifier, batch_ids, request_key, origin=None):
258
+ if not isinstance(batch_ids, list) or not 1 <= len(batch_ids) <= MAX_RESUMES or len(set(batch_ids)) != len(
259
+ batch_ids) or not all(isinstance(b, str) and RUN_ID.fullmatch(b) for b in batch_ids):
260
+ raise ValueError(f"batchIds is a list of 1 to {MAX_RESUMES} different run ids")
261
+ if not isinstance(request_key, str) or not re.fullmatch(r"[A-Za-z0-9_.:-]{1,100}", request_key):
262
+ raise ValueError("Supply one valid Idempotency-Key to resume runs")
263
+ if origin in harness_api.UNATTENDED_ORIGINS:
264
+ raise APIError("A test run or a re-run starts from Mobster's editor.", 403, "test_from_editor")
265
+ with owner.journal.lock:
266
+ owner._get(identifier)
267
+ items = []
268
+ for batch_id in batch_ids:
269
+ record = owner.batches.get(batch_id)
270
+ try:
271
+ if record is None or record["workflowId"] != identifier:
272
+ raise APIError("That run isn't kept any more.", 404, "not_found")
273
+ if record.get("status") in ("running", "completed"):
274
+ raise APIError(f"It {record['status'].replace('_', ' ')}.", 409, "rerun_unavailable")
275
+ if not owner.resume_preview(identifier, batch_id)["run"]:
276
+ raise APIError("Nothing in it would run again.", 409, "rerun_unavailable")
277
+ items.append({"batchId": batch_id, "state": "queued", "createdAt": record.get("createdAt") or 0})
278
+ except APIError as error:
279
+ items.append({"batchId": batch_id, "state": "refused", "reason": str(error), "createdAt": None})
280
+ # Oldest first, as they ran; what is refused is listed after.
281
+ items.sort(key=lambda item: (item["createdAt"] is None, item["createdAt"] or 0))
282
+ for item in items:
283
+ item.pop("createdAt")
284
+ with owner._batches_lock:
285
+ if identifier in owner._resuming and owner._resuming[identifier].is_alive():
286
+ raise APIError("This workflow is already resuming a list of runs.", 409, "resume_busy")
287
+ thread = threading.Thread(target=_resume_each, args=(owner, identifier, items, request_key, origin),
288
+ name=f"mobster-resume-{identifier}", daemon=True)
289
+ owner._resuming[identifier] = thread
290
+ thread.start()
291
+ return {"resumes": [dict(item) for item in items]}
292
+
293
+ def _resume_each(owner, identifier, items, request_key, origin):
294
+ """resume_all's thread: each queued run in turn, after the workflow's run before it has ended."""
295
+ def said():
296
+ _publish(owner, identifier, {"event": "resume_queue", "resumes": [dict(item) for item in items]})
297
+ for item in items:
298
+ if item["state"] != "queued":
299
+ continue
300
+ while identifier in owner.running() and not owner._closing.is_set():
301
+ owner._closing.wait(0.5)
302
+ if owner._closing.is_set():
303
+ item.update(state="refused", reason="Mobster quit before it started.")
304
+ break
305
+ try:
306
+ started = owner.start(identifier, f"{request_key}:{item['batchId']}"[:128],
307
+ rerun={"resume": item["batchId"]}, origin=origin)
308
+ except (APIError, ValueError) as error:
309
+ item.update(state="refused", reason=str(error)[:300])
310
+ said()
311
+ continue
312
+ item.update(state="running", resumedAs=(started.get("batch") or {}).get("id"))
313
+ said()
314
+ deadline = time.monotonic() + RESUME_WAIT_S
315
+ ended = {"status": "running"}
316
+ while ended["status"] in ("running", "queued") and time.monotonic() < deadline:
317
+ if owner._closing.is_set():
318
+ break
319
+ ended = owner.follow(identifier, started, 1.0)
320
+ item.update(state="ended", status=ended["status"])
321
+ said()
322
+
323
+ def _publish(owner, identifier, event):
324
+ publish = getattr(owner.runtime, "publish", None)
325
+ if callable(publish):
326
+ publish("workflows", {"workflowId": identifier, **event})
327
+