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,213 @@
1
+ """Prewarm (SPEC §3.1 F8): ready the phone while the person types; it never acts or touches a phone running a task."""
2
+
3
+ import dataclasses
4
+ import logging
5
+ import threading
6
+ import time
7
+ from concurrent import futures
8
+ from urllib.parse import quote
9
+
10
+ from ..compose import build_target_driver
11
+ from ..journal import JournalError, Lease
12
+ from ..run_executor import RunExecutor
13
+ from ..wda.frame_clock import attach_frame_clock
14
+
15
+ log = logging.getLogger("mobster.harness")
16
+
17
+ SCREEN_TTL = 3.0 # seconds a prewarmed screen read may be reused for
18
+ MIN_INTERVAL = 2.0 # a second prewarm of one phone within this answers from the first
19
+ WAIT = 1.5 # how long the route waits for the warm-up before answering 202
20
+ OBSERVE_TIMEOUT = 5.0
21
+ SPRINGBOARD = "com.apple.springboard"
22
+
23
+
24
+ @dataclasses.dataclass
25
+ class Warm:
26
+ url: str
27
+ session: str
28
+ snapshot: object
29
+ clock: object
30
+ read_started: float
31
+
32
+
33
+ _lock = threading.Lock()
34
+ _screens = {} # WDA url -> Warm
35
+ _jobs = {} # WDA url -> (Future, started monotonic, result dict)
36
+ _workers = RunExecutor()
37
+
38
+
39
+ def _close_clock(clock):
40
+ try:
41
+ if clock is not None:
42
+ clock.close()
43
+ except Exception: # noqa: BLE001
44
+ pass
45
+
46
+
47
+ def _drop(url, warm=None):
48
+ """Forget ``url``'s screen (only ``warm`` when given) and release its clock."""
49
+ with _lock:
50
+ current = _screens.get(url)
51
+ if current is None or (warm is not None and current is not warm):
52
+ return
53
+ del _screens[url]
54
+ _close_clock(current.clock)
55
+
56
+
57
+ def _lease_free(url):
58
+ """True when no Mobster process holds this phone's lease now (taken and let go at once)."""
59
+ try:
60
+ lease = Lease.device(url.rstrip("/"), wait=0)
61
+ except JournalError:
62
+ return False
63
+ except Exception: # noqa: BLE001 -- can't tell: treat it as busy, never as free
64
+ return False
65
+ lease.close()
66
+ return True
67
+
68
+
69
+ def release(url):
70
+ """Let go of ``url``'s kept screen and its frame clock: the phone has been idle."""
71
+ _drop(url)
72
+
73
+
74
+ def _owner_active(url):
75
+ from .. import owner_presence
76
+ return owner_presence.is_owner_active(url)
77
+
78
+
79
+ def _busy(runtime, phone):
80
+ try:
81
+ if runtime.run_on(phone) is not None:
82
+ return True
83
+ except Exception: # noqa: BLE001
84
+ return True
85
+ return not _lease_free(phone.wda_url)
86
+
87
+
88
+ def _warm(runtime, phone, result):
89
+ """Warm ``phone`` up; fills ``result["warmed"]``. Never raises."""
90
+ url = phone.wda_url
91
+ driver = None
92
+ try:
93
+ if _owner_active(url):
94
+ # The owner is using the phone: a session or screen read now would reach into their hands.
95
+ result["reason"] = "owner_active"
96
+ return
97
+ if _busy(runtime, phone):
98
+ result["reason"] = "busy"
99
+ return
100
+ session = phone.wda_session()
101
+ if not session:
102
+ result["reason"] = "no_session"
103
+ return
104
+ driver = build_target_driver(wda_url=url, session=session)
105
+ tune = getattr(driver, "tune", None)
106
+ if callable(tune):
107
+ tune(rich=True, glide=True)
108
+ result["warmed"].append("wda")
109
+ video = getattr(phone, "video", None)
110
+ clock = attach_frame_clock(driver, video) if video is not None else None
111
+ if clock is None or _busy(runtime, phone):
112
+ return
113
+ started = time.monotonic()
114
+ observe = getattr(driver, "observe_ready", None) or driver.observe
115
+ snapshot = observe(timeout=OBSERVE_TIMEOUT)
116
+ read_started = float(getattr(snapshot, "read_started", 0.0) or started)
117
+ driver.frame_clock = None # the clock outlives this driver, for SCREEN_TTL
118
+ warm = Warm(url, session, snapshot, clock, read_started)
119
+ with _lock:
120
+ old, _screens[url] = _screens.get(url), warm
121
+ if old is not None:
122
+ _close_clock(old.clock)
123
+ timer = threading.Timer(SCREEN_TTL + 0.5, _drop, args=(url, warm))
124
+ timer.daemon = True
125
+ timer.start()
126
+ result["warmed"].append("screen")
127
+ except Exception as error: # noqa: BLE001 -- a cold or busy phone is never an error here
128
+ result.setdefault("reason", type(error).__name__)
129
+ log.info("prewarm stopped: %s", type(error).__name__)
130
+ finally:
131
+ if driver is not None:
132
+ clock = getattr(driver, "frame_clock", None)
133
+ if clock is not None:
134
+ driver.frame_clock = None
135
+ _close_clock(clock)
136
+ try:
137
+ driver.close()
138
+ except Exception: # noqa: BLE001
139
+ pass
140
+
141
+
142
+ def prewarm(runtime, device=None, *, wait=WAIT):
143
+ """Warm the phone ``device`` names (None: the one a new task runs on)."""
144
+ phone = runtime.resolve_device(device)
145
+ url = getattr(phone, "wda_url", None)
146
+ if not url:
147
+ return {"warmed": [], "pending": False, "reason": "no_phone"}
148
+ if _owner_active(url):
149
+ return {"warmed": [], "pending": False, "reason": "owner_active"}
150
+ now = time.monotonic()
151
+ with _lock:
152
+ job = _jobs.get(url)
153
+ if job is None or (job[0].done() and now - job[1] >= MIN_INTERVAL):
154
+ result = {"warmed": []}
155
+ job = _jobs[url] = (_workers.submit("mobster-prewarm", _warm, runtime, phone, result), now, result)
156
+ warming, _, result = job
157
+ futures.wait((warming,), max(0.0, wait))
158
+ out = {"warmed": list(result["warmed"]), "pending": not warming.done()}
159
+ if result.get("reason"):
160
+ out["reason"] = result["reason"]
161
+ return out
162
+
163
+
164
+ def take(driver, run_context=None, *, now=None):
165
+ """The prewarmed read of ``driver``'s phone as its first screen, or None. (snapshot, age in seconds)."""
166
+ url = getattr(driver, "url", None)
167
+ if not url:
168
+ return None
169
+ with _lock:
170
+ warm = _screens.pop(url, None)
171
+ if warm is None:
172
+ return None
173
+ try:
174
+ now = time.monotonic() if now is None else now
175
+ age = now - warm.read_started
176
+ if age > SCREEN_TTL or age < 0:
177
+ return None
178
+ if not str(getattr(driver, "prefix", "")).endswith("/" + quote(warm.session, safe="")):
179
+ return None
180
+ expected = getattr(run_context, "app_bundle", None) or SPRINGBOARD
181
+ if getattr(warm.snapshot, "bundle_id", None) != expected:
182
+ return None
183
+ from ..wda.guards import STILL_MARGIN
184
+ still = warm.clock.still_for()
185
+ if still is None or now - still > warm.read_started - STILL_MARGIN:
186
+ return None # something on screen changed since the read began (or the stream can't say)
187
+ # Unchanged since the read: the latest the run's own clock can vouch for, so its first action reuses it.
188
+ own = getattr(getattr(driver, "frame_clock", None), "still_for", None)
189
+ try:
190
+ run_still = own() if callable(own) else None
191
+ except Exception: # noqa: BLE001
192
+ run_still = None
193
+ started = max(warm.read_started, now - run_still if run_still is not None else now)
194
+ return dataclasses.replace(warm.snapshot, captured_at=now, read_started=started), age
195
+ except Exception: # noqa: BLE001
196
+ return None
197
+ finally:
198
+ _close_clock(warm.clock)
199
+
200
+
201
+ def close_all(runtime=None):
202
+ """Release every kept read and its clock (Runtime.close)."""
203
+ with _lock:
204
+ warms = list(_screens.values())
205
+ _screens.clear()
206
+ for warm in warms:
207
+ _close_clock(warm.clock)
208
+
209
+
210
+ def reset_for_tests():
211
+ close_all()
212
+ with _lock:
213
+ _jobs.clear()
@@ -0,0 +1,43 @@
1
+ """Track `harness`'s registrations, staged so a register that raises leaves nothing registered."""
2
+
3
+
4
+ def _bool(value, runtime):
5
+ if type(value) is not bool:
6
+ raise ValueError("askUser must be true or false")
7
+ return value
8
+
9
+
10
+ def hints_route(request):
11
+ """GET /api/harness/hints: {apps: hints.health}: every app's hints by source (packs, replays,
12
+ the iOS knowledge base)."""
13
+ from pathlib import Path
14
+
15
+ from .. import hints
16
+ from ..api_routes import Response
17
+ state_db = getattr(getattr(request.runtime, "config", None), "state_db", None)
18
+ return Response.json({"apps": hints.health(Path(state_db).parent if state_db else None)})
19
+
20
+
21
+ def register(api):
22
+ """``api`` is mobile_agent.harness_api."""
23
+ from .. import api_routes, journal, project_env
24
+ from . import checkpoints, prewarm, routes
25
+ from .tools import ask_user, watch
26
+
27
+ journal.register_schema(checkpoints.SCHEMA, checkpoints.VERSION, checkpoints.migrate)
28
+ api.register_run_field("resumeFrom", checkpoints.validate_resume_from)
29
+ api.register_run_field("askUser", _bool)
30
+ # A saved task's ${NAME}s come from this project's secrets (project_env.py); the goal keeps the placeholder.
31
+ api.register_run_field("projectEnv", project_env.validate_root)
32
+ api.register_pre_run(checkpoints.pre_run, order=0)
33
+ api.register_frontier_options(checkpoints.frontier_options)
34
+ api.register_tool(ask_user.factory, order=0)
35
+ api.register_tool(watch.factory, order=1)
36
+ api.register_service(lambda runtime: None, lambda runtime: (checkpoints.close(runtime), prewarm.close_all(runtime)))
37
+ for route in routes.ROUTES:
38
+ api_routes.register(route)
39
+ from .. import hints
40
+ api.register_run_listener(hints.HintListener())
41
+ api_routes.register(api_routes.Route("harness", "GET", "/api/harness/hints", hints_route, body="none"))
42
+ from ..integrations import routes as client_routes
43
+ client_routes.register_routes()
@@ -0,0 +1,114 @@
1
+ """The harness track's HTTP API (seam S3, mobile_agent/docs/harness.md); none of its routes approves anything."""
2
+
3
+ from ..api_errors import APIError
4
+ from ..api_routes import HEADER_ORIGINS, Response, Route
5
+ from ..steering import SteeringFull
6
+ from . import checkpoints, control, prewarm, settings
7
+ from .control import PAUSE_LIMIT_SECONDS
8
+
9
+ NO_MESSAGES = "This task can't take messages while it works. Stop it, or wait for it to finish."
10
+ NO_PAUSE = "This task can't pause. Stop it instead."
11
+ FULL = "Mobster's agent has 10 messages it hasn't read yet. Wait for it to catch up."
12
+
13
+
14
+ def _run(request):
15
+ runtime = request.runtime
16
+ with runtime.lock:
17
+ run = runtime.runs.get(request.params["id"])
18
+ if run is None:
19
+ raise APIError("Task not found", 404, "not_found")
20
+ return run
21
+
22
+
23
+ def _steerable_now(run, unsupported, code):
24
+ if run.finished_at is not None:
25
+ raise APIError("This task has finished.", 409, "run_not_active")
26
+ if not run.steerable:
27
+ raise APIError(unsupported, 409, code)
28
+
29
+
30
+ def source_of(request, given):
31
+ """The message's source: the request's origin label, or "voice" from the Mac app."""
32
+ origin = request.origin if request.origin in HEADER_ORIGINS else "cli"
33
+ if given is None or given == origin:
34
+ return origin
35
+ if given == "voice" and origin == "app":
36
+ return "voice"
37
+ raise ValueError("source can only be \"voice\", from the Mac app")
38
+
39
+
40
+ def messages(request):
41
+ run = _run(request)
42
+ body = request.body or {}
43
+ if "control" in body:
44
+ if set(body) != {"control"} or body["control"] != "stop_after_step":
45
+ raise ValueError("The only control is \"stop_after_step\", on its own")
46
+ _steerable_now(run, NO_MESSAGES, "steer_unsupported")
47
+ control.request_stop_after(run.steering)
48
+ run.emit({"event": "stop_after_step_requested"})
49
+ return Response.json({"control": "stop_after_step"}, 202)
50
+ if set(body) - {"text", "source"} or not isinstance(body.get("text"), str) \
51
+ or body.get("source") is not None and not isinstance(body["source"], str):
52
+ raise ValueError("Send the message as text, 1 to 2,000 characters")
53
+ source = source_of(request, body.get("source"))
54
+ _steerable_now(run, NO_MESSAGES, "steer_unsupported")
55
+ try:
56
+ message = run.steer(body["text"], source)
57
+ except SteeringFull:
58
+ raise APIError(FULL, 429, "steering_full") from None
59
+ return Response.json({"message": message}, 202)
60
+
61
+
62
+ def pause(request):
63
+ run = _run(request)
64
+ body = request.body or {}
65
+ if set(body) != {"paused"} or type(body["paused"]) is not bool:
66
+ raise ValueError("Send {\"paused\": true} to pause or {\"paused\": false} to continue")
67
+ _steerable_now(run, NO_PAUSE, "pause_unsupported")
68
+ control.set_paused(run.steering, body["paused"])
69
+ return Response.json({"run": run.public(include_events=False), "paused": body["paused"]})
70
+
71
+
72
+ def pickup(request):
73
+ if request.body:
74
+ raise ValueError("Send an empty JSON object")
75
+ keys = request.headers.get_all("Idempotency-Key", []) if hasattr(request.headers, "get_all") else []
76
+ key = keys[0] if keys else None
77
+ run, replayed = checkpoints.pick_up(request.runtime, request.params["id"], idempotency_key=key,
78
+ origin=request.origin)
79
+ return Response.json({"run": run.public(), "replayed": replayed}, 200 if replayed else 201)
80
+
81
+
82
+ def prewarm_route(request):
83
+ body = request.body or {}
84
+ if set(body) - {"device"} or body.get("device") is not None and not isinstance(body["device"], str):
85
+ raise ValueError("Send {} or {\"device\": \"<a device's id, UDID or name>\"}")
86
+ return Response.json(prewarm.prewarm(request.runtime, body.get("device")), 202)
87
+
88
+
89
+ def state(runtime):
90
+ return {"askUser": settings.ask_user(runtime),
91
+ "askUserLimit": settings.ASK_USER_LIMIT, "pauseLimitSeconds": PAUSE_LIMIT_SECONDS,
92
+ "ledger": False, "earlyLaunch": False, "longRun": None}
93
+
94
+
95
+ def harness_get(request):
96
+ return Response.json(state(request.runtime))
97
+
98
+
99
+ def harness_post(request):
100
+ body = request.body or {}
101
+ if set(body) != {"askUser"} or type(body["askUser"]) is not bool:
102
+ raise ValueError("Send {\"askUser\": true} or {\"askUser\": false}")
103
+ settings.save(request.runtime, {"askUser": body["askUser"]})
104
+ return Response.json(state(request.runtime))
105
+
106
+
107
+ ROUTES = (
108
+ Route("harness", "POST", r"/api/runs/{id}/messages", messages, max_bytes=8_000),
109
+ Route("harness", "POST", r"/api/runs/{id}/pause", pause, max_bytes=1_000),
110
+ Route("harness", "POST", r"/api/runs/{id}/pickup", pickup, max_bytes=1_000, idempotency=True),
111
+ Route("harness", "POST", r"/api/prewarm", prewarm_route, max_bytes=1_000),
112
+ Route("harness", "GET", r"/api/harness", harness_get, body="none"),
113
+ Route("harness", "POST", r"/api/harness", harness_post, max_bytes=1_000),
114
+ )
@@ -0,0 +1,66 @@
1
+ """Harness settings, kept in the run journal's ``harness_settings`` table (schema ``harness``)."""
2
+
3
+ import json
4
+ import os
5
+ import threading
6
+ import weakref
7
+
8
+ DEFAULTS = {"askUser": True}
9
+ ASK_USER_LIMIT = 2 # questions a task may ask
10
+ _cache = weakref.WeakKeyDictionary() # journal -> {key: value}
11
+ _lock = threading.Lock()
12
+
13
+
14
+ def ask_user_switch():
15
+ """The process-wide switch: on unless MOBSTER_ASK_USER says off."""
16
+ return os.environ.get("MOBSTER_ASK_USER", "on").strip().casefold() not in ("off", "0", "false", "no")
17
+
18
+
19
+ def _journal(runtime):
20
+ return getattr(runtime, "journal", None)
21
+
22
+
23
+ def load(runtime):
24
+ """Every setting, the saved value over the default."""
25
+ journal = _journal(runtime)
26
+ values = dict(DEFAULTS)
27
+ if journal is None or getattr(journal, "connection", None) is None:
28
+ return values
29
+ with _lock:
30
+ cached = _cache.get(journal)
31
+ if cached is not None:
32
+ return {**values, **cached}
33
+ saved = {}
34
+ try:
35
+ with journal.transaction() as connection:
36
+ rows = connection.execute("SELECT key, value FROM harness_settings").fetchall()
37
+ for key, raw in rows:
38
+ if key in DEFAULTS:
39
+ try:
40
+ saved[key] = json.loads(raw)
41
+ except ValueError:
42
+ pass
43
+ except Exception: # noqa: BLE001 -- no table (the track is off) or no journal: the defaults
44
+ saved = {}
45
+ with _lock:
46
+ _cache[journal] = saved
47
+ return {**values, **saved}
48
+
49
+
50
+ def save(runtime, changes):
51
+ """Save ``changes`` (validated by the caller). Returns every setting."""
52
+ journal = _journal(runtime)
53
+ if journal is None or getattr(journal, "connection", None) is None:
54
+ raise ValueError("Settings can't be saved without Mobster's task history")
55
+ with journal.transaction() as connection:
56
+ for key, value in changes.items():
57
+ connection.execute("INSERT INTO harness_settings(key, value) VALUES (?, ?) "
58
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value", (key, json.dumps(value)))
59
+ with _lock:
60
+ _cache.pop(journal, None)
61
+ return load(runtime)
62
+
63
+
64
+ def ask_user(runtime):
65
+ """Whether Mobster's agent may ask clarifying questions in tasks a person watches."""
66
+ return ask_user_switch() and load(runtime).get("askUser") is not False
@@ -0,0 +1,67 @@
1
+ """The tool policy (SPEC §3.1 F7): which registered tools a Smart run offers its model."""
2
+
3
+ import logging
4
+ import re
5
+
6
+ from ...agent_hooks import SkillContext
7
+
8
+ log = logging.getLogger("mobster.harness")
9
+
10
+ MAX_TOOLS = 8
11
+ OP = re.compile(r"[A-Z][A-Z_]{1,30}")
12
+ PROMPT_CHARS = 400
13
+
14
+
15
+ def _default_ops():
16
+ from ...frontier import default_skills
17
+ return {getattr(skill, "op", None) for skill in default_skills()}
18
+
19
+
20
+ def _why_not(skill, taken, run_context, context):
21
+ """None when ``skill`` may be offered, else a short reason for the log."""
22
+ op = getattr(skill, "op", None)
23
+ if not isinstance(op, str) or not OP.fullmatch(op) or op in taken:
24
+ return "its op is invalid or taken"
25
+ if not callable(getattr(skill, "prepare", None)) or not callable(getattr(skill, "perform", None)):
26
+ return "it has no prepare or perform"
27
+ raw = str(getattr(skill, "prompt", "") or "")
28
+ prompt = raw.strip()
29
+ prompt = prompt[2:] if prompt.startswith("- ") else prompt
30
+ if not prompt.startswith(op) or len(prompt) > PROMPT_CHARS or "\n" in raw.strip():
31
+ return "its prompt is not one line that starts with its op"
32
+ if getattr(skill, "interactive_only", False) and not getattr(run_context, "interactive", False):
33
+ return "nobody is watching this run"
34
+ available = getattr(skill, "available", None)
35
+ if callable(available):
36
+ try:
37
+ if not available(context):
38
+ return "it isn't available here"
39
+ except Exception: # noqa: BLE001 -- a tool that can't say is not offered
40
+ return "its availability check failed"
41
+ return None
42
+
43
+
44
+ def offered(skills, *, run_context=None, driver=None, guard=None, emit=None):
45
+ """``skills`` as the policy offers them (a tuple), the default skills first as given."""
46
+ from ...frontier import OPERATIONS
47
+ defaults = _default_ops()
48
+ context = SkillContext(driver=driver, request="", app_bundle=getattr(run_context, "app_bundle", None),
49
+ emit=emit or (lambda event: None), guard=guard, run=run_context,
50
+ clarify=getattr(run_context, "clarify", None))
51
+ out, taken, extra = [], set(OPERATIONS), 0
52
+ for skill in skills or ():
53
+ op = getattr(skill, "op", None)
54
+ if op in defaults and op not in taken:
55
+ taken.add(op)
56
+ out.append(skill)
57
+ continue
58
+ why = _why_not(skill, taken, run_context, context)
59
+ if why is None and extra >= MAX_TOOLS:
60
+ why = f"only {MAX_TOOLS} tools are offered at once"
61
+ if why is not None:
62
+ log.info("tool %r not offered: %s", op, why)
63
+ continue
64
+ taken.add(op)
65
+ extra += 1
66
+ out.append(skill)
67
+ return tuple(out)
@@ -0,0 +1,102 @@
1
+ """ASK_USER: Mobster's agent asks the person a clarifying question (SPEC §3.1 F2); an answer approves nothing."""
2
+
3
+ import time
4
+
5
+ from ...agent_hooks import Prepared, SkillResult
6
+ from ...secret_filter import redact
7
+ from ..settings import ASK_USER_LIMIT
8
+
9
+ PROMPT = ("- ASK_USER text: only when the request can be read two ways the screen can't settle and a wrong guess would "
10
+ "change what is sent, bought, posted, deleted or answered. Never to confirm a send (that asks by itself). "
11
+ "Put up to 4 short answers after the question, each after \" | \", a person's with what tells them apart "
12
+ "after \" \u2014 \" (\"Which Sam? | Sam Lee \u2014 texted Tuesday | Sam Ortiz \u2014 ends 0142\").")
13
+ QUESTION_CHARS, LABEL_CHARS, CHOICE_CHARS, MAX_CHOICES = 300, 80, 120, 6
14
+
15
+ NO_QUESTION = "ASK_USER needs the question in text."
16
+ CANT_ASK = ("Refused: this task can't ask the user. Decide from the request and the screen, or finish with BLOCKED "
17
+ "and say what you need to know.")
18
+ ASKED_ENOUGH = ("You already asked the user twice in this task. Decide from the request and the screen, or finish "
19
+ "with BLOCKED and say what you need to know.")
20
+ DECLINED = ("The user chose not to answer. Decide from the request and the screen, or finish with BLOCKED and say "
21
+ "what you need to know. Don't ask again.")
22
+ NO_ANSWER = "Nobody answered Mobster's question in 10 minutes, so it stopped without doing anything more."
23
+ STOPPED = "Stopped while Mobster waited for an answer to its question."
24
+
25
+
26
+ def split(text):
27
+ """(question, choices) from "Question? | A | B": at most MAX_CHOICES distinct choices, none unless two or more."""
28
+ parts = [" ".join(part.split()) for part in str(text or "").split("|")]
29
+ question = parts[0][:QUESTION_CHARS]
30
+ choices, seen = [], set()
31
+ for part in parts[1:]:
32
+ label = part[:CHOICE_CHARS]
33
+ if label and label.casefold() not in seen:
34
+ seen.add(label.casefold())
35
+ choices.append({"id": f"c{len(choices) + 1}", "label": label})
36
+ if len(choices) == MAX_CHOICES:
37
+ break
38
+ return question, (choices if len(choices) >= 2 else [])
39
+
40
+
41
+ def feedback_for(answer, choices):
42
+ """(feedback for the model, run-ending status or None) for the asker's answer."""
43
+ answer = answer if isinstance(answer, str) else "denied"
44
+ if answer.startswith("answer:") or answer.startswith("redirected:"):
45
+ text = " ".join(answer.split(":", 1)[1].split())[:500]
46
+ return f"The user answered your question: {text!r}. Go on with that.", None
47
+ if answer.startswith("choice:"):
48
+ chosen = answer.split(":", 1)[1]
49
+ label = next((c["label"] for c in choices if c["id"] == chosen), chosen)
50
+ return f"The user answered your question: {label!r}. Go on with that.", None
51
+ if answer == "timeout":
52
+ return NO_ANSWER, "approval_timeout"
53
+ if answer == "stopped":
54
+ return STOPPED, "stopped"
55
+ return DECLINED, None
56
+
57
+
58
+ class AskUser:
59
+ """agent_hooks.Skill: ASK_USER (one instance per run: it counts its questions)."""
60
+
61
+ op = "ASK_USER"
62
+ prompt = PROMPT
63
+ needs_target = False
64
+ interactive_only = True
65
+
66
+ def __init__(self, limit=ASK_USER_LIMIT, clock=time.monotonic):
67
+ self.limit, self.clock, self.asked = limit, clock, 0
68
+
69
+ def available(self, ctx):
70
+ return callable(getattr(ctx, "clarify", None))
71
+
72
+ def prepare(self, ctx, act, snapshot):
73
+ return Prepared(None) # a question is not a commit: nothing to approve
74
+
75
+ def perform(self, ctx, prepared, act, snapshot):
76
+ question, choices = split(redact(str(act.get("text") or "")))
77
+ if not question:
78
+ return SkillResult(NO_QUESTION)
79
+ clarify = getattr(ctx, "clarify", None)
80
+ if not callable(clarify):
81
+ return SkillResult(CANT_ASK)
82
+ if self.asked >= self.limit:
83
+ return SkillResult(ASKED_ENOUGH)
84
+ self.asked += 1
85
+ started = self.clock()
86
+ answer = clarify({"kind": "clarify", "operation": self.op, "label": question[:LABEL_CHARS],
87
+ "question": question, "choices": choices, "allow_text": True})
88
+ waited = max(0.0, self.clock() - started)
89
+ feedback, status = feedback_for(answer, choices)
90
+ return SkillResult(feedback, changed=False, waited=waited, status=status)
91
+
92
+
93
+ def factory(ctx):
94
+ """register_tool's factory: an AskUser for a run a person watches, else None."""
95
+ from ..settings import ask_user
96
+ if not getattr(ctx, "interactive", False) or getattr(ctx, "engine", None) != "smart":
97
+ return None
98
+ if (ctx.extras or {}).get("askUser") is not True:
99
+ return None
100
+ if not ask_user(ctx.runtime):
101
+ return None
102
+ return AskUser()