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,361 @@
1
+ """Threads and their items in the run journal (schema ``threads`` v1, SPEC §3.2)."""
2
+
3
+ import json
4
+ import math
5
+ import re
6
+ import sqlite3
7
+ import time
8
+ import uuid
9
+
10
+ from ..api_errors import APIError
11
+ from ..journal import JournalError
12
+
13
+ SCHEMA = "threads"
14
+ VERSION = 1
15
+
16
+ MAX_ITEMS = 2000 # per thread, every kind
17
+ RESERVE = 20 # kept free for the items a running task still adds (questions, results)
18
+ MAX_THREAD_BYTES = 8_000_000 # per thread
19
+ MAX_SINK_ITEM_BYTES = 8_000 # an item another track posts (post_thread_item)
20
+ TITLE_CHARS = 80
21
+ MAX_RULES = 10
22
+ RULES_CHARS = 1_200 # "Rules you gave in this conversation", newest kept
23
+ LIST_LIMIT = 100
24
+ REQUEST_LIMIT = 10_000
25
+ KIND = re.compile(r"[a-z_]{1,40}")
26
+ ID = re.compile(r"[a-f0-9]{12}")
27
+ CORE_KINDS = frozenset({"user", "run", "clarify"})
28
+ STATUSES = ("idle", "running", "waiting")
29
+ FULL = "This conversation is full. Start a new one."
30
+
31
+
32
+ NOT_FOUND = "That conversation doesn't exist here. It may have been deleted, or it's in another Mobster."
33
+
34
+
35
+ class ThreadFull(APIError):
36
+ """The thread has no room for what was asked (MAX_ITEMS or MAX_THREAD_BYTES): 409 thread_full."""
37
+
38
+ def __init__(self, message=FULL):
39
+ super().__init__(message, 409, "thread_full")
40
+
41
+
42
+ class ThreadMissing(APIError):
43
+ """The thread doesn't exist (deleted meanwhile): 404 thread_not_found."""
44
+
45
+ def __init__(self, message=NOT_FOUND):
46
+ super().__init__(message, 404, "thread_not_found")
47
+
48
+
49
+ def new_id():
50
+ return uuid.uuid4().hex[:12]
51
+
52
+
53
+ def encode(value):
54
+ return json.dumps(value, allow_nan=False, separators=(",", ":"), ensure_ascii=False)
55
+
56
+
57
+ def now_ms():
58
+ return time.time() * 1000
59
+
60
+
61
+ def request_unknown():
62
+ return APIError("Mobster accepted this request but can't confirm its outcome. Check the conversation "
63
+ "and task history before starting another request.", 409, "request_outcome_unknown")
64
+
65
+
66
+ def request_result(response):
67
+ """Replay a stored success or known refusal; unresolved intent never authorizes another dispatch."""
68
+ if response is None:
69
+ raise request_unknown()
70
+ if response["status"] >= 400:
71
+ error = response["response"]
72
+ if "code" not in error:
73
+ raise ValueError(error["error"])
74
+ raise APIError(error["error"], response["status"], error["code"],
75
+ **{key: value for key, value in error.items() if key not in ("error", "code")})
76
+ return response
77
+
78
+
79
+ def failed_request(error):
80
+ body = {"error": str(error)}
81
+ if isinstance(error, APIError):
82
+ body.update(code=error.code, **error.details)
83
+ return {"status": error.status if isinstance(error, APIError) else 400, "response": body}
84
+
85
+
86
+ def cursor_parts(value):
87
+ """(stored timestamp, optional thread id) from a next cursor or a legacy millisecond timestamp."""
88
+ timestamp, separator, thread_id = str(value).partition(":")
89
+ try:
90
+ timestamp = float(timestamp) / 1000
91
+ if not math.isfinite(timestamp) or separator and not ID.fullmatch(thread_id):
92
+ raise ValueError
93
+ except ValueError:
94
+ raise ValueError("before is a next cursor or an updatedAt from a thread (milliseconds)") from None
95
+ return timestamp, thread_id if separator else None
96
+
97
+
98
+ def migrate(connection, from_version):
99
+ """journal.register_schema's migration: v0 -> v1 creates the tables. Runs inside BEGIN IMMEDIATE."""
100
+ if from_version < 1:
101
+ connection.execute("""CREATE TABLE IF NOT EXISTS threads(
102
+ id TEXT PRIMARY KEY, data TEXT NOT NULL, updated_at REAL NOT NULL)""")
103
+ connection.execute("""CREATE TABLE IF NOT EXISTS thread_items(
104
+ thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
105
+ seq INTEGER NOT NULL, data TEXT NOT NULL,
106
+ id TEXT NOT NULL, kind TEXT NOT NULL, run_id TEXT, bytes INTEGER NOT NULL,
107
+ PRIMARY KEY(thread_id, seq))""")
108
+ connection.execute("CREATE INDEX IF NOT EXISTS threads_updated ON threads(updated_at DESC)")
109
+ connection.execute("CREATE INDEX IF NOT EXISTS thread_items_run ON thread_items(thread_id, run_id)")
110
+ connection.execute("CREATE UNIQUE INDEX IF NOT EXISTS thread_items_id ON thread_items(id)")
111
+ # Idempotency-Key -> the answer a message got, so a retried post never starts a second task.
112
+ connection.execute("""CREATE TABLE IF NOT EXISTS thread_requests(
113
+ key TEXT PRIMARY KEY, thread_id TEXT, fingerprint TEXT NOT NULL, response TEXT NOT NULL,
114
+ created_at REAL NOT NULL)""")
115
+
116
+
117
+ def public_item(item):
118
+ """The item as the API and the bus show it: without its private ("_") keys."""
119
+ return {key: value for key, value in item.items() if not key.startswith("_")}
120
+
121
+
122
+ def title_from(text):
123
+ """A thread's title from its first message: one line, at most 80 characters, cut at a word."""
124
+ text = " ".join(str(text or "").split())
125
+ if len(text) <= TITLE_CHARS:
126
+ return text
127
+ cut = text[:TITLE_CHARS - 1]
128
+ space = cut.rfind(" ")
129
+ if space >= TITLE_CHARS // 2:
130
+ cut = cut[:space]
131
+ return cut.rstrip(" ,;:.") + "…"
132
+
133
+
134
+ class ThreadStore:
135
+ def __init__(self, journal):
136
+ self.journal = journal
137
+
138
+ def _read(self, sql, args=()):
139
+ with self.journal.lock:
140
+ if self.journal.connection is None:
141
+ raise JournalError("The run journal is closed")
142
+ try:
143
+ return self.journal.connection.execute(sql, args).fetchall()
144
+ except sqlite3.Error:
145
+ raise JournalError("Mobster could not read its conversations") from None
146
+
147
+ def create_thread(self, *, title=None, device=None, device_name=None, thread_id=None):
148
+ stamp = now_ms()
149
+ thread = {"id": thread_id or new_id(), "title": title_from(title) or "New conversation",
150
+ "createdAt": stamp, "updatedAt": stamp, "device": device, "deviceName": device_name,
151
+ "archived": False, "pinned": False, "status": "idle", "lastRunId": None,
152
+ "rules": []}
153
+ with self.journal.transaction() as connection:
154
+ connection.execute("INSERT INTO threads(id, data, updated_at) VALUES (?,?,?)",
155
+ (thread["id"], encode(thread), stamp / 1000))
156
+ return thread
157
+
158
+ def get(self, thread_id):
159
+ if not isinstance(thread_id, str) or not ID.fullmatch(thread_id):
160
+ return None
161
+ rows = self._read("SELECT data FROM threads WHERE id=?", (thread_id,))
162
+ return json.loads(rows[0][0]) if rows else None
163
+
164
+ def list(self, *, limit=50, before=None, device=None, query=None, archived=False):
165
+ """(threads newest first, the ``before`` cursor for the next page or None)."""
166
+ limit = max(1, min(LIST_LIMIT, int(limit)))
167
+ where, args = [], []
168
+ if before is not None:
169
+ timestamp, thread_id = cursor_parts(before)
170
+ if thread_id is None:
171
+ where.append("updated_at < ?")
172
+ args.append(timestamp)
173
+ else:
174
+ where.append("(updated_at, id) < (?, ?)")
175
+ args.extend((timestamp, thread_id))
176
+ sql = "SELECT data FROM threads" + (" WHERE " + " AND ".join(where) if where else "")
177
+ sql += " ORDER BY updated_at DESC, id DESC"
178
+ matching = None
179
+ if query:
180
+ # Search the words themselves: JSON escaping changes quotes and backslashes, and a run's other fields
181
+ # include private notes and metadata that aren't part of conversation search.
182
+ wanted = query.casefold()
183
+ matching = set()
184
+ for thread_id, kind, data in self._read(
185
+ "SELECT thread_id, kind, data FROM thread_items WHERE kind IN ('user', 'run')"):
186
+ item = json.loads(data)
187
+ if wanted in str(item.get("text" if kind == "user" else "goal") or "").casefold():
188
+ matching.add(thread_id)
189
+ out = []
190
+ for (data,) in self._read(sql, args):
191
+ thread = json.loads(data)
192
+ if archived != "all" and bool(thread.get("archived")) != bool(archived):
193
+ continue
194
+ if device is not None and device not in (thread.get("device"), thread.get("deviceName")):
195
+ continue
196
+ if matching is not None and thread["id"] not in matching and \
197
+ query.casefold() not in str(thread.get("title") or "").casefold():
198
+ continue
199
+ out.append(thread)
200
+ if len(out) > limit:
201
+ break
202
+ more = len(out) > limit
203
+ out = out[:limit]
204
+ cursor = f"{out[-1]['updatedAt']}:{out[-1]['id']}" if more and out else None
205
+ return out, cursor
206
+
207
+ def update_thread(self, thread_id, changes, *, touch=True):
208
+ """Merge ``changes`` into the thread; returns it, or None when it doesn't exist."""
209
+ with self.journal.transaction() as connection:
210
+ row = connection.execute("SELECT data FROM threads WHERE id=?", (thread_id,)).fetchone()
211
+ if row is None:
212
+ return None
213
+ thread = json.loads(row[0])
214
+ thread.update(changes)
215
+ if touch:
216
+ thread["updatedAt"] = max(now_ms(), thread.get("updatedAt") or 0)
217
+ connection.execute("UPDATE threads SET data=?, updated_at=? WHERE id=?",
218
+ (encode(thread), thread["updatedAt"] / 1000, thread_id))
219
+ return thread
220
+
221
+ def delete_thread(self, thread_id):
222
+ """Remove the thread and its items. Returns True when it existed."""
223
+ with self.journal.transaction() as connection:
224
+ deleted = connection.execute("DELETE FROM threads WHERE id=?", (thread_id,)).rowcount == 1
225
+ connection.execute("UPDATE thread_requests SET response=? WHERE thread_id=? AND response != 'null'", (
226
+ encode({"status": 410, "response": {"error": "That conversation was deleted; it was not started again",
227
+ "code": "thread_expired"}}), thread_id))
228
+ return deleted
229
+
230
+ def threads_with_status(self, statuses):
231
+ rows = self._read("SELECT data FROM threads")
232
+ return [t for t in (json.loads(r[0]) for r in rows) if t.get("status") in statuses]
233
+
234
+ def items(self, thread_id, after=None):
235
+ """The thread's items in order (``after``: only those with a larger seq)."""
236
+ rows = self._read("SELECT data FROM thread_items WHERE thread_id=? AND seq>? ORDER BY seq",
237
+ (thread_id, -1 if after is None else int(after)))
238
+ return [json.loads(r[0]) for r in rows]
239
+
240
+ def run_ids(self, thread_id):
241
+ rows = self._read("SELECT DISTINCT run_id FROM thread_items WHERE thread_id=? AND kind='run' "
242
+ "AND run_id IS NOT NULL", (thread_id,))
243
+ return [r[0] for r in rows]
244
+
245
+ def find(self, thread_id, *, kind=None, run_id=None, match=None):
246
+ """The items of ``kind`` (and ``run_id``) for which ``match(item)`` holds, in order."""
247
+ sql, args = "SELECT data FROM thread_items WHERE thread_id=?", [thread_id]
248
+ if kind is not None:
249
+ sql += " AND kind=?"
250
+ args.append(kind)
251
+ if run_id is not None:
252
+ sql += " AND run_id=?"
253
+ args.append(run_id)
254
+ items = [json.loads(r[0]) for r in self._read(sql + " ORDER BY seq", args)]
255
+ return [item for item in items if match is None or match(item)]
256
+
257
+ def usage(self, thread_id):
258
+ """(item count, bytes) of the thread."""
259
+ row = self._read("SELECT count(*), coalesce(sum(bytes), 0) FROM thread_items WHERE thread_id=?",
260
+ (thread_id,))[0]
261
+ return int(row[0]), int(row[1])
262
+
263
+ def append(self, thread_id, items, *, unless=None):
264
+ """Add ``items`` (dicts with at least "kind") in one transaction, after checking there is room for them."""
265
+ items = [dict(item) for item in items]
266
+ encoded, problem = [], None
267
+ # Journal.transaction turns any exception into a JournalError: decide inside, raise after.
268
+ with self.journal.transaction() as connection:
269
+ if connection.execute("SELECT 1 FROM threads WHERE id=?", (thread_id,)).fetchone() is None:
270
+ problem = ThreadMissing()
271
+ elif unless is not None and unless(self._existing(connection, thread_id, items)):
272
+ return None
273
+ else:
274
+ count, size, last = connection.execute(
275
+ "SELECT count(*), coalesce(sum(bytes), 0), coalesce(max(seq), -1) FROM thread_items "
276
+ "WHERE thread_id=?", (thread_id,)).fetchone()
277
+ stamp = now_ms()
278
+ for offset, item in enumerate(items):
279
+ if not (isinstance(item.get("id"), str) and ID.fullmatch(item["id"])):
280
+ item["id"] = new_id()
281
+ item["seq"] = last + 1 + offset
282
+ item.setdefault("at", stamp)
283
+ data = encode(item)
284
+ encoded.append((item, data, len(data.encode())))
285
+ if count + len(items) > MAX_ITEMS or \
286
+ size + sum(e[2] for e in encoded) > MAX_THREAD_BYTES:
287
+ problem = ThreadFull()
288
+ else:
289
+ for item, data, size_bytes in encoded:
290
+ connection.execute(
291
+ "INSERT INTO thread_items(thread_id, seq, data, id, kind, run_id, bytes) "
292
+ "VALUES (?,?,?,?,?,?,?)",
293
+ (thread_id, item["seq"], data, item["id"], item["kind"], item.get("runId"), size_bytes))
294
+ row = connection.execute("SELECT data FROM threads WHERE id=?", (thread_id,)).fetchone()
295
+ thread = json.loads(row[0])
296
+ thread["updatedAt"] = max(stamp, thread.get("updatedAt") or 0)
297
+ connection.execute("UPDATE threads SET data=?, updated_at=? WHERE id=?",
298
+ (encode(thread), thread["updatedAt"] / 1000, thread_id))
299
+ if problem is not None:
300
+ raise problem
301
+ return [e[0] for e in encoded]
302
+
303
+ @staticmethod
304
+ def _existing(connection, thread_id, items):
305
+ """The thread's stored items of the same kinds (and runs) as ``items``."""
306
+ kinds = sorted({item["kind"] for item in items})
307
+ runs = sorted({item.get("runId") for item in items if item.get("runId")})
308
+ sql = f"SELECT data FROM thread_items WHERE thread_id=? AND kind IN ({','.join('?' * len(kinds))})"
309
+ args = [thread_id, *kinds]
310
+ if runs:
311
+ sql += f" AND run_id IN ({','.join('?' * len(runs))})"
312
+ args += runs
313
+ return [json.loads(r[0]) for r in connection.execute(sql + " ORDER BY seq", args)]
314
+
315
+ def update_item(self, thread_id, item_id, changes, *, kinds=None, max_bytes=None):
316
+ """Merge ``changes`` into an item (never its id, seq, kind, at or runId)."""
317
+ changes = {k: v for k, v in dict(changes).items() if k not in ("id", "seq", "kind", "at", "runId")}
318
+ problem = None
319
+ with self.journal.transaction() as connection:
320
+ row = connection.execute("SELECT data, bytes FROM thread_items WHERE thread_id=? AND id=?",
321
+ (thread_id, item_id)).fetchone()
322
+ if row is None:
323
+ return None
324
+ item = json.loads(row[0])
325
+ if kinds is not None and item.get("kind") not in kinds:
326
+ return None
327
+ item.update(changes)
328
+ data = encode(item)
329
+ size = len(data.encode())
330
+ total = connection.execute("SELECT coalesce(sum(bytes), 0) FROM thread_items WHERE thread_id=?",
331
+ (thread_id,)).fetchone()[0]
332
+ if max_bytes is not None and size > max_bytes:
333
+ problem = ValueError(f"A thread item is at most {max_bytes:,} bytes")
334
+ elif total - row[1] + size > MAX_THREAD_BYTES:
335
+ problem = ThreadFull()
336
+ else:
337
+ connection.execute("UPDATE thread_items SET data=?, bytes=? WHERE thread_id=? AND id=?",
338
+ (data, size, thread_id, item_id))
339
+ if problem is not None:
340
+ raise problem
341
+ return item
342
+
343
+ def request(self, key):
344
+ """(thread id, fingerprint, response) saved under a key, or None. A null response is unresolved intent."""
345
+ rows = self._read("SELECT thread_id, fingerprint, response FROM thread_requests WHERE key=?", (key,))
346
+ return (rows[0][0], rows[0][1], json.loads(rows[0][2])) if rows else None
347
+
348
+ def remember(self, key, thread_id, fingerprint, response):
349
+ with self.journal.transaction() as connection:
350
+ if connection.execute("SELECT 1 FROM thread_requests WHERE key=?", (key,)).fetchone() is None:
351
+ excess = connection.execute("SELECT count(*) FROM thread_requests").fetchone()[0] - REQUEST_LIMIT + 1
352
+ if excess > 0:
353
+ # Never evict an unresolved intent: its effect may already have reached the phone.
354
+ connection.execute("DELETE FROM thread_requests WHERE rowid IN (SELECT rowid FROM thread_requests "
355
+ "WHERE response != 'null' ORDER BY created_at, rowid LIMIT ?)", (excess,))
356
+ if connection.execute("SELECT count(*) FROM thread_requests").fetchone()[0] >= REQUEST_LIMIT:
357
+ raise JournalError(
358
+ "Conversation request journal is full; check unresolved requests before continuing")
359
+ connection.execute("INSERT OR REPLACE INTO thread_requests(key, thread_id, fingerprint, response, "
360
+ "created_at) VALUES (?,?,?,?,?)",
361
+ (key, thread_id, fingerprint, encode(response), time.time()))
@@ -0,0 +1,117 @@
1
+ """What a finished run leaves in its thread: its public result, what the next task can refer to, its private context."""
2
+
3
+ import re
4
+
5
+ from .. import secret_filter
6
+
7
+ NOTE_CHARS = 600
8
+ ANSWER_CONTEXT_CHARS = 600
9
+ FACTS = 6
10
+ FACT_CHARS = 200
11
+
12
+
13
+ def group_fields(group):
14
+ """A group item's fields from fanout's public group: its phones (each with its run, state and why it didn't
15
+ start), the combined state and counts, the warnings, the cost estimate and, once every phone is done, the merged
16
+ summary."""
17
+ members = [{key: member.get(key) for key in ("device", "deviceName", "runId", "state", "error", "code")}
18
+ for member in group["members"]]
19
+ return {"groupId": group["id"], "goal": group["goal"], "members": members, "state": group["state"],
20
+ "counts": group["counts"], "warning": group.get("warning"), "held": group.get("held"),
21
+ "estimateUsd": group.get("estimateUsd"), "estimateTotalUsd": group.get("estimateTotalUsd"),
22
+ "summary": group.get("summary"), "finishedAt": group.get("finishedAt")}
23
+
24
+
25
+ def result_of(summary, run=None):
26
+ """The run item's public result: status, outcome, the answer's headline, facts, cost and time."""
27
+ summary = summary if isinstance(summary, dict) else {}
28
+ facts = []
29
+ for entry in summary.get("proof") or ():
30
+ # A quote of only spaces is no proof (the app's runOutcome trims it too), so it never becomes a fact.
31
+ if not isinstance(entry, dict) or not str(entry.get("quote") or "").strip():
32
+ continue
33
+ fact = secret_filter.redact(" ".join(str(entry["quote"]).split()))[:FACT_CHARS]
34
+ if entry.get("app"):
35
+ fact += f" ({str(entry['app'])[:40]})"
36
+ facts.append(fact)
37
+ if len(facts) >= FACTS:
38
+ break
39
+ answer = summary.get("answer")
40
+ cost = summary.get("costUsd")
41
+ if cost is None and run is not None:
42
+ cost = getattr(run, "cost_usd", None)
43
+ elapsed = summary.get("elapsed_ms")
44
+ return {"status": summary.get("status") or (getattr(run, "status", None) or "error"),
45
+ "outcome": summary.get("outcome") if isinstance(summary.get("outcome"), str) else None,
46
+ "answer": secret_filter.redact(answer)[:1000] if isinstance(answer, str) else None,
47
+ "facts": facts, "costUsd": cost if isinstance(cost, (int, float)) else None,
48
+ "elapsedMs": elapsed if isinstance(elapsed, (int, float)) else None}
49
+
50
+
51
+ REFERENTS = 12
52
+ NUMBERED = re.compile(r"^\s*(\d+)[.)]\s+(.+)$")
53
+ BULLETED = re.compile(r"^\s*[-•*]\s+(.+)$")
54
+
55
+
56
+ def referents_of(summary):
57
+ """Up to REFERENTS numbered things a run produced, so "the second one" resolves later (DESIGN §8.1)."""
58
+ summary = summary if isinstance(summary, dict) else {}
59
+ out = []
60
+ report = summary.get("report")
61
+ if isinstance(report, dict) and report.get("ranked"):
62
+ from ..longform.report import referents
63
+ out = referents(report)
64
+ if not out:
65
+ full = summary.get("full_answer") or (summary.get("data") if isinstance(summary.get("data"), str) else None) \
66
+ or summary.get("answer") or ""
67
+ numbered, bulleted = [], []
68
+ for line in str(full).splitlines():
69
+ found = NUMBERED.match(line)
70
+ if found:
71
+ numbered.append({"rank": int(found.group(1)), "text": found.group(2)})
72
+ continue
73
+ found = BULLETED.match(line)
74
+ if found:
75
+ bulleted.append({"rank": len(bulleted) + 1, "text": found.group(1)})
76
+ out = numbered or bulleted
77
+ if not out:
78
+ quotes = [entry.get("quote") for entry in summary.get("proof") or () if isinstance(entry, dict)
79
+ and entry.get("quote")]
80
+ out = [{"rank": n, "text": quote} for n, quote in enumerate(quotes, 1)] if len(quotes) >= 2 else []
81
+ clean = []
82
+ for ref in out[:REFERENTS]:
83
+ text = secret_filter.redact(" ".join(str(ref.get("text") or "").split()).strip("*"))[:200]
84
+ if text and not secret_filter.is_secret(text):
85
+ clean.append({"rank": int(ref.get("rank") or len(clean) + 1), "text": text})
86
+ return clean
87
+
88
+
89
+ def context_of(summary, private, last_app):
90
+ """What the next task in the thread reads about this one (private: never sent by the API)."""
91
+ full = summary.get("full_answer") or (summary.get("data") if isinstance(summary.get("data"), str) else None) \
92
+ or summary.get("answer")
93
+ notes, total = [], 0
94
+ for note in (private or {}).get("notes") or ():
95
+ if not isinstance(note, str) or not note.strip():
96
+ continue
97
+ clean = secret_filter.redact(" ".join(note.split()))
98
+ if secret_filter.is_secret(clean):
99
+ continue
100
+ if total + len(clean) > NOTE_CHARS:
101
+ clean = clean[:max(0, NOTE_CHARS - total - 1)] + "…"
102
+ if len(clean) > 1:
103
+ notes.append(clean)
104
+ total += len(clean)
105
+ if total >= NOTE_CHARS:
106
+ break
107
+ out = {"notes": notes, "lastApp": last_app}
108
+ if isinstance(full, str) and full.strip():
109
+ out["answer"] = secret_filter.redact(" ".join(full.split()))[:ANSWER_CONTEXT_CHARS]
110
+ report = summary.get("report")
111
+ if isinstance(report, dict):
112
+ # A long task's report outlives the run's retention here: its TL;DR, headings and shortlist (DESIGN §8.3).
113
+ from ..longform.report import short_form
114
+ short = short_form(report)
115
+ if short:
116
+ out["report"] = secret_filter.redact(short)
117
+ return out
@@ -0,0 +1,101 @@
1
+ """The terminal under `mobster chat`: lines printed above a prompt that keeps what you're typing."""
2
+
3
+ import codecs
4
+ import os
5
+ import select
6
+ import sys
7
+ import threading
8
+
9
+
10
+ class Term:
11
+ """Lines printed above a prompt that keeps what you're typing."""
12
+
13
+ def __init__(self, stdin=None, stdout=None, prompt="› "):
14
+ self.stdin, self.stdout = stdin or sys.stdin, stdout or sys.stdout
15
+ self.prompt, self.buffer = prompt, ""
16
+ self.tty = isatty(self.stdin) and isatty(self.stdout)
17
+ self.saved = None
18
+ self.lock = threading.RLock()
19
+ self.shown = False
20
+ self.prompting = True # whether the prompt line is drawn (one message: only while something waits)
21
+ # A character split across two reads (an emoji typed or pasted) decodes once, whole.
22
+ self.decoder = codecs.getincrementaldecoder("utf-8")("replace")
23
+
24
+ def __enter__(self):
25
+ if self.tty:
26
+ import termios
27
+ import tty
28
+ try:
29
+ fd = self.stdin.fileno()
30
+ self.saved = termios.tcgetattr(fd)
31
+ tty.setcbreak(fd)
32
+ mode = termios.tcgetattr(fd)
33
+ mode[3] &= ~termios.ECHO # this prompt draws what you type itself
34
+ termios.tcsetattr(fd, termios.TCSANOW, mode)
35
+ except (termios.error, OSError, ValueError):
36
+ self.saved = None
37
+ return self
38
+
39
+ def __exit__(self, *exc):
40
+ self.clear()
41
+ if self.saved is not None:
42
+ import termios
43
+ try:
44
+ termios.tcsetattr(self.stdin.fileno(), termios.TCSADRAIN, self.saved)
45
+ except (termios.error, OSError, ValueError):
46
+ pass
47
+ return False
48
+
49
+ def write(self, text):
50
+ try:
51
+ self.stdout.write(text)
52
+ self.stdout.flush()
53
+ except (OSError, ValueError):
54
+ pass
55
+
56
+ def clear(self):
57
+ with self.lock:
58
+ if self.tty and self.shown:
59
+ self.write("\r\033[2K")
60
+ self.shown = False
61
+
62
+ def show(self):
63
+ with self.lock:
64
+ if self.tty and self.prompting:
65
+ self.write("\r\033[2K" + self.prompt + self.buffer)
66
+ self.shown = True
67
+
68
+ def print(self, text=""):
69
+ with self.lock:
70
+ self.clear()
71
+ self.write(str(text) + "\n")
72
+ self.show()
73
+
74
+ def keys(self, timeout):
75
+ """What was typed within ``timeout`` seconds ("" for nothing; None at the end of input)."""
76
+ if not self.tty:
77
+ line = self.stdin.readline()
78
+ return None if line == "" else line if line.endswith("\n") else line + "\n"
79
+ ready, _, _ = select.select([self.stdin], [], [], timeout)
80
+ if not ready:
81
+ return ""
82
+ data = os.read(self.stdin.fileno(), 1024)
83
+ return None if not data else self.decoder.decode(data)
84
+
85
+ def discard_typeahead(self):
86
+ """Forget every key typed before now that this prompt hasn't read yet (the terminal's input queue)."""
87
+ if not self.tty:
88
+ return
89
+ import termios
90
+ try:
91
+ termios.tcflush(self.stdin.fileno(), termios.TCIFLUSH)
92
+ except (termios.error, OSError, ValueError):
93
+ pass
94
+ self.decoder.reset()
95
+
96
+
97
+ def isatty(stream):
98
+ try:
99
+ return stream.isatty()
100
+ except (AttributeError, ValueError):
101
+ return False