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,316 @@
1
+ """The deterministic front door: a request that asks only for what a pack reader shows, or for a Settings page, is
2
+ answered by code before any model call. It never acts: a request that holds an act word is never routed."""
3
+
4
+ import os
5
+ import re
6
+ import time
7
+ from dataclasses import dataclass, replace
8
+
9
+ from .. import harness_api
10
+ from . import core, health, pins, settings, tools
11
+
12
+ ORDER = 2 # after harness and long-form pick-up (0, 1), before the packs' own run fields (3)
13
+ MAX_GOAL = 200
14
+ MAX_N = 40
15
+ # Run fields a routed run may carry: a conversation's id, who asked, the permission mode. Anything else (a workflow's
16
+ # input, a destination, an account, a trigger, attachments, a replay mode) is a run the table wasn't written for.
17
+ SAFE_EXTRAS = frozenset({"threadId", "via", "permissionMode"})
18
+ # An app's name that is also what a request asks for ("messages"): the app is named by an alias, or started in.
19
+ NOUNS = frozenset({"messages"})
20
+ ALIASES = {"ig": "instagram", "insta": "instagram", "twitter": "x", "imessage": "messages",
21
+ "imessages": "messages"}
22
+
23
+ # Words a request may hold besides an ask's own: politeness, question frames, determiners, "latest".
24
+ STOP = frozenset("""a an the my of on in for to at up me please show tell give get read list check what whats what's
25
+ is are do does i have how much many now currently right latest last newest recent recently most top first this that
26
+ app""".split())
27
+ # Any of these in a request, and nothing is routed. Wider than the table needs: a word here only costs a model call.
28
+ ACTS = frozenset("""send sending sent post posting repost share buy purchase pay order book delete remove erase reset
29
+ like unlike follow unfollow block report mute restrict reply respond comment subscribe accept decline approve confirm
30
+ turn toggle enable disable change set update install uninstall clear add create write type call dial save unsend
31
+ submit cancel forward archive move rename edit join leave sign log unlock restart shut allow deny schedule invite
32
+ tag pin hide unhide""".split())
33
+ OPEN_VERBS = frozenset({"open", "go", "launch", "navigate", "take"})
34
+ OPEN_FILLER = frozenset({"to", "the", "my", "me", "app", "please", "up", "in", "and"})
35
+ SEPARATORS = re.compile(r"\s*(?:›|»|→|->|=>|>|/)\s*")
36
+ POLITE = re.compile(r"^(?:(?:hey|hi|ok|okay)[ ,]+)?(?:please[ ,]+)?"
37
+ r"(?:(?:can|could|would|will) you[ ,]+(?:please[ ,]+)?)?"
38
+ r"|[ ,.]*please[ .!?]*$", re.I)
39
+
40
+
41
+ def phrases(*sets):
42
+ return tuple(frozenset(s.split()) for s in sets)
43
+
44
+
45
+ def with_phone(*sets):
46
+ """Each set as is, and with the phone named ("what iOS version is this iPhone on")."""
47
+ out = []
48
+ for base in sets:
49
+ out.append(base)
50
+ out += [f"{base} {referent}" for referent in ("phone", "iphone", "device")]
51
+ return phrases(*out)
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class Ask:
56
+ pack: str # the pack's key
57
+ reader: str # one of the pack's readers
58
+ words: tuple # the content words the request may hold, as frozensets: one of these exactly
59
+ pick: tuple = () # (column, regex): answer only with the rows whose column matches
60
+ counted: bool = False # "my last 10 DMs": a count is part of the ask
61
+ needs_app: bool = True # the app must be named, started in, or in front (False: Settings' own words)
62
+
63
+
64
+ ASKS = (
65
+ Ask("settings", "about", with_phone("ios version", "software version", "version ios"),
66
+ pick=("setting", r"^iOS Version$"), needs_app=False),
67
+ Ask("settings", "about", with_phone("model", "model name"), pick=("setting", r"^Model Name$"), needs_app=False),
68
+ Ask("settings", "storage", with_phone("storage", "free storage", "storage left", "storage space", "space left",
69
+ "free space", "available storage", "storage available",
70
+ "storage remaining", "free storage space"),
71
+ pick=("item", r"^(Used|Capacity|Free)$"), needs_app=False),
72
+ Ask("settings", "battery", with_phone("battery", "battery level", "battery percentage", "battery percent"),
73
+ pick=("setting", r"^Level$"), needs_app=False),
74
+ Ask("settings", "battery", with_phone("battery health"), pick=("setting", r"^Battery Health$"), needs_app=False),
75
+ Ask("instagram", "dms", phrases("dms", "dm", "direct messages", "messages"), counted=True),
76
+ Ask("tiktok", "dms", phrases("dms", "dm", "inbox", "messages"), counted=True),
77
+ Ask("x", "messages", phrases("dms", "dm", "messages"), counted=True),
78
+ Ask("messages", "inbox", phrases("conversations", "messages", "texts", "chats"), counted=True),
79
+ Ask("messages", "unread", phrases("unread messages", "unread texts", "unread conversations", "unread"),
80
+ counted=True),
81
+ Ask("calendar", "events", phrases("events", "upcoming events", "upcoming"), counted=True),
82
+ )
83
+
84
+ # Settings pages a request may open, as Settings names them, and the one parent a nested path must name. A page
85
+ # nobody listed here (and a path nobody checked) goes to the model.
86
+ SETTINGS_PAGES = {name.casefold(): name for name in settings.PAGES}
87
+ PARENTS = {"about": "General", "iphone storage": "General", "software update": "General"}
88
+ OWN_ROUTES = {"About": "about", "iPhone Storage": "storage", "Battery": "battery"}
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class Plan:
93
+ pack: core.Pack
94
+ kind: str # "read" or "open"
95
+ what: str # the reader or the route
96
+ n: int = 10
97
+ arg: str = None
98
+ ask: Ask = None
99
+ page: str = None # an open's page, as Settings names it
100
+
101
+
102
+ def tokens(text):
103
+ return re.findall(r"[a-z0-9&']+", str(text or "").casefold().replace("’", "'"))
104
+
105
+
106
+ def packs_by_word():
107
+ """{word: pack} for every pack's name and key and the aliases people use."""
108
+ out = {}
109
+ for pack in registered():
110
+ for word in {pack.key, pack.name.casefold()} - NOUNS:
111
+ out[word] = pack
112
+ for alias, key in ALIASES.items():
113
+ pack = next((p for p in registered() if p.key == key), None)
114
+ if pack is not None:
115
+ out[alias] = pack
116
+ return out
117
+
118
+
119
+ def registered():
120
+ from . import register
121
+ return register.all_packs()
122
+
123
+
124
+ def reads_an_act(goal, words):
125
+ """Whether the request holds an act: an act word, or what the safety gate reads as one."""
126
+ from .. import harness_api as api
127
+ return (any(word in ACTS for word in words) or bool(core.NEVER.search(goal))
128
+ or api.commit_act("", goal, None, None) is not None)
129
+
130
+
131
+ def parse(goal, *, app_bundle=None, front_bundle=None):
132
+ """The ``Plan`` for a request that is exactly an ask in the table or an open of a Settings page, else None."""
133
+ text = POLITE.sub("", str(goal or "").strip())
134
+ if not text or len(text) > MAX_GOAL or "\n" in text:
135
+ return None
136
+ words = tokens(text)
137
+ if not words or reads_an_act(text, words):
138
+ return None
139
+ if words[0] in OPEN_VERBS:
140
+ return parse_open(text)
141
+ return parse_read(words, app_bundle, front_bundle)
142
+
143
+
144
+ def parse_read(words, app_bundle, front_bundle):
145
+ by_word = packs_by_word()
146
+ named = {by_word[w].key: by_word[w] for w in words if w in by_word}
147
+ if len(named) > 1:
148
+ return None # two apps in one request: the model decides what is meant
149
+ numbers = [w for w in words if w.isdigit()]
150
+ if len(numbers) > 1 or (numbers and not 1 <= int(numbers[0]) <= MAX_N):
151
+ return None
152
+ content = frozenset(w for w in words if w not in STOP and not w.isdigit() and w not in by_word)
153
+ matches = []
154
+ for ask in ASKS:
155
+ pack = next((p for p in registered() if p.key == ask.pack), None)
156
+ if pack is None or ask.reader not in pack.readers or (named and ask.pack not in named):
157
+ continue
158
+ if content not in ask.words or (numbers and not ask.counted):
159
+ continue
160
+ if ask.needs_app and not (ask.pack in named or pack.bundle in (app_bundle, front_bundle)):
161
+ continue
162
+ matches.append((pack, ask))
163
+ if len(matches) != 1:
164
+ return None
165
+ pack, ask = matches[0]
166
+ n = int(numbers[0]) if numbers else pack.read_default.get(ask.reader, 10)
167
+ return Plan(pack, "read", ask.reader, n, None, ask)
168
+
169
+
170
+ def parse_open(text):
171
+ """"open Settings", "go to Settings › General › About", "open settings > battery": Settings' own pages."""
172
+ parts = [p for p in SEPARATORS.split(text) if p.strip()]
173
+ first = [w for w in tokens(parts[0]) if w not in OPEN_VERBS and w not in OPEN_FILLER]
174
+ rest = [" ".join(tokens(p)) for p in parts[1:]]
175
+ if len(parts) == 1 and first and first[0] == "settings":
176
+ rest, first = [" ".join(first[1:])] if len(first) > 1 else [], ["settings"]
177
+ if first != ["settings"] or len(rest) > 2:
178
+ return None
179
+ pack = next((p for p in registered() if p.key == "settings"), None)
180
+ if pack is None:
181
+ return None
182
+ if not rest:
183
+ return Plan(pack, "open", "root")
184
+ pages = [SETTINGS_PAGES.get(name) for name in rest]
185
+ if None in pages:
186
+ return None
187
+ page = pages[-1]
188
+ if len(pages) == 2 and PARENTS.get(page.casefold()) != pages[0]:
189
+ return None # a path nobody checked: not claimed
190
+ route = OWN_ROUTES.get(page, "find")
191
+ return Plan(pack, "open", route, arg=page if route == "find" else None, page=page)
192
+
193
+
194
+ def enabled():
195
+ return os.environ.get("MOBSTER_FRONT_DOOR", "").strip().lower() not in ("off", "0", "false", "no")
196
+
197
+
198
+ def eligible(ctx):
199
+ if not enabled() or getattr(ctx, "engine", None) != "smart" or not getattr(ctx, "goal", None):
200
+ return False
201
+ if set(ctx.extras or {}) - SAFE_EXTRAS:
202
+ return False
203
+ run = (getattr(getattr(ctx, "runtime", None), "runs", None) or {}).get(ctx.run_id)
204
+ if run is not None and (getattr(run, "output_schema", None) is not None
205
+ or getattr(run, "output_format", None) in _structured()
206
+ or getattr(run, "longform", None) or getattr(run, "pinned_apps", None)):
207
+ return False # these need the model's structuring or its plan
208
+ return True
209
+
210
+
211
+ def _structured():
212
+ from .. import smart_result
213
+ return smart_result.AUTOMATIC_FORMATS
214
+
215
+
216
+ def front_app(driver):
217
+ """The bundle id in front on the phone when the connection can say, else None."""
218
+ reader = getattr(driver, "active_app", None)
219
+ if callable(reader) and getattr(driver, "reads_front_app", True):
220
+ try:
221
+ bundle = reader()
222
+ except Exception: # noqa: BLE001 -- can't tell: not named
223
+ return None
224
+ return bundle if isinstance(bundle, str) else None
225
+ return None
226
+
227
+
228
+ def hook(ctx, driver, approve):
229
+ """harness_api pre-run hook: answer the request by code, or None and the run goes on as it would."""
230
+ if driver is None or not eligible(ctx):
231
+ return None
232
+ words = tokens(ctx.goal)
233
+ # The phone is asked which app is in front only for a request that could use it (a bare "my DMs").
234
+ plan = parse(ctx.goal, app_bundle=ctx.app_bundle) or (
235
+ parse(ctx.goal, app_bundle=ctx.app_bundle, front_bundle=front_app(driver)) if words else None)
236
+ if plan is None:
237
+ return None
238
+ pinned = pins.aside(plan.pack)
239
+ if pinned:
240
+ ctx.emit({"event": "front_door", "routed": False, "pack": plan.pack.key, "why": pinned})
241
+ return None # the general agent does it
242
+ gate = tools.run_gate(ctx.run_id, ctx.data_dir)
243
+ version = core.app_version(ctx.runtime, plan.pack.bundle)
244
+ trusted, why = gate.trust("pack", plan.pack.bundle, plan.what, None, version)
245
+ if not trusted:
246
+ ctx.emit({"event": "front_door", "routed": False, "pack": plan.pack.key, "why": why})
247
+ return None
248
+ from .register import _io
249
+ pack, started = plan.pack, time.monotonic()
250
+ ctx.emit({"event": "skill_started", "op": f"{pack.prefix}_{'READ' if plan.kind == 'read' else 'OPEN'}"})
251
+ io = _io(ctx, driver, pack)
252
+ try:
253
+ answer = _read(pack, io, plan) if plan.kind == "read" else _open(pack, io, plan)
254
+ except core.Lost as lost:
255
+ answer, why = None, str(lost).rstrip(".")
256
+ ok = answer is not None
257
+ gate.note(ok, "pack", pack.bundle)
258
+ event = {"pack": pack.key, "op": f"{pack.prefix}_{plan.kind.upper()}", "step": plan.what, "ok": ok, "door": True,
259
+ "ms": round((time.monotonic() - started) * 1000), "taps": io.taps, "version": version,
260
+ "how": "code", **({} if ok else {"why": why})}
261
+ ctx.emit({"event": "app_pack", **event})
262
+ health.record(ctx.data_dir,
263
+ {k: v for k, v in event.items() if k != "why"} | ({} if ok else {"why": tools._quiet(why)}))
264
+ if not ok:
265
+ ctx.emit({"event": "front_door", "routed": False, "pack": pack.key, "why": why})
266
+ return None # the model takes the phone where the code left it
267
+ return harness_api.PreRun(result=_result(ctx, answer, started))
268
+
269
+
270
+ def _read(pack, io, plan):
271
+ result = pack.read(io, plan.what, plan.n, plan.arg)
272
+ rows = list(result.rows)
273
+ if plan.ask.pick:
274
+ column, pattern = plan.ask.pick
275
+ rows = [row for row in rows if re.search(pattern, str(row.get(column) or ""), re.I)]
276
+ if not rows:
277
+ raise core.Lost(f"{pack.read_titles.get(plan.what, plan.what)} showed nothing it could name")
278
+ columns = result.columns
279
+ keep = rows[:tools.FEEDBACK_ROWS]
280
+ lines = "\n".join(_pair(row, columns) if plan.ask.pick else
281
+ tools.row_line(i + 1, row, columns, pack.required.get(plan.what, ()))
282
+ for i, row in enumerate(keep))
283
+ note = f" It read {len(rows)} of the {plan.n} you asked for: {result.stopped}." if (
284
+ plan.ask.counted and len(rows) < plan.n and result.stopped) else ""
285
+ block = None if plan.ask.pick else tools.block_for(pack, replace(result, rows=rows))
286
+ return {"text": f"{pack.name}, {result.title}, as the screen shows it:\n{lines}{note}", "rows": rows,
287
+ "columns": columns, "title": result.title, "stopped": result.stopped, "block": block}
288
+
289
+
290
+ def _pair(row, columns):
291
+ """One picked row as "iOS Version: 26.0.1" (its first two columns)."""
292
+ keys = [key for key, _label, _kind in columns][:2]
293
+ return ": ".join(core.plain(row.get(key)) for key in keys if row.get(key) not in (None, ""))
294
+
295
+
296
+ def _open(pack, io, plan):
297
+ landing = core.navigate(pack, io, plan.what, plan.arg)
298
+ if not landing.ok:
299
+ raise core.Lost(landing.why)
300
+ page = plan.page or "Settings"
301
+ return {"text": f"Opened {page} in {pack.name}. The screen is {landing.seen.title}.", "rows": [], "columns": [],
302
+ "title": page, "stopped": None, "block": None}
303
+
304
+
305
+ def _result(ctx, answer, started):
306
+ from .. import result_blocks as rb
307
+ scalars = (str, int, float, bool, type(None))
308
+ rows = [{k: v for k, v in row.items() if isinstance(v, scalars)} for row in answer["rows"]]
309
+ columns = [{"key": k, "label": label, "type": kind} for k, label, kind in answer["columns"]]
310
+ data = {"rows": rows, "columns": columns, "title": answer["title"], "stopped": answer["stopped"] or None} \
311
+ if rows else None
312
+ blocks = [answer["block"]] if answer["block"] else []
313
+ return {"event": "result", "status": "completed", "engine": "smart", "outcome": "done", "answer": answer["text"],
314
+ "reason": None, "data": data, "actions": 1, "decisions": 0, "costUsd": 0.0, "model": None,
315
+ "elapsed_ms": round((time.monotonic() - started) * 1000), "blocks": rb.envelope(blocks),
316
+ "independently_verified": True, "proof": []}
@@ -0,0 +1,214 @@
1
+ """Packs that survive an app's redesigns and A/B tests: drift is logged, the general agent takes over at once, and a
2
+ screen the pack missed can become a learned variant, used only after it passes a replay check."""
3
+
4
+ import json
5
+ import re
6
+ import threading
7
+ import time
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ from . import core
12
+
13
+ KIND = "variant" # the heal file's lines this module owns
14
+ MIN_SIGNALS = 3
15
+ MAX_SIGNALS = 6
16
+ MAX_VARIANTS = 12 # per pack, version and screen
17
+ MAX_LABEL = 28
18
+ CHROME_TOP, CHROME_BOTTOM = .3, .85
19
+ # A name, a handle, a number, a time or a link: never kept as a signal.
20
+ PERSONAL = re.compile(r"(@|https?://|\d{2,}|\b\d+\s?[smhdw]\b|\bago\b|[\w.]+\.(com|net|org)\b|"
21
+ r"^[A-Za-z0-9]+[._][A-Za-z0-9._]+$|^[A-Z][a-z]+ [A-Z][a-z]+$)", re.I)
22
+ _stores = {}
23
+ _lock = threading.Lock()
24
+
25
+
26
+ def fingerprint(snapshot):
27
+ """The screen's structural hash (replay_core): which roles and label shapes, no text. None when unknown."""
28
+ try:
29
+ from ..replay_core.path import screen_hashes
30
+ hashed = screen_hashes(snapshot)
31
+ return hashed["hash"] if hashed else None
32
+ except Exception: # noqa: BLE001
33
+ return None
34
+
35
+
36
+ def chrome(snapshot):
37
+ """The app's own words on ``snapshot``: short labels in its top part (titles, filters, the search field), the
38
+ current tab, and named buttons in between, with no name, handle, number, time or link in them."""
39
+ picked, seen = [], set()
40
+ for element in getattr(snapshot, "elements", ()) or ():
41
+ label = core.plain(element.label)
42
+ if not label or len(label) > MAX_LABEL or PERSONAL.search(label) or label.casefold() in seen:
43
+ continue
44
+ y = core.centre_y(element)
45
+ if y > CHROME_BOTTOM:
46
+ if not core.is_selected(element):
47
+ continue
48
+ elif y >= CHROME_TOP and element.role != "Button":
49
+ continue
50
+ seen.add(label.casefold())
51
+ picked.append((label, element.role, round(y, 2)))
52
+ picked.sort(key=lambda item: item[2])
53
+ return picked
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class Variant:
58
+ id: str
59
+ pack: str
60
+ version: str
61
+ screen: str
62
+ fingerprint: str
63
+ signals: tuple # ({"pattern", "role", "region"}, ...)
64
+ pictured: bool
65
+ status: str = "proposed" # proposed | confirmed | trusted
66
+ runs: tuple = ()
67
+ at: float = 0.0
68
+ checked_at: float = 0.0
69
+
70
+ def spec(self, pack):
71
+ signals = tuple(core.Signal(re.compile(s["pattern"], re.I), s.get("role"), tuple(s["region"])
72
+ if s.get("region") else None) for s in self.signals)
73
+ title = pack.screen_title(self.screen)
74
+ if self.pictured:
75
+ return core.ScreenSpec(self.screen, title, (), "learned", ocr=signals)
76
+ return core.ScreenSpec(self.screen, title, signals, "learned")
77
+
78
+
79
+ class Learned:
80
+ """The variants this Mac learned, by pack, app version and screen (a JSON file beside the journal)."""
81
+
82
+ def __init__(self, path, clock=time.time):
83
+ self.path = Path(path) if path else None
84
+ self.clock = clock
85
+ self.lock = threading.Lock()
86
+ self.version_on_phone = {} # pack key -> the app version the phone has, when known
87
+ newest = {}
88
+ if self.path is not None and self.path.exists():
89
+ try:
90
+ for line in self.path.read_text(encoding="utf-8").splitlines():
91
+ try:
92
+ item = json.loads(line)
93
+ except ValueError:
94
+ continue
95
+ if item.get("kind") != KIND:
96
+ continue
97
+ item = {k: v for k, v in item.items() if k in Variant.__dataclass_fields__}
98
+ newest[item["id"]] = Variant(**{**item, "signals": tuple(item.get("signals") or ()),
99
+ "runs": tuple(item.get("runs") or ())})
100
+ except (OSError, TypeError, KeyError):
101
+ newest = {}
102
+ self.variants = list(newest.values())
103
+
104
+ def _write(self, variant):
105
+ """One line for ``variant``'s new state in the heal file (its newest line is its state)."""
106
+ if self.path is None:
107
+ return
108
+ from .. import hints
109
+ hints._append(self.path, {"kind": KIND, "source": "pack", **variant.__dict__, "signals": list(variant.signals),
110
+ "runs": list(variant.runs)})
111
+
112
+ def _replace(self, old, new):
113
+ self.variants = [new if v is old else v for v in self.variants]
114
+ self._write(new)
115
+
116
+ def propose(self, pack, snapshot, screen, version, run_id):
117
+ """A candidate variant of ``screen`` from ``snapshot`` (the pack missed it): its id, or None when the screen
118
+ shows too few of the app's own words to learn from."""
119
+ words = chrome(snapshot)[:MAX_SIGNALS]
120
+ if len(words) < MIN_SIGNALS:
121
+ return None
122
+ pictured = core.image_only(snapshot)
123
+ signals = tuple({"pattern": "^" + re.escape(label) + "$", "role": None if pictured else role,
124
+ "region": [max(0.0, round(y - .06, 2)), min(1.0, round(y + .06, 2))]}
125
+ for label, role, y in words)
126
+ mark = fingerprint(snapshot) or ""
127
+ with self.lock:
128
+ same = next((v for v in self.variants if (v.pack, v.version, v.screen) == (pack.key, version or "", screen)
129
+ and (v.fingerprint == mark and mark or v.signals == signals)), None)
130
+ if same is not None:
131
+ if run_id and run_id not in same.runs:
132
+ self._replace(same, Variant(**{**same.__dict__, "runs": (*same.runs, run_id)[-8:]}))
133
+ return same.id
134
+ kept = [v for v in self.variants if (v.pack, v.version, v.screen) == (pack.key, version or "", screen)]
135
+ if len(kept) >= MAX_VARIANTS:
136
+ return None
137
+ variant = Variant(f"{pack.key}-{screen}-{int(self.clock() * 1000) % 10**10}", pack.key, version or "",
138
+ screen, mark, signals, pictured, "proposed", (run_id,) if run_id else (), self.clock())
139
+ self.variants.append(variant)
140
+ self._write(variant)
141
+ return variant.id
142
+
143
+ def confirm_run(self, run_id):
144
+ """The run finished completed: the general agent finished from the screens the pack missed in it."""
145
+ changed = 0
146
+ with self.lock:
147
+ for variant in list(self.variants):
148
+ if variant.status == "proposed" and run_id in variant.runs:
149
+ self._replace(variant, Variant(**{**variant.__dict__, "status": "confirmed"}))
150
+ changed += 1
151
+ return changed
152
+
153
+ def check(self, pack, snapshot, screen, version):
154
+ """The replay check, on a fresh screen the pack missed again: a confirmed variant of ``screen`` that names it,
155
+ while no built-in screen of another name does, becomes trusted."""
156
+ trusted = []
157
+ with self.lock:
158
+ others = [spec for spec in pack.screens if spec.name != screen]
159
+ rival = max((core.score(spec, snapshot)[0] for spec in others), default=0.0)
160
+ for variant in list(self.variants):
161
+ if (variant.pack, variant.version, variant.screen, variant.status) != (pack.key, version or "", screen,
162
+ "confirmed"):
163
+ continue
164
+ if core.image_only(snapshot) != variant.pictured:
165
+ continue
166
+ value = core.score(variant.spec(pack), snapshot)[0]
167
+ if value >= core.SURE and rival < core.SURE:
168
+ new = Variant(**{**variant.__dict__, "status": "trusted", "checked_at": self.clock()})
169
+ self._replace(variant, new)
170
+ trusted.append(new.id)
171
+ return trusted
172
+
173
+ def specs(self, pack):
174
+ """The trusted variants of ``pack`` as ScreenSpecs, for the app version on the phone (all, when unknown). An
175
+ act's control is never found through one: core.control_for reads the pack's own patterns."""
176
+ version = self.version_on_phone.get(pack.key)
177
+ return tuple(v.spec(pack) for v in self.variants if v.pack == pack.key and v.status == "trusted"
178
+ and (not version or v.version in (version, "")))
179
+
180
+ def summary(self, pack_key):
181
+ counts = {}
182
+ for variant in self.variants:
183
+ if variant.pack == pack_key:
184
+ counts[variant.status] = counts.get(variant.status, 0) + 1
185
+ return counts
186
+
187
+
188
+ def store_for(data_dir):
189
+ """The one Learned store of a data folder. Without one (a benchmark, a test), a fresh store that keeps nothing."""
190
+ if not data_dir:
191
+ return Learned(None)
192
+ from .. import hints
193
+ key = str(Path(data_dir) / hints.HEALS)
194
+ with _lock:
195
+ if key not in _stores:
196
+ _stores[key] = Learned(key)
197
+ return _stores[key]
198
+
199
+
200
+ def confirm_run(run_id):
201
+ """A run that finished completed: confirm, in every store, the variants proposed from screens it missed."""
202
+ with _lock:
203
+ stores = list(_stores.values())
204
+ for store in stores:
205
+ store.confirm_run(run_id)
206
+
207
+
208
+ def attach(packs, data_dir):
209
+ """Give ``packs`` the store of ``data_dir``: core.recognize then uses its trusted variants."""
210
+ store = store_for(data_dir)
211
+ if data_dir:
212
+ for pack in packs:
213
+ pack.learned = store
214
+ return store
@@ -0,0 +1,122 @@
1
+ """Pack health: which app version a pack was checked on, how its steps go on this Mac, and when it steps aside."""
2
+
3
+ RECENT = 200
4
+
5
+
6
+ def bundle_of(pack_key):
7
+ """The bundle id of the pack named ``pack_key`` (the ledger's ``app``), or the key when no pack has it."""
8
+ try:
9
+ from .register import all_packs
10
+ return next((pack.bundle for pack in all_packs() if pack.key == pack_key), pack_key)
11
+ except Exception: # noqa: BLE001
12
+ return pack_key
13
+
14
+
15
+ def record(data_dir, entry):
16
+ """Append one pack step (``entry``, no screen text) to the hint ledger."""
17
+ from .. import hints
18
+ hints.record(data_dir, {**entry, "kind": "use", "source": "pack", "app": bundle_of(entry.get("pack")),
19
+ "key": entry.get("step"), "ok": bool(entry.get("ok"))})
20
+
21
+
22
+ def recent(data_dir, pack_key=None, limit=RECENT):
23
+ """The newest ``limit`` pack steps (of one pack), oldest first."""
24
+ from .. import hints
25
+ lines = [e for e in hints.recent(data_dir, source="pack", limit=10 ** 6)
26
+ if e.get("kind", "use") == "use" and (pack_key is None or e.get("pack") == pack_key)]
27
+ return lines[-limit:]
28
+
29
+
30
+ def status(pack, installed=None, entries=()):
31
+ """One pack's health: what it was checked on, what the phone has, and how its recent steps went."""
32
+ verified = pack.verified
33
+ steps = [e for e in entries if e.get("pack") == pack.key]
34
+ landed = sum(1 for e in steps if e.get("ok"))
35
+ aside = [e for e in steps if not e.get("ok")]
36
+ state = "unknown"
37
+ if steps:
38
+ state = "healthy" if not aside or landed / len(steps) >= .8 else "degraded"
39
+ changed = changed_screens(steps)
40
+ version = installed or (verified.version if verified else None)
41
+ if not steps:
42
+ line = f"{pack.name} pack: not used on this Mac yet"
43
+ elif changed:
44
+ line = (f"{pack.name} pack: working on {version}; {len(changed)} screen{'s' if len(changed) != 1 else ''} "
45
+ "changed")
46
+ else:
47
+ line = f"{pack.name} pack: working on {version}"
48
+ learned = (getattr(pack, "learned", None).summary(pack.key) if getattr(pack, "learned", None) is not None else {})
49
+ return {"pack": pack.key, "name": pack.name, "bundleId": pack.bundle, "summary": line,
50
+ "changedScreens": [{"screen": name, "title": pack.screen_title(name), "since": at, "version": v}
51
+ for name, (at, v) in changed.items()],
52
+ "learned": learned,
53
+ "verified": ({"version": verified.version, "date": verified.date, "device": verified.device or None}
54
+ if verified else None),
55
+ "pin": _pin(pack),
56
+ "installed": installed, "sameVersion": (installed == verified.version) if installed and verified else None,
57
+ "screens": [{"name": s.name, "title": s.title, "evidence": s.evidence} for s in pack.screens],
58
+ "steps": len(steps), "landed": landed, "steppedAside": len(aside), "state": state,
59
+ "lastProblem": ({"step": aside[-1].get("step"), "why": aside[-1].get("why"), "at": aside[-1].get("at"),
60
+ "version": aside[-1].get("version")} if aside else None)}
61
+
62
+
63
+ def _pin(pack):
64
+ """What the team pinned this pack to ("off" or an app version), and whether it steps aside; None with no pin."""
65
+ from . import pins
66
+ to = pins.current().get(pack.key)
67
+ return None if to is None else {"to": to, "aside": pins.aside(pack) is not None}
68
+
69
+
70
+ def changed_screens(steps):
71
+ """{screen: (since, version)} for each screen a pack last failed to recognize (``unsure_screen``) with no
72
+ recognition of it since, in log order: the screens the app changed under the pack."""
73
+ missed = {}
74
+ for entry in steps:
75
+ if entry.get("unsure_screen"):
76
+ missed[entry["unsure_screen"]] = (entry.get("at") or 0, entry.get("version"))
77
+ elif entry.get("ok") and entry.get("screen"):
78
+ missed.pop(entry["screen"], None)
79
+ return missed
80
+
81
+
82
+ def tab_routes(pack):
83
+ """The routes a health run visits: each reaches a screen from the tab bar by taps alone (no argument, no link
84
+ needed,
85
+ no act)."""
86
+ acts = {spec.route for spec in pack.controls.values()}
87
+ return [route for route in pack.routes.values()
88
+ if route.from_tabs and not route.arg and route.name not in acts and route.steps
89
+ and all(type(step).__name__ == "Tap" for step in route.steps)]
90
+
91
+
92
+ def check(pack, io):
93
+ """Visit each tab route and say whether the pack named the screen it reached: [{route, screen, landed, confidence,
94
+ evidence, why}]."""
95
+ from . import core
96
+ rows = []
97
+ for route in tab_routes(pack):
98
+ landing = core.navigate(pack, io, route.name)
99
+ rows.append({"route": route.name, "title": route.title, "screen": landing.seen.screen, "landed": landing.ok,
100
+ "confidence": landing.seen.confidence, "evidence": landing.seen.evidence or None,
101
+ "why": landing.why or None})
102
+ if io.cancelled():
103
+ break
104
+ return rows
105
+
106
+
107
+ def check_result(pack, rows, version, seconds):
108
+ """A health run's result, as a task's: the answer line and a checklist card."""
109
+ from .. import result_blocks as rb
110
+ landed = sum(1 for row in rows if row["landed"])
111
+ items = [{"id": f"s{i + 1}", "label": row["title"][:1].upper() + row["title"][1:],
112
+ "status": "pass" if row["landed"] else "fail",
113
+ "expected": f"{pack.name} shows {row['title']}",
114
+ "observed": (f"recognized ({row['confidence']:.2f})" if row["landed"] else (row["why"] or "not named"))}
115
+ for i, row in enumerate(rows)]
116
+ changed = len(rows) - landed
117
+ answer = (f"{pack.name} pack: working on {version or 'this version'}"
118
+ + (f"; {changed} screen{'s' if changed != 1 else ''} changed." if changed else "."))
119
+ return {"event": "result", "status": "completed", "engine": "smart", "outcome": "done" if not changed else "check",
120
+ "answer": answer, "actions": len(rows), "decisions": 0, "elapsed_ms": round(seconds * 1000),
121
+ "costUsd": 0.0, "model": None,
122
+ "blocks": rb.envelope([rb.checklist(items, title=f"{pack.name} pack health", id=f"{pack.key}-health")])}