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,549 @@
|
|
|
1
|
+
"""Runtime's task start: the readiness checks, Runtime.create and the results that need no phone."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from . import ai_plans, engine_health, engines, harness_api, orchestrate, ownership, permissions, project_env
|
|
9
|
+
from .api_errors import APIError
|
|
10
|
+
from .journal import JournalError, Lease
|
|
11
|
+
from .lockscreen import PHONE_LOCKED
|
|
12
|
+
from .output_contract import validate_output
|
|
13
|
+
from .owner_presence import OWNER_ACTIVE_REFUSAL
|
|
14
|
+
from .pool import NoDeviceAvailable
|
|
15
|
+
from .run import Run
|
|
16
|
+
from .runtime_driver import PooledLease, waits_for_unlock
|
|
17
|
+
from .runtime_limits import TASK_LIMIT_RANGE
|
|
18
|
+
from .state import validate_bundle_id
|
|
19
|
+
from .wda import watchdog as wda_watchdog
|
|
20
|
+
|
|
21
|
+
# A run that calls no model needs a ready phone and live runs on, but no model key.
|
|
22
|
+
NO_MODEL = "no_model"
|
|
23
|
+
|
|
24
|
+
ANY_APP = {"id": "any", "name": "Any app", "bundleId": None, "available": True}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TaskStartMixin:
|
|
28
|
+
def _replay(self, idempotency_key, fingerprint):
|
|
29
|
+
# Call with the lock held. A key reused for another task, or whose task left history, is refused.
|
|
30
|
+
prior = self.journal.lookup(idempotency_key) if idempotency_key else None
|
|
31
|
+
if not prior:
|
|
32
|
+
return None
|
|
33
|
+
if prior[1] != fingerprint:
|
|
34
|
+
raise APIError("This request key belongs to a different task", code="idempotency_conflict")
|
|
35
|
+
if prior[0] not in self.runs:
|
|
36
|
+
raise APIError("This previous task has expired from history; it was not restarted", 410, "run_expired")
|
|
37
|
+
return self.runs[prior[0]], True
|
|
38
|
+
|
|
39
|
+
def check_ready(self, phone, primary, chosen, origin, idempotency_key):
|
|
40
|
+
# Admission asks it, and a queued request asks it again when its turn comes.
|
|
41
|
+
if self.owner_blocks_start(phone, origin, idempotency_key):
|
|
42
|
+
raise APIError(OWNER_ACTIVE_REFUSAL, 409, "owner_active", device=getattr(phone, "id", None))
|
|
43
|
+
if primary:
|
|
44
|
+
status = self.status()
|
|
45
|
+
state = status.get("engines", {}).get(chosen) or {"available": True, "reason": None}
|
|
46
|
+
if not state["available"]:
|
|
47
|
+
# Never another engine instead: the user chose this one (or it is their default).
|
|
48
|
+
raise APIError(state["reason"] or "This engine can't run right now", 409, "engine_unavailable",
|
|
49
|
+
engine=chosen)
|
|
50
|
+
usable = status["live_enabled"] if chosen == engines.SMART else (
|
|
51
|
+
status.get("device_ready") and self.config.enable_live and not self.closing)
|
|
52
|
+
if not usable:
|
|
53
|
+
locked = (self.config.enable_live and not self.closing
|
|
54
|
+
and engine_health.cause_of(status["engine"], "phone:primary") == "locked")
|
|
55
|
+
if locked and not waits_for_unlock(origin, idempotency_key):
|
|
56
|
+
# The device manager's probe saw the lock: say so, not "not ready" (PLAN §3.9).
|
|
57
|
+
raise APIError(PHONE_LOCKED, 503, "device_unavailable", stop="phone_locked")
|
|
58
|
+
if not locked:
|
|
59
|
+
raise APIError(self.not_ready_message(status), 503, "device_unavailable")
|
|
60
|
+
# Locked and nothing else: a person started this task, so it waits for the unlock at its preflight.
|
|
61
|
+
else:
|
|
62
|
+
state = self.engines_status().get(chosen) or {"available": True, "reason": None}
|
|
63
|
+
if not state["available"]:
|
|
64
|
+
raise APIError(state["reason"] or "This engine can't run right now", 409, "engine_unavailable",
|
|
65
|
+
engine=chosen)
|
|
66
|
+
if not self.config.enable_live:
|
|
67
|
+
raise APIError("Live runs are off; enable them to act on the phone", 503, "device_unavailable")
|
|
68
|
+
target = phone.target_status()
|
|
69
|
+
if not (target["ready"] and target["can_act"]):
|
|
70
|
+
locked = engine_health.cause_of(self.engine(), f"phone:{phone.id}") == "locked"
|
|
71
|
+
if locked and not waits_for_unlock(origin, idempotency_key):
|
|
72
|
+
raise APIError(f"{phone.name or 'This iPhone'} is locked. Unlock it and try again. "
|
|
73
|
+
"No action was taken.", 503, "device_unavailable", device=phone.id,
|
|
74
|
+
stop="phone_locked")
|
|
75
|
+
if not (locked and target["can_act"]):
|
|
76
|
+
raise APIError(f"{phone.name or 'This device'} is not ready for live tasks. "
|
|
77
|
+
+ (target.get("health") or "Try again when it is available."), 503,
|
|
78
|
+
"device_unavailable", device=phone.id)
|
|
79
|
+
# Locked only: waits for the unlock at its preflight, like the first iPhone's.
|
|
80
|
+
|
|
81
|
+
def create(self, app_id, goal, mode, idempotency_key=None, output_schema=None, output_format="auto",
|
|
82
|
+
allowed_bundles=None, engine=None, device=None, default_device="last_used",
|
|
83
|
+
extras=None, origin="api", group_id=None, pinned_apps=None, cap_usd=None, queue=False,
|
|
84
|
+
_resume_run=None, _workflow_id=None):
|
|
85
|
+
if self.plan_gate is not None:
|
|
86
|
+
self.plan_gate.require()
|
|
87
|
+
engine = engines.read_engine(engine)
|
|
88
|
+
if cap_usd is not None and (isinstance(cap_usd, bool) or not isinstance(cap_usd, (int, float))
|
|
89
|
+
or not 0 < cap_usd <= TASK_LIMIT_RANGE[1]):
|
|
90
|
+
raise ValueError("cap_usd is a positive amount in USD")
|
|
91
|
+
if engine is not None and engine not in engines.ENGINES and not ai_plans.is_agent(engine):
|
|
92
|
+
raise ValueError("engine must be smart, or codex, claude_code or gemini for your own coding agent")
|
|
93
|
+
if origin not in harness_api.ORIGINS:
|
|
94
|
+
raise ValueError("Unknown task origin")
|
|
95
|
+
if origin in harness_api.CLAIMED_ORIGINS:
|
|
96
|
+
queue = False # the claim admits it only as it starts now
|
|
97
|
+
source = None
|
|
98
|
+
if _resume_run is not None:
|
|
99
|
+
source = self.runs.get(getattr(_resume_run, "id", None))
|
|
100
|
+
if (source is not _resume_run or not isinstance(extras, dict)
|
|
101
|
+
or extras.get("resumeFrom") != source.id or source.finished_at is None):
|
|
102
|
+
raise ValueError("This task cannot reuse a saved run context")
|
|
103
|
+
if "account" in extras and extras["account"] != source.extras.get("account"):
|
|
104
|
+
raise ValueError("A picked-up task keeps its original profile")
|
|
105
|
+
_workflow_id = source.workflow_id
|
|
106
|
+
elif _workflow_id is not None:
|
|
107
|
+
if (not isinstance(_workflow_id, str) or not isinstance(idempotency_key, str)
|
|
108
|
+
or not self.workflows.owns_run_request(_workflow_id, idempotency_key)):
|
|
109
|
+
raise ValueError("This task has no claimed saved workflow")
|
|
110
|
+
# A canonical pickup carries its original private snapshot even if the saved profile was edited or removed.
|
|
111
|
+
if source is not None and isinstance(source.extras.get("account"), dict):
|
|
112
|
+
if source.account_context is None:
|
|
113
|
+
raise ValueError("This task has no saved profile context")
|
|
114
|
+
extras = harness_api.clean_extras({k: v for k, v in extras.items() if k != "account"}, self)
|
|
115
|
+
extras["account"] = source.extras["account"]
|
|
116
|
+
else:
|
|
117
|
+
extras = harness_api.clean_extras(extras, self)
|
|
118
|
+
predecessor = self.runs.get(extras.get("resumeFrom")) if extras.get("resumeFrom") else None
|
|
119
|
+
if source is None and predecessor is not None and (predecessor.extras.get("account")
|
|
120
|
+
or predecessor.workflow_id):
|
|
121
|
+
raise ValueError("Pick up this task from its saved run so its profile and workflow stay fixed")
|
|
122
|
+
if harness_api.calls_no_model(extras) and engine not in (None, engines.SMART):
|
|
123
|
+
raise ValueError("This task calls no model: leave the engine out")
|
|
124
|
+
if extras.get("projectEnv") and isinstance(goal, str):
|
|
125
|
+
self._check_project_env(extras["projectEnv"], goal, engine, origin)
|
|
126
|
+
account = extras.get("account")
|
|
127
|
+
account_context = account_digest = None
|
|
128
|
+
if account is not None:
|
|
129
|
+
from .accounts import routing as account_routing
|
|
130
|
+
if source is not None:
|
|
131
|
+
account_context = source.account_context
|
|
132
|
+
account_digest = account_routing.digest(account_context)
|
|
133
|
+
else:
|
|
134
|
+
account_context, account_digest = account_routing.capture(self, account)
|
|
135
|
+
if account is not None and isinstance(goal, str):
|
|
136
|
+
goal, device = account_routing.route(self, account_context, goal, device)
|
|
137
|
+
if default_device not in ("last_used", "primary"):
|
|
138
|
+
raise ValueError("default_device must be last_used or primary")
|
|
139
|
+
fleet = getattr(self, "fleet", None)
|
|
140
|
+
phone = None
|
|
141
|
+
if device is not None and (fleet is None or self.pool is not None):
|
|
142
|
+
raise ValueError("This Mobster runs every task on its own device; leave device out")
|
|
143
|
+
if fleet is not None and self.pool is None:
|
|
144
|
+
# Resolved before the runtime's lock: naming a device reads the USB listing and the registries.
|
|
145
|
+
if device is not None:
|
|
146
|
+
phone = self.resolve_device(device)
|
|
147
|
+
elif self.one_target:
|
|
148
|
+
phone = fleet.primary
|
|
149
|
+
elif default_device == "primary":
|
|
150
|
+
phone = self.resolve_default(sticky=False)
|
|
151
|
+
else:
|
|
152
|
+
phone = self.resolve_device(None)
|
|
153
|
+
primary = phone is None or phone.primary
|
|
154
|
+
chosen = engine
|
|
155
|
+
if chosen is None:
|
|
156
|
+
chosen = engines.SMART
|
|
157
|
+
if account is not None and chosen != engines.SMART:
|
|
158
|
+
# Only Smart confirms the account on screen before it commits (accounts/screen.py).
|
|
159
|
+
from .accounts.routing import SMART_ONLY
|
|
160
|
+
raise ValueError(SMART_ONLY)
|
|
161
|
+
if pinned_apps is not None:
|
|
162
|
+
if (not isinstance(pinned_apps, list) or not 1 <= len(pinned_apps) <= orchestrate.PINNED_APPS
|
|
163
|
+
or not all(isinstance(item, str) and 0 < len(item) <= 160 for item in pinned_apps)
|
|
164
|
+
or len(set(pinned_apps)) != len(pinned_apps)):
|
|
165
|
+
raise ValueError(f"pinnedApps is 1-{orchestrate.PINNED_APPS} different app ids")
|
|
166
|
+
if app_id is None:
|
|
167
|
+
app_id = pinned_apps[0]
|
|
168
|
+
any_app = chosen == engines.SMART or ai_plans.is_agent(chosen)
|
|
169
|
+
if app_id is None and any_app:
|
|
170
|
+
app_id = ANY_APP["id"]
|
|
171
|
+
if (not isinstance(app_id, str) or not isinstance(goal, str) or not 1 <= len(goal.strip()) <= 4000
|
|
172
|
+
or mode != "live"):
|
|
173
|
+
raise ValueError("Choose an app, a task (1–4000 characters), and live mode")
|
|
174
|
+
if idempotency_key is not None and not re.fullmatch(r"[A-Za-z0-9_.:-]{1,128}", idempotency_key):
|
|
175
|
+
raise ValueError("Invalid Idempotency-Key")
|
|
176
|
+
output_schema = validate_output(output_format, output_schema)
|
|
177
|
+
if allowed_bundles is not None:
|
|
178
|
+
if not isinstance(allowed_bundles, list) or not allowed_bundles or len(allowed_bundles) > 31:
|
|
179
|
+
raise ValueError("allowedBundles must be 1-31 bundle identifiers")
|
|
180
|
+
try:
|
|
181
|
+
extra_bundles = frozenset(validate_bundle_id(bundle) for bundle in allowed_bundles)
|
|
182
|
+
except ValueError:
|
|
183
|
+
raise ValueError("allowedBundles must be valid bundle identifiers") from None
|
|
184
|
+
else:
|
|
185
|
+
extra_bundles = None
|
|
186
|
+
identity = [app_id, goal.strip(), mode, output_schema]
|
|
187
|
+
# Preserve existing JSON request tombstones; another format is a distinct request.
|
|
188
|
+
if output_format != "json":
|
|
189
|
+
identity.append({"outputFormat": output_format})
|
|
190
|
+
if extra_bundles is not None:
|
|
191
|
+
identity.append({"allowedBundles": sorted(extra_bundles)})
|
|
192
|
+
if engine is not None:
|
|
193
|
+
identity.append({"engine": engine})
|
|
194
|
+
if device is not None and phone is not None:
|
|
195
|
+
identity.append({"device": phone.id})
|
|
196
|
+
if extras:
|
|
197
|
+
identity.append({"extras": extras})
|
|
198
|
+
if account_digest is not None:
|
|
199
|
+
identity.append({"accountContextSha256": account_digest})
|
|
200
|
+
if _workflow_id is not None:
|
|
201
|
+
identity.append({"workflowId": _workflow_id})
|
|
202
|
+
if group_id is not None:
|
|
203
|
+
if not isinstance(group_id, str) or not re.fullmatch(r"[a-f0-9]{12}", group_id):
|
|
204
|
+
raise ValueError("A group id is 12 lowercase hex characters")
|
|
205
|
+
identity.append({"groupId": group_id})
|
|
206
|
+
if pinned_apps:
|
|
207
|
+
identity.append({"pinnedApps": list(pinned_apps)})
|
|
208
|
+
fingerprint = hashlib.sha256(json.dumps(identity,
|
|
209
|
+
ensure_ascii=True, sort_keys=True, allow_nan=False).encode()).hexdigest()
|
|
210
|
+
if account is not None and phone is not None and getattr(fleet, "health", None) is not None:
|
|
211
|
+
# Refused before anything is claimed; a retry of a started request gets its task back first.
|
|
212
|
+
with self.lock:
|
|
213
|
+
replayed = None if self.closing else self._replay(idempotency_key, fingerprint)
|
|
214
|
+
if replayed is not None:
|
|
215
|
+
return replayed
|
|
216
|
+
fleet.health.preflight(account, phone)
|
|
217
|
+
if ai_plans.is_agent(chosen):
|
|
218
|
+
# Before the runtime's lock: a first check runs the CLI's own status command.
|
|
219
|
+
self.check_agent(chosen)
|
|
220
|
+
with self.lock:
|
|
221
|
+
if self.closing:
|
|
222
|
+
raise APIError("The service is shutting down", 503, "shutting_down")
|
|
223
|
+
if source is not None and self.runs.get(source.id) is not source:
|
|
224
|
+
raise APIError("The task being picked up left saved history. Nothing was restarted.", 410,
|
|
225
|
+
"run_expired")
|
|
226
|
+
replayed = self._replay(idempotency_key, fingerprint)
|
|
227
|
+
if replayed is not None:
|
|
228
|
+
return replayed
|
|
229
|
+
waits = None
|
|
230
|
+
line = self.queues.find(self.queue_key(phone))
|
|
231
|
+
in_line = line is not None and (len(line) > 0 or line.held())
|
|
232
|
+
if phone is not None:
|
|
233
|
+
busy = self.run_on(phone)
|
|
234
|
+
if busy is not None or in_line:
|
|
235
|
+
if not queue:
|
|
236
|
+
raise self.busy_error(phone, busy, primary)
|
|
237
|
+
waits = self.queue_key(phone)
|
|
238
|
+
elif not self.capacity() or in_line:
|
|
239
|
+
if not queue:
|
|
240
|
+
raise self.busy_error(None, self.active, primary)
|
|
241
|
+
waits = self.queue_key(None)
|
|
242
|
+
if waits is not None and len(self.queues.of(waits)) >= self.queues.limit:
|
|
243
|
+
raise APIError(ownership.QUEUE_FULL.format(count=len(self.queues.of(waits)),
|
|
244
|
+
phone=self.phone_name(phone)), 429, "queue_full")
|
|
245
|
+
phone_prefs = fleet.prefs(phone) if fleet is not None and phone is not None else {}
|
|
246
|
+
if phone is not None and fleet.is_disconnected(phone):
|
|
247
|
+
raise APIError(f"{phone.name or 'This iPhone'} is disconnected in Mobster. Connect it in Settings › "
|
|
248
|
+
"Phones to run tasks on it.", 409, "device_disconnected", device=phone.id)
|
|
249
|
+
phone_apps = frozenset(phone_prefs["apps"]) if phone_prefs.get("apps") else None
|
|
250
|
+
admitted = None
|
|
251
|
+
if origin in harness_api.CLAIMED_ORIGINS:
|
|
252
|
+
# A console run (teams/link.py) narrows the apps and limits; no claim registered, no run.
|
|
253
|
+
claim = harness_api.origin_claim(origin)
|
|
254
|
+
if claim is None or not set(claim[2]) <= set(extras):
|
|
255
|
+
raise APIError("That origin needs its own session on this Mac.", 403, "origin_refused")
|
|
256
|
+
admitted = claim[1](self, extras=extras, phone=phone, engine=chosen)
|
|
257
|
+
if admitted.apps is not None:
|
|
258
|
+
phone_apps = admitted.apps if phone_apps is None else phone_apps & admitted.apps
|
|
259
|
+
if admitted.cap_usd is not None:
|
|
260
|
+
cap_usd = admitted.cap_usd if cap_usd is None else min(cap_usd, admitted.cap_usd)
|
|
261
|
+
if admitted.ask_floor == permissions.ASK_ALL:
|
|
262
|
+
extras["permissionMode"] = permissions.ASK_ALL
|
|
263
|
+
if not admitted.long_tasks and extras.get("longform") is not None:
|
|
264
|
+
raise ValueError("A long task can't start from there. Start it in Mobster on this Mac")
|
|
265
|
+
catalog = self.apps_for(phone)
|
|
266
|
+
if account is not None and app_id == ANY_APP["id"]:
|
|
267
|
+
# A task as an account starts in the account's app, unless it named another one.
|
|
268
|
+
app_id = next((a["id"] for a in catalog if a.get("bundleId") == account["app"]["bundleId"]), app_id)
|
|
269
|
+
app = ANY_APP if app_id == ANY_APP["id"] and any_app else next(
|
|
270
|
+
(a for a in catalog if a["id"] == app_id), None)
|
|
271
|
+
if not app:
|
|
272
|
+
raise ValueError("Choose an app from your catalog")
|
|
273
|
+
pins = None
|
|
274
|
+
if pinned_apps:
|
|
275
|
+
pins = []
|
|
276
|
+
for pinned in pinned_apps:
|
|
277
|
+
found = next((a for a in catalog if a["id"] == pinned), None)
|
|
278
|
+
if found is None:
|
|
279
|
+
raise ValueError("Pin apps from your catalog")
|
|
280
|
+
if found.get("installed") is False:
|
|
281
|
+
raise ValueError(f"{found['name']} isn't installed on "
|
|
282
|
+
f"{(phone.name if phone is not None and phone.name else 'this iPhone')}")
|
|
283
|
+
pins.append({"id": found["id"], "name": found["name"], "bundleId": found.get("bundleId")})
|
|
284
|
+
app_policy = permissions.from_env().apps
|
|
285
|
+
outside = origin in permissions.policy.OUTSIDE_ORIGINS
|
|
286
|
+
if app.get("bundleId") and not app_policy.allows(app["bundleId"], outside=outside):
|
|
287
|
+
raise APIError(app_policy.blocked_message(app.get("name"), outside=outside and app_policy.allows(
|
|
288
|
+
app["bundleId"])), 403, "app_not_allowed")
|
|
289
|
+
long_task = None
|
|
290
|
+
if chosen == engines.SMART and not harness_api.calls_no_model(extras) and \
|
|
291
|
+
(admitted is None or admitted.long_tasks):
|
|
292
|
+
long_task = self.long_task(goal, app, extras, origin, pins)
|
|
293
|
+
elif extras.get("longform") is not None:
|
|
294
|
+
raise ValueError("Long tasks run on Mobster's agent")
|
|
295
|
+
if phone_apps is not None and app is not ANY_APP and app.get("bundleId") not in phone_apps:
|
|
296
|
+
raise APIError(f"{app['name']} isn't one of the apps Mobster may use on {phone.name or 'this phone'}. "
|
|
297
|
+
"Change it in Settings › Phones.", 400, "app_not_allowed", device=phone.id)
|
|
298
|
+
outside = [pin for pin in pins or () if phone_apps is not None and pin["bundleId"] not in phone_apps]
|
|
299
|
+
if outside:
|
|
300
|
+
# A pin is an app the task uses: the phone's app list limits it as it limits the start app.
|
|
301
|
+
raise APIError(f"{outside[0]['name']} isn't one of the apps Mobster may use on "
|
|
302
|
+
f"{phone.name or 'this phone'}. Change it in Settings › Phones.", 400, "app_not_allowed",
|
|
303
|
+
device=phone.id)
|
|
304
|
+
allowed = None
|
|
305
|
+
if extra_bundles is not None:
|
|
306
|
+
known = {a["bundleId"] for a in catalog if isinstance(a.get("bundleId"), str)}
|
|
307
|
+
unknown = sorted(extra_bundles - known)
|
|
308
|
+
if unknown:
|
|
309
|
+
raise ValueError("Unknown app bundle: " + ", ".join(unknown[:5]))
|
|
310
|
+
if phone_apps is not None and extra_bundles - phone_apps:
|
|
311
|
+
raise APIError("Some of those apps aren't ones Mobster may use on "
|
|
312
|
+
f"{phone.name or 'this phone'}: " + ", ".join(sorted(extra_bundles - phone_apps)[:5])
|
|
313
|
+
+ ". Change it in Settings › Phones.", 400, "app_not_allowed", device=phone.id)
|
|
314
|
+
blocked = sorted(b for b in extra_bundles if not app_policy.allows(b))
|
|
315
|
+
if blocked:
|
|
316
|
+
names = {a["bundleId"]: a["name"] for a in catalog if a.get("bundleId")}
|
|
317
|
+
raise APIError(app_policy.blocked_message(names.get(blocked[0], blocked[0])), 403,
|
|
318
|
+
"app_not_allowed")
|
|
319
|
+
allowed = frozenset({app["bundleId"]} | extra_bundles)
|
|
320
|
+
self.check_ready(phone, primary, NO_MODEL if harness_api.calls_no_model(extras) else chosen, origin,
|
|
321
|
+
idempotency_key)
|
|
322
|
+
cap = None
|
|
323
|
+
limit = self.monthly_limit()
|
|
324
|
+
if limit is not None:
|
|
325
|
+
cap = self.monthly_cap(limit, raised=(long_task or {}).get("capUsd"))
|
|
326
|
+
if app is not ANY_APP:
|
|
327
|
+
if primary:
|
|
328
|
+
self.check_app(app)
|
|
329
|
+
elif app.get("installed") is False:
|
|
330
|
+
raise ValueError(f"{app['name']} isn't installed on {phone.name or 'this device'}")
|
|
331
|
+
self.teaching(None if primary else phone)
|
|
332
|
+
run = Run(app, goal.strip(), mode, idempotency_key=idempotency_key, output_schema=output_schema,
|
|
333
|
+
output_format=output_format, allowed_bundles=allowed, journal=self.journal,
|
|
334
|
+
phone=None if primary else phone,
|
|
335
|
+
device_id=phone.id if phone is not None else None,
|
|
336
|
+
device_name=phone.name if phone is not None else None,
|
|
337
|
+
engine=chosen, frames=getattr(self, "frames", None),
|
|
338
|
+
estimate_usd=self.estimate_for(chosen, goal.strip(), output_schema, output_format),
|
|
339
|
+
extras=extras, account_context=account_context, workflow_id=_workflow_id, origin=origin,
|
|
340
|
+
listeners=getattr(self, "listeners", None),
|
|
341
|
+
longform=long_task, group_id=group_id, phone_apps=phone_apps, pinned_apps=pins,
|
|
342
|
+
ask_override=phone_prefs.get("askBeforeActing"),
|
|
343
|
+
approvals_closed=self.approvals_closed if origin in harness_api.UNATTENDED_ORIGINS else None)
|
|
344
|
+
self.remembering(run)
|
|
345
|
+
run.client = ownership.clean_client(ownership.current_client()) if origin == "mcp" else None
|
|
346
|
+
self.apply_caps(run, cap, cap_usd)
|
|
347
|
+
if self.pool is None and waits is None:
|
|
348
|
+
try:
|
|
349
|
+
run.lease = Lease.device((self.config.wda_url if primary else phone.wda_url).rstrip("/"))
|
|
350
|
+
except JournalError:
|
|
351
|
+
raise APIError("Your iPhone is busy in another Mobster process" if primary else
|
|
352
|
+
f"{phone.name or 'This device'} is busy in another Mobster process",
|
|
353
|
+
code="device_busy") from None
|
|
354
|
+
device = None
|
|
355
|
+
if self.pool is not None and waits is None:
|
|
356
|
+
# At admission, not dispatch: a task that can't get a device is refused while the caller waits.
|
|
357
|
+
try:
|
|
358
|
+
device = self.pool.acquire(run.id, timeout=0)
|
|
359
|
+
except NoDeviceAvailable:
|
|
360
|
+
elsewhere = any("another Mobster process" in item["reason"] for item in self.pool.public())
|
|
361
|
+
raise APIError(
|
|
362
|
+
"Your iPhone is busy in another Mobster process" if elsewhere else
|
|
363
|
+
"Every assigned phone is busy; stop a run or wait for one to finish",
|
|
364
|
+
code="device_busy" if elsewhere else "run_active",
|
|
365
|
+
activeRunId=self.active) from None
|
|
366
|
+
run.device = device
|
|
367
|
+
run.lease = PooledLease(self.pool, run.id)
|
|
368
|
+
held_url = self.run_phone_url(run)
|
|
369
|
+
if waits is None:
|
|
370
|
+
try:
|
|
371
|
+
wda_watchdog.tracker.run_started(held_url, run.id) # refused while the helper restarts
|
|
372
|
+
except wda_watchdog.ActionBlocked:
|
|
373
|
+
if run.lease:
|
|
374
|
+
run.lease.close()
|
|
375
|
+
raise APIError("Mobster's helper on your iPhone is restarting. No action was taken. "
|
|
376
|
+
"Try again in a moment.", 503, "device_unavailable") from None
|
|
377
|
+
try:
|
|
378
|
+
expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
|
|
379
|
+
except Exception:
|
|
380
|
+
if waits is None:
|
|
381
|
+
wda_watchdog.tracker.run_ended(held_url, run.id)
|
|
382
|
+
if run.lease:
|
|
383
|
+
run.lease.close()
|
|
384
|
+
raise
|
|
385
|
+
self._retain_run(run, expired)
|
|
386
|
+
thread = None
|
|
387
|
+
try:
|
|
388
|
+
if waits is not None:
|
|
389
|
+
# Held runs are durable, listed and cancellable, with no phone, lease or worker.
|
|
390
|
+
self.enqueue(run, phone, waits, queue_cap=cap_usd, chosen=chosen, primary=primary)
|
|
391
|
+
else:
|
|
392
|
+
self.active_runs[run.id] = device.id if device else phone.key if phone is not None else None
|
|
393
|
+
if run.listeners is not None:
|
|
394
|
+
run.listeners.post("on_created", run, self)
|
|
395
|
+
self.publish("runs", {"event": "run_created", "runId": run.id,
|
|
396
|
+
**({"threadId": extras["threadId"]} if extras.get("threadId") else {}),
|
|
397
|
+
**({"device": run.device_id} if run.device_id else {})})
|
|
398
|
+
if waits is not None:
|
|
399
|
+
self.publish("devices", {"event": "queue_changed", "device": run.device_id})
|
|
400
|
+
return (run, False) if idempotency_key else run
|
|
401
|
+
thread = threading.Thread(target=self.work, args=(run,), daemon=True)
|
|
402
|
+
self.threads.add(thread)
|
|
403
|
+
thread.start()
|
|
404
|
+
except Exception:
|
|
405
|
+
self.threads.discard(thread)
|
|
406
|
+
self.active_runs.pop(run.id, None)
|
|
407
|
+
if waits is not None:
|
|
408
|
+
self.unqueue(run.id)
|
|
409
|
+
if waits is None:
|
|
410
|
+
wda_watchdog.tracker.run_ended(held_url, run.id)
|
|
411
|
+
if run.lease:
|
|
412
|
+
run.lease.close()
|
|
413
|
+
run.finish({"status": "interrupted", "independently_verified": False,
|
|
414
|
+
"reason": "The task worker could not start. No agent action was dispatched."})
|
|
415
|
+
raise APIError("The task could not start; check its saved status before retrying", 503,
|
|
416
|
+
"start_failed") from None
|
|
417
|
+
if phone is not None:
|
|
418
|
+
fleet.mark_used(phone)
|
|
419
|
+
return (run, False) if idempotency_key else run
|
|
420
|
+
|
|
421
|
+
def _retain_run(self, run, expired):
|
|
422
|
+
# Call under self.lock after journal.create succeeds; its expired ids own history retention.
|
|
423
|
+
for identifier in expired:
|
|
424
|
+
self.runs.pop(identifier, None)
|
|
425
|
+
if getattr(self, "frames", None) is not None:
|
|
426
|
+
self.frames.delete(identifier)
|
|
427
|
+
self.runs[run.id] = run
|
|
428
|
+
|
|
429
|
+
def compose_result(self, *, app, goal, summary, idempotency_key, extras=None, origin="workflow",
|
|
430
|
+
device=(None, None)):
|
|
431
|
+
# No phone, no model call, acts on nothing; Run.finish validates its blocks like any task's.
|
|
432
|
+
if self.plan_gate is not None:
|
|
433
|
+
self.plan_gate.require()
|
|
434
|
+
if origin not in harness_api.ORIGINS:
|
|
435
|
+
raise ValueError("Unknown task origin")
|
|
436
|
+
if not isinstance(app, dict) or not isinstance(app.get("id"), str):
|
|
437
|
+
raise ValueError("A result names the app its tasks ran in")
|
|
438
|
+
extras = harness_api.clean_extras(extras, self)
|
|
439
|
+
fingerprint = hashlib.sha256(json.dumps(["compose", app["id"], goal, extras], ensure_ascii=True,
|
|
440
|
+
sort_keys=True, default=str).encode()).hexdigest()
|
|
441
|
+
with self.lock:
|
|
442
|
+
if self.closing:
|
|
443
|
+
raise APIError("The service is shutting down", 503, "shutting_down")
|
|
444
|
+
prior = self.journal.lookup(idempotency_key)
|
|
445
|
+
if prior and prior[0] in self.runs:
|
|
446
|
+
return self.runs[prior[0]]
|
|
447
|
+
if prior:
|
|
448
|
+
raise APIError("This result has expired from history", 410, "run_expired")
|
|
449
|
+
run = Run(dict(app), goal.strip()[:4000], "live", idempotency_key=idempotency_key, journal=self.journal,
|
|
450
|
+
frames=getattr(self, "frames", None), device_id=device[0], device_name=device[1],
|
|
451
|
+
extras=extras, origin=origin, listeners=getattr(self, "listeners", None))
|
|
452
|
+
expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
|
|
453
|
+
self._retain_run(run, expired)
|
|
454
|
+
if run.listeners is not None:
|
|
455
|
+
run.listeners.post("on_created", run, self)
|
|
456
|
+
self.publish("runs", {"event": "run_created", "runId": run.id,
|
|
457
|
+
**({"threadId": extras["threadId"]} if extras.get("threadId") else {})})
|
|
458
|
+
run.finish(dict(summary))
|
|
459
|
+
self.finished(run)
|
|
460
|
+
return run
|
|
461
|
+
|
|
462
|
+
def model_result(self, *, app, goal, call, idempotency_key, extras=None, origin="workflow"):
|
|
463
|
+
# The call is metered into the run's events, so the usage ledger and the monthly limit count it.
|
|
464
|
+
if self.plan_gate is not None:
|
|
465
|
+
self.plan_gate.require()
|
|
466
|
+
if origin not in harness_api.ORIGINS or origin in harness_api.CLAIMED_ORIGINS:
|
|
467
|
+
# A claimed origin's runs are admitted in create(); this path has no admission, so none comes here.
|
|
468
|
+
raise ValueError("Unknown task origin")
|
|
469
|
+
if not isinstance(app, dict) or not isinstance(app.get("id"), str):
|
|
470
|
+
raise ValueError("A result names the app its tasks ran in")
|
|
471
|
+
extras = harness_api.clean_extras(extras, self)
|
|
472
|
+
fingerprint = hashlib.sha256(json.dumps(["model", app["id"], goal, extras], ensure_ascii=True,
|
|
473
|
+
sort_keys=True, default=str).encode()).hexdigest()
|
|
474
|
+
with self.lock:
|
|
475
|
+
if self.closing:
|
|
476
|
+
raise APIError("The service is shutting down", 503, "shutting_down")
|
|
477
|
+
prior = self.journal.lookup(idempotency_key)
|
|
478
|
+
if prior and prior[0] in self.runs:
|
|
479
|
+
return self.runs[prior[0]]
|
|
480
|
+
if prior:
|
|
481
|
+
raise APIError("This result has expired from history", 410, "run_expired")
|
|
482
|
+
limit = self.monthly_limit()
|
|
483
|
+
if limit is not None:
|
|
484
|
+
self.monthly_cap(limit) # refuses when this month has nothing left
|
|
485
|
+
run = Run(dict(app), goal.strip()[:4000], "live", idempotency_key=idempotency_key, journal=self.journal,
|
|
486
|
+
frames=getattr(self, "frames", None), extras=extras, origin=origin,
|
|
487
|
+
listeners=getattr(self, "listeners", None))
|
|
488
|
+
expired = self.journal.create(run.metadata(), idempotency_key, fingerprint)
|
|
489
|
+
self._retain_run(run, expired)
|
|
490
|
+
if run.listeners is not None:
|
|
491
|
+
run.listeners.post("on_created", run, self)
|
|
492
|
+
self.publish("runs", {"event": "run_created", "runId": run.id,
|
|
493
|
+
**({"threadId": extras["threadId"]} if extras.get("threadId") else {})})
|
|
494
|
+
try:
|
|
495
|
+
summary = call(run.emit)
|
|
496
|
+
except Exception as error: # the call failed or its answer didn't fit: the run says so; nothing acted
|
|
497
|
+
summary = {"event": "result", "status": "error", "outcome": "couldnt_finish", "engine": None,
|
|
498
|
+
"answer": None, "reason": f"The model call didn't finish: {type(error).__name__}.",
|
|
499
|
+
"actions": 0, "decisions": 1, "elapsed_ms": 0, "model": None,
|
|
500
|
+
"independently_verified": False, "proof": []}
|
|
501
|
+
run.finish(dict(summary))
|
|
502
|
+
self.finished(run)
|
|
503
|
+
return run
|
|
504
|
+
|
|
505
|
+
def frame(self, run_id, frame_id):
|
|
506
|
+
frames = getattr(self, "frames", None)
|
|
507
|
+
run = self.runs.get(run_id)
|
|
508
|
+
if frames is None or run is None:
|
|
509
|
+
return None
|
|
510
|
+
data = frames.get(run_id, frame_id)
|
|
511
|
+
if data is not None:
|
|
512
|
+
return data
|
|
513
|
+
brief = (run.summary or {}).get("brief") if isinstance(run.summary, dict) else None
|
|
514
|
+
for source in (brief or {}).get("runs") or () if isinstance(brief, dict) else ():
|
|
515
|
+
if isinstance(source, str) and source in self.runs:
|
|
516
|
+
data = frames.get(source, frame_id)
|
|
517
|
+
if data is not None:
|
|
518
|
+
return data
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
def start_group(self, phones, app_id, goal, idempotency_key=None, fingerprint=None, thread_id=None, **create):
|
|
522
|
+
# Without a plan no phone's run is admitted, so the group isn't made either.
|
|
523
|
+
if self.plan_gate is not None:
|
|
524
|
+
self.plan_gate.require()
|
|
525
|
+
if self.closing:
|
|
526
|
+
raise APIError("The service is shutting down", 503, "shutting_down")
|
|
527
|
+
return self.fanout.start(phones, app_id, goal, idempotency_key=idempotency_key, fingerprint=fingerprint,
|
|
528
|
+
thread_id=thread_id, **create)
|
|
529
|
+
|
|
530
|
+
@staticmethod
|
|
531
|
+
def _check_project_env(root, goal, engine, origin):
|
|
532
|
+
# Only Mobster's agent masks the values, and a task never starts with a blank or a literal ${NAME}.
|
|
533
|
+
if not project_env.refs(goal):
|
|
534
|
+
return
|
|
535
|
+
if origin not in {"app", "cli", "tui", "workflow", "schedule"} or engine not in (None, engines.SMART):
|
|
536
|
+
raise ValueError("A task that uses project secrets (${NAME}) runs on Mobster's agent from the app, the "
|
|
537
|
+
"terminal or a saved workflow. Remove the ${NAME}s to run it here.")
|
|
538
|
+
try:
|
|
539
|
+
project_env.for_task(root, goal)
|
|
540
|
+
except project_env.EnvError as error:
|
|
541
|
+
raise ValueError(str(error)) from None
|
|
542
|
+
|
|
543
|
+
def start_profiles(self, profiles, app_id, goal, idempotency_key=None, fingerprint=None, thread_id=None, **create):
|
|
544
|
+
if self.plan_gate is not None:
|
|
545
|
+
self.plan_gate.require()
|
|
546
|
+
if self.closing:
|
|
547
|
+
raise APIError("The service is shutting down", 503, "shutting_down")
|
|
548
|
+
return self.fanout.start_profiles(profiles, app_id, goal, idempotency_key=idempotency_key,
|
|
549
|
+
fingerprint=fingerprint, thread_id=thread_id, **create)
|