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,213 @@
|
|
|
1
|
+
"""Prewarm (SPEC §3.1 F8): ready the phone while the person types; it never acts or touches a phone running a task."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from concurrent import futures
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
from ..compose import build_target_driver
|
|
11
|
+
from ..journal import JournalError, Lease
|
|
12
|
+
from ..run_executor import RunExecutor
|
|
13
|
+
from ..wda.frame_clock import attach_frame_clock
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger("mobster.harness")
|
|
16
|
+
|
|
17
|
+
SCREEN_TTL = 3.0 # seconds a prewarmed screen read may be reused for
|
|
18
|
+
MIN_INTERVAL = 2.0 # a second prewarm of one phone within this answers from the first
|
|
19
|
+
WAIT = 1.5 # how long the route waits for the warm-up before answering 202
|
|
20
|
+
OBSERVE_TIMEOUT = 5.0
|
|
21
|
+
SPRINGBOARD = "com.apple.springboard"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass
|
|
25
|
+
class Warm:
|
|
26
|
+
url: str
|
|
27
|
+
session: str
|
|
28
|
+
snapshot: object
|
|
29
|
+
clock: object
|
|
30
|
+
read_started: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_lock = threading.Lock()
|
|
34
|
+
_screens = {} # WDA url -> Warm
|
|
35
|
+
_jobs = {} # WDA url -> (Future, started monotonic, result dict)
|
|
36
|
+
_workers = RunExecutor()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _close_clock(clock):
|
|
40
|
+
try:
|
|
41
|
+
if clock is not None:
|
|
42
|
+
clock.close()
|
|
43
|
+
except Exception: # noqa: BLE001
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _drop(url, warm=None):
|
|
48
|
+
"""Forget ``url``'s screen (only ``warm`` when given) and release its clock."""
|
|
49
|
+
with _lock:
|
|
50
|
+
current = _screens.get(url)
|
|
51
|
+
if current is None or (warm is not None and current is not warm):
|
|
52
|
+
return
|
|
53
|
+
del _screens[url]
|
|
54
|
+
_close_clock(current.clock)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _lease_free(url):
|
|
58
|
+
"""True when no Mobster process holds this phone's lease now (taken and let go at once)."""
|
|
59
|
+
try:
|
|
60
|
+
lease = Lease.device(url.rstrip("/"), wait=0)
|
|
61
|
+
except JournalError:
|
|
62
|
+
return False
|
|
63
|
+
except Exception: # noqa: BLE001 -- can't tell: treat it as busy, never as free
|
|
64
|
+
return False
|
|
65
|
+
lease.close()
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def release(url):
|
|
70
|
+
"""Let go of ``url``'s kept screen and its frame clock: the phone has been idle."""
|
|
71
|
+
_drop(url)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _owner_active(url):
|
|
75
|
+
from .. import owner_presence
|
|
76
|
+
return owner_presence.is_owner_active(url)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _busy(runtime, phone):
|
|
80
|
+
try:
|
|
81
|
+
if runtime.run_on(phone) is not None:
|
|
82
|
+
return True
|
|
83
|
+
except Exception: # noqa: BLE001
|
|
84
|
+
return True
|
|
85
|
+
return not _lease_free(phone.wda_url)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _warm(runtime, phone, result):
|
|
89
|
+
"""Warm ``phone`` up; fills ``result["warmed"]``. Never raises."""
|
|
90
|
+
url = phone.wda_url
|
|
91
|
+
driver = None
|
|
92
|
+
try:
|
|
93
|
+
if _owner_active(url):
|
|
94
|
+
# The owner is using the phone: a session or screen read now would reach into their hands.
|
|
95
|
+
result["reason"] = "owner_active"
|
|
96
|
+
return
|
|
97
|
+
if _busy(runtime, phone):
|
|
98
|
+
result["reason"] = "busy"
|
|
99
|
+
return
|
|
100
|
+
session = phone.wda_session()
|
|
101
|
+
if not session:
|
|
102
|
+
result["reason"] = "no_session"
|
|
103
|
+
return
|
|
104
|
+
driver = build_target_driver(wda_url=url, session=session)
|
|
105
|
+
tune = getattr(driver, "tune", None)
|
|
106
|
+
if callable(tune):
|
|
107
|
+
tune(rich=True, glide=True)
|
|
108
|
+
result["warmed"].append("wda")
|
|
109
|
+
video = getattr(phone, "video", None)
|
|
110
|
+
clock = attach_frame_clock(driver, video) if video is not None else None
|
|
111
|
+
if clock is None or _busy(runtime, phone):
|
|
112
|
+
return
|
|
113
|
+
started = time.monotonic()
|
|
114
|
+
observe = getattr(driver, "observe_ready", None) or driver.observe
|
|
115
|
+
snapshot = observe(timeout=OBSERVE_TIMEOUT)
|
|
116
|
+
read_started = float(getattr(snapshot, "read_started", 0.0) or started)
|
|
117
|
+
driver.frame_clock = None # the clock outlives this driver, for SCREEN_TTL
|
|
118
|
+
warm = Warm(url, session, snapshot, clock, read_started)
|
|
119
|
+
with _lock:
|
|
120
|
+
old, _screens[url] = _screens.get(url), warm
|
|
121
|
+
if old is not None:
|
|
122
|
+
_close_clock(old.clock)
|
|
123
|
+
timer = threading.Timer(SCREEN_TTL + 0.5, _drop, args=(url, warm))
|
|
124
|
+
timer.daemon = True
|
|
125
|
+
timer.start()
|
|
126
|
+
result["warmed"].append("screen")
|
|
127
|
+
except Exception as error: # noqa: BLE001 -- a cold or busy phone is never an error here
|
|
128
|
+
result.setdefault("reason", type(error).__name__)
|
|
129
|
+
log.info("prewarm stopped: %s", type(error).__name__)
|
|
130
|
+
finally:
|
|
131
|
+
if driver is not None:
|
|
132
|
+
clock = getattr(driver, "frame_clock", None)
|
|
133
|
+
if clock is not None:
|
|
134
|
+
driver.frame_clock = None
|
|
135
|
+
_close_clock(clock)
|
|
136
|
+
try:
|
|
137
|
+
driver.close()
|
|
138
|
+
except Exception: # noqa: BLE001
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def prewarm(runtime, device=None, *, wait=WAIT):
|
|
143
|
+
"""Warm the phone ``device`` names (None: the one a new task runs on)."""
|
|
144
|
+
phone = runtime.resolve_device(device)
|
|
145
|
+
url = getattr(phone, "wda_url", None)
|
|
146
|
+
if not url:
|
|
147
|
+
return {"warmed": [], "pending": False, "reason": "no_phone"}
|
|
148
|
+
if _owner_active(url):
|
|
149
|
+
return {"warmed": [], "pending": False, "reason": "owner_active"}
|
|
150
|
+
now = time.monotonic()
|
|
151
|
+
with _lock:
|
|
152
|
+
job = _jobs.get(url)
|
|
153
|
+
if job is None or (job[0].done() and now - job[1] >= MIN_INTERVAL):
|
|
154
|
+
result = {"warmed": []}
|
|
155
|
+
job = _jobs[url] = (_workers.submit("mobster-prewarm", _warm, runtime, phone, result), now, result)
|
|
156
|
+
warming, _, result = job
|
|
157
|
+
futures.wait((warming,), max(0.0, wait))
|
|
158
|
+
out = {"warmed": list(result["warmed"]), "pending": not warming.done()}
|
|
159
|
+
if result.get("reason"):
|
|
160
|
+
out["reason"] = result["reason"]
|
|
161
|
+
return out
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def take(driver, run_context=None, *, now=None):
|
|
165
|
+
"""The prewarmed read of ``driver``'s phone as its first screen, or None. (snapshot, age in seconds)."""
|
|
166
|
+
url = getattr(driver, "url", None)
|
|
167
|
+
if not url:
|
|
168
|
+
return None
|
|
169
|
+
with _lock:
|
|
170
|
+
warm = _screens.pop(url, None)
|
|
171
|
+
if warm is None:
|
|
172
|
+
return None
|
|
173
|
+
try:
|
|
174
|
+
now = time.monotonic() if now is None else now
|
|
175
|
+
age = now - warm.read_started
|
|
176
|
+
if age > SCREEN_TTL or age < 0:
|
|
177
|
+
return None
|
|
178
|
+
if not str(getattr(driver, "prefix", "")).endswith("/" + quote(warm.session, safe="")):
|
|
179
|
+
return None
|
|
180
|
+
expected = getattr(run_context, "app_bundle", None) or SPRINGBOARD
|
|
181
|
+
if getattr(warm.snapshot, "bundle_id", None) != expected:
|
|
182
|
+
return None
|
|
183
|
+
from ..wda.guards import STILL_MARGIN
|
|
184
|
+
still = warm.clock.still_for()
|
|
185
|
+
if still is None or now - still > warm.read_started - STILL_MARGIN:
|
|
186
|
+
return None # something on screen changed since the read began (or the stream can't say)
|
|
187
|
+
# Unchanged since the read: the latest the run's own clock can vouch for, so its first action reuses it.
|
|
188
|
+
own = getattr(getattr(driver, "frame_clock", None), "still_for", None)
|
|
189
|
+
try:
|
|
190
|
+
run_still = own() if callable(own) else None
|
|
191
|
+
except Exception: # noqa: BLE001
|
|
192
|
+
run_still = None
|
|
193
|
+
started = max(warm.read_started, now - run_still if run_still is not None else now)
|
|
194
|
+
return dataclasses.replace(warm.snapshot, captured_at=now, read_started=started), age
|
|
195
|
+
except Exception: # noqa: BLE001
|
|
196
|
+
return None
|
|
197
|
+
finally:
|
|
198
|
+
_close_clock(warm.clock)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def close_all(runtime=None):
|
|
202
|
+
"""Release every kept read and its clock (Runtime.close)."""
|
|
203
|
+
with _lock:
|
|
204
|
+
warms = list(_screens.values())
|
|
205
|
+
_screens.clear()
|
|
206
|
+
for warm in warms:
|
|
207
|
+
_close_clock(warm.clock)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def reset_for_tests():
|
|
211
|
+
close_all()
|
|
212
|
+
with _lock:
|
|
213
|
+
_jobs.clear()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Track `harness`'s registrations, staged so a register that raises leaves nothing registered."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _bool(value, runtime):
|
|
5
|
+
if type(value) is not bool:
|
|
6
|
+
raise ValueError("askUser must be true or false")
|
|
7
|
+
return value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def hints_route(request):
|
|
11
|
+
"""GET /api/harness/hints: {apps: hints.health}: every app's hints by source (packs, replays,
|
|
12
|
+
the iOS knowledge base)."""
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .. import hints
|
|
16
|
+
from ..api_routes import Response
|
|
17
|
+
state_db = getattr(getattr(request.runtime, "config", None), "state_db", None)
|
|
18
|
+
return Response.json({"apps": hints.health(Path(state_db).parent if state_db else None)})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register(api):
|
|
22
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
23
|
+
from .. import api_routes, journal, project_env
|
|
24
|
+
from . import checkpoints, prewarm, routes
|
|
25
|
+
from .tools import ask_user, watch
|
|
26
|
+
|
|
27
|
+
journal.register_schema(checkpoints.SCHEMA, checkpoints.VERSION, checkpoints.migrate)
|
|
28
|
+
api.register_run_field("resumeFrom", checkpoints.validate_resume_from)
|
|
29
|
+
api.register_run_field("askUser", _bool)
|
|
30
|
+
# A saved task's ${NAME}s come from this project's secrets (project_env.py); the goal keeps the placeholder.
|
|
31
|
+
api.register_run_field("projectEnv", project_env.validate_root)
|
|
32
|
+
api.register_pre_run(checkpoints.pre_run, order=0)
|
|
33
|
+
api.register_frontier_options(checkpoints.frontier_options)
|
|
34
|
+
api.register_tool(ask_user.factory, order=0)
|
|
35
|
+
api.register_tool(watch.factory, order=1)
|
|
36
|
+
api.register_service(lambda runtime: None, lambda runtime: (checkpoints.close(runtime), prewarm.close_all(runtime)))
|
|
37
|
+
for route in routes.ROUTES:
|
|
38
|
+
api_routes.register(route)
|
|
39
|
+
from .. import hints
|
|
40
|
+
api.register_run_listener(hints.HintListener())
|
|
41
|
+
api_routes.register(api_routes.Route("harness", "GET", "/api/harness/hints", hints_route, body="none"))
|
|
42
|
+
from ..integrations import routes as client_routes
|
|
43
|
+
client_routes.register_routes()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""The harness track's HTTP API (seam S3, mobile_agent/docs/harness.md); none of its routes approves anything."""
|
|
2
|
+
|
|
3
|
+
from ..api_errors import APIError
|
|
4
|
+
from ..api_routes import HEADER_ORIGINS, Response, Route
|
|
5
|
+
from ..steering import SteeringFull
|
|
6
|
+
from . import checkpoints, control, prewarm, settings
|
|
7
|
+
from .control import PAUSE_LIMIT_SECONDS
|
|
8
|
+
|
|
9
|
+
NO_MESSAGES = "This task can't take messages while it works. Stop it, or wait for it to finish."
|
|
10
|
+
NO_PAUSE = "This task can't pause. Stop it instead."
|
|
11
|
+
FULL = "Mobster's agent has 10 messages it hasn't read yet. Wait for it to catch up."
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _run(request):
|
|
15
|
+
runtime = request.runtime
|
|
16
|
+
with runtime.lock:
|
|
17
|
+
run = runtime.runs.get(request.params["id"])
|
|
18
|
+
if run is None:
|
|
19
|
+
raise APIError("Task not found", 404, "not_found")
|
|
20
|
+
return run
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _steerable_now(run, unsupported, code):
|
|
24
|
+
if run.finished_at is not None:
|
|
25
|
+
raise APIError("This task has finished.", 409, "run_not_active")
|
|
26
|
+
if not run.steerable:
|
|
27
|
+
raise APIError(unsupported, 409, code)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def source_of(request, given):
|
|
31
|
+
"""The message's source: the request's origin label, or "voice" from the Mac app."""
|
|
32
|
+
origin = request.origin if request.origin in HEADER_ORIGINS else "cli"
|
|
33
|
+
if given is None or given == origin:
|
|
34
|
+
return origin
|
|
35
|
+
if given == "voice" and origin == "app":
|
|
36
|
+
return "voice"
|
|
37
|
+
raise ValueError("source can only be \"voice\", from the Mac app")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def messages(request):
|
|
41
|
+
run = _run(request)
|
|
42
|
+
body = request.body or {}
|
|
43
|
+
if "control" in body:
|
|
44
|
+
if set(body) != {"control"} or body["control"] != "stop_after_step":
|
|
45
|
+
raise ValueError("The only control is \"stop_after_step\", on its own")
|
|
46
|
+
_steerable_now(run, NO_MESSAGES, "steer_unsupported")
|
|
47
|
+
control.request_stop_after(run.steering)
|
|
48
|
+
run.emit({"event": "stop_after_step_requested"})
|
|
49
|
+
return Response.json({"control": "stop_after_step"}, 202)
|
|
50
|
+
if set(body) - {"text", "source"} or not isinstance(body.get("text"), str) \
|
|
51
|
+
or body.get("source") is not None and not isinstance(body["source"], str):
|
|
52
|
+
raise ValueError("Send the message as text, 1 to 2,000 characters")
|
|
53
|
+
source = source_of(request, body.get("source"))
|
|
54
|
+
_steerable_now(run, NO_MESSAGES, "steer_unsupported")
|
|
55
|
+
try:
|
|
56
|
+
message = run.steer(body["text"], source)
|
|
57
|
+
except SteeringFull:
|
|
58
|
+
raise APIError(FULL, 429, "steering_full") from None
|
|
59
|
+
return Response.json({"message": message}, 202)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def pause(request):
|
|
63
|
+
run = _run(request)
|
|
64
|
+
body = request.body or {}
|
|
65
|
+
if set(body) != {"paused"} or type(body["paused"]) is not bool:
|
|
66
|
+
raise ValueError("Send {\"paused\": true} to pause or {\"paused\": false} to continue")
|
|
67
|
+
_steerable_now(run, NO_PAUSE, "pause_unsupported")
|
|
68
|
+
control.set_paused(run.steering, body["paused"])
|
|
69
|
+
return Response.json({"run": run.public(include_events=False), "paused": body["paused"]})
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def pickup(request):
|
|
73
|
+
if request.body:
|
|
74
|
+
raise ValueError("Send an empty JSON object")
|
|
75
|
+
keys = request.headers.get_all("Idempotency-Key", []) if hasattr(request.headers, "get_all") else []
|
|
76
|
+
key = keys[0] if keys else None
|
|
77
|
+
run, replayed = checkpoints.pick_up(request.runtime, request.params["id"], idempotency_key=key,
|
|
78
|
+
origin=request.origin)
|
|
79
|
+
return Response.json({"run": run.public(), "replayed": replayed}, 200 if replayed else 201)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def prewarm_route(request):
|
|
83
|
+
body = request.body or {}
|
|
84
|
+
if set(body) - {"device"} or body.get("device") is not None and not isinstance(body["device"], str):
|
|
85
|
+
raise ValueError("Send {} or {\"device\": \"<a device's id, UDID or name>\"}")
|
|
86
|
+
return Response.json(prewarm.prewarm(request.runtime, body.get("device")), 202)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def state(runtime):
|
|
90
|
+
return {"askUser": settings.ask_user(runtime),
|
|
91
|
+
"askUserLimit": settings.ASK_USER_LIMIT, "pauseLimitSeconds": PAUSE_LIMIT_SECONDS,
|
|
92
|
+
"ledger": False, "earlyLaunch": False, "longRun": None}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def harness_get(request):
|
|
96
|
+
return Response.json(state(request.runtime))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def harness_post(request):
|
|
100
|
+
body = request.body or {}
|
|
101
|
+
if set(body) != {"askUser"} or type(body["askUser"]) is not bool:
|
|
102
|
+
raise ValueError("Send {\"askUser\": true} or {\"askUser\": false}")
|
|
103
|
+
settings.save(request.runtime, {"askUser": body["askUser"]})
|
|
104
|
+
return Response.json(state(request.runtime))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
ROUTES = (
|
|
108
|
+
Route("harness", "POST", r"/api/runs/{id}/messages", messages, max_bytes=8_000),
|
|
109
|
+
Route("harness", "POST", r"/api/runs/{id}/pause", pause, max_bytes=1_000),
|
|
110
|
+
Route("harness", "POST", r"/api/runs/{id}/pickup", pickup, max_bytes=1_000, idempotency=True),
|
|
111
|
+
Route("harness", "POST", r"/api/prewarm", prewarm_route, max_bytes=1_000),
|
|
112
|
+
Route("harness", "GET", r"/api/harness", harness_get, body="none"),
|
|
113
|
+
Route("harness", "POST", r"/api/harness", harness_post, max_bytes=1_000),
|
|
114
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Harness settings, kept in the run journal's ``harness_settings`` table (schema ``harness``)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import weakref
|
|
7
|
+
|
|
8
|
+
DEFAULTS = {"askUser": True}
|
|
9
|
+
ASK_USER_LIMIT = 2 # questions a task may ask
|
|
10
|
+
_cache = weakref.WeakKeyDictionary() # journal -> {key: value}
|
|
11
|
+
_lock = threading.Lock()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ask_user_switch():
|
|
15
|
+
"""The process-wide switch: on unless MOBSTER_ASK_USER says off."""
|
|
16
|
+
return os.environ.get("MOBSTER_ASK_USER", "on").strip().casefold() not in ("off", "0", "false", "no")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _journal(runtime):
|
|
20
|
+
return getattr(runtime, "journal", None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load(runtime):
|
|
24
|
+
"""Every setting, the saved value over the default."""
|
|
25
|
+
journal = _journal(runtime)
|
|
26
|
+
values = dict(DEFAULTS)
|
|
27
|
+
if journal is None or getattr(journal, "connection", None) is None:
|
|
28
|
+
return values
|
|
29
|
+
with _lock:
|
|
30
|
+
cached = _cache.get(journal)
|
|
31
|
+
if cached is not None:
|
|
32
|
+
return {**values, **cached}
|
|
33
|
+
saved = {}
|
|
34
|
+
try:
|
|
35
|
+
with journal.transaction() as connection:
|
|
36
|
+
rows = connection.execute("SELECT key, value FROM harness_settings").fetchall()
|
|
37
|
+
for key, raw in rows:
|
|
38
|
+
if key in DEFAULTS:
|
|
39
|
+
try:
|
|
40
|
+
saved[key] = json.loads(raw)
|
|
41
|
+
except ValueError:
|
|
42
|
+
pass
|
|
43
|
+
except Exception: # noqa: BLE001 -- no table (the track is off) or no journal: the defaults
|
|
44
|
+
saved = {}
|
|
45
|
+
with _lock:
|
|
46
|
+
_cache[journal] = saved
|
|
47
|
+
return {**values, **saved}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save(runtime, changes):
|
|
51
|
+
"""Save ``changes`` (validated by the caller). Returns every setting."""
|
|
52
|
+
journal = _journal(runtime)
|
|
53
|
+
if journal is None or getattr(journal, "connection", None) is None:
|
|
54
|
+
raise ValueError("Settings can't be saved without Mobster's task history")
|
|
55
|
+
with journal.transaction() as connection:
|
|
56
|
+
for key, value in changes.items():
|
|
57
|
+
connection.execute("INSERT INTO harness_settings(key, value) VALUES (?, ?) "
|
|
58
|
+
"ON CONFLICT(key) DO UPDATE SET value=excluded.value", (key, json.dumps(value)))
|
|
59
|
+
with _lock:
|
|
60
|
+
_cache.pop(journal, None)
|
|
61
|
+
return load(runtime)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def ask_user(runtime):
|
|
65
|
+
"""Whether Mobster's agent may ask clarifying questions in tasks a person watches."""
|
|
66
|
+
return ask_user_switch() and load(runtime).get("askUser") is not False
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""The tool policy (SPEC §3.1 F7): which registered tools a Smart run offers its model."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from ...agent_hooks import SkillContext
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger("mobster.harness")
|
|
9
|
+
|
|
10
|
+
MAX_TOOLS = 8
|
|
11
|
+
OP = re.compile(r"[A-Z][A-Z_]{1,30}")
|
|
12
|
+
PROMPT_CHARS = 400
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _default_ops():
|
|
16
|
+
from ...frontier import default_skills
|
|
17
|
+
return {getattr(skill, "op", None) for skill in default_skills()}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _why_not(skill, taken, run_context, context):
|
|
21
|
+
"""None when ``skill`` may be offered, else a short reason for the log."""
|
|
22
|
+
op = getattr(skill, "op", None)
|
|
23
|
+
if not isinstance(op, str) or not OP.fullmatch(op) or op in taken:
|
|
24
|
+
return "its op is invalid or taken"
|
|
25
|
+
if not callable(getattr(skill, "prepare", None)) or not callable(getattr(skill, "perform", None)):
|
|
26
|
+
return "it has no prepare or perform"
|
|
27
|
+
raw = str(getattr(skill, "prompt", "") or "")
|
|
28
|
+
prompt = raw.strip()
|
|
29
|
+
prompt = prompt[2:] if prompt.startswith("- ") else prompt
|
|
30
|
+
if not prompt.startswith(op) or len(prompt) > PROMPT_CHARS or "\n" in raw.strip():
|
|
31
|
+
return "its prompt is not one line that starts with its op"
|
|
32
|
+
if getattr(skill, "interactive_only", False) and not getattr(run_context, "interactive", False):
|
|
33
|
+
return "nobody is watching this run"
|
|
34
|
+
available = getattr(skill, "available", None)
|
|
35
|
+
if callable(available):
|
|
36
|
+
try:
|
|
37
|
+
if not available(context):
|
|
38
|
+
return "it isn't available here"
|
|
39
|
+
except Exception: # noqa: BLE001 -- a tool that can't say is not offered
|
|
40
|
+
return "its availability check failed"
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def offered(skills, *, run_context=None, driver=None, guard=None, emit=None):
|
|
45
|
+
"""``skills`` as the policy offers them (a tuple), the default skills first as given."""
|
|
46
|
+
from ...frontier import OPERATIONS
|
|
47
|
+
defaults = _default_ops()
|
|
48
|
+
context = SkillContext(driver=driver, request="", app_bundle=getattr(run_context, "app_bundle", None),
|
|
49
|
+
emit=emit or (lambda event: None), guard=guard, run=run_context,
|
|
50
|
+
clarify=getattr(run_context, "clarify", None))
|
|
51
|
+
out, taken, extra = [], set(OPERATIONS), 0
|
|
52
|
+
for skill in skills or ():
|
|
53
|
+
op = getattr(skill, "op", None)
|
|
54
|
+
if op in defaults and op not in taken:
|
|
55
|
+
taken.add(op)
|
|
56
|
+
out.append(skill)
|
|
57
|
+
continue
|
|
58
|
+
why = _why_not(skill, taken, run_context, context)
|
|
59
|
+
if why is None and extra >= MAX_TOOLS:
|
|
60
|
+
why = f"only {MAX_TOOLS} tools are offered at once"
|
|
61
|
+
if why is not None:
|
|
62
|
+
log.info("tool %r not offered: %s", op, why)
|
|
63
|
+
continue
|
|
64
|
+
taken.add(op)
|
|
65
|
+
extra += 1
|
|
66
|
+
out.append(skill)
|
|
67
|
+
return tuple(out)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""ASK_USER: Mobster's agent asks the person a clarifying question (SPEC §3.1 F2); an answer approves nothing."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from ...agent_hooks import Prepared, SkillResult
|
|
6
|
+
from ...secret_filter import redact
|
|
7
|
+
from ..settings import ASK_USER_LIMIT
|
|
8
|
+
|
|
9
|
+
PROMPT = ("- ASK_USER text: only when the request can be read two ways the screen can't settle and a wrong guess would "
|
|
10
|
+
"change what is sent, bought, posted, deleted or answered. Never to confirm a send (that asks by itself). "
|
|
11
|
+
"Put up to 4 short answers after the question, each after \" | \", a person's with what tells them apart "
|
|
12
|
+
"after \" \u2014 \" (\"Which Sam? | Sam Lee \u2014 texted Tuesday | Sam Ortiz \u2014 ends 0142\").")
|
|
13
|
+
QUESTION_CHARS, LABEL_CHARS, CHOICE_CHARS, MAX_CHOICES = 300, 80, 120, 6
|
|
14
|
+
|
|
15
|
+
NO_QUESTION = "ASK_USER needs the question in text."
|
|
16
|
+
CANT_ASK = ("Refused: this task can't ask the user. Decide from the request and the screen, or finish with BLOCKED "
|
|
17
|
+
"and say what you need to know.")
|
|
18
|
+
ASKED_ENOUGH = ("You already asked the user twice in this task. Decide from the request and the screen, or finish "
|
|
19
|
+
"with BLOCKED and say what you need to know.")
|
|
20
|
+
DECLINED = ("The user chose not to answer. Decide from the request and the screen, or finish with BLOCKED and say "
|
|
21
|
+
"what you need to know. Don't ask again.")
|
|
22
|
+
NO_ANSWER = "Nobody answered Mobster's question in 10 minutes, so it stopped without doing anything more."
|
|
23
|
+
STOPPED = "Stopped while Mobster waited for an answer to its question."
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def split(text):
|
|
27
|
+
"""(question, choices) from "Question? | A | B": at most MAX_CHOICES distinct choices, none unless two or more."""
|
|
28
|
+
parts = [" ".join(part.split()) for part in str(text or "").split("|")]
|
|
29
|
+
question = parts[0][:QUESTION_CHARS]
|
|
30
|
+
choices, seen = [], set()
|
|
31
|
+
for part in parts[1:]:
|
|
32
|
+
label = part[:CHOICE_CHARS]
|
|
33
|
+
if label and label.casefold() not in seen:
|
|
34
|
+
seen.add(label.casefold())
|
|
35
|
+
choices.append({"id": f"c{len(choices) + 1}", "label": label})
|
|
36
|
+
if len(choices) == MAX_CHOICES:
|
|
37
|
+
break
|
|
38
|
+
return question, (choices if len(choices) >= 2 else [])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def feedback_for(answer, choices):
|
|
42
|
+
"""(feedback for the model, run-ending status or None) for the asker's answer."""
|
|
43
|
+
answer = answer if isinstance(answer, str) else "denied"
|
|
44
|
+
if answer.startswith("answer:") or answer.startswith("redirected:"):
|
|
45
|
+
text = " ".join(answer.split(":", 1)[1].split())[:500]
|
|
46
|
+
return f"The user answered your question: {text!r}. Go on with that.", None
|
|
47
|
+
if answer.startswith("choice:"):
|
|
48
|
+
chosen = answer.split(":", 1)[1]
|
|
49
|
+
label = next((c["label"] for c in choices if c["id"] == chosen), chosen)
|
|
50
|
+
return f"The user answered your question: {label!r}. Go on with that.", None
|
|
51
|
+
if answer == "timeout":
|
|
52
|
+
return NO_ANSWER, "approval_timeout"
|
|
53
|
+
if answer == "stopped":
|
|
54
|
+
return STOPPED, "stopped"
|
|
55
|
+
return DECLINED, None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AskUser:
|
|
59
|
+
"""agent_hooks.Skill: ASK_USER (one instance per run: it counts its questions)."""
|
|
60
|
+
|
|
61
|
+
op = "ASK_USER"
|
|
62
|
+
prompt = PROMPT
|
|
63
|
+
needs_target = False
|
|
64
|
+
interactive_only = True
|
|
65
|
+
|
|
66
|
+
def __init__(self, limit=ASK_USER_LIMIT, clock=time.monotonic):
|
|
67
|
+
self.limit, self.clock, self.asked = limit, clock, 0
|
|
68
|
+
|
|
69
|
+
def available(self, ctx):
|
|
70
|
+
return callable(getattr(ctx, "clarify", None))
|
|
71
|
+
|
|
72
|
+
def prepare(self, ctx, act, snapshot):
|
|
73
|
+
return Prepared(None) # a question is not a commit: nothing to approve
|
|
74
|
+
|
|
75
|
+
def perform(self, ctx, prepared, act, snapshot):
|
|
76
|
+
question, choices = split(redact(str(act.get("text") or "")))
|
|
77
|
+
if not question:
|
|
78
|
+
return SkillResult(NO_QUESTION)
|
|
79
|
+
clarify = getattr(ctx, "clarify", None)
|
|
80
|
+
if not callable(clarify):
|
|
81
|
+
return SkillResult(CANT_ASK)
|
|
82
|
+
if self.asked >= self.limit:
|
|
83
|
+
return SkillResult(ASKED_ENOUGH)
|
|
84
|
+
self.asked += 1
|
|
85
|
+
started = self.clock()
|
|
86
|
+
answer = clarify({"kind": "clarify", "operation": self.op, "label": question[:LABEL_CHARS],
|
|
87
|
+
"question": question, "choices": choices, "allow_text": True})
|
|
88
|
+
waited = max(0.0, self.clock() - started)
|
|
89
|
+
feedback, status = feedback_for(answer, choices)
|
|
90
|
+
return SkillResult(feedback, changed=False, waited=waited, status=status)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def factory(ctx):
|
|
94
|
+
"""register_tool's factory: an AskUser for a run a person watches, else None."""
|
|
95
|
+
from ..settings import ask_user
|
|
96
|
+
if not getattr(ctx, "interactive", False) or getattr(ctx, "engine", None) != "smart":
|
|
97
|
+
return None
|
|
98
|
+
if (ctx.extras or {}).get("askUser") is not True:
|
|
99
|
+
return None
|
|
100
|
+
if not ask_user(ctx.runtime):
|
|
101
|
+
return None
|
|
102
|
+
return AskUser()
|