mobster-cli 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
mobile_agent/server.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Loopback-only dashboard API with SSE, cancellation, and one device owner at a time."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from http.server import ThreadingHTTPServer
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from . import engine_health, engines, eventbus, harness_api, ownership, plan_gate, tracks
|
|
9
|
+
from .api_devices import DeviceRoutesMixin
|
|
10
|
+
from .api_get import GetRoutesMixin
|
|
11
|
+
from .api_http import ApiBase
|
|
12
|
+
from .api_post import WriteRoutesMixin
|
|
13
|
+
from .app_icons import AppIcons
|
|
14
|
+
from .cli_constants import DEFAULT_WDA_URL
|
|
15
|
+
from .costs import NANODOLLARS
|
|
16
|
+
from .device_info import DeviceInfo
|
|
17
|
+
from .device_manager import DeviceManager
|
|
18
|
+
from .fanout import GROUP_ID, Fanout
|
|
19
|
+
from .fleet import Fleet
|
|
20
|
+
from .installed_apps import InstalledApps, InventoryCache
|
|
21
|
+
from .journal import Journal, JournalError
|
|
22
|
+
from .manual_control import ManualControl
|
|
23
|
+
from .paths import user_data_dir
|
|
24
|
+
from .pool import default_mjpeg_url
|
|
25
|
+
from .power_watch import PowerWatch
|
|
26
|
+
from .run import Run
|
|
27
|
+
from .runtime_create import TaskStartMixin
|
|
28
|
+
from .runtime_driver import PhoneDriverMixin
|
|
29
|
+
from .runtime_limits import LimitsMixin
|
|
30
|
+
from .runtime_queue import PhoneQueueMixin
|
|
31
|
+
from .runtime_runs import RunActionsMixin
|
|
32
|
+
from .runtime_status import StatusMixin
|
|
33
|
+
from .runtime_teach import LiveViewMixin
|
|
34
|
+
from .runtime_work import TaskWorkMixin
|
|
35
|
+
from .setup_service import SetupService
|
|
36
|
+
from .wda.frame_store import FrameStore
|
|
37
|
+
from .wda.video import WdaVideo
|
|
38
|
+
from .workflows.service import Workflows
|
|
39
|
+
|
|
40
|
+
DONE_ACTION_EVENTS = frozenset({"action_acknowledged", "frontier_action"})
|
|
41
|
+
ACTING_EVENTS = frozenset({"action_started", "app_launch_started", "frontier_action", "frontier_failed",
|
|
42
|
+
"skill_started",
|
|
43
|
+
"frontier_app_relaunched", "frontier_cleanup"})
|
|
44
|
+
IDLE_OPERATIONS = frozenset({None, "DONE", "BLOCKED", "WAIT"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def may_have_acted(event):
|
|
48
|
+
# Fails closed: a decision is journaled before it is sent, and one whose ops can't be read counts as acting.
|
|
49
|
+
kind = event.get("event")
|
|
50
|
+
if kind in ACTING_EVENTS:
|
|
51
|
+
return True
|
|
52
|
+
if kind != "frontier_decision":
|
|
53
|
+
return False
|
|
54
|
+
ops = event.get("ops")
|
|
55
|
+
return not isinstance(ops, list) or any(op not in IDLE_OPERATIONS for op in ops)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Runtime(PhoneQueueMixin, PhoneDriverMixin, LiveViewMixin, LimitsMixin, StatusMixin, TaskStartMixin, TaskWorkMixin,
|
|
59
|
+
RunActionsMixin):
|
|
60
|
+
# `mobster run` and the terminal UI: a task that names no device runs on the one target, without the device list.
|
|
61
|
+
one_target = False
|
|
62
|
+
plan_gate = None
|
|
63
|
+
# The demo (a scripted phone and model) never acts on a phone, so it needs no plan.
|
|
64
|
+
needs_plan = True
|
|
65
|
+
# The sentence a yes to an outside agent's approval is refused with, or None: the hosted review demo
|
|
66
|
+
# (remote/review.py) sets it, and Run.answer_approval then takes no yes from the Mac window, a link or the console.
|
|
67
|
+
approvals_closed = None
|
|
68
|
+
# The device list's class (fleet.Fleet); a scripted world of several phones gives its own (tests/fleet_world.py).
|
|
69
|
+
fleet_class = Fleet
|
|
70
|
+
|
|
71
|
+
def __init__(self, config):
|
|
72
|
+
# The tracks register first: their schemas migrate when the journal opens below.
|
|
73
|
+
tracks.load()
|
|
74
|
+
self.config = config
|
|
75
|
+
self.runs = {}
|
|
76
|
+
self.lock = threading.Lock()
|
|
77
|
+
self._conversation_apps = {}
|
|
78
|
+
self.listeners = harness_api.ListenerQueue()
|
|
79
|
+
self.bus = eventbus.bus
|
|
80
|
+
# run id -> device id: one live run per device.
|
|
81
|
+
self.active_runs = {}
|
|
82
|
+
self.queues = ownership.Queues(ttl=ownership.ttl_seconds())
|
|
83
|
+
self.queued = {}
|
|
84
|
+
self.queue_timers = {}
|
|
85
|
+
self.pool = self.make_pool(config)
|
|
86
|
+
self.inventory = {}
|
|
87
|
+
self.inventory_at = 0
|
|
88
|
+
self.inventory_lock = threading.Lock()
|
|
89
|
+
self.inventory_ok = False
|
|
90
|
+
self.wda_health_at, self.wda_ready = 0, False
|
|
91
|
+
# Re-resolved from WDA itself: a runner restart replaces the session, so a fixed --session would fail.
|
|
92
|
+
self.wda_session_id, self.wda_session_lock = getattr(config, "session", None), threading.Lock()
|
|
93
|
+
self.closing = False
|
|
94
|
+
self.threads = set()
|
|
95
|
+
self.streams = threading.BoundedSemaphore(8)
|
|
96
|
+
# Set by ``serve`` once it listens; Mobster Remote's bridge waits on ``serving``.
|
|
97
|
+
self.local_api = None
|
|
98
|
+
self.serving = threading.Event()
|
|
99
|
+
self.device_info = self.make_device_info(config)
|
|
100
|
+
self.awake = self.make_awake_lease()
|
|
101
|
+
self.power = PowerWatch(self)
|
|
102
|
+
# WDA's MJPEG port is paired with its own (8100 -> 9100, 8203 -> 9203), never another device's stream.
|
|
103
|
+
self.video = (WdaVideo(config.wda_url, mjpeg_url=default_mjpeg_url(config.wda_url),
|
|
104
|
+
session=self.current_wda_session,
|
|
105
|
+
on_activity=self.video_activity, quality=self.video_quality())
|
|
106
|
+
if getattr(config, "wda_url", None) else self.make_video(config))
|
|
107
|
+
self.control = ManualControl(config.wda_url, self.wda_session) if getattr(config, "wda_url", None) else None
|
|
108
|
+
self.controlled_at = {}
|
|
109
|
+
self.lessons, self.lessons_lock = {}, threading.Lock()
|
|
110
|
+
self.journal = Journal(getattr(config, "state_db", None))
|
|
111
|
+
state_db = getattr(config, "state_db", None)
|
|
112
|
+
self.frames = FrameStore(Path(state_db).parent / "frames" if state_db else None)
|
|
113
|
+
self.model_reach = engines.ModelReach()
|
|
114
|
+
self.scheduler = None
|
|
115
|
+
self.manager = self.setup = None
|
|
116
|
+
self.installed_apps = None
|
|
117
|
+
data_dir = Path(getattr(config, "data_dir", None) or user_data_dir())
|
|
118
|
+
self.plan_gate = plan_gate.for_runtime(config, data_dir) if self.needs_plan else None
|
|
119
|
+
self.icons = AppIcons(data_dir / "app-icons", allowed=self.icon_bundles)
|
|
120
|
+
if getattr(config, "manage_device", False):
|
|
121
|
+
env_file = getattr(config, "env_file", None) or data_dir / "agent.env"
|
|
122
|
+
self.manager = DeviceManager(data_dir, config.wda_url or DEFAULT_WDA_URL)
|
|
123
|
+
self.setup = SetupService(self, self.manager, env_file)
|
|
124
|
+
self.device_info = DeviceInfo(manager=self.manager)
|
|
125
|
+
threading.Thread(target=self.manager.watch, args=(self.manager.watching,), name="mobster-device-watch",
|
|
126
|
+
daemon=True).start()
|
|
127
|
+
# --socket exists only with the optional extension, which the signed app doesn't ship.
|
|
128
|
+
if not getattr(config, "socket", None):
|
|
129
|
+
self.installed_apps = InstalledApps(self.manager.device,
|
|
130
|
+
cache=InventoryCache(data_dir / "installed-apps"),
|
|
131
|
+
over_wifi=self.manager.wifi_udid)
|
|
132
|
+
self.fleet = self.fleet_class(self)
|
|
133
|
+
self.engine_health = engine_health.EngineHealth(publish=self.publish, boot=self.bus.boot)
|
|
134
|
+
self.engine_sampler = engine_health.Sampler(self)
|
|
135
|
+
try:
|
|
136
|
+
self.workflows = Workflows(self)
|
|
137
|
+
for item in self.journal.load():
|
|
138
|
+
run = Run(item["app"], item["goal"], item["mode"], id=item["id"],
|
|
139
|
+
status=item["status"], created_at=item["createdAt"] / 1000,
|
|
140
|
+
finished_at=item["finishedAt"] / 1000 if item["finishedAt"] is not None else None,
|
|
141
|
+
events=item["events"], summary=item["summary"],
|
|
142
|
+
idempotency_key=item.get("idempotencyKey"), output_schema=item.get("outputSchema"),
|
|
143
|
+
output_format=item.get("outputFormat", "json"),
|
|
144
|
+
allowed_bundles=(frozenset(item["allowedBundles"]) if item.get("allowedBundles") else None),
|
|
145
|
+
journal=self.journal, engine=item.get("engine"), estimate_usd=item.get("estimateUsd"),
|
|
146
|
+
reviewed_ok=item.get("reviewedOk") is True, frames=self.frames,
|
|
147
|
+
device_id=item.get("device"), device_name=item.get("deviceName"),
|
|
148
|
+
group_id=item["groupId"] if isinstance(item.get("groupId"), str)
|
|
149
|
+
and GROUP_ID.fullmatch(item["groupId"]) else None,
|
|
150
|
+
extras=dict(item.get("extras") or {}),
|
|
151
|
+
origin=item.get("origin") if item.get("origin") in harness_api.ORIGINS else "api",
|
|
152
|
+
pinned_apps=item.get("pinnedApps") if isinstance(item.get("pinnedApps"), list) else None,
|
|
153
|
+
block_state=dict(item["blockState"]) if isinstance(item.get("blockState"), dict) else {},
|
|
154
|
+
shares=[dict(x) for x in item["shares"] if isinstance(x, dict)][:50]
|
|
155
|
+
if isinstance(item.get("shares"), list) else [],
|
|
156
|
+
issues=[dict(x) for x in item["issues"] if isinstance(x, dict)][:50]
|
|
157
|
+
if isinstance(item.get("issues"), list) else [])
|
|
158
|
+
if isinstance(item.get("costUsd"), (int, float)):
|
|
159
|
+
run.cost_nanodollars, run.cost_known = round(item["costUsd"] * NANODOLLARS), True
|
|
160
|
+
self.runs[run.id] = run
|
|
161
|
+
if run.finished_at is None:
|
|
162
|
+
# A task that waited for a held phone never started a worker: it acted on nothing.
|
|
163
|
+
waited = not any(e.get("event") == "run_started" or may_have_acted(e) for e in run.events)
|
|
164
|
+
run.finish({"event": "result", "status": "interrupted", "independently_verified": False,
|
|
165
|
+
"reason": ownership.QUEUE_RESTART if waited else
|
|
166
|
+
"The service restarted. An in-flight action may have run; this task was not resumed.",
|
|
167
|
+
"actions": sum(e["event"] in DONE_ACTION_EVENTS for e in run.events),
|
|
168
|
+
"actions_may_have_run": any(may_have_acted(e) for e in run.events)})
|
|
169
|
+
if run.summary.get("persistence_failed"):
|
|
170
|
+
raise JournalError("Interrupted task recovery could not be saved")
|
|
171
|
+
except Exception:
|
|
172
|
+
self.journal.close()
|
|
173
|
+
raise
|
|
174
|
+
self.fanout = Fanout(self)
|
|
175
|
+
self.start_services()
|
|
176
|
+
|
|
177
|
+
def start_services(self):
|
|
178
|
+
self._services = list(harness_api.services())
|
|
179
|
+
for start, _close in self._services:
|
|
180
|
+
try:
|
|
181
|
+
start(self)
|
|
182
|
+
except Exception: # noqa: BLE001 -- a track's service never stops Mobster from starting
|
|
183
|
+
harness_api.log.exception("a service failed to start")
|
|
184
|
+
|
|
185
|
+
def close_services(self):
|
|
186
|
+
for _start, close in reversed(getattr(self, "_services", ())):
|
|
187
|
+
try:
|
|
188
|
+
close(self)
|
|
189
|
+
except Exception: # noqa: BLE001
|
|
190
|
+
harness_api.log.exception("a service failed to close")
|
|
191
|
+
self._services = []
|
|
192
|
+
|
|
193
|
+
def scripted_models(self):
|
|
194
|
+
return type(self).smart_client is not Runtime.smart_client
|
|
195
|
+
|
|
196
|
+
def device_busy(self, device_id):
|
|
197
|
+
with self.lock:
|
|
198
|
+
return any(value == device_id for value in self.active_runs.values()) or any(
|
|
199
|
+
getattr(run, "device_id", None) == device_id and run.id in self.active_runs
|
|
200
|
+
for run in self.runs.values())
|
|
201
|
+
|
|
202
|
+
def run_context(self, run):
|
|
203
|
+
phone = self.phone_for(run) if getattr(self, "fleet", None) is not None else None
|
|
204
|
+
kind = getattr(phone, "kind", None) if phone is not None else None
|
|
205
|
+
if kind is None and getattr(self.config, "wda_url", None):
|
|
206
|
+
kind = "usb" if getattr(self, "manager", None) is not None else "wda"
|
|
207
|
+
state_db = getattr(self.config, "state_db", None)
|
|
208
|
+
return harness_api.RunContext(
|
|
209
|
+
run_id=run.id, goal=run.goal, engine=run.engine or engines.SMART, origin=run.origin,
|
|
210
|
+
app_bundle=run.app.get("bundleId"), device_id=run.device_id, device_kind=kind,
|
|
211
|
+
extras=harness_api.frozen_mapping(run.extras), data_dir=Path(state_db).parent if state_db else None,
|
|
212
|
+
emit=run.emit, clarify=harness_api.clarify_only(run.request_approval), cancelled=run.stop.is_set,
|
|
213
|
+
runtime=self, account_context=run.account_context, workflow_id=run.workflow_id)
|
|
214
|
+
|
|
215
|
+
def current_wda_session(self):
|
|
216
|
+
# Without a session the stream stays at full size and framerate (measured: 1178x2556 at ~9.5 fps on an
|
|
217
|
+
# iPhone 15 Pro). WDA keeps MJPEG settings server-wide, so making one here changes nothing a run relies on.
|
|
218
|
+
try:
|
|
219
|
+
return self.wda_session()
|
|
220
|
+
except Exception:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
def video_activity(self, active):
|
|
224
|
+
self.awake.acquire("video") if active else self.awake.release("video")
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def active(self):
|
|
228
|
+
return next(iter(self.active_runs), None)
|
|
229
|
+
|
|
230
|
+
def capacity(self):
|
|
231
|
+
if self.pool is None:
|
|
232
|
+
fleet = getattr(self, "fleet", None)
|
|
233
|
+
if fleet is None:
|
|
234
|
+
return 0 if self.active_runs else 1
|
|
235
|
+
return 0 if self.run_on(fleet.primary) else 1
|
|
236
|
+
free = self.pool.position()
|
|
237
|
+
return max(0, free["healthy"] - free["busy"])
|
|
238
|
+
|
|
239
|
+
def publish(self, topic, event):
|
|
240
|
+
try:
|
|
241
|
+
(getattr(self, "bus", None) or eventbus.bus).publish(topic, event)
|
|
242
|
+
except Exception: # noqa: BLE001
|
|
243
|
+
harness_api.log.exception("event bus publish failed")
|
|
244
|
+
|
|
245
|
+
def close(self, timeout=25):
|
|
246
|
+
deadline = time.monotonic() + timeout
|
|
247
|
+
self.engine_sampler.close()
|
|
248
|
+
# Stop every task before the runners and relays go: quitting the Mac app gives this process only SIDECAR_GRACE
|
|
249
|
+
# (desktop/src-tauri/src/sidecar.rs).
|
|
250
|
+
with self.lock:
|
|
251
|
+
self.closing = True
|
|
252
|
+
waiting = self.drop_queues()
|
|
253
|
+
for run in self.runs.values():
|
|
254
|
+
if run.finished_at is None:
|
|
255
|
+
with run.condition:
|
|
256
|
+
if not run.stop.is_set():
|
|
257
|
+
run.quitting = True
|
|
258
|
+
run.stop.set()
|
|
259
|
+
run.condition.notify_all()
|
|
260
|
+
scheduled_checks = getattr(self, "test_schedules", None)
|
|
261
|
+
if scheduled_checks is not None and not scheduled_checks.close(max(0.0, deadline - time.monotonic())):
|
|
262
|
+
return False # Its suite may still dispatch: keep the phone and journal owned.
|
|
263
|
+
from .projects import service as project_service
|
|
264
|
+
projects = project_service.service_for(self)
|
|
265
|
+
if projects is not None:
|
|
266
|
+
if projects.begin_close() is False:
|
|
267
|
+
return False
|
|
268
|
+
if not projects.join(max(0.0, deadline - time.monotonic())):
|
|
269
|
+
return False # An accepted project check may still dispatch or write its report.
|
|
270
|
+
for timer in list(self.queue_timers.values()):
|
|
271
|
+
timer.cancel()
|
|
272
|
+
for run in waiting:
|
|
273
|
+
try:
|
|
274
|
+
self.finished(run)
|
|
275
|
+
except Exception: # noqa: BLE001 -- closing goes on
|
|
276
|
+
harness_api.log.exception("run_finished listeners failed")
|
|
277
|
+
if not getattr(self.config, "keep_runner", False):
|
|
278
|
+
phones = list(getattr(getattr(self, "fleet", None), "phones", {}).values())
|
|
279
|
+
for manager in [self.manager] + [getattr(phone, "manager", None) for phone in phones]:
|
|
280
|
+
begin = getattr(manager, "begin_close", None)
|
|
281
|
+
if callable(begin):
|
|
282
|
+
try:
|
|
283
|
+
begin()
|
|
284
|
+
except Exception: # noqa: BLE001 -- closing goes on; close() below stops it the slow way
|
|
285
|
+
pass
|
|
286
|
+
workflows = getattr(self, "workflows", None)
|
|
287
|
+
if workflows is not None and hasattr(workflows, "close"):
|
|
288
|
+
if workflows.close() is False:
|
|
289
|
+
return False # a batch may still write its ending to the journal
|
|
290
|
+
if self.scheduler and not self.scheduler.close(max(0.0, deadline - time.monotonic())):
|
|
291
|
+
return False # a scheduler dispatch may still be in flight
|
|
292
|
+
self.video.close()
|
|
293
|
+
if getattr(self, "control", None) is not None:
|
|
294
|
+
self.control.close()
|
|
295
|
+
if getattr(self, "fleet", None) is not None:
|
|
296
|
+
self.fleet.close(keep_runner=getattr(self.config, "keep_runner", False))
|
|
297
|
+
if not getattr(self.config, "keep_runner", False):
|
|
298
|
+
from .sim import wda as sim_wda
|
|
299
|
+
sim_wda.stop_started() # a simulator's runner this process started goes with it
|
|
300
|
+
if self.manager is not None:
|
|
301
|
+
if getattr(self.config, "keep_runner", False):
|
|
302
|
+
# A development reload: the runner and relay outlive this process and the next server adopts them.
|
|
303
|
+
self.manager.watching.set()
|
|
304
|
+
else:
|
|
305
|
+
self.manager.close()
|
|
306
|
+
with self.lock:
|
|
307
|
+
self.closing = True
|
|
308
|
+
threads = list(self.threads)
|
|
309
|
+
for run in self.runs.values():
|
|
310
|
+
if run.finished_at is None:
|
|
311
|
+
run.stop.set()
|
|
312
|
+
self.awake.close()
|
|
313
|
+
self.power.close()
|
|
314
|
+
for thread in threads:
|
|
315
|
+
thread.join(max(0, deadline - time.monotonic()))
|
|
316
|
+
if any(thread.is_alive() for thread in threads):
|
|
317
|
+
# Keep the leases and journal until exit: an old worker might still dispatch.
|
|
318
|
+
return False
|
|
319
|
+
if getattr(self, "fanout", None) is not None:
|
|
320
|
+
self.fanout.close()
|
|
321
|
+
if getattr(self, "listeners", None) is not None:
|
|
322
|
+
if not self.listeners.close(timeout=max(0.0, deadline - time.monotonic())):
|
|
323
|
+
return False
|
|
324
|
+
self.close_services()
|
|
325
|
+
if getattr(self, "frames", None) is not None:
|
|
326
|
+
self.frames.close()
|
|
327
|
+
self.journal.close()
|
|
328
|
+
return True
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class ApiHandler(GetRoutesMixin, WriteRoutesMixin, DeviceRoutesMixin, ApiBase):
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def make_handler(runtime, token=None, app_session=None, setup_token=None):
|
|
336
|
+
# token None (tests only) leaves requests unauthenticated.
|
|
337
|
+
return type("Handler", (ApiHandler,), {"runtime": runtime, "token": token, "app_session": app_session,
|
|
338
|
+
"setup_token": setup_token})
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class BoundedServer(ThreadingHTTPServer):
|
|
342
|
+
daemon_threads = True
|
|
343
|
+
request_queue_size = 32
|
|
344
|
+
|
|
345
|
+
def __init__(self, *args, **kwargs):
|
|
346
|
+
self.capacity = threading.BoundedSemaphore(32)
|
|
347
|
+
super().__init__(*args, **kwargs)
|
|
348
|
+
|
|
349
|
+
def process_request(self, request, address):
|
|
350
|
+
if not self.capacity.acquire(blocking=False):
|
|
351
|
+
self.shutdown_request(request)
|
|
352
|
+
return
|
|
353
|
+
try:
|
|
354
|
+
super().process_request(request, address)
|
|
355
|
+
except Exception:
|
|
356
|
+
self.capacity.release()
|
|
357
|
+
raise
|
|
358
|
+
|
|
359
|
+
def process_request_thread(self, request, address):
|
|
360
|
+
try:
|
|
361
|
+
super().process_request_thread(request, address)
|
|
362
|
+
finally:
|
|
363
|
+
self.capacity.release()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""What the per-runtime services (connectors, outlets, plugins) share: their registry, ``ms`` and the app-only check."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
from .api_errors import APIError
|
|
6
|
+
|
|
7
|
+
APP_ONLY = "Do this in the Mobster app."
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ServiceRegistry:
|
|
11
|
+
"""One service per Runtime, keyed by the runtime object."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._items = {}
|
|
15
|
+
self._lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
def get(self, runtime):
|
|
18
|
+
with self._lock:
|
|
19
|
+
return self._items.get(id(runtime))
|
|
20
|
+
|
|
21
|
+
def add(self, runtime, service):
|
|
22
|
+
with self._lock:
|
|
23
|
+
self._items[id(runtime)] = service
|
|
24
|
+
|
|
25
|
+
def pop(self, runtime):
|
|
26
|
+
with self._lock:
|
|
27
|
+
return self._items.pop(id(runtime), None)
|
|
28
|
+
|
|
29
|
+
def all(self):
|
|
30
|
+
with self._lock:
|
|
31
|
+
return list(self._items.values())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def ms(seconds):
|
|
35
|
+
return round(seconds * 1000) if isinstance(seconds, (int, float)) else None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def needs_app(request, message=APP_ONLY):
|
|
39
|
+
"""403 app_only when this agent has a Mac app session and the request lacks it."""
|
|
40
|
+
local = getattr(request.runtime, "local_api", None) or {}
|
|
41
|
+
if local.get("app_session") and not request.app_session:
|
|
42
|
+
raise APIError(message, 403, "app_only")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Predictive settle and screen streams (MOBSTER_EXPERIMENTAL_SETTLE). See predict.py and docs/waiting.mdx."""
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Predictive settle: wait the learned p90, read once, let the next verify read prove it.
|
|
2
|
+
|
|
3
|
+
Polling is the floor: the settle loop reads until two reads agree for 0.3 to 0.5 s. When an app's transition has
|
|
4
|
+
settled in X ms at least nine times in ten, a step can sleep X ms, take one read and go on. The next observation (the
|
|
5
|
+
frontier's verify read, taken while the model thinks) proves the read, the same contract as ``single_read``; so it
|
|
6
|
+
runs only for callers that verify (``single_read`` or ``early``), and only with ``MOBSTER_EXPERIMENTAL_SETTLE``.
|
|
7
|
+
|
|
8
|
+
Nothing here decides a step. A prediction that is missing, a read that still shows the screen before the action, or
|
|
9
|
+
pixels that moved during the read fall back to the polling loop, unchanged. Each settle row records what was
|
|
10
|
+
predicted and what happened (``predicted``: ms, ``outcome``: hit, miss reason or none; ``arrival_ms``).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
from .store import SettleStore, enabled, shape_of
|
|
17
|
+
from .stream import WdaMjpegObserver
|
|
18
|
+
|
|
19
|
+
_store = None
|
|
20
|
+
_store_lock = threading.Lock()
|
|
21
|
+
STABLE_SECONDS = .18 # frame_clock.STABLE_SECONDS: the stream must be still this long before the read ended
|
|
22
|
+
READ_MARGIN_SECONDS = .35 # wda.observe.WDA_READ_MARGIN_SECONDS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def default_store():
|
|
26
|
+
global _store
|
|
27
|
+
with _store_lock:
|
|
28
|
+
if _store is None:
|
|
29
|
+
_store = SettleStore()
|
|
30
|
+
return _store
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def reset_default_store():
|
|
34
|
+
global _store
|
|
35
|
+
with _store_lock:
|
|
36
|
+
_store = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def arrival_ms(started, reads, before):
|
|
40
|
+
"""When the final screen first showed, in ms after ``started``: the first read of the trailing run of agreeing
|
|
41
|
+
reads, or None when the run is shorter than two reads or shows the screen before the action."""
|
|
42
|
+
if len(reads) < 2 or reads[-1][2] == before:
|
|
43
|
+
return None
|
|
44
|
+
first = len(reads) - 1
|
|
45
|
+
while first > 0 and reads[first - 1][2] == reads[-1][2]:
|
|
46
|
+
first -= 1
|
|
47
|
+
if len(reads) - first < 2:
|
|
48
|
+
return None
|
|
49
|
+
return max(0.0, (reads[first][0] - started) * 1000)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def predicted_settle(driver, snapshot, action, watch, clock, deadline, started, cap, reads, info,
|
|
53
|
+
store=None):
|
|
54
|
+
"""A changed read after the learned wait, or None (``info`` says why). Reads happen at most once."""
|
|
55
|
+
if not enabled() or not action:
|
|
56
|
+
return None
|
|
57
|
+
store = store or default_store()
|
|
58
|
+
app, shape = snapshot.bundle_id, shape_of(action.get("op"))
|
|
59
|
+
wait = store.p90(app, shape)
|
|
60
|
+
if wait is None:
|
|
61
|
+
info["outcome"] = "none"
|
|
62
|
+
return None
|
|
63
|
+
info["predicted"], info["shape"] = round(wait, 1), shape
|
|
64
|
+
# The wait is counted from the dispatch's end, which is when ``started`` was taken.
|
|
65
|
+
pause = min(started + wait / 1000, cap - READ_MARGIN_SECONDS) - time.monotonic()
|
|
66
|
+
if pause > 0:
|
|
67
|
+
time.sleep(pause)
|
|
68
|
+
read_started = time.monotonic()
|
|
69
|
+
if deadline.end - read_started <= READ_MARGIN_SECONDS:
|
|
70
|
+
info["outcome"] = "no_time"
|
|
71
|
+
return None
|
|
72
|
+
try:
|
|
73
|
+
current = driver.observe(timeout=deadline.remaining())
|
|
74
|
+
except Exception: # TransportError or TimeoutError: the polling loop owns the failure.
|
|
75
|
+
info["outcome"] = "read_failed"
|
|
76
|
+
return None
|
|
77
|
+
now = time.monotonic()
|
|
78
|
+
reads.append((read_started, now, current.content_fingerprint))
|
|
79
|
+
if current.content_fingerprint == snapshot.content_fingerprint:
|
|
80
|
+
info["outcome"] = "unchanged"
|
|
81
|
+
return None
|
|
82
|
+
if WdaMjpegObserver(clock, watch).still_between(read_started - STABLE_SECONDS, now) is False:
|
|
83
|
+
info["outcome"] = "moved"
|
|
84
|
+
return None
|
|
85
|
+
info["outcome"] = "hit"
|
|
86
|
+
return current
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def annotate(driver, entry, action, snapshot, result, started, reads, info):
|
|
90
|
+
"""Put the prediction on the settle row, remember it on the driver (``predicted_settle``) and learn from the
|
|
91
|
+
settle. Learns only from settles that ended on agreeing reads; a hit is one read, so it teaches nothing."""
|
|
92
|
+
if not enabled():
|
|
93
|
+
return
|
|
94
|
+
arrival = arrival_ms(started, reads, snapshot.content_fingerprint)
|
|
95
|
+
shape = shape_of((action or {}).get("op"))
|
|
96
|
+
if arrival is not None:
|
|
97
|
+
entry["arrival_ms"] = round(arrival, 1)
|
|
98
|
+
default_store().add(snapshot.bundle_id, shape, arrival)
|
|
99
|
+
if info is not None and "outcome" in info:
|
|
100
|
+
entry.update({key: info[key] for key in ("predicted", "outcome", "shape") if key in info})
|
|
101
|
+
driver.predicted_settle = {key: entry.get(key) for key in ("shape", "predicted", "outcome", "arrival_ms")
|
|
102
|
+
if entry.get(key) is not None} or None
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Offline replay of recorded settles: what predictive settle would have done on frameclock.jsonl logs.
|
|
2
|
+
|
|
3
|
+
python -m mobile_agent.settle.replay LOG [LOG ...]
|
|
4
|
+
|
|
5
|
+
Reads ``settle`` rows (``MOBSTER_FRAME_CLOCK_LOG`` files) in time order, learns each key's arrival the way a run does,
|
|
6
|
+
and for every settle that polled (path ax, frame or frame_fallback) asks whether the learned p90 would have been
|
|
7
|
+
enough. A row records no arrival unless it was logged with the switch on (``arrival_ms``), so the replay estimates it:
|
|
8
|
+
``stable_ms`` when the row has it, else ``settle_ms`` minus the polling loop's quiet period (0.5 s on AX alone, 0.3 s
|
|
9
|
+
with the pixels). The wall time of a hit is the wait plus one read; of a miss, the wait, one read and then the
|
|
10
|
+
whole polled settle (an upper bound). These are projections from recordings, not a measured effect of the switch.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from .store import SettleStore, shape_of
|
|
19
|
+
|
|
20
|
+
POLLED = {"ax", "frame", "frame_fallback"}
|
|
21
|
+
QUIET_AX_MS, QUIET_PIXELS_MS, READ_MS = 500.0, 300.0, 200.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def estimate_arrival(row):
|
|
25
|
+
if row.get("arrival_ms") is not None:
|
|
26
|
+
return float(row["arrival_ms"])
|
|
27
|
+
if row.get("stable_ms") is not None:
|
|
28
|
+
return float(row["stable_ms"])
|
|
29
|
+
quiet = QUIET_PIXELS_MS if row.get("path") == "frame" else QUIET_AX_MS
|
|
30
|
+
return max(0.0, float(row["settle_ms"]) - quiet)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load(paths):
|
|
34
|
+
rows = []
|
|
35
|
+
for path in paths:
|
|
36
|
+
with open(path, encoding="utf-8") as handle:
|
|
37
|
+
for line in handle:
|
|
38
|
+
try:
|
|
39
|
+
row = json.loads(line)
|
|
40
|
+
except ValueError:
|
|
41
|
+
continue
|
|
42
|
+
if row.get("kind") == "settle" and row.get("changed") and "settle_ms" in row:
|
|
43
|
+
rows.append(row)
|
|
44
|
+
return sorted(rows, key=lambda row: row.get("at", 0))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def replay(rows):
|
|
48
|
+
with tempfile.TemporaryDirectory() as folder:
|
|
49
|
+
store = SettleStore(Path(folder) / "durations.json")
|
|
50
|
+
totals = {"settles": 0, "predicted": 0, "hits": 0, "misses": 0, "polled_ms": 0.0, "with_ms": 0.0,
|
|
51
|
+
"polled_reads": 0, "with_reads": 0, "no_prediction": 0}
|
|
52
|
+
for row in rows:
|
|
53
|
+
if row.get("path") not in POLLED:
|
|
54
|
+
continue
|
|
55
|
+
app, shape = row.get("bundle") or "", shape_of(row.get("op"))
|
|
56
|
+
arrival, wall, reads = estimate_arrival(row), float(row["settle_ms"]), int(row.get("reads", 1))
|
|
57
|
+
totals["settles"] += 1
|
|
58
|
+
totals["polled_ms"] += wall
|
|
59
|
+
totals["polled_reads"] += reads
|
|
60
|
+
wait = store.p90(app, shape)
|
|
61
|
+
if wait is None:
|
|
62
|
+
totals["no_prediction"] += 1
|
|
63
|
+
totals["with_ms"], totals["with_reads"] = totals["with_ms"] + wall, totals["with_reads"] + reads
|
|
64
|
+
store.add(app, shape, arrival)
|
|
65
|
+
elif wait >= arrival:
|
|
66
|
+
totals["predicted"] += 1
|
|
67
|
+
totals["hits"] += 1
|
|
68
|
+
totals["with_ms"] += wait + READ_MS
|
|
69
|
+
totals["with_reads"] += 1
|
|
70
|
+
else:
|
|
71
|
+
totals["predicted"] += 1
|
|
72
|
+
totals["misses"] += 1
|
|
73
|
+
totals["with_ms"] += wait + READ_MS + wall
|
|
74
|
+
totals["with_reads"] += 1 + reads
|
|
75
|
+
store.add(app, shape, arrival)
|
|
76
|
+
return totals
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def report(totals):
|
|
80
|
+
n = totals["settles"] or 1
|
|
81
|
+
lines = [f"{totals['settles']} polled settles; {totals['no_prediction']} had no prediction yet, "
|
|
82
|
+
f"{totals['hits']} hits, {totals['misses']} misses ({totals['predicted']} predicted)",
|
|
83
|
+
f"step settle wall: {totals['polled_ms'] / n:.0f} ms polled -> {totals['with_ms'] / n:.0f} ms predicted "
|
|
84
|
+
f"(mean per settle)",
|
|
85
|
+
f"AX reads per settle: {totals['polled_reads'] / n:.2f} -> {totals['with_reads'] / n:.2f}"]
|
|
86
|
+
if totals["predicted"]:
|
|
87
|
+
lines.append(f"hit rate when predicted: {totals['hits'] / totals['predicted']:.0%}")
|
|
88
|
+
return "\n".join(lines)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
print(report(replay(load(sys.argv[1:]))))
|