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,813 @@
1
+ """Agent events as the steps a person reads: shared by the terminal UI and `mobster run` on a terminal.
2
+
3
+ The agent emits one event per boundary it crosses (observation, decision, action
4
+ check, dispatch, settle). A reader wants one line per step: what Mobster did, to
5
+ what, and whether the screen answered. ``Narrator`` folds the event stream into
6
+ those steps without changing or dropping any event; ``--json`` output and the
7
+ journal keep the raw events.
8
+
9
+ The wording follows the Mac app's presentation (dashboard/src/lib/run-presentation.ts)
10
+ so a task reads the same in both. Nothing here imports a UI library.
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+
15
+ # The Mac app's labels (dashboard/src/lib/api.ts statusLabels), so both products agree; a test keeps every shared
16
+ # key equal. The last few are statuses only the terminal shows.
17
+ STATUS_LABELS = {
18
+ "queued": "Queued", "running": "Running", "stopped": "Stopped", "interrupted": "Interrupted",
19
+ "user_condition_met": "Stop condition met", "needs_clarification": "Needs clarification",
20
+ "error": "Failed", "blocked": "Needs attention", "completed_unverified": "Take a look",
21
+ "expected_text_visible": "Expected text visible", "demo_complete": "Demo complete",
22
+ "inconsistent_completion": "Take a look", "completion_not_confirmed": "Take a look",
23
+ "needs_text_helper": "Text entry unavailable", "no_progress": "Needs attention",
24
+ "max_steps": "Step limit reached", "timeout": "Time limit reached", "approval_denied": "Declined",
25
+ "approval_timeout": "Approval expired", "spend_cap": "Spending limit reached",
26
+ # Smart's proven finish: every item of the task's contract was shown done (engine-contract.md).
27
+ "completed": "Done",
28
+ "preview": "Preview", "duplicate_effect_blocked": "Duplicate refused",
29
+ "duplicate_text_blocked": "Duplicate refused",
30
+ "invalid_action": "Stopped before acting", "invalid_target": "Stopped before acting",
31
+ "expected_text_missing": "Expected text missing",
32
+ }
33
+
34
+ STATUS_TONES = {
35
+ "completed": "success", "completed_unverified": "success", "expected_text_visible": "success",
36
+ "user_condition_met": "neutral",
37
+ "preview": "neutral", "stopped": "neutral", "approval_denied": "neutral", "demo_complete": "review",
38
+ "inconsistent_completion": "review", "completion_not_confirmed": "review",
39
+ "error": "error", "interrupted": "error", "approval_timeout": "warning", "spend_cap": "warning",
40
+ "max_steps": "warning", "timeout": "warning", "blocked": "error", "no_progress": "error",
41
+ "needs_text_helper": "error", "invalid_action": "error", "invalid_target": "error",
42
+ "expected_text_missing": "error", "needs_clarification": "error",
43
+ "duplicate_effect_blocked": "neutral", "duplicate_text_blocked": "neutral",
44
+ }
45
+
46
+ DONE, FOUND, TAKE_A_LOOK, COULDNT, STOPPED_SAFELY, DECLINED, STOPPED = (
47
+ "Done", "Here's what Mobster found", "Take a look", "Couldn't finish", "Stopped safely", "You declined", "Stopped")
48
+ TAKE_A_LOOK_LINE = "Mobster couldn't confirm all of this on the screen. Look at your iPhone."
49
+
50
+
51
+ def where_to_look(summary):
52
+ summary = summary if isinstance(summary, dict) else {}
53
+ if summary.get("deviceKind") == "simulator":
54
+ return "the simulator"
55
+ name = " ".join(str(summary.get("deviceName") or "").split())[:60]
56
+ return name or "your iPhone"
57
+
58
+
59
+ def take_a_look_line(summary=None):
60
+ return f"Mobster couldn't confirm all of this on the screen. Look at {where_to_look(summary)}."
61
+ UNPROVEN = {"completed_unverified", "expected_text_visible", "inconsistent_completion", "completion_not_confirmed",
62
+ "demo_complete"}
63
+
64
+
65
+ def result_head(status, summary=None):
66
+ summary = summary if isinstance(summary, dict) else {}
67
+ outcome = summary.get("outcome")
68
+ found = summary.get("data") is not None and summary.get("data_status") in (None, "extracted")
69
+ if status == "completed" or (status in UNPROVEN and (outcome == "done" or (outcome is None and
70
+ summary.get("proof")))):
71
+ return (FOUND if found else DONE), "success"
72
+ if status in UNPROVEN or outcome == "check":
73
+ return TAKE_A_LOOK, "review"
74
+ if status == "preview":
75
+ return "Preview", "neutral"
76
+ if status == "approval_denied":
77
+ return DECLINED, "neutral"
78
+ if status in ("stopped", "user_condition_met"):
79
+ return STOPPED, "neutral"
80
+ if status in ("approval_timeout", "spend_cap", "duplicate_effect_blocked", "duplicate_text_blocked",
81
+ "max_steps", "timeout"):
82
+ return STOPPED_SAFELY, "warning" if status in ("spend_cap", "max_steps", "timeout") else "neutral"
83
+ return COULDNT, "error"
84
+
85
+
86
+ def proof_line(summary, apps=None):
87
+ """"Seen on screen: “Dark: On” in Display & Brightness" from the result's first proof, or ""."""
88
+ proof = (summary or {}).get("proof") if isinstance(summary, dict) else None
89
+ if not proof or not isinstance(proof, list) or not isinstance(proof[0], dict):
90
+ return ""
91
+ item = proof[0]
92
+ quote = str(item.get("quote") or "").strip()
93
+ if not quote:
94
+ return ""
95
+ where = str(item.get("screen") or "").strip() or app_name(item.get("app"), apps)
96
+ return f"Seen on screen: “{quote}”" + (f" in {where}" if where else "")
97
+
98
+
99
+ def hero_line(summary):
100
+ """The result's answer, when there is one: what Mobster found, or what it did in one sentence."""
101
+ summary = summary if isinstance(summary, dict) else {}
102
+ answer = summary.get("answer")
103
+ if isinstance(answer, str) and answer.strip():
104
+ return answer.strip()
105
+ data = summary.get("data")
106
+ if isinstance(data, str) and data.strip():
107
+ return data.strip()
108
+ return ""
109
+
110
+
111
+ def cents(nanodollars):
112
+ """What a task cost, as MESSAGING's facts say it: 2¢, under 1¢, $1.20."""
113
+ dollars = (nanodollars or 0) / 1e9
114
+ if dollars >= 1:
115
+ return f"${dollars:.2f}"
116
+ value = dollars * 100
117
+ if value < 1:
118
+ return "under 1¢"
119
+ return f"{value:.0f}¢"
120
+
121
+
122
+ def facts(actions, elapsed_ms, cost_nanodollars=None, priced=False, plan=False):
123
+ """"3 steps, 6.2 s, 2¢"; ``plan``: every call ran on the person's plan, so no cents: "on your plan"."""
124
+ out = [f"{actions} step{'s' if actions != 1 else ''}"]
125
+ if elapsed_ms:
126
+ out.append(seconds(elapsed_ms))
127
+ if plan and not cost_nanodollars:
128
+ out.append("on your plan")
129
+ elif priced:
130
+ out.append(cents(cost_nanodollars))
131
+ return ", ".join(out)
132
+
133
+ TERMINAL_MESSAGES = {
134
+ "approval_denied": "You declined the action, so Mobster did not take it. Earlier steps may have completed.",
135
+ "approval_timeout": "Nobody answered the approval request in time, so Mobster did not take the action.",
136
+ "stopped": "You stopped this task.",
137
+ "user_condition_met": "Stopped at your requested condition.",
138
+ "needs_clarification": "Mobster needs clarification before taking further action. "
139
+ "Edit the instruction to make the next step clear.",
140
+ }
141
+
142
+ ANSWER_FAILURES = {
143
+ "not_extracted": "The requested answer was not produced. The task stopped before result extraction.",
144
+ "no_observed_evidence": "The app did not expose enough evidence to return the requested answer.",
145
+ "insufficient_evidence": "The app did not expose enough evidence to return the requested answer.",
146
+ "unsupported_answer": "The extracted answer could not be verified against the observed app evidence. "
147
+ "No answer was returned.",
148
+ "verification_timeout": "Answer verification timed out. No verified answer was returned.",
149
+ "verification_failed": "Answer verification could not be completed. No verified answer was returned.",
150
+ "extraction_failed": "Result extraction failed. The requested answer was not produced.",
151
+ "extraction_timeout": "Result extraction timed out. The requested answer was not produced.",
152
+ "helper_unavailable": "No helper model is configured, so the requested answer was not produced.",
153
+ "helper_budget_exhausted": "The helper budget ran out before the requested answer was produced.",
154
+ }
155
+
156
+ OPERATIONS = {
157
+ "TAP": ("Tap", "Tapped"), "DOUBLE_TAP": ("Double-tap", "Double-tapped"),
158
+ "LONG_PRESS": ("Press and hold", "Pressed and held"),
159
+ "TYPE": ("Type into", "Typed into"), "TYPE_SUBMIT": ("Type and submit in", "Typed and submitted in"),
160
+ "SUBMIT": ("Submit", "Submitted"),
161
+ # A swipe up moves the content up: the reader sees the page scroll down.
162
+ "SWIPE_UP": ("Scroll down", "Scrolled down"), "SWIPE_DOWN": ("Scroll up", "Scrolled up"),
163
+ "SWIPE_LEFT": ("Swipe left", "Swiped left"), "SWIPE_RIGHT": ("Swipe right", "Swiped right"),
164
+ "BACK": ("Go back", "Went back"), "HOME": ("Go home", "Went home"),
165
+ "LAUNCH_APP": ("Open", "Opened"), "OPEN_URL": ("Open the address", "Opened the address"),
166
+ "INCREMENT": ("Increase", "Increased"), "DECREMENT": ("Decrease", "Decreased"),
167
+ "VOLUME_UP": ("Volume up", "Pressed volume up"), "VOLUME_DOWN": ("Volume down", "Pressed volume down"),
168
+ "WAIT": ("Waiting for the screen", "Waited for the screen"),
169
+ "DONE": ("Check the task is done", "Checked the task is done"),
170
+ # A task as one of your profiles (accounts/screen.py); a run journaled before the rename names CHECK_ACCOUNT.
171
+ "CHECK_PROFILE": ("Check the profile", "Checked the profile"),
172
+ "CHECK_ACCOUNT": ("Check the profile", "Checked the profile"),
173
+ "BLOCKED": ("Looking for a way forward", "Looking for a way forward"),
174
+ }
175
+
176
+ # Events a reader never needs; the raw stream (--json, the journal) keeps them.
177
+ QUIET = {"run_started", "latency_trace", "inference_started", "inference_finished", "stop_requested",
178
+ "decision_memo_hit", "decision_predicted", "decision_first_screen_predicted", "decision_speculation_used",
179
+ "decision_speculation_discarded", "decision_input_tightened", "refresh_skipped_fresh",
180
+ "refresh_skipped_pixels_still", "refresh_target_stable", "completion_reread_skipped_pixels_still",
181
+ "answer_signals", "stale_decision", "text_selected", "result"}
182
+
183
+ NOTES = {
184
+ "unclear_bypassed": ("warning", "Bypass: acted although the action check was unclear"),
185
+ "unclear_put_to_user": ("neutral", "The action check was unclear, so you are asked"),
186
+ "effect_duplicate_refused": ("warning", "Refused to repeat an action that already took effect"),
187
+ "duplicate_text_refused": ("warning", "Refused to type the same text twice"),
188
+ "completion_continued": ("neutral", "Not finished yet: continuing"),
189
+ "demotion_lifted": ("neutral", "Allowed a held-back action"),
190
+ "replay_abandoned": ("neutral", "The screen changed: stopped replaying a recorded run"),
191
+ "plan_compiled": ("neutral", "Planned the steps across apps"),
192
+ "loop_compiled": ("neutral", "Compiled the repeated action into a loop"),
193
+ "milestones_compiled": ("neutral", "Split the request into milestones"),
194
+ "page_probe_answered": ("neutral", "Found the answer in the page text"),
195
+ "visual_answer": ("neutral", "Answered from the screen image"),
196
+ "completion_by_route": ("neutral", "Reached the requested page"),
197
+ "route_decision": ("neutral", "Followed a known route"),
198
+ "helper": ("neutral", "Asked the helper model for another route"),
199
+ }
200
+
201
+
202
+ # The phone guard's and the skills' events: one plain line each, never a digit of a passcode or a code.
203
+ GUARD_NOTES = {
204
+ ("unlock_finished", True): ("neutral", "Unlocked the iPhone"),
205
+ ("unlock_finished", False): ("warning", "The saved passcode didn't unlock the iPhone"),
206
+ ("handoff_waiting", "face_id"): ("neutral", "Waiting for you: approve Face ID on your iPhone"),
207
+ ("handoff_waiting", "apple_confirmation"): ("neutral", "Waiting for you: confirm on your iPhone"),
208
+ # Look at your iPhone: a task that started on a locked iPhone waits for the unlock.
209
+ ("handoff_waiting", "lock_screen"): ("warning", "Look at your iPhone: unlock it and Mobster starts"),
210
+ ("handoff_waiting", "passcode_keypad"): ("warning", "Look at your iPhone: unlock it and Mobster starts"),
211
+ ("handoff_finished", True): ("neutral", "Done on your iPhone: continuing"),
212
+ ("handoff_finished", False): ("warning", "Nothing was confirmed on your iPhone"),
213
+ ("handoff_finished", "lock_screen", True): ("neutral", "Your iPhone is unlocked: starting"),
214
+ ("handoff_finished", "passcode_keypad", True): ("neutral", "Your iPhone is unlocked: starting"),
215
+ ("handoff_finished", "lock_screen", False): None, # the run's own stop says it ("Still locked, so …")
216
+ ("handoff_finished", "passcode_keypad", False): None,
217
+ # The phone locked in the middle of a task a person started: it waits for them, then carries on from there.
218
+ ("handoff_waiting", "lock_screen", "midrun"): ("warning", "Your iPhone locked: unlock it and Mobster carries on"),
219
+ ("handoff_waiting", "passcode_keypad", "midrun"): ("warning",
220
+ "Your iPhone locked: unlock it and Mobster carries on"),
221
+ ("handoff_finished", "lock_screen", True, "midrun"): ("neutral", "Your iPhone is unlocked: carrying on"),
222
+ ("handoff_finished", "passcode_keypad", True, "midrun"): ("neutral", "Your iPhone is unlocked: carrying on"),
223
+ # The cable came out, or the Wi-Fi link went down, under a task a person started: it waits for the phone to return.
224
+ ("handoff_waiting", "unplugged"): ("warning", "Your iPhone isn't plugged in: plug it in and Mobster starts"),
225
+ ("handoff_waiting", "unplugged", "midrun"): ("warning", "Your iPhone was unplugged: plug it back in and Mobster "
226
+ "carries on"),
227
+ ("handoff_waiting", "wifi_lost"): ("warning", "Your iPhone is out of reach over Wi-Fi: bring it back or plug it "
228
+ "in and Mobster starts"),
229
+ ("handoff_waiting", "wifi_lost", "midrun"): ("warning", "Your iPhone went out of reach over Wi-Fi: bring it back "
230
+ "or plug it in and Mobster carries on"),
231
+ ("handoff_finished", "unplugged", True): ("neutral", "Your iPhone is back: starting"),
232
+ ("handoff_finished", "unplugged", True, "midrun"): ("neutral", "Your iPhone is back: carrying on"),
233
+ ("handoff_finished", "wifi_lost", True): ("neutral", "Your iPhone is back: starting"),
234
+ ("handoff_finished", "wifi_lost", True, "midrun"): ("neutral", "Your iPhone is back: carrying on"),
235
+ ("handoff_finished", "unplugged", False): None,
236
+ ("handoff_finished", "wifi_lost", False): None,
237
+ ("skill_finished", "USE_CODE"): ("neutral", "Entered the verification code"),
238
+ # USE_CODE looked before the code arrived (skills/codes.await_code): it watches for it,
239
+ # then goes on or says why not.
240
+ ("code_waiting", None): ("neutral", "Waiting for the sign-in code to arrive"),
241
+ ("code_wait_finished", True): ("neutral", "The sign-in code arrived"),
242
+ ("skill_finished", "READ_NOTIFICATIONS"): ("neutral", "Read the notifications"),
243
+ ("frontier_ax_only", None): ("warning", "The iPhone's screenshots are timing out: reading its screen without them "
244
+ "for a few steps"),
245
+ }
246
+
247
+
248
+ def guard_note(event):
249
+ """(tone, text) for a phone-guard or skill event, or None."""
250
+ kind = event.get("event")
251
+ if kind == "unlock_finished":
252
+ return GUARD_NOTES[(kind, event.get("ok") is True)]
253
+ midrun = event.get("midrun") is True
254
+ if kind == "handoff_waiting":
255
+ if midrun and (kind, event.get("kind"), "midrun") in GUARD_NOTES:
256
+ return GUARD_NOTES[(kind, event.get("kind"), "midrun")]
257
+ return GUARD_NOTES.get((kind, event.get("kind")))
258
+ if kind == "handoff_finished":
259
+ ok = event.get("ok") is True
260
+ if midrun and (kind, event.get("kind"), ok, "midrun") in GUARD_NOTES:
261
+ return GUARD_NOTES[(kind, event.get("kind"), ok, "midrun")]
262
+ if (kind, event.get("kind"), ok) in GUARD_NOTES:
263
+ return GUARD_NOTES[(kind, event.get("kind"), ok)]
264
+ return GUARD_NOTES[(kind, ok)]
265
+ if kind == "skill_finished" and event.get("ok") is True:
266
+ return GUARD_NOTES.get((kind, event.get("op")))
267
+ if kind == "frontier_ax_only":
268
+ return GUARD_NOTES[(kind, None)]
269
+ if kind == "code_waiting":
270
+ return GUARD_NOTES[(kind, None)]
271
+ if kind == "code_wait_finished":
272
+ return GUARD_NOTES.get((kind, event.get("ok") is True))
273
+ return None
274
+
275
+
276
+ ONCE = {"route_decision", "completion_continued", "replay_abandoned", "helper"}
277
+ STALLS = {"WAIT", "BLOCKED"}
278
+
279
+
280
+ def status_label(status):
281
+ return STATUS_LABELS.get(status) or str(status or "").replace("_", " ").capitalize()
282
+
283
+
284
+ def status_tone(status):
285
+ return STATUS_TONES.get(status, "neutral")
286
+
287
+
288
+ STATUS_GLYPHS = {"success": "✓", "error": "✗", "warning": "!", "review": "◆"}
289
+
290
+
291
+ def status_glyph(tone, default="■"):
292
+ return STATUS_GLYPHS.get(tone, default)
293
+
294
+
295
+ def completion_message(status, reason="", data_status=None, summary=None):
296
+ """The sentence under a finished run's status (dashboard completionMessage)."""
297
+ reason = reason if isinstance(reason, str) else ""
298
+ if status == "needs_clarification" and "exact effect could not be established" in reason:
299
+ return ("The proposed action could not be checked safely and was not dispatched. "
300
+ "Earlier actions may have completed.")
301
+ if status in TERMINAL_MESSAGES:
302
+ return TERMINAL_MESSAGES[status]
303
+ if status in {"inconsistent_completion", "completion_not_confirmed"}:
304
+ return (ANSWER_FAILURES.get(data_status)
305
+ or "Mobster could not confidently confirm completion. Review the final screen.")
306
+ if status in {"blocked", "no_progress"} and (not reason or reason == "No supported progress"):
307
+ return "Nothing on the screen moved the task forward, so Mobster stopped without acting further. " \
308
+ "Look at the phone, or add detail to the task."
309
+ if status in {"completed_unverified", "expected_text_visible", "demo_complete"} and (
310
+ not reason or reason.startswith(("Model agreement only", "Text assertion passed"))):
311
+ # The head (Done with its proof, or Take a look) already says it; the agent's internal wording stays in
312
+ # the raw events.
313
+ return take_a_look_line(summary) if status != "expected_text_visible" else ""
314
+ return reason
315
+
316
+
317
+ def operation_words(operation):
318
+ """(in progress, done) phrases for an operation."""
319
+ operation = str(operation or "").upper()
320
+ if operation in OPERATIONS:
321
+ return OPERATIONS[operation]
322
+ word = operation.replace("_", " ").capitalize() or "Act"
323
+ return word, word
324
+
325
+
326
+ # The Mac app's words for the two answers (dashboard approval-prompt.tsx, approvalVerbs), so the terminal names the
327
+ # same buttons the app shows. An act this doesn't know keeps Approve and Decline.
328
+ _SENT, _POSTED = ("Send", "Don't send"), ("Post", "Don't post")
329
+ ACTS = {
330
+ "send_message": _SENT, "send": _SENT, "reply": _SENT, "send message": _SENT,
331
+ "post": _POSTED, "comment": _POSTED, "publish": ("Publish", "Don't publish"), "tweet": _POSTED,
332
+ "pay": ("Pay", "Don't pay"), "transfer": ("Transfer", "Don't transfer"),
333
+ "request_money": ("Request", "Don't request"), "donate": ("Donate", "Don't donate"), "tip": ("Tip", "Don't tip"),
334
+ "order": ("Place order", "Don't order"), "place order": ("Place order", "Don't order"), "buy": ("Buy", "Don't buy"),
335
+ "purchase": ("Buy", "Don't buy"), "checkout": ("Check out", "Don't check out"),
336
+ "book": ("Book", "Don't book"), "reserve": ("Reserve", "Don't reserve"),
337
+ "delete": ("Delete", "Don't delete"), "erase": ("Erase", "Don't erase"), "remove": ("Remove", "Don't remove"),
338
+ "trash": ("Move to trash", "Keep it"), "clear": ("Clear", "Don't clear"),
339
+ "follow": ("Follow", "Don't follow"), "unfollow": ("Unfollow", "Don't unfollow"), "share": ("Share", "Don't share"),
340
+ "call": ("Call", "Don't call"), "dial": ("Call", "Don't call"), "facetime": ("Call", "Don't call"),
341
+ "subscribe": ("Subscribe", "Don't subscribe"), "unsubscribe": ("Unsubscribe", "Don't unsubscribe"),
342
+ "sign up": ("Sign up", "Don't sign up"), "request": ("Send request", "Don't send"),
343
+ "react": ("React", "Don't react"), "use app": ("Allow", "Not now"),
344
+ "add": ("Add", "Don't add"), "save": ("Save", "Don't save"), "archive": ("Archive", "Don't archive"),
345
+ "submit": ("Submit", "Don't submit"), "confirm": ("Confirm", "Don't confirm"), "cancel": ("Cancel it", "Keep it"),
346
+ "accept": ("Accept", "Don't accept"), "join": ("Join", "Don't join"), "leave": ("Leave", "Stay"),
347
+ "block": ("Block", "Don't block"), "report": ("Report", "Don't report"), "install": ("Install", "Don't install"),
348
+ "upload": ("Upload", "Don't upload"),
349
+ }
350
+ ASK = ("Approve", "Decline")
351
+
352
+
353
+ def verbs(approval):
354
+ if approval.get("kind") == "loop":
355
+ return ASK
356
+ if approval.get("kind") == "plan":
357
+ return ("Start", "Skip")
358
+ if approval.get("kind") == "proposals":
359
+ # Yes and no answer the whole batch here: every draft not yet decided is sent, or skipped.
360
+ yes, no = verbs({"act": approval.get("act")}) if approval.get("act") else ASK
361
+ return (f"{yes} all", "Skip all")
362
+ act = approval.get("act")
363
+ if not act and approval.get("operation") == "TAP":
364
+ act = (str(approval.get("label") or "").split() or [""])[0]
365
+ raw = str(act or "").strip().lower()
366
+ key = raw if raw in ACTS else " ".join(raw.replace("_", " ").replace("-", " ").split())
367
+ if key in ACTS:
368
+ return ACTS[key]
369
+ if key == "cancel" or key.startswith("cancel "):
370
+ return ACTS["cancel"]
371
+ if key == "end call":
372
+ return ("End call", "Stay on")
373
+ return ASK
374
+
375
+
376
+ def approval_title(approval):
377
+ """What Mobster asks to do, in its own words (the approval's title), else from the control it would tap."""
378
+ if approval.get("act") == "use app" and approval.get("apps"):
379
+ # The wire title is shortened; a terminal still needs to name every app it would allow.
380
+ names = approval["apps"]
381
+ listed = names[0] if len(names) == 1 else ", ".join(names[:-1]) + " and " + names[-1]
382
+ return f"Allow Mobster to use {listed}?"
383
+ if approval.get("title"):
384
+ return str(approval["title"])
385
+ label = approval.get("label") or ""
386
+ title = operation_words(approval.get("operation"))[0] + (f" “{label}”" if label else "")
387
+ app = approval.get("app")
388
+ return f"{title} in {app}" if app and app.casefold() not in title.casefold() else title
389
+
390
+
391
+ def seconds(ms):
392
+ """A duration for a reader: 180 ms, 1.4 s, 2 min 5 s."""
393
+ if ms is None:
394
+ return ""
395
+ ms = float(ms)
396
+ if ms < 1000:
397
+ return f"{ms:.0f} ms"
398
+ if ms < 60_000:
399
+ return f"{ms / 1000:.1f} s"
400
+ return f"{int(ms // 60_000)} min {int(ms % 60_000 // 1000)} s"
401
+
402
+
403
+ def usd(nanodollars):
404
+ """Spend for a status line: $0.0042, $0.12, $3.40."""
405
+ dollars = (nanodollars or 0) / 1e9
406
+ return f"${dollars:.4f}" if dollars < 0.1 else f"${dollars:.2f}"
407
+
408
+
409
+ def app_name(bundle, apps=None):
410
+ """An app's display name from the catalog (or ``apps``), else its bundle id."""
411
+ if not bundle:
412
+ return ""
413
+ from .catalog import APPS
414
+ for app in list(apps or ()) + APPS:
415
+ if app.get("bundleId") == bundle:
416
+ return app["name"]
417
+ return bundle
418
+
419
+
420
+ @dataclass
421
+ class Step:
422
+ """One observe-decide-verify-act iteration of the agent."""
423
+ key: str
424
+ index: int
425
+ app: str = ""
426
+ elements: int | None = None
427
+ observe_ms: float | None = None
428
+ operation: str | None = None
429
+ target: str = ""
430
+ confidence: float | None = None
431
+ goal_probability: float | None = None
432
+ decide_ms: float | None = None
433
+ model: str = ""
434
+ check: str | None = None
435
+ act: str | None = None
436
+ act_ms: float | None = None
437
+ changed: bool | None = None
438
+ settle_ms: float | None = None
439
+ started_at: float | None = None
440
+ ended_at: float | None = None
441
+ completion: str | None = None
442
+ note: str = "" # why a decided action was never sent
443
+ target_id: str | None = None
444
+ tries: int = 1 # consecutive steps that did not act, collapsed into this one
445
+ merged_into: object = None # set on a step folded into the one before it
446
+
447
+ @property
448
+ def state(self):
449
+ """thinking, acting, done, unchanged, failed."""
450
+ if self.act == "not_dispatched":
451
+ return "failed"
452
+ if self.act is None and self.note:
453
+ return "skipped"
454
+ if self.act == "started":
455
+ return "acting"
456
+ if self.act == "acknowledged":
457
+ return "done" if self.changed is not False else "unchanged"
458
+ if self.operation in STALLS:
459
+ return "stalled" # a decision not to act ends its step; the next read starts another
460
+ if self.operation == "DONE" and self.ended_at is not None:
461
+ return "done"
462
+ return "thinking"
463
+
464
+ @property
465
+ def title(self):
466
+ if not self.operation:
467
+ if self.ended_at is not None:
468
+ return "Read the screen"
469
+ return "Deciding" if self.elements is not None else "Reading the screen"
470
+ present, past = operation_words(self.operation)
471
+ finished = self.operation in {"DONE", "WAIT"} and self.ended_at is not None
472
+ verb = past if self.act == "acknowledged" or finished else present
473
+ if self.operation in {"TAP", "DOUBLE_TAP", "LONG_PRESS", "TYPE", "TYPE_SUBMIT", "SUBMIT",
474
+ "INCREMENT", "DECREMENT"} and self.target:
475
+ return f"{verb} “{self.target}”"
476
+ if self.operation == "LAUNCH_APP" and self.target:
477
+ return f"{verb} {self.target}"
478
+ return verb
479
+
480
+ @property
481
+ def duration_ms(self):
482
+ if self.started_at is None or self.ended_at is None:
483
+ return None
484
+ return max(0.0, self.ended_at - self.started_at)
485
+
486
+ def phases(self):
487
+ """[(phase, text)] for observe, decide, verify and act, the ones that happened."""
488
+ rows = []
489
+ if self.elements is not None or self.observe_ms is not None:
490
+ where = f"{self.app}, " if self.app else ""
491
+ count = f"{self.elements} elements" if self.elements is not None else "screen read"
492
+ rows.append(("observe", f"{where}{count}" + (f", {seconds(self.observe_ms)}" if self.observe_ms else "")))
493
+ if self.operation:
494
+ sure = f"{self.confidence:.0%} sure" if isinstance(self.confidence, (int, float)) else ""
495
+ if self.operation == "DONE" and isinstance(self.goal_probability, (int, float)):
496
+ sure = f"goal {self.goal_probability:.0%} likely met"
497
+ parts = [operation_words(self.operation)[0] + (f" “{self.target}”" if self.target else ""), sure,
498
+ seconds(self.decide_ms) if self.decide_ms else ""]
499
+ rows.append(("decide", ", ".join(p for p in parts if p)))
500
+ if self.check:
501
+ rows.append(("verify", self.check))
502
+ if self.act:
503
+ if self.act == "not_dispatched":
504
+ text = "not sent: the screen changed first"
505
+ elif self.act == "started":
506
+ text = "sending…"
507
+ else:
508
+ outcome = {True: "screen changed", False: "no visible change", None: "sent"}[self.changed]
509
+ text = ", ".join(p for p in (outcome, seconds((self.act_ms or 0) + (self.settle_ms or 0))
510
+ if self.act_ms is not None else "") if p)
511
+ rows.append(("act", text))
512
+ elif self.note:
513
+ rows.append(("act", self.note))
514
+ if self.completion:
515
+ rows.append(("check", "done" if self.completion == "confirmed" else "not finished yet"))
516
+ return rows
517
+
518
+
519
+ @dataclass
520
+ class Launch:
521
+ key: str
522
+ app: str
523
+ done: bool = False
524
+
525
+
526
+ @dataclass
527
+ class Note:
528
+ key: str
529
+ tone: str
530
+ text: str
531
+
532
+
533
+ @dataclass
534
+ class Approval:
535
+ key: str
536
+ approval_id: str
537
+ operation: str
538
+ label: str
539
+ kind: str = "action"
540
+ text_present: bool = False
541
+ choices: tuple = ()
542
+ decision: str | None = None
543
+ request: dict = field(default_factory=dict) # the live request (with any text), never journaled
544
+
545
+ @property
546
+ def title(self):
547
+ if self.kind != "action":
548
+ return self.request.get("question") or self.label or "Mobster has a question"
549
+ present = operation_words(self.operation)[0]
550
+ return f"{present} “{self.label}”" if self.label else present
551
+
552
+
553
+ @dataclass
554
+ class Outcome:
555
+ key: str
556
+ status: str
557
+ summary: dict
558
+
559
+ @property
560
+ def label(self):
561
+ return status_label(self.status)
562
+
563
+ @property
564
+ def tone(self):
565
+ return status_tone(self.status)
566
+
567
+ @property
568
+ def message(self):
569
+ return completion_message(self.status, self.summary.get("reason"), self.summary.get("data_status"),
570
+ self.summary)
571
+
572
+ @property
573
+ def head(self):
574
+ """MESSAGING §11's head: Done, Here's what Mobster found, Take a look, Couldn't finish, …"""
575
+ return result_head(self.status, self.summary)[0]
576
+
577
+ @property
578
+ def head_tone(self):
579
+ return result_head(self.status, self.summary)[1]
580
+
581
+ @property
582
+ def hero(self):
583
+ return hero_line(self.summary) if self.head in (DONE, FOUND, TAKE_A_LOOK) else ""
584
+
585
+ @property
586
+ def proof(self):
587
+ return proof_line(self.summary) if self.head in (DONE, FOUND) else ""
588
+
589
+ @property
590
+ def detail(self):
591
+ head = self.head
592
+ if head in (DONE, FOUND):
593
+ return ""
594
+ if head == TAKE_A_LOOK:
595
+ return take_a_look_line(self.summary)
596
+ return self.message
597
+
598
+
599
+ class Narrator:
600
+ """Folds one run's events into items, in order; a target resolves against the latest earlier observation only."""
601
+
602
+ def __init__(self, apps=None):
603
+ self.apps = apps
604
+ self.items = []
605
+ self.by_key = {}
606
+ self.labels = {}
607
+ self.app = ""
608
+ self.step = None
609
+ self.cost_nanodollars = 0
610
+ self.priced_calls = 0
611
+ self.unpriced_calls = 0
612
+ self.plan_calls = 0
613
+ self.decisions = 0
614
+ self.actions = 0
615
+ self.started_at = None
616
+ self.finished_at = None
617
+ self.status = "running"
618
+ self.noted = set()
619
+ # The latest screen Mobster read: {"app", "bundle", "elements", "keyboard"} (elements as in events).
620
+ self.screen = None
621
+
622
+ def _add(self, item):
623
+ self.items.append(item)
624
+ self.by_key[item.key] = item
625
+ return item
626
+
627
+ def _close_unsent(self, step, note):
628
+ """A step whose decided action was never sent: say why instead of spinning forever."""
629
+ if step is not None and step.act is None and step.operation not in {None, "DONE", "WAIT", "BLOCKED"}:
630
+ step.note = step.note or note
631
+
632
+ def _merge_stall(self, step):
633
+ """Fold a step that did not act into the stalled step right before it. Returns that step, or None."""
634
+ previous = next((item for item in reversed(self.items) if isinstance(item, Step) and item is not step), None)
635
+ between = self.items[self.items.index(previous) + 1:self.items.index(step)] if previous else []
636
+ if (previous is None or previous.operation not in STALLS or previous.act is not None
637
+ or any(not isinstance(item, Note) for item in between)):
638
+ return None
639
+ previous.tries += 1
640
+ for name in ("operation", "confidence", "goal_probability", "decide_ms", "observe_ms", "elements", "app",
641
+ "ended_at", "target_id"):
642
+ setattr(previous, name, getattr(step, name))
643
+ step.merged_into = previous
644
+ self.items.remove(step)
645
+ self.by_key[step.key] = previous
646
+ self.step = previous
647
+ return previous
648
+
649
+ def _step(self, index, timestamp=None, key=None):
650
+ key = key or f"step-{index}"
651
+ step = self.by_key.get(key)
652
+ if step is None:
653
+ if self.step is not None and self.step.ended_at is None:
654
+ self.step.ended_at = timestamp
655
+ self._close_unsent(self.step, "not sent: the screen moved first, so Mobster decided again")
656
+ step = self._add(Step(key, index, app=self.app, started_at=timestamp))
657
+ self.step = step
658
+ return step
659
+
660
+ def feed(self, event):
661
+ if not isinstance(event, dict):
662
+ return []
663
+ kind = event.get("event")
664
+ stamp = event.get("timestamp")
665
+ if stamp is not None and self.started_at is None:
666
+ self.started_at = stamp
667
+ if kind == "inference_finished":
668
+ cost = event.get("cost_nanodollars")
669
+ if event.get("cost_basis") == "plan":
670
+ self.plan_calls += 1
671
+ if type(cost) is int and cost >= 0:
672
+ self.cost_nanodollars += cost
673
+ self.priced_calls += 1
674
+ else:
675
+ self.unpriced_calls += 1
676
+ return []
677
+ if kind in ("frontier_decision", "frontier_action"):
678
+ return self._frontier(kind, event, stamp)
679
+ if kind == "app_launch_started":
680
+ return [self._add(Launch("launch", event.get("app") or ""))]
681
+ if kind == "app_launch_acknowledged":
682
+ launch = self.by_key.get("launch")
683
+ if launch:
684
+ launch.done = True
685
+ return [launch]
686
+ return []
687
+ if kind == "observation":
688
+ self.labels = {e.get("id"): (e.get("label") or "").strip() for e in event.get("elements") or ()
689
+ if isinstance(e, dict)}
690
+ if event.get("bundle_id"):
691
+ self.app = app_name(event["bundle_id"], self.apps)
692
+ self.screen = {"app": self.app, "bundle": event.get("bundle_id") or "",
693
+ "elements": [e for e in event.get("elements") or () if isinstance(e, dict)],
694
+ "keyboard": event.get("keyboard") or ""}
695
+ step = self._step(event.get("step", 0), stamp)
696
+ step.app = self.app or step.app
697
+ step.elements = len(event.get("elements") or ())
698
+ return [step]
699
+ if kind == "decision":
700
+ self.decisions += 1
701
+ step = self._step(event.get("step", self.step.index if self.step else 0), stamp)
702
+ step.operation = event.get("operation")
703
+ target = event.get("target")
704
+ if step.operation == "LAUNCH_APP":
705
+ step.target = app_name(target, self.apps)
706
+ else:
707
+ step.target = (event.get("target_label") or self.labels.get(target) or
708
+ ("an element on screen" if target else ""))
709
+ step.confidence = event.get("confidence")
710
+ step.goal_probability = event.get("goal_probability")
711
+ step.decide_ms = event.get("latency_ms")
712
+ step.observe_ms = event.get("observe_ms", step.observe_ms)
713
+ step.model = event.get("model") or ""
714
+ step.target_id = target if isinstance(target, str) else None
715
+ if step.operation in STALLS | {"DONE"}:
716
+ step.ended_at = stamp
717
+ if step.operation in STALLS:
718
+ merged = self._merge_stall(step)
719
+ if merged is not None:
720
+ return [step, merged]
721
+ return [step]
722
+ if kind == "action_check" and self.step is not None:
723
+ skipped = event.get("skipped")
724
+ self.step.check = ({"plain_navigation": "skipped: plain navigation", "keypad": "skipped: keypad key"}
725
+ .get(skipped) or {"allowed": "allowed", "unclear": "unclear", "mismatch":
726
+ "does not match the request"}.get(event.get("support"),
727
+ event.get("support")))
728
+ return [self.step]
729
+ if kind == "approval_requested":
730
+ item = self._add(Approval(f"approval-{event.get('approval_id')}", event.get("approval_id") or "",
731
+ event.get("operation") or "", event.get("label") or "",
732
+ kind=event.get("kind") or "action",
733
+ text_present=bool(event.get("text_present")),
734
+ choices=tuple(event.get("choices") or ())))
735
+ return [item]
736
+ if kind == "approval_resolved":
737
+ item = self.by_key.get(f"approval-{event.get('approval_id')}")
738
+ if item:
739
+ item.decision = event.get("decision")
740
+ return [item]
741
+ return []
742
+ if kind == "action_started":
743
+ self.actions += 1
744
+ step = self.step if self.step is not None else self._step(event.get("step", 0), stamp)
745
+ if event.get("operation") == "OPEN_URL":
746
+ step.operation, step.target = "OPEN_URL", ""
747
+ step.act = "started"
748
+ return [step]
749
+ if kind == "action_acknowledged" and self.step is not None:
750
+ self.step.act, self.step.act_ms = "acknowledged", event.get("act_ms")
751
+ return [self.step]
752
+ if kind == "action_not_dispatched" and self.step is not None:
753
+ self.step.act = "not_dispatched"
754
+ self.step.ended_at = stamp
755
+ return [self.step]
756
+ if kind == "observation_after_action" and self.step is not None:
757
+ self.step.changed, self.step.settle_ms = event.get("changed"), event.get("settle_ms")
758
+ self.step.ended_at = stamp
759
+ return [self.step]
760
+ if kind == "completion_check" and self.step is not None:
761
+ self.step.completion = "confirmed" if event.get("operation") == "DONE" else "not_yet"
762
+ return [self.step]
763
+ if kind == "step_interrupted":
764
+ return [self._add(Note(f"note-{len(self.items)}", "neutral",
765
+ "A device or network request timed out; re-reading the screen"))]
766
+ if kind == "error":
767
+ detail = event.get("native_code") or event.get("type") or "error"
768
+ return [self._add(Note(f"note-{len(self.items)}", "error", f"Error: {detail}"))]
769
+ note = guard_note(event)
770
+ if note is not None:
771
+ return [self._add(Note(f"note-{len(self.items)}", note[0], note[1]))]
772
+ if kind in NOTES:
773
+ if kind in ONCE and kind in self.noted:
774
+ return []
775
+ self.noted.add(kind)
776
+ tone, text = NOTES[kind]
777
+ return [self._add(Note(f"note-{len(self.items)}", tone, text))]
778
+ if kind == "run_finished":
779
+ summary = event.get("summary") if isinstance(event.get("summary"), dict) else {}
780
+ self.status = event.get("status") or summary.get("status") or "error"
781
+ self.finished_at = stamp
782
+ if self.step is not None and self.step.ended_at is None:
783
+ self.step.ended_at = stamp
784
+ self._close_unsent(self.step, "preview: nothing was sent" if self.status == "preview" else "not sent")
785
+ changed = [self.step] if self.step is not None else []
786
+ return changed + [self._add(Outcome("outcome", self.status, summary))]
787
+ return []
788
+
789
+ def _frontier(self, kind, event, stamp):
790
+ index = event.get("step", 0)
791
+ if kind == "frontier_decision":
792
+ self.decisions += 1
793
+ step = self._step(index, stamp)
794
+ step.operation, step.target = event.get("operation"), event.get("target_label") or ""
795
+ step.decide_ms, step.model = event.get("latency_ms"), event.get("model") or ""
796
+ if step.operation in STALLS | {"DONE"}:
797
+ step.ended_at = stamp
798
+ return [step]
799
+ position = event.get("index") or 0
800
+ step = self._step(index, stamp, key=f"step-{index}.{position}" if position else None)
801
+ step.operation = event.get("operation") or step.operation
802
+ step.target = event.get("target_label") or step.target or ""
803
+ step.app = event.get("app_name") or step.app
804
+ self.actions += 1
805
+ step.act, step.changed, step.settle_ms = "acknowledged", event.get("changed"), event.get("settle_ms")
806
+ step.ended_at = stamp
807
+ return [step]
808
+
809
+ def elapsed_ms(self, now_ms=None):
810
+ if self.started_at is None:
811
+ return 0.0
812
+ end = self.finished_at if self.finished_at is not None else now_ms
813
+ return max(0.0, (end or self.started_at) - self.started_at)