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
mobile_agent/dry_run.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""``mobster run --dry-run`` and ``"dryRun": true`` on POST /api/runs: what a task would do, read from its words alone.
|
|
2
|
+
|
|
3
|
+
Nothing starts: no run, no journal entry, no phone, no model call, no count against a policy's daily cap. The plan
|
|
4
|
+
names the apps the task would open, the outward acts its words ask for and whether Mobster would ask about each one
|
|
5
|
+
under the person's permission mode and the project's policy, and the estimated cost. The screens are not listed:
|
|
6
|
+
Mobster reads them from the phone as the task goes, and a dry run never touches it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from . import costs
|
|
12
|
+
from .app_choice import infer_app
|
|
13
|
+
from .permissions import gate as permission_gate
|
|
14
|
+
from .permissions import policy as P
|
|
15
|
+
from .permissions import project_policy
|
|
16
|
+
from .task_policy import COMMIT_CONTROL, asked_part
|
|
17
|
+
|
|
18
|
+
MAX_ACTS = 12
|
|
19
|
+
_NOUN_BEFORE = re.compile(r"\b(?:the|a|an|best|my|your|its|top|new|each|every|one-line|last|\d+)\s*$", re.I)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _acts(goal):
|
|
23
|
+
"""[(word, act)] the task's own words ask for, once each, in order; a prohibition ("don't send") asks for none."""
|
|
24
|
+
from .harness_api import commit_act
|
|
25
|
+
from .task_policy import PROHIBITION_SENTENCE
|
|
26
|
+
# "Draft a reply. Don't send anything." rules out every act of the kind it names.
|
|
27
|
+
ruled_out = {kind for sentence in PROHIBITION_SENTENCE.findall(goal) for found in COMMIT_CONTROL.finditer(sentence)
|
|
28
|
+
for kind in project_policy.kinds_of("commit", commit_act("TAP", found.group(0), None, None))}
|
|
29
|
+
found, seen = [], set()
|
|
30
|
+
asked = asked_part(goal)
|
|
31
|
+
for match in COMMIT_CONTROL.finditer(asked):
|
|
32
|
+
if _NOUN_BEFORE.search(asked[:match.start()]):
|
|
33
|
+
continue # "the best post", "needs a reply": the word names a thing
|
|
34
|
+
word = " ".join(match.group(0).casefold().split())
|
|
35
|
+
act = commit_act("TAP", word, None, None) or word
|
|
36
|
+
if act not in seen and not project_policy.kinds_of("commit", act) & ruled_out:
|
|
37
|
+
seen.add(act)
|
|
38
|
+
found.append((word, act))
|
|
39
|
+
return found[:MAX_ACTS]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _apps(goal, apps):
|
|
43
|
+
"""The apps the task would open: the one its words name, then any other app it names."""
|
|
44
|
+
chosen = infer_app(goal, apps)
|
|
45
|
+
named = [app for app in sorted(apps, key=lambda a: -len(a["name"]))
|
|
46
|
+
if len(app["name"]) >= 4 and app is not chosen and re.search(
|
|
47
|
+
rf"(?<![\w-]){re.escape(app['name'])}(?![\w-])", goal, re.I)]
|
|
48
|
+
return [app for app in [chosen, *named] if app]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def plan(goal, *, apps, mode=P.DEFAULT_MODE, policy=None, profile=None, origin=None, estimator=None, now=None):
|
|
52
|
+
"""The dry run of ``goal`` as JSON. ``apps``: the phone's apps (catalog shape). ``estimator(goal, names)``: the cost
|
|
53
|
+
range, default ``costs.smart_estimate``. Reads the policy's counts and never writes them."""
|
|
54
|
+
goal = " ".join(str(goal or "").replace("’", "'").split())
|
|
55
|
+
chosen = _apps(goal, apps)
|
|
56
|
+
names = [app["name"] for app in apps]
|
|
57
|
+
cost = (estimator or costs.smart_estimate)(goal, names)
|
|
58
|
+
mode = P.effective_mode(mode, None, origin)
|
|
59
|
+
bundle = chosen[0]["bundleId"] if chosen else ""
|
|
60
|
+
asks = []
|
|
61
|
+
for word, act in _acts(goal):
|
|
62
|
+
kinds = project_policy.kinds_of("commit", act)
|
|
63
|
+
by_mode = permission_gate.decision_for("commit", mode, existing=True) == "ask"
|
|
64
|
+
by_policy = policy.asks(kinds, bundle, profile, now=now) if policy is not None else None
|
|
65
|
+
if by_mode or by_policy:
|
|
66
|
+
asks.append({"act": act, "words": word, "kinds": sorted(kinds), "bundleId": bundle or None,
|
|
67
|
+
"why": "your permission mode asks" if by_mode and not by_policy else by_policy,
|
|
68
|
+
"by": "policy" if by_policy and not by_mode else "mode"})
|
|
69
|
+
return {"goal": goal, "dryRun": True, "sent": False, "apps": [{"id": a["id"], "name": a["name"],
|
|
70
|
+
"bundleId": a["bundleId"]} for a in chosen],
|
|
71
|
+
"permissionMode": mode, "asks": asks, "policy": _policy(policy),
|
|
72
|
+
"cost": {"lowUsd": cost.get("lowUsd"), "highUsd": cost.get("highUsd"), "model": cost.get("model"),
|
|
73
|
+
"billing": cost.get("billing") or "api"},
|
|
74
|
+
"screens": "Mobster reads each screen from the phone as the task goes. A dry run never touches it."}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _policy(policy):
|
|
78
|
+
if policy is None:
|
|
79
|
+
return None
|
|
80
|
+
return {"file": str(policy.path) if policy.path else None, "rules": len(policy.rules), "error": policy.error}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def lines(result):
|
|
84
|
+
"""The plan as text for a terminal."""
|
|
85
|
+
out = [f"Dry run: {result['goal']}", "Nothing was sent to your iPhone."]
|
|
86
|
+
apps = ", ".join(a["name"] for a in result["apps"])
|
|
87
|
+
out.append(f"Apps it would open: {apps}." if apps else "It names no app, so it starts on the Home Screen.")
|
|
88
|
+
if result["asks"]:
|
|
89
|
+
out.append("It would ask before:")
|
|
90
|
+
out += [f" {a['act']}: {a['why']}" for a in result["asks"]]
|
|
91
|
+
else:
|
|
92
|
+
out.append("It would ask about nothing its words name. Mobster still asks about any step that sends, buys, "
|
|
93
|
+
"posts or deletes.")
|
|
94
|
+
cost = result["cost"]
|
|
95
|
+
if cost["billing"] == "plan":
|
|
96
|
+
out.append("Estimated cost: nothing per call, it runs on your plan.")
|
|
97
|
+
elif cost["lowUsd"] is not None:
|
|
98
|
+
out.append(f"Estimated cost: ${cost['lowUsd']:.2f} to ${cost['highUsd']:.2f}.")
|
|
99
|
+
if result["policy"] and result["policy"]["error"]:
|
|
100
|
+
out.append(f"Your project policy can't be read, so every act asks: {result['policy']['error']}")
|
|
101
|
+
out.append(result["screens"])
|
|
102
|
+
return out
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""One run's ledger of side effects: one whose outcome is acknowledged or unknown is never sent again."""
|
|
2
|
+
|
|
3
|
+
# Operations that can leave a durable effect on the device.
|
|
4
|
+
PERSISTENT_OPERATIONS = frozenset({"TAP", "TYPE", "TYPE_SUBMIT", "SET_TEXT", "INCREMENT", "DECREMENT", "SUBMIT"})
|
|
5
|
+
|
|
6
|
+
# Proven refusals and outcomes settle/resolve reconciled; any other outcome, unfamiliar ones too, blocks a replay.
|
|
7
|
+
RELEASING_OUTCOMES = frozenset({
|
|
8
|
+
"not_dispatched",
|
|
9
|
+
"failed_pre_dispatch",
|
|
10
|
+
"observed_absent", # a fresh look after an unknown outcome showed the action did not happen (settle)
|
|
11
|
+
"resolved", # the same effect, sent again after that look or with the person's OK, came back answered
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
# A refusal before anything was sent. It releases a slot nothing was sent through, never one an earlier send holds.
|
|
15
|
+
PRE_DISPATCH_REFUSALS = frozenset({"not_dispatched", "failed_pre_dispatch"})
|
|
16
|
+
|
|
17
|
+
# Times a look showing nothing happened can release one unknown outcome; a retry unknown again stays held.
|
|
18
|
+
RETRY_LIMIT = 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EffectLedger:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self._effects = {}
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _key(operation, target_id, text=None, effect=None):
|
|
27
|
+
if effect is not None:
|
|
28
|
+
return effect
|
|
29
|
+
return (operation, target_id, text)
|
|
30
|
+
|
|
31
|
+
def would_duplicate(self, operation, target_id, text=None, *, effect=None):
|
|
32
|
+
"""Whether this effect was already attempted on this run with an acknowledged or unknown outcome."""
|
|
33
|
+
if operation not in PERSISTENT_OPERATIONS:
|
|
34
|
+
return False
|
|
35
|
+
record = self._effects.get(self._key(operation, target_id, text, effect))
|
|
36
|
+
return record is not None and record["outcome"] not in RELEASING_OUTCOMES
|
|
37
|
+
|
|
38
|
+
def record_outcome(self, operation, target_id, text=None, outcome="acknowledged", *,
|
|
39
|
+
effect=None, context=None):
|
|
40
|
+
"""Record a dispatch outcome: only a pre-dispatch refusal releases the slot; unknown is never retried."""
|
|
41
|
+
if operation not in PERSISTENT_OPERATIONS:
|
|
42
|
+
return
|
|
43
|
+
if not isinstance(outcome, str) or not outcome:
|
|
44
|
+
raise ValueError("Effect outcome must be a non-empty string")
|
|
45
|
+
key = self._key(operation, target_id, text, effect)
|
|
46
|
+
record = self._effects.get(key)
|
|
47
|
+
if (record is not None and outcome in PRE_DISPATCH_REFUSALS
|
|
48
|
+
and record["outcome"] not in RELEASING_OUTCOMES):
|
|
49
|
+
return # a later refusal says nothing about the earlier attempt, which may have run
|
|
50
|
+
if record is None:
|
|
51
|
+
record = {"operation": operation, "target_id": target_id, "text": text}
|
|
52
|
+
self._effects[key] = record
|
|
53
|
+
record["outcome"] = outcome
|
|
54
|
+
if context is not None:
|
|
55
|
+
record["context"] = context
|
|
56
|
+
|
|
57
|
+
def lookup(self, operation, target_id, text=None, *, effect=None):
|
|
58
|
+
"""A copy of this logical effect's record (operation, outcome, context...), or None."""
|
|
59
|
+
record = self._effects.get(self._key(operation, target_id, text, effect))
|
|
60
|
+
return dict(record) if record is not None else None
|
|
61
|
+
|
|
62
|
+
def held(self, match):
|
|
63
|
+
"""Keys of the effects still held back (a blocking outcome) whose record ``match(key, record)`` accepts."""
|
|
64
|
+
return [key for key, record in self._effects.items()
|
|
65
|
+
if record["outcome"] not in RELEASING_OUTCOMES and match(key, dict(record))]
|
|
66
|
+
|
|
67
|
+
def settle(self, operation, target_id, text=None, *, effect=None, happened):
|
|
68
|
+
"""Settle an unknown outcome from a look: happened holds it, not happened releases it RETRY_LIMIT times."""
|
|
69
|
+
record = self._effects.get(self._key(operation, target_id, text, effect))
|
|
70
|
+
if record is None or record["outcome"] != "unknown":
|
|
71
|
+
return record["outcome"] if record is not None else None
|
|
72
|
+
if happened:
|
|
73
|
+
record["outcome"] = "observed_change"
|
|
74
|
+
elif record.get("retries", 0) < RETRY_LIMIT:
|
|
75
|
+
record["outcome"] = "observed_absent"
|
|
76
|
+
record["retries"] = record.get("retries", 0) + 1
|
|
77
|
+
return record["outcome"]
|
|
78
|
+
|
|
79
|
+
def resolve(self, operation, target_id, text=None, *, effect=None):
|
|
80
|
+
"""The same effect was sent again and came back answered: nothing is unknown, and its retries start over."""
|
|
81
|
+
record = self._effects.get(self._key(operation, target_id, text, effect))
|
|
82
|
+
if record is not None and record["outcome"] in ("unknown", "observed_absent"):
|
|
83
|
+
record["outcome"], record["retries"] = "resolved", 0
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""One answer to "is the engine OK?": the state every surface shows. Its words and fix are a row of ``states.ROWS``."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .states import (
|
|
7
|
+
DOWN,
|
|
8
|
+
FROZEN,
|
|
9
|
+
HELPER_DOWN,
|
|
10
|
+
LIVE_OFF,
|
|
11
|
+
MODEL_UNAVAILABLE,
|
|
12
|
+
NO_PHONE,
|
|
13
|
+
READY,
|
|
14
|
+
RESTARTING,
|
|
15
|
+
ROWS,
|
|
16
|
+
SLOW,
|
|
17
|
+
STOPPING,
|
|
18
|
+
WAITING_FOR_PHONE,
|
|
19
|
+
fix_public,
|
|
20
|
+
row,
|
|
21
|
+
words,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# The state said first when several stand, most blocking first. A state not in the list never outranks one that is.
|
|
25
|
+
PRIORITY = (STOPPING, MODEL_UNAVAILABLE, LIVE_OFF, NO_PHONE, HELPER_DOWN, WAITING_FOR_PHONE)
|
|
26
|
+
|
|
27
|
+
TOPIC = "engine"
|
|
28
|
+
EVENT = "engine_health"
|
|
29
|
+
|
|
30
|
+
# Worst-case detection (device_manager.PROBE_TICK + PROBE_INTERVAL + wda_ready + PROBE_TIMEOUT + SAMPLE_S) must stay <=
|
|
31
|
+
# NAMED_WITHIN_S; test_engine_health asserts it.
|
|
32
|
+
SAMPLE_S = 1.0
|
|
33
|
+
SLOW_AFTER_S = 3.0
|
|
34
|
+
FROZEN_AFTER_S = 8.0
|
|
35
|
+
NAMED_WITHIN_S = 10.0
|
|
36
|
+
|
|
37
|
+
ACTION_UNKNOWN_STEP = ("An action was on its way when the phone went away. Mobster looks at the screen before it goes "
|
|
38
|
+
"on and doesn't send that action again.")
|
|
39
|
+
|
|
40
|
+
# The causes a helper log line or a phone state can name (runner_hints.RUNNER_HINTS).
|
|
41
|
+
PHONE_CAUSES = frozenset(r.cause for r in ROWS if r.state == WAITING_FOR_PHONE and "app" in r.surfaces)
|
|
42
|
+
TASK_WAIT_CAUSES = {"lock_screen": "locked", "passcode_keypad": "locked", "unplugged": "unplugged",
|
|
43
|
+
"wifi_lost": "wifi_lost"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def task_wait(kind, phone=None):
|
|
47
|
+
"""(need, sentence) for what a task waits on (``handoff_waiting``'s kind), or None for a kind that has no words."""
|
|
48
|
+
cause = TASK_WAIT_CAUSES.get(kind)
|
|
49
|
+
if cause is None:
|
|
50
|
+
return None
|
|
51
|
+
_message, step, need = words(WAITING_FOR_PHONE, cause, phone)
|
|
52
|
+
return need, step
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Condition:
|
|
56
|
+
"""One thing that is not as it should be."""
|
|
57
|
+
|
|
58
|
+
__slots__ = ("state", "cause", "message", "next_step", "need", "fix", "since", "device", "device_name", "task",
|
|
59
|
+
"midrun", "action_unknown", "seconds")
|
|
60
|
+
|
|
61
|
+
def __init__(self, state, cause, *, since, phone=None, message=None, device=None, task=None, midrun=False,
|
|
62
|
+
action_unknown=False, seconds=None):
|
|
63
|
+
self.state, self.cause, self.since = state, cause, since
|
|
64
|
+
self.message, self.next_step, self.need = words(state, cause, phone, message)
|
|
65
|
+
self.fix = row(cause).fix
|
|
66
|
+
if action_unknown:
|
|
67
|
+
self.next_step = f"{self.next_step} {ACTION_UNKNOWN_STEP}"
|
|
68
|
+
self.device, self.device_name = device, phone
|
|
69
|
+
self.task, self.midrun, self.action_unknown, self.seconds = task, midrun, action_unknown, seconds
|
|
70
|
+
|
|
71
|
+
def public(self):
|
|
72
|
+
value = {"state": self.state, "cause": self.cause, "message": self.message, "next_step": self.next_step,
|
|
73
|
+
"fix": fix_public(self.fix), "since": self.since}
|
|
74
|
+
if self.need:
|
|
75
|
+
value["need"] = self.need
|
|
76
|
+
if self.device:
|
|
77
|
+
value["device"] = self.device
|
|
78
|
+
if self.device_name:
|
|
79
|
+
value["device_name"] = self.device_name
|
|
80
|
+
if self.task:
|
|
81
|
+
value.update(task=self.task, midrun=self.midrun)
|
|
82
|
+
if self.action_unknown:
|
|
83
|
+
value["action_unknown"] = True
|
|
84
|
+
if self.seconds:
|
|
85
|
+
value["wait_seconds"] = self.seconds
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EngineHealth:
|
|
90
|
+
"""The newest condition of each source, the one to say first, and a publish when it changes."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, clock=time.time, publish=None, boot=None):
|
|
93
|
+
self.clock = clock
|
|
94
|
+
self.publish = publish
|
|
95
|
+
self.boot = boot
|
|
96
|
+
self.lock = threading.Lock()
|
|
97
|
+
self.conditions = {}
|
|
98
|
+
self.sampled_at = None
|
|
99
|
+
self._said = None
|
|
100
|
+
|
|
101
|
+
def report(self, source, state, cause, *, phone=None, message=None, device=None, task=None, midrun=False,
|
|
102
|
+
action_unknown=False, seconds=None):
|
|
103
|
+
"""``source`` is in this state now, for this cause. The same state and cause again keeps their ``since``."""
|
|
104
|
+
with self.lock:
|
|
105
|
+
found = self.conditions.get(source)
|
|
106
|
+
same = found is not None and (found.state, found.cause) == (state, cause)
|
|
107
|
+
self.conditions[source] = Condition(
|
|
108
|
+
state, cause, since=found.since if same else self.clock(), phone=phone, message=message,
|
|
109
|
+
device=device, task=task, midrun=midrun, action_unknown=action_unknown, seconds=seconds)
|
|
110
|
+
self._publish_if_changed()
|
|
111
|
+
|
|
112
|
+
def clear(self, source):
|
|
113
|
+
"""``source`` is fine now."""
|
|
114
|
+
with self.lock:
|
|
115
|
+
self.conditions.pop(source, None)
|
|
116
|
+
self._publish_if_changed()
|
|
117
|
+
|
|
118
|
+
def clear_prefix(self, prefix, keep=()):
|
|
119
|
+
"""Every source starting with ``prefix`` is fine now."""
|
|
120
|
+
with self.lock:
|
|
121
|
+
for source in [s for s in self.conditions if s.startswith(prefix) and s not in keep]:
|
|
122
|
+
del self.conditions[source]
|
|
123
|
+
self._publish_if_changed()
|
|
124
|
+
|
|
125
|
+
def sampled(self):
|
|
126
|
+
"""The sampler finished a pass (its time is the snapshot's ``observed_at``)."""
|
|
127
|
+
with self.lock:
|
|
128
|
+
self.sampled_at = self.clock()
|
|
129
|
+
|
|
130
|
+
def _ordered(self):
|
|
131
|
+
rank = {state: index for index, state in enumerate(PRIORITY)}
|
|
132
|
+
return sorted(self.conditions.items(), key=lambda item: (rank.get(item[1].state, len(rank)), item[1].since,
|
|
133
|
+
item[0]))
|
|
134
|
+
|
|
135
|
+
def snapshot(self):
|
|
136
|
+
"""The state to say first (``ready`` when nothing is wrong), with every standing issue and the liveness."""
|
|
137
|
+
with self.lock:
|
|
138
|
+
ordered = self._ordered()
|
|
139
|
+
now = self.clock()
|
|
140
|
+
observed = self.sampled_at if self.sampled_at is not None else now
|
|
141
|
+
if ordered:
|
|
142
|
+
first = ordered[0][1].public()
|
|
143
|
+
else:
|
|
144
|
+
first = {"state": READY, "cause": None, "message": "Ready.", "next_step": None,
|
|
145
|
+
"fix": None, "since": observed}
|
|
146
|
+
return {**first, "observed_at": observed, "issues": [c.public() | {"source": s} for s, c in ordered],
|
|
147
|
+
"liveness": {"boot": self.boot, "sample_s": SAMPLE_S, "slow_after_s": SLOW_AFTER_S,
|
|
148
|
+
"frozen_after_s": FROZEN_AFTER_S}}
|
|
149
|
+
|
|
150
|
+
def _publish_if_changed(self):
|
|
151
|
+
with self.lock:
|
|
152
|
+
said = tuple((s, c.state, c.cause, c.action_unknown) for s, c in self._ordered())
|
|
153
|
+
if said == self._said:
|
|
154
|
+
return
|
|
155
|
+
self._said = said
|
|
156
|
+
if callable(self.publish):
|
|
157
|
+
self.publish(TOPIC, {"event": EVENT, **self.snapshot()})
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def cause_of(snapshot, source):
|
|
161
|
+
"""The cause ``source`` stands in in a snapshot (``phone:primary``, ``model``), or None when it is fine."""
|
|
162
|
+
return next((issue["cause"] for issue in snapshot.get("issues", ()) if issue.get("source") == source), None)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def classify_phone(reading):
|
|
166
|
+
"""(state, cause) for what a phone's helper and link say, or None when ready; a None fact is unknown,
|
|
167
|
+
never a reason."""
|
|
168
|
+
if reading.get("exposed"):
|
|
169
|
+
return HELPER_DOWN, "exposed"
|
|
170
|
+
if reading.get("expired"):
|
|
171
|
+
return HELPER_DOWN, "signature_expired"
|
|
172
|
+
if not reading.get("chosen"):
|
|
173
|
+
return NO_PHONE, "none_chosen"
|
|
174
|
+
if reading.get("wifi"):
|
|
175
|
+
if reading.get("wifi_lost"):
|
|
176
|
+
return WAITING_FOR_PHONE, "wifi_lost"
|
|
177
|
+
elif reading.get("usb") is False:
|
|
178
|
+
return WAITING_FOR_PHONE, "unplugged"
|
|
179
|
+
if not reading.get("built"):
|
|
180
|
+
return HELPER_DOWN, "not_built"
|
|
181
|
+
runner, hint = reading.get("runner"), reading.get("hint")
|
|
182
|
+
if runner == "running":
|
|
183
|
+
if reading.get("locked") is True:
|
|
184
|
+
return WAITING_FOR_PHONE, "locked"
|
|
185
|
+
if reading.get("responsive") is False:
|
|
186
|
+
return WAITING_FOR_PHONE, "not_responding"
|
|
187
|
+
return None
|
|
188
|
+
if reading.get("holding") == "unlock":
|
|
189
|
+
return WAITING_FOR_PHONE, "locked"
|
|
190
|
+
if hint in PHONE_CAUSES:
|
|
191
|
+
return WAITING_FOR_PHONE, hint
|
|
192
|
+
if runner == "failed":
|
|
193
|
+
return HELPER_DOWN, "failing"
|
|
194
|
+
if runner == "starting":
|
|
195
|
+
return WAITING_FOR_PHONE, "helper_restarting"
|
|
196
|
+
return HELPER_DOWN, "stopped"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def classify_model(available, reason, credit_reasons=(), has_key=True):
|
|
200
|
+
"""(state, cause, message) for whether Smart's model can run, or None when it can."""
|
|
201
|
+
if available:
|
|
202
|
+
return None
|
|
203
|
+
if not has_key:
|
|
204
|
+
return MODEL_UNAVAILABLE, "no_key", reason
|
|
205
|
+
return MODEL_UNAVAILABLE, "no_credit" if reason in credit_reasons else "key_rejected", reason
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def action_in_flight(events):
|
|
209
|
+
"""Whether the newest decision of a run sent an action that has no result yet; an unreadable decision counts."""
|
|
210
|
+
done = {"frontier_action", "frontier_failed"}
|
|
211
|
+
results = 0
|
|
212
|
+
for event in reversed(events[-400:]):
|
|
213
|
+
name = event.get("event")
|
|
214
|
+
if name in done:
|
|
215
|
+
results += 1
|
|
216
|
+
elif name == "frontier_decision":
|
|
217
|
+
ops = event.get("ops")
|
|
218
|
+
if not isinstance(ops, list):
|
|
219
|
+
return True
|
|
220
|
+
acting = sum(op not in (None, "DONE", "BLOCKED", "WAIT") for op in ops)
|
|
221
|
+
return acting > results
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def task_condition(run_id, wait, events, phone=None, device=None):
|
|
226
|
+
"""The ``Condition`` arguments of a task waiting on the phone (``run.phone_wait``'s dict)."""
|
|
227
|
+
cause = TASK_WAIT_CAUSES.get(wait.get("kind"))
|
|
228
|
+
if cause is None:
|
|
229
|
+
return None
|
|
230
|
+
return dict(state=WAITING_FOR_PHONE, cause=cause, phone=phone, device=device, task=run_id,
|
|
231
|
+
midrun=wait.get("midrun") is True, action_unknown=wait.get("midrun") is True
|
|
232
|
+
and action_in_flight(events),
|
|
233
|
+
seconds=wait.get("seconds"))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def from_silence(age_s=None, *, refused=False, boot=None, seen_boot=None, thresholds=None):
|
|
237
|
+
"""The state a process that can't say it for itself is in, from the asker's side."""
|
|
238
|
+
slow, frozen = (thresholds or (SLOW_AFTER_S, FROZEN_AFTER_S))
|
|
239
|
+
if boot is not None and seen_boot is not None and boot != seen_boot:
|
|
240
|
+
state, cause = RESTARTING, "restarted"
|
|
241
|
+
elif refused:
|
|
242
|
+
state, cause = DOWN, "not_running"
|
|
243
|
+
elif age_s is None or age_s < slow:
|
|
244
|
+
return None
|
|
245
|
+
elif age_s < frozen:
|
|
246
|
+
state, cause = SLOW, "slow_answer"
|
|
247
|
+
else:
|
|
248
|
+
state, cause = FROZEN, "no_answer"
|
|
249
|
+
message, step, _need = words(state, cause)
|
|
250
|
+
return {"state": state, "cause": cause, "message": message, "next_step": step, "fix": fix_public(row(cause).fix)}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def read_from_app(url=None, *, origin="cli", timeout=4.0, connect=None):
|
|
254
|
+
"""The Mac app's engine state as an asker sees it: its snapshot, or what its silence is (``from_silence``: frozen
|
|
255
|
+
when it accepted the connection and never answered, down when the connection was refused). None when it can't be
|
|
256
|
+
asked (it refused the token). ``connect`` replaces ``Client.connect`` in tests."""
|
|
257
|
+
from .threads.client import Client, NoService, ServiceError
|
|
258
|
+
try:
|
|
259
|
+
return (connect or Client.connect)(url=url, origin=origin, timeout=timeout).status.get("engine")
|
|
260
|
+
except NoService as silent:
|
|
261
|
+
return from_silence(FROZEN_AFTER_S if silent.frozen else None, refused=not silent.frozen)
|
|
262
|
+
except ServiceError:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class Sampler:
|
|
267
|
+
"""Reads a ``Runtime``'s detectors (model, live runs, phones, waiting tasks) into its ``EngineHealth``."""
|
|
268
|
+
|
|
269
|
+
def __init__(self, runtime, interval=SAMPLE_S):
|
|
270
|
+
self.runtime, self.interval = runtime, interval
|
|
271
|
+
self.stop = threading.Event()
|
|
272
|
+
self.thread = None
|
|
273
|
+
self.pass_lock = threading.Lock()
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def health(self):
|
|
277
|
+
return self.runtime.engine_health
|
|
278
|
+
|
|
279
|
+
def start(self):
|
|
280
|
+
if self.thread is None:
|
|
281
|
+
self.thread = threading.Thread(target=self._loop, name="mobster-engine-health", daemon=True)
|
|
282
|
+
self.thread.start()
|
|
283
|
+
|
|
284
|
+
def close(self):
|
|
285
|
+
self.stop.set()
|
|
286
|
+
if self.thread is not None and self.thread is not threading.current_thread():
|
|
287
|
+
self.thread.join(2.0)
|
|
288
|
+
|
|
289
|
+
def _loop(self):
|
|
290
|
+
while not self.stop.is_set():
|
|
291
|
+
self.sample()
|
|
292
|
+
self.stop.wait(self.interval)
|
|
293
|
+
|
|
294
|
+
def snapshot(self):
|
|
295
|
+
if self.thread is None or not self.thread.is_alive():
|
|
296
|
+
self.sample()
|
|
297
|
+
return self.health.snapshot()
|
|
298
|
+
|
|
299
|
+
def sample(self):
|
|
300
|
+
"""One pass. A detector that raises leaves its source as it was and is logged:
|
|
301
|
+
a failed read never says ready."""
|
|
302
|
+
if not self.pass_lock.acquire(blocking=False):
|
|
303
|
+
return # a pass is running (the thread's, or another asker's): its answer is the answer
|
|
304
|
+
try:
|
|
305
|
+
for part in (self._engine, self._model, self._phones, self._tasks):
|
|
306
|
+
try:
|
|
307
|
+
part()
|
|
308
|
+
except Exception: # noqa: BLE001 -- one detector's failure never hides the others
|
|
309
|
+
from . import harness_api
|
|
310
|
+
harness_api.log.exception("engine health: a detector failed")
|
|
311
|
+
self.health.sampled()
|
|
312
|
+
finally:
|
|
313
|
+
self.pass_lock.release()
|
|
314
|
+
|
|
315
|
+
def _set(self, source, found, **fields):
|
|
316
|
+
"""Report ``found`` ((state, cause) or None) for ``source``."""
|
|
317
|
+
if found is None:
|
|
318
|
+
self.health.clear(source)
|
|
319
|
+
else:
|
|
320
|
+
self.health.report(source, found[0], found[1], **fields)
|
|
321
|
+
|
|
322
|
+
def _engine(self):
|
|
323
|
+
runtime = self.runtime
|
|
324
|
+
self._set("engine", (STOPPING, "closing") if runtime.closing else None)
|
|
325
|
+
self._set("live", None if getattr(runtime.config, "enable_live", True) else (LIVE_OFF, "disabled"))
|
|
326
|
+
|
|
327
|
+
def _model(self):
|
|
328
|
+
from . import engines
|
|
329
|
+
state = self.runtime.engines_status()[engines.SMART]
|
|
330
|
+
found = classify_model(state["available"], state["reason"], engines.CREDIT_REASONS,
|
|
331
|
+
has_key=bool(engines.smart_key()) or self.runtime.scripted_models())
|
|
332
|
+
if found is None:
|
|
333
|
+
self.health.clear("model")
|
|
334
|
+
else:
|
|
335
|
+
self.health.report("model", found[0], found[1], message=found[2])
|
|
336
|
+
|
|
337
|
+
def _phones(self):
|
|
338
|
+
runtime = self.runtime
|
|
339
|
+
if runtime.pool is not None:
|
|
340
|
+
return # a target with its own pool of phones says its own
|
|
341
|
+
fleet = getattr(runtime, "fleet", None)
|
|
342
|
+
seen = set()
|
|
343
|
+
primary_name = getattr(getattr(fleet, "primary", None), "name", None) or None
|
|
344
|
+
if runtime.manager is not None:
|
|
345
|
+
found = classify_phone(runtime.manager.engine_reading())
|
|
346
|
+
elif getattr(runtime.config, "wda_url", None):
|
|
347
|
+
found = None if runtime.target_status()["ready"] else (HELPER_DOWN, "not_answering")
|
|
348
|
+
else:
|
|
349
|
+
found = (NO_PHONE, "none_attached")
|
|
350
|
+
self._phone("phone:primary", found, primary_name, getattr(getattr(fleet, "primary", None), "id", None), seen)
|
|
351
|
+
for phone in list(getattr(fleet, "phones", {}).values()):
|
|
352
|
+
if phone.manager is not None:
|
|
353
|
+
found = classify_phone(phone.manager.engine_reading())
|
|
354
|
+
else:
|
|
355
|
+
found = None if phone.target_status()["ready"] else (HELPER_DOWN, "not_answering")
|
|
356
|
+
self._phone(f"phone:{phone.id}", found, phone.name, phone.id, seen)
|
|
357
|
+
self.health.clear_prefix("phone:", keep=seen)
|
|
358
|
+
|
|
359
|
+
def _phone(self, source, found, name, device, seen):
|
|
360
|
+
seen.add(source)
|
|
361
|
+
self._set(source, found, phone=name, device=device)
|
|
362
|
+
|
|
363
|
+
def _tasks(self):
|
|
364
|
+
from .run import phone_wait
|
|
365
|
+
seen = set()
|
|
366
|
+
for run in list(self.runtime.runs.values()):
|
|
367
|
+
if run.finished_at is not None:
|
|
368
|
+
continue
|
|
369
|
+
with run.condition:
|
|
370
|
+
events = list(run.events[-400:])
|
|
371
|
+
wait = phone_wait(events)
|
|
372
|
+
fields = task_condition(run.id, wait, events, run.device_name, run.device_id) if wait else None
|
|
373
|
+
if fields is None:
|
|
374
|
+
continue
|
|
375
|
+
source = f"task:{run.id}"
|
|
376
|
+
seen.add(source)
|
|
377
|
+
self.health.report(source, fields.pop("state"), fields.pop("cause"), **fields)
|
|
378
|
+
self.health.clear_prefix("task:", keep=seen)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""The harness's harness_api.EngineHooks: what the engine reaches in the full Mobster."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Harness:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.target_driver, self.cli, self.runtime_for = None, [], None
|
|
7
|
+
|
|
8
|
+
def model_routes(self):
|
|
9
|
+
from .ai_plans import providers
|
|
10
|
+
return providers
|
|
11
|
+
|
|
12
|
+
def key(self, provider, env):
|
|
13
|
+
from . import keys
|
|
14
|
+
return (keys.anthropic_key if provider == "anthropic" else keys.openai_key)(env)
|
|
15
|
+
|
|
16
|
+
def tool_policy(self, skills, *, run_context, driver, guard):
|
|
17
|
+
from .harness.tools import offered
|
|
18
|
+
return offered(skills, run_context=run_context, driver=driver, guard=guard)
|
|
19
|
+
|
|
20
|
+
def first_screen(self, driver, run_context):
|
|
21
|
+
from .harness.prewarm import take
|
|
22
|
+
return take(driver, run_context)
|
|
23
|
+
|
|
24
|
+
def private_rects(self, elements, bundle_id):
|
|
25
|
+
from .run_gif import private_rects
|
|
26
|
+
return private_rects(elements, bundle_id)
|
|
27
|
+
|
|
28
|
+
def long_report(self):
|
|
29
|
+
from .longform import report
|
|
30
|
+
return report
|
|
31
|
+
|
|
32
|
+
def wifi_attached(self, udid):
|
|
33
|
+
from .wireless import transport
|
|
34
|
+
value = transport.attached(udid)
|
|
35
|
+
return (False, None) if value is transport.NOT_WIFI else (True, value)
|
|
36
|
+
|
|
37
|
+
def wifi_lost(self, udid):
|
|
38
|
+
from .wireless import transport
|
|
39
|
+
return transport.lost(udid)
|
|
40
|
+
|
|
41
|
+
def wifi_may_reach(self, udid):
|
|
42
|
+
from .paths import user_data_dir
|
|
43
|
+
from .wireless import store, transport, transport_on
|
|
44
|
+
return transport_on() and transport.link(udid) is None and store.enabled(user_data_dir(), udid)
|
|
45
|
+
|
|
46
|
+
def usb_attached(self, udid):
|
|
47
|
+
from .mac_tools import usb_attached
|
|
48
|
+
return usb_attached(udid)
|
|
49
|
+
|
|
50
|
+
def devicectl_json(self, args, timeout):
|
|
51
|
+
from .wireless import devicectl
|
|
52
|
+
return devicectl.run_json(args, timeout)
|
|
53
|
+
|
|
54
|
+
def coredevice_locked(self, udid):
|
|
55
|
+
from .wireless import devicectl
|
|
56
|
+
return devicectl.read_lock_state(udid)
|
|
57
|
+
|
|
58
|
+
def coredevice_apps(self, udid, kind):
|
|
59
|
+
from .wireless import devicectl
|
|
60
|
+
return devicectl.read_apps(udid, kind)
|
|
61
|
+
|
|
62
|
+
def track_disabled(self, track, reason):
|
|
63
|
+
from . import tracks
|
|
64
|
+
tracks.disable(track, reason)
|
|
65
|
+
|
|
66
|
+
def hedge(self):
|
|
67
|
+
from . import hedge
|
|
68
|
+
return hedge
|
|
69
|
+
|
|
70
|
+
def skills(self):
|
|
71
|
+
from .skills import default_skills
|
|
72
|
+
return default_skills()
|
|
73
|
+
|
|
74
|
+
def app_catalog(self):
|
|
75
|
+
from .catalog import APPS
|
|
76
|
+
return APPS
|
|
77
|
+
|
|
78
|
+
def image_policy(self):
|
|
79
|
+
from . import image_policy
|
|
80
|
+
return image_policy
|
|
81
|
+
|
|
82
|
+
def frame_clock(self):
|
|
83
|
+
from .wda import frame_clock
|
|
84
|
+
return frame_clock
|
|
85
|
+
|
|
86
|
+
def ios_slice(self, snapshot, request, driver):
|
|
87
|
+
from .ios.guide import turn_slice
|
|
88
|
+
return turn_slice(snapshot, request, driver)
|
|
89
|
+
|
|
90
|
+
def screen_text(self):
|
|
91
|
+
from .screen_text import reader
|
|
92
|
+
return reader()
|
|
93
|
+
|
|
94
|
+
def pixel_screen(self):
|
|
95
|
+
from . import perception
|
|
96
|
+
return perception
|