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,192 @@
1
+ """The stages that run once an action was sent: the screen after, the record, and whether the chain goes on."""
2
+
3
+ import time
4
+
5
+ from ... import contract_asks as proof_asks
6
+ from ... import contract_screen as proof_screen
7
+ from ...agent_hooks import GuardVerdict
8
+ from ..observe import TWO_READ_OPS, UNPROVEN_SETTLES
9
+ from ..prompt import row_texts
10
+ from ..protocol import HOUSEKEEPING_OPS, SCROLL_OPS, TYPED_OPS
11
+ from ..safety import COVERED, PENDING_REASON, SAME_FAILURES_TO_CHECK, GuardStop, consequential, input_shows
12
+ from ..screen import names_act, screen_signature, text_status
13
+
14
+ LOG_LABEL_CHARS, LOG_TEXT_CHARS = 200, 600
15
+
16
+
17
+ def settled_rect(op, element, snapshot):
18
+ """Where the element this step acted on sits on the screen the step ends on, as fractions ``[x, y, w, h]``, for a
19
+ tap or typing step whose element is still there (the same locator, role and label). None when it is unknown."""
20
+ if element is None or not (op in TYPED_OPS or op in ("TAP", "LONG_PRESS")):
21
+ return None
22
+ for other in getattr(snapshot, "elements", None) or ():
23
+ if (other.locator and other.locator == element.locator and other.role == element.role
24
+ and other.label == element.label):
25
+ try:
26
+ x, y, w, h = (float(value) for value in other.rect)
27
+ except (TypeError, ValueError):
28
+ return None
29
+ return [round(x, 4), round(y, 4), round(w, 4), round(h, 4)] if w > 0 and h > 0 else None
30
+ return None
31
+
32
+
33
+ def may_commit(op, element, snapshot):
34
+ """Whether a press may have committed something, by the loop's own test. A test that fails counts as a commit."""
35
+ try:
36
+ return consequential(op, element, snapshot)
37
+ except Exception:
38
+ return True
39
+
40
+
41
+ def action_label(op, element, app):
42
+ if op in ("SWIPE_LEFT", "SWIPE_RIGHT"):
43
+ return element.label[:120] if element is not None and element.label else None
44
+ return element.label[:120] if element else app
45
+
46
+
47
+ def settle(agent, attempt, turn):
48
+ index = turn.index
49
+ op, element, gate, before = attempt.op, attempt.element, attempt.gate, attempt.before
50
+ attempt.exec_ms = agent._spend("exec", attempt.t_exec)
51
+ t_settle = time.monotonic()
52
+ following = turn.chunk[index + 1].op if index + 1 < len(turn.chunk) else None
53
+ last = following in (None, "DONE", "BLOCKED")
54
+ # HOME right before LAUNCH_APP uses nothing on the home screen, so its settle is skipped.
55
+ passing = op == "HOME" and following == "LAUNCH_APP"
56
+ if passing:
57
+ turn.snapshot = before
58
+ elif op in TYPED_OPS and following == "SUBMIT":
59
+ # Return goes to the field just typed into: one read finds it.
60
+ turn.snapshot = agent._read_once()
61
+ else:
62
+ turn.snapshot = agent._settle(before, last and op not in TWO_READ_OPS, rest=last and op == "LAUNCH_APP")
63
+ turn.unproven = last and getattr(agent.driver, "settled_by", None) in UNPROVEN_SETTLES
64
+ attempt.settle_ms = agent._spend("settle", t_settle)
65
+ if agent._contract is not None:
66
+ agent._contract.observe(agent._masked(turn.snapshot), agent._app_name(turn.snapshot), turn.step)
67
+ attempt.changed = passing or turn.snapshot.content_fingerprint != before.content_fingerprint
68
+ if (not attempt.changed and gate is not None and gate.item is not None and proof_asks.asks(gate.item)
69
+ and agent.permissions is not None and gate.item.status != "closed"
70
+ and str(gate.item.count or "1").strip() in ("", "1")):
71
+ # Sent, no receipt yet: the same act again asks first, with the warning, in every mode.
72
+ agent.run_state.held_items[gate.item.id] = PENDING_REASON
73
+ gone = agent._row_gone(op, element, before, turn.snapshot)
74
+ if gone is not None:
75
+ turn.result = {"status": "blocked", "answer": None, "code": "unexpected_change",
76
+ "reason": gone}
77
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "row_gone"})
78
+ return "end"
79
+ if op in SCROLL_OPS and following in SCROLL_OPS and attempt.changed:
80
+ # The next swipe was chosen without this screen: its rows would scroll past unseen (mem-009).
81
+ if turn.turn_rows is None:
82
+ turn.turn_rows = set(row_texts(agent._masked(turn.turn_screen)))
83
+ for row in row_texts(agent._masked(turn.snapshot)):
84
+ if row not in turn.turn_rows:
85
+ turn.turn_rows.add(row)
86
+ agent.run_state.passed.append(row)
87
+ done_key = agent._act_key(op, element, before) if attempt.changed else None
88
+ if agent.run_state.act_seen:
89
+ where, now = agent._app_name(turn.snapshot), None
90
+ for (act_app, _, _), looked in agent.run_state.act_seen.items():
91
+ if act_app == where:
92
+ now = now or screen_signature(turn.snapshot)
93
+ looked.add(now)
94
+ if done_key is not None:
95
+ agent.run_state.acts_done[done_key] = turn.step
96
+ agent.run_state.act_left[done_key] = screen_signature(turn.snapshot)
97
+ agent.run_state.act_seen[done_key] = {agent.run_state.act_left[done_key]}
98
+ agent.run_state.act_refused.pop(done_key, None)
99
+
100
+
101
+ def record(agent, attempt, turn):
102
+ index = turn.index
103
+ op, text, app, element, gate, before, changed = (
104
+ attempt.op, attempt.text, attempt.app, attempt.element, attempt.gate, attempt.before, attempt.changed)
105
+ turn.after = op if changed or op in TYPED_OPS else None
106
+ turn.last_done = (attempt.key, before.content_fingerprint, turn.snapshot.content_fingerprint)
107
+ turn.recent.append(attempt.spot)
108
+ agent.last_screen = turn.snapshot
109
+ agent._emit({"event": "frontier_action", "step": turn.step, "index": index, "operation": op,
110
+ "target_label": action_label(op, element, app), "changed": changed,
111
+ "text": agent._mask(text[:200]) if text is not None and (op in TYPED_OPS or op == "SCROLL_TO") else None,
112
+ "field": ("search" if proof_screen.is_search_field(element) else "composer"
113
+ if proof_screen.COMPOSER.search(element.label or "") else "field")
114
+ if element is not None and op in TYPED_OPS else None,
115
+ "app_name": agent._app_name(before) or None,
116
+ # Whether a press may have committed something: the step's commit.
117
+ **({"consequential": may_commit(op, element, before)} if op in ("TAP", "LONG_PRESS") else {}),
118
+ **({"element_rect": rect} if (rect := settled_rect(op, element, turn.snapshot)) else {}),
119
+ "refresh_ms": attempt.refresh_ms, "exec_ms": attempt.exec_ms, "settle_ms": attempt.settle_ms,
120
+ **({"settle_prediction": ps} if (ps := getattr(agent.driver, "predicted_settle", None)) else {})})
121
+ what = (element.label[:LOG_LABEL_CHARS] if element else app or "")
122
+ receipt = agent.run_state.last_receipt
123
+ if receipt is not None:
124
+ agent._emit({"event": "frontier_text", "step": turn.step, "index": index, "method": receipt.get("method"),
125
+ "chars": receipt.get("chars"), "matches": receipt.get("matches"),
126
+ "repaired": receipt.get("repaired"), "flattened": receipt.get("flattened")})
127
+ turn.history.append(f"{turn.step}.{index}: {op} {what!r}" + (f" text={text[:LOG_TEXT_CHARS]!r}" if text else "")
128
+ + (" -> screen changed" if changed else " -> NO visible change")
129
+ + (f"; field: {text_status(receipt)[:160]}" if receipt is not None else "")
130
+ + ("; Return NOT pressed" if receipt is not None and op == "TYPE_SUBMIT"
131
+ and receipt.get("submitted") is False else ""))
132
+ if agent.run_state.readback:
133
+ if changed and op in ("TAP", "LONG_PRESS", "TAP_XY") and names_act(op, element, text):
134
+ said = agent._read_back(turn.snapshot)
135
+ if said:
136
+ turn.history[-1] += "; " + said
137
+ agent._remember_lists(turn.snapshot)
138
+ if op == "SET_TEXT" and text and not changed and not input_shows(element, text):
139
+ # Only an unchanged SET_TEXT stops what follows: a TYPE's text is checked by its receipt.
140
+ turn.untaken = (op, text)
141
+ if agent.step_observer is not None:
142
+ agent._observe_step(turn.step, index, op, before, element, text, changed, gate)
143
+ else:
144
+ agent.run_state.asked = None
145
+ agent._check_app(turn.snapshot)
146
+ if gate is not None and gate.item is not None and gate.item.act == "install":
147
+ # The App Store's own confirmation is the person's: the guard hands it off or stops (C3).
148
+ verdict = agent._guard_check("sheet_suspected", turn.history)
149
+ if verdict is not None and verdict.state == "unlocked":
150
+ turn.snapshot = agent._observe()
151
+ if changed:
152
+ turn.covered = 0
153
+ turn.failures.clear() # only failures in a row stop the run
154
+ if (not changed and op in ("TAP", "TAP_XY", "LONG_PRESS") and not turn.checked_front
155
+ and agent._front_app_differs(turn.snapshot)):
156
+ # The tree is the app's, but SpringBoard is in front: the lock screen or a system sheet.
157
+ turn.checked_front = True
158
+ verdict = agent._guard_check("lock_suspected", turn.history)
159
+ if verdict is not None and verdict.state == "unlocked":
160
+ turn.snapshot = agent._observe()
161
+ elif verdict is None:
162
+ # No guard to look: taps land on a screen the tree doesn't show, and the second time the run stops (B2).
163
+ turn.covered += 1
164
+ name = agent._app_name(turn.snapshot) or "the app"
165
+ if turn.covered >= SAME_FAILURES_TO_CHECK:
166
+ raise GuardStop(GuardVerdict("stop", "phone_locked", COVERED.format(app=name)))
167
+ turn.feedback = (f"The iPhone's lock screen, Home Screen or a system sheet is in front of {name}: "
168
+ "what is listed may not be what the screen shows. If it is the lock screen or a "
169
+ "passcode screen, finish with BLOCKED; never tap it.")
170
+ return "stop"
171
+
172
+
173
+ def chain(agent, attempt, turn):
174
+ index = turn.index
175
+ op, element, changed = attempt.op, attempt.element, attempt.changed
176
+ if attempt.commit_now and index + 1 < len(turn.chunk) and turn.chunk[index + 1].op not in ("DONE", "BLOCKED"):
177
+ # A commit ends its chain: the next action is chosen on the screen it left.
178
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index, "reason": "commit"})
179
+ return "stop"
180
+ if not changed and op in HOUSEKEEPING_OPS and [a.op for a in turn.chunk[index + 1:]] == ["DONE"]:
181
+ # Closing or scrolling had nothing to do; the DONE after it stands (mem-004).
182
+ return "next"
183
+ if (not changed and op == "TAP" and element is not None and element.editable
184
+ and index + 1 < len(turn.chunk) and turn.chunk[index + 1].op in TYPED_OPS):
185
+ # Focusing a field often shows nothing; the typing after it is the point (multi-010).
186
+ return "next"
187
+ if not changed and op not in TYPED_OPS:
188
+ if index + 1 < len(turn.chunk):
189
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
190
+ "reason": "no_change"})
191
+ return "stop" # an action with no effect: look again before going on
192
+ return "next"
@@ -0,0 +1,138 @@
1
+ """The stage that decides who asks for an action, and what follows the answer."""
2
+
3
+ from ... import contract_asks as proof_asks
4
+ from ...agent_hooks import RESUME_AFTER_SECONDS
5
+ from ..interrupts import Where, check
6
+ from ..protocol import ELEMENT_OPS
7
+ from ..safety import (
8
+ CAP_REASON,
9
+ CHANGED_WHILE_ASKING,
10
+ INJECTION_REASON,
11
+ MAX_COMMITS_PER_RUN,
12
+ PENDING_REASON,
13
+ SAME_REFUSALS_TO_STOP,
14
+ UNKNOWN_REASON,
15
+ _nobody,
16
+ binding,
17
+ declined_result,
18
+ refused_sentence,
19
+ same_binding,
20
+ )
21
+
22
+
23
+ def approval(agent, attempt, turn):
24
+ index = turn.index
25
+ op, text, unknown_repeat, gate = attempt.op, attempt.text, attempt.unknown_repeat, attempt.gate
26
+ asker, force = agent.approve, False
27
+ agent.run_state.ask_reason = None
28
+ attempt.commit_now = agent._is_commit(gate, op, attempt.element, turn.snapshot, text)
29
+ held_reason = (agent.run_state.held_items.get(gate.item.id) if gate is not None and gate.item is not None
30
+ and gate.item.status != "closed" else None)
31
+ if (held_reason is None and gate is not None and gate.item is not None and gate.item.status != "closed"
32
+ and gate.item.act in agent.run_state.held_acts and proof_asks.asks(gate.item)):
33
+ held_reason = UNKNOWN_REASON # an earlier run of this task may have done it already
34
+ extra = None
35
+ if held_reason is not None and unknown_repeat is None:
36
+ if (agent.approve or agent.forced_approve or agent.floor_approve) is None and agent.permissions is None:
37
+ if held_reason == UNKNOWN_REASON:
38
+ turn.feedback = (f"Refused: an earlier {gate.item.act} for this request has an unknown outcome: "
39
+ "it may have happened. Doing it again needs the user's OK, and nobody can "
40
+ "give it here.")
41
+ turn.history.append(f"{turn.step}.{index}: {op} {attempt.element.label[:60]!r} refused "
42
+ "(an earlier attempt's outcome is unknown)")
43
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "outcome_unknown"})
44
+ return "stop"
45
+ else:
46
+ extra = held_reason
47
+ elif (attempt.commit_now or agent._out_of_scope(op, attempt.element, turn.request)) and agent.run_state.injected:
48
+ # A screen this run tried to steer Mobster: every commit and every change no request named asks.
49
+ extra = INJECTION_REASON
50
+ elif attempt.commit_now and agent.run_state.commits >= MAX_COMMITS_PER_RUN:
51
+ extra = CAP_REASON.format(agent.run_state.commits)
52
+ if extra is not None and attempt.element is not None:
53
+ force, agent.run_state.ask_reason = True, extra
54
+ asker = asker or agent.forced_approve or agent.floor_approve or (
55
+ _nobody if agent.permissions is not None or extra in (UNKNOWN_REASON, PENDING_REASON) else None)
56
+ if turn.session is not None and attempt.element is not None and agent._long_asks(gate, op, attempt.element):
57
+ # A long run asks before every act that asks, Ask before acting off too (P1-2).
58
+ force = True
59
+ if force and attempt.element is not None:
60
+ if asker is None:
61
+ asker = agent.forced_approve
62
+ if asker is None:
63
+ turn.feedback = (f"Refused: {attempt.element.label[:60]!r} needs the user's OK, and this run can't ask for "
64
+ "it. Finish with what you found, or BLOCKED if the request needs this.")
65
+ turn.history.append(
66
+ f"{turn.step}.{index}: {op} {attempt.element.label[:60]!r} refused (needs the user's OK)")
67
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "needs_approval"})
68
+ return "stop"
69
+ if asker is None and agent.permissions is not None:
70
+ # Full access: what asks in every mode (a one-time code) still reaches the person.
71
+ asker = agent.floor_approve
72
+ if asker is None and agent._asks_mode():
73
+ # A mode that asks with nobody to answer: what would ask is refused, never done.
74
+ asker = _nobody
75
+ cards_before = agent.run_state.cards
76
+ if unknown_repeat == "ask":
77
+ # A consequential action whose first outcome is unknown asks in every mode.
78
+ asker = agent.approve or agent.forced_approve or agent.floor_approve
79
+ if asker is not None:
80
+ verdict, waited = agent._approval(gate, op, attempt.element, turn.snapshot, text, turn.chain_typed, turn.step,
81
+ asker=asker, force=force, unknown=unknown_repeat == "ask")
82
+ turn.started += waited # waiting for the user never spends the task's time
83
+ agent._waited(waited)
84
+ if verdict is None and check(agent, turn, Where.WAITED) is not None:
85
+ # Approved, but Stop came too: a stopped task never acts on an approval.
86
+ return "end"
87
+ if verdict is None and agent.run_state.cards == cards_before and op in ("TAP", "LONG_PRESS") \
88
+ and agent._asks_mode():
89
+ # The two-headed check: the pixels in the control's frame against its label.
90
+ verdict, more = agent._two_heads(asker, op, attempt.element, turn.snapshot, turn.chain_typed, turn.step)
91
+ turn.started += more
92
+ agent._waited(more)
93
+ if verdict is not None and verdict.startswith("refused:"):
94
+ turn.feedback = (f"Refused: {attempt.element.label[:60]!r} needs the user's OK, and nobody can approve it "
95
+ "here. Finish with what you found, or BLOCKED if the request needs this.")
96
+ turn.history.append(
97
+ f"{turn.step}.{index}: {op} {attempt.element.label[:60]!r} refused (needs the user's OK)")
98
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "needs_approval"})
99
+ turn.refusals[turn.feedback] = turn.refusals.get(turn.feedback, 0) + 1
100
+ if turn.refusals[turn.feedback] >= SAME_REFUSALS_TO_STOP:
101
+ turn.result = {"status": "blocked", "answer": None, "code": "stuck",
102
+ "reason": refused_sentence(turn.feedback, turn.refusals[turn.feedback], turn.actions)}
103
+ return "end"
104
+ return "stop"
105
+ if verdict is not None and verdict.startswith("redirected:"):
106
+ agent.run_state.asked = "redirected"
107
+ instruction = " ".join(verdict.split(":", 1)[1].split())[:500]
108
+ turn.request = f"{turn.request}\n\nThe user's later instruction: {instruction}"
109
+ turn.feedback = (f"The user declined {attempt.element.label[:60]!r} and asked instead: {instruction!r}. "
110
+ "Do that now; change what you typed if it needs changing.")
111
+ turn.history.append(f"{turn.step}.{index}: {op} {attempt.element.label[:60]!r} declined; user redirected")
112
+ return "stop"
113
+ if verdict is not None:
114
+ turn.result = declined_result(verdict)
115
+ return "end"
116
+ if waited >= RESUME_AFTER_SECONDS:
117
+ # The phone may have locked while the person decided.
118
+ resumed = agent._guard_check("resume", turn.history)
119
+ if resumed is not None and resumed.state == "unlocked" and attempt.element is not None:
120
+ turn.snapshot = agent._observe()
121
+ attempt.element = agent._find(
122
+ turn.snapshot, attempt.element.label, attempt.element.role, attempt.element.locator)
123
+ if attempt.element is None:
124
+ turn.feedback = "The screen changed while Mobster waited for your OK: look again."
125
+ return "stop"
126
+ if agent.run_state.cards != cards_before and attempt.element is not None and op in ELEMENT_OPS:
127
+ # The approval is bound to what the card showed: any change on a fresh read means no action now.
128
+ bound = binding(turn.snapshot, attempt.element)
129
+ fresh = agent._read_once()
130
+ found = agent._find(fresh, attempt.element.label, attempt.element.role, attempt.element.locator)
131
+ if found is None or not same_binding(bound, binding(fresh, found)):
132
+ turn.feedback = CHANGED_WHILE_ASKING
133
+ turn.history.append(f"{turn.step}.{index}: {op} {attempt.element.label[:60]!r} not done "
134
+ "(the screen changed while asking)")
135
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "changed_while_asking"})
136
+ turn.snapshot, turn.unproven = fresh, False
137
+ return "stop"
138
+ turn.snapshot, attempt.element = fresh, found
@@ -0,0 +1,115 @@
1
+ """The stages that find the control an action names and check its shape."""
2
+
3
+ import dataclasses
4
+
5
+ from ...agent_hooks import safe_allows
6
+ from ...state import ADJUSTABLE_ROLES, TEXT_OPERATIONS, pixel_read
7
+ from ..act import opens_field
8
+ from ..protocol import ELEMENT_OPS, TYPED_OPS
9
+ from ..safety import GuardStop, holds_text, revealed_actions, row_under_stroke
10
+ from ..screen import editable_in
11
+
12
+
13
+ def target(agent, attempt, turn):
14
+ index = turn.index
15
+ if attempt.op in ELEMENT_OPS:
16
+ attempt.element = agent._target(attempt.act, index, turn.aliases, turn.snapshot, turn.after)
17
+ if attempt.element is None:
18
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
19
+ "reason": "target_missing", "wanted": (attempt.act.target_label or "")[:80]})
20
+ if index == 0:
21
+ turn.feedback = f"{attempt.op} needs a target element id from the current screen."
22
+ wanted = attempt.act.target_label or attempt.act.target or attempt.act.opened_by
23
+ turn.history.append(f"{turn.step}.{index}: {attempt.op} target {wanted!r} "
24
+ + ("(the field it opened) " if attempt.act.opened_by else "")
25
+ + "not on screen; stopped the sequence")
26
+ return "stop"
27
+ if attempt.op in TYPED_OPS and opens_field(attempt.element, turn.snapshot):
28
+ # Typing into a field's prompt drawn as a button fails: it is tapped first, with every check a TAP gets.
29
+ agent._emit({"event": "frontier_repaired", "step": turn.step, "index": index, "operation": attempt.op,
30
+ "as": "TAP", "label": agent._mask(attempt.element.label)[:80]})
31
+ turn.chunk.insert(index + 1, dataclasses.replace(attempt.act, target=None, target_label=None,
32
+ opened_by=attempt.element.label))
33
+ attempt.act = dataclasses.replace(attempt.act, op="TAP", text=None)
34
+ attempt.op, attempt.text = "TAP", None
35
+
36
+
37
+ def sideways_swipe(agent, attempt, turn):
38
+ index = turn.index
39
+ act, op = attempt.act, attempt.op
40
+ if op in ("SWIPE_LEFT", "SWIPE_RIGHT"):
41
+ # A sideways swipe strokes through the row it targets (B7); untargeted over a list row it is refused.
42
+ named = act.target or act.target_label
43
+ attempt.element = agent._target(act, index, turn.aliases, turn.snapshot, turn.after) if named else None
44
+ if named and attempt.element is None:
45
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
46
+ "reason": "target_missing", "wanted": str(named)[:80]})
47
+ turn.history.append(f"{turn.step}.{index}: {op} target {named!r} not on screen; stopped the sequence")
48
+ return "stop"
49
+ if (attempt.element is not None and (attempt.element.role == "Cell" or pixel_read(turn.snapshot))
50
+ and revealed_actions(turn.snapshot, attempt.element)):
51
+ # Its actions already show: another swipe could run the app's full swipe (Mail trashes).
52
+ turn.feedback = (f"Refused: {agent._mask(attempt.element.label)[:60]!r} already shows its actions. "
53
+ "TAP the one the request needs, or leave it.")
54
+ turn.history.append(f"{turn.step}.{index}: {op} refused (its row's actions already show)")
55
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "swipe_again"})
56
+ return "stop"
57
+ crossed = row_under_stroke(turn.snapshot) if attempt.element is None else None
58
+ if crossed is not None:
59
+ turn.feedback = (f"Refused: {op} needs a target here: name the row to swipe (its id), or it "
60
+ f"would swipe whichever row is in the middle of the screen "
61
+ f"({agent._mask(crossed.label)[:60]!r}).")
62
+ turn.history.append(f"{turn.step}.{index}: {op} refused (no target row)")
63
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "untargeted_swipe"})
64
+ return "stop"
65
+
66
+
67
+ def shape(agent, attempt, turn):
68
+ index = turn.index
69
+ app = attempt.app
70
+ clearing = attempt.op == "SET_TEXT" and attempt.text == "" and attempt.element is not None and (
71
+ agent._exact_text() or callable(getattr(agent.driver, "clear_text", None)))
72
+ if (attempt.op in TYPED_OPS or attempt.op == "SCROLL_TO") and not attempt.text and not clearing:
73
+ turn.feedback = f"{attempt.op} needs text." + (
74
+ ' SET_TEXT with text "" empties a field.' if attempt.op in TYPED_OPS else "")
75
+ return "stop"
76
+ if attempt.op in ("TAP", "LONG_PRESS") and attempt.element is not None and not attempt.element.enabled:
77
+ turn.feedback = (f"Refused: {attempt.element.label[:60]!r} is disabled: it does nothing yet. "
78
+ "Do what enables it first (fill the required fields, choose an option).")
79
+ turn.history.append(f"{turn.step}.{index}: {attempt.op} {attempt.element.label[:60]!r} refused (disabled)")
80
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "disabled"})
81
+ return "stop"
82
+ if attempt.op == "LAUNCH_APP" and app not in agent.apps:
83
+ turn.feedback = "LAUNCH_APP needs one of the listed apps."
84
+ return "stop"
85
+ if attempt.op == "LAUNCH_APP" and not safe_allows(agent.guard, app):
86
+ instruction = agent._app_instruction()
87
+ if not instruction:
88
+ raise GuardStop(agent._blocked_app(app))
89
+ turn.request = f"{turn.request}\n\nThe user's later instruction: {instruction}"
90
+ turn.feedback = (f"The user didn't allow {agent.apps.get(app, app)} and asked instead: {instruction!r}. "
91
+ "Do that now.")
92
+ turn.history.append(f"{turn.step}.{index}: LAUNCH_APP {app} declined; user redirected")
93
+ return "stop"
94
+ if attempt.op in TYPED_OPS and attempt.element is not None and not attempt.element.editable \
95
+ and attempt.element.role not in ADJUSTABLE_ROLES:
96
+ # A row that holds a field is not the field.
97
+ attempt.element = editable_in(turn.snapshot, attempt.element) or attempt.element
98
+ if attempt.text and attempt.element is not None and agent._exact_text():
99
+ # write_text keeps line breaks where the field takes them and never types Return; other controls are spaces.
100
+ attempt.text = "".join(ch if ch.isprintable() or ch == "\n" else " " for ch in attempt.text)
101
+ elif attempt.text and attempt.element is not None and attempt.element.role != "TextView":
102
+ # Line breaks are text only in a text view; elsewhere Return would submit.
103
+ attempt.text = " ".join("".join(ch if ch.isprintable() else " " for ch in attempt.text).split())
104
+ if (attempt.op in TEXT_OPERATIONS and attempt.element is not None and attempt.element.editable
105
+ and holds_text(attempt.element, attempt.text)
106
+ and (attempt.op == "TYPE" or agent._appends(attempt.op, attempt.element))):
107
+ # Text a field already shows isn't typed again; a TYPE_SUBMIT that would append only presses Return.
108
+ turn.feedback = f"{attempt.element.label[:60]!r} already held that text, so it wasn't typed again."
109
+ turn.history.append(f"{turn.step}.{index}: {attempt.op} {attempt.element.label[:60]!r} not typed: "
110
+ "the field already held the text")
111
+ agent._emit({"event": "frontier_skipped", "step": turn.step, "index": index, "operation": attempt.op,
112
+ "reason": "already_typed"})
113
+ if attempt.op == "TYPE":
114
+ return "next"
115
+ attempt.op, attempt.text = "SUBMIT", None
@@ -0,0 +1,102 @@
1
+ """The stages that end or hold an action: an input that changed nothing, DONE, BLOCKED and WAIT."""
2
+
3
+ import time
4
+
5
+ from ..prompt import DONE_CHECK
6
+ from ..protocol import TYPED_OPS, WRAP_UP_SHARE
7
+
8
+ IDLE_WAIT_LIMIT = 2
9
+
10
+
11
+ def input_not_taken(agent, attempt, turn):
12
+ index = turn.index
13
+ op = attempt.op
14
+ if turn.untaken is not None and op not in TYPED_OPS:
15
+ # An input that changed nothing: what follows was planned on a value that isn't there.
16
+ turn.feedback = (f"Your {turn.untaken[0]} {agent._mask(turn.untaken[1])[:60]!r} changed nothing on screen, so "
17
+ f"{op} did not run. Check the values on screen and set them before going on.")
18
+ turn.history.append(f"{turn.step}.{index}: {op} not run: the {turn.untaken[0]} before it changed nothing")
19
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
20
+ "reason": "input_not_taken"})
21
+ return "stop"
22
+
23
+
24
+ def _said(attempt, turn):
25
+ return turn.out.get("answer") or attempt.text
26
+
27
+
28
+ def done_gate(agent, attempt, turn):
29
+ if agent._contract is None:
30
+ return None
31
+ index = turn.index
32
+ wrap = (agent.max_cost_usd is not None and turn.spent is not None
33
+ and turn.spent + turn.reserve >= WRAP_UP_SHARE * agent.max_cost_usd)
34
+ accepted, reason, answer = agent._contract.done_gate(
35
+ _said(attempt, turn), 0 if wrap else agent.max_steps - turn.calls)
36
+ if not accepted:
37
+ turn.feedback = reason
38
+ turn.history.append(f"{turn.step}.{index}: DONE refused: items not shown on screen yet")
39
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
40
+ "reason": "proof_gate", "open": [i.id for i in agent._contract.items
41
+ if i.reason and not i.unproven]})
42
+ return "stop"
43
+ agent._reveal_found(turn)
44
+ turn.result = {"status": "completed", "answer": answer}
45
+ if turn.data_json:
46
+ turn.result["data_json"] = turn.out.get("data_json")
47
+ return "end"
48
+
49
+
50
+ # No contract: a DONE right after a change, or before the request was walked item by item, is deferred once.
51
+ def done_deferred(agent, attempt, turn):
52
+ if turn.calls >= agent.max_steps - 1 or (
53
+ agent.max_cost_usd is not None and turn.spent is not None
54
+ and turn.spent >= WRAP_UP_SHARE * agent.max_cost_usd):
55
+ return None
56
+ index = turn.index
57
+ reasons = ["You finished right after changing things: check on this screen that every "
58
+ "change took effect as intended."] if turn.acted else []
59
+ if not turn.request_checked:
60
+ reasons.append(DONE_CHECK)
61
+ turn.request_checked = True
62
+ if reasons:
63
+ turn.feedback = " ".join(reasons)
64
+ turn.history.append(f"{turn.step}.{index}: DONE deferred: check the result and the request first")
65
+ agent._emit({"event": "frontier_chunk_stop", "step": turn.step, "index": index,
66
+ "reason": "verify_before_done"})
67
+ return "stop"
68
+
69
+
70
+ def finish(agent, attempt, turn):
71
+ said = _said(attempt, turn)
72
+ if attempt.op == "BLOCKED":
73
+ sentence = said or (turn.out.get("thought") or "").strip() or None
74
+ turn.result = {"status": "blocked", "answer": sentence, "reason": sentence}
75
+ else:
76
+ turn.result = {"status": "completed", "answer": said}
77
+ if turn.data_json:
78
+ turn.result["data_json"] = turn.out.get("data_json")
79
+ return "end"
80
+
81
+
82
+ def wait(agent, attempt, turn):
83
+ index = turn.index
84
+ if turn.idle_waits >= IDLE_WAIT_LIMIT:
85
+ turn.feedback = ("Refused: you have waited twice and nothing on screen changed; nothing is "
86
+ "loading. Act instead: the thing you wait for may not exist (check that "
87
+ "earlier steps were really saved), or try another route.")
88
+ turn.history.append(f"{turn.step}.{index}: WAIT refused (screen idle)")
89
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "idle_wait"})
90
+ return "stop"
91
+ turn.history.append(f"{turn.step}.{index}: WAIT")
92
+ t_wait = time.monotonic()
93
+ time.sleep(1.0)
94
+ fresh = agent._observe()
95
+ agent._spend("wait", t_wait)
96
+ turn.idle_waits = turn.idle_waits + 1 if fresh.content_fingerprint == turn.snapshot.content_fingerprint else 0
97
+ turn.snapshot, turn.unproven = fresh, False
98
+ return "stop"
99
+
100
+
101
+ def active(agent, attempt, turn):
102
+ turn.idle_waits = 0
@@ -0,0 +1,92 @@
1
+ """The stages that ask the task's contract and the signed-in profile before an action."""
2
+
3
+ from ... import contract_asks as proof_asks
4
+ from ... import contract_screen as proof_screen
5
+ from ... import harness_api
6
+ from ...agent_hooks import GuardVerdict
7
+ from ..actions import Action
8
+ from ..safety import SAME_REFUSALS_TO_STOP, GuardStop, refused_sentence
9
+ from ..screen import screen_image
10
+
11
+
12
+ def contract_gate(agent, attempt, turn):
13
+ index = turn.index
14
+ op, element = attempt.op, attempt.element
15
+ attempt.gate = None
16
+ if agent._contract is not None and element is not None:
17
+ attempt.gate = agent._contract.check(op, element, turn.snapshot, agent._app_name(turn.snapshot),
18
+ text=attempt.text, chain_typed=tuple(turn.chain_typed))
19
+ if not attempt.gate.allowed:
20
+ turn.feedback = attempt.gate.reason
21
+ turn.history.append(f"{turn.step}.{index}: {op} {element.label[:60]!r} refused (not declared)")
22
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": element.label[:120],
23
+ "reason": attempt.gate.reason[:200]})
24
+ turn.refusals[attempt.gate.reason] = turn.refusals.get(attempt.gate.reason, 0) + 1
25
+ if turn.refusals[attempt.gate.reason] >= SAME_REFUSALS_TO_STOP:
26
+ refused = turn.refusals[attempt.gate.reason]
27
+ agent._emit({"event": "frontier_stuck", "step": turn.step, "refused": refused,
28
+ "reason": attempt.gate.reason[:200]})
29
+ turn.result = {"status": "blocked", "answer": None, "code": "stuck",
30
+ "reason": refused_sentence(attempt.gate.reason, refused, turn.actions)}
31
+ return "end"
32
+ return "stop"
33
+ if (attempt.unknown_repeat == "retry" and attempt.gate is not None and attempt.gate.item is not None
34
+ and proof_asks.asks(attempt.gate.item)):
35
+ # The contract calls it a commit, so the person is asked again.
36
+ if (agent.approve or agent.forced_approve or agent.floor_approve) is None:
37
+ turn.feedback = (f"Refused: your earlier {op} {element.label[:60]!r} has an unknown outcome: it may "
38
+ "have happened. Sending it again needs the user's OK, and nobody can give it here.")
39
+ turn.history.append(f"{turn.step}.{index}: {op} {element.label[:60]!r} refused (its earlier outcome was "
40
+ "unknown)")
41
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": "outcome_unknown"})
42
+ return "stop"
43
+ attempt.unknown_repeat = "ask"
44
+
45
+
46
+ def ask_at_send(agent, attempt, turn):
47
+ index = turn.index
48
+ text, element = attempt.text, attempt.element
49
+ can_ask = agent.approve is not None or (turn.session is not None and agent.forced_approve is not None)
50
+ if (attempt.op == "TYPE_SUBMIT" and can_ask and attempt.gate is not None
51
+ and attempt.gate.item is not None and proof_asks.asks(attempt.gate.item)
52
+ and attempt.gate.verbs == frozenset({"send"})):
53
+ # One message, one approval: type now, unasked; the Return asks with the typed text in its composer.
54
+ turn.chunk.insert(index + 1, Action("SUBMIT", target_label=element.label, locator=element.locator,
55
+ role=element.role))
56
+ attempt.op, attempt.gate = "TYPE", proof_screen.Gate(True)
57
+ attempt.key = (attempt.op, element.label, text)
58
+ attempt.spot = (attempt.op, element.locator or element.label, text, turn.snapshot.content_fingerprint)
59
+ agent._emit({"event": "frontier_split", "step": turn.step, "index": index, "reason": "ask_at_send"})
60
+
61
+
62
+ def account(agent, attempt, turn):
63
+ index = turn.index
64
+ op, element, gate = attempt.op, attempt.element, attempt.gate
65
+ if agent.commit_check is not None and element is not None:
66
+ commit = (gate.item.act if gate is not None and gate.item is not None and proof_asks.asks(gate.item)
67
+ else None) if agent._contract is not None else harness_api.commit_act(
68
+ op, element.label, None, getattr(turn.snapshot, "bundle_id", None))
69
+ agent.run_state.acting_as = agent.run_state.media_checked = None
70
+ if commit:
71
+ checked = agent.commit_check(commit, turn.snapshot) or {}
72
+ if checked.get("stop"):
73
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": element.label[:120],
74
+ "reason": "account"})
75
+ raise GuardStop(GuardVerdict("stop", checked.get("code") or "account_mismatch",
76
+ checked["stop"]))
77
+ if checked.get("refuse"):
78
+ turn.feedback = checked["refuse"]
79
+ turn.history.append(f"{turn.step}.{index}: {op} {element.label[:60]!r} refused (profile not "
80
+ "confirmed)")
81
+ agent._emit({"event": "frontier_refused", "step": turn.step, "label": element.label[:120],
82
+ "reason": "account"})
83
+ return "stop"
84
+ agent.run_state.acting_as = checked.get("acting_as")
85
+ if callable(checked.get("preview")):
86
+ # A check of the screen this commit is made on (the queue's media check): no picture, or any failure,
87
+ # is no match.
88
+ image = screen_image(agent.driver, getattr(turn.snapshot, "read_started", None))
89
+ try:
90
+ agent.run_state.media_checked = bool(image is not None and checked["preview"](image))
91
+ except Exception: # noqa: BLE001
92
+ agent.run_state.media_checked = False