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,366 @@
1
+ """Asking the person before an action: the approval card, taps at a point and gestures, the two-headed check."""
2
+
3
+ import re
4
+ import time
5
+ from types import SimpleNamespace
6
+
7
+ from .. import contract_asks as proof_asks
8
+ from .. import contract_screen as proof_screen
9
+ from .. import offers
10
+ from ..agent_hooks import GuardVerdict
11
+ from ..task_policy import COMMIT_CONTROL, GUARD_WORDS, SHEET_OPENERS, commit_label, is_keypad_key, springboard_keypad
12
+ from ..wda.point_gestures import check_gesture_path, same_point_image
13
+ from .protocol import TYPED_OPS
14
+ from .safety import INJECTION_REASON, LIKE, PHONE_LOCKED, TAINT_REASON, UNKNOWN_REASON, GuardStop, _nobody, guard
15
+ from .screen import point_crop, screen_pairs
16
+
17
+ CLOSE_LABELS = frozenset({"cancel", "close"})
18
+ SAME_SHEET = .8
19
+ STOPPED_BEFORE_POINT = "This task stopped before the screen point was used."
20
+
21
+
22
+ class ApprovalsMixin:
23
+ def _tap_xy(self, request, act, aliases, snapshot):
24
+ name = " ".join((act.text or "").split())
25
+ spot = act.target_label or act.target or ""
26
+ found = re.search(r"(\d+(?:\.\d+)?)\s*%?\s*[,; ]\s*(\d+(?:\.\d+)?)", spot)
27
+ if not name or found is None:
28
+ return None, ("TAP_XY needs the point as x,y percent of the screen in target (e.g. \"52,91\") and the "
29
+ "control's name in text.")
30
+ x, y = float(found.group(1)), float(found.group(2))
31
+ if x > 1 or y > 1:
32
+ x, y = x / 100, y / 100
33
+ if not (0 < x < 1 and 0 < y < 1):
34
+ return None, "TAP_XY's point must lie on the screen (0-100 percent each way)."
35
+ listed = self._find(snapshot, name)
36
+ alias = next((a for a, e in aliases.items() if e is listed), None) if listed is not None else None
37
+ if alias is not None:
38
+ return None, f"Refused: {name!r} is a listed element ({alias}): TAP it instead of a point."
39
+ if listed is not None and not is_keypad_key("TAP", listed.role, listed.label):
40
+ # Only an app keypad's key goes by its point: a point on an unlisted App Store Get would install unasked.
41
+ return None, (f"Refused: {name!r} is an element on this screen: TAP it with target_label {name!r} "
42
+ "instead of a point.")
43
+ for element in snapshot.elements:
44
+ label = " ".join((element.label or "").split()[:GUARD_WORDS])
45
+ if not COMMIT_CONTROL.search(label) or label.casefold() in SHEET_OPENERS:
46
+ continue
47
+ ex, ey, ew, eh = element.rect
48
+ reach_y, reach_x = eh, eh * snapshot.height / snapshot.width
49
+ if ex - reach_x <= x <= ex + ew + reach_x and ey - reach_y <= y <= ey + eh + reach_y:
50
+ return None, (f"Refused: that point is within one control-height of {label!r}, which commits. "
51
+ "Tap a listed element, or a point clear of it.")
52
+ kind, under = proof_asks.point_spot(snapshot, x, y)
53
+ if kind == "commit":
54
+ # Where the point is decides, not the name the model gave it.
55
+ label = " ".join((under.label or under.role).split()[:GUARD_WORDS])
56
+ return None, (f"Refused: that point is on {label!r}, which commits. TAP it by its id instead of a point.")
57
+ # The model's name for the point can refuse it, never let it through.
58
+ refused = guard(request, "TAP", SimpleNamespace(label=name, role="Button"))
59
+ if refused:
60
+ return None, refused
61
+ return (x, y), None
62
+
63
+ # None without the contract's ledger of screens: the gate then fails closed.
64
+ def _code_origins(self, text):
65
+ if self._contract is None:
66
+ return None
67
+ digits = re.sub(r"[\s-]", "", str(text or ""))
68
+ if not digits.isdigit() or len(digits) > 10:
69
+ return ()
70
+ rows = []
71
+ for _, _, _, originals in getattr(self._contract, "shown", ()):
72
+ for original in originals.values():
73
+ groups = (re.sub(r"\D", "", g) for g in re.findall(r"\d[\d \-]{2,14}\d", original))
74
+ if any(4 <= len(g) <= 10 and digits in g for g in groups):
75
+ rows.append(original)
76
+ return tuple(dict.fromkeys(rows))[-40:]
77
+
78
+ # A point is bound to no listed element: its ask is forced, and no "Don't ask again" rule meets it.
79
+ def _point_approval(self, point, snapshot, request, chain_typed, step, session=None, *, gesture=None):
80
+ from ..permissions import DONT_ASK
81
+ x, y = point
82
+ asked = ((f"{gesture.kind.capitalize()} {gesture.label}? This may change something.", "other")
83
+ if gesture else proof_asks.point_ask(self._contract, request, snapshot, x, y))
84
+ if asked is None:
85
+ return None, 0.0, False
86
+ title, act = asked
87
+ title = self._mask(title)
88
+ if gesture and self.permissions is not None:
89
+ from ..permissions import action_from
90
+ for location in gesture.points:
91
+ _, actual = proof_asks.point_spot(snapshot, *location)
92
+ spot = actual or SimpleNamespace(label="", role="Point", value="", locator="",
93
+ rect=(*location, .001, .001))
94
+ decision = self.permissions.decide(action_from(
95
+ "TAP_XY", spot, self._masked(snapshot), app_name=self._app_name(snapshot) or ""),
96
+ existing_act="other", forced=True)
97
+ if decision.decision == "block":
98
+ return f"blocked:{decision.reason or 'not allowed'}", 0.0, False
99
+ asker = self.approve
100
+ if session is not None:
101
+ asker = asker or self.forced_approve
102
+ elif asker is None and self.run_state.injected and self.permissions is not None:
103
+ # A screen this run tried to steer Mobster: a spot asks in Full access too.
104
+ asker = self.floor_approve or _nobody
105
+ self.run_state.ask_reason = INJECTION_REASON
106
+ elif asker is None and (self.permissions is None or getattr(self.permissions, "mode", None) == DONT_ASK):
107
+ # Full access, or a run with no permission mode and no approver (a benchmark): nothing asks.
108
+ return None, 0.0, False
109
+ if asker is None:
110
+ return ("refused:a tap at a spot that no listed control is, or while the task commits, needs the person's "
111
+ "OK, and nobody can approve it here. TAP a listed element instead", 0.0, False)
112
+ w, h = .08, .04
113
+ spot = SimpleNamespace(label="", role="Point", value="", locator="",
114
+ rect=(min(max(x - w / 2, 0), 1 - w), min(max(y - h / 2, 0), 1 - h), w, h))
115
+ reason = None
116
+ if self.permissions is not None:
117
+ from ..permissions import action_from
118
+ decision = self.permissions.decide(action_from(
119
+ "TAP_XY", spot, self._masked(snapshot), app_name=self._app_name(snapshot) or ""), existing_act=act,
120
+ forced=True)
121
+ reason = decision.reason or None
122
+ # The card shows the spot itself, secrets painted over as in the model's picture.
123
+ crop = point_crop(self.driver, x, y, since=getattr(snapshot, "read_started", None),
124
+ blur=self._secret_blur(snapshot))
125
+ named = proof_asks.point_target(snapshot, x, y)[1] == "on"
126
+ verdict, waited = self._ask(asker, title, "other" if named or gesture else act,
127
+ gesture.operation if gesture else "TAP_XY", spot, snapshot, None,
128
+ chain_typed, step, None, reason=reason, crop=crop)
129
+ return verdict, waited, True
130
+
131
+ # A coordinate belongs to the picture the model saw, never to a tree that may have moved since.
132
+ def point_screen(self, previous, image, points):
133
+ if previous is None or image is None:
134
+ raise ValueError("Read a screenshot before choosing screen points.")
135
+ changed = "The screen changed. Read a fresh screenshot before choosing screen points."
136
+
137
+ def read():
138
+ if self._stopping():
139
+ raise ValueError(STOPPED_BEFORE_POINT)
140
+ snapshot = self.driver.observe(timeout=5)
141
+ self._check_app(snapshot)
142
+ if springboard_keypad(snapshot):
143
+ raise GuardStop(GuardVerdict("stop", "phone_locked", PHONE_LOCKED))
144
+ if (snapshot.bundle_id != previous.bundle_id
145
+ or snapshot.content_fingerprint != previous.content_fingerprint):
146
+ raise ValueError(changed)
147
+ return snapshot
148
+
149
+ self._guard_check("resume")
150
+ read()
151
+ current = self._screenshot(previous)
152
+ if current is None or not all(same_point_image(image, current, x * 100, y * 100) for x, y in points):
153
+ raise ValueError(changed)
154
+ return read()
155
+
156
+ def gesture_screen(self, previous, image, gesture, request):
157
+ fresh = self.point_screen(previous, image, gesture.points)
158
+ check_gesture_path(fresh, gesture)
159
+ for label in (gesture.start_label, gesture.end_label):
160
+ if label is not None:
161
+ reason = guard(request, "TAP", SimpleNamespace(label=label, role="Button"))
162
+ if reason:
163
+ raise ValueError(reason)
164
+ return fresh
165
+
166
+ # One message, one approval: a Return approved in a composer that didn't send covers the Send after it, once.
167
+ def _approval(self, gate, op, element, snapshot, text, chain_typed, step, asker=None, force=False, unknown=False):
168
+ asker = asker or self.approve
169
+ if element is None or asker is None:
170
+ return None, 0.0
171
+ item, payload, amount = (gate.item, gate.payload, gate.amount) if gate is not None else (None, None, None)
172
+ if unknown:
173
+ label = " ".join((element.label or "").split()[:GUARD_WORDS]) or op
174
+ act = item.act if item is not None else None
175
+ if self.permissions is not None:
176
+ decision = self._gate(op, element, snapshot, text, payload, chain_typed, act or "other", True)
177
+ act = act or decision.act
178
+ return self._ask(asker, f"{label} again?", act or "other", op, element, snapshot, payload, chain_typed,
179
+ step, item, text=text, reason=UNKNOWN_REASON)
180
+ if force and (self._contract is None or not proof_asks.asks(item)):
181
+ label = " ".join((element.label or "").split()[:GUARD_WORDS]) or op
182
+ title, act = f"{label}?", (item.act if item is not None else "other")
183
+ if self.permissions is not None:
184
+ self._gate(op, element, snapshot, text, payload, chain_typed, act, True)
185
+ if payload is None and op in TYPED_OPS:
186
+ payload = text
187
+ return self._ask(asker, title, act, op, element, snapshot, payload, chain_typed, step, item)
188
+ title = act = party = None
189
+ seen = False
190
+ if self._contract is not None and proof_asks.asks(item):
191
+ app_name = self._app_name(snapshot)
192
+ shown = (proof_screen.visible_recipient(snapshot, app_name, element)
193
+ if item.act in proof_screen.MESSAGE_ACTS else None)
194
+ approved = self.run_state.approved_send
195
+ if (approved is not None and not approved["used"] and approved["item"] == item.id
196
+ and approved["payload"] == proof_screen.plain(payload or (chain_typed[-1] if chain_typed else ""))
197
+ and approved["thread"] == proof_screen.screen_title(snapshot) and approved["app"] == app_name
198
+ and approved["typed"] == len(self._contract.typed) and approved["receipts"] == len(item.receipts)):
199
+ approved["used"] = True
200
+ self.run_state.asked = "approved"
201
+ self._emit({"event": "frontier_approval", "step": step, "act": item.act, "answer": "approved",
202
+ "reused": True})
203
+ return None, 0.0
204
+ title, act = proof_asks.approval_title(item, amount, shown=shown), item.act
205
+ seen = bool(shown) and not proof_screen.internal_name(shown)
206
+ sending = payload or (chain_typed[-1] if chain_typed else "") or (text if op == "TYPE_SUBMIT" else "")
207
+ if item.act in proof_screen.MESSAGE_ACTS and sending and self._contract.screen_text_in(sending):
208
+ # Text copied from the screen into a message asks whatever the person's rules say.
209
+ force = True
210
+ self.run_state.ask_reason = self.run_state.ask_reason or TAINT_REASON
211
+ party = (shown if seen else None) or proof_screen.recipient(item)
212
+ elif self._contract is None:
213
+ label = " ".join((element.label or "").split()[:GUARD_WORDS])
214
+ if (op in ("TAP", "LONG_PRESS") and COMMIT_CONTROL.search(label) and label.casefold() not in SHEET_OPENERS
215
+ and not proof_screen.is_opener(label) and commit_label(element.label, element.role) is not None):
216
+ title, act = f"Tap {label!r}?", "other"
217
+ reason = decision = None
218
+ if self.permissions is not None:
219
+ decision = self._gate(op, element, snapshot, text, payload, chain_typed, act, force)
220
+ if decision.decision == "allow":
221
+ return None, 0.0
222
+ reason = decision.reason or None
223
+ if title is None:
224
+ title, act = decision.title, decision.act or "other"
225
+ elif title is None:
226
+ return None, 0.0
227
+ if act in offers.MONEY_ACTS:
228
+ reason = offers.reason(self._masked(snapshot), element.label, reason)
229
+ remember = (self.permissions.remember(decision, getattr(snapshot, "bundle_id", "") or "",
230
+ self._app_name(snapshot) or "") if decision is not None else None)
231
+ return self._ask(asker, title, act, op, element, snapshot, payload, chain_typed, step, item, text=text,
232
+ reason=reason, remember=remember, recipient=party, recipient_seen=seen)
233
+
234
+ def _gate(self, op, element, snapshot, text, payload, chain_typed, act, forced):
235
+ from ..permissions import action_from
236
+ return self.permissions.decide(action_from(
237
+ op, element, self._masked(snapshot), app_name=self._app_name(snapshot) or "",
238
+ text=text if op in TYPED_OPS else None,
239
+ origins=self._code_origins(text or payload or (chain_typed[-1] if chain_typed else ""))),
240
+ existing_act=act, forced=forced)
241
+
242
+ def _ask(self, asker, title, act, op, element, snapshot, payload, chain_typed, step, item, text=None, reason=None,
243
+ remember=None, recipient=None, crop=None, recipient_seen=False):
244
+ if payload is None:
245
+ payload = text if op in TYPED_OPS else (chain_typed[-1] if chain_typed else None)
246
+ self.run_state.cards += 1
247
+ if self.run_state.ask_reason:
248
+ reason, self.run_state.ask_reason = self.run_state.ask_reason, None
249
+ x, y, w, h = element.rect
250
+ if crop is None and op != "TAP_XY" and asker is not _nobody:
251
+ crop = point_crop(self.driver, x + w / 2, y + h / 2, since=getattr(snapshot, "read_started", None),
252
+ blur=self._secret_blur(snapshot))
253
+ request_ = {"kind": "commit", "step": step, "operation": op, "label": (element.label or "")[:200],
254
+ "role": element.role, "text": payload, "title": title, "act": act,
255
+ "app_name": self._app_name(snapshot) or None, "reason": reason, "remember": remember,
256
+ "target": {"x": round(x, 4), "y": round(y, 4), "w": round(w, 4), "h": round(h, 4)}}
257
+ if self.run_state.acting_as:
258
+ request_["acting_as"] = self.run_state.acting_as
259
+ if self.run_state.media_checked is not None:
260
+ request_["media_checked"] = self.run_state.media_checked
261
+ if recipient:
262
+ # Who it goes to is never journaled: the title already names them there.
263
+ request_["recipient"] = recipient
264
+ # Only a recipient the screen showed lets a review queue's Approve stand for this card.
265
+ request_["recipient_seen"] = bool(recipient_seen)
266
+ if crop:
267
+ request_["crop"] = crop
268
+ waited = time.monotonic()
269
+ answer = asker(request_)
270
+ waited = time.monotonic() - waited
271
+ if answer == "approved":
272
+ self.run_state.asked = "approved"
273
+ elif answer == "denied":
274
+ self.run_state.declined_on = snapshot
275
+ self._emit({"event": "frontier_approval", "step": step, "act": act,
276
+ "answer": answer.split(":", 1)[0] if isinstance(answer, str) else None})
277
+ if (answer == "approved" and self._contract is not None and item is not None and op in ("SUBMIT", "TYPE_SUBMIT")
278
+ and item.act in proof_screen.MESSAGE_ACTS and payload):
279
+ self.run_state.approved_send = {"item": item.id, "payload": proof_screen.plain(payload), "used": False,
280
+ "thread": proof_screen.screen_title(snapshot), "app": self._app_name(snapshot),
281
+ "typed": len(self._contract.typed) + (1 if op == "TYPE_SUBMIT" else 0),
282
+ "receipts": len(item.receipts)}
283
+ return (None if answer == "approved" else str(answer or "denied")), waited
284
+
285
+ def _asks_mode(self):
286
+ """Whether this run asks before acting: a mode that asks (not Full access) or a project policy that can ask."""
287
+ from ..permissions import DONT_ASK
288
+ gate = self.permissions
289
+ narrowed = getattr(gate, "narrowed", False) is True
290
+ return gate is not None and (getattr(gate, "mode", None) != DONT_ASK or narrowed)
291
+
292
+ def _is_commit(self, gate, op, element, snapshot, text):
293
+ if element is None:
294
+ return False
295
+ if gate is not None and gate.item is not None and proof_asks.asks(gate.item):
296
+ return True
297
+ if self.permissions is None:
298
+ return False
299
+ from ..permissions import action_from
300
+ try:
301
+ kind, _, _ = self.permissions.reading(action_from(
302
+ op, element, self._masked(snapshot), app_name=self._app_name(snapshot) or "",
303
+ text=text if op in TYPED_OPS else None), None)
304
+ except Exception: # unsure: a commit
305
+ return True
306
+ return kind == "commit"
307
+
308
+ @staticmethod
309
+ def _out_of_scope(op, element, request):
310
+ from ..permissions.consequence import names_switch
311
+ return (op == "TAP" and element is not None and element.role in ("Switch", "Toggle")
312
+ and not names_switch(request, element.label))
313
+
314
+ # Pixels that can't be read leave the tree's reading standing.
315
+ def _two_heads(self, asker, op, element, snapshot, chain_typed, step):
316
+ from ..permissions.consequence import label_act
317
+ from ..permissions.rules import norm
318
+ try:
319
+ shown = self.region_reader(self.driver, element, snapshot)
320
+ except Exception:
321
+ shown = None
322
+ shown = " ".join(str(shown or "").split())
323
+ if not shown or norm(shown) == norm(element.label):
324
+ return None, 0.0
325
+ act = label_act(shown, "Button")
326
+ if act is None or label_act(element.label, element.role) == act:
327
+ return None, 0.0
328
+ self._emit({"event": "frontier_two_heads", "step": step, "act": act})
329
+ self.run_state.ask_reason = (
330
+ f"The screen shows “{shown[:30]}” here; the app calls it “{(element.label or element.role)[:30]}”")
331
+ return self._ask(asker, f"Tap “{shown[:40]}”?", act, op, element, snapshot, None, chain_typed, step, None)
332
+
333
+ # One tap, journaled before it is sent and never repeated, only on the screen the person said no on.
334
+ def _close_declined_sheet(self):
335
+ declined, self.run_state.declined_on = self.run_state.declined_on, None
336
+ if declined is None or self._stopping():
337
+ return
338
+ try:
339
+ now = self._read_once()
340
+ except Exception:
341
+ return
342
+ before, after = screen_pairs(declined), screen_pairs(now)
343
+ if not before or not after or len(before & after) / len(before | after) < SAME_SHEET:
344
+ return
345
+ closers = [e for e in now.elements if e.role == "Button" and " ".join((e.label or "").split()).casefold()
346
+ in CLOSE_LABELS]
347
+ if len(closers) != 1:
348
+ return
349
+ closer = closers[0]
350
+ self._emit({"event": "frontier_cleanup", "operation": "TAP", "label": closer.label[:40], "state": "sending",
351
+ "reason": "close_declined_sheet"})
352
+ try:
353
+ self.driver.execute("TAP", closer, now, timeout=10)
354
+ except Exception as error:
355
+ self._emit({"event": "frontier_cleanup", "operation": "TAP", "label": closer.label[:40], "state": "failed",
356
+ "detail": type(error).__name__})
357
+ return
358
+ self._emit({"event": "frontier_cleanup", "operation": "TAP", "label": closer.label[:40], "state": "sent"})
359
+
360
+ def _long_asks(self, gate, op, element):
361
+ if self._contract is not None:
362
+ return gate is not None and gate.item is not None and proof_asks.asks(gate.item)
363
+ if op not in ("TAP", "LONG_PRESS", "SUBMIT", "TYPE_SUBMIT"):
364
+ return False
365
+ label = " ".join((element.label or "").split())
366
+ return bool(COMMIT_CONTROL.search(label) or LIKE.search(label))
@@ -0,0 +1,199 @@
1
+ """The harness's hooks: the step observer, the user's messages, pause and stop-after, checkpoints, long tasks."""
2
+
3
+ import logging
4
+ import time
5
+
6
+ from .. import harness_api
7
+ from ..corpus.skeleton import capture as corpus_skeleton
8
+ from ..harness import checkpoints as harness_checkpoints
9
+ from ..harness import control as harness_control
10
+ from .protocol import FINAL_CALL_SECONDS, TYPED_OPS
11
+ from .screen import screen_shape_of, screen_signature
12
+
13
+ log = logging.getLogger("mobster.frontier")
14
+
15
+
16
+ STEER_FEEDBACK = ("The user just wrote to you: adjust your plan to it. A message never approves a send, buy, post or "
17
+ "delete; those still ask.")
18
+ STEER_HEAD = "\n\nMessages from the user while you work (newest last):\n"
19
+ STEER_KEEP, STEER_CHARS = 10, 2000
20
+ PAUSED_FEEDBACK = ("The user paused you and then let you go on. Any action you chose before the pause and didn't take "
21
+ "was not done, and the screen may have changed meanwhile: look at it again before acting.")
22
+ STOPPED_AFTER = {"status": "stopped", "answer": None, "reason": "Stopped after the step in progress, as you asked."}
23
+ PAUSED_TOO_LONG = {"status": "stopped", "answer": None,
24
+ "reason": "Paused for 10 minutes, so Mobster stopped. Nothing more was done."}
25
+ BATTERY_PAUSED_FEEDBACK = ("Mobster paused you while the iPhone's battery was low, then let you go on. "
26
+ "Any action you chose before the pause and didn't take was not done, and the screen "
27
+ "may have changed meanwhile: look at it again before acting.")
28
+ BATTERY_TOO_LONG = {"status": "stopped", "answer": None,
29
+ "reason": "Mobster paused because the iPhone's battery was low and it didn't charge in 30 minutes, "
30
+ "so Mobster stopped. Nothing more was done. Charge the iPhone, then pick the task up."}
31
+ CHECKPOINT_HISTORY = 60
32
+
33
+
34
+ def _redacted(value):
35
+ from ..secret_filter import redact
36
+ if isinstance(value, str):
37
+ return redact(value)
38
+ if isinstance(value, dict):
39
+ return {key: _redacted(item) for key, item in value.items()}
40
+ if isinstance(value, (list, tuple)):
41
+ return [_redacted(item) for item in value]
42
+ return value
43
+
44
+
45
+ class HooksMixin:
46
+ def _observe_step(self, step, index, op, before, element, text, changed, gate):
47
+ asked, self.run_state.asked = self.run_state.asked, None
48
+ if self.step_observer is None:
49
+ return
50
+ try:
51
+ target = None
52
+ if element is not None:
53
+ target = {"label": self._mask(element.label), "role": element.role, "id": element.locator or None,
54
+ "frame": [round(float(v), 4) for v in element.rect]}
55
+ app = getattr(before, "bundle_id", None) or None
56
+ recorded = gate.item.act if gate is not None and getattr(gate, "item", None) is not None else None
57
+ record = harness_api.StepRecord(
58
+ run_id=getattr(self.run_context, "run_id", "") or "", step=step, index=index, op=op, app=app,
59
+ target=target, text=self._mask(text) if text is not None and (op in TYPED_OPS or op == "SCROLL_TO")
60
+ else None,
61
+ screen=screen_signature(before), changed=bool(changed), approval=asked,
62
+ commit=harness_api.commit_act(op, element.label if element is not None else None, recorded, app),
63
+ shape=tuple(screen_shape_of(before)), corpus=corpus_skeleton(before, element))
64
+ self.step_observer(record)
65
+ except Exception:
66
+ log.exception("step_observer failed")
67
+
68
+ def _turn_sections(self, calls, snapshot, plan, notes, started, spent):
69
+ out = ""
70
+ if calls == 0 or (self.run_state.initial is not None and calls == self.run_state.initial.steps_used):
71
+ out += self._first_turn_context
72
+ if self.turn_context is not None:
73
+ try:
74
+ state = harness_api.TurnState(step=calls, app=getattr(snapshot, "bundle_id", None), plan=plan,
75
+ notes=tuple(notes), elapsed_s=round(time.monotonic() - started, 2),
76
+ cost_usd=spent)
77
+ for block in self.turn_context(state) or ():
78
+ out += harness_api.render_block(block)
79
+ except Exception:
80
+ log.exception("turn_context failed")
81
+ if self.run_state.steered:
82
+ out += STEER_HEAD + "\n".join(f"- {message.text}" for message in self.run_state.steered)
83
+ return out
84
+
85
+ def _take_steering(self, calls, history):
86
+ if self.steering is None:
87
+ return []
88
+ while len(self.run_state.steered) > 1 and sum(len(m.text) for m in self.run_state.steered) > STEER_CHARS:
89
+ self.run_state.steered.pop(0)
90
+ try:
91
+ if not self.steering.pending():
92
+ return []
93
+ fresh = list(self.steering.drain())
94
+ except Exception:
95
+ log.exception("steering failed")
96
+ return []
97
+ if not fresh:
98
+ return []
99
+ # No message is lost: the budget only drops ones the model has already read.
100
+ kept = (self.run_state.steered + fresh)[-max(STEER_KEEP, len(fresh)):]
101
+ seen = len(kept) - len(fresh)
102
+ while seen > 0 and sum(len(m.text) for m in kept) > STEER_CHARS:
103
+ kept.pop(0)
104
+ seen -= 1
105
+ self.run_state.steered = kept
106
+ history.append(f"{calls}: the user wrote to you ({len(fresh)} message{'s' if len(fresh) != 1 else ''})")
107
+ self._emit({"event": "steer_applied", "ids": [m.id for m in fresh], "step": calls})
108
+ return fresh
109
+
110
+ def _pause_asked(self):
111
+ return self.steering is not None and harness_control.paused(self.steering)
112
+
113
+ def _stop_after_asked(self):
114
+ return self.steering is not None and harness_control.stop_after_requested(self.steering)
115
+
116
+ def _hold(self, calls, history):
117
+ t_pause = time.monotonic()
118
+ why = harness_control.reason(self.steering)
119
+ battery = bool(why) and why.get("code") == "phone_battery"
120
+ self.run_state.battery_pause = battery
121
+ event = {"event": "paused", "step": calls}
122
+ if battery:
123
+ event.update(reason="phone_battery", **({"level": why["level"]} if type(why.get("level")) is int else {}))
124
+ self._emit(event)
125
+ outcome = harness_control.wait_while_paused(self.steering, cancelled=self.cancelled)
126
+ waited = time.monotonic() - t_pause
127
+ if outcome == "continued":
128
+ self._emit({"event": "continued", "step": calls, **({"reason": "phone_battery"} if battery else {})})
129
+ history.append(
130
+ f"{calls}: Mobster paused you while the iPhone's battery was low, then let you go on"
131
+ if battery else f"{calls}: the user paused you, then let you go on")
132
+ return waited, None
133
+ if outcome == "timeout":
134
+ return waited, dict(BATTERY_TOO_LONG if battery else PAUSED_TOO_LONG)
135
+ if outcome == "stop_after":
136
+ return waited, dict(STOPPED_AFTER)
137
+ return waited, {"status": "stopped", "answer": None, "reason": "Stopped by the user"}
138
+
139
+ def _first_screen(self):
140
+ take = harness_api.hooks().first_screen if self.run_context is not None else None
141
+ if take is not None:
142
+ try:
143
+ taken = take(self.driver, self.run_context)
144
+ except Exception:
145
+ taken = None
146
+ if taken is not None:
147
+ snapshot, age = taken
148
+ self._emit({"event": "prewarm_used", "age_ms": round(age * 1000)})
149
+ return snapshot
150
+ return self._observe()
151
+
152
+ def _send_checkpoint(self, calls, snapshot, plan, notes, history, started, spent):
153
+ if self.checkpoint is None and self.run_context is None:
154
+ return
155
+ try:
156
+ state = {"step": calls, "plan": self._mask(plan), "notes": [self._mask(n) for n in notes],
157
+ "history": [self._mask(line) for line in history[-CHECKPOINT_HISTORY:]],
158
+ "app": getattr(snapshot, "bundle_id", None), "cost_usd": spent,
159
+ "elapsed_s": round(time.monotonic() - started, 2),
160
+ "contract": self._contract.summary() if self._contract is not None else None}
161
+ if self.longform is not None:
162
+ try:
163
+ self.longform.capture_contract(self._contract)
164
+ except Exception:
165
+ pass # supplemental receipts never suppress the ordinary checkpoint
166
+ state["longform"] = self.longform.checkpoint()
167
+ if self.checkpoint is not None:
168
+ state = _redacted(state) # the hook's contract (S7.3): masked, then through the secret filter
169
+ self.checkpoint(state)
170
+ if self.run_context is not None:
171
+ # Masked here; the store's writer thread redacts it, off the path between an action and the next call.
172
+ harness_checkpoints.keep(self.run_context, state)
173
+ except Exception:
174
+ log.exception("checkpoint failed")
175
+
176
+ def _waited(self, seconds):
177
+ if self.longform is not None and seconds:
178
+ self.longform.add_wait(seconds)
179
+
180
+ def _long_final(self, session, calls, started, spent, reserve):
181
+ covered = session.final_reason()
182
+ if covered is None and self.max_cost_usd is not None and spent + reserve >= self.max_cost_usd:
183
+ covered = "spend"
184
+ if covered is None and calls >= self.max_steps - 1:
185
+ covered = "max_steps"
186
+ if covered is None and time.monotonic() - started > self.max_seconds - FINAL_CALL_SECONDS:
187
+ covered = "timeout"
188
+ if covered is None:
189
+ return None
190
+ status = {"items": "completed", "phone_time": "completed", "list_end": "completed", "spend": "budget",
191
+ "stuck": "blocked"}.get(covered, covered)
192
+ reason = {"items": "the budget's items are covered", "phone_time": "the phone time it was given is used",
193
+ "list_end": "the list ended: every row is read",
194
+ "stuck": f"it made no progress ({session.stalled})",
195
+ "spend": f"spend cap ${self.max_cost_usd} reached (with the report's reserve)",
196
+ "max_steps": f"{self.max_steps} turns used", "timeout": f"{self.max_seconds} s used"}[covered]
197
+ self._emit({"event": "longform_budget_end", "reason": covered, "step": calls,
198
+ **({"detail": session.stalled} if covered == "stuck" else {})})
199
+ return {"status": status, "answer": None, "reason": reason, "covered": covered}