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,88 @@
|
|
|
1
|
+
"""Bounded, local-only MCP view of two exact saved test suites."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .comparison import ComparisonError, compare_suites
|
|
7
|
+
|
|
8
|
+
NAME = "compare_test_suites"
|
|
9
|
+
MAX_CASES = 100
|
|
10
|
+
SUITE = r"^[0-9]{8}-[0-9]{6}-[0-9a-f]{4}$"
|
|
11
|
+
REF = re.compile(r"[a-f0-9]{12}\Z")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def definition():
|
|
15
|
+
return {"name": NAME,
|
|
16
|
+
"description": "Compare two exact completed local test suites. Returns both verdicts and bounded "
|
|
17
|
+
"case transitions; never treats a green run alone as proof of a fix.",
|
|
18
|
+
"inputSchema": {"type": "object", "properties": {
|
|
19
|
+
"base_suite_id": {"type": "string", "pattern": SUITE,
|
|
20
|
+
"description": "the saved suite before the proposed change"},
|
|
21
|
+
"candidate_suite_id": {"type": "string", "pattern": SUITE,
|
|
22
|
+
"description": "the saved suite after the proposed change"}},
|
|
23
|
+
"required": ["base_suite_id", "candidate_suite_id"],
|
|
24
|
+
"additionalProperties": False}}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def call(args, toolset, *, compare=compare_suites):
|
|
28
|
+
from ..mcp_server.protocol import ToolResult
|
|
29
|
+
from ..mcp_server.tools import ToolError
|
|
30
|
+
|
|
31
|
+
runs = Path(toolset.runs_dir).absolute()
|
|
32
|
+
private = runs.parent
|
|
33
|
+
results = private / "test-results"
|
|
34
|
+
if runs.name != "runs" or private.name != ".mobster":
|
|
35
|
+
raise ToolError("Start this MCP server in the test project without a custom --out to compare its suites.")
|
|
36
|
+
if runs.is_symlink() or private.is_symlink() or results.is_symlink():
|
|
37
|
+
raise ToolError("The project's test-results folder must not be a symlink.")
|
|
38
|
+
try:
|
|
39
|
+
answer = compare(results, args["base_suite_id"], args["candidate_suite_id"])
|
|
40
|
+
except (ComparisonError, OSError) as error:
|
|
41
|
+
raise ToolError(str(error)) from None
|
|
42
|
+
total = len(answer["cases"])
|
|
43
|
+
answer = {**answer, "cases": answer["cases"][:MAX_CASES], "casesTotal": total,
|
|
44
|
+
"casesTruncated": total > MAX_CASES}
|
|
45
|
+
proof = "yes, for controlled app state" if answer["verifiedFix"] else "no"
|
|
46
|
+
text = (f"Compared suites {args['base_suite_id']} and {args['candidate_suite_id']}: "
|
|
47
|
+
f"{'comparable' if answer['comparable'] else 'not comparable'}; verified fix: {proof}. "
|
|
48
|
+
f"Showing {len(answer['cases'])} of {total} case-target pairs.")
|
|
49
|
+
return ToolResult(text, answer)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def mask_comparison(mask, args, result):
|
|
53
|
+
"""Preserve only validated opaque suite/case handles through the contextual secret mask."""
|
|
54
|
+
from ..mcp_server.tools import ToolError
|
|
55
|
+
|
|
56
|
+
if result.is_error:
|
|
57
|
+
return mask(result)
|
|
58
|
+
value = result.structured
|
|
59
|
+
if not isinstance(value, dict) or set(value) != {
|
|
60
|
+
"schema", "baseSuite", "candidateSuite", "baseVerdict", "candidateVerdict", "comparable",
|
|
61
|
+
"verifiedFix", "proof", "reasons", "cases", "casesTotal", "casesTruncated"} or \
|
|
62
|
+
value["schema"] != "mobster.test-compare/1" or \
|
|
63
|
+
value["baseSuite"] != args["base_suite_id"] or value["candidateSuite"] != args["candidate_suite_id"] or \
|
|
64
|
+
type(value["verifiedFix"]) is not bool or \
|
|
65
|
+
value["proof"] != {"scope": "controlled_app_state" if value["verifiedFix"] else None} or \
|
|
66
|
+
type(value["casesTotal"]) is not int or \
|
|
67
|
+
not isinstance(value["cases"], list) or len(value["cases"]) > MAX_CASES or \
|
|
68
|
+
type(value["casesTruncated"]) is not bool:
|
|
69
|
+
raise ToolError("Mobster returned an invalid local suite comparison.")
|
|
70
|
+
refs = []
|
|
71
|
+
for row in value["cases"]:
|
|
72
|
+
if not isinstance(row, dict) or type(row.get("verifiedFix")) is not bool or \
|
|
73
|
+
row.get("proof") != {"scope": "controlled_app_state" if row["verifiedFix"] else None} or \
|
|
74
|
+
not all(isinstance(row.get(key), str) and REF.fullmatch(row[key])
|
|
75
|
+
for key in ("caseRef", "targetRef")):
|
|
76
|
+
raise ToolError("Mobster returned invalid comparison case references.")
|
|
77
|
+
refs.append((row["caseRef"], row["targetRef"]))
|
|
78
|
+
result.structured = {**value, "schema": None, "baseSuite": None, "candidateSuite": None, "proof": None,
|
|
79
|
+
"cases": [{**row, "caseRef": None, "targetRef": None, "proof": None}
|
|
80
|
+
for row in value["cases"]]}
|
|
81
|
+
masked = mask(result)
|
|
82
|
+
masked.structured["schema"] = value["schema"]
|
|
83
|
+
masked.structured["baseSuite"] = value["baseSuite"]
|
|
84
|
+
masked.structured["candidateSuite"] = value["candidateSuite"]
|
|
85
|
+
masked.structured["proof"] = value["proof"]
|
|
86
|
+
for row, source, (case_ref, target_ref) in zip(masked.structured["cases"], value["cases"], refs):
|
|
87
|
+
row.update(caseRef=case_ref, targetRef=target_ref, proof=source["proof"])
|
|
88
|
+
return masked
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"""Find a smaller local reproducer by running bounded, isolated check variants.
|
|
2
|
+
|
|
3
|
+
Only steps change. The saved failure, app bytes, registered fixture, assertions and
|
|
4
|
+
resolved simulator stay fixed. A draft is never an approved check or a proof of
|
|
5
|
+
external backend isolation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import stat
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from contextlib import ExitStack, contextmanager
|
|
15
|
+
from dataclasses import replace
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from ..verify.checks import CheckError, dump_check
|
|
19
|
+
from . import capsule, comparison
|
|
20
|
+
from .api import Options, UsageError, execute, plan
|
|
21
|
+
from .discover import Entry
|
|
22
|
+
from .provenance import app_digest, case_provenance, require_case_provenance
|
|
23
|
+
|
|
24
|
+
SCHEMA = "mobster.test-minimize/1"
|
|
25
|
+
MAX_ATTEMPTS = 8
|
|
26
|
+
MAX_SECONDS = 1800
|
|
27
|
+
MAX_USD = 2
|
|
28
|
+
REF = re.compile(r"[0-9a-f]{12}\Z")
|
|
29
|
+
MAX_DRAFT_BYTES = 1_000_000
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MinimizationError(ValueError):
|
|
33
|
+
"""The saved failure cannot safely enter a new reduction experiment."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _Draft:
|
|
37
|
+
def __init__(self, root, folder, root_chain, descriptors):
|
|
38
|
+
self.root = root
|
|
39
|
+
self.folder = folder
|
|
40
|
+
self.root_chain = root_chain
|
|
41
|
+
self.descriptors = descriptors
|
|
42
|
+
self.fd = descriptors[-1]
|
|
43
|
+
|
|
44
|
+
def attached(self):
|
|
45
|
+
try:
|
|
46
|
+
paths = (*self.root_chain, (".mobster", self.descriptors[0], self.descriptors[1]),
|
|
47
|
+
("drafts", self.descriptors[1], self.descriptors[2]),
|
|
48
|
+
("repro", self.descriptors[2], self.descriptors[3]),
|
|
49
|
+
(self.folder.name, self.descriptors[3], self.descriptors[4]))
|
|
50
|
+
for name, parent, held_fd in paths:
|
|
51
|
+
found = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
|
52
|
+
held = os.fstat(held_fd)
|
|
53
|
+
if not stat.S_ISDIR(found.st_mode) or (found.st_dev, found.st_ino) != (held.st_dev, held.st_ino):
|
|
54
|
+
return False
|
|
55
|
+
except OSError:
|
|
56
|
+
return False
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def _bytes(self, data):
|
|
60
|
+
if len(data) > MAX_DRAFT_BYTES or not self.attached():
|
|
61
|
+
raise MinimizationError("The draft directory changed or its output is too large. Review this attempt.")
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
def write_new(self, name, content):
|
|
65
|
+
try:
|
|
66
|
+
capsule._write_at(self.fd, name, self._bytes(content.encode("utf-8")))
|
|
67
|
+
os.fsync(self.fd)
|
|
68
|
+
except (OSError, capsule.CapsuleError) as error:
|
|
69
|
+
raise MinimizationError(
|
|
70
|
+
"The draft file already exists or its directory changed. Review this attempt."
|
|
71
|
+
) from error
|
|
72
|
+
|
|
73
|
+
def save(self, value):
|
|
74
|
+
data = (json.dumps(value, sort_keys=True, indent=2, allow_nan=False) + "\n").encode("utf-8")
|
|
75
|
+
try:
|
|
76
|
+
capsule._atomic_bytes(self.fd, "result.json", self._bytes(data))
|
|
77
|
+
except (OSError, capsule.CapsuleError) as error:
|
|
78
|
+
raise MinimizationError("The draft result could not be saved. Review this attempt.") from error
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@contextmanager
|
|
82
|
+
def _draft_directory(root, name):
|
|
83
|
+
folder = root / ".mobster" / "drafts" / "repro" / name
|
|
84
|
+
with ExitStack() as stack:
|
|
85
|
+
descriptors = []
|
|
86
|
+
root_chain = []
|
|
87
|
+
|
|
88
|
+
def opened(path, parent=None, create=False):
|
|
89
|
+
if create:
|
|
90
|
+
try:
|
|
91
|
+
os.mkdir(path, 0o700, dir_fd=parent)
|
|
92
|
+
except FileExistsError:
|
|
93
|
+
pass
|
|
94
|
+
fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent)
|
|
95
|
+
stack.callback(os.close, fd)
|
|
96
|
+
return fd
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
root_fd = opened("/")
|
|
100
|
+
root_chain.append(("/", None, root_fd))
|
|
101
|
+
for part in root.parts[1:]:
|
|
102
|
+
child = opened(part, root_fd)
|
|
103
|
+
root_chain.append((part, root_fd, child))
|
|
104
|
+
root_fd = child
|
|
105
|
+
descriptors.append(root_fd)
|
|
106
|
+
mobster_fd = opened(".mobster", root_fd)
|
|
107
|
+
descriptors.append(mobster_fd)
|
|
108
|
+
drafts_fd = opened("drafts", mobster_fd, create=True)
|
|
109
|
+
descriptors.append(drafts_fd)
|
|
110
|
+
repro_fd = opened("repro", drafts_fd, create=True)
|
|
111
|
+
descriptors.append(repro_fd)
|
|
112
|
+
try:
|
|
113
|
+
os.mkdir(name, 0o700, dir_fd=repro_fd)
|
|
114
|
+
except FileExistsError as error:
|
|
115
|
+
raise MinimizationError("This reduction already started. Review its draft and suite results before "
|
|
116
|
+
"a new attempt.") from error
|
|
117
|
+
descriptors.append(opened(name, repro_fd))
|
|
118
|
+
for fd in descriptors[:-1]:
|
|
119
|
+
os.fsync(fd)
|
|
120
|
+
except OSError as error:
|
|
121
|
+
raise MinimizationError("The draft output directory must be a physical project directory.") from error
|
|
122
|
+
draft = _Draft(root, folder, root_chain, descriptors)
|
|
123
|
+
if not draft.attached():
|
|
124
|
+
raise MinimizationError("The draft directory changed before the experiment started.")
|
|
125
|
+
yield draft
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _selected(doc, case_ref, target_ref):
|
|
129
|
+
found = [(check, cell) for check in doc.get("checks", ()) if isinstance(check, dict)
|
|
130
|
+
for cell in (check.get("results") if isinstance(check.get("results"), list) else ())
|
|
131
|
+
if isinstance(cell, dict)
|
|
132
|
+
and comparison._ref(check.get("caseId") or check.get("file") or "") == case_ref
|
|
133
|
+
and comparison._ref(cell.get("deviceKey") or "") == target_ref]
|
|
134
|
+
if len(found) != 1:
|
|
135
|
+
raise MinimizationError("Select one exact case and simulator from each saved suite.")
|
|
136
|
+
return found[0]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _app(root, supplied, expected):
|
|
140
|
+
try:
|
|
141
|
+
path = Path(supplied).expanduser()
|
|
142
|
+
path = (path if path.is_absolute() else root / path).resolve(strict=True)
|
|
143
|
+
path.relative_to(root)
|
|
144
|
+
if path.suffix != ".app" or not path.is_dir() or app_digest(path) != expected:
|
|
145
|
+
raise ValueError("app digest mismatch")
|
|
146
|
+
except (OSError, TypeError, ValueError) as error:
|
|
147
|
+
raise MinimizationError("Keep both exact saved .app builds under this project and pass their paths.") from error
|
|
148
|
+
return path
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _source(results_root, bad_suite_id, control_suite_id, case_ref, target_ref,
|
|
152
|
+
bad_app, control_app, isolated, max_attempts):
|
|
153
|
+
from .. import __version__
|
|
154
|
+
if isolated is not True:
|
|
155
|
+
raise MinimizationError("Pass --isolated only after resetting the external fixture yourself.")
|
|
156
|
+
if type(max_attempts) is not int or not 5 <= max_attempts <= MAX_ATTEMPTS:
|
|
157
|
+
raise MinimizationError("--max-attempts must be between 5 and 8 total suite runs.")
|
|
158
|
+
if not isinstance(case_ref, str) or not REF.fullmatch(case_ref) or \
|
|
159
|
+
not isinstance(target_ref, str) or not REF.fullmatch(target_ref):
|
|
160
|
+
raise MinimizationError("Choose the exact caseRef and targetRef from a comparison report.")
|
|
161
|
+
try:
|
|
162
|
+
bad = comparison._read(results_root, bad_suite_id)
|
|
163
|
+
control = comparison._read(results_root, control_suite_id)
|
|
164
|
+
except comparison.ComparisonError as error:
|
|
165
|
+
raise MinimizationError(str(error)) from error
|
|
166
|
+
paired = comparison.compare(bad, control)
|
|
167
|
+
selected = [row for row in paired["cases"] if row["caseRef"] == case_ref and row["targetRef"] == target_ref]
|
|
168
|
+
if len(selected) != 1 or not selected[0]["verifiedFix"] or \
|
|
169
|
+
selected[0]["proof"]["scope"] != "controlled_app_state":
|
|
170
|
+
raise MinimizationError("This exact bad/control case needs a controlled app-state verified comparison.")
|
|
171
|
+
if any(len(doc["checks"]) != 1 or len(doc["checks"][0]["results"]) != 1
|
|
172
|
+
for doc in (bad, control)):
|
|
173
|
+
raise MinimizationError("Use paired suites with one check on one simulator for this reduction.")
|
|
174
|
+
policy = comparison._policy(bad)
|
|
175
|
+
if (policy is None or policy["replay"] != "fresh" or policy["keyless"] or policy["retries"] != 0 or
|
|
176
|
+
policy["repeat"] != 1 or policy["mobster"] != __version__):
|
|
177
|
+
raise MinimizationError("Use paired single-attempt fresh Smart suites from this Mobster version.")
|
|
178
|
+
check_row, cell = _selected(bad, case_ref, target_ref)
|
|
179
|
+
control_row, control_cell = _selected(control, case_ref, target_ref)
|
|
180
|
+
proof = comparison._business_fields(check_row, cell)
|
|
181
|
+
env = comparison._environment(cell)
|
|
182
|
+
if (cell.get("status") != "failed" or comparison._reason(cell) != "business" or
|
|
183
|
+
not proof or not proof[1] or not env or env["kind"] != "simulator" or
|
|
184
|
+
cell.get("deviceKey") != f"sim:{env['model']}@{env['runtime']}" or
|
|
185
|
+
not comparison._digest(cell.get("appDigest")) or
|
|
186
|
+
cell["runs"][0].get("class") != "business" or cell.get("flaky") is not False or
|
|
187
|
+
cell.get("quarantined") is not False or cell.get("replayed") is not False or
|
|
188
|
+
cell.get("healed") != [] or cell.get("safetyStop") or
|
|
189
|
+
not comparison._supporting_evidence(cell, check_row.get("visualBaselineSha256") is not None) or
|
|
190
|
+
check_row.get("provenanceDrift") or not comparison._digest(check_row.get("checkSpecDigest"))):
|
|
191
|
+
raise MinimizationError("The source needs one clean, registered app-state assertion failure.")
|
|
192
|
+
relative = check_row.get("file")
|
|
193
|
+
results_root = Path(results_root)
|
|
194
|
+
if results_root.name != "test-results" or results_root.parent.name != ".mobster" or results_root.is_symlink():
|
|
195
|
+
raise MinimizationError("Choose this project's physical .mobster/test-results directory.")
|
|
196
|
+
root = results_root.resolve().parent.parent
|
|
197
|
+
checks = root / ".mobster" / "checks"
|
|
198
|
+
if not isinstance(relative, str) or Path(relative).is_absolute() or ".." in Path(relative).parts:
|
|
199
|
+
raise MinimizationError("The saved check path is not a project check.")
|
|
200
|
+
path = root / relative
|
|
201
|
+
options = bad.get("options")
|
|
202
|
+
control_options = control.get("options")
|
|
203
|
+
if (not isinstance(options, dict) or type(options.get("strict")) is not bool or
|
|
204
|
+
not isinstance(control_options, dict) or control_options.get("strict") is not options["strict"]):
|
|
205
|
+
raise MinimizationError("The paired suites need the same frozen action policy.")
|
|
206
|
+
source_options = Options(paths=(str(path),), sims=(f"{env['model']}@{env['runtime']}",),
|
|
207
|
+
retries=0, fresh=True, strict=options["strict"],
|
|
208
|
+
assert_timeout=policy["assertTimeout"])
|
|
209
|
+
try:
|
|
210
|
+
if checks.resolve(strict=True) != checks or path.resolve(strict=True) != path:
|
|
211
|
+
raise ValueError("source check is linked")
|
|
212
|
+
path.relative_to(checks)
|
|
213
|
+
source_plan = plan(source_options, cwd=root)
|
|
214
|
+
if source_plan.root != root or len(source_plan.entries) != 1 or len(source_plan.targets) != 1:
|
|
215
|
+
raise ValueError("unexpected source plan")
|
|
216
|
+
source_entry = source_plan.entries[0]
|
|
217
|
+
check = source_entry.check
|
|
218
|
+
if (check is None or source_entry.quarantined is not None or source_entry.file != relative or
|
|
219
|
+
source_entry.path.resolve() != path.resolve()):
|
|
220
|
+
raise ValueError("source refused")
|
|
221
|
+
frozen = case_provenance(source_entry, root)
|
|
222
|
+
if frozen["sourceDigest"] is None:
|
|
223
|
+
raise ValueError("source check unreadable")
|
|
224
|
+
except (OSError, ValueError, CheckError, UsageError) as error:
|
|
225
|
+
raise MinimizationError("The saved check or registered fixture is unavailable.") from error
|
|
226
|
+
identity = ("checkSpecDigest", "baselineDigest", "visualBaselineSha256", "fixtureDigest",
|
|
227
|
+
"businessBindingSha256")
|
|
228
|
+
if any(frozen[field] != check_row.get(field) for field in identity):
|
|
229
|
+
raise MinimizationError("The check, baseline or enrolled fixture changed since the saved failure.")
|
|
230
|
+
if (check.business is None or check.reset_level() not in ("data", "reinstall") or
|
|
231
|
+
not check.steps or
|
|
232
|
+
check.max_seconds * max_attempts > MAX_SECONDS or check.max_usd * max_attempts > MAX_USD):
|
|
233
|
+
raise MinimizationError("Use a resettable app check with steps and a bounded run budget.")
|
|
234
|
+
common = {field: frozen[field] for field in identity}
|
|
235
|
+
if common != {field: check_row[field] for field in common} or \
|
|
236
|
+
common != {field: control_row[field] for field in common}:
|
|
237
|
+
raise MinimizationError("The check, baseline or enrolled fixture changed since the paired suites.")
|
|
238
|
+
bad_path = _app(root, bad_app, cell["appDigest"])
|
|
239
|
+
control_path = _app(root, control_app, control_cell["appDigest"])
|
|
240
|
+
if bad_path == control_path:
|
|
241
|
+
raise MinimizationError("The bad and control builds must be distinct preserved app artifacts.")
|
|
242
|
+
if any(replace(check, app_path=str(path)).reset_level() != check.reset_level()
|
|
243
|
+
for path in (bad_path, control_path)):
|
|
244
|
+
raise MinimizationError("Adding a saved app build would change the check's reset policy.")
|
|
245
|
+
return root, check, frozen, env, proof, policy, options, (bad_path, cell["appDigest"]), \
|
|
246
|
+
(control_path, control_cell["appDigest"])
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _outcome(doc, expected, expected_policy, env, fields, failed, arm):
|
|
250
|
+
"""True: expected bad failure or clean control pass; False: known mismatch; None: uncertain effect."""
|
|
251
|
+
if (not isinstance(doc, dict) or doc.get("schema") != "mobster.test/1" or
|
|
252
|
+
not isinstance(doc.get("suite"), str) or not capsule.SUITE_ID.fullmatch(doc["suite"]) or
|
|
253
|
+
type(doc.get("finishedAt")) is not int or doc.get("stopReason") or doc.get("stopped") or
|
|
254
|
+
type(doc.get("exitCode")) is not int or doc["exitCode"] not in (0, 1) or
|
|
255
|
+
comparison._policy(doc) != expected_policy or
|
|
256
|
+
not isinstance(doc.get("checks"), list) or len(doc["checks"]) != 1):
|
|
257
|
+
return None
|
|
258
|
+
row = doc["checks"][0]
|
|
259
|
+
cells = row.get("results") if isinstance(row, dict) else None
|
|
260
|
+
if (not isinstance(cells, list) or len(cells) != 1 or
|
|
261
|
+
any(row.get(field) != expected[field] for field in
|
|
262
|
+
("checkSpecDigest", "fixtureDigest", "businessBindingSha256", "baselineDigest",
|
|
263
|
+
"visualBaselineSha256")) or row.get("provenanceDrift")):
|
|
264
|
+
return None
|
|
265
|
+
cell = cells[0]
|
|
266
|
+
if (not isinstance(cell, dict) or comparison._environment(cell) != env or
|
|
267
|
+
cell.get("deviceKey") != f"sim:{env['model']}@{env['runtime']}" or
|
|
268
|
+
cell.get("appDigest") != expected["appDigest"] or cell.get("status") not in ("failed", "passed") or
|
|
269
|
+
cell.get("flaky") is not False or cell.get("quarantined") is not False or
|
|
270
|
+
cell.get("replayed") is not False or cell.get("healed") != [] or cell.get("safetyStop")):
|
|
271
|
+
return None
|
|
272
|
+
if doc["exitCode"] != (1 if cell["status"] == "failed" else 0):
|
|
273
|
+
return None
|
|
274
|
+
if not comparison._supporting_evidence(cell, expected["visualBaselineSha256"] is not None):
|
|
275
|
+
return None
|
|
276
|
+
proof = comparison._business_fields(row, cell)
|
|
277
|
+
if not proof or proof[0] != fields:
|
|
278
|
+
return None
|
|
279
|
+
if cell["status"] == "failed":
|
|
280
|
+
if comparison._reason(cell) != "business" or cell["runs"][0].get("class") != "business":
|
|
281
|
+
return False
|
|
282
|
+
return arm == "bad" and set(proof[1]) == set(failed)
|
|
283
|
+
if cell["runs"][0].get("class") is not None or cell.get("reason") is not None or proof[1]:
|
|
284
|
+
return None
|
|
285
|
+
return arm == "control"
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _canonical_evaluate(root, path, options, key, stop, frozen):
|
|
289
|
+
planned = plan(options, cwd=root)
|
|
290
|
+
if (planned.root != root or len(planned.entries) != 1 or
|
|
291
|
+
planned.entries[0].path.resolve() != path.resolve() or
|
|
292
|
+
planned.entries[0].quarantined is not None or
|
|
293
|
+
case_provenance(planned.entries[0], root) != frozen):
|
|
294
|
+
raise MinimizationError("The trial check changed before it was admitted. Review the draft before retrying.")
|
|
295
|
+
planned.entries[0].check = replace(planned.entries[0].check, draft_trial=True)
|
|
296
|
+
doc, _ = execute(planned, cwd=root, key=key, stop=stop,
|
|
297
|
+
before_attempt=lambda entry, check, record: require_case_provenance(entry, root, frozen))
|
|
298
|
+
return doc
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def minimize(results_root, bad_suite_id, control_suite_id, *, case_ref, target_ref, bad_app, control_app,
|
|
302
|
+
isolated=False, max_attempts=8, key=None, evaluator=None, stop=None):
|
|
303
|
+
"""Run a finite experiment and save a separate draft; never retry an interrupted experiment."""
|
|
304
|
+
admission = _source(
|
|
305
|
+
results_root, bad_suite_id, control_suite_id, case_ref, target_ref,
|
|
306
|
+
bad_app, control_app, isolated, max_attempts)
|
|
307
|
+
name = f"{bad_suite_id}-{control_suite_id}-{case_ref}-{target_ref}"
|
|
308
|
+
with _draft_directory(admission[0], name) as draft:
|
|
309
|
+
return _run(admission, draft, bad_suite_id, control_suite_id, case_ref, target_ref,
|
|
310
|
+
max_attempts, key, evaluator, stop)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _run(admission, draft, bad_suite_id, control_suite_id, case_ref, target_ref,
|
|
314
|
+
max_attempts, key, evaluator, stop):
|
|
315
|
+
root, check, frozen, env, proof, policy, source_options, bad_build, control_build = admission
|
|
316
|
+
folder = draft.folder
|
|
317
|
+
result = {"schema": SCHEMA, "badSuite": bad_suite_id, "controlSuite": control_suite_id,
|
|
318
|
+
"caseRef": case_ref, "targetRef": target_ref,
|
|
319
|
+
"status": "running", "originalSteps": len(check.steps), "candidateSteps": None,
|
|
320
|
+
"attemptsUsed": 0, "candidateCheck": None, "reason": None, "trials": []}
|
|
321
|
+
def save():
|
|
322
|
+
draft.save(result)
|
|
323
|
+
|
|
324
|
+
save() # Durable intent precedes the first action. An interrupted run is not retried automatically.
|
|
325
|
+
deadline = time.monotonic() + MAX_SECONDS
|
|
326
|
+
stop = stop or threading.Event()
|
|
327
|
+
options = Options(sims=(f"{env['model']}@{env['runtime']}",), retries=0, repeat=1,
|
|
328
|
+
fresh=True, keyless=False, strict=source_options["strict"],
|
|
329
|
+
assert_timeout=policy["assertTimeout"])
|
|
330
|
+
|
|
331
|
+
def trial(steps, arm):
|
|
332
|
+
if stop.is_set():
|
|
333
|
+
result.update(status="stopped", reason="Stopped before the next trial started.")
|
|
334
|
+
save()
|
|
335
|
+
return None
|
|
336
|
+
if result["attemptsUsed"] >= max_attempts or time.monotonic() + check.max_seconds > deadline:
|
|
337
|
+
result.update(status="budget_exhausted", reason="The configured trial budget ended before dispatch.")
|
|
338
|
+
save()
|
|
339
|
+
return None
|
|
340
|
+
source_entry = Entry(Path(check.source), Path(check.source).relative_to(root).as_posix(), check)
|
|
341
|
+
current = case_provenance(source_entry, root)
|
|
342
|
+
if any(current[field] != frozen[field] for field in frozen):
|
|
343
|
+
result.update(status="stopped_unknown", reason="The source check or admitted assets changed.")
|
|
344
|
+
save()
|
|
345
|
+
return None
|
|
346
|
+
app_path, expected_app_digest = bad_build if arm == "bad" else control_build
|
|
347
|
+
try:
|
|
348
|
+
if app_digest(app_path) != expected_app_digest:
|
|
349
|
+
raise ValueError("build changed")
|
|
350
|
+
except (OSError, ValueError):
|
|
351
|
+
result.update(status="stopped_unknown", reason="A preserved app build changed.")
|
|
352
|
+
save()
|
|
353
|
+
return None
|
|
354
|
+
number = result["attemptsUsed"] + 1
|
|
355
|
+
path = folder / f"trial-{number}.yaml"
|
|
356
|
+
trial_file = path.relative_to(root).as_posix()
|
|
357
|
+
intended = replace(check, steps=tuple(steps), app_path=str(app_path), source=str(path),
|
|
358
|
+
a11y_scope=trial_file)
|
|
359
|
+
draft.write_new(path.name, dump_check(intended, project_root=root))
|
|
360
|
+
trial_frozen = case_provenance(Entry(path, trial_file, intended), root)
|
|
361
|
+
if (not comparison._digest(trial_frozen["checkSpecDigest"]) or
|
|
362
|
+
not comparison._digest(trial_frozen["sourceDigest"]) or
|
|
363
|
+
any(trial_frozen[field] != frozen[field] for field in
|
|
364
|
+
("baselineDigest", "visualBaselineSha256", "fixtureDigest", "businessBindingSha256")) or
|
|
365
|
+
trial_frozen["appDigest"] != expected_app_digest):
|
|
366
|
+
result.update(status="stopped_unknown", reason="The draft's admitted assets changed.")
|
|
367
|
+
save()
|
|
368
|
+
return None
|
|
369
|
+
options.paths = (str(path),)
|
|
370
|
+
result["attemptsUsed"] = number
|
|
371
|
+
result["trials"].append({"suite": None, "arm": arm, "steps": len(steps), "outcome": "unknown"})
|
|
372
|
+
save()
|
|
373
|
+
if not draft.attached():
|
|
374
|
+
raise MinimizationError("The draft directory changed before trial admission. Review this attempt.")
|
|
375
|
+
doc = evaluator(path, options) if evaluator else _canonical_evaluate(
|
|
376
|
+
root, path, options, key, stop, trial_frozen)
|
|
377
|
+
outcome = _outcome(doc, trial_frozen, policy, env, proof[0], proof[1], arm)
|
|
378
|
+
snapshot = doc["checks"][0]["results"][0]["business"]["sourceSha256"] if outcome is not None else None
|
|
379
|
+
result["trials"][-1].update({"suite": doc.get("suite") if isinstance(doc, dict) else None,
|
|
380
|
+
"snapshotSha256": snapshot,
|
|
381
|
+
"outcome": (("same_failure" if arm == "bad" else "clean_pass")
|
|
382
|
+
if outcome is True else
|
|
383
|
+
"different" if outcome is False else "unknown")})
|
|
384
|
+
save()
|
|
385
|
+
return outcome
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
bad_original = trial(check.steps, "bad")
|
|
389
|
+
if bad_original is None:
|
|
390
|
+
if result["status"] == "running":
|
|
391
|
+
result.update(status="stopped_unknown", reason="The original bad run has an unknown outcome.")
|
|
392
|
+
elif bad_original is False:
|
|
393
|
+
result.update(status="original_not_reproduced", reason="The saved bad failure did not recur.")
|
|
394
|
+
else:
|
|
395
|
+
control_original = trial(check.steps, "control")
|
|
396
|
+
if control_original is None:
|
|
397
|
+
if result["status"] == "running":
|
|
398
|
+
result.update(status="stopped_unknown", reason="The original control run has an unknown outcome.")
|
|
399
|
+
elif control_original is False:
|
|
400
|
+
result.update(status="control_not_reproduced", reason="The saved clean control did not pass.")
|
|
401
|
+
elif result["trials"][-2]["snapshotSha256"] == result["trials"][-1]["snapshotSha256"]:
|
|
402
|
+
result.update(status="stopped_unknown", reason="Bad and control reported identical app-state bytes.")
|
|
403
|
+
else:
|
|
404
|
+
steps = list(check.steps)
|
|
405
|
+
index = 0
|
|
406
|
+
while index < len(steps) and result["attemptsUsed"] + 3 <= max_attempts:
|
|
407
|
+
smaller = steps[:index] + steps[index + 1:]
|
|
408
|
+
bad_first = trial(smaller, "bad")
|
|
409
|
+
if bad_first is None:
|
|
410
|
+
if result["status"] == "running":
|
|
411
|
+
result.update(status="stopped_unknown", reason="A bad trial has an unknown outcome.")
|
|
412
|
+
break
|
|
413
|
+
if bad_first is False:
|
|
414
|
+
index += 1
|
|
415
|
+
continue
|
|
416
|
+
clean = trial(smaller, "control")
|
|
417
|
+
if clean is None:
|
|
418
|
+
if result["status"] == "running":
|
|
419
|
+
result.update(status="stopped_unknown", reason="A control trial has an unknown outcome.")
|
|
420
|
+
break
|
|
421
|
+
if clean is False:
|
|
422
|
+
index += 1
|
|
423
|
+
continue
|
|
424
|
+
bad_second = trial(smaller, "bad")
|
|
425
|
+
if bad_second is None:
|
|
426
|
+
if result["status"] == "running":
|
|
427
|
+
result.update(status="stopped_unknown", reason="A confirming bad trial is unknown.")
|
|
428
|
+
break
|
|
429
|
+
if result["trials"][-2]["snapshotSha256"] in (
|
|
430
|
+
result["trials"][-3]["snapshotSha256"], result["trials"][-1]["snapshotSha256"]):
|
|
431
|
+
result.update(
|
|
432
|
+
status="stopped_unknown",
|
|
433
|
+
reason="Bad and control reported identical app-state bytes.",
|
|
434
|
+
)
|
|
435
|
+
break
|
|
436
|
+
if bad_second:
|
|
437
|
+
steps = smaller
|
|
438
|
+
else:
|
|
439
|
+
index += 1
|
|
440
|
+
if result["status"] in ("running", "budget_exhausted"):
|
|
441
|
+
if control_original is True and len(steps) < len(check.steps):
|
|
442
|
+
source_entry = Entry(Path(check.source), Path(check.source).relative_to(root).as_posix(), check)
|
|
443
|
+
current = case_provenance(source_entry, root)
|
|
444
|
+
if any(current[field] != frozen[field] for field in frozen):
|
|
445
|
+
result.update(status="stopped_unknown", reason="The source check or admitted assets changed.")
|
|
446
|
+
save()
|
|
447
|
+
return result
|
|
448
|
+
try:
|
|
449
|
+
builds_stable = all(app_digest(path) == digest
|
|
450
|
+
for path, digest in (bad_build, control_build))
|
|
451
|
+
except (OSError, ValueError):
|
|
452
|
+
builds_stable = False
|
|
453
|
+
if not builds_stable:
|
|
454
|
+
result.update(status="stopped_unknown", reason="A preserved app build changed.")
|
|
455
|
+
save()
|
|
456
|
+
return result
|
|
457
|
+
candidate = folder / "candidate.yaml"
|
|
458
|
+
draft.write_new(candidate.name, dump_check(replace(check, steps=tuple(steps),
|
|
459
|
+
app_path=str(bad_build[0])), project_root=root))
|
|
460
|
+
result.update(status="smaller_reproducer", candidateSteps=len(steps),
|
|
461
|
+
candidateCheck=candidate.relative_to(root).as_posix(),
|
|
462
|
+
reason=("The configured budget ended after this confirmed reduction."
|
|
463
|
+
if result["status"] == "budget_exhausted" else None))
|
|
464
|
+
elif control_original is True and result["status"] == "running":
|
|
465
|
+
result.update(status="no_smaller_reproducer",
|
|
466
|
+
reason="No tested deletion kept the bad failure and clean control within the budget.")
|
|
467
|
+
except BaseException:
|
|
468
|
+
# The result remains 'running' with the in-flight trial marked unknown for manual reconciliation.
|
|
469
|
+
raise
|
|
470
|
+
save()
|
|
471
|
+
return result
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Run one bounded local reduction from an exact saved failure."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..verify.checks import project_root
|
|
8
|
+
from .minimize import MinimizationError, minimize
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run(args, operands, *, out=None, err=None, reduce=minimize):
|
|
12
|
+
out, err = out or sys.stdout, err or sys.stderr
|
|
13
|
+
|
|
14
|
+
def refuse(message):
|
|
15
|
+
if getattr(args, "json", False):
|
|
16
|
+
print(json.dumps({"ok": False, "error": message}), file=out)
|
|
17
|
+
else:
|
|
18
|
+
print(f"mobster test minimize: {message}", file=err)
|
|
19
|
+
return 3
|
|
20
|
+
|
|
21
|
+
if len(operands) != 4:
|
|
22
|
+
return refuse("name exact bad and control suites, caseRef and targetRef: "
|
|
23
|
+
"mobster test minimize BAD_SUITE_ID CONTROL_SUITE_ID CASE_REF TARGET_REF "
|
|
24
|
+
"--bad-app PATH --control-app PATH --isolated")
|
|
25
|
+
if not getattr(args, "minimize_isolated", False):
|
|
26
|
+
return refuse("pass --isolated only after resetting the external fixture yourself")
|
|
27
|
+
if not getattr(args, "minimize_bad_app", None) or not getattr(args, "minimize_control_app", None):
|
|
28
|
+
return refuse("name both exact saved builds with --bad-app and --control-app")
|
|
29
|
+
private = project_root(Path.cwd()) / ".mobster"
|
|
30
|
+
results = private / "test-results"
|
|
31
|
+
if private.is_symlink() or results.is_symlink():
|
|
32
|
+
return refuse("the project's test-results folder must not be a symlink")
|
|
33
|
+
try:
|
|
34
|
+
result = reduce(results, operands[0], operands[1], case_ref=operands[2], target_ref=operands[3],
|
|
35
|
+
bad_app=args.minimize_bad_app, control_app=args.minimize_control_app, isolated=True,
|
|
36
|
+
max_attempts=getattr(args, "minimize_max_attempts", None) or 8)
|
|
37
|
+
except (MinimizationError, OSError) as error:
|
|
38
|
+
return refuse(str(error))
|
|
39
|
+
if getattr(args, "json", False):
|
|
40
|
+
print(json.dumps(result, ensure_ascii=False, allow_nan=False), file=out)
|
|
41
|
+
else:
|
|
42
|
+
print(f"Reduction {result['status'].replace('_', ' ')}: {result['attemptsUsed']} trial suites; "
|
|
43
|
+
f"{result['originalSteps']} original steps.", file=out)
|
|
44
|
+
if result.get("candidateCheck"):
|
|
45
|
+
print(f"Review {result['candidateCheck']} ({result['candidateSteps']} steps).", file=out)
|
|
46
|
+
elif result.get("reason"):
|
|
47
|
+
print(result["reason"], file=out)
|
|
48
|
+
return 130 if result["status"] == "stopped" else 3 if result["status"] == "stopped_unknown" else 0
|