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.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Runtime's actions on a finished run: stop, delete, export, share, review and block decisions."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import re
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import harness_api, ownership, result_blocks, traces
|
|
11
|
+
from .api_errors import APIError
|
|
12
|
+
from .downloads import save_export_bytes
|
|
13
|
+
from .journal import JournalError
|
|
14
|
+
|
|
15
|
+
# One render at a time: a GIF takes a second or two of CPU, which a running task shares.
|
|
16
|
+
GIF_RENDERS = threading.BoundedSemaphore(1)
|
|
17
|
+
|
|
18
|
+
# One at a time: each is a WebKit page off screen.
|
|
19
|
+
ARTIFACT_RENDERS = threading.BoundedSemaphore(1)
|
|
20
|
+
|
|
21
|
+
# A result that stays on this Mac (Network X-ray's report) is never shared, sent or read but by the Mac app.
|
|
22
|
+
STAYS_ON_MAC = "This result stays on this Mac, so Mobster doesn't share it. Export it as a file to keep a copy."
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RunActionsMixin:
|
|
26
|
+
def stop_run(self, identifier):
|
|
27
|
+
announce = None
|
|
28
|
+
with self.lock:
|
|
29
|
+
run = self.runs.get(identifier)
|
|
30
|
+
if not run:
|
|
31
|
+
raise APIError("Task not found", 404, "not_found")
|
|
32
|
+
item = self.unqueue(identifier) if identifier in self.queued else None
|
|
33
|
+
with run.condition:
|
|
34
|
+
if run.finished_at is None and not run.stop.is_set():
|
|
35
|
+
run.stop.set()
|
|
36
|
+
run.emit({"event": "stop_requested"})
|
|
37
|
+
if item is not None:
|
|
38
|
+
# It never had the phone: it leaves the line and ends, doing nothing.
|
|
39
|
+
self.end_queued(run, "stopped", ownership.QUEUE_CANCELLED.format(phone=self.phone_name(item["phone"])))
|
|
40
|
+
announce = run
|
|
41
|
+
if announce is not None:
|
|
42
|
+
self.finished(announce)
|
|
43
|
+
self.publish("devices", {"event": "queue_changed", "device": announce.device_id})
|
|
44
|
+
return run
|
|
45
|
+
|
|
46
|
+
def delete_run(self, identifier):
|
|
47
|
+
with self.lock:
|
|
48
|
+
run = self.runs.get(identifier)
|
|
49
|
+
if not run:
|
|
50
|
+
raise APIError("Task not found", 404, "not_found")
|
|
51
|
+
if run.finished_at is None:
|
|
52
|
+
raise APIError("Stop this task before deleting it", 409, "run_active")
|
|
53
|
+
self.journal.delete(identifier)
|
|
54
|
+
self.runs.pop(identifier, None)
|
|
55
|
+
if getattr(self, "frames", None) is not None:
|
|
56
|
+
self.frames.delete(identifier)
|
|
57
|
+
self.publish("runs", {"event": "run_deleted", "runId": identifier})
|
|
58
|
+
|
|
59
|
+
def save_run_gif(self, identifier, theme="paper", redact=False, mp4=False, directory=None):
|
|
60
|
+
from . import run_gif
|
|
61
|
+
run = self._finished_run(identifier, "This task is still running. Save it as a GIF once it ends.")
|
|
62
|
+
frames = getattr(self, "frames", None)
|
|
63
|
+
|
|
64
|
+
def read(frame_id):
|
|
65
|
+
return frames.get(run.id, frame_id) if frames is not None else None
|
|
66
|
+
|
|
67
|
+
if not GIF_RENDERS.acquire(blocking=False):
|
|
68
|
+
raise APIError("Mobster is saving another GIF. Try again in a moment.", 429, "export_busy")
|
|
69
|
+
try:
|
|
70
|
+
public = run.public()
|
|
71
|
+
try:
|
|
72
|
+
clip = run_gif.build_clip(public, read, theme=theme, redact=redact)
|
|
73
|
+
except ValueError as error:
|
|
74
|
+
if str(error) == run_gif.NO_SCREENS:
|
|
75
|
+
raise APIError(str(error), 404, "no_frames") from None
|
|
76
|
+
raise APIError(str(error), 422, "gif_too_long") from None
|
|
77
|
+
stem = run_gif.file_stem(public.get("goal"))
|
|
78
|
+
saved = save_export_bytes(stem, "gif", clip.gif, directory)
|
|
79
|
+
result = {**saved, "frames": clip.frames, "bytes": len(clip.gif), "seconds": round(clip.seconds, 1),
|
|
80
|
+
"mp4": None}
|
|
81
|
+
if mp4:
|
|
82
|
+
data = run_gif.build_mp4(public, read, theme=theme, redact=redact)
|
|
83
|
+
if data is not None:
|
|
84
|
+
# The GIF's own name, so the two sit together ("… (1).gif" and "… (1).mp4").
|
|
85
|
+
video = save_export_bytes(Path(saved["name"]).stem, "mp4", data, directory)
|
|
86
|
+
result["mp4"] = {**video, "bytes": len(data)}
|
|
87
|
+
return result
|
|
88
|
+
finally:
|
|
89
|
+
GIF_RENDERS.release()
|
|
90
|
+
|
|
91
|
+
def add_to_evals(self, identifier):
|
|
92
|
+
"""A finished task into the local eval set, beside the journal (traces.add_to_evals): {id, path, added,
|
|
93
|
+
expected, recorded}. APIError 503 traces_off without MOBSTER_EXPERIMENTAL_TRACES, 409 run_active while it
|
|
94
|
+
runs, 422 no_tape or unreadable when it can't be replayed."""
|
|
95
|
+
run = self._finished_run(identifier, "This task is still running. Add it to your evals once it ends.")
|
|
96
|
+
state_db = getattr(self.config, "state_db", None)
|
|
97
|
+
if not traces.enabled():
|
|
98
|
+
raise APIError("Traces are off. Set MOBSTER_EXPERIMENTAL_TRACES=1 to keep tasks as evals.", 503,
|
|
99
|
+
"traces_off")
|
|
100
|
+
frames = getattr(self, "frames", None)
|
|
101
|
+
try:
|
|
102
|
+
return traces.add_to_evals({**run.public(), "app": run.app}, frames.folder(run.id) if frames else None,
|
|
103
|
+
Path(state_db).parent / "evals" if state_db else None)
|
|
104
|
+
except traces.EvalError as error:
|
|
105
|
+
raise APIError(str(error), 409 if error.code == "running" else 422, error.code) from None
|
|
106
|
+
|
|
107
|
+
def save_artifact(self, identifier, kind, blocks=None, directory=None):
|
|
108
|
+
from . import artifacts
|
|
109
|
+
if kind not in ("pdf", "png", "md"):
|
|
110
|
+
raise ValueError("format is pdf, png or md")
|
|
111
|
+
if blocks is not None and (not isinstance(blocks, list) or not 1 <= len(blocks) <= result_blocks.MAX_BLOCKS
|
|
112
|
+
or not all(isinstance(b, str) for b in blocks)):
|
|
113
|
+
raise ValueError("blocks is a list of 1 to 8 block ids")
|
|
114
|
+
if kind == "png" and (blocks is None or len(blocks) != 1):
|
|
115
|
+
raise ValueError("An image is one block: name it in blocks")
|
|
116
|
+
run = self._finished_run(identifier, "This task is still running. Save it once it ends.")
|
|
117
|
+
public = run.public(include_events=False)
|
|
118
|
+
envelope = (public.get("summary") or {}).get("blocks")
|
|
119
|
+
known = {b.get("id"): b for b in (envelope or {}).get("blocks") or ()} if isinstance(envelope, dict) else {}
|
|
120
|
+
if blocks is not None and any(b not in known for b in blocks):
|
|
121
|
+
raise APIError("That card isn't in this task's results anymore.", 404, "block_not_found")
|
|
122
|
+
frames = getattr(self, "frames", None)
|
|
123
|
+
|
|
124
|
+
def read(frame_id):
|
|
125
|
+
return self.frame(run.id, frame_id) if frames is not None else None
|
|
126
|
+
|
|
127
|
+
first = known[blocks[0]] if blocks else None
|
|
128
|
+
stem = (first.get("title") if first and first.get("title") else run.goal or "Mobster result")
|
|
129
|
+
stem = " ".join(str(stem).split())[:80]
|
|
130
|
+
if kind == "md":
|
|
131
|
+
data = artifacts.markdown(public, blocks=blocks).encode("utf-8")
|
|
132
|
+
else:
|
|
133
|
+
if not ARTIFACT_RENDERS.acquire(blocking=False):
|
|
134
|
+
raise APIError("Mobster is making another file. Try again in a moment.", 429, "export_busy")
|
|
135
|
+
try:
|
|
136
|
+
page = artifacts.document(public, blocks=blocks, frames=read, card=kind == "png",
|
|
137
|
+
report=kind == "pdf", draw_map=self._map_drawer())
|
|
138
|
+
data = artifacts.render(page, kind, title=stem)
|
|
139
|
+
except artifacts.RenderError as error:
|
|
140
|
+
raise APIError(str(error), 503, "artifact_unavailable") from None
|
|
141
|
+
finally:
|
|
142
|
+
ARTIFACT_RENDERS.release()
|
|
143
|
+
saved = save_export_bytes(stem, kind, data, directory)
|
|
144
|
+
return {**saved, "bytes": len(data)}
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _map_drawer():
|
|
148
|
+
from . import maps
|
|
149
|
+
return lambda block: maps.snapshot(maps.places_of(block), near=block.get("near"), width=720, height=340)
|
|
150
|
+
|
|
151
|
+
def block_map(self, identifier, block_id, dark=False, width=720, height=360):
|
|
152
|
+
from . import maps
|
|
153
|
+
run = self._finished_run(identifier)
|
|
154
|
+
envelope = ((run.public(include_events=False).get("summary") or {}).get("blocks"))
|
|
155
|
+
block, _ = result_blocks.find(envelope, block_id)
|
|
156
|
+
places = maps.places_of(block)
|
|
157
|
+
if not places:
|
|
158
|
+
raise APIError("That card has no places to show on a map.", 404, "block_not_found")
|
|
159
|
+
try:
|
|
160
|
+
drawn = maps.snapshot(places, near=(block or {}).get("near"), dark=dark, width=width, height=height)
|
|
161
|
+
except maps.MapError as error:
|
|
162
|
+
raise APIError(str(error), 503, "map_unavailable") from None
|
|
163
|
+
return {**{k: v for k, v in drawn.items() if k != "image"},
|
|
164
|
+
"image": "data:image/png;base64," + base64.b64encode(drawn["image"]).decode()}
|
|
165
|
+
|
|
166
|
+
def _shareable(self, identifier, people, screens, title):
|
|
167
|
+
from . import share
|
|
168
|
+
if people not in ("mask", "show") or type(screens) is not bool:
|
|
169
|
+
raise ValueError("people is mask or show, and screens is true or false")
|
|
170
|
+
if title is not None and (not isinstance(title, str) or not 1 <= len(title.strip()) <= 120):
|
|
171
|
+
raise ValueError("title is 1 to 120 characters")
|
|
172
|
+
run = self._finished_run(identifier, "This task is still running. Share it once it ends.")
|
|
173
|
+
if harness_api.stays_on_mac(run.extras):
|
|
174
|
+
raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
|
|
175
|
+
frames = getattr(self, "frames", None)
|
|
176
|
+
public = run.public(include_events=False)
|
|
177
|
+
html = share.page(public, frames=(lambda f: self.frame(run.id, f)) if frames is not None else None,
|
|
178
|
+
people=people, screens=screens, title=title)
|
|
179
|
+
if len(html.encode("utf-8")) > share.MAX_PAGE_BYTES:
|
|
180
|
+
raise APIError("That page is over 4 MB. Leave out the screens.", 413, "too_large")
|
|
181
|
+
return run, public, html
|
|
182
|
+
|
|
183
|
+
def share_preview(self, identifier, people="mask", screens=False, title=None):
|
|
184
|
+
from . import artifacts
|
|
185
|
+
run, _public, html = self._shareable(identifier, people, screens, title)
|
|
186
|
+
if not ARTIFACT_RENDERS.acquire(blocking=False):
|
|
187
|
+
raise APIError("Mobster is making another file. Try again in a moment.", 429, "export_busy")
|
|
188
|
+
try:
|
|
189
|
+
image = artifacts.render(html, "png", width=640)
|
|
190
|
+
except artifacts.RenderError as error:
|
|
191
|
+
raise APIError(str(error), 503, "artifact_unavailable") from None
|
|
192
|
+
finally:
|
|
193
|
+
ARTIFACT_RENDERS.release()
|
|
194
|
+
return {"image": "data:image/png;base64," + base64.b64encode(image).decode(),
|
|
195
|
+
"bytes": len(html.encode("utf-8"))}
|
|
196
|
+
|
|
197
|
+
def share_run(self, identifier, access_token, people="mask", screens=False, title=None, expires_days=30):
|
|
198
|
+
# The account's access token is used for this request only.
|
|
199
|
+
from . import share
|
|
200
|
+
if not isinstance(access_token, str) or not 16 <= len(access_token) <= 8192:
|
|
201
|
+
raise APIError("Sign in to Mobster to share.", 401, "session")
|
|
202
|
+
if expires_days is not None and expires_days not in share.EXPIRY_DAYS:
|
|
203
|
+
raise ValueError("expiresDays is 1, 7, 30, 90 or null")
|
|
204
|
+
run, public, html = self._shareable(identifier, people, screens, title)
|
|
205
|
+
heading = " ".join(str(title or public.get("goal") or "Mobster result").split())[:120]
|
|
206
|
+
try:
|
|
207
|
+
made = share.create(access_token, title=heading, html=html, expires_days=expires_days)
|
|
208
|
+
except share.ShareError as error:
|
|
209
|
+
raise APIError(str(error), error.status, error.code) from None
|
|
210
|
+
finally:
|
|
211
|
+
access_token = None
|
|
212
|
+
note = {"id": made["id"], "url": made["url"], "title": made.get("title") or heading,
|
|
213
|
+
"createdAt": made.get("createdAt"), "expiresAt": made.get("expiresAt"), "revokedAt": None,
|
|
214
|
+
"people": people, "screens": screens}
|
|
215
|
+
self._note_share(run, lambda shares: [note, *shares][:50])
|
|
216
|
+
return {"share": note}
|
|
217
|
+
|
|
218
|
+
def unshare_run(self, identifier, share_id, access_token):
|
|
219
|
+
from . import share
|
|
220
|
+
if not isinstance(share_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]{22}", share_id):
|
|
221
|
+
raise APIError("That link isn't one of this task's.", 404, "not_found")
|
|
222
|
+
if not isinstance(access_token, str) or not 16 <= len(access_token) <= 8192:
|
|
223
|
+
raise APIError("Sign in to Mobster to turn the link off.", 401, "session")
|
|
224
|
+
with self.lock:
|
|
225
|
+
run = self.runs.get(identifier)
|
|
226
|
+
if not run or not any(item.get("id") == share_id for item in run.shares):
|
|
227
|
+
raise APIError("That link isn't one of this task's.", 404, "not_found")
|
|
228
|
+
try:
|
|
229
|
+
gone = share.revoke(access_token, share_id)
|
|
230
|
+
except share.ShareError as error:
|
|
231
|
+
raise APIError(str(error), error.status, error.code) from None
|
|
232
|
+
finally:
|
|
233
|
+
access_token = None
|
|
234
|
+
revoked = gone.get("revokedAt") or time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
|
235
|
+
self._note_share(run, lambda shares: [{**item, "revokedAt": item.get("revokedAt") or revoked}
|
|
236
|
+
if item.get("id") == share_id else item for item in shares])
|
|
237
|
+
return {"share": next(item for item in run.shares if item.get("id") == share_id)}
|
|
238
|
+
|
|
239
|
+
def _note_share(self, run, change):
|
|
240
|
+
with run.condition:
|
|
241
|
+
updated = change([dict(item) for item in run.shares])
|
|
242
|
+
if run.journal:
|
|
243
|
+
try:
|
|
244
|
+
run.journal.annotate(run.id, {"shares": updated})
|
|
245
|
+
except JournalError:
|
|
246
|
+
pass # the link exists either way; only this Mac's note of it is lost
|
|
247
|
+
run.shares = updated
|
|
248
|
+
self.publish("runs", {"event": "run_shared", "runId": run.id})
|
|
249
|
+
|
|
250
|
+
def review_run(self, identifier, ok):
|
|
251
|
+
run = self._finished_run(identifier, "This task is still running")
|
|
252
|
+
with run.condition:
|
|
253
|
+
if run.journal:
|
|
254
|
+
run.journal.annotate(run.id, {"reviewedOk": ok})
|
|
255
|
+
run.reviewed_ok = ok
|
|
256
|
+
self.publish("runs", {"event": "run_reviewed", "runId": run.id, "reviewedOk": ok})
|
|
257
|
+
return run
|
|
258
|
+
|
|
259
|
+
def decide_block_item(self, identifier, block_id, item_id, decision, text=None):
|
|
260
|
+
if decision not in ("approve", "skip", "reopen"):
|
|
261
|
+
raise ValueError("decision is approve, skip or reopen")
|
|
262
|
+
run = self._finished_run(identifier, "This task is still running")
|
|
263
|
+
blocks = (run.summary or {}).get("blocks")
|
|
264
|
+
block, item = result_blocks.find(blocks, block_id, item_id)
|
|
265
|
+
if block is None:
|
|
266
|
+
raise APIError("That item isn't in this task's results anymore.", 404, "block_item_not_found")
|
|
267
|
+
source = self._brief_source(run, block_id)
|
|
268
|
+
if source is not None:
|
|
269
|
+
# A brief's card is its task's: the item is decided once wherever it shows, and _block_state mirrors it.
|
|
270
|
+
answer = self.decide_block_item(source[0], source[1], item_id, decision, text)
|
|
271
|
+
with run.condition:
|
|
272
|
+
return {**answer, "blockState": dict(run.block_state)}
|
|
273
|
+
key = f"{block_id}/{item_id}"
|
|
274
|
+
with run.condition:
|
|
275
|
+
current = dict(run.block_state.get(key) or {})
|
|
276
|
+
if current.get("state") == "approved":
|
|
277
|
+
if decision == "approve" and current.get("runId"):
|
|
278
|
+
return {"state": "approved", "runId": current["runId"], "replayed": True,
|
|
279
|
+
"blockState": dict(run.block_state)}
|
|
280
|
+
raise APIError("You already approved this one, so it can't change now.", 409, "block_item_approved",
|
|
281
|
+
runId=current.get("runId"))
|
|
282
|
+
if decision in ("skip", "reopen"):
|
|
283
|
+
if decision == "reopen" and current.get("state") != "skipped":
|
|
284
|
+
raise APIError("Only a skipped item can go back in the queue.", 409, "block_item_not_skipped")
|
|
285
|
+
state = {"state": "skipped", "at": time.time() * 1000} if decision == "skip" else None
|
|
286
|
+
return self._block_state(run, key, state)
|
|
287
|
+
if block.get("type") != "review_queue":
|
|
288
|
+
raise APIError("Only a draft in a review queue can be approved.", 409, "block_item_not_approvable")
|
|
289
|
+
try:
|
|
290
|
+
block, item, words = result_blocks.approval_item(blocks, block_id, item_id, text)
|
|
291
|
+
except result_blocks.BlockError as problem:
|
|
292
|
+
raise APIError(str(problem), 400, "block_item_invalid") from None
|
|
293
|
+
goal = result_blocks.follow_up_goal(block, item, words)
|
|
294
|
+
app_id = next((app["id"] for app in self.apps_for(None) if item.get("bundleId")
|
|
295
|
+
and app.get("bundleId") == item["bundleId"]), None)
|
|
296
|
+
# One task per item, ever: a retried Approve (a lost response, a double click) gets the task it started.
|
|
297
|
+
request_key = f"rb-{run.id}-{hashlib.sha256(key.encode()).hexdigest()[:16]}"
|
|
298
|
+
thread_id = (run.extras or {}).get("threadId") if isinstance(run.extras, dict) else None
|
|
299
|
+
follow = None
|
|
300
|
+
if thread_id:
|
|
301
|
+
from .threads.service import service_for
|
|
302
|
+
service = service_for(self)
|
|
303
|
+
if service is not None:
|
|
304
|
+
_status, response = service.message(thread_id, {"text": goal, "mode": "new_task",
|
|
305
|
+
**({"appId": app_id} if app_id else {})},
|
|
306
|
+
origin="app", key=request_key)
|
|
307
|
+
follow = self.runs.get(((response or {}).get("run") or {}).get("id") or "")
|
|
308
|
+
if follow is None:
|
|
309
|
+
same_phone = ({"device": run.device_id} if run.device_id and getattr(self, "fleet", None) is not None
|
|
310
|
+
and self.pool is None else {})
|
|
311
|
+
created = self.create(app_id, goal, "live", request_key, origin="app", **same_phone)
|
|
312
|
+
follow = created[0] if isinstance(created, tuple) else created
|
|
313
|
+
with follow.condition:
|
|
314
|
+
if not follow.preapproved and follow.finished_at is None:
|
|
315
|
+
follow.preapproved = [result_blocks.preapproval(block, item, words)]
|
|
316
|
+
return {**self._block_state(run, key, {"state": "approved", "runId": follow.id, "at": time.time() * 1000}),
|
|
317
|
+
"runId": follow.id}
|
|
318
|
+
|
|
319
|
+
def _brief_source(self, run, block_id):
|
|
320
|
+
brief = (run.summary or {}).get("brief") if isinstance(run.summary, dict) else None
|
|
321
|
+
found = ((brief or {}).get("sources") or {}).get(block_id) if isinstance(brief, dict) else None
|
|
322
|
+
if (isinstance(found, list) and len(found) == 2 and all(isinstance(part, str) for part in found)
|
|
323
|
+
and found[0] != run.id and found[0] in self.runs):
|
|
324
|
+
return found[0], found[1]
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
def _mirror_briefs(self, run, key, state):
|
|
328
|
+
block_id, _, item_id = key.partition("/")
|
|
329
|
+
with self.lock:
|
|
330
|
+
briefs = [other for other in self.runs.values() if other is not run and isinstance(other.summary, dict)
|
|
331
|
+
and isinstance(other.summary.get("brief"), dict)]
|
|
332
|
+
for other in briefs:
|
|
333
|
+
for card, found in ((other.summary["brief"].get("sources") or {}).items()):
|
|
334
|
+
if found == [run.id, block_id]:
|
|
335
|
+
try:
|
|
336
|
+
self._block_state(other, f"{card}/{item_id}", state, mirror=False)
|
|
337
|
+
except APIError:
|
|
338
|
+
harness_api.log.warning("brief %s: couldn't save a copy of a decision", other.id)
|
|
339
|
+
|
|
340
|
+
def _block_state(self, run, key, state, mirror=True):
|
|
341
|
+
# Journaled before the app is told.
|
|
342
|
+
with run.condition:
|
|
343
|
+
updated = dict(run.block_state)
|
|
344
|
+
if state is None:
|
|
345
|
+
updated.pop(key, None)
|
|
346
|
+
else:
|
|
347
|
+
updated[key] = state
|
|
348
|
+
if run.journal:
|
|
349
|
+
try:
|
|
350
|
+
run.journal.annotate(run.id, {"blockState": updated})
|
|
351
|
+
except JournalError:
|
|
352
|
+
raise APIError("Mobster couldn't save that. Try again.", 503, "journal_unavailable") from None
|
|
353
|
+
run.block_state = updated
|
|
354
|
+
self.publish("runs", {"event": "run_block_decided", "runId": run.id, "item": key,
|
|
355
|
+
"state": (state or {}).get("state", "open")})
|
|
356
|
+
if mirror:
|
|
357
|
+
self._mirror_briefs(run, key, state)
|
|
358
|
+
return {"state": (state or {}).get("state", "open"), "blockState": dict(updated)}
|
|
359
|
+
|
|
360
|
+
def send_run(self, identifier, destination):
|
|
361
|
+
from . import run_delivery
|
|
362
|
+
run = self._finished_run(identifier, "This task is still running. Send it once it ends.")
|
|
363
|
+
if harness_api.stays_on_mac(run.extras):
|
|
364
|
+
raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
|
|
365
|
+
return run_delivery.send(self, run, destination)
|
|
366
|
+
|
|
367
|
+
def file_run_issue(self, identifier, body):
|
|
368
|
+
from . import run_delivery
|
|
369
|
+
run = self._finished_run(identifier, "This task is still running. File it once it ends.")
|
|
370
|
+
if harness_api.stays_on_mac(run.extras):
|
|
371
|
+
raise APIError(STAYS_ON_MAC, 403, "stays_on_mac")
|
|
372
|
+
return run_delivery.file_issue(self, run, body)
|
|
373
|
+
|
|
374
|
+
def inbox(self):
|
|
375
|
+
from . import inbox
|
|
376
|
+
with self.lock:
|
|
377
|
+
runs = list(self.runs.values())
|
|
378
|
+
try:
|
|
379
|
+
workflows = self.workflows.list_with_health() if getattr(self, "workflows", None) is not None else []
|
|
380
|
+
except JournalError:
|
|
381
|
+
workflows = []
|
|
382
|
+
items = inbox.collect(runs, workflows)
|
|
383
|
+
return {"items": items, "count": len(items)}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Runtime's settings, keys and the status every surface shows."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from . import __version__, engines, listen, permissions, tracks
|
|
6
|
+
from .api_errors import APIError
|
|
7
|
+
from .devices import AmbiguousDevice, DeviceNotFound
|
|
8
|
+
from .env_secrets import update_env_file
|
|
9
|
+
from .gemini import configured as helper_configured
|
|
10
|
+
from .keys import Keys
|
|
11
|
+
from .power_watch import PAUSE_RANGE as BATTERY_PAUSE_RANGE
|
|
12
|
+
from .power_watch import phone_pause_threshold
|
|
13
|
+
from .runtime_limits import MONTH_LIMIT_RANGE, TASK_LIMIT_RANGE
|
|
14
|
+
from .wda import frame_source
|
|
15
|
+
from .wda.video import mjpeg_settings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StatusMixin:
|
|
19
|
+
def settings(self):
|
|
20
|
+
value = {"askBeforeActing": self.ask_before_acting(), "taskCostLimitUsd": self.task_cost_limit(),
|
|
21
|
+
"monthlyLimitUsd": self.monthly_limit(), "permissionMode": permissions.from_env().mode,
|
|
22
|
+
"phoneBatteryPause": phone_pause_threshold(),
|
|
23
|
+
"hearPhone": listen.enabled()}
|
|
24
|
+
if frame_source.streams(getattr(self, "video", None)):
|
|
25
|
+
value["videoFps"], value["videoDetail"] = self.video_quality()
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def env_file(self):
|
|
30
|
+
return self.setup.env_file if getattr(self, "setup", None) else getattr(self.config, "env_file", None)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def keys(self):
|
|
34
|
+
return Keys(self.env_file)
|
|
35
|
+
|
|
36
|
+
def save_keys(self, body):
|
|
37
|
+
# Ask Smart's provider afresh: a project the user just fixed must not wait out ModelReach's cached refusal.
|
|
38
|
+
state = self.keys.update(body)
|
|
39
|
+
reach = getattr(self, "model_reach", None)
|
|
40
|
+
if reach is not None and {"openai", "anthropic", "helper"} & set(body):
|
|
41
|
+
reach.forget(engines.smart_key())
|
|
42
|
+
return state
|
|
43
|
+
|
|
44
|
+
def check_key(self, target):
|
|
45
|
+
model = engines.smart_model()
|
|
46
|
+
# The key the test is about to read, when it is the one Smart runs on.
|
|
47
|
+
key = engines.smart_key() if target == engines.model_provider(model) else None
|
|
48
|
+
result = self.keys.test(target)
|
|
49
|
+
reach = getattr(self, "model_reach", None)
|
|
50
|
+
# The test takes up to a minute: a key saved meanwhile was never tested, and the next status checks it.
|
|
51
|
+
if key and reach is not None and engines.smart_key() == key:
|
|
52
|
+
if result.get("ok"):
|
|
53
|
+
reach.record(key, True)
|
|
54
|
+
elif result.get("problem") == "no_credit":
|
|
55
|
+
reach.record(key, False, engines.no_credit(model))
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
def update_settings(self, changes):
|
|
59
|
+
allowed = {"askBeforeActing", "videoFps", "videoDetail", "taskCostLimitUsd", "monthlyLimitUsd",
|
|
60
|
+
"phoneBatteryPause", "hearPhone"}
|
|
61
|
+
if not isinstance(changes, dict) or not changes or set(changes) - allowed:
|
|
62
|
+
raise ValueError("Unsupported setting")
|
|
63
|
+
saved = {}
|
|
64
|
+
if "askBeforeActing" in changes:
|
|
65
|
+
if type(changes["askBeforeActing"]) is not bool:
|
|
66
|
+
raise ValueError("askBeforeActing must be true or false")
|
|
67
|
+
saved["MOBSTER_ASK_BEFORE_ACTING"] = "1" if changes["askBeforeActing"] else "0"
|
|
68
|
+
# The older switch sets the mode: off is Don't ask; on keeps a mode that asks, else the default.
|
|
69
|
+
mode = permissions.from_env().mode
|
|
70
|
+
saved["MOBSTER_PERMISSION_MODE"] = permissions.policy.mode_for_ask(changes["askBeforeActing"], mode)
|
|
71
|
+
if "hearPhone" in changes:
|
|
72
|
+
if type(changes["hearPhone"]) is not bool:
|
|
73
|
+
raise ValueError("hearPhone must be true or false")
|
|
74
|
+
saved[listen.SETTING] = "on" if changes["hearPhone"] else "off"
|
|
75
|
+
if "phoneBatteryPause" in changes:
|
|
76
|
+
low, high = BATTERY_PAUSE_RANGE
|
|
77
|
+
percent = changes["phoneBatteryPause"]
|
|
78
|
+
if percent is not None and (type(percent) is not int or not low <= percent <= high):
|
|
79
|
+
raise ValueError(f"phoneBatteryPause must be null or a whole percent from {low} to {high}")
|
|
80
|
+
saved["MOBSTER_PHONE_BATTERY_PAUSE"] = "off" if percent is None else str(percent)
|
|
81
|
+
for name, env, (low, high) in (("taskCostLimitUsd", "MOBSTER_TASK_COST_LIMIT", TASK_LIMIT_RANGE),
|
|
82
|
+
("monthlyLimitUsd", "MOBSTER_MONTHLY_LIMIT", MONTH_LIMIT_RANGE)):
|
|
83
|
+
if name not in changes:
|
|
84
|
+
continue
|
|
85
|
+
amount = changes[name]
|
|
86
|
+
if amount is None:
|
|
87
|
+
saved[env] = "off" if name == "taskCostLimitUsd" else ""
|
|
88
|
+
continue
|
|
89
|
+
if type(amount) not in (int, float) or not low <= amount <= high:
|
|
90
|
+
raise ValueError(f"{name} must be null or between ${low:g} and ${high:,g}")
|
|
91
|
+
saved[env] = f"{round(float(amount), 2):.2f}"
|
|
92
|
+
if {"videoFps", "videoDetail"} & set(changes):
|
|
93
|
+
if not frame_source.streams(getattr(self, "video", None)):
|
|
94
|
+
raise ValueError("Live video settings need a USB iPhone")
|
|
95
|
+
fps, detail = self.video_quality()
|
|
96
|
+
fps, detail = changes.get("videoFps", fps), changes.get("videoDetail", detail)
|
|
97
|
+
if type(fps) is not int:
|
|
98
|
+
raise ValueError("videoFps must be 15, 30 or 60")
|
|
99
|
+
mjpeg_settings(fps, detail) # validates both
|
|
100
|
+
saved.update(MOBSTER_VIDEO_FPS=str(fps), MOBSTER_VIDEO_DETAIL=detail)
|
|
101
|
+
if self.env_file:
|
|
102
|
+
update_env_file(self.env_file, saved)
|
|
103
|
+
os.environ.update(saved)
|
|
104
|
+
if "MOBSTER_VIDEO_FPS" in saved:
|
|
105
|
+
self.video.set_quality(*self.video_quality())
|
|
106
|
+
for video in (self.fleet.videos() if getattr(self, "fleet", None) is not None else ()):
|
|
107
|
+
video.set_quality(*self.video_quality())
|
|
108
|
+
return self.settings()
|
|
109
|
+
|
|
110
|
+
def device_status(self, phone=None):
|
|
111
|
+
if phone is None:
|
|
112
|
+
fleet = getattr(self, "fleet", None)
|
|
113
|
+
phone = fleet.primary if fleet is not None else None
|
|
114
|
+
return {"phone": self.power.phone(phone) if phone is not None else {},
|
|
115
|
+
"mac": {**self.power.mac(), **self.power.awake.status()},
|
|
116
|
+
"warnings": self.power.warnings(),
|
|
117
|
+
"phoneBatteryPause": phone_pause_threshold()}
|
|
118
|
+
|
|
119
|
+
def engines_status(self):
|
|
120
|
+
managed = getattr(self, "setup", None) is not None
|
|
121
|
+
key, model = engines.smart_key(), engines.smart_model()
|
|
122
|
+
reach = getattr(self, "model_reach", None)
|
|
123
|
+
if self.scripted_models():
|
|
124
|
+
smart = (True, None)
|
|
125
|
+
elif not key:
|
|
126
|
+
smart = (False, engines.no_key_reason(managed, model))
|
|
127
|
+
else:
|
|
128
|
+
smart = reach.state(key, model) if reach is not None else (True, None)
|
|
129
|
+
return {engines.SMART: {"available": smart[0], "reason": smart[1], "model": model,
|
|
130
|
+
"provider": engines.provider_name(model),
|
|
131
|
+
# Whose bill: a plan (Kimi Code, Grok ...) or a key (ai_plans/providers.py).
|
|
132
|
+
"billing": "plan" if engines.on_plan(model) else "key"}}
|
|
133
|
+
|
|
134
|
+
def engine(self):
|
|
135
|
+
return self.engine_sampler.snapshot()
|
|
136
|
+
|
|
137
|
+
def status(self):
|
|
138
|
+
target = self.target_status()
|
|
139
|
+
device, ready, health = target["device"], target["ready"], target["health"]
|
|
140
|
+
wda = bool(self.config.wda_url)
|
|
141
|
+
available = self.engines_status()
|
|
142
|
+
key = available[engines.SMART]["available"]
|
|
143
|
+
return {"api_version": "v1", "version": __version__, "helper_configured": helper_configured(),
|
|
144
|
+
"engines": available, "agents": self.agents_status(), "engine": self.engine(),
|
|
145
|
+
"live_enabled": bool(not self.closing and self.config.enable_live and key and ready
|
|
146
|
+
and target["can_act"]),
|
|
147
|
+
"device_ready": ready, "durable_history": getattr(self.config, "state_db", None) is not None,
|
|
148
|
+
"screen_reading": target["screen_reading"], "ask_before_acting": self.ask_before_acting(),
|
|
149
|
+
"spend_cap_usd": getattr(self.config, "spend_cap_usd", None),
|
|
150
|
+
"devices": self.pool.public() if self.pool else [],
|
|
151
|
+
"concurrency": {"capacity": self.pool.size if self.pool else self.device_count(),
|
|
152
|
+
"running": len(self.active_runs),
|
|
153
|
+
"available": self.capacity()},
|
|
154
|
+
"device": "Your iPhone" if device else None,
|
|
155
|
+
"driver": target["driver"],
|
|
156
|
+
"extensions": dict(tracks.STATUS),
|
|
157
|
+
"limitations": self.limitations(key=key, device=device, ready=ready, health=health, wda=wda,
|
|
158
|
+
can_act=target["can_act"],
|
|
159
|
+
engine_reason=available[engines.SMART]["reason"])}
|
|
160
|
+
|
|
161
|
+
def device_count(self):
|
|
162
|
+
fleet = getattr(self, "fleet", None)
|
|
163
|
+
if fleet is None:
|
|
164
|
+
return 1
|
|
165
|
+
with fleet.lock:
|
|
166
|
+
others = sum(1 for phone in fleet.phones.values() if phone.wda_url)
|
|
167
|
+
return 1 + others
|
|
168
|
+
|
|
169
|
+
def resolve_default(self, sticky=True):
|
|
170
|
+
try:
|
|
171
|
+
return self.fleet.default(sticky=sticky)
|
|
172
|
+
except LookupError as error:
|
|
173
|
+
raise APIError(str(error), 404, "device_not_found") from None
|
|
174
|
+
|
|
175
|
+
def resolve_device(self, device):
|
|
176
|
+
try:
|
|
177
|
+
return self.fleet.for_run(device)
|
|
178
|
+
except DeviceNotFound as error:
|
|
179
|
+
raise APIError(str(error), 404, "device_not_found") from None
|
|
180
|
+
except AmbiguousDevice as error:
|
|
181
|
+
raise APIError(str(error), 400, "device_ambiguous") from None
|