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,596 @@
|
|
|
1
|
+
"""Replay a recorded path on a driver, with no model.
|
|
2
|
+
|
|
3
|
+
Each step waits for its precondition, then acts through the same driver calls the frontier makes:
|
|
4
|
+
|
|
5
|
+
- **The app**: the screen is in the app the step was recorded in (LAUNCH_APP and HOME need nothing).
|
|
6
|
+
- **The screen**: the same structural screen (its hash), or one at least 80% alike by role/label pairs
|
|
7
|
+
(Jaccard overlap). While the screen settles only those count; once the wait is over, a screen at least 50%
|
|
8
|
+
alike counts too when the step's target is there exactly once.
|
|
9
|
+
- **The target**: exactly one element with the recorded label and role (the recorded tree path breaks a tie). Never
|
|
10
|
+
a partial label: a heal is cheaper than a tap on the wrong control.
|
|
11
|
+
|
|
12
|
+
When a precondition doesn't hold within the wait, the step *drifted*. Then, in order:
|
|
13
|
+
|
|
14
|
+
1. a step the reviewer chose to keep failing (``strict``) stops the replay: status "drift";
|
|
15
|
+
2. with no healer (``--no-heal``) the replay stops: status "drift";
|
|
16
|
+
3. the circuit breaker: when healing this step would make more than half the path healed, the replay stops: status
|
|
17
|
+
"drift", reason "breaker";
|
|
18
|
+
4. else the healer (the model, for this step only) acts until the screen matches a later recorded step again, or
|
|
19
|
+
finishes the flow. Its actions replace the drifted steps, and the change is returned as a heal entry for review.
|
|
20
|
+
|
|
21
|
+
A step that sends, posts, deletes, buys or submits (its recorded commit act, or harness_api.commit_act on the step
|
|
22
|
+
now) asks ``approve`` first, exactly as a live run asks. With no approver the replay hands that step to the model
|
|
23
|
+
(``on_commit="handoff"``) or stops (``"deny"``): it never commits on its own.
|
|
24
|
+
|
|
25
|
+
Typed text is never invented: a step types its recorded literal, or the value of its blank from ``params``. A step
|
|
26
|
+
the model must do each run (``live``) goes to ``live``, or hands off.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import inspect
|
|
30
|
+
import logging
|
|
31
|
+
import time
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
from .. import harness_api
|
|
36
|
+
from ..wda.system_dialogs import DialogStop
|
|
37
|
+
from .path import ELEMENT_OPS, POINT_OPS, SWIPES, TEXT_OPS, anchor, describe, signature_hash
|
|
38
|
+
|
|
39
|
+
STRONG, WEAK = .8, .5
|
|
40
|
+
SEEK_SWIPES = 4
|
|
41
|
+
WAIT_SECONDS = 3.0
|
|
42
|
+
POLL_SECONDS = .15
|
|
43
|
+
MIN_WAIT, STILL_READS = 1.0, 2
|
|
44
|
+
BREAKER = .5
|
|
45
|
+
DIALOG_ROUNDS = 3 # times a step that doesn't match looks for an iOS dialog in front of it before it drifts
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Heal:
|
|
50
|
+
"""What a healer did."""
|
|
51
|
+
ok: bool
|
|
52
|
+
steps: list = field(default_factory=list)
|
|
53
|
+
resume_at: Optional[int] = None
|
|
54
|
+
done: bool = False
|
|
55
|
+
cost_usd: float = 0.0
|
|
56
|
+
reason: str = ""
|
|
57
|
+
status: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Result:
|
|
62
|
+
status: str # done | drift | stopped | declined | handoff | heal_failed | error | dialog
|
|
63
|
+
steps: list
|
|
64
|
+
strict: list = field(default_factory=list) # protected positions in the resulting path
|
|
65
|
+
completed: int = 0
|
|
66
|
+
replayed: int = 0
|
|
67
|
+
live: int = 0
|
|
68
|
+
heals: list = field(default_factory=list)
|
|
69
|
+
reason: str = ""
|
|
70
|
+
cost_usd: float = 0.0
|
|
71
|
+
seconds: float = 0.0
|
|
72
|
+
at: Optional[int] = None
|
|
73
|
+
answer: Optional[str] = None
|
|
74
|
+
snapshot: object = None
|
|
75
|
+
model_status: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def plain(text):
|
|
79
|
+
return " ".join(str(text or "").split()).casefold()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def find_target(step, snapshot):
|
|
83
|
+
"""The one element ``step`` acts on in ``snapshot``, or None."""
|
|
84
|
+
target = step.get("target")
|
|
85
|
+
if not isinstance(target, dict):
|
|
86
|
+
return None
|
|
87
|
+
label, role, path = plain(target.get("label")), target.get("role") or "", target.get("id")
|
|
88
|
+
elements = list(getattr(snapshot, "elements", ()) or ())
|
|
89
|
+
if not label:
|
|
90
|
+
found = [e for e in elements if path and e.locator == path and (not role or e.role == role)]
|
|
91
|
+
return found[0] if len(found) == 1 else None
|
|
92
|
+
found = [e for e in elements if plain(e.label) == label and (not role or e.role == role)]
|
|
93
|
+
if len(found) > 1 and path:
|
|
94
|
+
found = [e for e in found if e.locator == path] or found
|
|
95
|
+
return found[0] if len(found) == 1 else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def screen_likeness(step, snapshot):
|
|
99
|
+
from ..state import signature
|
|
100
|
+
screen = step.get("screen") or {}
|
|
101
|
+
pairs = screen.get("signature")
|
|
102
|
+
if not pairs and not screen.get("hash") and not screen.get("shape"):
|
|
103
|
+
return 0.0
|
|
104
|
+
now = signature(snapshot)
|
|
105
|
+
if screen.get("hash") and signature_hash(now) == screen["hash"]:
|
|
106
|
+
return 1.0
|
|
107
|
+
if not pairs and screen.get("shape"):
|
|
108
|
+
# Kept as hashed pairs (a saved program keeps no screen text): the same overlap measure on the hashes.
|
|
109
|
+
from .path import screen_shape
|
|
110
|
+
ours, theirs = set(screen["shape"]), set(screen_shape(now))
|
|
111
|
+
union = ours | theirs
|
|
112
|
+
return len(ours & theirs) / len(union) if union else 1.0
|
|
113
|
+
if not pairs:
|
|
114
|
+
return 0.0
|
|
115
|
+
ours, theirs = {tuple(pair) for pair in pairs}, {tuple(pair) for pair in now}
|
|
116
|
+
union = ours | theirs
|
|
117
|
+
return len(ours & theirs) / len(union) if union else 1.0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def match(step, snapshot, *, settled=False):
|
|
121
|
+
"""(ok, element, why) for ``step`` on ``snapshot``."""
|
|
122
|
+
op = step.get("op")
|
|
123
|
+
if op in ("LAUNCH_APP", "HOME", "WAIT"):
|
|
124
|
+
return True, None, ""
|
|
125
|
+
app, now = step.get("app"), getattr(snapshot, "bundle_id", "") or ""
|
|
126
|
+
if app and app != now:
|
|
127
|
+
return False, None, f"{now or 'an unknown app'} is in front, not {app}"
|
|
128
|
+
if op in POINT_OPS:
|
|
129
|
+
proof = (step.get("screen") or {}).get("fingerprint")
|
|
130
|
+
if not proof:
|
|
131
|
+
return False, None, "the recorded point has no screen geometry proof"
|
|
132
|
+
if proof != getattr(snapshot, "content_fingerprint", None):
|
|
133
|
+
return False, None, "the screen geometry changed around the recorded point"
|
|
134
|
+
likeness = screen_likeness(step, snapshot)
|
|
135
|
+
element = None
|
|
136
|
+
if op in ELEMENT_OPS or (op in SWIPES and step.get("target")):
|
|
137
|
+
element = find_target(step, snapshot)
|
|
138
|
+
if element is None and op in ELEMENT_OPS:
|
|
139
|
+
label = (step.get("target") or {}).get("label")
|
|
140
|
+
return False, None, (f'"{label}" isn\'t on the screen' if label else "the field isn't on the screen")
|
|
141
|
+
if likeness >= STRONG or (settled and element is not None and likeness >= WEAK):
|
|
142
|
+
return True, element, ""
|
|
143
|
+
if settled and element is not None and not (step.get("screen") or {}).get("signature") \
|
|
144
|
+
and not (step.get("screen") or {}).get("shape"):
|
|
145
|
+
# A step learned from a live run's step records has only its screen's hash: pinned by its target alone.
|
|
146
|
+
return True, element, ""
|
|
147
|
+
return False, None, f"the screen is different ({round(likeness * 100)}% alike)"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Replayer:
|
|
151
|
+
"""Replays one path on ``driver``; a recorded step never opens or acts in an app the run says no to."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, driver, *, approve=None, heal=None, live=None, emit=None, cancelled=None, params=None,
|
|
154
|
+
frame=None, on_commit="handoff", wait_seconds=WAIT_SECONDS, breaker=BREAKER, sleep=time.sleep,
|
|
155
|
+
clock=time.monotonic, app_names=None, allows_app=None, decide=None, dialogs=None):
|
|
156
|
+
self.driver = driver
|
|
157
|
+
# dialogs: a verify.dialogs.Responder. iOS's own dialogs (permission alerts, Allow Paste, the purchase sheet)
|
|
158
|
+
# are answered from the check's policy between steps, and are neither a step nor a heal. None: the model's job.
|
|
159
|
+
self.dialogs = dialogs
|
|
160
|
+
# decide(step, element, snapshot, text) -> permissions.gate.Decision: the run's permission gate, so a replayed
|
|
161
|
+
# step asks what the same step asks live (ask_all's changes, a like or follow, an amount, a one-time code). None
|
|
162
|
+
# (replay_core.checks on simulators): the recorded commit act alone.
|
|
163
|
+
self.decide = decide
|
|
164
|
+
# allows_app(bundle) -> bool: whether the run may use an app (its first-use card asks). None: every app.
|
|
165
|
+
self.allows_app = allows_app
|
|
166
|
+
self.approve, self.heal, self.live = approve, heal, live
|
|
167
|
+
self.emit = emit or (lambda event: None)
|
|
168
|
+
self.cancelled = cancelled or (lambda: False)
|
|
169
|
+
self.params = dict(params or {})
|
|
170
|
+
self.frame = frame
|
|
171
|
+
self.on_commit = on_commit
|
|
172
|
+
self.wait_seconds, self.breaker = wait_seconds, breaker
|
|
173
|
+
self.sleep, self.clock = sleep, clock
|
|
174
|
+
self.app_names = dict(app_names or {})
|
|
175
|
+
|
|
176
|
+
def read(self):
|
|
177
|
+
ready = getattr(self.driver, "observe_ready", None)
|
|
178
|
+
return ready(timeout=15) if callable(ready) else self.driver.observe(timeout=15)
|
|
179
|
+
|
|
180
|
+
def settle(self, before, op):
|
|
181
|
+
if op in TEXT_OPS:
|
|
182
|
+
return self.driver.observe(timeout=15)
|
|
183
|
+
native = getattr(self.driver, "wait_for_change", None)
|
|
184
|
+
if callable(native):
|
|
185
|
+
try:
|
|
186
|
+
return native(before, wait_seconds=.6, timeout=2.1)
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
return self.read()
|
|
190
|
+
|
|
191
|
+
def wait_for(self, step, snapshot):
|
|
192
|
+
"""(ok, element, snapshot, why) once the precondition holds; a weak match goes only once two reads agree."""
|
|
193
|
+
started = self.clock()
|
|
194
|
+
deadline = started + self.wait_seconds
|
|
195
|
+
still, last = 0, getattr(snapshot, "content_fingerprint", None)
|
|
196
|
+
while True:
|
|
197
|
+
ok, element, why = match(step, snapshot)
|
|
198
|
+
if ok:
|
|
199
|
+
return True, element, snapshot, ""
|
|
200
|
+
if still >= 1:
|
|
201
|
+
ok, element, why = match(step, snapshot, settled=True)
|
|
202
|
+
if ok:
|
|
203
|
+
return True, element, snapshot, ""
|
|
204
|
+
now = self.clock()
|
|
205
|
+
if now >= deadline or (still >= STILL_READS and now - started >= MIN_WAIT) or self.cancelled():
|
|
206
|
+
ok, element, why = match(step, snapshot, settled=True)
|
|
207
|
+
return ok, element, snapshot, why
|
|
208
|
+
self.sleep(POLL_SECONDS)
|
|
209
|
+
snapshot = self.read()
|
|
210
|
+
key = getattr(snapshot, "content_fingerprint", None)
|
|
211
|
+
still = still + 1 if key is not None and key == last else 0
|
|
212
|
+
last = key
|
|
213
|
+
|
|
214
|
+
def run(self, path):
|
|
215
|
+
steps = list(path.get("steps") or ())
|
|
216
|
+
strict = {int(n) for n in path.get("strict") or ()}
|
|
217
|
+
started = self.clock()
|
|
218
|
+
out = Result(status="done", steps=[])
|
|
219
|
+
total = len(steps)
|
|
220
|
+
healed_over = 0
|
|
221
|
+
index = 0
|
|
222
|
+
attempted = False # an action or a model callback may have acted: an exception from here on is unknown
|
|
223
|
+
snapshot = self.read()
|
|
224
|
+
|
|
225
|
+
def answer_dialogs(current):
|
|
226
|
+
"""The screen after iOS's dialogs in front were answered (verify.dialogs); a stop becomes the result."""
|
|
227
|
+
if self.dialogs is None:
|
|
228
|
+
return current, 0
|
|
229
|
+
return self.dialogs.clear(current, thorough=True, read=self.read)
|
|
230
|
+
|
|
231
|
+
def finish(status, reason="", at=None, **extra):
|
|
232
|
+
out.status, out.reason, out.at = status, reason, at
|
|
233
|
+
out.seconds = round(self.clock() - started, 3)
|
|
234
|
+
out.snapshot = snapshot
|
|
235
|
+
for key, value in extra.items():
|
|
236
|
+
setattr(out, key, value)
|
|
237
|
+
if status != "done":
|
|
238
|
+
out.steps.extend(steps[index:]) # the rest stays as recorded
|
|
239
|
+
try:
|
|
240
|
+
self.emit({"event": "replay_finished", "status": status, "reason": reason, "replayed": out.replayed,
|
|
241
|
+
"healed": len(out.heals), "live": out.live, "seconds": out.seconds})
|
|
242
|
+
except Exception: # an observer never costs the run its result, an unknown outcome included
|
|
243
|
+
logging.getLogger(__name__).exception("replay completion observer failed")
|
|
244
|
+
return out
|
|
245
|
+
|
|
246
|
+
self.emit({"event": "replay_started", "steps": total})
|
|
247
|
+
asked_at, rounds = -1, 0
|
|
248
|
+
try:
|
|
249
|
+
while index < total:
|
|
250
|
+
if self.cancelled():
|
|
251
|
+
return finish("stopped", "The run was stopped.", index)
|
|
252
|
+
step = steps[index]
|
|
253
|
+
if asked_at != index:
|
|
254
|
+
asked_at, rounds = index, 0
|
|
255
|
+
try:
|
|
256
|
+
snapshot, _ = answer_dialogs(snapshot)
|
|
257
|
+
except DialogStop as stop:
|
|
258
|
+
return finish("dialog", str(stop), index)
|
|
259
|
+
if step.get("live"):
|
|
260
|
+
if self.live is None:
|
|
261
|
+
return finish("handoff", f"step {index + 1} ({describe(step)}) needs the model", index)
|
|
262
|
+
attempted = True
|
|
263
|
+
outcome = self.live(index, step, snapshot, list(out.steps))
|
|
264
|
+
out.cost_usd += outcome.cost_usd
|
|
265
|
+
if not outcome.ok:
|
|
266
|
+
return finish("heal_failed", outcome.reason or f"the model couldn't do step {index + 1}", index,
|
|
267
|
+
model_status=outcome.status)
|
|
268
|
+
resume = outcome.resume_at if outcome.resume_at is not None and outcome.resume_at > index \
|
|
269
|
+
else index + 1
|
|
270
|
+
if outcome.done:
|
|
271
|
+
resume = total
|
|
272
|
+
if any(index + 1 < number <= resume for number in strict):
|
|
273
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): the model crossed a step you "
|
|
274
|
+
"chose to keep failing. Review this check before running it again.",
|
|
275
|
+
index)
|
|
276
|
+
out.live += 1
|
|
277
|
+
out.steps.append(step) # a live step stays live
|
|
278
|
+
if index + 1 in strict:
|
|
279
|
+
out.strict.append(len(out.steps))
|
|
280
|
+
out.completed = index + 1
|
|
281
|
+
if outcome.done:
|
|
282
|
+
index = total
|
|
283
|
+
snapshot = self.read()
|
|
284
|
+
break
|
|
285
|
+
index = resume
|
|
286
|
+
snapshot = self.read()
|
|
287
|
+
continue
|
|
288
|
+
refused = self._app_refused(step, snapshot)
|
|
289
|
+
if refused: # before anything in that app, a seek's swipes included
|
|
290
|
+
return finish("handoff", refused, index)
|
|
291
|
+
if step.get("seek"):
|
|
292
|
+
try:
|
|
293
|
+
snapshot = self.seek(step, snapshot)
|
|
294
|
+
except Exception as error:
|
|
295
|
+
# A swipe may have landed before the failure: stop, never swipe again.
|
|
296
|
+
return finish("error", f"Step {index + 1} ({describe(step)}) failed while seeking: "
|
|
297
|
+
f"{type(error).__name__}: {str(error)[:160]}", index,
|
|
298
|
+
model_status="unknown_outcome")
|
|
299
|
+
ok, element, snapshot, why = self.wait_for(step, snapshot)
|
|
300
|
+
if self.cancelled():
|
|
301
|
+
return finish("stopped", "The run was stopped.", index)
|
|
302
|
+
refused = self._app_refused(step, snapshot)
|
|
303
|
+
if refused:
|
|
304
|
+
return finish("handoff", refused, index)
|
|
305
|
+
if not ok and self.dialogs is not None and rounds < DIALOG_ROUNDS:
|
|
306
|
+
# A dialog that came up while the step waited: answer it, then look at the step again.
|
|
307
|
+
rounds += 1
|
|
308
|
+
try:
|
|
309
|
+
snapshot, answered = answer_dialogs(snapshot)
|
|
310
|
+
except DialogStop as stop:
|
|
311
|
+
return finish("dialog", str(stop), index)
|
|
312
|
+
if answered:
|
|
313
|
+
continue
|
|
314
|
+
if not ok:
|
|
315
|
+
self.emit({"event": "replay_drift", "step": index + 1, "action": describe(step), "why": why})
|
|
316
|
+
if index + 1 in strict:
|
|
317
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): {why}. You chose to keep this "
|
|
318
|
+
"check failing when this step changes.", index)
|
|
319
|
+
if self.heal is None:
|
|
320
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): {why}.", index)
|
|
321
|
+
if (healed_over + 1) > total * self.breaker:
|
|
322
|
+
return finish("drift",
|
|
323
|
+
f"More than half the recorded steps would heal (step {index + 1}: {why}), "
|
|
324
|
+
"so Mobster stopped instead of healing.", index, model_status="breaker")
|
|
325
|
+
before = self._frame("heal-before")
|
|
326
|
+
limit = max(1, int(total * self.breaker) - healed_over)
|
|
327
|
+
attempted = True
|
|
328
|
+
outcome = self.heal(index, step, snapshot, why, list(out.steps), limit)
|
|
329
|
+
out.cost_usd += outcome.cost_usd
|
|
330
|
+
breaker = (outcome.status == "breaker" or (outcome.ok and healed_over + len(
|
|
331
|
+
steps[index:total if outcome.done else max(outcome.resume_at or index + 1, index)])
|
|
332
|
+
> total * self.breaker))
|
|
333
|
+
if breaker:
|
|
334
|
+
snapshot = self.read()
|
|
335
|
+
return finish("drift", f"More than half the recorded steps would heal (from step {index + 1}: "
|
|
336
|
+
f"{why}), so Mobster stopped instead of healing.", index,
|
|
337
|
+
model_status="breaker")
|
|
338
|
+
if not outcome.ok:
|
|
339
|
+
return finish("heal_failed", outcome.reason or f"Smart couldn't heal step {index + 1}", index,
|
|
340
|
+
model_status=outcome.status)
|
|
341
|
+
snapshot = self.read()
|
|
342
|
+
refused = self._app_refused(step, snapshot)
|
|
343
|
+
if refused:
|
|
344
|
+
return finish("handoff", refused, index)
|
|
345
|
+
after = self._frame("heal-after")
|
|
346
|
+
resume = total if outcome.done else outcome.resume_at
|
|
347
|
+
if resume is None or resume < index or (resume == index and not outcome.steps):
|
|
348
|
+
resume = index + 1 # resume == index with new steps: the heal added steps before this one
|
|
349
|
+
if any(index + 1 <= number <= resume for number in strict):
|
|
350
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): the heal crossed a step you "
|
|
351
|
+
"chose to keep failing. Review this check before running it again.",
|
|
352
|
+
index)
|
|
353
|
+
old = steps[index:resume]
|
|
354
|
+
healed_over += len(old)
|
|
355
|
+
out.heals.append(heal_entry(index, old, outcome.steps, why, before, after))
|
|
356
|
+
out.steps.extend(outcome.steps)
|
|
357
|
+
out.completed = resume
|
|
358
|
+
self.emit({"event": "replay_healed", "step": index + 1, "from": describe(step),
|
|
359
|
+
"to": describe(outcome.steps[0]) if outcome.steps else "nothing", "resume": resume + 1})
|
|
360
|
+
if outcome.done:
|
|
361
|
+
out.model_status = outcome.status
|
|
362
|
+
index = total
|
|
363
|
+
break
|
|
364
|
+
index = resume
|
|
365
|
+
continue
|
|
366
|
+
gate = self._commit(step, element, snapshot, index)
|
|
367
|
+
act = None
|
|
368
|
+
if gate is not None:
|
|
369
|
+
act, refusal, approved_screen = gate
|
|
370
|
+
if refusal is not None:
|
|
371
|
+
status, answer = refusal
|
|
372
|
+
if status == "handoff":
|
|
373
|
+
return finish("handoff", f"step {index + 1} ({describe(step)}) commits and asks a person "
|
|
374
|
+
"through the model", index)
|
|
375
|
+
return finish("declined", f"{describe(step)} wasn't approved ({answer}).", index,
|
|
376
|
+
answer=answer)
|
|
377
|
+
if self.cancelled(): # stopped while an approval waited
|
|
378
|
+
return finish("stopped", "The run was stopped.", index)
|
|
379
|
+
current = self.read()
|
|
380
|
+
if current.fingerprint != approved_screen:
|
|
381
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): the screen changed while "
|
|
382
|
+
"approval was waiting.", index)
|
|
383
|
+
refused = self._app_refused(step, current)
|
|
384
|
+
if refused:
|
|
385
|
+
return finish("handoff", refused, index)
|
|
386
|
+
ok, element, why = match(step, current, settled=True)
|
|
387
|
+
if not ok:
|
|
388
|
+
return finish("drift", f"Step {index + 1} ({describe(step)}): {why} after approval.", index)
|
|
389
|
+
snapshot = current
|
|
390
|
+
if self.cancelled():
|
|
391
|
+
return finish("stopped", "The run was stopped.", index)
|
|
392
|
+
attempted = True
|
|
393
|
+
try:
|
|
394
|
+
self.act(step, element, snapshot)
|
|
395
|
+
except Exception as error:
|
|
396
|
+
# The action may have landed: report an unknown outcome and never send it again.
|
|
397
|
+
return finish("error", f"Step {index + 1} ({describe(step)}) failed: {type(error).__name__}: "
|
|
398
|
+
f"{str(error)[:160]}", index, model_status="unknown_outcome")
|
|
399
|
+
out.replayed += 1
|
|
400
|
+
executed = refreshed(step, element, snapshot)
|
|
401
|
+
if act:
|
|
402
|
+
executed["commit"] = act
|
|
403
|
+
out.steps.append(executed)
|
|
404
|
+
if index + 1 in strict:
|
|
405
|
+
out.strict.append(len(out.steps))
|
|
406
|
+
out.completed = index + 1
|
|
407
|
+
self.emit({"event": "replay_step", "step": index + 1, "op": step["op"], "action": describe(step),
|
|
408
|
+
"label": (step.get("target") or {}).get("label"),
|
|
409
|
+
"role": (step.get("target") or {}).get("role"),
|
|
410
|
+
"typed": self.text_for(step) if step["op"] in TEXT_OPS else None, "app": step.get("app")})
|
|
411
|
+
snapshot = self.settle(snapshot, step["op"])
|
|
412
|
+
index += 1
|
|
413
|
+
except Exception as error:
|
|
414
|
+
if not attempted:
|
|
415
|
+
raise # nothing was sent: the caller can hand the run on
|
|
416
|
+
# Something may have acted, even before ``completed`` advanced (a model callback raising after its
|
|
417
|
+
# tap): stop with what is known and never run it again.
|
|
418
|
+
index = max(index, out.completed)
|
|
419
|
+
detail = f"{type(error).__name__}: {str(error)[:160]}"
|
|
420
|
+
reason = (f"Step {index} ({describe(steps[index - 1])}) ran, then Mobster lost the screen: {detail}"
|
|
421
|
+
if out.completed else f"Step {index + 1} ({describe(steps[index])}) failed: {detail}")
|
|
422
|
+
return finish("error", reason, index, model_status="unknown_outcome")
|
|
423
|
+
try:
|
|
424
|
+
snapshot, _ = answer_dialogs(snapshot) # a dialog the last step brought up would hide the screen it reads
|
|
425
|
+
except DialogStop as stop:
|
|
426
|
+
return finish("dialog", str(stop), index)
|
|
427
|
+
return finish("done")
|
|
428
|
+
|
|
429
|
+
def seek(self, step, snapshot):
|
|
430
|
+
for _ in range(SEEK_SWIPES):
|
|
431
|
+
if find_target(step, snapshot) is not None or self.cancelled() or self._app_refused(step, snapshot):
|
|
432
|
+
return snapshot
|
|
433
|
+
app, now = step.get("app"), getattr(snapshot, "bundle_id", "") or ""
|
|
434
|
+
if app and app != now:
|
|
435
|
+
return snapshot
|
|
436
|
+
before = snapshot
|
|
437
|
+
self.driver.execute("SWIPE_UP", None, snapshot, timeout=10)
|
|
438
|
+
snapshot = self.settle(before, "SWIPE_UP")
|
|
439
|
+
self.emit({"event": "replay_seek", "label": (step.get("target") or {}).get("label")})
|
|
440
|
+
if getattr(snapshot, "content_fingerprint", None) == getattr(before, "content_fingerprint", object()):
|
|
441
|
+
return snapshot
|
|
442
|
+
return snapshot
|
|
443
|
+
|
|
444
|
+
def _app_refused(self, step, snapshot):
|
|
445
|
+
"""Why step ``step`` may not run here (the app it opens, or the one in front, isn't allowed), else ""."""
|
|
446
|
+
if self.allows_app is None:
|
|
447
|
+
return ""
|
|
448
|
+
apps = [getattr(snapshot, "bundle_id", "") or ""]
|
|
449
|
+
if step["op"] == "LAUNCH_APP":
|
|
450
|
+
apps.append(step.get("app") or "")
|
|
451
|
+
for bundle in apps:
|
|
452
|
+
if not bundle or bundle == "com.apple.springboard":
|
|
453
|
+
continue
|
|
454
|
+
try:
|
|
455
|
+
allowed = bool(self.allows_app(bundle))
|
|
456
|
+
except Exception: # noqa: BLE001 -- fails closed
|
|
457
|
+
allowed = False
|
|
458
|
+
if not allowed:
|
|
459
|
+
return f"{self.app_names.get(bundle) or bundle} isn't allowed yet"
|
|
460
|
+
return ""
|
|
461
|
+
|
|
462
|
+
def _frame(self, label):
|
|
463
|
+
if self.frame is None:
|
|
464
|
+
return None
|
|
465
|
+
try:
|
|
466
|
+
return self.frame(label)
|
|
467
|
+
except Exception:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
def _commit(self, step, element, snapshot, index):
|
|
471
|
+
"""None for a plain step; otherwise (effective act, refusal, approved screen fingerprint)."""
|
|
472
|
+
label = element.label if element is not None else (step.get("target") or {}).get("label")
|
|
473
|
+
act = step.get("commit") or harness_api.commit_act(step["op"], label, None,
|
|
474
|
+
getattr(snapshot, "bundle_id", None) or step.get("app"))
|
|
475
|
+
if self.decide is not None:
|
|
476
|
+
try:
|
|
477
|
+
decision = self.decide(step, element, snapshot, self.text_for(step))
|
|
478
|
+
except Exception: # noqa: BLE001 -- an unreadable step asks
|
|
479
|
+
decision = None
|
|
480
|
+
# Asks when either says so: the recorded act, or today's rules for this step as it looks now.
|
|
481
|
+
if decision is None:
|
|
482
|
+
act = act or "act"
|
|
483
|
+
elif decision.asks:
|
|
484
|
+
act = act or decision.act or decision.kind
|
|
485
|
+
if not act:
|
|
486
|
+
return None
|
|
487
|
+
if self.approve is None:
|
|
488
|
+
refusal = ("handoff", None) if self.on_commit == "handoff" else ("declined", "no one to ask")
|
|
489
|
+
return act, refusal, None
|
|
490
|
+
x, y, w, h = element.rect if element is not None else (0, 0, 0, 0)
|
|
491
|
+
app = getattr(snapshot, "bundle_id", None) or step.get("app")
|
|
492
|
+
request = {"kind": "commit", "step": index + 1, "operation": step["op"], "label": (label or "")[:200],
|
|
493
|
+
"role": element.role if element is not None else None, "text": self.text_for(step),
|
|
494
|
+
"title": f"{describe(step)}?", "act": act, "app_name": self.app_names.get(app) or None,
|
|
495
|
+
"target": {"x": round(x, 4), "y": round(y, 4), "w": round(w, 4), "h": round(h, 4)},
|
|
496
|
+
"replayed": True}
|
|
497
|
+
approved_screen = snapshot.fingerprint # capture before an approval callback can mutate the snapshot
|
|
498
|
+
answer = self.approve(request)
|
|
499
|
+
self.emit({"event": "replay_approval", "step": index + 1, "act": act,
|
|
500
|
+
"answer": answer.split(":", 1)[0] if isinstance(answer, str) else None})
|
|
501
|
+
if answer == "approved":
|
|
502
|
+
return act, None, approved_screen
|
|
503
|
+
return act, ("declined", str(answer or "denied")), None
|
|
504
|
+
|
|
505
|
+
def text_for(self, step):
|
|
506
|
+
if step.get("param"):
|
|
507
|
+
return self.params.get(step["param"])
|
|
508
|
+
return step.get("text")
|
|
509
|
+
|
|
510
|
+
def act(self, step, element, snapshot):
|
|
511
|
+
"""One action through the driver, the way frontier._execute sends it."""
|
|
512
|
+
from ..state import ADJUSTABLE_ROLES, TEXT_OPERATIONS, typing_timeout
|
|
513
|
+
driver, op = self.driver, step["op"]
|
|
514
|
+
if op == "LAUNCH_APP":
|
|
515
|
+
driver.execute("LAUNCH_APP", step["app"], snapshot, timeout=20)
|
|
516
|
+
elif op == "HOME":
|
|
517
|
+
driver.execute("HOME", None, snapshot, timeout=10)
|
|
518
|
+
elif op == "WAIT":
|
|
519
|
+
self.sleep(1.0)
|
|
520
|
+
elif op in POINT_OPS:
|
|
521
|
+
x, y = step["point"]
|
|
522
|
+
tap = driver.tap_point
|
|
523
|
+
try: # bind first: a TypeError raised inside the tap must never send it a second time
|
|
524
|
+
inspect.signature(tap).bind(x, y, snapshot, timeout=10, purpose=op.casefold())
|
|
525
|
+
except TypeError:
|
|
526
|
+
tap(x, y, snapshot, timeout=10)
|
|
527
|
+
else:
|
|
528
|
+
tap(x, y, snapshot, timeout=10, purpose=op.casefold())
|
|
529
|
+
elif op in SWIPES:
|
|
530
|
+
driver.execute(op, element if op in ("SWIPE_LEFT", "SWIPE_RIGHT") else None, snapshot, timeout=10)
|
|
531
|
+
elif op == "LONG_PRESS":
|
|
532
|
+
driver.long_press(element, snapshot, timeout=10)
|
|
533
|
+
elif op == "TAP":
|
|
534
|
+
driver.execute("TAP", element, snapshot, timeout=10)
|
|
535
|
+
elif op == "SUBMIT":
|
|
536
|
+
if "SUBMIT" not in element.actions:
|
|
537
|
+
driver.execute("TAP", element, snapshot, timeout=10)
|
|
538
|
+
snapshot = self.read()
|
|
539
|
+
element = find_target(step, snapshot) or element
|
|
540
|
+
driver.execute("SUBMIT", element, snapshot, timeout=10)
|
|
541
|
+
elif op in TEXT_OPS:
|
|
542
|
+
text = self.text_for(step)
|
|
543
|
+
if not isinstance(text, str):
|
|
544
|
+
raise ValueError("no text for this step")
|
|
545
|
+
if op == "SET_TEXT" and element.role in ADJUSTABLE_ROLES:
|
|
546
|
+
driver.set_value(element, text, timeout=10)
|
|
547
|
+
elif callable(getattr(driver, "write_text", None)):
|
|
548
|
+
append = op in TEXT_OPERATIONS and element.role != "SearchField"
|
|
549
|
+
receipt = driver.write_text(element, snapshot, text, append=append, submit=op == "TYPE_SUBMIT",
|
|
550
|
+
timeout=typing_timeout(text) + 20)
|
|
551
|
+
if not receipt.get("matches") or op == "TYPE_SUBMIT" and not receipt.get("submitted"):
|
|
552
|
+
raise ValueError("The driver did not confirm the recorded text or its submission; inspect "
|
|
553
|
+
"the field before trying again")
|
|
554
|
+
elif op == "SET_TEXT" and callable(getattr(driver, "replace_text", None)) and driver.replace_text(
|
|
555
|
+
element, text, timeout=typing_timeout(text)):
|
|
556
|
+
pass
|
|
557
|
+
elif op == "SET_TEXT":
|
|
558
|
+
clear = getattr(driver, "clear_text", None)
|
|
559
|
+
if callable(clear):
|
|
560
|
+
clear(element, timeout=10)
|
|
561
|
+
driver.execute("TYPE", element, snapshot, text=text, timeout=typing_timeout(text))
|
|
562
|
+
else:
|
|
563
|
+
driver.execute(op, element, snapshot, text=text, timeout=typing_timeout(text))
|
|
564
|
+
else:
|
|
565
|
+
raise ValueError(f"{op} can't be replayed")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def refreshed(step, element, snapshot):
|
|
569
|
+
"""``step`` with its anchors brought up to date; the label and role are what matched, so this is no heal."""
|
|
570
|
+
new = dict(step)
|
|
571
|
+
if step["op"] in POINT_OPS:
|
|
572
|
+
return new # the point stays bound to its recorded pre-action geometry
|
|
573
|
+
from ..state import signature
|
|
574
|
+
if element is not None and isinstance(step.get("target"), dict):
|
|
575
|
+
new["target"] = {**step["target"], "id": element.locator or step["target"].get("id"),
|
|
576
|
+
"frame": [round(float(v), 4) for v in element.rect]}
|
|
577
|
+
try:
|
|
578
|
+
pairs = signature(snapshot)
|
|
579
|
+
new["screen"] = {"hash": signature_hash(pairs), "signature": pairs}
|
|
580
|
+
except Exception:
|
|
581
|
+
pass
|
|
582
|
+
return new
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def heal_entry(index, old, new, why, before=None, after=None):
|
|
586
|
+
"""A heal as results.json's ``healed[]`` and the .heal.yaml file carry it."""
|
|
587
|
+
return {"step": index + 1, "text": describe(old[0]) if old else "",
|
|
588
|
+
"from": describe(old[0]) if old else "nothing",
|
|
589
|
+
"to": "; ".join(describe(step) for step in new[:3]) + ("; …" if len(new) > 3 else "") if new
|
|
590
|
+
else "nothing (the screen it needed came by itself)",
|
|
591
|
+
"why": why, "old": [anchor(step) for step in old], "new": [anchor(step) for step in new],
|
|
592
|
+
"replaced": len(old), "frames": {"before": before and str(before), "after": after and str(after)},
|
|
593
|
+
"status": "pending"}
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
__all__ = ["Replayer", "Result", "Heal", "match", "find_target", "heal_entry", "describe"]
|