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,419 @@
1
+ """The screen as the agent reads it: its picture, its rows, the lists on it and its signature."""
2
+
3
+ import base64
4
+ import io
5
+ import json
6
+ import os
7
+ import re
8
+ import threading
9
+
10
+ from .. import contract_screen as proof_screen
11
+ from ..task_policy import is_keypad_key
12
+ from .protocol import TYPED_OPS
13
+
14
+ SCREENSHOT_SIDE = 448
15
+ MAX_ELEMENTS = 220
16
+ VERIFY_TIMEOUT = 10
17
+
18
+
19
+ # Never a frame older than ``since``: the picture is never older than the tree it goes with.
20
+ def video_frame(driver, still_seconds=0.0, since=None):
21
+ clock = getattr(driver, "frame_clock", None)
22
+ if clock is None:
23
+ return None
24
+ try:
25
+ still = clock.still_for() if callable(getattr(clock, "still_for", None)) else None
26
+ latest = getattr(getattr(clock, "video", None), "latest", None)
27
+ frame = latest() if callable(latest) and still is not None and still >= still_seconds else None
28
+ except Exception:
29
+ return None
30
+ if isinstance(frame, (tuple, list)) and len(frame) > 2 and frame[1] == "image/jpeg":
31
+ if since is not None and not (len(frame) > 4 and isinstance(frame[4], (int, float)) and frame[4] >= since):
32
+ return None
33
+ return frame[2]
34
+ return None
35
+
36
+
37
+ def screen_image(driver, since=None):
38
+ data = video_frame(driver, since=since)
39
+ capture = getattr(driver, "capture_preview", None)
40
+ if data is None and (not callable(capture) or getattr(driver, "ax_only_steps", 0)):
41
+ return None
42
+ try:
43
+ if data is None:
44
+ data = capture(timeout=5)
45
+ if isinstance(data, str):
46
+ data = base64.b64decode(data.split(",", 1)[1] if data.startswith("data:") else data)
47
+ from PIL import Image
48
+ return Image.open(io.BytesIO(data)).convert("RGB")
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def screenshot_part(driver, side=SCREENSHOT_SIDE, since=None, blur=()):
54
+ image = screen_image(driver, since)
55
+ if image is None:
56
+ return None
57
+ try:
58
+ image.thumbnail((side, side * 2.2))
59
+ if blur:
60
+ image = blurred(image, blur)
61
+ out = io.BytesIO()
62
+ image.save(out, "JPEG", quality=70)
63
+ except Exception:
64
+ return None
65
+ url = "data:image/jpeg;base64," + base64.b64encode(out.getvalue()).decode()
66
+ return {"type": "image_url", "image_url": {"url": url, "detail": "low"}}
67
+
68
+
69
+ POINT_CROP = (1, .18) # its whole width, so a typed line shows whole
70
+ POINT_CROP_SIDE = 360
71
+
72
+
73
+ def point_crop(driver, x, y, since=None, blur=()):
74
+ image = screen_image(driver, since)
75
+ if image is None:
76
+ return None
77
+ try:
78
+ from PIL import ImageDraw
79
+ if blur:
80
+ image = blurred(image, blur)
81
+ width, height = image.size
82
+ w, h = POINT_CROP[0] * width, POINT_CROP[1] * height
83
+ left = min(max(x * width - w / 2, 0), width - w)
84
+ top = min(max(y * height - h / 2, 0), height - h)
85
+ image = image.crop((int(left), int(top), int(left + w), int(top + h)))
86
+ scale = POINT_CROP_SIDE / max(image.size)
87
+ image = image.resize((max(1, round(image.width * scale)), max(1, round(image.height * scale))))
88
+ cx, cy = (x * width - left) * scale, (y * height - top) * scale
89
+ ring = max(10, round(POINT_CROP_SIDE * .045))
90
+ draw = ImageDraw.Draw(image)
91
+ draw.ellipse((cx - ring - 2, cy - ring - 2, cx + ring + 2, cy + ring + 2), outline=(255, 255, 255), width=2)
92
+ draw.ellipse((cx - ring, cy - ring, cx + ring, cy + ring), outline=(255, 92, 72), width=3)
93
+ out = io.BytesIO()
94
+ image.save(out, "JPEG", quality=80)
95
+ return out.getvalue()
96
+ except Exception:
97
+ return None
98
+
99
+
100
+ def blurred(image, rects):
101
+ from PIL import ImageStat
102
+ width, height = image.size
103
+ for x, y, w, h in rects:
104
+ box = (max(0, int((x - .01) * width)), max(0, int((y - .005) * height)),
105
+ min(width, int((x + w + .01) * width) + 1), min(height, int((y + h + .005) * height) + 1))
106
+ if box[2] <= box[0] or box[3] <= box[1]:
107
+ continue
108
+ mean = tuple(int(v) for v in ImageStat.Stat(image.crop(box)).mean[:3])
109
+ image.paste(mean, box)
110
+ return image
111
+
112
+
113
+ # The driver takes one request at a time: nothing else uses it until result() joins this read.
114
+ class _Verify:
115
+ def __init__(self, driver):
116
+ self.snapshot = None
117
+ self._thread = threading.Thread(target=self._read, args=(driver,), name="frontier-verify", daemon=True)
118
+ self._thread.start()
119
+
120
+ def _read(self, driver):
121
+ try:
122
+ self.snapshot = driver.observe(timeout=VERIFY_TIMEOUT)
123
+ except Exception:
124
+ pass
125
+
126
+ def result(self):
127
+ self._thread.join()
128
+ return self.snapshot
129
+
130
+
131
+ # An app's own keypad is listed: left out as keyboard keys, Calculator's keys could not be tapped (B5).
132
+ def lean_skip(elements, keyboard=None):
133
+ app_keys = keyboard is not None and keyboard != "visible" and any(
134
+ element.role == "Key" and is_keypad_key("TAP", "Key", element.label) for element in elements)
135
+ return set() if app_keys else {index for index, element in enumerate(elements) if element.role == "Key"}
136
+
137
+
138
+ KEYBOARD_ROW = "(keyboard up: its keys are not listed; TYPE and SET_TEXT type, SUBMIT presses Return)"
139
+
140
+
141
+ def context_on(name):
142
+ return os.environ.get(f"MOBSTER_CTX_{name}", "off").strip().casefold() == "on"
143
+
144
+
145
+ FIELD_PROMPT = re.compile(r"^(search|ask|type|write|enter|where to)\b|\bsearch\b", re.I)
146
+ UNDER_BAR_MARK = "(hidden under the bottom bar)"
147
+ BAR_ROLES = frozenset({"TabBar", "Toolbar", "NavigationBar"})
148
+
149
+
150
+ def looks_like_field(element):
151
+ words = (element.label or "").split()
152
+ return (element.role == "Button" and not element.editable and 2 <= len(words) <= 8
153
+ and bool(FIELD_PROMPT.search(" ".join(words))))
154
+
155
+
156
+ def _bars(elements, width, height):
157
+ from ..state import chrome_bands
158
+ nodes = [(f"/{index}", e.role, e.label, e.value,
159
+ (e.rect[0] * width, e.rect[1] * height,
160
+ (e.rect[0] + e.rect[2]) * width, (e.rect[1] + e.rect[3]) * height),
161
+ e.enabled) for index, e in enumerate(elements)]
162
+ return nodes, (chrome_bands(nodes, width, height) if width >= 100 else ()) # bars are found in points
163
+
164
+
165
+ # A row listed before a bottom bar is drawn behind it, and taps on it do nothing (multi-087).
166
+ def under_bars(elements, width, height):
167
+ from ..state import visible_span
168
+ nodes, bands = _bars(elements, width, height)
169
+ return {elements[index].id for index, node in enumerate(nodes)
170
+ if bands and elements[index].role not in BAR_ROLES
171
+ and visible_span(node[0], index, *node[4], bands) is None}
172
+
173
+
174
+ def chrome_ids(elements, width, height):
175
+ nodes, bands = _bars(elements, width, height)
176
+ owners = {int(path[1:]) for band in bands for path in band[0] if path[1:].isdigit()}
177
+ frames = [e.rect for e in elements if e.role in BAR_ROLES] + [elements[i].rect for i in owners]
178
+ out = {elements[i].id for i in owners}
179
+ for e in elements:
180
+ cx, cy = e.rect[0] + e.rect[2] / 2, e.rect[1] + e.rect[3] / 2
181
+ if e.role in BAR_ROLES or any(x <= cx <= x + w and y <= cy <= y + h for x, y, w, h in frames):
182
+ out.add(e.id)
183
+ return out
184
+
185
+
186
+ WORDY = re.compile(r"[^\W\d_]{2}")
187
+
188
+
189
+ def content_rows(elements, width, height):
190
+ chrome = chrome_ids(elements, width, height)
191
+ out = {}
192
+ for e in elements:
193
+ label = " ".join((e.label or "").split())
194
+ if e.id not in chrome and not e.editable and WORDY.search(label):
195
+ out[label] = out.get(label, 0) + 1
196
+ return out
197
+
198
+
199
+ LIST_MIN_ROWS, LIST_MIN_WIDTH = 3, .4
200
+ LIST_ROLES = frozenset({"Cell", "Button", "StaticText", "Link", "Other"})
201
+
202
+
203
+ def list_rows(elements, width, height):
204
+ rows = content_rows(elements, width, height)
205
+ groups, place = {}, {}
206
+ for e in elements:
207
+ label = " ".join((e.label or "").split())
208
+ if label in rows and e.role in LIST_ROLES:
209
+ place.setdefault(label, e.rect[1])
210
+ if e.rect[2] >= LIST_MIN_WIDTH:
211
+ shape = (e.role, round(e.rect[0] * 50), round(e.rect[2] * 50))
212
+ groups.setdefault(shape, {}).setdefault(label, e.rect[1])
213
+ stacked = [group for group in groups.values() if len({round(y * 100) for y in group.values()}) >= LIST_MIN_ROWS]
214
+ if not stacked:
215
+ return []
216
+ top, bottom = min(min(g.values()) for g in stacked), max(max(g.values()) for g in stacked)
217
+ return [label for label, y in sorted(place.items(), key=lambda kv: kv[1]) if top <= y <= bottom]
218
+
219
+
220
+ def list_name(snapshot, width, height):
221
+ title = proof_screen.screen_title(snapshot)
222
+ chrome = chrome_ids(snapshot.elements, width, height)
223
+ chosen = next((" ".join(e.label.split()) for e in snapshot.elements if e.selected and e.id not in chrome
224
+ and e.role == "Button" and (e.label or "").strip() and e.label.strip() != title), None)
225
+ return f"{title} › {chosen}" if title and chosen else title
226
+
227
+
228
+ def view_of(app, snapshot):
229
+ return app or "", proof_screen.screen_title(snapshot)
230
+
231
+
232
+ def names_act(op, element, text=None):
233
+ if op in ("TAP", "LONG_PRESS", "TAP_XY"):
234
+ return bool(proof_screen.ACT_LABEL.search((element.label if element is not None else text) or ""))
235
+ if op in TYPED_OPS or op == "SUBMIT":
236
+ return element is None or not proof_screen.is_search_field(element)
237
+ return False
238
+
239
+
240
+ _DONE = (r"(added|saved|sent|booked|confirmed|created|updated|deleted|removed|completed|scheduled|posted|submitted|"
241
+ r"reserved|paid|placed)")
242
+ STATUS = re.compile(rf"\bsuccess(ful|fully)?\b|^{_DONE}( (to|in|for|on)\b.*)?[.!]?$|^\w+ {_DONE}[.!]?$", re.I)
243
+ PROVENANCE_MARK = "(on screen since this page opened, before any action of yours here)"
244
+
245
+
246
+ # A page can open showing a status the agent never wrote ("Added successfully", multi-069).
247
+ class Provenance:
248
+ def __init__(self):
249
+ self.view, self.texts, self.with_page = None, frozenset(), frozenset()
250
+
251
+ def observe(self, view, elements, acted):
252
+ texts = frozenset(" ".join((e.label or "").split()) for e in elements if e.label)
253
+ self.with_page = (frozenset() if acted else texts - self.texts) if view != self.view else self.with_page & texts
254
+ self.view, self.texts = view, texts
255
+
256
+ def marks(self, elements):
257
+ out = {}
258
+ for e in elements:
259
+ label = " ".join((e.label or "").split())
260
+ if (e.role in ("StaticText", "Other") and label in self.with_page and 2 <= len(label.split()) <= 6
261
+ and STATUS.search(label)):
262
+ out[e.id] = PROVENANCE_MARK
263
+ return out
264
+
265
+
266
+ READBACK_ROWS = 4
267
+
268
+
269
+ # An app can leave the list a commit changed; reading it back stops a second add (caltrack-001).
270
+ def readback(title, before, after):
271
+ changes = []
272
+ for label in [*after, *(label for label in before if label not in after)]:
273
+ now, was = after.get(label, 0), before.get(label, 0)
274
+ if now != was:
275
+ changes.append(f"{json.dumps(label[:60], ensure_ascii=False)} {now} row{'' if now == 1 else 's'} "
276
+ f"({was} before)")
277
+ if not changes or len(changes) > READBACK_ROWS:
278
+ return "" # a scroll changes every row: no write to read back
279
+ return f"read back from {title}: " + "; ".join(changes)
280
+
281
+
282
+ KEPT_ROWS, KEPT_CHARS, KEPT_LISTS = 5, 30, 3
283
+ IDENTIFIER = re.compile(r"^\S*_\S*(?:\s+\d+)?$|^[a-z][a-z0-9]*(?:\.[a-z0-9]+)+$") # mail_row_x 1, gearshape.fill
284
+
285
+
286
+ def kept_list(title, rows):
287
+ rows = [row for row in dict.fromkeys(" ".join(str(row).split()) for row in rows)
288
+ if row != title and len(row.split()) >= 2 and not IDENTIFIER.match(row)]
289
+ cut = [row[:KEPT_CHARS] + ("…" if len(row) > KEPT_CHARS else "") for row in rows[:KEPT_ROWS]]
290
+ more = f"; {len(rows) - KEPT_ROWS} more" if len(rows) > KEPT_ROWS else ""
291
+ return f"{title or 'a list'}: " + "; ".join(cut) + more if cut else ""
292
+
293
+
294
+ def text_status(receipt):
295
+ if receipt.get("value") is None:
296
+ return "not read back"
297
+ chars = f"{len(receipt['value']):,} chars"
298
+ if receipt.get("matches"):
299
+ return chars + (", matches what you wrote (line breaks became spaces: one-line field)"
300
+ if receipt.get("flattened") else ", matches what you wrote")
301
+ return chars + ", DIFFERS from what you wrote: " + text_difference(receipt["value"], receipt.get("expected") or "")
302
+
303
+
304
+ def text_difference(value, expected):
305
+ index = next((i for i, (a, b) in enumerate(zip(value, expected)) if a != b), min(len(value), len(expected)))
306
+ return (f"from character {index + 1} it reads {value[index:index + 40]!r} where you wrote "
307
+ f"{expected[index:index + 40]!r} ({len(value)} vs {len(expected)} characters)")
308
+
309
+
310
+ def receipt_for(receipts, element):
311
+ receipt = (receipts or {}).get(element.locator)
312
+ written = receipt.get("value") if receipt else None
313
+ if written is None:
314
+ return None
315
+ from ..state import truncated_field_value
316
+ shown = element.text if element.placeholder else (element.value or "")
317
+ same = reads_back(shown, written) if truncated_field_value(shown) else shown == written
318
+ return receipt if same else None
319
+
320
+
321
+ # WDA reads a long value as its head and tail around " … ": both must be the text's.
322
+ def reads_back(shown, text):
323
+ from ..state import FIELD_HEAD, FIELD_TAIL, truncated_field_value
324
+ if truncated_field_value(shown):
325
+ return (len(text) > FIELD_HEAD + FIELD_TAIL and text.startswith(shown[:FIELD_HEAD])
326
+ and text.endswith(shown[-FIELD_TAIL:]))
327
+ return _plain(shown) == _plain(text)
328
+
329
+
330
+ QUOTES = str.maketrans({"‘": "'", "’": "'", "‚": "'", "‛": "'", "“": '"', "”": '"', "„": '"', "‟": '"'})
331
+
332
+
333
+ def _plain(text):
334
+ return " ".join(text.casefold().translate(QUOTES).split())
335
+
336
+
337
+ def screen_rows(snapshot, receipts=None, marks=None):
338
+ from ..state import redundant_ids
339
+ skip = redundant_ids(snapshot.public().get("elements") or ())
340
+ shown = [e for e in snapshot.elements if e.id not in skip]
341
+ lean = lean_skip(shown, getattr(snapshot, "keyboard", None))
342
+ marking = context_on("MARKS")
343
+ under = under_bars(shown, snapshot.width, snapshot.height) if marking else frozenset()
344
+ aliases, lines = {}, []
345
+ for element in [e for index, e in enumerate(shown) if index not in lean][:MAX_ELEMENTS]:
346
+ alias = f"e{len(aliases) + 1}"
347
+ aliases[alias] = element
348
+ x, y, w, h = (round(v * 100) for v in element.rect)
349
+ value = (element.text if element.placeholder else element.value or "").strip()
350
+ if element.role in ("Switch", "Toggle") and value in ("0", "1"):
351
+ value = "on" if value == "1" else "off"
352
+ parts = [alias, element.role, json.dumps((element.label or "")[:160], ensure_ascii=False)
353
+ if element.label else "(no label: find it by position in the screenshot)"]
354
+ receipt = receipt_for(receipts, element) if element.editable else None
355
+ if receipt is not None and len(value) > 160:
356
+ # A read-back field shows its status, not a cut window: a cut value got a note retyped 9 times (mem-030).
357
+ parts.append("value=" + json.dumps(value[:80] + " …", ensure_ascii=False))
358
+ elif value and value != element.label:
359
+ if element.editable and len(value) > 400:
360
+ # Typing lands at a long field's end, so its end is shown (clouddocs-004).
361
+ value = value[:120] + " … " + value[-280:]
362
+ else:
363
+ value = value[:400]
364
+ parts.append("value=" + json.dumps(value, ensure_ascii=False))
365
+ elif element.editable and element.placeholder and not value:
366
+ parts.append("empty (placeholder " + json.dumps(element.placeholder[:60], ensure_ascii=False) + ")")
367
+ if receipt is not None:
368
+ parts.append("(" + text_status(receipt) + ")")
369
+ if not element.enabled:
370
+ parts.append("disabled")
371
+ if element.selected:
372
+ parts.append("selected")
373
+ if element.editable:
374
+ parts.append("editable")
375
+ elif marking and looks_like_field(element):
376
+ parts.append("(not editable)")
377
+ if element.id in under:
378
+ parts.append(UNDER_BAR_MARK)
379
+ if marks and element.id in marks:
380
+ parts.append(marks[element.id])
381
+ parts.append(f"@{x},{y},{w},{h}")
382
+ lines.append(" ".join(parts))
383
+ if any(shown[index].role == "Key" for index in lean):
384
+ lines.append(KEYBOARD_ROW)
385
+ return aliases, lines
386
+
387
+
388
+ def screen_shape_of(snapshot):
389
+ from ..state import screen_shape, signature
390
+ try:
391
+ return screen_shape(signature(snapshot))
392
+ except Exception:
393
+ return []
394
+
395
+
396
+ def screen_pairs(snapshot):
397
+ from ..state import signature
398
+ try:
399
+ return frozenset(tuple(pair) for pair in signature(snapshot))
400
+ except Exception:
401
+ return None
402
+
403
+
404
+ def screen_signature(snapshot):
405
+ from ..state import signature, signature_hash
406
+ try:
407
+ pairs = signature(snapshot)
408
+ except Exception:
409
+ return ""
410
+ return signature_hash(pairs)
411
+
412
+
413
+ def editable_in(snapshot, element):
414
+ x, y, w, h = element.rect
415
+ inside = [e for e in snapshot.elements if e.editable and x <= e.center[0] <= x + w and y <= e.center[1] <= y + h]
416
+ if len(inside) == 1:
417
+ return inside[0]
418
+ fields = [e for e in snapshot.elements if e.editable]
419
+ return fields[0] if len(fields) == 1 else None
@@ -0,0 +1,147 @@
1
+ """Skills: operations code performs for the model."""
2
+
3
+ import re
4
+ import time
5
+
6
+ from .. import harness_api
7
+ from ..agent_hooks import RESUME_AFTER_SECONDS, SKILL_IMAGES, SkillContext
8
+ from ..state import Snapshot
9
+ from .protocol import OPERATIONS
10
+ from .safety import GuardStop, declined_result
11
+
12
+ SKILL_ENDINGS = frozenset({"approval_timeout", "stopped"})
13
+ SKILL_SCREENS = 40
14
+
15
+
16
+ def default_skills():
17
+ load = harness_api.hooks().skills
18
+ try:
19
+ return tuple(load())
20
+ except Exception:
21
+ return ()
22
+
23
+
24
+ def usable_skills(skills):
25
+ out, taken = [], set(OPERATIONS)
26
+ for skill in skills or ():
27
+ op = getattr(skill, "op", None)
28
+ if not isinstance(op, str) or not re.fullmatch(r"[A-Z][A-Z_]{1,30}", op) or op in taken:
29
+ continue
30
+ if not callable(getattr(skill, "prepare", None)) or not callable(getattr(skill, "perform", None)):
31
+ continue
32
+ taken.add(op)
33
+ out.append(skill)
34
+ return tuple(out)
35
+
36
+
37
+ class SkillsMixin:
38
+ def _run_skill(self, skill, act, index, aliases, snapshot, request, step, history, after):
39
+ op = skill.op
40
+ element = None
41
+ if getattr(skill, "needs_target", False):
42
+ element = self._target(act, index, aliases, snapshot, after)
43
+ if element is None:
44
+ history.append(f"{step}.{index}: {op} target {act.target_label or act.target!r} "
45
+ "not on screen; stopped the sequence")
46
+ return None, f"{op} needs a target element id from the current screen.", snapshot, False, 0.0
47
+ # An account's task gives skills no approver: its commit steps come back here, where the account is checked.
48
+ context = SkillContext(driver=self.driver, request=request, app_bundle=getattr(snapshot, "bundle_id", None),
49
+ emit=self._emit, guard=self.guard,
50
+ approve=self.approve if self.commit_check is None else None, clarify=self.clarify,
51
+ run=self.run_context)
52
+ call = {**act.reply(), "element": element}
53
+ self._emit({"event": "skill_started", "op": op})
54
+ try:
55
+ prepared = skill.prepare(context, call, snapshot)
56
+ except Exception as error:
57
+ # The error's text is never shown: it may hold what the skill read.
58
+ self._emit({"event": "skill_finished", "op": op, "ok": False})
59
+ history.append(f"{step}.{index}: {op} failed")
60
+ return None, f"{op} could not be done ({type(error).__name__}).", self._observe(), False, 0.0
61
+ title = self._mask(getattr(prepared, "title", None))
62
+ waited = 0.0
63
+ asker = self.approve
64
+ if title and asker is None and getattr(skill, "asks_in_every_mode", False):
65
+ # A skill whose question the person always wants to see asks through the floor approver in Full access.
66
+ asker = self.floor_approve
67
+ if title and asker is None:
68
+ # A skill that asks never runs unasked: with no way to ask, it is refused.
69
+ self._emit({"event": "skill_finished", "op": op, "ok": False})
70
+ history.append(f"{step}.{index}: {op} refused (needs the user's OK)")
71
+ return (None, f"Refused: {op} needs the user's OK, and this run cannot ask for it. Do the task another "
72
+ "way, or finish and say what is left.", snapshot, False, 0.0)
73
+ if title:
74
+ target = element.rect if element is not None else None
75
+ request_ = {"kind": op.lower(), "step": step, "operation": op,
76
+ "label": ((element.label if element is not None else title.split("?")[0]) or op)[:200],
77
+ "role": element.role if element is not None else "",
78
+ "text": self._mask(getattr(prepared, "detail", None) or None) or None, "title": title[:160],
79
+ "act": op.lower(),
80
+ "app_name": getattr(prepared, "app_name", None) or self._app_name(snapshot) or None,
81
+ "target": ({"x": round(target[0], 4), "y": round(target[1], 4), "w": round(target[2], 4),
82
+ "h": round(target[3], 4)} if target else None)}
83
+ if getattr(prepared, "remember", None):
84
+ request_["remember"] = prepared.remember
85
+ waited = time.monotonic()
86
+ answer = asker(request_)
87
+ waited = time.monotonic() - waited
88
+ self._emit({"event": "frontier_approval", "step": step, "act": op.lower(),
89
+ "answer": answer.split(":", 1)[0] if isinstance(answer, str) else None})
90
+ self.run_state.asked = "approved" if answer == "approved" else (
91
+ "redirected" if isinstance(answer, str) and answer.startswith("redirected:") else None)
92
+ if answer == "approved" and self._stopping():
93
+ answer = "stopped" # approved, but Stop came too: a stopped task never acts on an approval
94
+ if answer != "approved":
95
+ self._emit({"event": "skill_finished", "op": op, "ok": False})
96
+ if isinstance(answer, str) and answer.startswith("redirected:"):
97
+ return ({"redirect": " ".join(answer.split(":", 1)[1].split())[:500]}, None, snapshot, False,
98
+ waited)
99
+ return (declined_result(answer), None, snapshot, False, waited)
100
+ if waited >= RESUME_AFTER_SECONDS:
101
+ verdict = self._guard_check("resume", history)
102
+ if verdict is not None and verdict.state == "unlocked":
103
+ snapshot = self._observe()
104
+ try:
105
+ outcome = skill.perform(context, prepared, call, snapshot)
106
+ except Exception as error:
107
+ self._emit({"event": "skill_finished", "op": op, "ok": False})
108
+ history.append(f"{step}.{index}: {op} failed")
109
+ return None, f"{op} could not be done ({type(error).__name__}).", self._observe(), False, waited
110
+ self.run_state.secrets.update(str(secret) for secret in (getattr(outcome, "secrets", ()) or ()) if secret)
111
+ for rect in getattr(outcome, "secret_rects", ()) or ():
112
+ if isinstance(rect, (tuple, list)) and len(rect) == 4:
113
+ self.run_state.secret_rects.append(
114
+ (tuple(float(v) for v in rect), getattr(snapshot, "bundle_id", None)))
115
+ stop = getattr(outcome, "stop", None)
116
+ # Time the skill waited for the person never spends the task's time.
117
+ asked_for = getattr(outcome, "waited", 0.0)
118
+ asked_for = float(asked_for) if isinstance(asked_for, (int, float)) and asked_for > 0 else 0.0
119
+ waited += asked_for
120
+ ending = getattr(outcome, "status", None)
121
+ ending = ending if ending in SKILL_ENDINGS else None
122
+ self._emit({"event": "skill_finished", "op": op, "ok": stop is None and ending is None})
123
+ if stop is not None:
124
+ raise GuardStop(stop)
125
+ if self._contract is not None:
126
+ for seen in tuple(getattr(outcome, "screens", ()) or ())[-SKILL_SCREENS:]:
127
+ if isinstance(seen, Snapshot):
128
+ self._contract.observe(self._masked(seen), self._app_name(seen), step)
129
+ feedback = self._mask(getattr(outcome, "feedback", "") or "")
130
+ changed = bool(getattr(outcome, "changed", False))
131
+ self.run_state.skill_images = [(mime, bytes(data)) for mime, data in (getattr(outcome, "images", ()) or ())
132
+ if mime in ("image/jpeg", "image/png") and isinstance(data, (bytes, bytearray)) and data
133
+ ][:SKILL_IMAGES]
134
+ history.append(f"{step}.{index}: {op} -> {feedback[:160]}")
135
+ if ending is not None:
136
+ return {"status": ending, "answer": None, "reason": feedback}, None, snapshot, False, waited
137
+ if asked_for >= RESUME_AFTER_SECONDS:
138
+ # The phone may have locked while the person answered.
139
+ self._guard_check("resume", history)
140
+ fresh = self._observe()
141
+ self.last_screen = fresh
142
+ self._emit({"event": "frontier_action", "step": step, "index": index, "operation": op,
143
+ "target_label": element.label[:120] if element is not None and element.label else None,
144
+ "changed": changed, "text": None, "field": None, "app_name": self._app_name(snapshot) or None,
145
+ "refresh_ms": 0, "exec_ms": 0, "settle_ms": 0})
146
+ self._observe_step(step, index, op, snapshot, element, None, changed, None)
147
+ return None, feedback, fresh, changed, waited
@@ -0,0 +1 @@
1
+ """The stages of one action's trip through the loop, grouped by what they decide."""