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
mobile_agent/run.py ADDED
@@ -0,0 +1,642 @@
1
+ """A task's record: its event log, its approval handshake and what it says outside. It knows no phone and no HTTP."""
2
+
3
+ import itertools
4
+ import re
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+
10
+ from . import failure_line, harness_api, orchestrate, result_blocks, secret_filter, traces
11
+ from .api_errors import APIError
12
+ from .costs import NANODOLLARS
13
+ from .explain import explain
14
+ from .journal import JournalError
15
+ from .run_executor import CancelToken
16
+ from .steering import SteeringQueue
17
+
18
+ APPROVAL_TIMEOUT_SECONDS = 600
19
+ INSTRUCTION_MAX_CHARS = 500
20
+ ANSWER_MAX_CHARS = 500
21
+ STEER_MAX_CHARS = 2000
22
+ APPROVAL_PENDING = "Choose Send or Don't send above, or say what to do instead there."
23
+
24
+
25
+ QUIT_REASON = "Mobster quit while this task was running. Run it again to finish."
26
+
27
+
28
+ def quit_summary(summary):
29
+ out = {**summary, "status": "interrupted", "reason": QUIT_REASON, "interrupted_by": "quit"}
30
+ if out.get("outcome") == "stopped":
31
+ out["outcome"] = "couldnt_finish"
32
+ return out
33
+
34
+
35
+ def preapproved_matches(spec, request):
36
+ if isinstance(spec, dict) and spec.get("kind") == "queue_post":
37
+ from .content_queue.runner import preapproval_matches
38
+ return preapproval_matches(spec, request)
39
+ return result_blocks.preapproval_matches(spec, request)
40
+
41
+
42
+ PHONE_WAIT_KINDS = frozenset({"lock_screen", "passcode_keypad", "unplugged", "wifi_lost"})
43
+ PHONE_WAIT_SCAN = 400
44
+
45
+
46
+ def phone_wait(events):
47
+ for event in reversed(events[-PHONE_WAIT_SCAN:]):
48
+ name = event.get("event")
49
+ if name == "handoff_finished":
50
+ return None
51
+ if name != "handoff_waiting":
52
+ continue
53
+ if event.get("kind") not in PHONE_WAIT_KINDS:
54
+ return None
55
+ seconds = event.get("seconds") if isinstance(event.get("seconds"), int) and event["seconds"] > 0 else 120
56
+ return {"kind": event["kind"], "seconds": seconds, "since": event.get("timestamp"),
57
+ "midrun": event.get("midrun") is True}
58
+ return None
59
+
60
+
61
+ CHOICE_DETAIL = " \u2014 "
62
+
63
+
64
+ def clarify_choice(choice):
65
+ # The detail after an em dash tells two people with one first name apart (CR-25).
66
+ label, _, after = str(choice.get("label") or choice["id"]).partition(CHOICE_DETAIL)
67
+ detail = " ".join(str(choice.get("detail") or after).split())[:120]
68
+ return {"id": str(choice["id"])[:40], "label": " ".join(label.split())[:120] or str(choice["id"])[:120],
69
+ **({"detail": detail} if detail else {})}
70
+
71
+
72
+ @dataclass
73
+ class Run:
74
+ app: dict
75
+ goal: str
76
+ mode: str
77
+ id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
78
+ status: str = "queued"
79
+ created_at: float = field(default_factory=time.time)
80
+ finished_at: float | None = None
81
+ events: list = field(default_factory=list)
82
+ summary: dict | None = None
83
+ image: str | None = None
84
+ image_at: float = 0
85
+ image_lock: threading.Lock = field(default_factory=threading.Lock)
86
+ capture: object = None
87
+ image_captured_at: float | None = None
88
+ idempotency_key: str | None = None
89
+ output_schema: dict | None = None
90
+ output_format: str = "auto"
91
+ allowed_bundles: frozenset | None = None
92
+ journal: object = None
93
+ lease: object = None
94
+ device: object = None
95
+ guard: object = None
96
+ device_id: str | None = None
97
+ device_name: str | None = None
98
+ phone: object = None
99
+ group_id: str | None = None
100
+ # The phone's own rules, kept with the run: a later change never reaches a running task.
101
+ phone_apps: frozenset | None = None
102
+ ask_override: bool | None = None
103
+ stop: CancelToken = field(default_factory=CancelToken) # a token, so RunExecutor(run.stop) stops with it
104
+ condition: threading.Condition = field(default_factory=threading.Condition)
105
+ # In memory only: it can carry the exact text to be sent, which is never written to the journal.
106
+ approval: dict | None = None
107
+ approval_answer: str | None = None
108
+ approval_deadline: float | None = None
109
+ answered: dict = field(default_factory=dict)
110
+ engine: str | None = None
111
+ cost_nanodollars: int = 0
112
+ cost_known: bool = False
113
+ estimate_usd: float | None = None
114
+ cap_usd: float | None = None
115
+ cap_from_month: bool = False
116
+ cap_from_workflow: bool = False
117
+ reviewed_ok: bool = False
118
+ frames: object = None
119
+ extras: dict = field(default_factory=dict)
120
+ # Detached writing context; never part of public metadata, journal or repr.
121
+ account_context: object = field(default=None, repr=False)
122
+ workflow_id: str | None = field(default=None, repr=False)
123
+ phone_finished: bool = field(default=False, repr=False)
124
+ origin: str = "api"
125
+ # The hosted review demo (remote/review.py) takes no yes to an outside agent's approval: the sentence a yes is
126
+ # refused with. None everywhere else.
127
+ approvals_closed: str | None = None
128
+ steering: SteeringQueue = field(default_factory=SteeringQueue)
129
+ # The agent's masked notes and plan for listeners: in memory only, never journaled.
130
+ private: dict = field(default_factory=dict)
131
+ listeners: object = None
132
+ # A workflow loop waits for the listeners' on_finished, so round two finds what round one taught.
133
+ settled: threading.Event = field(default_factory=threading.Event)
134
+ longform: dict | None = None
135
+ start_on_home: bool = False
136
+ remember_spec: dict | None = None
137
+ on_remember: object = None
138
+ pinned_apps: list | None = None
139
+ plan: dict | None = None
140
+ plan_terms: dict = field(default_factory=dict)
141
+ subrun_open: str | None = None
142
+ subrun_said: dict = field(default_factory=dict)
143
+ subruns_seen: set = field(default_factory=set)
144
+ quitting: bool = False
145
+ # In memory only; a send that doesn't match one exactly asks as usual.
146
+ preapproved: list = field(default_factory=list)
147
+ # Journaled, so an approved item is never approved, and so never sent, twice.
148
+ block_state: dict = field(default_factory=dict)
149
+ # Never the page itself, never a token.
150
+ shares: list = field(default_factory=list)
151
+ # Issues filed from the result, noted before each is sent: "filing" or "unknown" is never sent again.
152
+ issues: list = field(default_factory=list)
153
+ # The MCP client's own name for itself: a label only, never an identity.
154
+ client: str | None = None
155
+ started_at: float | None = None
156
+ taking_back: bool = False
157
+ queue_info: dict | None = None
158
+
159
+ def request_approval(self, request, timeout=None, pending_out=None):
160
+ # approved, denied, timeout, stopped, decided, choice:<id>, redirected:<text> or answer:<text>.
161
+ timeout = APPROVAL_TIMEOUT_SECONDS if timeout is None else timeout
162
+ now = time.time()
163
+ pending = {"id": uuid.uuid4().hex[:12], "operation": request["operation"], "label": request["label"],
164
+ "role": request.get("role") or "", "text": request.get("text"),
165
+ "app": str(request.get("app_name") or self.app["name"])[:80],
166
+ "requestedAt": now * 1000, "expiresAt": (now + timeout) * 1000,
167
+ "actingAs": str(request["acting_as"])[:120] if request.get("acting_as") else None,
168
+ "recipient": str(request["recipient"])[:120] if request.get("recipient") else None}
169
+ kind = request.get("kind") or "action"
170
+ if kind == "commit":
171
+ with self.condition:
172
+ spec = next((one for one in self.preapproved if preapproved_matches(one, request)), None)
173
+ if spec is not None and not self.stop.is_set() and not self.approvals_closed:
174
+ spec["used"] = True
175
+ else:
176
+ spec = None
177
+ if spec is not None:
178
+ # Approved on its review queue item: exactly these words, to this person, once.
179
+ self.emit({"event": "approval_requested", "approval_id": pending["id"], "step": request.get("step"),
180
+ "operation": pending["operation"], "label": pending["label"],
181
+ "text_present": pending["text"] is not None, "kind": kind, "title": request.get("title"),
182
+ "act": request.get("act"), "reason": None, "target": None, "choices": [],
183
+ "preapproved": spec.get("via") or "review_queue"})
184
+ with self.condition:
185
+ self._remember(pending["id"], "approved")
186
+ self.emit({"event": "approval_resolved", "approval_id": pending["id"], "decision": "approved",
187
+ "via": spec.get("via") or "review_queue", "source": dict(spec.get("source") or {})})
188
+ return "approved"
189
+ target = request.get("target")
190
+ pending.update(kind=kind, title=str(request["title"])[:160] if request.get("title") else None,
191
+ act=str(request["act"])[:40] if request.get("act") else None,
192
+ reason=" ".join(str(request["reason"]).split())[:120] if request.get("reason") else None,
193
+ target=({key: float(target[key]) for key in ("x", "y", "w", "h")}
194
+ if isinstance(target, dict) and all(isinstance(target.get(k), (int, float))
195
+ for k in ("x", "y", "w", "h")) else None))
196
+ crop = request.get("crop")
197
+ if isinstance(crop, (bytes, bytearray)) and crop and self.frames is not None:
198
+ frame_id = self.frames.put(self.id, crop)
199
+ pending["crop"] = f"/api/runs/{self.id}/frames/{frame_id}" if frame_id else None
200
+ if isinstance(request.get("apps"), list):
201
+ named = [(name, bundle) for name, bundle in itertools.zip_longest(
202
+ request["apps"], request.get("bundles") if isinstance(request.get("bundles"), list) else ())
203
+ if isinstance(name, str) and name]
204
+ pending["apps"] = [name for name, _ in named]
205
+ if isinstance(request.get("bundles"), list):
206
+ pending["appIds"] = [bundle[:200] if isinstance(bundle, str) else None for _, bundle in named]
207
+ if kind in ("loop", "question"):
208
+ choices = [{"id": str(c["id"])[:40], "label": str(c["label"])[:120]}
209
+ for c in request.get("choices") or () if isinstance(c, dict) and c.get("id")][:6]
210
+ default = request.get("default_choice")
211
+ pending.update(kind=kind, question=str(request.get("question") or "")[:300], choices=choices,
212
+ defaultChoice=default if any(c["id"] == default for c in choices) else None,
213
+ limits=request.get("limits"))
214
+ elif kind == "clarify":
215
+ choices = [clarify_choice(c) for c in request.get("choices") or () if isinstance(c, dict)
216
+ and c.get("id")][:6]
217
+ pending.update(label=str(request["label"])[:80],
218
+ question=str(request.get("question") or request["label"])[:300], choices=choices,
219
+ allowText=bool(request.get("allow_text", not choices)))
220
+ elif kind == "practice":
221
+ known = {"right": "That’s right", "skip": "Skip this step", "model": "Let Mobster do it"}
222
+ choices = [{"id": c["id"], "label": known[c["id"]]} for c in request.get("choices") or ()
223
+ if isinstance(c, dict) and c.get("id") in known][:3]
224
+ practice = request.get("practice") if isinstance(request.get("practice"), dict) else {}
225
+ pending.update(label=str(request["label"])[:80], question=str(request.get("question") or "")[:300],
226
+ choices=choices, defaultChoice="right" if any(c["id"] == "right" for c in choices) else None,
227
+ allowTap=True, practice={"step": int(practice.get("step") or 0),
228
+ "of": int(practice.get("of") or 0),
229
+ "purpose": str(practice.get("purpose") or "")[:160],
230
+ "differs": bool(practice.get("differs"))})
231
+ elif kind == "plan":
232
+ shaped = orchestrate.plan(request.get("plan"))
233
+ pending.update(operation="PLAN", label=shaped["summary"][:80], title=shaped["summary"][:160], text=None,
234
+ plan=shaped, limits=" ".join(shaped["policy"]) or None)
235
+ if shaped.get("long"):
236
+ pending.update(choices=orchestrate.long_choices(shaped["long"]), defaultChoice="default")
237
+ with self.condition:
238
+ self.plan_terms = {}
239
+ elif kind == "proposals":
240
+ items = orchestrate.proposals(request.get("items"))
241
+ count = len(items)
242
+ title = str(request.get("title") or "").strip()[:120] or (
243
+ items[0]["title"] if count == 1 else f"Review {count} drafts")
244
+ pending.update(operation="PROPOSE", label=title[:80], title=title, text=None,
245
+ act=items[0]["act"] if len({item["act"] for item in items}) == 1 else None,
246
+ app=items[0]["app"] if len({item["app"] for item in items}) == 1 else "Several apps",
247
+ items=items)
248
+ pending["explain"] = explain(request)
249
+ spec = request.get("remember") if isinstance(request.get("remember"), dict) else None
250
+ pending["rememberable"] = spec is not None
251
+ pending["rememberLabel"] = str(spec["words"])[:80] if spec and spec.get("words") else None
252
+ if isinstance(pending_out, dict):
253
+ pending_out.clear()
254
+ pending_out.update(pending)
255
+ deadline = time.monotonic() + timeout
256
+ with self.condition:
257
+ self.approval, self.approval_answer, self.remember_spec = pending, None, spec
258
+ self.approval_deadline = deadline
259
+ self.emit({"event": "approval_requested", "approval_id": pending["id"], "step": request.get("step"),
260
+ "operation": pending["operation"], "label": pending["label"],
261
+ "text_present": pending["text"] is not None, "kind": kind,
262
+ "title": pending["title"], "act": pending["act"], "reason": pending["reason"],
263
+ "target": pending["target"], "explain": explain({**request, "recipient": None}),
264
+ **({"crop": pending["crop"]} if pending.get("crop") else {}),
265
+ "choices": [c["id"] for c in pending.get("choices", ())],
266
+ **({"acting_as": pending["actingAs"]} if pending["actingAs"] else {}),
267
+ **({"plan": pending["plan"]} if kind == "plan" else {}),
268
+ **({"items": [orchestrate.public_proposal(item) for item in pending["items"]],
269
+ "count": len(pending["items"])} if kind == "proposals" else {})})
270
+ with self.condition:
271
+ while self.approval_answer is None and not self.stop.is_set():
272
+ # The monotonic clock stands still while the Mac sleeps: the wall clock the card counts down to wins.
273
+ remaining = min(deadline - time.monotonic(), now + timeout - time.time())
274
+ if remaining <= 0:
275
+ break
276
+ self.condition.wait(timeout=min(remaining, 1))
277
+ # Stop wins over an answer it raced with: a stopped task never acts, whichever reached it first.
278
+ answer = "stopped" if self.stop.is_set() else self.approval_answer or "timeout"
279
+ self.approval, self.approval_answer = None, None
280
+ self.approval_deadline = None
281
+ tally = {}
282
+ if kind == "proposals":
283
+ # Fail closed: nothing nobody chose goes out, and after Stop nothing does.
284
+ for item in pending["items"]:
285
+ if item["decision"] is None or answer == "stopped":
286
+ item["decision"] = "skip"
287
+ tally = {"sent": sum(item["decision"] == "send" for item in pending["items"]),
288
+ "skipped": sum(item["decision"] == "skip" for item in pending["items"])}
289
+ self._remember(pending["id"], "redirected" if answer.startswith("redirected:") else
290
+ "answered" if answer.startswith("answer:") else
291
+ "choice" if answer.startswith("choice:") else answer)
292
+ # A redirect's instruction and a typed answer go to the agent, never into the journal.
293
+ self.emit({"event": "approval_resolved", "approval_id": pending["id"],
294
+ "decision": "redirected" if answer.startswith("redirected:") else
295
+ "answered" if answer.startswith("answer:") else answer, **tally})
296
+ return answer
297
+
298
+ def _remember(self, approval_id, decision):
299
+ self.answered.pop(approval_id, None)
300
+ self.answered[approval_id] = decision
301
+ while len(self.answered) > 20:
302
+ self.answered.pop(next(iter(self.answered)))
303
+
304
+ def approval_decision(self, approval_id):
305
+ with self.condition:
306
+ if self.approval is not None and self.approval.get("id") == approval_id:
307
+ if self.stop.is_set():
308
+ return "stopped"
309
+ answer = self.approval_answer or ""
310
+ return ("redirected" if answer.startswith("redirected:") else
311
+ "answered" if answer.startswith("answer:") else
312
+ "choice" if answer.startswith("choice:") else answer or None)
313
+ return self.answered.get(approval_id)
314
+
315
+ def _approval_waiting(self, approval_id):
316
+ if (self.stop.is_set() or self.approval is None or self.approval["id"] != approval_id
317
+ or self.approval_answer is not None):
318
+ return False
319
+ if (self.approval_deadline is None or time.monotonic() >= self.approval_deadline
320
+ or time.time() * 1000 >= self.approval["expiresAt"]):
321
+ self.approval_answer = "timeout"
322
+ self.condition.notify_all()
323
+ return False
324
+ return True
325
+
326
+ def _refuse_yes(self, approval_id, approve):
327
+ """A run that ``approvals_closed`` takes no yes (an approval, a choice, a send): the refusal is journaled and
328
+ raised, before anything is decided. A no, a redirect and any answer to a clarifying question go through."""
329
+ if not self.approvals_closed or approve is not True:
330
+ return
331
+ with self.condition:
332
+ pending = self.approval
333
+ if pending is None or pending["id"] != approval_id or pending.get("kind") == "clarify":
334
+ return
335
+ self.emit({"event": "approval_refused", "approval_id": approval_id, "reason": "approvals_closed"})
336
+ raise ValueError(self.approvals_closed)
337
+
338
+ def answer_approval(self, approval_id, approve, choice=None, instruction=None, answer=None, remember=False,
339
+ allow_cost=False):
340
+ """Answer the pending request. ``instruction`` with approve false is a redirect: the task goes on
341
+ with it ("Decline and say what to do instead"), and request_approval returns "redirected:<text>".
342
+ ``answer`` (a clarifying question only, with approve true): the user's typed answer, 1–500 characters;
343
+ request_approval returns "answer:<text>"."""
344
+ self._refuse_yes(approval_id, approve)
345
+ with self.condition:
346
+ if not self._approval_waiting(approval_id):
347
+ return False
348
+ proposals = self.approval is not None and self.approval.get("kind") == "proposals"
349
+ if allow_cost:
350
+ if (self.approval is None or self.approval.get("kind") != "plan" or not approve
351
+ or not (self.approval.get("plan") or {}).get("long")):
352
+ raise ValueError("allowCost goes with Start on a long task's plan")
353
+ if proposals:
354
+ if choice is not None or instruction is not None or answer is not None:
355
+ raise ValueError("Proposals take Send or Skip, for some items or for all")
356
+ return self.decide_proposals(approval_id, approve)
357
+ with self.condition:
358
+ # After Stop nothing is answered, though the card shows until the agent's thread wakes.
359
+ if not self._approval_waiting(approval_id):
360
+ return False
361
+ if answer is not None and self.approval.get("kind") == "practice":
362
+ # Where the person tapped on the live view: never sent to the phone by itself.
363
+ found = re.fullmatch(r"\s*(0(?:\.\d+)?|1(?:\.0+)?|\.\d+)\s*,\s*(0(?:\.\d+)?|1(?:\.0+)?|\.\d+)\s*",
364
+ str(answer))
365
+ if not approve or choice is not None or instruction is not None or not found:
366
+ raise ValueError("A practice answer is a tap: x,y in screen fractions from 0 to 1, "
367
+ "with approve true")
368
+ self.approval_answer = f"answer:{float(found.group(1)):.4f},{float(found.group(2)):.4f}"
369
+ elif answer is not None:
370
+ text = " ".join(str(answer).split())
371
+ if self.approval.get("kind") != "clarify":
372
+ raise ValueError("Only a question from Mobster's agent takes a typed answer")
373
+ if not approve or choice is not None or instruction is not None \
374
+ or not 1 <= len(text) <= ANSWER_MAX_CHARS:
375
+ raise ValueError("An answer says in 1–500 characters what Mobster's agent asked, with approve true")
376
+ self.approval_answer = f"answer:{text}"
377
+ elif instruction is not None:
378
+ text = " ".join(str(instruction).split())
379
+ if approve or choice is not None or not 1 <= len(text) <= INSTRUCTION_MAX_CHARS:
380
+ raise ValueError("A redirect declines the action and says in 1–500 characters what to do instead")
381
+ self.approval_answer = f"redirected:{text}"
382
+ elif choice is not None:
383
+ if not approve or choice not in {c["id"] for c in self.approval.get("choices", ())}:
384
+ raise ValueError("That choice is not one of this question's answers")
385
+ self.approval_answer = f"choice:{choice}"
386
+ else:
387
+ self.approval_answer = "approved" if approve else "denied"
388
+ spec = self.remember_spec if approve and remember and self.approval.get("rememberable") else None
389
+ if spec is not None and callable(self.on_remember):
390
+ try:
391
+ self.on_remember(spec)
392
+ except Exception:
393
+ pass # the answer stands; only the remembering failed
394
+ if allow_cost:
395
+ self.plan_terms = {"allowCost": True}
396
+ self.condition.notify_all()
397
+ return True
398
+
399
+ def decide_proposals(self, approval_id, approve, items=None, text=None):
400
+ if type(approve) is not bool:
401
+ raise ValueError("approve is true or false")
402
+ self._refuse_yes(approval_id, approve)
403
+ if text is not None:
404
+ text = orchestrate._draft(text, "An edited draft")
405
+ if not text:
406
+ raise ValueError("An edited draft has text")
407
+ with self.condition:
408
+ if not self._approval_waiting(approval_id):
409
+ return False
410
+ if self.approval.get("kind") != "proposals":
411
+ raise ValueError("Only proposals are decided item by item")
412
+ pool = self.approval["items"]
413
+ if items is None:
414
+ chosen = [item for item in pool if item["decision"] is None]
415
+ else:
416
+ by_id = {item["id"]: item for item in pool}
417
+ if (not isinstance(items, list) or not items or len(set(map(str, items))) != len(items)
418
+ or not all(isinstance(key, str) and key in by_id for key in items)):
419
+ raise ValueError("items are ids of this batch's proposals, each once")
420
+ chosen = [by_id[key] for key in items]
421
+ if any(item["decision"] is not None for item in chosen):
422
+ return False
423
+ if not chosen:
424
+ return False
425
+ if text is not None and (not approve or len(chosen) != 1):
426
+ raise ValueError("An edited draft sends exactly one item")
427
+ for item in chosen:
428
+ item["decision"] = "send" if approve else "skip"
429
+ if text is not None and text != item["text"]:
430
+ item["text"], item["edited"] = text, True
431
+ decided = [(item["id"], item["decision"], item["edited"]) for item in chosen]
432
+ for item_id, decision, edited in decided:
433
+ self.emit({"event": "proposal_decided", "approval_id": approval_id, "item_id": item_id,
434
+ "decision": decision, "edited": edited})
435
+ with self.condition:
436
+ if (self.approval is not None and self.approval["id"] == approval_id and self.approval_answer is None
437
+ and all(item["decision"] for item in self.approval["items"])):
438
+ self.approval_answer = "decided"
439
+ self.condition.notify_all()
440
+ return True
441
+
442
+ def interpret(self, text, apps=(), read_only=True):
443
+ self.emit(orchestrate.interpretation(text, apps, read_only))
444
+
445
+ def propose_plan(self, plan, timeout=None):
446
+ shaped = orchestrate.plan(plan)
447
+ return self.request_approval({"kind": "plan", "operation": "PLAN", "label": shaped["summary"][:80],
448
+ "plan": shaped}, timeout=timeout)
449
+
450
+ def propose(self, items, title=None, timeout=None):
451
+ pending = {}
452
+ decision = self.request_approval({"kind": "proposals", "operation": "PROPOSE", "label": "Proposals",
453
+ "items": items, "title": title}, timeout=timeout, pending_out=pending)
454
+ return {"approvalId": pending["id"], "decision": decision,
455
+ "items": {item["id"]: {"decision": item["decision"], "text": item["text"], "edited": item["edited"]}
456
+ for item in pending["items"]}}
457
+
458
+ def proposal_result(self, approval_id, item_id, status, line=None):
459
+ if status not in orchestrate.PROPOSAL_STATUS:
460
+ raise ValueError("A proposal's result is sent, failed or skipped")
461
+ self.emit({"event": "proposal_result", "approval_id": str(approval_id)[:40], "item_id": str(item_id)[:40],
462
+ "status": status, "line": orchestrate._text(line, 200, "A result's line", required=False)})
463
+
464
+ def subruns_planned(self, subruns):
465
+ if not isinstance(subruns, (list, tuple)) or not 1 <= len(subruns) <= orchestrate.PLAN_APPS:
466
+ raise ValueError(f"Plan 1-{orchestrate.PLAN_APPS} sub-runs")
467
+ rows = []
468
+ for row in subruns:
469
+ if not isinstance(row, dict):
470
+ raise ValueError("A sub-run is a dict")
471
+ rows.append(orchestrate.subrun(row.get("app"), row.get("bundleId"), row.get("title"),
472
+ row.get("id") or uuid.uuid4().hex[:8]))
473
+ self.emit({"event": "subruns_planned", "subruns": rows})
474
+ return [row["subrun"] for row in rows]
475
+
476
+ def subrun_start(self, app, bundle_id=None, title=None, subrun_id=None, run_id=None):
477
+ row = orchestrate.subrun(app, bundle_id, title, subrun_id or uuid.uuid4().hex[:8], run_id)
478
+ with self.condition:
479
+ if self.subrun_open is not None:
480
+ raise ValueError("Finish the open sub-run first")
481
+ if row["subrun"] in self.subruns_seen and row["subrun"] != subrun_id:
482
+ raise ValueError("Each sub-run has its own id")
483
+ self.subrun_open = row["subrun"]
484
+ self.subruns_seen.add(row["subrun"])
485
+ self.emit({"event": "subrun_started", **row})
486
+ return row["subrun"]
487
+
488
+ def subrun_progress(self, subrun_id, line, counts=None):
489
+ shaped = {"line": orchestrate._text(line, 160, "A progress line"), "counts": orchestrate.counts(counts)}
490
+ now = time.monotonic()
491
+ with self.condition:
492
+ if subrun_id not in self.subruns_seen:
493
+ raise ValueError("No sub-run has that id")
494
+ if now - self.subrun_said.get(subrun_id, 0) < 0.5:
495
+ return False
496
+ self.subrun_said[subrun_id] = now
497
+ self.emit({"event": "subrun_progress", "subrun": subrun_id, **shaped})
498
+ return True
499
+
500
+ def subrun_finish(self, subrun_id, outcome="done", line=None, counts=None):
501
+ if outcome not in orchestrate.SUBRUN_OUTCOMES:
502
+ raise ValueError("A sub-run ends done, check, couldnt_finish, stopped or skipped")
503
+ shaped = {"line": orchestrate._text(line, 200, "A sub-run's line", required=False),
504
+ "counts": orchestrate.counts(counts)}
505
+ with self.condition:
506
+ if subrun_id not in self.subruns_seen:
507
+ raise ValueError("No sub-run has that id")
508
+ if self.subrun_open == subrun_id:
509
+ self.subrun_open = None
510
+ self.emit({"event": "subrun_finished", "subrun": subrun_id, "outcome": outcome, **shaped})
511
+
512
+ def emit(self, event):
513
+ with self.condition:
514
+ if "_frame" in event:
515
+ # Screens are stored beside the run and referenced by id; the caller's dict gets the id too.
516
+ data = event.pop("_frame")
517
+ event["frameId"] = self.frames.put(self.id, data) if self.frames is not None and data else None
518
+ # A call on the person's plan is no spend: History and Home show "On your Claude plan", not "0¢".
519
+ if (event.get("event") == "inference_finished" and type(event.get("cost_nanodollars")) is int
520
+ and event.get("billing") != "plan"):
521
+ self.cost_nanodollars += event["cost_nanodollars"]
522
+ self.cost_known = True
523
+ # The one gate for what a run stores and serves: secrets are masked in the journal and every reader.
524
+ entry = {**secret_filter.redact_deep(event), "seq": len(self.events), "timestamp": time.time() * 1000}
525
+ if self.subrun_open is not None and "subrun" not in entry:
526
+ entry["subrun"] = self.subrun_open
527
+ if self.journal:
528
+ self.journal.append(self.metadata(), entry)
529
+ self.events.append(entry)
530
+ self.condition.notify_all()
531
+ if self.listeners is not None:
532
+ # Posting under the lock keeps publication in saved sequence order when two threads emit.
533
+ self.listeners.post("on_event", self, entry)
534
+
535
+ def steer(self, text, source="app"):
536
+ if source not in ("app", "tui", "cli", "mcp", "voice", "remote"):
537
+ raise ValueError("Unknown message source")
538
+ allowed = harness_api.steer_sources(self.origin)
539
+ if source not in allowed:
540
+ raise APIError("This task takes messages only where it was started.", 403, "steer_not_allowed")
541
+ if not isinstance(text, str) or not 1 <= len(text.strip()) <= STEER_MAX_CHARS:
542
+ raise ValueError("A message is 1 to 2,000 characters")
543
+ with self.condition:
544
+ if self.finished_at is not None or self.steering.closed:
545
+ raise APIError("This task has finished.", 409, "run_not_active")
546
+ if self.approval is not None and self.approval.get("kind") != "clarify":
547
+ raise APIError(APPROVAL_PENDING, 409, "approval_pending", approvalId=self.approval["id"])
548
+ message = self.steering.put(text, source=source)
549
+ self.emit({"event": "user_message", "id": message.id, "text": message.text, "source": message.source})
550
+ return {"id": message.id, "text": message.text, "source": message.source, "at": message.at * 1000}
551
+
552
+ def finish(self, summary):
553
+ if self.finished_at is None:
554
+ unread = self.steering.close()
555
+ if unread:
556
+ try:
557
+ self.emit({"event": "steer_unread", "ids": [message.id for message in unread]})
558
+ except Exception:
559
+ pass # the terminal record below still goes in
560
+ with self.condition:
561
+ if self.finished_at is not None:
562
+ return
563
+ finished_at = time.time()
564
+ if summary.get("status") == "stopped" and self.quitting:
565
+ summary = quit_summary(summary)
566
+ if "deviceKind" not in summary:
567
+ kind = getattr(self.phone, "kind", None) or "usb"
568
+ summary = {**summary, "deviceKind": kind,
569
+ **({"deviceName": self.device_name} if self.device_name else {})}
570
+ if "blocks" in summary:
571
+ # The one gate for result blocks: bounded and redacted, else the block falls back to its text.
572
+ blocks, problems = result_blocks.validate(summary.get("blocks"))
573
+ summary = {**summary, "blocks": blocks}
574
+ if blocks is None:
575
+ summary.pop("blocks")
576
+ if problems:
577
+ summary["blocks_problems"] = [str(p)[:160] for p in problems[:8]]
578
+ status = summary["status"]
579
+ if status in {"queued", "running"}:
580
+ raise ValueError("A terminal run cannot remain active")
581
+ if traces.enabled() and "failure" not in summary:
582
+ failure = failure_line.failure_line(summary, self.events)
583
+ if failure is not None:
584
+ summary = {**summary, "failure": failure}
585
+ summary = secret_filter.redact_deep(summary)
586
+ status = summary["status"]
587
+ entry = {"event": "run_finished", "status": status, "summary": summary,
588
+ "seq": len(self.events), "timestamp": finished_at * 1000}
589
+ metadata = {**self.metadata(), "status": status, "summary": summary,
590
+ "finishedAt": finished_at * 1000}
591
+ try:
592
+ if self.journal:
593
+ self.journal.finish(metadata, entry)
594
+ except JournalError:
595
+ # Visible, never presented as durable; failed events were not appended, so seq ids stay contiguous.
596
+ summary = {**summary, "persistence_failed": True}
597
+ entry.update(summary=summary, persistence_failed=True)
598
+ self.status, self.summary, self.finished_at = status, summary, finished_at
599
+ self.events.append(entry)
600
+ self.condition.notify_all()
601
+
602
+ def metadata(self):
603
+ return {"id": self.id, "app": self.app, "appId": self.app["id"], "appName": self.app["name"],
604
+ "goal": self.goal, "mode": self.mode, "status": self.status,
605
+ "createdAt": self.created_at * 1000,
606
+ "finishedAt": self.finished_at * 1000 if self.finished_at is not None else None,
607
+ "summary": self.summary, "idempotencyKey": self.idempotency_key,
608
+ "outputSchema": self.output_schema, "outputFormat": self.output_format,
609
+ "allowedBundles": sorted(self.allowed_bundles) if self.allowed_bundles is not None else None,
610
+ "engine": self.engine, "costUsd": self.cost_usd,
611
+ "estimateUsd": self.estimate_usd, "reviewedOk": self.reviewed_ok,
612
+ "device": self.device_id, "deviceName": self.device_name,
613
+ "extras": harness_api.public_extras(self.extras), "origin": self.origin,
614
+ "pinnedApps": self.pinned_apps, **({"groupId": self.group_id} if self.group_id else {}),
615
+ **({"blockState": dict(self.block_state)} if self.block_state else {}),
616
+ **({"shares": [dict(item) for item in self.shares]} if self.shares else {}),
617
+ **({"issues": [dict(item) for item in self.issues]} if self.issues else {})}
618
+
619
+ @property
620
+ def steerable(self):
621
+ return self.engine == "smart" and self.mode == "live"
622
+
623
+ @property
624
+ def cost_usd(self):
625
+ return round(self.cost_nanodollars / NANODOLLARS, 6) if self.cost_known else None
626
+
627
+ def public(self, include_events=True):
628
+ with self.condition:
629
+ waiting = phone_wait(self.events) if self.finished_at is None else None
630
+ return {**{k: v for k, v in self.metadata().items() if k != "app"},
631
+ "approval": dict(self.approval) if self.approval else None,
632
+ **({"phoneWait": waiting} if waiting else {}),
633
+ **({"queue": dict(self.queue_info)} if self.queue_info else {}),
634
+ **({"takingBack": True} if self.taking_back and self.finished_at is None else {}),
635
+ "steerable": self.steerable,
636
+ "events": list(self.events) if include_events else [], "eventCount": len(self.events)}
637
+
638
+ def row(self):
639
+ value = self.public(include_events=False)
640
+ if isinstance(value["summary"], dict) and "timing" in value["summary"]:
641
+ value["summary"] = {k: v for k, v in value["summary"].items() if k != "timing"}
642
+ return value