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/engines.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Mobster's one engine (``smart``, Mobster's agent) and the frozen config the benchmark shares."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from . import harness_api
|
|
14
|
+
|
|
15
|
+
SMART = "smart"
|
|
16
|
+
ENGINES = (SMART,)
|
|
17
|
+
RETIRED = ("fast", "quick")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def read_engine(value):
|
|
21
|
+
"""``value`` as an engine to run: Quick mode's retired names read as ``smart``; anything else is unchanged."""
|
|
22
|
+
return SMART if isinstance(value, str) and value.strip().casefold() in RETIRED else value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class SmartConfig:
|
|
27
|
+
"""The frozen Smart loop. Change it only with a benchmark run that shows why."""
|
|
28
|
+
# Claude Sonnet 5.5 at low reasoning: paired with gpt-5.6-sol on 16 iOSWorld tasks it matched the pass rate at 0.68x
|
|
29
|
+
# the cost (docs/benchmarks.md "Smart on Claude").
|
|
30
|
+
model: str = "claude-sonnet-5-5"
|
|
31
|
+
reasoning: str = "low"
|
|
32
|
+
max_steps: int = 50 # iOSWorld's own step limit
|
|
33
|
+
max_seconds: int = 900
|
|
34
|
+
screenshots: bool = True
|
|
35
|
+
settle_seconds: float = .6
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
SMART_CONFIG = SmartConfig()
|
|
39
|
+
ANTHROPIC_SMART_MODEL = "claude-sonnet-5-5"
|
|
40
|
+
OPENAI_SMART_MODEL = "gpt-5.6-sol"
|
|
41
|
+
PROVIDER_NAMES = {"openai": "OpenAI", "anthropic": "Anthropic"}
|
|
42
|
+
# People know the account as "Claude", never "Anthropic" (MESSAGING § 11); PROVIDER_NAMES stays the company for errors.
|
|
43
|
+
ACCOUNT_NAMES = {"OpenAI": "OpenAI", "Anthropic": "Claude"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def account_name(provider):
|
|
47
|
+
"""The account's name in a sentence ("Claude" for Anthropic) from a company name or a model provider id."""
|
|
48
|
+
company = PROVIDER_NAMES.get(provider, provider)
|
|
49
|
+
return ACCOUNT_NAMES.get(company, company)
|
|
50
|
+
MODEL_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def model_provider(model):
|
|
54
|
+
"""Who serves ``model``: a Settings › Models provider for "<provider>:<model>",
|
|
55
|
+
anthropic for claude-*, else openai."""
|
|
56
|
+
model = str(model or "")
|
|
57
|
+
providers = harness_api.hooks().model_routes()
|
|
58
|
+
routed, _ = providers.split(model)
|
|
59
|
+
if routed:
|
|
60
|
+
return routed # "<provider>:<model>": a plan or a key from Settings › Models (ai_plans/providers.py)
|
|
61
|
+
return "anthropic" if model.startswith("claude-") else "openai"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def on_plan(model):
|
|
65
|
+
"""Whether ``model`` runs on a plan the user pays for (no per-call cost)."""
|
|
66
|
+
providers = harness_api.hooks().model_routes()
|
|
67
|
+
routed, _ = providers.split(model)
|
|
68
|
+
return bool(routed) and providers.get(routed).group == "plan"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def model_words(model):
|
|
72
|
+
"""``model`` for a status line: "kimi-for-coding, using your Kimi Code plan" for a plan model, else the id."""
|
|
73
|
+
if on_plan(model):
|
|
74
|
+
providers = harness_api.hooks().model_routes()
|
|
75
|
+
routed, slug = providers.split(model)
|
|
76
|
+
return f"{slug}, using {providers.get(routed).plan_label or 'your plan'}"
|
|
77
|
+
return str(model or "")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def provider_name(model):
|
|
81
|
+
provider = model_provider(model)
|
|
82
|
+
if provider in PROVIDER_NAMES:
|
|
83
|
+
return PROVIDER_NAMES[provider]
|
|
84
|
+
providers = harness_api.hooks().model_routes()
|
|
85
|
+
return providers.get(provider).name
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def anthropic_key(env=None):
|
|
89
|
+
"""The Anthropic key Smart uses on Claude."""
|
|
90
|
+
key, _ = harness_api.hooks().key("anthropic", os.environ if env is None else env)
|
|
91
|
+
return (key or "").strip() or None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def smart_model(env=None):
|
|
95
|
+
"""The model Smart plans with: MOBSTER_SMART_MODEL, else the provider the user chose, else Claude on a Claude key,
|
|
96
|
+
OpenAI on an OpenAI key alone, else SMART_CONFIG's."""
|
|
97
|
+
env = os.environ if env is None else env
|
|
98
|
+
chosen = (env.get("MOBSTER_SMART_MODEL") or "").strip()
|
|
99
|
+
if chosen and MODEL_ID.fullmatch(chosen) and not chosen.startswith("gemini-"):
|
|
100
|
+
return chosen
|
|
101
|
+
preferred = (env.get("MOBSTER_SMART_PROVIDER") or "").strip()
|
|
102
|
+
providers = harness_api.hooks().model_routes()
|
|
103
|
+
if providers.get(preferred) is not None:
|
|
104
|
+
# A plan or key chosen in Settings stays the choice while its key is missing,
|
|
105
|
+
# never a silent switch to another bill.
|
|
106
|
+
return providers.model_id(providers.get(preferred), env)
|
|
107
|
+
if preferred == "openai" and openai_smart_key(env):
|
|
108
|
+
return OPENAI_SMART_MODEL
|
|
109
|
+
if anthropic_key(env):
|
|
110
|
+
return ANTHROPIC_SMART_MODEL
|
|
111
|
+
if openai_smart_key(env):
|
|
112
|
+
return OPENAI_SMART_MODEL
|
|
113
|
+
return SMART_CONFIG.model
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def smart_config(env=None):
|
|
117
|
+
"""SMART_CONFIG on ``smart_model``: the frozen loop, only its model chosen."""
|
|
118
|
+
model = smart_model(env)
|
|
119
|
+
return SMART_CONFIG if model == SMART_CONFIG.model else dataclasses.replace(SMART_CONFIG, model=model)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def frontier_kwargs(config=SMART_CONFIG, *, max_cost_usd=None):
|
|
123
|
+
"""FrontierAgent's keyword arguments for ``config``."""
|
|
124
|
+
return {"max_steps": config.max_steps, "max_seconds": config.max_seconds, "screenshots": config.screenshots,
|
|
125
|
+
"settle_seconds": config.settle_seconds, "max_cost_usd": max_cost_usd}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_client(config=None, *, key=None):
|
|
129
|
+
"""The Smart model's client on the user's key."""
|
|
130
|
+
from .model_clients import chat_client
|
|
131
|
+
config = smart_config() if config is None else config
|
|
132
|
+
return chat_client(config.model, reasoning=config.reasoning, key=key or None)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_frontier(driver, client, *, apps, emit, max_cost_usd=None, approve=None, cancelled=None, config=SMART_CONFIG,
|
|
136
|
+
guard=None, skills=None, output_schema=None, context_blocks=(), turn_context=None,
|
|
137
|
+
steering=None, clarify=None, step_observer=None, checkpoint=None, max_steps=None,
|
|
138
|
+
max_seconds=None, run_context=None, commit_check=None, dialog_approve=None, longform=None,
|
|
139
|
+
forced_approve=None, permissions=None, floor_approve=None,
|
|
140
|
+
permission_mode=None, saved_program=None, program_parts=None, dialogs=None,
|
|
141
|
+
dialog_policy=None, recorder=None):
|
|
142
|
+
"""The Smart agent: FrontierAgent with ``config``'s arguments. ``recorder``: traces.Recording.attach, which returns
|
|
143
|
+
the driver and client the agent runs on (a tape on, the pair itself off)."""
|
|
144
|
+
from .frontier import FrontierAgent
|
|
145
|
+
kwargs = frontier_kwargs(config, max_cost_usd=max_cost_usd)
|
|
146
|
+
if max_steps is not None:
|
|
147
|
+
kwargs["max_steps"] = int(max_steps)
|
|
148
|
+
if max_seconds is not None:
|
|
149
|
+
kwargs["max_seconds"] = max_seconds
|
|
150
|
+
if skills:
|
|
151
|
+
# The tool policy (harness/tools): registered tools only when available, interactive ones only when a person
|
|
152
|
+
# watches, at most 8.
|
|
153
|
+
skills = harness_api.hooks().tool_policy(skills, run_context=run_context, driver=driver, guard=guard)
|
|
154
|
+
hooks = {name: value for name, value in (
|
|
155
|
+
("context_blocks", tuple(context_blocks or ())), ("turn_context", turn_context), ("steering", steering),
|
|
156
|
+
("clarify", clarify), ("step_observer", step_observer), ("checkpoint", checkpoint),
|
|
157
|
+
("run_context", run_context), ("commit_check", commit_check), ("dialog_approve", dialog_approve),
|
|
158
|
+
("longform", longform), ("forced_approve", forced_approve),
|
|
159
|
+
("permissions", permissions), ("floor_approve", floor_approve),
|
|
160
|
+
("permission_mode", permission_mode), ("saved_program", saved_program),
|
|
161
|
+
("program_parts", program_parts), ("dialogs", dialogs), ("dialog_policy", dialog_policy)) if value}
|
|
162
|
+
if recorder is not None:
|
|
163
|
+
driver, client = recorder(driver, client, apps=apps, frontier={**kwargs, "output_schema": output_schema},
|
|
164
|
+
hooks=[*hooks, *(("approve",) if approve else ()), *(("guard",) if guard else ())])
|
|
165
|
+
return FrontierAgent(driver, client, apps=apps, emit=emit, approve=approve, cancelled=cancelled, guard=guard,
|
|
166
|
+
skills=skills, output_schema=output_schema, **hooks, **kwargs)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def acquire_client(key):
|
|
170
|
+
"""The Smart model's client for one run (seam S6.2): today, a new one on ``key`` (``build_client``)."""
|
|
171
|
+
return build_client(key=key)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def release_client(client):
|
|
175
|
+
"""A run is done with ``client``: close its connection."""
|
|
176
|
+
connection = getattr(client, "connection", None)
|
|
177
|
+
if connection is not None:
|
|
178
|
+
try:
|
|
179
|
+
connection.close()
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def smart_key(env=None):
|
|
185
|
+
"""The key Smart uses: the one of ``smart_model``'s provider."""
|
|
186
|
+
env = os.environ if env is None else env
|
|
187
|
+
provider = model_provider(smart_model(env))
|
|
188
|
+
if provider == "anthropic":
|
|
189
|
+
return anthropic_key(env)
|
|
190
|
+
providers = harness_api.hooks().model_routes()
|
|
191
|
+
if providers.get(provider) is not None:
|
|
192
|
+
key = providers.credential(providers.get(provider), env)
|
|
193
|
+
return None if key is None else (key or "local-server-no-key")
|
|
194
|
+
return openai_smart_key(env)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def openai_smart_key(env=None):
|
|
198
|
+
"""The OpenAI key Smart uses: OPENAI_API_KEY, else the helper's key when the helper is OpenAI."""
|
|
199
|
+
env = os.environ if env is None else env
|
|
200
|
+
key = (env.get("OPENAI_API_KEY") or "").strip()
|
|
201
|
+
if key:
|
|
202
|
+
return key
|
|
203
|
+
helper, source = harness_api.hooks().key("openai", {name: value for name, value in env.items()
|
|
204
|
+
if name != "OPENAI_API_KEY"})
|
|
205
|
+
if source != "helper":
|
|
206
|
+
return None
|
|
207
|
+
return helper.strip() or None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# The Mac app reads this sentence as a missing key (keyMissing).
|
|
211
|
+
NO_SMART_KEY = "Connect your AI account in Setup"
|
|
212
|
+
NO_CREDIT = "Your OpenAI account is out of credit. Add credit at platform.openai.com, then try again."
|
|
213
|
+
NO_ANTHROPIC_CREDIT = "Your Claude account is out of credit. Add credit at platform.claude.com, then try again."
|
|
214
|
+
CREDIT_REASONS = (NO_CREDIT, NO_ANTHROPIC_CREDIT)
|
|
215
|
+
CREDIT_CODES = ("insufficient_quota", "credit_balance_exhausted")
|
|
216
|
+
# Anthropic's no-credit answers: a 400 "credit balance is too low", or a 402 billing_error.
|
|
217
|
+
ANTHROPIC_CREDIT = ("credit balance is too low", "billing_error")
|
|
218
|
+
PROVIDER_ERROR = re.compile(r"(OpenAI|Anthropic) HTTP (\d{3})")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def no_key_reason(managed=True, model=None, env=None):
|
|
222
|
+
"""Why Smart can't run without a key."""
|
|
223
|
+
env = os.environ if env is None else env
|
|
224
|
+
model = model or smart_model(env)
|
|
225
|
+
chosen = (env.get("MOBSTER_SMART_MODEL") or "").strip() == model
|
|
226
|
+
if model_provider(model) == "anthropic" and (chosen or model != SMART_CONFIG.model):
|
|
227
|
+
return f"Add ANTHROPIC_API_KEY to the agent's env file to run Smart on {model}"
|
|
228
|
+
return NO_SMART_KEY if managed else "Add OPENAI_API_KEY or ANTHROPIC_API_KEY to the agent's env file"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def no_credit(model=None):
|
|
232
|
+
return NO_ANTHROPIC_CREDIT if model_provider(model or smart_model()) == "anthropic" else NO_CREDIT
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def credit_provider(text):
|
|
236
|
+
"""Whose account an out-of-credit error is about: Anthropic when the error names it, else OpenAI."""
|
|
237
|
+
return "Anthropic" if "Anthropic" in str(text or "") else "OpenAI"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def out_of_credit(text):
|
|
241
|
+
"""Whether a model error (its status and body, as a run's reason carries them) says the account has no credit."""
|
|
242
|
+
text = str(text or "")
|
|
243
|
+
return (any(code in text for code in CREDIT_CODES + ANTHROPIC_CREDIT) or "OpenAI HTTP 402" in text
|
|
244
|
+
or "Anthropic HTTP 402" in text)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ModelReach:
|
|
248
|
+
"""Whether the saved key can run Smart's model, cached per key and model; unknown counts as reachable."""
|
|
249
|
+
|
|
250
|
+
TTL_SECONDS = 6 * 3600
|
|
251
|
+
|
|
252
|
+
def __init__(self, model=None, opener=urllib.request.urlopen, enabled=False):
|
|
253
|
+
self.model, self.opener, self.enabled = model, opener, enabled
|
|
254
|
+
self.lock = threading.Lock()
|
|
255
|
+
self.results = {} # key digest -> (ok, reason, checked_at, credit): credit marks an empty account
|
|
256
|
+
self.checking = set()
|
|
257
|
+
|
|
258
|
+
def _model(self, model=None):
|
|
259
|
+
return model or self.model or smart_model()
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def digest(key, model=""):
|
|
263
|
+
return hashlib.sha256(f"{model}\0{key}".encode()).hexdigest()[:16]
|
|
264
|
+
|
|
265
|
+
def state(self, key, model=None):
|
|
266
|
+
"""(available, reason) for ``key``; starts a check when none is fresh (never over an empty account)."""
|
|
267
|
+
model = self._model(model)
|
|
268
|
+
if not key:
|
|
269
|
+
return False, no_key_reason(model=model)
|
|
270
|
+
if model_provider(model) not in ("openai", "anthropic"):
|
|
271
|
+
return True, None # a provider from Settings › Models: its key was checked when it was saved
|
|
272
|
+
digest = self.digest(key, model)
|
|
273
|
+
with self.lock:
|
|
274
|
+
found = self.results.get(digest)
|
|
275
|
+
stale = found is None or not found[3] and time.time() - found[2] > self.TTL_SECONDS
|
|
276
|
+
start = self.enabled and stale and digest not in self.checking
|
|
277
|
+
if start:
|
|
278
|
+
self.checking.add(digest)
|
|
279
|
+
if start:
|
|
280
|
+
threading.Thread(target=self._check, args=(key, digest, model), name="mobster-model-reach",
|
|
281
|
+
daemon=True).start()
|
|
282
|
+
return (True, None) if found is None else (found[0], found[1])
|
|
283
|
+
|
|
284
|
+
def record(self, key, ok, reason=None, model=None):
|
|
285
|
+
"""What a run or a key test found."""
|
|
286
|
+
if key:
|
|
287
|
+
with self.lock:
|
|
288
|
+
self.results[self.digest(key, self._model(model))] = (ok, reason, time.time(),
|
|
289
|
+
ok is False and reason in CREDIT_REASONS)
|
|
290
|
+
|
|
291
|
+
def forget(self, key, model=None):
|
|
292
|
+
"""Drop what is known about ``key``: the next ``state`` counts it reachable and checks it again."""
|
|
293
|
+
if key:
|
|
294
|
+
with self.lock:
|
|
295
|
+
self.results.pop(self.digest(key, self._model(model)), None)
|
|
296
|
+
|
|
297
|
+
def _check(self, key, digest, model=None):
|
|
298
|
+
try:
|
|
299
|
+
ok, reason = self.probe(key, model)
|
|
300
|
+
if ok is not None:
|
|
301
|
+
with self.lock:
|
|
302
|
+
found = self.results.get(digest)
|
|
303
|
+
# The model read can't see credit: a pass says nothing about an account a run found empty.
|
|
304
|
+
if not (ok and found is not None and found[3]):
|
|
305
|
+
self.results[digest] = (ok, reason, time.time(), False)
|
|
306
|
+
finally:
|
|
307
|
+
with self.lock:
|
|
308
|
+
self.checking.discard(digest)
|
|
309
|
+
|
|
310
|
+
def probe(self, key, model=None):
|
|
311
|
+
"""(True, None), (False, why) or (None, None) when the answer is unknown."""
|
|
312
|
+
model = self._model(model)
|
|
313
|
+
if model_provider(model) not in ("openai", "anthropic"):
|
|
314
|
+
return None, None
|
|
315
|
+
if model_provider(model) == "anthropic":
|
|
316
|
+
from .model_clients import ANTHROPIC_VERSION
|
|
317
|
+
request = urllib.request.Request(f"https://api.anthropic.com/v1/models/{model}", headers={
|
|
318
|
+
"x-api-key": key, "anthropic-version": ANTHROPIC_VERSION, "User-Agent": "Mobster"})
|
|
319
|
+
else:
|
|
320
|
+
request = urllib.request.Request(f"https://api.openai.com/v1/models/{model}", headers={
|
|
321
|
+
"Authorization": f"Bearer {key}", "User-Agent": "Mobster"})
|
|
322
|
+
try:
|
|
323
|
+
with self.opener(request, timeout=15) as response:
|
|
324
|
+
response.read(65536)
|
|
325
|
+
return True, None
|
|
326
|
+
except urllib.error.HTTPError as error:
|
|
327
|
+
return unreachable(error.code, model)
|
|
328
|
+
except (urllib.error.URLError, TimeoutError, OSError):
|
|
329
|
+
return None, None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def rejected_key(provider):
|
|
333
|
+
"""Why Smart can't run on a key its provider refused, in the app's words (no env names: the app saves the key)."""
|
|
334
|
+
return f"{account_name(provider)} didn't accept your key. Check it in Settings › Models."
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def unreachable(status, model=None):
|
|
338
|
+
"""(ok, reason) for a model provider's HTTP status, or (None, None) when it says nothing about the key."""
|
|
339
|
+
model = model or smart_model()
|
|
340
|
+
if status in (401, 403):
|
|
341
|
+
return False, rejected_key(provider_name(model))
|
|
342
|
+
if status == 404:
|
|
343
|
+
return False, f"Your {account_name(provider_name(model))} key can't use {model}."
|
|
344
|
+
return None, None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def model_failure(reason, model=None):
|
|
348
|
+
"""(False, why) when a run's error says Smart can't run on this key, else (None, None)."""
|
|
349
|
+
text = str(reason or "")
|
|
350
|
+
found = PROVIDER_ERROR.search(text)
|
|
351
|
+
if not found:
|
|
352
|
+
return None, None
|
|
353
|
+
if out_of_credit(text):
|
|
354
|
+
return False, NO_ANTHROPIC_CREDIT if found.group(1) == "Anthropic" else NO_CREDIT
|
|
355
|
+
if found.group(2) == "404" and "model" not in text.casefold():
|
|
356
|
+
return None, None
|
|
357
|
+
return unreachable(int(found.group(2)), model or smart_model())
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
NO_OPENAI_REACH = "Mobster couldn't reach OpenAI. Check your internet connection, then try again."
|
|
361
|
+
NO_ANTHROPIC_REACH = "Mobster couldn't reach Claude. Check your internet connection, then try again."
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def model_error(reason, model_failed=False):
|
|
365
|
+
"""Which model-provider failure ended a run."""
|
|
366
|
+
text = str(reason or "")
|
|
367
|
+
found = PROVIDER_ERROR.search(text)
|
|
368
|
+
status = found.group(2) if found else None
|
|
369
|
+
if out_of_credit(text):
|
|
370
|
+
return "no_credit"
|
|
371
|
+
if status in ("401", "403"):
|
|
372
|
+
return "rejected_key"
|
|
373
|
+
if status == "404":
|
|
374
|
+
return "model_unavailable"
|
|
375
|
+
if status == "429":
|
|
376
|
+
return "rate_limited"
|
|
377
|
+
if status and status.startswith("5"):
|
|
378
|
+
return "provider_error"
|
|
379
|
+
if "Anthropic declined the request" in text:
|
|
380
|
+
return "declined"
|
|
381
|
+
if model_failed:
|
|
382
|
+
return "unreachable"
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def plain_error(reason, model_failed=False, model=None):
|
|
387
|
+
"""A run error in words a person can act on, or None."""
|
|
388
|
+
text = str(reason or "")
|
|
389
|
+
model = model or smart_model()
|
|
390
|
+
found = PROVIDER_ERROR.search(text)
|
|
391
|
+
name = found.group(1) if found else provider_name(model)
|
|
392
|
+
code = model_error(text, model_failed)
|
|
393
|
+
if code:
|
|
394
|
+
return {
|
|
395
|
+
"no_credit": NO_ANTHROPIC_CREDIT if name == "Anthropic" else NO_CREDIT,
|
|
396
|
+
"rejected_key": rejected_key(name),
|
|
397
|
+
"model_unavailable": f"Your {account_name(name)} key can't use {model}.",
|
|
398
|
+
"rate_limited": f"{account_name(name)} is rate limiting your key. Wait a minute, then try again.",
|
|
399
|
+
"provider_error": f"{account_name(name)} had a problem answering. Try again in a moment.",
|
|
400
|
+
"declined": "Claude declined this request. Try rewording it, or run it on another model.",
|
|
401
|
+
"unreachable": NO_ANTHROPIC_REACH if name == "Anthropic" else NO_OPENAI_REACH,
|
|
402
|
+
}[code]
|
|
403
|
+
if re.search(r"TimeoutError|timed out|ConnectionRefused|ConnectionReset|RemoteDisconnected", text):
|
|
404
|
+
return "Your iPhone stopped answering. Check it is unlocked and connected, then try again."
|
|
405
|
+
if re.match(r"[A-Z]\w*(Error|Exception)\b", text):
|
|
406
|
+
# An internal error's name and message mean nothing to the user; the steps show how far it got.
|
|
407
|
+
return "Mobster couldn't read your iPhone's screen partway through. Try the task again."
|
|
408
|
+
return None
|
mobile_agent/env_file.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Private folders and files, and the KEY=value lines of an env file (agent.env)."""
|
|
2
|
+
|
|
3
|
+
import fcntl
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import tempfile
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def private_dir(path):
|
|
12
|
+
"""A folder only this user can open (the app data folder, logs), also when it exists."""
|
|
13
|
+
path = Path(path)
|
|
14
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
os.chmod(path, 0o700)
|
|
16
|
+
return path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def open_private(path, mode):
|
|
20
|
+
"""A file opened for writing ("w", "ab") with mode 0600, also when it exists."""
|
|
21
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW | (os.O_APPEND if "a" in mode else os.O_TRUNC)
|
|
22
|
+
stream = open(os.open(path, flags, 0o600), mode)
|
|
23
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
24
|
+
return stream
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_changes(changes):
|
|
28
|
+
for key, value in changes.items():
|
|
29
|
+
if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", key) or value is not None and (
|
|
30
|
+
not isinstance(value, str) or any(c in "\r\n" or "\ud800" <= c <= "\udfff" for c in value)):
|
|
31
|
+
raise ValueError("Invalid environment entry")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def writer(path):
|
|
36
|
+
"""Own the file for a whole read, change and write, in any process. Keychain migrations hold it too."""
|
|
37
|
+
path = Path(path).expanduser()
|
|
38
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
# Replacing the env file changes its inode, so lock a stable sibling for the entire read/change/write.
|
|
40
|
+
with os.fdopen(os.open(path.with_name(path.name + ".lock"), os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600),
|
|
41
|
+
"w") as lock:
|
|
42
|
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
43
|
+
yield path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_locked(path, changes):
|
|
47
|
+
"""Rewrite while ``writer`` owns the file, including a Keychain migration."""
|
|
48
|
+
# A line that is not UTF-8 is kept byte for byte, so Setup can still save a key into such a file (and
|
|
49
|
+
# a byte-order mark goes, as load_env_file ignores it: a key on the first line is still replaced).
|
|
50
|
+
text = path.read_bytes().decode("utf-8", "surrogateescape").removeprefix("\ufeff") if path.exists() else ""
|
|
51
|
+
lines = text.splitlines()
|
|
52
|
+
for key in changes:
|
|
53
|
+
lines = [line for line in lines if not re.match(rf"\s*(export\s+)?{re.escape(key)}\s*=", line)]
|
|
54
|
+
lines += [f"{key}={value}" for key, value in changes.items() if value is not None]
|
|
55
|
+
handle, temporary = tempfile.mkstemp(dir=path.parent, prefix=".env-")
|
|
56
|
+
try:
|
|
57
|
+
with os.fdopen(handle, "w", encoding="utf-8", errors="surrogateescape") as stream:
|
|
58
|
+
stream.write("\n".join(lines) + ("\n" if lines else ""))
|
|
59
|
+
os.chmod(temporary, 0o600)
|
|
60
|
+
os.replace(temporary, path)
|
|
61
|
+
except BaseException:
|
|
62
|
+
Path(temporary).unlink(missing_ok=True)
|
|
63
|
+
raise
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""The secrets in Mobster for Mac's settings file live in the login Keychain, never in the file."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import env_file, keychain_acl
|
|
10
|
+
from .ai_plans.vault import on_mac
|
|
11
|
+
|
|
12
|
+
SECRET_NAMES = ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "TEXT_MODEL_API_KEY", "MOBSTER_ALERT_WEBHOOK_URL")
|
|
13
|
+
MARKER = "MOBSTER_SECRETS_IN_KEYCHAIN"
|
|
14
|
+
SERVICE = "app.mobster.desktop.settings"
|
|
15
|
+
ACCOUNT = "agent.env"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
REFUSED = ("Mobster couldn't save this in your Keychain, so nothing was saved. Open Keychain Access, unlock the login "
|
|
19
|
+
"keychain, then try again.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class KeychainError(OSError):
|
|
23
|
+
"""The Keychain didn't read or take the secrets."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Keychain:
|
|
27
|
+
"""The secrets as one generic password: base64 of a JSON object."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, service=SERVICE, account=ACCOUNT, runner=subprocess.run, bridge=None):
|
|
30
|
+
self.service, self.account = service, account
|
|
31
|
+
self.store = keychain_acl.Store(
|
|
32
|
+
service, bridge=bridge,
|
|
33
|
+
runner=lambda argv, data: runner(argv, input=data, capture_output=True, timeout=10))
|
|
34
|
+
|
|
35
|
+
def load(self):
|
|
36
|
+
"""{name: value}; {} when there is no item. KeychainError when there is one it can't read."""
|
|
37
|
+
try:
|
|
38
|
+
word = self.store.get(self.account)
|
|
39
|
+
except keychain_acl.KeychainError as error:
|
|
40
|
+
raise KeychainError(f"The Keychain didn't give Mobster its settings ({error})") from None
|
|
41
|
+
if word is None:
|
|
42
|
+
return {}
|
|
43
|
+
try:
|
|
44
|
+
data = json.loads(base64.b64decode(word.strip()))
|
|
45
|
+
except (ValueError, TypeError):
|
|
46
|
+
raise KeychainError("Mobster's settings in the Keychain aren't readable") from None
|
|
47
|
+
return {k: v for k, v in data.items() if k in SECRET_NAMES and isinstance(v, str)} if isinstance(data, dict) \
|
|
48
|
+
else {}
|
|
49
|
+
|
|
50
|
+
def save(self, record):
|
|
51
|
+
secret = base64.b64encode(json.dumps(record, sort_keys=True).encode()).decode()
|
|
52
|
+
try:
|
|
53
|
+
self.store.set(self.account, secret)
|
|
54
|
+
except keychain_acl.KeychainError:
|
|
55
|
+
raise KeychainError("The Keychain didn't take Mobster's settings") from None
|
|
56
|
+
finally:
|
|
57
|
+
secret = None # noqa: F841 -- the secret's only other copy goes with this frame
|
|
58
|
+
|
|
59
|
+
def clear(self):
|
|
60
|
+
try:
|
|
61
|
+
self.store.delete(self.account)
|
|
62
|
+
except keychain_acl.KeychainError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def backend():
|
|
67
|
+
"""The Keychain, or None where secrets stay in the file (off a Mac, a scratch HOME, MOBSTER_VAULT=file)."""
|
|
68
|
+
if os.environ.get("MOBSTER_VAULT") == "file" or not on_mac():
|
|
69
|
+
return None
|
|
70
|
+
return Keychain()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def applies(path):
|
|
74
|
+
"""Whether ``path`` is the Mac app's agent.env and its secrets go to the Keychain."""
|
|
75
|
+
if path is None:
|
|
76
|
+
return False
|
|
77
|
+
from .config import app_env_file
|
|
78
|
+
try:
|
|
79
|
+
if Path(path).expanduser().resolve() != app_env_file().resolve():
|
|
80
|
+
return False
|
|
81
|
+
except OSError:
|
|
82
|
+
return False
|
|
83
|
+
return backend() is not None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def split(path, changes):
|
|
87
|
+
"""The file's own changes: each secret goes to the Keychain first; KeychainError if refused, never plain text."""
|
|
88
|
+
names = [name for name in changes if name in SECRET_NAMES]
|
|
89
|
+
if not names or not applies(path):
|
|
90
|
+
return changes
|
|
91
|
+
store = backend()
|
|
92
|
+
record = store.load()
|
|
93
|
+
for name in names:
|
|
94
|
+
if changes[name] is None:
|
|
95
|
+
record.pop(name, None)
|
|
96
|
+
else:
|
|
97
|
+
record[name] = changes[name]
|
|
98
|
+
if record:
|
|
99
|
+
store.save(record)
|
|
100
|
+
else:
|
|
101
|
+
store.clear()
|
|
102
|
+
rest = {name: value for name, value in changes.items() if name not in SECRET_NAMES}
|
|
103
|
+
return {**rest, **{name: None for name in names}, MARKER: "1" if record else None}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def secrets_for(path, values):
|
|
107
|
+
"""(secrets, warnings) for ``path`` (lines ``values``): a secret in the file moves to the Keychain, or stays."""
|
|
108
|
+
if not applies(path):
|
|
109
|
+
return {}, []
|
|
110
|
+
if not any(values.get(name) for name in SECRET_NAMES) and values.get(MARKER) != "1":
|
|
111
|
+
return {}, []
|
|
112
|
+
from .config import read_env_values
|
|
113
|
+
store = backend()
|
|
114
|
+
# The caller's read can precede a save or logout. Only the current file may initiate a migration or fallback.
|
|
115
|
+
try:
|
|
116
|
+
with env_file.writer(path) as path:
|
|
117
|
+
values = read_env_values(path)
|
|
118
|
+
in_file = {name: values[name] for name in SECRET_NAMES if values.get(name)}
|
|
119
|
+
if not in_file and values.get(MARKER) != "1":
|
|
120
|
+
return {}, []
|
|
121
|
+
try:
|
|
122
|
+
record = store.load()
|
|
123
|
+
except KeychainError as error:
|
|
124
|
+
return in_file, [f"{error}; Mobster's keys weren't loaded from it"]
|
|
125
|
+
if in_file:
|
|
126
|
+
try:
|
|
127
|
+
store.save({**record, **in_file})
|
|
128
|
+
env_file.write_locked(path, {**{name: None for name in in_file}, MARKER: "1"})
|
|
129
|
+
except OSError:
|
|
130
|
+
return {**record, **in_file}, ["Mobster couldn't move its keys to the "
|
|
131
|
+
"Keychain; they stay in agent.env"]
|
|
132
|
+
record = {**record, **in_file}
|
|
133
|
+
return record, []
|
|
134
|
+
except OSError as error:
|
|
135
|
+
return {}, [f"Mobster couldn't read its saved keys ({type(error).__name__}); no keys "
|
|
136
|
+
"were loaded from the Keychain"]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def load(path):
|
|
140
|
+
"""The secrets saved for the file at ``path`` ({} when none, or not on a Mac)."""
|
|
141
|
+
from .config import read_env_values
|
|
142
|
+
return secrets_for(path, read_env_values(path))[0]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def update_env_file(path, changes):
|
|
146
|
+
"""Set (str) or remove (None) several KEY=value lines in one private, atomic rewrite."""
|
|
147
|
+
env_file.check_changes(changes)
|
|
148
|
+
with env_file.writer(path) as path:
|
|
149
|
+
env_file.write_locked(path, split(path, changes))
|
mobile_agent/errors.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""One root for every Mobster domain error."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MobsterError(RuntimeError):
|
|
5
|
+
"""Root of the framework's failures. Never retry without a proven dispatch boundary."""
|
|
6
|
+
|
|
7
|
+
retryable = False
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SimError(RuntimeError):
|
|
11
|
+
"""A simulator problem; ``kind`` and ``fix`` are its stable public details."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, kind: str, message: str, fix: str = ""):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.kind, self.fix = kind, fix
|