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,644 @@
1
+ """A long task's run-wide state (DESIGN §13.1): the plan, budget, notebook and models, and the run's spend and time."""
2
+
3
+ import math
4
+ import threading
5
+ import time
6
+
7
+ from . import context as ctx_text
8
+ from .notebook import Notebook, cut
9
+
10
+ # longform_progress is journaled like every event, and the app reads only the newest: one at most every 5 s while
11
+ # nothing changes (a 10-minute run journals about 120), at once on a new phase and on every 10th item.
12
+ PROGRESS_INTERVAL = 5.0
13
+ STORE_EVERY_ITEMS = 10
14
+ WRAP_UP_SHARE = 0.8
15
+ COVERED_GRACE_TURNS = 2 # turns the model may take after the budget is covered before the run ends
16
+ # A run that makes no progress ends early with a partial report (review of PR #150, P1-1): the fake Calendar that
17
+ # ignored app switches kept a run repeating LAUNCH_APP for 38 turns, until the spend cap.
18
+ STALL_TURNS = 6 # turns in a row with no new item, note, step change or screen
19
+ REFUSED_IN_A_ROW = 3 # actions refused in a row (a loop, a repeat, a gate)
20
+ DRY_MACROS = 2 # macro returns in a row that saved nothing and say the feed or list is done
21
+ DRY_REASONS = frozenset({"stuck", "looping", "not_feed", "end", "refused"})
22
+ MAX_RULES, MAX_RULE_CHARS = 12, 160 # the conversation's standing rules a long run ranks against
23
+
24
+ _sessions = {}
25
+ _lock = threading.Lock()
26
+
27
+
28
+ def for_run(run_id):
29
+ """The run's LongSession, or None (not a long run, or it ended)."""
30
+ with _lock:
31
+ return _sessions.get(run_id)
32
+
33
+
34
+ def bind(session):
35
+ with _lock:
36
+ _sessions[session.run_id] = session
37
+ return session
38
+
39
+
40
+ def unbind(run_id):
41
+ with _lock:
42
+ return _sessions.pop(run_id, None)
43
+
44
+
45
+ class LongSession:
46
+ # The prompt text of a long task (context.py), for FrontierAgent: the engine reaches it through the session.
47
+ context = ctx_text
48
+
49
+ def __init__(self, run_id, request, plan, budget, *, models=None, interactive=True, focus=None,
50
+ preferences=(), ledger=None, cap_usd=None, emit=None, frames=None, store=None, journal=None,
51
+ notebook=None, spent_offset=0.0, phone_offset=0.0, clock=time.monotonic, awake=None):
52
+ self.run_id, self.request, self.plan, self.budget = run_id, request, dict(plan or {}), budget
53
+ self.models, self.interactive = models, bool(interactive)
54
+ self.focus = focus
55
+ self.preferences = list(preferences or ())
56
+ self.ledger, self.cap_usd = ledger, cap_usd
57
+ self.frames, self.store, self.journal = frames, store, journal
58
+ self.clock = clock
59
+ self.started = clock()
60
+ self.awake = awake
61
+ steps = [{**s, "state": s.get("state") or "next"} for s in (self.plan.get("steps") or ())]
62
+ self.notebook = notebook or Notebook(run_id, request, self.plan.get("kind", "feed"), focus, self.preferences,
63
+ steps)
64
+ if notebook is None and self.notebook.steps:
65
+ self.notebook.steps[0]["state"] = "now"
66
+ self.notebook.steps[0]["started_turn"] = 0
67
+ self.spent_offset, self.phone_offset = float(spent_offset or 0.0), float(phone_offset or 0.0)
68
+ self._sink = emit or (lambda event: None)
69
+ self._agent = None
70
+ self._first_action = None
71
+ self._released = None
72
+ self._waited = 0.0
73
+ self._last_progress = -PROGRESS_INTERVAL
74
+ self._last_tenth = 0
75
+ self._phase = "navigating"
76
+ self._pool = None
77
+ self._digester = None
78
+ self._step_notes = {}
79
+ self._covered_turns = 0
80
+ # Progress (P1-1): the work signature at the last progress, the screens seen since, and the streaks.
81
+ self._work = None
82
+ self._screens = set()
83
+ self._stall_turns = 0
84
+ self._refused_row = 0
85
+ self._dry_macros = []
86
+ self.stalled = None # why the run stopped making progress, once it has
87
+ self.turn = 0
88
+ self.bundle = None # the app on screen at the latest turn (an item without its own takes it)
89
+ self.apps_by_turn = {}
90
+ self.epochs = ctx_text.Epochs()
91
+ self.compactor = ctx_text.Compactor()
92
+ self.report = None
93
+ self.closed = False
94
+ self.lock = threading.RLock()
95
+
96
+ def attach(self, agent):
97
+ """The frontier running this session: its emit (masked), its mask, its stop and steering."""
98
+ self._agent = agent
99
+ self.notebook.mask = self.mask
100
+
101
+ def emit(self, event):
102
+ """An event through FrontierAgent.emit (masked, timed) once attached, else straight to the run's events."""
103
+ agent = self._agent
104
+ if agent is not None:
105
+ agent._emit(event)
106
+ else:
107
+ self._sink(event)
108
+
109
+ def mask(self, text):
110
+ agent = self._agent
111
+ return agent._mask(text) if agent is not None and isinstance(text, str) else text
112
+
113
+ def should_yield(self):
114
+ """Why a macro should return now: "stopped" | "stop_after" | "paused" | "steer" | "budget" | None."""
115
+ agent = self._agent
116
+ if agent is not None:
117
+ try:
118
+ if agent._stopping():
119
+ return "stopped"
120
+ if agent._stop_after_asked():
121
+ return "stop_after"
122
+ if agent._pause_asked():
123
+ return "paused"
124
+ steering = getattr(agent, "steering", None)
125
+ if steering is not None and steering.pending():
126
+ return "steer"
127
+ except Exception: # noqa: BLE001 -- can't tell: return to the model
128
+ return "stopped"
129
+ if self.covered() is not None:
130
+ return "budget"
131
+ return None
132
+
133
+ def spent_usd(self):
134
+ """The run's spend: every metered call (navigation, extraction, digest, synthesis, hedges), plus what a
135
+ picked-up run spent before."""
136
+ nano = getattr(self.ledger, "cost_nanodollars", 0) if self.ledger is not None else 0
137
+ return round(nano / 1e9 + self.spent_offset, 6)
138
+
139
+ def reserve_usd(self):
140
+ """What the report will cost (costs.synth_estimate on the kept items so far, at least the budget's)."""
141
+ from .costs import synth_estimate
142
+ model = getattr(getattr(self.models, "synth", None), "model", None) or (self.plan.get("models") or {}).get(
143
+ "report") or "claude-sonnet-5-5"
144
+ kept = self.notebook.watched()
145
+ target = 0 if self.budget.strict else (self.budget.items or 0)
146
+ return synth_estimate(max(kept, target), model)
147
+
148
+ def phone_seconds(self):
149
+ """Seconds the run has held the phone: from its first action to now (or the release), less the time it
150
+ waited for the person (approvals, pauses), plus a picked-up run's earlier time."""
151
+ if self._first_action is None:
152
+ return round(self.phone_offset, 2)
153
+ end = self._released if self._released is not None else self.clock()
154
+ return round(max(0.0, end - self._first_action - self._waited) + self.phone_offset, 2)
155
+
156
+ def mark_action(self):
157
+ if self._first_action is None:
158
+ self._first_action = self.clock()
159
+ self.notebook.phone["first_action_at"] = round(self._first_action - self.started, 2)
160
+
161
+ def add_wait(self, seconds):
162
+ if seconds and seconds > 0 and self._first_action is not None:
163
+ self._waited += float(seconds)
164
+ self.notebook.phone["waited_s"] = round(self._waited, 2)
165
+
166
+ def budget_left(self):
167
+ kept = self.notebook.watched()
168
+ items = None if self.budget.items is None or self.budget.strict else max(0, self.budget.items - kept)
169
+ usd = (self.cap_usd - self.spent_usd() - self.reserve_usd()) if self.cap_usd is not None else float("inf")
170
+ return {"items": items,
171
+ "phone_seconds": round(max(0.0, self.budget.phone_seconds_cap - self.phone_seconds()), 2),
172
+ "usd": round(usd, 4) if usd != float("inf") else None}
173
+
174
+ def covered(self):
175
+ """Why the budget is used up ("items", "phone_time", "spend"), or None."""
176
+ kept = self.notebook.watched()
177
+ if self.budget.items and not self.budget.strict and kept >= self.budget.items:
178
+ return "items"
179
+ if self.phone_seconds() >= self.budget.phone_seconds_cap:
180
+ return "phone_time"
181
+ if self.cap_usd is not None and self.spent_usd() + self.reserve_usd() >= self.cap_usd:
182
+ return "spend"
183
+ return None
184
+
185
+ def advice(self):
186
+ covered = self.covered()
187
+ if covered == "items":
188
+ return "The items are covered: wrap up now with DONE."
189
+ if covered == "phone_time":
190
+ return "The phone time is used up: wrap up now with DONE."
191
+ if covered == "spend":
192
+ return "The spend cap is reached: wrap up now with DONE."
193
+ if self.cap_usd is not None and self.spent_usd() + self.reserve_usd() >= WRAP_UP_SHARE * self.cap_usd:
194
+ return "Spend is nearly at the cap: wrap up."
195
+ if self.budget.items and not self.budget.strict:
196
+ return "Wrap up when the items are covered."
197
+ return "Keep going until the time is used, then wrap up."
198
+
199
+ def final_reason(self):
200
+ """The frontier's reason to end the loop now without another model call, or None."""
201
+ covered = self.covered()
202
+ if covered is None:
203
+ self._covered_turns = 0
204
+ return self.stall_reason()
205
+ if covered in ("phone_time", "spend"):
206
+ return covered
207
+ self._covered_turns += 1
208
+ return covered if self._covered_turns > COVERED_GRACE_TURNS else None
209
+
210
+ def _work_signature(self):
211
+ book = self.notebook
212
+ with book.lock:
213
+ return (tuple(sorted((book.counters or {}).items())), len(book.facts),
214
+ tuple((step.get("id"), step.get("state")) for step in book.steps))
215
+
216
+ def _progress(self, screen):
217
+ """One turn's progress: a new item, note or step state resets the count, and so does a screen not seen since
218
+ the last of those (navigating); the same screens again with nothing new count toward STALL_TURNS."""
219
+ work = self._work_signature()
220
+ if work != self._work:
221
+ self._work, self._screens, self._stall_turns = work, {screen}, 0
222
+ elif screen not in self._screens:
223
+ self._screens.add(screen)
224
+ self._stall_turns = 0
225
+ else:
226
+ self._stall_turns += 1
227
+
228
+ def on_refused(self):
229
+ """An action was refused (a loop, a repeat, a gate): REFUSED_IN_A_ROW of them in a row end the run."""
230
+ self._refused_row += 1
231
+
232
+ def on_acted(self, noop=False):
233
+ """An action was taken. A LAUNCH_APP of the app already in front is a no-op: it keeps the refusal streak."""
234
+ if not noop:
235
+ self._refused_row = 0
236
+
237
+ def on_macro(self, op, reason, saved):
238
+ """A macro (WATCH_FEED, READ_EACH) returned: ``saved`` items, stopped for ``reason``."""
239
+ if saved or reason not in DRY_REASONS:
240
+ self._dry_macros = []
241
+ if saved:
242
+ self._refused_row = 0
243
+ return
244
+ self._dry_macros.append((op, reason))
245
+
246
+ def stall_reason(self):
247
+ """"stuck" (and ``stalled`` says why) once the run has stopped making progress; else None."""
248
+ if self.stalled is None:
249
+ if self._stall_turns >= STALL_TURNS:
250
+ self.stalled = f"nothing new in {self._stall_turns} turns"
251
+ elif self._refused_row >= REFUSED_IN_A_ROW:
252
+ self.stalled = f"{self._refused_row} actions in a row were refused"
253
+ elif len(self._dry_macros) >= DRY_MACROS:
254
+ op, reason = self._dry_macros[-1]
255
+ self.stalled = (f"{op} found nothing new {len(self._dry_macros)} times in a row "
256
+ f"({'the list ended' if reason == 'end' else reason.replace('_', ' ')})")
257
+ if self.stalled is None:
258
+ return None
259
+ return "list_end" if self._list_done() else "stuck"
260
+
261
+ def _list_done(self):
262
+ """The macro said the list ended (every row read) and something was saved: the run is complete, not stuck."""
263
+ return bool(self._dry_macros) and all(reason == "end" for _, reason in self._dry_macros[-DRY_MACROS:]) \
264
+ and bool(self.notebook.counters.get("kept"))
265
+
266
+ def _frame_id(self, frame):
267
+ if not frame or self.frames is None:
268
+ return None
269
+ try:
270
+ return self.frames.put(self.run_id, bytes(frame))
271
+ except Exception: # noqa: BLE001
272
+ return None
273
+
274
+ def current_step_id(self):
275
+ step = self.notebook.current_step()
276
+ return step["id"] if step else None
277
+
278
+ def add_item(self, item, *, frame=None):
279
+ """Add a captured item (dedupe and counters in the notebook); emits longform_item. Returns the stored Item."""
280
+ if item.step is None:
281
+ item.step = self.current_step_id()
282
+ if not item.app:
283
+ item.app = self.bundle
284
+ if not item.at:
285
+ item.at = round(self.clock() - self.started, 2)
286
+ item.frame_id = self._frame_id(frame) or item.frame_id
287
+ self.mark_action()
288
+ stored = self.notebook.add_item(item)
289
+ self._item_event(stored, "skipped" if stored.skip else "captured")
290
+ self.progress(self._phase if self._phase in ("watching", "reading") else "watching")
291
+ if stored.n % STORE_EVERY_ITEMS == 0:
292
+ self.save()
293
+ self._poke_digest()
294
+ return stored
295
+
296
+ def update_item(self, item_id, *, extract=None, skip=None, frame=None):
297
+ if extract is not None:
298
+ extract = dict(extract)
299
+ if extract.get("ad") is True and skip is None:
300
+ skip = "ad"
301
+ elif extract.get("relevant") is False and self.focus and skip is None:
302
+ skip = "off_topic"
303
+ frame_id = self._frame_id(frame)
304
+ stored = self.notebook.update_item(item_id, extract=extract, skip=skip, frame_id=frame_id)
305
+ if stored is None:
306
+ return None
307
+ self._item_event(stored, "skipped" if stored.skip else ("extracted" if stored.extract else "captured"))
308
+ self._poke_digest()
309
+ return stored
310
+
311
+ def extract_failed(self, item_id):
312
+ self.notebook.extract_failed(item_id)
313
+
314
+ def _item_event(self, item, status):
315
+ extract = item.extract or {}
316
+ self.emit({"event": "longform_item", "itemId": item.id, "n": item.n, "kind": item.kind, "status": status,
317
+ "skip": item.skip, "source": dict(item.source), "summary": item.summary() or None,
318
+ "tags": list(extract.get("tags") or [])[:8], "relevant": extract.get("relevant"),
319
+ "frameId": item.frame_id, "step": item.step})
320
+
321
+ def add_fact(self, text, item_ids=(), kind="fact", source="model"):
322
+ fact = self.notebook.add_fact(text, item_ids, kind=kind, source=source, turn=self.turn)
323
+ if fact is None:
324
+ return None
325
+ if kind != "step_result":
326
+ self.emit({"event": "longform_finding", "findingId": fact.id, "text": fact.text,
327
+ "itemIds": list(fact.item_ids), "kind": "insight" if kind == "insight" else
328
+ "theme" if kind == "theme" else "fact"})
329
+ step = self.current_step_id()
330
+ if step is not None and source == "model":
331
+ self._step_notes.setdefault(step, []).append(fact.text)
332
+ self._poke_digest()
333
+ return fact
334
+
335
+ def progress(self, phase, *, label=None, force=False):
336
+ """longform_progress, at most 2 a second (a change of phase always goes out)."""
337
+ now = self.clock()
338
+ c = self.notebook.counters
339
+ tenth = c["kept"] + c["off_topic"]
340
+ changed = phase != self._phase or (tenth // 10 != self._last_tenth // 10)
341
+ self._phase = phase
342
+ if not force and not changed and now - self._last_progress < PROGRESS_INTERVAL:
343
+ return
344
+ self._last_progress = now
345
+ self._last_tenth = tenth
346
+ target = None if self.budget.strict else self.budget.items
347
+ left = max(0.0, self.budget.phone_seconds_cap - self.phone_seconds())
348
+ self.emit({"event": "longform_progress", "phase": phase, "stepId": self.current_step_id(),
349
+ "items": {"seen": c["seen"], "kept": c["kept"], "ads": c["ads"], "repeats": c["repeats"],
350
+ "offTopic": c["off_topic"], "watched": c["kept"] + c["off_topic"], "target": target},
351
+ "phoneSeconds": round(self.phone_seconds(), 1), "phoneSecondsLeft": round(left, 1),
352
+ "costUsd": round(self.spent_usd(), 4), "label": label})
353
+
354
+ def extract_pool(self):
355
+ """The run's extraction pool (feed's extract.ExtractPool), made on first use."""
356
+ with self.lock:
357
+ if self._pool is None:
358
+ from .extract import ExtractPool
359
+ self._pool = ExtractPool(self, self.models.extract if self.models is not None else None)
360
+ return self._pool
361
+
362
+ def drain(self, timeout=20.0):
363
+ """Wait for the extraction pool (at most ``timeout`` s); returns the items still pending."""
364
+ pool = self._pool
365
+ if pool is None:
366
+ return 0
367
+ try:
368
+ return int(pool.drain(timeout=timeout) or 0)
369
+ except Exception: # noqa: BLE001
370
+ return 0
371
+
372
+ def _poke_digest(self):
373
+ if self.models is None or getattr(self.models, "digest", None) is None or self.closed:
374
+ return
375
+ with self.lock:
376
+ if self._digester is None:
377
+ from .digest import Digester
378
+ self._digester = Digester(self)
379
+ self._digester.poke()
380
+
381
+ def on_screen(self, bundle):
382
+ """The screen is in ``bundle`` (an item read without its own app takes it)."""
383
+ if bundle:
384
+ if bundle != getattr(self, "bundle", None):
385
+ self._subrun_for(bundle)
386
+ self.bundle = bundle
387
+
388
+ def _subrun_for(self, bundle):
389
+ run = getattr(self, "run", None)
390
+ apps = {a.get("bundleId"): a.get("name") for a in (self.plan or {}).get("apps") or () if isinstance(a, dict)}
391
+ start = getattr(run, "subrun_start", None)
392
+ if not callable(start) or len(apps) < 2 or bundle not in apps:
393
+ return
394
+ self._finish_subrun()
395
+ try:
396
+ self._subrun = start(app=apps[bundle] or bundle, bundle_id=bundle, title=f"{apps[bundle] or bundle}")
397
+ except Exception: # noqa: BLE001 -- rows are a view; the run goes on without them
398
+ self._subrun = None
399
+
400
+ def _finish_subrun(self, outcome="done"):
401
+ run, open_ = getattr(self, "run", None), getattr(self, "_subrun", None)
402
+ if open_ is None or not callable(getattr(run, "subrun_finish", None)):
403
+ return
404
+ counts = {"read": self.notebook.counters.get("kept", 0)}
405
+ try:
406
+ run.subrun_finish(open_, outcome=outcome, counts=counts)
407
+ except Exception: # noqa: BLE001
408
+ pass
409
+ self._subrun = None
410
+
411
+ def on_turn(self, turn, app_name, history=None, bundle=None, screen=None):
412
+ """The frontier starts turn ``turn`` in ``app_name``: compaction of history that left the window, and the
413
+ turn's progress (``screen``: the screen's content fingerprint)."""
414
+ self.on_screen(bundle)
415
+ self._progress((bundle, screen))
416
+ self.turn = turn
417
+ if app_name:
418
+ self.apps_by_turn[turn] = app_name
419
+ if history is not None:
420
+ self.compactor.fold(history, self.notebook, self.apps_by_turn)
421
+ self.progress(self._phase if self._phase != "done" else "navigating")
422
+
423
+ def on_reply(self, step_id, turn):
424
+ """The reply's ``step``: moving past a step marks it done (steps jumped over: skipped) and the new one now."""
425
+ if not isinstance(step_id, int) or isinstance(step_id, bool):
426
+ return []
427
+ target = self.notebook.step(step_id)
428
+ current = self.notebook.current_step()
429
+ if target is None or (current is not None and current["id"] == step_id):
430
+ return []
431
+ changes = []
432
+ with self.notebook.lock:
433
+ if current is not None:
434
+ if step_id > current["id"]:
435
+ changes.append(self._set_step(current, "done", turn))
436
+ for step in self.notebook.steps:
437
+ if current["id"] < step["id"] < step_id and step["state"] == "next":
438
+ changes.append(self._set_step(step, "skipped", turn))
439
+ else:
440
+ changes.append(self._set_step(current, "next", turn))
441
+ changes.append(self._set_step(target, "now", turn))
442
+ self.save()
443
+ return changes
444
+
445
+ def _set_step(self, step, state, turn):
446
+ step["state"] = state
447
+ if state == "now":
448
+ step["started_turn"] = turn
449
+ elif state in ("done", "skipped", "failed"):
450
+ step["ended_turn"] = turn
451
+ notes = self._step_notes.get(step["id"]) or []
452
+ if notes and state == "done":
453
+ result = cut("; ".join(notes), 300)
454
+ step["result"] = result
455
+ self.notebook.add_fact(result, (), kind="step_result", source="step", turn=turn)
456
+ self.notebook.revision += 1
457
+ self.emit({"event": "longform_step", "stepId": step["id"], "state": state, "result": step.get("result")})
458
+ return step["id"], state
459
+
460
+ def finish_steps(self, status):
461
+ """The loop ended with ``status``: the step in progress and the ones left are settled."""
462
+ steps = self.notebook.steps
463
+ writeup = steps[-1] if steps and steps[-1].get("app") is None and len(steps) > 1 else None
464
+ for step in steps:
465
+ if step is writeup or step["state"] in ("done", "skipped", "failed"):
466
+ continue
467
+ if status == "completed":
468
+ self._set_step(step, "done", self.turn)
469
+ elif step["state"] == "now":
470
+ self._set_step(step, "failed" if status in ("error", "blocked") else "skipped", self.turn)
471
+ else:
472
+ self._set_step(step, "skipped", self.turn)
473
+ if writeup is not None and writeup["state"] == "next":
474
+ self._set_step(writeup, "now", self.turn)
475
+
476
+ def finish_writeup(self, ok=True):
477
+ steps = self.notebook.steps
478
+ if steps and steps[-1].get("app") is None and steps[-1]["state"] == "now":
479
+ self._set_step(steps[-1], "done" if ok else "failed", self.turn)
480
+
481
+ def checkpoint(self):
482
+ """The checkpoint's ``longform`` key (DESIGN §4.3)."""
483
+ seconds = self.phone_seconds()
484
+ self.notebook.phone["cumulative_seconds"] = seconds
485
+ return {"planId": self.plan.get("id"), "step": self.current_step_id(),
486
+ "counters": dict(self.notebook.counters), "phoneSeconds": seconds}
487
+
488
+ def capture_contract(self, contract, *, final=False):
489
+ """Persist what the task contract can show, separately from plan-step progress."""
490
+ from . import receipts
491
+ entry = receipts.snapshot(self.notebook, contract, final=final)
492
+ if entry is not None:
493
+ receipts.put(self.notebook, entry)
494
+ self.save()
495
+ return entry
496
+
497
+ def release_phone(self):
498
+ """The loop is done with the phone: phone_released, the awake lease let go, the notebook saved."""
499
+ with self.lock:
500
+ if self._released is not None:
501
+ return False
502
+ self._released = self.clock()
503
+ self._finish_subrun()
504
+ self.notebook.phone["released_at"] = round(self._released - self.started, 2)
505
+ self.notebook.phone["cumulative_seconds"] = self.phone_seconds()
506
+ if self.awake is not None:
507
+ try:
508
+ self.awake.release(self.run_id)
509
+ except Exception: # noqa: BLE001
510
+ pass
511
+ self.emit({"event": "phone_released", "phoneSeconds": round(self.phone_seconds(), 1)})
512
+ self.progress("writing", force=True)
513
+ self.save()
514
+ return True
515
+
516
+ @property
517
+ def released(self):
518
+ return self._released is not None
519
+
520
+ def save(self, report=None, markdown=None):
521
+ """The notebook (and the report) to the journal, on the store's writer thread."""
522
+ self.notebook.phone["cumulative_seconds"] = self.phone_seconds()
523
+ if self.store is None or self.journal is None:
524
+ return
525
+ try:
526
+ self.store.submit(self.journal, self.run_id, self.notebook, report=report, markdown=markdown)
527
+ except Exception: # noqa: BLE001 -- a journal that can't take it never breaks the run
528
+ pass
529
+
530
+ def close(self):
531
+ """Stop the digest and the pool, write the notebook, and leave the registry."""
532
+ self.closed = True
533
+ digester, self._digester = self._digester, None
534
+ if digester is not None:
535
+ digester.stop()
536
+ pool = self._pool
537
+ if pool is not None:
538
+ try:
539
+ pool.close()
540
+ except Exception: # noqa: BLE001
541
+ pass
542
+ for client in (getattr(self.models, "digest", None), getattr(self.models, "synth", None),
543
+ getattr(self.models, "synth_fallback", None)):
544
+ close = getattr(client, "close", None)
545
+ if callable(close):
546
+ close()
547
+ self.save()
548
+ unbind(self.run_id)
549
+
550
+
551
+ _pickups = {} # run id -> what the long-run pick-up hook loaded for it (register.pickup)
552
+
553
+
554
+ def _elapsed(value):
555
+ if type(value) not in (int, float) or value < 0:
556
+ return None
557
+ try:
558
+ seconds = float(value)
559
+ except OverflowError:
560
+ return None
561
+ return seconds if math.isfinite(seconds) else None
562
+
563
+
564
+ def _pickup_phone_seconds(phone, picked, cap):
565
+ """Restore trusted elapsed time; a damaged source consumes the remaining phone budget."""
566
+ checkpoint = _elapsed(picked["phone_seconds"]) if "phone_seconds" in picked else None
567
+ cumulative = _elapsed(phone["cumulative_seconds"]) if "cumulative_seconds" in phone else None
568
+ invalid = ("phone_seconds" in picked and checkpoint is None) or (
569
+ "cumulative_seconds" in phone and cumulative is None)
570
+ legacy = 0.0
571
+ if "cumulative_seconds" not in phone:
572
+ first = phone.get("first_action_at")
573
+ if first is not None:
574
+ end = phone.get("released_at")
575
+ if end is None:
576
+ end = phone.get("last_at")
577
+ if end is None:
578
+ # A crash has no segment end; its checkpoint already excludes approval waits.
579
+ invalid |= checkpoint is None or _elapsed(first) is None or _elapsed(phone.get("waited_s", 0)) is None
580
+ else:
581
+ first, end, waited = _elapsed(first), _elapsed(end), _elapsed(phone.get("waited_s", 0))
582
+ invalid |= first is None or end is None or waited is None or (
583
+ first is not None and end is not None and waited is not None and end - first < waited)
584
+ if not invalid:
585
+ legacy = end - first - waited
586
+ elif (phone.get("released_at") is not None or phone.get("last_at") is not None
587
+ or _elapsed(phone.get("waited_s", 0)) is None or phone.get("waited_s", 0) > 0):
588
+ invalid = True
589
+ return cap if invalid else max(legacy, cumulative or 0.0, checkpoint or 0.0)
590
+
591
+
592
+ def start(*, run_id, goal, plan, budget, origin, nav_client, key, inference, ledger, cap_usd, emit, frames=None,
593
+ journal=None, awake=None, model_factory=None, spent_offset=0.0, rules=(), earlier=(), run=None):
594
+ """A bound LongSession for one run: the models routed (models.model_set), the conversation's standing rules
595
+ (``rules``, "only people in Brooklyn") as preferences next to the request, a picked-up run's notebook and budget,
596
+ the awake lease taken, and the ``longform_plan`` event."""
597
+ from ..harness_api import INTERACTIVE
598
+ from .budget import Budget
599
+ from .models import model_set
600
+ from .notebook import Notebook
601
+ from .store import writer
602
+ budget = budget if hasattr(budget, "phone_seconds_cap") else Budget.from_dict(budget)
603
+ kept = list(dict.fromkeys(" ".join(str(rule).split())[:MAX_RULE_CHARS] for rule in rules or ()
604
+ if str(rule).strip()))[:MAX_RULES]
605
+ models = model_set(nav_client, key, emit=inference, items_hint=budget.items or 60, kind=plan.get("kind", "feed"),
606
+ factory=model_factory)
607
+ picked = _pickups.pop(run_id, None)
608
+ notebook, phone_offset = None, 0.0
609
+ if picked is not None and picked.get("notebook"):
610
+ notebook = Notebook.from_dict(picked["notebook"])
611
+ notebook.run_id = run_id
612
+ phone = picked["notebook"].get("phone") or {}
613
+ phone_offset = _pickup_phone_seconds(phone, picked, budget.phone_seconds_cap)
614
+ # Segment timestamps belong to the source run. Keep only its cumulative elapsed budget in this run.
615
+ notebook.phone = {"first_action_at": None, "released_at": None, "waited_s": 0.0,
616
+ "cumulative_seconds": round(phone_offset, 2)}
617
+ spent_offset = max(float(spent_offset or 0.0), float(picked.get("cost_usd") or 0.0))
618
+ session = LongSession(run_id, goal, plan, budget,
619
+ models=models, interactive=origin in INTERACTIVE,
620
+ focus=plan.get("focus"), preferences=kept, ledger=ledger, cap_usd=cap_usd, emit=emit,
621
+ frames=frames, store=writer, journal=journal, notebook=notebook,
622
+ spent_offset=spent_offset, phone_offset=phone_offset, awake=awake)
623
+ session.notebook.phone["cumulative_seconds"] = session.phone_seconds()
624
+ session.notebook.plan = {k: v for k, v in plan.items() if not str(k).startswith("_")}
625
+ session.run = run # the Runtime's Run: sub-run rows per app, and the inbox's proposals (orchestrate)
626
+ if earlier:
627
+ # What an earlier long task in this conversation already showed is skipped ("12 new since last time").
628
+ session.notebook.remember_earlier(earlier)
629
+ if picked is not None:
630
+ session.picked_up_from = picked.get("source")
631
+ session.turns_used = int(picked.get("turns") or 0)
632
+ bind(session)
633
+ if awake is not None:
634
+ try:
635
+ awake.acquire(run_id)
636
+ except Exception: # noqa: BLE001
637
+ pass
638
+ session.emit({"event": "longform_plan", "plan": session.notebook.plan, "revision": 0})
639
+ current = session.notebook.current_step()
640
+ if current is not None:
641
+ session.emit({"event": "longform_step", "stepId": current["id"], "state": "now", "result": None})
642
+ session.progress("navigating", force=True)
643
+ session.save()
644
+ return session