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,186 @@
|
|
|
1
|
+
"""One device as the server drives it (SPEC §3): ``Phone``, and ``PrimaryPhone``, the runtime's own device."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Phone:
|
|
8
|
+
"""One device as the server drives it."""
|
|
9
|
+
|
|
10
|
+
primary = False
|
|
11
|
+
|
|
12
|
+
def __init__(self, fleet, *, id, kind, wda_url=None, mjpeg_url=None, udid=None, name=None, model=None, ios=None,
|
|
13
|
+
manager=None, setup=None):
|
|
14
|
+
self.fleet, self.runtime = fleet, fleet.runtime
|
|
15
|
+
self.id, self.kind, self.udid = id, kind, udid
|
|
16
|
+
self._wda_url, self.mjpeg_url = (wda_url or "").rstrip("/") or None, mjpeg_url
|
|
17
|
+
self.name, self.model, self.ios = name, model, ios
|
|
18
|
+
self.manager, self.setup = manager, setup
|
|
19
|
+
self.session_id, self.session_lock = None, threading.Lock()
|
|
20
|
+
self.health_at, self.health_ready = 0.0, False
|
|
21
|
+
self.health_lock = threading.Lock()
|
|
22
|
+
self._video = self._control = None
|
|
23
|
+
self.installed_apps = self.device_info = None
|
|
24
|
+
self.objects_lock = threading.Lock()
|
|
25
|
+
# A simulator's start (boot and WebDriverAgent) runs in the background: {"state", "error"}.
|
|
26
|
+
self.start_state = {"state": "idle", "error": None}
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def key(self):
|
|
30
|
+
"""What ``Runtime.active_runs`` records for a run on this device."""
|
|
31
|
+
return self.id
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def wda_url(self):
|
|
35
|
+
return self._wda_url
|
|
36
|
+
|
|
37
|
+
# -- WebDriverAgent ---------------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
def wda_session(self):
|
|
40
|
+
from .drivers import resolve_wda_session
|
|
41
|
+
with self.session_lock:
|
|
42
|
+
self.session_id = resolve_wda_session(self.wda_url, preferred=self.session_id)
|
|
43
|
+
return self.session_id
|
|
44
|
+
|
|
45
|
+
def current_session(self):
|
|
46
|
+
try:
|
|
47
|
+
return self.wda_session()
|
|
48
|
+
except Exception:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def target_status(self):
|
|
52
|
+
"""The runtime's target_status for this device: whether WDA answers and reaches the phone's UI."""
|
|
53
|
+
ready, health = False, None
|
|
54
|
+
if self.wda_url:
|
|
55
|
+
with self.health_lock:
|
|
56
|
+
if not self.health_at or time.monotonic() - self.health_at > 5:
|
|
57
|
+
from .transport import HTTP
|
|
58
|
+
client = None
|
|
59
|
+
try:
|
|
60
|
+
client = HTTP(self.wda_url)
|
|
61
|
+
response = client.request("GET", "/status", timeout=2)
|
|
62
|
+
self.health_ready = response.get("value", {}).get("ready") is True
|
|
63
|
+
if isinstance(response.get("sessionId"), str) and response["sessionId"]:
|
|
64
|
+
self.session_id = response["sessionId"]
|
|
65
|
+
except Exception:
|
|
66
|
+
self.health_ready = False
|
|
67
|
+
finally:
|
|
68
|
+
if client:
|
|
69
|
+
client.close()
|
|
70
|
+
self.health_at = time.monotonic()
|
|
71
|
+
ready = self.health_ready
|
|
72
|
+
health = self.manager.health_error() if self.manager is not None else None
|
|
73
|
+
if ready and health:
|
|
74
|
+
ready = False
|
|
75
|
+
return {"device": bool(self.wda_url), "ready": ready, "health": health, "can_act": bool(self.wda_url),
|
|
76
|
+
"driver": "wda", "screen_reading": False}
|
|
77
|
+
|
|
78
|
+
# -- per-device objects -----------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def video(self):
|
|
82
|
+
"""This device's live view (and the frame clock's stream), made on first use."""
|
|
83
|
+
if self._video is None and self.wda_url:
|
|
84
|
+
from .wda.video import WdaVideo
|
|
85
|
+
with self.objects_lock:
|
|
86
|
+
if self._video is None:
|
|
87
|
+
self._video = WdaVideo(self.wda_url, mjpeg_url=self.mjpeg_url, session=self.current_session,
|
|
88
|
+
quality=self.runtime.video_quality())
|
|
89
|
+
return self._video
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def control(self):
|
|
93
|
+
if self._control is None and self.wda_url:
|
|
94
|
+
from .manual_control import ManualControl
|
|
95
|
+
with self.objects_lock:
|
|
96
|
+
if self._control is None:
|
|
97
|
+
self._control = ManualControl(self.wda_url, self.wda_session)
|
|
98
|
+
return self._control
|
|
99
|
+
|
|
100
|
+
def apps(self):
|
|
101
|
+
"""The apps a task on this device may name: the catalog, then the apps found on the phone."""
|
|
102
|
+
from .catalog import APPS
|
|
103
|
+
from .installed_apps import merge as merge_installed
|
|
104
|
+
inventory = self.installed_apps.snapshot() if self.installed_apps else None
|
|
105
|
+
return merge_installed(APPS, inventory)
|
|
106
|
+
|
|
107
|
+
def info(self):
|
|
108
|
+
if self.device_info is not None:
|
|
109
|
+
return self.device_info.snapshot()
|
|
110
|
+
return {"modelIdentifier": self.model if self.kind == "usb" else None,
|
|
111
|
+
"modelName": self.model if self.kind == "simulator" else None, "iosVersion": self.ios,
|
|
112
|
+
"buildVersion": None, "virtual": self.kind != "usb", "source": self.kind,
|
|
113
|
+
"verifiedAt": None, "stale": False}
|
|
114
|
+
|
|
115
|
+
def close(self, keep_runner=False):
|
|
116
|
+
for item in (self._video, self._control):
|
|
117
|
+
if item is not None:
|
|
118
|
+
try:
|
|
119
|
+
item.close()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
if self.manager is not None:
|
|
123
|
+
if keep_runner:
|
|
124
|
+
self.manager.watching.set()
|
|
125
|
+
else:
|
|
126
|
+
self.manager.close()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class PrimaryPhone(Phone):
|
|
130
|
+
"""The runtime's own device: everything is the runtime's, looked up when used."""
|
|
131
|
+
|
|
132
|
+
primary = True
|
|
133
|
+
|
|
134
|
+
def __init__(self, fleet):
|
|
135
|
+
super().__init__(fleet, id=None, kind="usb")
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def key(self):
|
|
139
|
+
return "@primary"
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def wda_url(self):
|
|
143
|
+
url = getattr(self.runtime.config, "wda_url", None)
|
|
144
|
+
return url.rstrip("/") if isinstance(url, str) and url else None
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def manager(self):
|
|
148
|
+
return getattr(self.runtime, "manager", None)
|
|
149
|
+
|
|
150
|
+
@manager.setter
|
|
151
|
+
def manager(self, value):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def setup(self):
|
|
156
|
+
return getattr(self.runtime, "setup", None)
|
|
157
|
+
|
|
158
|
+
@setup.setter
|
|
159
|
+
def setup(self, value):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def video(self):
|
|
164
|
+
return self.runtime.video
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def control(self):
|
|
168
|
+
return getattr(self.runtime, "control", None)
|
|
169
|
+
|
|
170
|
+
def wda_session(self):
|
|
171
|
+
return self.runtime.wda_session()
|
|
172
|
+
|
|
173
|
+
def current_session(self):
|
|
174
|
+
return self.runtime.current_wda_session()
|
|
175
|
+
|
|
176
|
+
def target_status(self):
|
|
177
|
+
return self.runtime.target_status()
|
|
178
|
+
|
|
179
|
+
def apps(self):
|
|
180
|
+
return self.runtime.apps()
|
|
181
|
+
|
|
182
|
+
def info(self):
|
|
183
|
+
return self.runtime.device_info.snapshot()
|
|
184
|
+
|
|
185
|
+
def close(self, keep_runner=False):
|
|
186
|
+
pass # Runtime.close closes the runtime's own objects
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""The turn pipeline: observe, decide, verify and act, with a frontier model choosing each step."""
|
|
2
|
+
|
|
3
|
+
from ..agent_hooks import RESUME_AFTER_SECONDS
|
|
4
|
+
from .act import DISMISS_POINTS, MACRO_SWIPES, READ_LIST_ROWS, READ_LIST_SECONDS, SCROLL_TO_SEARCHABLE_SWIPES
|
|
5
|
+
from .actions import Action, parse_reply
|
|
6
|
+
from .agent import FrontierAgent, screen_goal
|
|
7
|
+
from .hooks import BATTERY_PAUSED_FEEDBACK, BATTERY_TOO_LONG, PAUSED_FEEDBACK, STEER_FEEDBACK, STOPPED_AFTER, _redacted
|
|
8
|
+
from .observe import RECOVERY_BUDGET_SECONDS, STALL_BUDGET_SECONDS, STUCK_APP_FEEDBACK
|
|
9
|
+
from .phases import MAX_ACTIONS, MAX_NOTES
|
|
10
|
+
from .prompt import (
|
|
11
|
+
DONE_CHECK,
|
|
12
|
+
FENCE_CLOSE,
|
|
13
|
+
FENCE_OPEN,
|
|
14
|
+
LISTING_HEAD,
|
|
15
|
+
MODEL_RETRY_WAITS,
|
|
16
|
+
PASSED_HEAD,
|
|
17
|
+
STEERING_MARK,
|
|
18
|
+
SYSTEM,
|
|
19
|
+
image_part,
|
|
20
|
+
prompt_messages,
|
|
21
|
+
prompt_text,
|
|
22
|
+
system_prompt,
|
|
23
|
+
)
|
|
24
|
+
from .protocol import ELEMENT_OPS, OPERATIONS, STEP_FIELD, _schema, normalize_reply, output_instruction
|
|
25
|
+
from .safety import (
|
|
26
|
+
CHANGED_WHILE_ASKING,
|
|
27
|
+
CONNECTION_LOST,
|
|
28
|
+
KEYPAD_REFUSAL,
|
|
29
|
+
MAX_COMMITS_PER_RUN,
|
|
30
|
+
PENDING_REASON,
|
|
31
|
+
PHONE_LOCKED,
|
|
32
|
+
SAME_FAILURES_TO_STOP,
|
|
33
|
+
UNKNOWN_BLOCKS,
|
|
34
|
+
UNKNOWN_REASON,
|
|
35
|
+
GuardStop,
|
|
36
|
+
binding,
|
|
37
|
+
failure_feedback,
|
|
38
|
+
guard,
|
|
39
|
+
revealed_actions,
|
|
40
|
+
same_binding,
|
|
41
|
+
)
|
|
42
|
+
from .saved import START_BACKS, back_control
|
|
43
|
+
from .screen import (
|
|
44
|
+
KEYBOARD_ROW,
|
|
45
|
+
PROVENANCE_MARK,
|
|
46
|
+
SCREENSHOT_SIDE,
|
|
47
|
+
UNDER_BAR_MARK,
|
|
48
|
+
Provenance,
|
|
49
|
+
content_rows,
|
|
50
|
+
kept_list,
|
|
51
|
+
lean_skip,
|
|
52
|
+
list_rows,
|
|
53
|
+
point_crop,
|
|
54
|
+
readback,
|
|
55
|
+
reads_back,
|
|
56
|
+
receipt_for,
|
|
57
|
+
screen_rows,
|
|
58
|
+
screenshot_part,
|
|
59
|
+
text_status,
|
|
60
|
+
video_frame,
|
|
61
|
+
view_of,
|
|
62
|
+
)
|
|
63
|
+
from .skills import default_skills, usable_skills
|
|
64
|
+
|
|
65
|
+
__all__ = ["Action", "BATTERY_PAUSED_FEEDBACK", "BATTERY_TOO_LONG", "CHANGED_WHILE_ASKING", "CONNECTION_LOST",
|
|
66
|
+
"DISMISS_POINTS",
|
|
67
|
+
"DONE_CHECK", "ELEMENT_OPS", "FENCE_CLOSE", "FENCE_OPEN", "FrontierAgent", "GuardStop", "KEYBOARD_ROW",
|
|
68
|
+
"KEYPAD_REFUSAL", "LISTING_HEAD", "MACRO_SWIPES", "MAX_ACTIONS", "MAX_COMMITS_PER_RUN", "MAX_NOTES",
|
|
69
|
+
"MODEL_RETRY_WAITS", "OPERATIONS", "PASSED_HEAD", "PAUSED_FEEDBACK", "PENDING_REASON", "PHONE_LOCKED",
|
|
70
|
+
"PROVENANCE_MARK", "Provenance", "READ_LIST_ROWS", "READ_LIST_SECONDS", "RECOVERY_BUDGET_SECONDS",
|
|
71
|
+
"RESUME_AFTER_SECONDS", "SAME_FAILURES_TO_STOP", "SCREENSHOT_SIDE", "SCROLL_TO_SEARCHABLE_SWIPES",
|
|
72
|
+
"STALL_BUDGET_SECONDS", "START_BACKS", "STEERING_MARK", "STEER_FEEDBACK", "STEP_FIELD", "STOPPED_AFTER",
|
|
73
|
+
"STUCK_APP_FEEDBACK", "SYSTEM", "UNDER_BAR_MARK", "UNKNOWN_BLOCKS", "UNKNOWN_REASON", "_redacted", "_schema",
|
|
74
|
+
"back_control", "binding", "content_rows", "default_skills", "failure_feedback", "guard",
|
|
75
|
+
"image_part", "kept_list", "lean_skip", "list_rows", "normalize_reply", "output_instruction",
|
|
76
|
+
"parse_reply", "point_crop", "prompt_messages", "prompt_text", "readback", "reads_back", "receipt_for",
|
|
77
|
+
"revealed_actions",
|
|
78
|
+
"same_binding", "screen_goal", "screen_rows", "screenshot_part", "system_prompt", "text_status",
|
|
79
|
+
"usable_skills", "video_frame", "view_of"]
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Acting on the phone: one action through the driver, the list macros, typed text and finding targets."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from ..state import ADJUSTABLE_ROLES, TEXT_OPERATIONS, typing_timeout
|
|
6
|
+
from ..task_policy import same_words
|
|
7
|
+
from ..wda.guards import DriverRejection
|
|
8
|
+
from ..wda.point_gestures import GESTURE_OPS
|
|
9
|
+
from .protocol import TYPED_OPS
|
|
10
|
+
from .safety import _commits, consequential
|
|
11
|
+
from .screen import editable_in, looks_like_field, receipt_for, text_difference
|
|
12
|
+
|
|
13
|
+
MACRO_SWIPES = 8
|
|
14
|
+
SCROLL_TO_SEARCHABLE_SWIPES = 3
|
|
15
|
+
READ_LIST_SECONDS = 8.0
|
|
16
|
+
READ_LIST_ROWS = 60
|
|
17
|
+
MACRO_PAUSE_SECONDS = .35
|
|
18
|
+
# After a glide (no momentum) the list is still at release.
|
|
19
|
+
MACRO_GLIDE_PAUSE_SECONDS = .1
|
|
20
|
+
# A tap at the status bar scrolls to top instead of closing a menu, so each DISMISS tries the next free point.
|
|
21
|
+
DISMISS_POINTS = ((.04, .5), (.5, .7), (.96, .5), (.5, .97), (.04, .2), (.96, .2), (.5, .035))
|
|
22
|
+
# Actions that leave the screen: before one, a value the turn recorded under a bottom bar is scrolled into view.
|
|
23
|
+
LEAVING_OPS = frozenset({"LAUNCH_APP", "HOME", "OPEN_URL"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_address_field(element):
|
|
27
|
+
label = " ".join((getattr(element, "label", "") or "").split()).casefold()
|
|
28
|
+
placeholder = " ".join((getattr(element, "placeholder", "") or "").split()).casefold()
|
|
29
|
+
return bool(getattr(element, "editable", False)) and (
|
|
30
|
+
label in ("address", "url") or "website" in placeholder or "enter website" in label)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# A tap soon after a search's Return waits for two agreeing reads: results still arriving move under it.
|
|
34
|
+
SUBMIT_WINDOW_SECONDS = 4.0
|
|
35
|
+
SUBMIT_SETTLE_GAP = .35
|
|
36
|
+
SUBMIT_SETTLE_SECONDS = 3.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Never one a tap could commit: the model chooses those.
|
|
40
|
+
def opens_field(element, snapshot):
|
|
41
|
+
return (element is not None and not element.editable and element.role not in ADJUSTABLE_ROLES
|
|
42
|
+
and (looks_like_field(element) or element.role == "Button"
|
|
43
|
+
and " ".join((element.label or "").split()).casefold() == "search")
|
|
44
|
+
and editable_in(snapshot, element) is None and not consequential("TAP", element, snapshot))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# A chained tap on a tile's name goes to the tile: on the name, Files opens a rename.
|
|
48
|
+
CONTAINER_ROLES = frozenset({"Cell", "Button"})
|
|
49
|
+
TEXT_ROLES = frozenset({"StaticText", "Text", "Other", "Image", "Icon", "NavigationBar"})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _container(snapshot, element, want):
|
|
53
|
+
if element is None or element.role != "StaticText":
|
|
54
|
+
return element
|
|
55
|
+
x, y = element.center
|
|
56
|
+
for other in snapshot.elements:
|
|
57
|
+
if other is element or other.role not in CONTAINER_ROLES:
|
|
58
|
+
continue
|
|
59
|
+
label = " ".join((other.label or "").split()).casefold()
|
|
60
|
+
ox, oy, ow, oh = other.rect
|
|
61
|
+
if (label.startswith(want) and len(label) > len(want) and label[len(want)] in ",: ("
|
|
62
|
+
and ox <= x <= ox + ow and oy <= y <= oy + oh):
|
|
63
|
+
return other
|
|
64
|
+
return element
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ActMixin:
|
|
68
|
+
def _read_list(self, snapshot, needle=None):
|
|
69
|
+
from ..state import redundant_ids
|
|
70
|
+
lines, seen, current = [], set(), snapshot
|
|
71
|
+
want = " ".join(str(needle or "").split()).casefold()
|
|
72
|
+
found = []
|
|
73
|
+
begun, last_swipe = time.monotonic(), 0.0
|
|
74
|
+
app = getattr(snapshot, "bundle_id", None)
|
|
75
|
+
|
|
76
|
+
def texts(screen):
|
|
77
|
+
masked = self._masked(screen)
|
|
78
|
+
skip = redundant_ids(masked.public().get("elements") or ())
|
|
79
|
+
for element in masked.elements:
|
|
80
|
+
if element.editable or element.id in skip:
|
|
81
|
+
continue
|
|
82
|
+
value = element.value if element.value and element.value != element.label else ""
|
|
83
|
+
text = " ".join(f"{element.label or ''} {value}".split())
|
|
84
|
+
if text:
|
|
85
|
+
yield text
|
|
86
|
+
|
|
87
|
+
if self.run_state.list_read and self.run_state.list_read[0] == app and (
|
|
88
|
+
self.run_state.list_read[1] == snapshot.content_fingerprint
|
|
89
|
+
or set(texts(snapshot)) <= self.run_state.list_read[2]):
|
|
90
|
+
# Read afresh, the same screen filled the cap again and the list never moved.
|
|
91
|
+
seen = set(self.run_state.list_read[2])
|
|
92
|
+
self.run_state.list_read = None
|
|
93
|
+
|
|
94
|
+
def harvest(screen):
|
|
95
|
+
new = 0
|
|
96
|
+
for text in texts(screen):
|
|
97
|
+
if text not in seen:
|
|
98
|
+
seen.add(text)
|
|
99
|
+
lines.append(text[:300])
|
|
100
|
+
new += 1
|
|
101
|
+
if want and want in text.casefold():
|
|
102
|
+
found.append(text)
|
|
103
|
+
return new
|
|
104
|
+
ended = False
|
|
105
|
+
for swipe in range(MACRO_SWIPES + 1):
|
|
106
|
+
new = harvest(current)
|
|
107
|
+
ended = bool(swipe and not new)
|
|
108
|
+
if (found or len(lines) >= READ_LIST_ROWS or swipe == MACRO_SWIPES or ended or self._stopping()
|
|
109
|
+
or time.monotonic() - begun + last_swipe > READ_LIST_SECONDS):
|
|
110
|
+
break
|
|
111
|
+
t_swipe = time.monotonic()
|
|
112
|
+
self._drag_next() # XCTest's drag even where SWIPE_UP glides
|
|
113
|
+
self.driver.execute("SWIPE_UP", None, current, timeout=10)
|
|
114
|
+
current = self._macro_read()
|
|
115
|
+
last_swipe = time.monotonic() - t_swipe
|
|
116
|
+
if found:
|
|
117
|
+
self.run_state.macro_feedback = f"READ_LIST stopped at the first row containing {needle!r}."
|
|
118
|
+
elif not ended and (len(lines) >= READ_LIST_ROWS or time.monotonic() - begun + last_swipe > READ_LIST_SECONDS):
|
|
119
|
+
self.run_state.macro_feedback = (f"READ_LIST stopped after {min(len(lines), READ_LIST_ROWS)} rows and "
|
|
120
|
+
f"{time.monotonic() - begun:.0f} s, before the end of the list. If you need more, "
|
|
121
|
+
"READ_LIST again from here; if the answer is in these rows, use them.")
|
|
122
|
+
self.run_state.list_read = (app, current.content_fingerprint,
|
|
123
|
+
(seen - set(lines)) | set(lines[:READ_LIST_ROWS]))
|
|
124
|
+
return lines[:READ_LIST_ROWS]
|
|
125
|
+
|
|
126
|
+
def _drag_next(self):
|
|
127
|
+
drag_next = getattr(self.driver, "drag_next", None)
|
|
128
|
+
return bool(callable(drag_next) and drag_next())
|
|
129
|
+
|
|
130
|
+
def _macro_read(self):
|
|
131
|
+
glided = getattr(self.driver, "last_swipe_glided", False) is True
|
|
132
|
+
time.sleep(MACRO_GLIDE_PAUSE_SECONDS if glided else MACRO_PAUSE_SECONDS)
|
|
133
|
+
return self._read_once()
|
|
134
|
+
|
|
135
|
+
def _scroll_to(self, snapshot, text):
|
|
136
|
+
want, current = " ".join(text.split()).casefold(), snapshot
|
|
137
|
+
|
|
138
|
+
def shown(screen):
|
|
139
|
+
return any(want in " ".join((e.label or "").split()).casefold() and .08 < e.center[1] < .9
|
|
140
|
+
for e in screen.elements)
|
|
141
|
+
search = next((e for e in snapshot.elements if e.role == "SearchField"), None)
|
|
142
|
+
for swipe in range(MACRO_SWIPES):
|
|
143
|
+
if shown(current):
|
|
144
|
+
return True
|
|
145
|
+
if self._stopping():
|
|
146
|
+
break
|
|
147
|
+
if search is not None and swipe >= SCROLL_TO_SEARCHABLE_SWIPES:
|
|
148
|
+
# A list with its own search field: a name further down is found faster by searching.
|
|
149
|
+
label = (search.label or search.value or "Search")[:40]
|
|
150
|
+
self.run_state.macro_feedback = (
|
|
151
|
+
f"SCROLL_TO: {text!r} is not in the first {swipe + 1} screens of this list, "
|
|
152
|
+
f"and the list has a search field ({label!r}): search for it there.")
|
|
153
|
+
return False
|
|
154
|
+
self.driver.execute("SWIPE_UP", None, current, timeout=10)
|
|
155
|
+
after = self._macro_read()
|
|
156
|
+
if after.content_fingerprint == current.content_fingerprint and self._drag_next():
|
|
157
|
+
self.driver.execute("SWIPE_UP", None, current, timeout=10) # as in _read_list: once, a drag
|
|
158
|
+
after = self._macro_read()
|
|
159
|
+
if after.content_fingerprint == current.content_fingerprint:
|
|
160
|
+
break
|
|
161
|
+
current = after
|
|
162
|
+
return shown(current)
|
|
163
|
+
|
|
164
|
+
def _reveal_found(self, state):
|
|
165
|
+
"""One code swipe up, once a turn, before the turn leaves its screen or finishes, when a value it recorded sits
|
|
166
|
+
only in rows a bottom bar covers or the screen's foot cuts (Contract.hidden_row), so the run's frames show it.
|
|
167
|
+
Journaled before it is sent; no model call. iOSWorld dev, 22 Oct: +0.074 rubric score over 34 paired runs."""
|
|
168
|
+
if self._contract is None or self.run_state.revealed == state.step or self._stopping():
|
|
169
|
+
return
|
|
170
|
+
self.run_state.revealed = state.step
|
|
171
|
+
row = self._contract.hidden_row(self._masked(state.snapshot).elements, state.step)
|
|
172
|
+
if row is None:
|
|
173
|
+
return
|
|
174
|
+
event = {"event": "frontier_cleanup", "step": state.step, "operation": "SWIPE_UP",
|
|
175
|
+
"label": self._mask(row.label)[:40], "reason": "reveal_value"}
|
|
176
|
+
t_reveal, before = time.monotonic(), state.snapshot
|
|
177
|
+
self._emit({**event, "state": "sending"})
|
|
178
|
+
try:
|
|
179
|
+
self._drag_next() # XCTest's drag, a measured half screen: a glide's 0.68 can carry the row under a top bar
|
|
180
|
+
self.driver.execute("SWIPE_UP", None, before, timeout=10)
|
|
181
|
+
except Exception as error:
|
|
182
|
+
self._emit({**event, "state": "failed", "detail": type(error).__name__})
|
|
183
|
+
state.snapshot, state.unproven = self._observe(), False # it may have moved: never sent again
|
|
184
|
+
return
|
|
185
|
+
state.snapshot, state.unproven = self._settle(before), False
|
|
186
|
+
self._spend("reveal", t_reveal)
|
|
187
|
+
self._emit({**event, "state": "sent"})
|
|
188
|
+
self._contract.observe(self._masked(state.snapshot), self._app_name(state.snapshot), state.step)
|
|
189
|
+
|
|
190
|
+
def _exact_text(self):
|
|
191
|
+
return callable(getattr(self.driver, "write_text", None))
|
|
192
|
+
|
|
193
|
+
def _write(self, op, element, text, snapshot):
|
|
194
|
+
# A search, an address and a submit into text this task didn't write replace what the field holds.
|
|
195
|
+
append = op in TEXT_OPERATIONS and element.role != "SearchField" and not is_address_field(element) and not (
|
|
196
|
+
op == "TYPE_SUBMIT" and self._foreign_text(element))
|
|
197
|
+
receipt = self.driver.write_text(element, snapshot, text or "", append=append, submit=op == "TYPE_SUBMIT",
|
|
198
|
+
timeout=typing_timeout(text) + 20)
|
|
199
|
+
self.run_state.receipts[element.locator or element.label] = receipt
|
|
200
|
+
self.run_state.last_receipt = receipt
|
|
201
|
+
if receipt.get("value") is not None and not receipt.get("matches"):
|
|
202
|
+
self.run_state.macro_feedback = (
|
|
203
|
+
"The field does not hold what you wrote ("
|
|
204
|
+
+ ("it was cleared and written once more" if receipt.get("repaired") else
|
|
205
|
+
"what it held before was kept, and nothing was rewritten: the app may have formatted it")
|
|
206
|
+
+ "): " + text_difference(receipt["value"], receipt.get("expected") or "")
|
|
207
|
+
+ ". Do not retype blindly: see the field's status on this screen.")
|
|
208
|
+
if op == "TYPE_SUBMIT" and receipt.get("submitted") is False:
|
|
209
|
+
said = self.run_state.macro_feedback
|
|
210
|
+
self.run_state.macro_feedback = ((said + " ") if said else "") + (
|
|
211
|
+
"Return was not pressed, because the field does not hold exactly what you wrote: fix it (SET_TEXT "
|
|
212
|
+
"replaces the field's text), then SUBMIT.")
|
|
213
|
+
|
|
214
|
+
def _appends(self, op, element):
|
|
215
|
+
if not self._exact_text():
|
|
216
|
+
return True
|
|
217
|
+
return element.role != "SearchField" and not is_address_field(element) and not (
|
|
218
|
+
op == "TYPE_SUBMIT" and self._foreign_text(element))
|
|
219
|
+
|
|
220
|
+
def _foreign_text(self, element):
|
|
221
|
+
shown = "" if element.placeholder else (element.value or "")
|
|
222
|
+
if not shown.strip() or shown.strip() == (element.label or "").strip():
|
|
223
|
+
return False
|
|
224
|
+
return receipt_for(self.run_state.receipts, element) is None
|
|
225
|
+
|
|
226
|
+
def _gesture(self, gesture, snapshot):
|
|
227
|
+
send = getattr(self.driver, gesture.kind + "_point", None)
|
|
228
|
+
if not callable(send):
|
|
229
|
+
raise DriverRejection("unsupported_or_stale_target")
|
|
230
|
+
options = {"duration": gesture.duration, "timeout": 15 if gesture.kind == "hold" else 10}
|
|
231
|
+
if gesture.kind == "drag":
|
|
232
|
+
options["hold_seconds"] = gesture.hold_seconds
|
|
233
|
+
send(*(coordinate for location in gesture.points for coordinate in location), snapshot, **options)
|
|
234
|
+
|
|
235
|
+
# Fails closed: unsure means no tap.
|
|
236
|
+
def _still_after_submit(self, element):
|
|
237
|
+
deadline = time.monotonic() + SUBMIT_SETTLE_SECONDS
|
|
238
|
+
previous = self._read_once()
|
|
239
|
+
while True:
|
|
240
|
+
time.sleep(SUBMIT_SETTLE_GAP)
|
|
241
|
+
current = self._read_once()
|
|
242
|
+
same = current.content_fingerprint == previous.content_fingerprint
|
|
243
|
+
if same and element is not None:
|
|
244
|
+
before = self._find(previous, element.label, element.role, element.locator)
|
|
245
|
+
after = self._find(current, element.label, element.role, element.locator)
|
|
246
|
+
if before is None and after is None:
|
|
247
|
+
return current, None
|
|
248
|
+
if before is not None and after is not None and tuple(before.rect) == tuple(after.rect):
|
|
249
|
+
return current, after
|
|
250
|
+
elif same:
|
|
251
|
+
return current, None
|
|
252
|
+
if time.monotonic() >= deadline:
|
|
253
|
+
return None, None
|
|
254
|
+
previous = current
|
|
255
|
+
|
|
256
|
+
def _execute(self, op, element, app, text, snapshot, point=None):
|
|
257
|
+
if op != "READ_LIST":
|
|
258
|
+
self.run_state.list_read = None
|
|
259
|
+
if op == "TAP_XY":
|
|
260
|
+
self.driver.tap_point(point[0], point[1], snapshot, timeout=10, purpose="tap_xy")
|
|
261
|
+
elif op in GESTURE_OPS:
|
|
262
|
+
self._gesture(point, snapshot)
|
|
263
|
+
elif op == "LAUNCH_APP":
|
|
264
|
+
# A start reset's reopen, in the turn that chose it only: never a later LAUNCH_APP.
|
|
265
|
+
fresh, self.run_state.fresh_launch = self.run_state.fresh_launch == (app, self.run_state.calls), None
|
|
266
|
+
relaunch = getattr(self.driver, "relaunch_app", None)
|
|
267
|
+
if not (fresh and callable(relaunch) and relaunch(app, timeout=40)):
|
|
268
|
+
self.driver.execute("LAUNCH_APP", app, snapshot, timeout=20)
|
|
269
|
+
elif op in ("SWIPE_LEFT", "SWIPE_RIGHT") and element is not None:
|
|
270
|
+
self.driver.execute(op, element, snapshot, timeout=10) # through the targeted row (B7)
|
|
271
|
+
elif op == "HOME" or op.startswith("SWIPE_"):
|
|
272
|
+
self.driver.execute(op, None, snapshot, timeout=10)
|
|
273
|
+
elif op == "LONG_PRESS":
|
|
274
|
+
self.driver.long_press(element, snapshot, timeout=10)
|
|
275
|
+
elif op == "READ_LIST":
|
|
276
|
+
self.run_state.listing = self._read_list(snapshot, text)
|
|
277
|
+
elif op == "SCROLL_TO":
|
|
278
|
+
if not self._scroll_to(snapshot, text) and not self.run_state.macro_feedback:
|
|
279
|
+
self.run_state.macro_feedback = (
|
|
280
|
+
f"SCROLL_TO found no item containing {text!r} down to the end of the list.")
|
|
281
|
+
elif op == "DISMISS":
|
|
282
|
+
free = [(px, py) for px, py in DISMISS_POINTS if not any(
|
|
283
|
+
ex <= px <= ex + ew and ey <= py <= ey + eh for ex, ey, ew, eh in
|
|
284
|
+
(e.rect for e in snapshot.elements))] or list(DISMISS_POINTS)
|
|
285
|
+
screen, tries = self.run_state.dismissed
|
|
286
|
+
tries = tries + 1 if screen == snapshot.content_fingerprint else 0
|
|
287
|
+
self.run_state.dismissed = (snapshot.content_fingerprint, tries)
|
|
288
|
+
x, y = free[tries % len(free)]
|
|
289
|
+
# A harness that checks actions sees a DISMISS; a phone driver taps the point.
|
|
290
|
+
tap = getattr(self.driver, "dismiss_at", None)
|
|
291
|
+
(tap if callable(tap) else self.driver.tap_point)(x, y, snapshot, timeout=10)
|
|
292
|
+
elif op == "SET_TEXT" and element.role in ADJUSTABLE_ROLES:
|
|
293
|
+
self.driver.set_value(element, text, timeout=30) # a wheel may be stepped item by item
|
|
294
|
+
elif op in TYPED_OPS and self._exact_text():
|
|
295
|
+
self._write(op, element, text, snapshot)
|
|
296
|
+
elif op == "SET_TEXT" and not text:
|
|
297
|
+
clear = getattr(self.driver, "clear_text", None)
|
|
298
|
+
if not callable(clear):
|
|
299
|
+
raise ValueError("this driver cannot empty a field")
|
|
300
|
+
clear(element, timeout=10)
|
|
301
|
+
elif op == "SET_TEXT" and callable(getattr(self.driver, "replace_text", None)) and self.driver.replace_text(
|
|
302
|
+
element, text, timeout=typing_timeout(text)):
|
|
303
|
+
pass # cleared and typed through one element reference, no read between
|
|
304
|
+
elif op == "SET_TEXT":
|
|
305
|
+
clear = getattr(self.driver, "clear_text", None)
|
|
306
|
+
if callable(clear):
|
|
307
|
+
clear(element, timeout=10)
|
|
308
|
+
# One read: the field is empty when the clear returns.
|
|
309
|
+
try:
|
|
310
|
+
snapshot = self.driver.observe(timeout=15)
|
|
311
|
+
except Exception:
|
|
312
|
+
snapshot = self._observe()
|
|
313
|
+
element = next((e for e in snapshot.elements if e.locator == element.locator), element)
|
|
314
|
+
self.driver.execute("TYPE", element, snapshot, text=text, timeout=typing_timeout(text))
|
|
315
|
+
elif op == "SUBMIT" and "SUBMIT" not in element.actions:
|
|
316
|
+
# Return needs a focused field.
|
|
317
|
+
self.driver.execute("TAP", element, snapshot, timeout=10)
|
|
318
|
+
snapshot = self._observe()
|
|
319
|
+
element = self._find(snapshot, element.label, element.role, element.locator) or element
|
|
320
|
+
self.driver.execute("SUBMIT", element, snapshot, timeout=10)
|
|
321
|
+
else:
|
|
322
|
+
self.driver.execute(op, element, snapshot, text=text, timeout=typing_timeout(text))
|
|
323
|
+
return snapshot
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _find(snapshot, label, role=None, locator=None):
|
|
327
|
+
want = " ".join((label or "").split()).casefold()
|
|
328
|
+
if not want or not want.strip():
|
|
329
|
+
# A labelled element is found by its label: paths shift as the screen changes (splitpay-001).
|
|
330
|
+
if locator:
|
|
331
|
+
return next((e for e in snapshot.elements if e.locator == locator
|
|
332
|
+
and (role is None or e.role == role)), None)
|
|
333
|
+
return None
|
|
334
|
+
exact = [e for e in snapshot.elements if " ".join((e.label or "").split()).casefold() == want]
|
|
335
|
+
if len(exact) > 1 and locator:
|
|
336
|
+
same = [e for e in exact if e.locator == locator]
|
|
337
|
+
if same:
|
|
338
|
+
return same[0]
|
|
339
|
+
# An unlabelled field is named by its placeholder value ("Search restaurants").
|
|
340
|
+
norm = [(e, " ".join((e.label or (e.value if e.editable else "") or "").split()).casefold())
|
|
341
|
+
for e in snapshot.elements]
|
|
342
|
+
found = [e for e, text in norm if text == want]
|
|
343
|
+
if not found:
|
|
344
|
+
# The model names later targets from memory ("Save" for "Save Note"): a unique partial match is it.
|
|
345
|
+
found = [e for e, text in norm if text and len(want) >= 3 and (want in text or text in want)]
|
|
346
|
+
if len(found) > 1:
|
|
347
|
+
found = [e for e in found if role and e.role == role] if role else []
|
|
348
|
+
if len(found) != 1:
|
|
349
|
+
return None
|
|
350
|
+
if role is None and len(found) > 1:
|
|
351
|
+
# By label alone a control beats text that reads the same: Calculator's display shows the 4 just typed.
|
|
352
|
+
controls = [e for e in found if e.role not in TEXT_ROLES]
|
|
353
|
+
found = controls or found
|
|
354
|
+
return next((e for e in found if role and e.role == role), found[0] if found else None)
|
|
355
|
+
|
|
356
|
+
def _target(self, act, index, aliases, snapshot, after=None):
|
|
357
|
+
element = None
|
|
358
|
+
if act.locator:
|
|
359
|
+
# The Return of a split TYPE_SUBMIT goes to the very field just typed into.
|
|
360
|
+
element = self._find(snapshot, act.target_label, act.role, act.locator)
|
|
361
|
+
if element is not None:
|
|
362
|
+
return element
|
|
363
|
+
if act.target in aliases:
|
|
364
|
+
# Typing changes the screen: later actions re-find an id by its path or label.
|
|
365
|
+
element = aliases[act.target]
|
|
366
|
+
element = self._find(snapshot, element.label, element.role, element.locator) if index else element
|
|
367
|
+
if element is None and act.target_label:
|
|
368
|
+
element = self._find(snapshot, act.target_label)
|
|
369
|
+
if act.op == "TAP":
|
|
370
|
+
element = _container(snapshot, element, " ".join(act.target_label.split()).casefold())
|
|
371
|
+
if element is not None and not same_words(element.label, act.target_label) and _commits(element):
|
|
372
|
+
# A commit is found by its exact label only: "Delete" never finds "Delete Account".
|
|
373
|
+
element = None
|
|
374
|
+
op = act.op
|
|
375
|
+
if element is None and index and (after == "TAP" and op in TYPED_OPS | {"SUBMIT"}
|
|
376
|
+
or after in TYPED_OPS and op == "SUBMIT"):
|
|
377
|
+
# A tap that opened a search screen leaves one field, under another name: the typing goes there.
|
|
378
|
+
fields = [e for e in snapshot.elements if e.editable and (op != "SUBMIT" or "SUBMIT" in e.actions)]
|
|
379
|
+
element = fields[0] if len(fields) == 1 else None
|
|
380
|
+
return element
|