mobster-cli 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
mobile_agent/narrate.py
ADDED
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
"""Agent events as the steps a person reads: shared by the terminal UI and `mobster run` on a terminal.
|
|
2
|
+
|
|
3
|
+
The agent emits one event per boundary it crosses (observation, decision, action
|
|
4
|
+
check, dispatch, settle). A reader wants one line per step: what Mobster did, to
|
|
5
|
+
what, and whether the screen answered. ``Narrator`` folds the event stream into
|
|
6
|
+
those steps without changing or dropping any event; ``--json`` output and the
|
|
7
|
+
journal keep the raw events.
|
|
8
|
+
|
|
9
|
+
The wording follows the Mac app's presentation (dashboard/src/lib/run-presentation.ts)
|
|
10
|
+
so a task reads the same in both. Nothing here imports a UI library.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
# The Mac app's labels (dashboard/src/lib/api.ts statusLabels), so both products agree; a test keeps every shared
|
|
16
|
+
# key equal. The last few are statuses only the terminal shows.
|
|
17
|
+
STATUS_LABELS = {
|
|
18
|
+
"queued": "Queued", "running": "Running", "stopped": "Stopped", "interrupted": "Interrupted",
|
|
19
|
+
"user_condition_met": "Stop condition met", "needs_clarification": "Needs clarification",
|
|
20
|
+
"error": "Failed", "blocked": "Needs attention", "completed_unverified": "Take a look",
|
|
21
|
+
"expected_text_visible": "Expected text visible", "demo_complete": "Demo complete",
|
|
22
|
+
"inconsistent_completion": "Take a look", "completion_not_confirmed": "Take a look",
|
|
23
|
+
"needs_text_helper": "Text entry unavailable", "no_progress": "Needs attention",
|
|
24
|
+
"max_steps": "Step limit reached", "timeout": "Time limit reached", "approval_denied": "Declined",
|
|
25
|
+
"approval_timeout": "Approval expired", "spend_cap": "Spending limit reached",
|
|
26
|
+
# Smart's proven finish: every item of the task's contract was shown done (engine-contract.md).
|
|
27
|
+
"completed": "Done",
|
|
28
|
+
"preview": "Preview", "duplicate_effect_blocked": "Duplicate refused",
|
|
29
|
+
"duplicate_text_blocked": "Duplicate refused",
|
|
30
|
+
"invalid_action": "Stopped before acting", "invalid_target": "Stopped before acting",
|
|
31
|
+
"expected_text_missing": "Expected text missing",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
STATUS_TONES = {
|
|
35
|
+
"completed": "success", "completed_unverified": "success", "expected_text_visible": "success",
|
|
36
|
+
"user_condition_met": "neutral",
|
|
37
|
+
"preview": "neutral", "stopped": "neutral", "approval_denied": "neutral", "demo_complete": "review",
|
|
38
|
+
"inconsistent_completion": "review", "completion_not_confirmed": "review",
|
|
39
|
+
"error": "error", "interrupted": "error", "approval_timeout": "warning", "spend_cap": "warning",
|
|
40
|
+
"max_steps": "warning", "timeout": "warning", "blocked": "error", "no_progress": "error",
|
|
41
|
+
"needs_text_helper": "error", "invalid_action": "error", "invalid_target": "error",
|
|
42
|
+
"expected_text_missing": "error", "needs_clarification": "error",
|
|
43
|
+
"duplicate_effect_blocked": "neutral", "duplicate_text_blocked": "neutral",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
DONE, FOUND, TAKE_A_LOOK, COULDNT, STOPPED_SAFELY, DECLINED, STOPPED = (
|
|
47
|
+
"Done", "Here's what Mobster found", "Take a look", "Couldn't finish", "Stopped safely", "You declined", "Stopped")
|
|
48
|
+
TAKE_A_LOOK_LINE = "Mobster couldn't confirm all of this on the screen. Look at your iPhone."
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def where_to_look(summary):
|
|
52
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
53
|
+
if summary.get("deviceKind") == "simulator":
|
|
54
|
+
return "the simulator"
|
|
55
|
+
name = " ".join(str(summary.get("deviceName") or "").split())[:60]
|
|
56
|
+
return name or "your iPhone"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def take_a_look_line(summary=None):
|
|
60
|
+
return f"Mobster couldn't confirm all of this on the screen. Look at {where_to_look(summary)}."
|
|
61
|
+
UNPROVEN = {"completed_unverified", "expected_text_visible", "inconsistent_completion", "completion_not_confirmed",
|
|
62
|
+
"demo_complete"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def result_head(status, summary=None):
|
|
66
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
67
|
+
outcome = summary.get("outcome")
|
|
68
|
+
found = summary.get("data") is not None and summary.get("data_status") in (None, "extracted")
|
|
69
|
+
if status == "completed" or (status in UNPROVEN and (outcome == "done" or (outcome is None and
|
|
70
|
+
summary.get("proof")))):
|
|
71
|
+
return (FOUND if found else DONE), "success"
|
|
72
|
+
if status in UNPROVEN or outcome == "check":
|
|
73
|
+
return TAKE_A_LOOK, "review"
|
|
74
|
+
if status == "preview":
|
|
75
|
+
return "Preview", "neutral"
|
|
76
|
+
if status == "approval_denied":
|
|
77
|
+
return DECLINED, "neutral"
|
|
78
|
+
if status in ("stopped", "user_condition_met"):
|
|
79
|
+
return STOPPED, "neutral"
|
|
80
|
+
if status in ("approval_timeout", "spend_cap", "duplicate_effect_blocked", "duplicate_text_blocked",
|
|
81
|
+
"max_steps", "timeout"):
|
|
82
|
+
return STOPPED_SAFELY, "warning" if status in ("spend_cap", "max_steps", "timeout") else "neutral"
|
|
83
|
+
return COULDNT, "error"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def proof_line(summary, apps=None):
|
|
87
|
+
""""Seen on screen: “Dark: On” in Display & Brightness" from the result's first proof, or ""."""
|
|
88
|
+
proof = (summary or {}).get("proof") if isinstance(summary, dict) else None
|
|
89
|
+
if not proof or not isinstance(proof, list) or not isinstance(proof[0], dict):
|
|
90
|
+
return ""
|
|
91
|
+
item = proof[0]
|
|
92
|
+
quote = str(item.get("quote") or "").strip()
|
|
93
|
+
if not quote:
|
|
94
|
+
return ""
|
|
95
|
+
where = str(item.get("screen") or "").strip() or app_name(item.get("app"), apps)
|
|
96
|
+
return f"Seen on screen: “{quote}”" + (f" in {where}" if where else "")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def hero_line(summary):
|
|
100
|
+
"""The result's answer, when there is one: what Mobster found, or what it did in one sentence."""
|
|
101
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
102
|
+
answer = summary.get("answer")
|
|
103
|
+
if isinstance(answer, str) and answer.strip():
|
|
104
|
+
return answer.strip()
|
|
105
|
+
data = summary.get("data")
|
|
106
|
+
if isinstance(data, str) and data.strip():
|
|
107
|
+
return data.strip()
|
|
108
|
+
return ""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cents(nanodollars):
|
|
112
|
+
"""What a task cost, as MESSAGING's facts say it: 2¢, under 1¢, $1.20."""
|
|
113
|
+
dollars = (nanodollars or 0) / 1e9
|
|
114
|
+
if dollars >= 1:
|
|
115
|
+
return f"${dollars:.2f}"
|
|
116
|
+
value = dollars * 100
|
|
117
|
+
if value < 1:
|
|
118
|
+
return "under 1¢"
|
|
119
|
+
return f"{value:.0f}¢"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def facts(actions, elapsed_ms, cost_nanodollars=None, priced=False, plan=False):
|
|
123
|
+
""""3 steps, 6.2 s, 2¢"; ``plan``: every call ran on the person's plan, so no cents: "on your plan"."""
|
|
124
|
+
out = [f"{actions} step{'s' if actions != 1 else ''}"]
|
|
125
|
+
if elapsed_ms:
|
|
126
|
+
out.append(seconds(elapsed_ms))
|
|
127
|
+
if plan and not cost_nanodollars:
|
|
128
|
+
out.append("on your plan")
|
|
129
|
+
elif priced:
|
|
130
|
+
out.append(cents(cost_nanodollars))
|
|
131
|
+
return ", ".join(out)
|
|
132
|
+
|
|
133
|
+
TERMINAL_MESSAGES = {
|
|
134
|
+
"approval_denied": "You declined the action, so Mobster did not take it. Earlier steps may have completed.",
|
|
135
|
+
"approval_timeout": "Nobody answered the approval request in time, so Mobster did not take the action.",
|
|
136
|
+
"stopped": "You stopped this task.",
|
|
137
|
+
"user_condition_met": "Stopped at your requested condition.",
|
|
138
|
+
"needs_clarification": "Mobster needs clarification before taking further action. "
|
|
139
|
+
"Edit the instruction to make the next step clear.",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
ANSWER_FAILURES = {
|
|
143
|
+
"not_extracted": "The requested answer was not produced. The task stopped before result extraction.",
|
|
144
|
+
"no_observed_evidence": "The app did not expose enough evidence to return the requested answer.",
|
|
145
|
+
"insufficient_evidence": "The app did not expose enough evidence to return the requested answer.",
|
|
146
|
+
"unsupported_answer": "The extracted answer could not be verified against the observed app evidence. "
|
|
147
|
+
"No answer was returned.",
|
|
148
|
+
"verification_timeout": "Answer verification timed out. No verified answer was returned.",
|
|
149
|
+
"verification_failed": "Answer verification could not be completed. No verified answer was returned.",
|
|
150
|
+
"extraction_failed": "Result extraction failed. The requested answer was not produced.",
|
|
151
|
+
"extraction_timeout": "Result extraction timed out. The requested answer was not produced.",
|
|
152
|
+
"helper_unavailable": "No helper model is configured, so the requested answer was not produced.",
|
|
153
|
+
"helper_budget_exhausted": "The helper budget ran out before the requested answer was produced.",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
OPERATIONS = {
|
|
157
|
+
"TAP": ("Tap", "Tapped"), "DOUBLE_TAP": ("Double-tap", "Double-tapped"),
|
|
158
|
+
"LONG_PRESS": ("Press and hold", "Pressed and held"),
|
|
159
|
+
"TYPE": ("Type into", "Typed into"), "TYPE_SUBMIT": ("Type and submit in", "Typed and submitted in"),
|
|
160
|
+
"SUBMIT": ("Submit", "Submitted"),
|
|
161
|
+
# A swipe up moves the content up: the reader sees the page scroll down.
|
|
162
|
+
"SWIPE_UP": ("Scroll down", "Scrolled down"), "SWIPE_DOWN": ("Scroll up", "Scrolled up"),
|
|
163
|
+
"SWIPE_LEFT": ("Swipe left", "Swiped left"), "SWIPE_RIGHT": ("Swipe right", "Swiped right"),
|
|
164
|
+
"BACK": ("Go back", "Went back"), "HOME": ("Go home", "Went home"),
|
|
165
|
+
"LAUNCH_APP": ("Open", "Opened"), "OPEN_URL": ("Open the address", "Opened the address"),
|
|
166
|
+
"INCREMENT": ("Increase", "Increased"), "DECREMENT": ("Decrease", "Decreased"),
|
|
167
|
+
"VOLUME_UP": ("Volume up", "Pressed volume up"), "VOLUME_DOWN": ("Volume down", "Pressed volume down"),
|
|
168
|
+
"WAIT": ("Waiting for the screen", "Waited for the screen"),
|
|
169
|
+
"DONE": ("Check the task is done", "Checked the task is done"),
|
|
170
|
+
# A task as one of your profiles (accounts/screen.py); a run journaled before the rename names CHECK_ACCOUNT.
|
|
171
|
+
"CHECK_PROFILE": ("Check the profile", "Checked the profile"),
|
|
172
|
+
"CHECK_ACCOUNT": ("Check the profile", "Checked the profile"),
|
|
173
|
+
"BLOCKED": ("Looking for a way forward", "Looking for a way forward"),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Events a reader never needs; the raw stream (--json, the journal) keeps them.
|
|
177
|
+
QUIET = {"run_started", "latency_trace", "inference_started", "inference_finished", "stop_requested",
|
|
178
|
+
"decision_memo_hit", "decision_predicted", "decision_first_screen_predicted", "decision_speculation_used",
|
|
179
|
+
"decision_speculation_discarded", "decision_input_tightened", "refresh_skipped_fresh",
|
|
180
|
+
"refresh_skipped_pixels_still", "refresh_target_stable", "completion_reread_skipped_pixels_still",
|
|
181
|
+
"answer_signals", "stale_decision", "text_selected", "result"}
|
|
182
|
+
|
|
183
|
+
NOTES = {
|
|
184
|
+
"unclear_bypassed": ("warning", "Bypass: acted although the action check was unclear"),
|
|
185
|
+
"unclear_put_to_user": ("neutral", "The action check was unclear, so you are asked"),
|
|
186
|
+
"effect_duplicate_refused": ("warning", "Refused to repeat an action that already took effect"),
|
|
187
|
+
"duplicate_text_refused": ("warning", "Refused to type the same text twice"),
|
|
188
|
+
"completion_continued": ("neutral", "Not finished yet: continuing"),
|
|
189
|
+
"demotion_lifted": ("neutral", "Allowed a held-back action"),
|
|
190
|
+
"replay_abandoned": ("neutral", "The screen changed: stopped replaying a recorded run"),
|
|
191
|
+
"plan_compiled": ("neutral", "Planned the steps across apps"),
|
|
192
|
+
"loop_compiled": ("neutral", "Compiled the repeated action into a loop"),
|
|
193
|
+
"milestones_compiled": ("neutral", "Split the request into milestones"),
|
|
194
|
+
"page_probe_answered": ("neutral", "Found the answer in the page text"),
|
|
195
|
+
"visual_answer": ("neutral", "Answered from the screen image"),
|
|
196
|
+
"completion_by_route": ("neutral", "Reached the requested page"),
|
|
197
|
+
"route_decision": ("neutral", "Followed a known route"),
|
|
198
|
+
"helper": ("neutral", "Asked the helper model for another route"),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# The phone guard's and the skills' events: one plain line each, never a digit of a passcode or a code.
|
|
203
|
+
GUARD_NOTES = {
|
|
204
|
+
("unlock_finished", True): ("neutral", "Unlocked the iPhone"),
|
|
205
|
+
("unlock_finished", False): ("warning", "The saved passcode didn't unlock the iPhone"),
|
|
206
|
+
("handoff_waiting", "face_id"): ("neutral", "Waiting for you: approve Face ID on your iPhone"),
|
|
207
|
+
("handoff_waiting", "apple_confirmation"): ("neutral", "Waiting for you: confirm on your iPhone"),
|
|
208
|
+
# Look at your iPhone: a task that started on a locked iPhone waits for the unlock.
|
|
209
|
+
("handoff_waiting", "lock_screen"): ("warning", "Look at your iPhone: unlock it and Mobster starts"),
|
|
210
|
+
("handoff_waiting", "passcode_keypad"): ("warning", "Look at your iPhone: unlock it and Mobster starts"),
|
|
211
|
+
("handoff_finished", True): ("neutral", "Done on your iPhone: continuing"),
|
|
212
|
+
("handoff_finished", False): ("warning", "Nothing was confirmed on your iPhone"),
|
|
213
|
+
("handoff_finished", "lock_screen", True): ("neutral", "Your iPhone is unlocked: starting"),
|
|
214
|
+
("handoff_finished", "passcode_keypad", True): ("neutral", "Your iPhone is unlocked: starting"),
|
|
215
|
+
("handoff_finished", "lock_screen", False): None, # the run's own stop says it ("Still locked, so …")
|
|
216
|
+
("handoff_finished", "passcode_keypad", False): None,
|
|
217
|
+
# The phone locked in the middle of a task a person started: it waits for them, then carries on from there.
|
|
218
|
+
("handoff_waiting", "lock_screen", "midrun"): ("warning", "Your iPhone locked: unlock it and Mobster carries on"),
|
|
219
|
+
("handoff_waiting", "passcode_keypad", "midrun"): ("warning",
|
|
220
|
+
"Your iPhone locked: unlock it and Mobster carries on"),
|
|
221
|
+
("handoff_finished", "lock_screen", True, "midrun"): ("neutral", "Your iPhone is unlocked: carrying on"),
|
|
222
|
+
("handoff_finished", "passcode_keypad", True, "midrun"): ("neutral", "Your iPhone is unlocked: carrying on"),
|
|
223
|
+
# The cable came out, or the Wi-Fi link went down, under a task a person started: it waits for the phone to return.
|
|
224
|
+
("handoff_waiting", "unplugged"): ("warning", "Your iPhone isn't plugged in: plug it in and Mobster starts"),
|
|
225
|
+
("handoff_waiting", "unplugged", "midrun"): ("warning", "Your iPhone was unplugged: plug it back in and Mobster "
|
|
226
|
+
"carries on"),
|
|
227
|
+
("handoff_waiting", "wifi_lost"): ("warning", "Your iPhone is out of reach over Wi-Fi: bring it back or plug it "
|
|
228
|
+
"in and Mobster starts"),
|
|
229
|
+
("handoff_waiting", "wifi_lost", "midrun"): ("warning", "Your iPhone went out of reach over Wi-Fi: bring it back "
|
|
230
|
+
"or plug it in and Mobster carries on"),
|
|
231
|
+
("handoff_finished", "unplugged", True): ("neutral", "Your iPhone is back: starting"),
|
|
232
|
+
("handoff_finished", "unplugged", True, "midrun"): ("neutral", "Your iPhone is back: carrying on"),
|
|
233
|
+
("handoff_finished", "wifi_lost", True): ("neutral", "Your iPhone is back: starting"),
|
|
234
|
+
("handoff_finished", "wifi_lost", True, "midrun"): ("neutral", "Your iPhone is back: carrying on"),
|
|
235
|
+
("handoff_finished", "unplugged", False): None,
|
|
236
|
+
("handoff_finished", "wifi_lost", False): None,
|
|
237
|
+
("skill_finished", "USE_CODE"): ("neutral", "Entered the verification code"),
|
|
238
|
+
# USE_CODE looked before the code arrived (skills/codes.await_code): it watches for it,
|
|
239
|
+
# then goes on or says why not.
|
|
240
|
+
("code_waiting", None): ("neutral", "Waiting for the sign-in code to arrive"),
|
|
241
|
+
("code_wait_finished", True): ("neutral", "The sign-in code arrived"),
|
|
242
|
+
("skill_finished", "READ_NOTIFICATIONS"): ("neutral", "Read the notifications"),
|
|
243
|
+
("frontier_ax_only", None): ("warning", "The iPhone's screenshots are timing out: reading its screen without them "
|
|
244
|
+
"for a few steps"),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def guard_note(event):
|
|
249
|
+
"""(tone, text) for a phone-guard or skill event, or None."""
|
|
250
|
+
kind = event.get("event")
|
|
251
|
+
if kind == "unlock_finished":
|
|
252
|
+
return GUARD_NOTES[(kind, event.get("ok") is True)]
|
|
253
|
+
midrun = event.get("midrun") is True
|
|
254
|
+
if kind == "handoff_waiting":
|
|
255
|
+
if midrun and (kind, event.get("kind"), "midrun") in GUARD_NOTES:
|
|
256
|
+
return GUARD_NOTES[(kind, event.get("kind"), "midrun")]
|
|
257
|
+
return GUARD_NOTES.get((kind, event.get("kind")))
|
|
258
|
+
if kind == "handoff_finished":
|
|
259
|
+
ok = event.get("ok") is True
|
|
260
|
+
if midrun and (kind, event.get("kind"), ok, "midrun") in GUARD_NOTES:
|
|
261
|
+
return GUARD_NOTES[(kind, event.get("kind"), ok, "midrun")]
|
|
262
|
+
if (kind, event.get("kind"), ok) in GUARD_NOTES:
|
|
263
|
+
return GUARD_NOTES[(kind, event.get("kind"), ok)]
|
|
264
|
+
return GUARD_NOTES[(kind, ok)]
|
|
265
|
+
if kind == "skill_finished" and event.get("ok") is True:
|
|
266
|
+
return GUARD_NOTES.get((kind, event.get("op")))
|
|
267
|
+
if kind == "frontier_ax_only":
|
|
268
|
+
return GUARD_NOTES[(kind, None)]
|
|
269
|
+
if kind == "code_waiting":
|
|
270
|
+
return GUARD_NOTES[(kind, None)]
|
|
271
|
+
if kind == "code_wait_finished":
|
|
272
|
+
return GUARD_NOTES.get((kind, event.get("ok") is True))
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
ONCE = {"route_decision", "completion_continued", "replay_abandoned", "helper"}
|
|
277
|
+
STALLS = {"WAIT", "BLOCKED"}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def status_label(status):
|
|
281
|
+
return STATUS_LABELS.get(status) or str(status or "").replace("_", " ").capitalize()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def status_tone(status):
|
|
285
|
+
return STATUS_TONES.get(status, "neutral")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
STATUS_GLYPHS = {"success": "✓", "error": "✗", "warning": "!", "review": "◆"}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def status_glyph(tone, default="■"):
|
|
292
|
+
return STATUS_GLYPHS.get(tone, default)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def completion_message(status, reason="", data_status=None, summary=None):
|
|
296
|
+
"""The sentence under a finished run's status (dashboard completionMessage)."""
|
|
297
|
+
reason = reason if isinstance(reason, str) else ""
|
|
298
|
+
if status == "needs_clarification" and "exact effect could not be established" in reason:
|
|
299
|
+
return ("The proposed action could not be checked safely and was not dispatched. "
|
|
300
|
+
"Earlier actions may have completed.")
|
|
301
|
+
if status in TERMINAL_MESSAGES:
|
|
302
|
+
return TERMINAL_MESSAGES[status]
|
|
303
|
+
if status in {"inconsistent_completion", "completion_not_confirmed"}:
|
|
304
|
+
return (ANSWER_FAILURES.get(data_status)
|
|
305
|
+
or "Mobster could not confidently confirm completion. Review the final screen.")
|
|
306
|
+
if status in {"blocked", "no_progress"} and (not reason or reason == "No supported progress"):
|
|
307
|
+
return "Nothing on the screen moved the task forward, so Mobster stopped without acting further. " \
|
|
308
|
+
"Look at the phone, or add detail to the task."
|
|
309
|
+
if status in {"completed_unverified", "expected_text_visible", "demo_complete"} and (
|
|
310
|
+
not reason or reason.startswith(("Model agreement only", "Text assertion passed"))):
|
|
311
|
+
# The head (Done with its proof, or Take a look) already says it; the agent's internal wording stays in
|
|
312
|
+
# the raw events.
|
|
313
|
+
return take_a_look_line(summary) if status != "expected_text_visible" else ""
|
|
314
|
+
return reason
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def operation_words(operation):
|
|
318
|
+
"""(in progress, done) phrases for an operation."""
|
|
319
|
+
operation = str(operation or "").upper()
|
|
320
|
+
if operation in OPERATIONS:
|
|
321
|
+
return OPERATIONS[operation]
|
|
322
|
+
word = operation.replace("_", " ").capitalize() or "Act"
|
|
323
|
+
return word, word
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# The Mac app's words for the two answers (dashboard approval-prompt.tsx, approvalVerbs), so the terminal names the
|
|
327
|
+
# same buttons the app shows. An act this doesn't know keeps Approve and Decline.
|
|
328
|
+
_SENT, _POSTED = ("Send", "Don't send"), ("Post", "Don't post")
|
|
329
|
+
ACTS = {
|
|
330
|
+
"send_message": _SENT, "send": _SENT, "reply": _SENT, "send message": _SENT,
|
|
331
|
+
"post": _POSTED, "comment": _POSTED, "publish": ("Publish", "Don't publish"), "tweet": _POSTED,
|
|
332
|
+
"pay": ("Pay", "Don't pay"), "transfer": ("Transfer", "Don't transfer"),
|
|
333
|
+
"request_money": ("Request", "Don't request"), "donate": ("Donate", "Don't donate"), "tip": ("Tip", "Don't tip"),
|
|
334
|
+
"order": ("Place order", "Don't order"), "place order": ("Place order", "Don't order"), "buy": ("Buy", "Don't buy"),
|
|
335
|
+
"purchase": ("Buy", "Don't buy"), "checkout": ("Check out", "Don't check out"),
|
|
336
|
+
"book": ("Book", "Don't book"), "reserve": ("Reserve", "Don't reserve"),
|
|
337
|
+
"delete": ("Delete", "Don't delete"), "erase": ("Erase", "Don't erase"), "remove": ("Remove", "Don't remove"),
|
|
338
|
+
"trash": ("Move to trash", "Keep it"), "clear": ("Clear", "Don't clear"),
|
|
339
|
+
"follow": ("Follow", "Don't follow"), "unfollow": ("Unfollow", "Don't unfollow"), "share": ("Share", "Don't share"),
|
|
340
|
+
"call": ("Call", "Don't call"), "dial": ("Call", "Don't call"), "facetime": ("Call", "Don't call"),
|
|
341
|
+
"subscribe": ("Subscribe", "Don't subscribe"), "unsubscribe": ("Unsubscribe", "Don't unsubscribe"),
|
|
342
|
+
"sign up": ("Sign up", "Don't sign up"), "request": ("Send request", "Don't send"),
|
|
343
|
+
"react": ("React", "Don't react"), "use app": ("Allow", "Not now"),
|
|
344
|
+
"add": ("Add", "Don't add"), "save": ("Save", "Don't save"), "archive": ("Archive", "Don't archive"),
|
|
345
|
+
"submit": ("Submit", "Don't submit"), "confirm": ("Confirm", "Don't confirm"), "cancel": ("Cancel it", "Keep it"),
|
|
346
|
+
"accept": ("Accept", "Don't accept"), "join": ("Join", "Don't join"), "leave": ("Leave", "Stay"),
|
|
347
|
+
"block": ("Block", "Don't block"), "report": ("Report", "Don't report"), "install": ("Install", "Don't install"),
|
|
348
|
+
"upload": ("Upload", "Don't upload"),
|
|
349
|
+
}
|
|
350
|
+
ASK = ("Approve", "Decline")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def verbs(approval):
|
|
354
|
+
if approval.get("kind") == "loop":
|
|
355
|
+
return ASK
|
|
356
|
+
if approval.get("kind") == "plan":
|
|
357
|
+
return ("Start", "Skip")
|
|
358
|
+
if approval.get("kind") == "proposals":
|
|
359
|
+
# Yes and no answer the whole batch here: every draft not yet decided is sent, or skipped.
|
|
360
|
+
yes, no = verbs({"act": approval.get("act")}) if approval.get("act") else ASK
|
|
361
|
+
return (f"{yes} all", "Skip all")
|
|
362
|
+
act = approval.get("act")
|
|
363
|
+
if not act and approval.get("operation") == "TAP":
|
|
364
|
+
act = (str(approval.get("label") or "").split() or [""])[0]
|
|
365
|
+
raw = str(act or "").strip().lower()
|
|
366
|
+
key = raw if raw in ACTS else " ".join(raw.replace("_", " ").replace("-", " ").split())
|
|
367
|
+
if key in ACTS:
|
|
368
|
+
return ACTS[key]
|
|
369
|
+
if key == "cancel" or key.startswith("cancel "):
|
|
370
|
+
return ACTS["cancel"]
|
|
371
|
+
if key == "end call":
|
|
372
|
+
return ("End call", "Stay on")
|
|
373
|
+
return ASK
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def approval_title(approval):
|
|
377
|
+
"""What Mobster asks to do, in its own words (the approval's title), else from the control it would tap."""
|
|
378
|
+
if approval.get("act") == "use app" and approval.get("apps"):
|
|
379
|
+
# The wire title is shortened; a terminal still needs to name every app it would allow.
|
|
380
|
+
names = approval["apps"]
|
|
381
|
+
listed = names[0] if len(names) == 1 else ", ".join(names[:-1]) + " and " + names[-1]
|
|
382
|
+
return f"Allow Mobster to use {listed}?"
|
|
383
|
+
if approval.get("title"):
|
|
384
|
+
return str(approval["title"])
|
|
385
|
+
label = approval.get("label") or ""
|
|
386
|
+
title = operation_words(approval.get("operation"))[0] + (f" “{label}”" if label else "")
|
|
387
|
+
app = approval.get("app")
|
|
388
|
+
return f"{title} in {app}" if app and app.casefold() not in title.casefold() else title
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def seconds(ms):
|
|
392
|
+
"""A duration for a reader: 180 ms, 1.4 s, 2 min 5 s."""
|
|
393
|
+
if ms is None:
|
|
394
|
+
return ""
|
|
395
|
+
ms = float(ms)
|
|
396
|
+
if ms < 1000:
|
|
397
|
+
return f"{ms:.0f} ms"
|
|
398
|
+
if ms < 60_000:
|
|
399
|
+
return f"{ms / 1000:.1f} s"
|
|
400
|
+
return f"{int(ms // 60_000)} min {int(ms % 60_000 // 1000)} s"
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def usd(nanodollars):
|
|
404
|
+
"""Spend for a status line: $0.0042, $0.12, $3.40."""
|
|
405
|
+
dollars = (nanodollars or 0) / 1e9
|
|
406
|
+
return f"${dollars:.4f}" if dollars < 0.1 else f"${dollars:.2f}"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def app_name(bundle, apps=None):
|
|
410
|
+
"""An app's display name from the catalog (or ``apps``), else its bundle id."""
|
|
411
|
+
if not bundle:
|
|
412
|
+
return ""
|
|
413
|
+
from .catalog import APPS
|
|
414
|
+
for app in list(apps or ()) + APPS:
|
|
415
|
+
if app.get("bundleId") == bundle:
|
|
416
|
+
return app["name"]
|
|
417
|
+
return bundle
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@dataclass
|
|
421
|
+
class Step:
|
|
422
|
+
"""One observe-decide-verify-act iteration of the agent."""
|
|
423
|
+
key: str
|
|
424
|
+
index: int
|
|
425
|
+
app: str = ""
|
|
426
|
+
elements: int | None = None
|
|
427
|
+
observe_ms: float | None = None
|
|
428
|
+
operation: str | None = None
|
|
429
|
+
target: str = ""
|
|
430
|
+
confidence: float | None = None
|
|
431
|
+
goal_probability: float | None = None
|
|
432
|
+
decide_ms: float | None = None
|
|
433
|
+
model: str = ""
|
|
434
|
+
check: str | None = None
|
|
435
|
+
act: str | None = None
|
|
436
|
+
act_ms: float | None = None
|
|
437
|
+
changed: bool | None = None
|
|
438
|
+
settle_ms: float | None = None
|
|
439
|
+
started_at: float | None = None
|
|
440
|
+
ended_at: float | None = None
|
|
441
|
+
completion: str | None = None
|
|
442
|
+
note: str = "" # why a decided action was never sent
|
|
443
|
+
target_id: str | None = None
|
|
444
|
+
tries: int = 1 # consecutive steps that did not act, collapsed into this one
|
|
445
|
+
merged_into: object = None # set on a step folded into the one before it
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def state(self):
|
|
449
|
+
"""thinking, acting, done, unchanged, failed."""
|
|
450
|
+
if self.act == "not_dispatched":
|
|
451
|
+
return "failed"
|
|
452
|
+
if self.act is None and self.note:
|
|
453
|
+
return "skipped"
|
|
454
|
+
if self.act == "started":
|
|
455
|
+
return "acting"
|
|
456
|
+
if self.act == "acknowledged":
|
|
457
|
+
return "done" if self.changed is not False else "unchanged"
|
|
458
|
+
if self.operation in STALLS:
|
|
459
|
+
return "stalled" # a decision not to act ends its step; the next read starts another
|
|
460
|
+
if self.operation == "DONE" and self.ended_at is not None:
|
|
461
|
+
return "done"
|
|
462
|
+
return "thinking"
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def title(self):
|
|
466
|
+
if not self.operation:
|
|
467
|
+
if self.ended_at is not None:
|
|
468
|
+
return "Read the screen"
|
|
469
|
+
return "Deciding" if self.elements is not None else "Reading the screen"
|
|
470
|
+
present, past = operation_words(self.operation)
|
|
471
|
+
finished = self.operation in {"DONE", "WAIT"} and self.ended_at is not None
|
|
472
|
+
verb = past if self.act == "acknowledged" or finished else present
|
|
473
|
+
if self.operation in {"TAP", "DOUBLE_TAP", "LONG_PRESS", "TYPE", "TYPE_SUBMIT", "SUBMIT",
|
|
474
|
+
"INCREMENT", "DECREMENT"} and self.target:
|
|
475
|
+
return f"{verb} “{self.target}”"
|
|
476
|
+
if self.operation == "LAUNCH_APP" and self.target:
|
|
477
|
+
return f"{verb} {self.target}"
|
|
478
|
+
return verb
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def duration_ms(self):
|
|
482
|
+
if self.started_at is None or self.ended_at is None:
|
|
483
|
+
return None
|
|
484
|
+
return max(0.0, self.ended_at - self.started_at)
|
|
485
|
+
|
|
486
|
+
def phases(self):
|
|
487
|
+
"""[(phase, text)] for observe, decide, verify and act, the ones that happened."""
|
|
488
|
+
rows = []
|
|
489
|
+
if self.elements is not None or self.observe_ms is not None:
|
|
490
|
+
where = f"{self.app}, " if self.app else ""
|
|
491
|
+
count = f"{self.elements} elements" if self.elements is not None else "screen read"
|
|
492
|
+
rows.append(("observe", f"{where}{count}" + (f", {seconds(self.observe_ms)}" if self.observe_ms else "")))
|
|
493
|
+
if self.operation:
|
|
494
|
+
sure = f"{self.confidence:.0%} sure" if isinstance(self.confidence, (int, float)) else ""
|
|
495
|
+
if self.operation == "DONE" and isinstance(self.goal_probability, (int, float)):
|
|
496
|
+
sure = f"goal {self.goal_probability:.0%} likely met"
|
|
497
|
+
parts = [operation_words(self.operation)[0] + (f" “{self.target}”" if self.target else ""), sure,
|
|
498
|
+
seconds(self.decide_ms) if self.decide_ms else ""]
|
|
499
|
+
rows.append(("decide", ", ".join(p for p in parts if p)))
|
|
500
|
+
if self.check:
|
|
501
|
+
rows.append(("verify", self.check))
|
|
502
|
+
if self.act:
|
|
503
|
+
if self.act == "not_dispatched":
|
|
504
|
+
text = "not sent: the screen changed first"
|
|
505
|
+
elif self.act == "started":
|
|
506
|
+
text = "sending…"
|
|
507
|
+
else:
|
|
508
|
+
outcome = {True: "screen changed", False: "no visible change", None: "sent"}[self.changed]
|
|
509
|
+
text = ", ".join(p for p in (outcome, seconds((self.act_ms or 0) + (self.settle_ms or 0))
|
|
510
|
+
if self.act_ms is not None else "") if p)
|
|
511
|
+
rows.append(("act", text))
|
|
512
|
+
elif self.note:
|
|
513
|
+
rows.append(("act", self.note))
|
|
514
|
+
if self.completion:
|
|
515
|
+
rows.append(("check", "done" if self.completion == "confirmed" else "not finished yet"))
|
|
516
|
+
return rows
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@dataclass
|
|
520
|
+
class Launch:
|
|
521
|
+
key: str
|
|
522
|
+
app: str
|
|
523
|
+
done: bool = False
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@dataclass
|
|
527
|
+
class Note:
|
|
528
|
+
key: str
|
|
529
|
+
tone: str
|
|
530
|
+
text: str
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
@dataclass
|
|
534
|
+
class Approval:
|
|
535
|
+
key: str
|
|
536
|
+
approval_id: str
|
|
537
|
+
operation: str
|
|
538
|
+
label: str
|
|
539
|
+
kind: str = "action"
|
|
540
|
+
text_present: bool = False
|
|
541
|
+
choices: tuple = ()
|
|
542
|
+
decision: str | None = None
|
|
543
|
+
request: dict = field(default_factory=dict) # the live request (with any text), never journaled
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def title(self):
|
|
547
|
+
if self.kind != "action":
|
|
548
|
+
return self.request.get("question") or self.label or "Mobster has a question"
|
|
549
|
+
present = operation_words(self.operation)[0]
|
|
550
|
+
return f"{present} “{self.label}”" if self.label else present
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@dataclass
|
|
554
|
+
class Outcome:
|
|
555
|
+
key: str
|
|
556
|
+
status: str
|
|
557
|
+
summary: dict
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def label(self):
|
|
561
|
+
return status_label(self.status)
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def tone(self):
|
|
565
|
+
return status_tone(self.status)
|
|
566
|
+
|
|
567
|
+
@property
|
|
568
|
+
def message(self):
|
|
569
|
+
return completion_message(self.status, self.summary.get("reason"), self.summary.get("data_status"),
|
|
570
|
+
self.summary)
|
|
571
|
+
|
|
572
|
+
@property
|
|
573
|
+
def head(self):
|
|
574
|
+
"""MESSAGING §11's head: Done, Here's what Mobster found, Take a look, Couldn't finish, …"""
|
|
575
|
+
return result_head(self.status, self.summary)[0]
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def head_tone(self):
|
|
579
|
+
return result_head(self.status, self.summary)[1]
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def hero(self):
|
|
583
|
+
return hero_line(self.summary) if self.head in (DONE, FOUND, TAKE_A_LOOK) else ""
|
|
584
|
+
|
|
585
|
+
@property
|
|
586
|
+
def proof(self):
|
|
587
|
+
return proof_line(self.summary) if self.head in (DONE, FOUND) else ""
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def detail(self):
|
|
591
|
+
head = self.head
|
|
592
|
+
if head in (DONE, FOUND):
|
|
593
|
+
return ""
|
|
594
|
+
if head == TAKE_A_LOOK:
|
|
595
|
+
return take_a_look_line(self.summary)
|
|
596
|
+
return self.message
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
class Narrator:
|
|
600
|
+
"""Folds one run's events into items, in order; a target resolves against the latest earlier observation only."""
|
|
601
|
+
|
|
602
|
+
def __init__(self, apps=None):
|
|
603
|
+
self.apps = apps
|
|
604
|
+
self.items = []
|
|
605
|
+
self.by_key = {}
|
|
606
|
+
self.labels = {}
|
|
607
|
+
self.app = ""
|
|
608
|
+
self.step = None
|
|
609
|
+
self.cost_nanodollars = 0
|
|
610
|
+
self.priced_calls = 0
|
|
611
|
+
self.unpriced_calls = 0
|
|
612
|
+
self.plan_calls = 0
|
|
613
|
+
self.decisions = 0
|
|
614
|
+
self.actions = 0
|
|
615
|
+
self.started_at = None
|
|
616
|
+
self.finished_at = None
|
|
617
|
+
self.status = "running"
|
|
618
|
+
self.noted = set()
|
|
619
|
+
# The latest screen Mobster read: {"app", "bundle", "elements", "keyboard"} (elements as in events).
|
|
620
|
+
self.screen = None
|
|
621
|
+
|
|
622
|
+
def _add(self, item):
|
|
623
|
+
self.items.append(item)
|
|
624
|
+
self.by_key[item.key] = item
|
|
625
|
+
return item
|
|
626
|
+
|
|
627
|
+
def _close_unsent(self, step, note):
|
|
628
|
+
"""A step whose decided action was never sent: say why instead of spinning forever."""
|
|
629
|
+
if step is not None and step.act is None and step.operation not in {None, "DONE", "WAIT", "BLOCKED"}:
|
|
630
|
+
step.note = step.note or note
|
|
631
|
+
|
|
632
|
+
def _merge_stall(self, step):
|
|
633
|
+
"""Fold a step that did not act into the stalled step right before it. Returns that step, or None."""
|
|
634
|
+
previous = next((item for item in reversed(self.items) if isinstance(item, Step) and item is not step), None)
|
|
635
|
+
between = self.items[self.items.index(previous) + 1:self.items.index(step)] if previous else []
|
|
636
|
+
if (previous is None or previous.operation not in STALLS or previous.act is not None
|
|
637
|
+
or any(not isinstance(item, Note) for item in between)):
|
|
638
|
+
return None
|
|
639
|
+
previous.tries += 1
|
|
640
|
+
for name in ("operation", "confidence", "goal_probability", "decide_ms", "observe_ms", "elements", "app",
|
|
641
|
+
"ended_at", "target_id"):
|
|
642
|
+
setattr(previous, name, getattr(step, name))
|
|
643
|
+
step.merged_into = previous
|
|
644
|
+
self.items.remove(step)
|
|
645
|
+
self.by_key[step.key] = previous
|
|
646
|
+
self.step = previous
|
|
647
|
+
return previous
|
|
648
|
+
|
|
649
|
+
def _step(self, index, timestamp=None, key=None):
|
|
650
|
+
key = key or f"step-{index}"
|
|
651
|
+
step = self.by_key.get(key)
|
|
652
|
+
if step is None:
|
|
653
|
+
if self.step is not None and self.step.ended_at is None:
|
|
654
|
+
self.step.ended_at = timestamp
|
|
655
|
+
self._close_unsent(self.step, "not sent: the screen moved first, so Mobster decided again")
|
|
656
|
+
step = self._add(Step(key, index, app=self.app, started_at=timestamp))
|
|
657
|
+
self.step = step
|
|
658
|
+
return step
|
|
659
|
+
|
|
660
|
+
def feed(self, event):
|
|
661
|
+
if not isinstance(event, dict):
|
|
662
|
+
return []
|
|
663
|
+
kind = event.get("event")
|
|
664
|
+
stamp = event.get("timestamp")
|
|
665
|
+
if stamp is not None and self.started_at is None:
|
|
666
|
+
self.started_at = stamp
|
|
667
|
+
if kind == "inference_finished":
|
|
668
|
+
cost = event.get("cost_nanodollars")
|
|
669
|
+
if event.get("cost_basis") == "plan":
|
|
670
|
+
self.plan_calls += 1
|
|
671
|
+
if type(cost) is int and cost >= 0:
|
|
672
|
+
self.cost_nanodollars += cost
|
|
673
|
+
self.priced_calls += 1
|
|
674
|
+
else:
|
|
675
|
+
self.unpriced_calls += 1
|
|
676
|
+
return []
|
|
677
|
+
if kind in ("frontier_decision", "frontier_action"):
|
|
678
|
+
return self._frontier(kind, event, stamp)
|
|
679
|
+
if kind == "app_launch_started":
|
|
680
|
+
return [self._add(Launch("launch", event.get("app") or ""))]
|
|
681
|
+
if kind == "app_launch_acknowledged":
|
|
682
|
+
launch = self.by_key.get("launch")
|
|
683
|
+
if launch:
|
|
684
|
+
launch.done = True
|
|
685
|
+
return [launch]
|
|
686
|
+
return []
|
|
687
|
+
if kind == "observation":
|
|
688
|
+
self.labels = {e.get("id"): (e.get("label") or "").strip() for e in event.get("elements") or ()
|
|
689
|
+
if isinstance(e, dict)}
|
|
690
|
+
if event.get("bundle_id"):
|
|
691
|
+
self.app = app_name(event["bundle_id"], self.apps)
|
|
692
|
+
self.screen = {"app": self.app, "bundle": event.get("bundle_id") or "",
|
|
693
|
+
"elements": [e for e in event.get("elements") or () if isinstance(e, dict)],
|
|
694
|
+
"keyboard": event.get("keyboard") or ""}
|
|
695
|
+
step = self._step(event.get("step", 0), stamp)
|
|
696
|
+
step.app = self.app or step.app
|
|
697
|
+
step.elements = len(event.get("elements") or ())
|
|
698
|
+
return [step]
|
|
699
|
+
if kind == "decision":
|
|
700
|
+
self.decisions += 1
|
|
701
|
+
step = self._step(event.get("step", self.step.index if self.step else 0), stamp)
|
|
702
|
+
step.operation = event.get("operation")
|
|
703
|
+
target = event.get("target")
|
|
704
|
+
if step.operation == "LAUNCH_APP":
|
|
705
|
+
step.target = app_name(target, self.apps)
|
|
706
|
+
else:
|
|
707
|
+
step.target = (event.get("target_label") or self.labels.get(target) or
|
|
708
|
+
("an element on screen" if target else ""))
|
|
709
|
+
step.confidence = event.get("confidence")
|
|
710
|
+
step.goal_probability = event.get("goal_probability")
|
|
711
|
+
step.decide_ms = event.get("latency_ms")
|
|
712
|
+
step.observe_ms = event.get("observe_ms", step.observe_ms)
|
|
713
|
+
step.model = event.get("model") or ""
|
|
714
|
+
step.target_id = target if isinstance(target, str) else None
|
|
715
|
+
if step.operation in STALLS | {"DONE"}:
|
|
716
|
+
step.ended_at = stamp
|
|
717
|
+
if step.operation in STALLS:
|
|
718
|
+
merged = self._merge_stall(step)
|
|
719
|
+
if merged is not None:
|
|
720
|
+
return [step, merged]
|
|
721
|
+
return [step]
|
|
722
|
+
if kind == "action_check" and self.step is not None:
|
|
723
|
+
skipped = event.get("skipped")
|
|
724
|
+
self.step.check = ({"plain_navigation": "skipped: plain navigation", "keypad": "skipped: keypad key"}
|
|
725
|
+
.get(skipped) or {"allowed": "allowed", "unclear": "unclear", "mismatch":
|
|
726
|
+
"does not match the request"}.get(event.get("support"),
|
|
727
|
+
event.get("support")))
|
|
728
|
+
return [self.step]
|
|
729
|
+
if kind == "approval_requested":
|
|
730
|
+
item = self._add(Approval(f"approval-{event.get('approval_id')}", event.get("approval_id") or "",
|
|
731
|
+
event.get("operation") or "", event.get("label") or "",
|
|
732
|
+
kind=event.get("kind") or "action",
|
|
733
|
+
text_present=bool(event.get("text_present")),
|
|
734
|
+
choices=tuple(event.get("choices") or ())))
|
|
735
|
+
return [item]
|
|
736
|
+
if kind == "approval_resolved":
|
|
737
|
+
item = self.by_key.get(f"approval-{event.get('approval_id')}")
|
|
738
|
+
if item:
|
|
739
|
+
item.decision = event.get("decision")
|
|
740
|
+
return [item]
|
|
741
|
+
return []
|
|
742
|
+
if kind == "action_started":
|
|
743
|
+
self.actions += 1
|
|
744
|
+
step = self.step if self.step is not None else self._step(event.get("step", 0), stamp)
|
|
745
|
+
if event.get("operation") == "OPEN_URL":
|
|
746
|
+
step.operation, step.target = "OPEN_URL", ""
|
|
747
|
+
step.act = "started"
|
|
748
|
+
return [step]
|
|
749
|
+
if kind == "action_acknowledged" and self.step is not None:
|
|
750
|
+
self.step.act, self.step.act_ms = "acknowledged", event.get("act_ms")
|
|
751
|
+
return [self.step]
|
|
752
|
+
if kind == "action_not_dispatched" and self.step is not None:
|
|
753
|
+
self.step.act = "not_dispatched"
|
|
754
|
+
self.step.ended_at = stamp
|
|
755
|
+
return [self.step]
|
|
756
|
+
if kind == "observation_after_action" and self.step is not None:
|
|
757
|
+
self.step.changed, self.step.settle_ms = event.get("changed"), event.get("settle_ms")
|
|
758
|
+
self.step.ended_at = stamp
|
|
759
|
+
return [self.step]
|
|
760
|
+
if kind == "completion_check" and self.step is not None:
|
|
761
|
+
self.step.completion = "confirmed" if event.get("operation") == "DONE" else "not_yet"
|
|
762
|
+
return [self.step]
|
|
763
|
+
if kind == "step_interrupted":
|
|
764
|
+
return [self._add(Note(f"note-{len(self.items)}", "neutral",
|
|
765
|
+
"A device or network request timed out; re-reading the screen"))]
|
|
766
|
+
if kind == "error":
|
|
767
|
+
detail = event.get("native_code") or event.get("type") or "error"
|
|
768
|
+
return [self._add(Note(f"note-{len(self.items)}", "error", f"Error: {detail}"))]
|
|
769
|
+
note = guard_note(event)
|
|
770
|
+
if note is not None:
|
|
771
|
+
return [self._add(Note(f"note-{len(self.items)}", note[0], note[1]))]
|
|
772
|
+
if kind in NOTES:
|
|
773
|
+
if kind in ONCE and kind in self.noted:
|
|
774
|
+
return []
|
|
775
|
+
self.noted.add(kind)
|
|
776
|
+
tone, text = NOTES[kind]
|
|
777
|
+
return [self._add(Note(f"note-{len(self.items)}", tone, text))]
|
|
778
|
+
if kind == "run_finished":
|
|
779
|
+
summary = event.get("summary") if isinstance(event.get("summary"), dict) else {}
|
|
780
|
+
self.status = event.get("status") or summary.get("status") or "error"
|
|
781
|
+
self.finished_at = stamp
|
|
782
|
+
if self.step is not None and self.step.ended_at is None:
|
|
783
|
+
self.step.ended_at = stamp
|
|
784
|
+
self._close_unsent(self.step, "preview: nothing was sent" if self.status == "preview" else "not sent")
|
|
785
|
+
changed = [self.step] if self.step is not None else []
|
|
786
|
+
return changed + [self._add(Outcome("outcome", self.status, summary))]
|
|
787
|
+
return []
|
|
788
|
+
|
|
789
|
+
def _frontier(self, kind, event, stamp):
|
|
790
|
+
index = event.get("step", 0)
|
|
791
|
+
if kind == "frontier_decision":
|
|
792
|
+
self.decisions += 1
|
|
793
|
+
step = self._step(index, stamp)
|
|
794
|
+
step.operation, step.target = event.get("operation"), event.get("target_label") or ""
|
|
795
|
+
step.decide_ms, step.model = event.get("latency_ms"), event.get("model") or ""
|
|
796
|
+
if step.operation in STALLS | {"DONE"}:
|
|
797
|
+
step.ended_at = stamp
|
|
798
|
+
return [step]
|
|
799
|
+
position = event.get("index") or 0
|
|
800
|
+
step = self._step(index, stamp, key=f"step-{index}.{position}" if position else None)
|
|
801
|
+
step.operation = event.get("operation") or step.operation
|
|
802
|
+
step.target = event.get("target_label") or step.target or ""
|
|
803
|
+
step.app = event.get("app_name") or step.app
|
|
804
|
+
self.actions += 1
|
|
805
|
+
step.act, step.changed, step.settle_ms = "acknowledged", event.get("changed"), event.get("settle_ms")
|
|
806
|
+
step.ended_at = stamp
|
|
807
|
+
return [step]
|
|
808
|
+
|
|
809
|
+
def elapsed_ms(self, now_ms=None):
|
|
810
|
+
if self.started_at is None:
|
|
811
|
+
return 0.0
|
|
812
|
+
end = self.finished_at if self.finished_at is not None else now_ms
|
|
813
|
+
return max(0.0, (end or self.started_at) - self.started_at)
|