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,468 @@
|
|
|
1
|
+
"""Checkpoints and picking up (SPEC §3.1 F6): a checkpoint holds no approval text or code,
|
|
2
|
+
and pickup is never automatic."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from .. import harness_api, permissions
|
|
10
|
+
from ..api_errors import APIError
|
|
11
|
+
from ..run_executor import RunExecutor
|
|
12
|
+
from ..secret_filter import redact
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("mobster.harness")
|
|
15
|
+
|
|
16
|
+
SCHEMA, VERSION = "harness", 1
|
|
17
|
+
MAX_BYTES = 64_000
|
|
18
|
+
PICKUP_STATUSES = frozenset({"interrupted", "error", "stopped", "approval_timeout"})
|
|
19
|
+
PICKUP_CODES = frozenset({"unplugged", "phone_locked"})
|
|
20
|
+
MORE_TURNS = 50
|
|
21
|
+
MAX_TURNS = 200
|
|
22
|
+
PICKUP_FEEDBACK = ("You're picking up a task after it was interrupted. The last action may or may not have happened: "
|
|
23
|
+
"look at the screen before repeating anything.")
|
|
24
|
+
MESSAGE_NOTE = "The user wrote to you while you worked on this earlier: "
|
|
25
|
+
MESSAGE_NOTE_CHARS = 2000
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def migrate(connection, from_version):
|
|
29
|
+
"""Schema ``harness`` v1: run_checkpoints (one row per run) and harness_settings."""
|
|
30
|
+
if from_version < 1:
|
|
31
|
+
connection.execute(
|
|
32
|
+
"CREATE TABLE IF NOT EXISTS run_checkpoints(run_id TEXT PRIMARY KEY REFERENCES runs(id) ON DELETE CASCADE, "
|
|
33
|
+
"step INTEGER NOT NULL, data TEXT NOT NULL, bytes INTEGER NOT NULL, updated_at REAL NOT NULL)")
|
|
34
|
+
connection.execute("CREATE TABLE IF NOT EXISTS harness_settings(key TEXT PRIMARY KEY, value TEXT NOT NULL)")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _clean(value):
|
|
38
|
+
"""Every string through the secret filter (idempotent: a state the seam's hook saw is redacted already)."""
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
return redact(value)
|
|
41
|
+
if isinstance(value, dict):
|
|
42
|
+
return {str(key): _clean(item) for key, item in value.items()}
|
|
43
|
+
if isinstance(value, (list, tuple)):
|
|
44
|
+
return [_clean(item) for item in value]
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fit(state):
|
|
49
|
+
"""``state`` cleaned and cut to MAX_BYTES of JSON: the oldest history lines go first, then the oldest notes."""
|
|
50
|
+
state = _clean(dict(state or {}))
|
|
51
|
+
state["history"] = [str(line)[:400] for line in state.get("history") or ()]
|
|
52
|
+
state["notes"] = [str(note)[:400] for note in state.get("notes") or ()]
|
|
53
|
+
state["plan"] = str(state.get("plan") or "")[:1200]
|
|
54
|
+
data = json.dumps(state, ensure_ascii=False, allow_nan=False)
|
|
55
|
+
while len(data.encode()) > MAX_BYTES and (state["history"] or state["notes"]):
|
|
56
|
+
if state["history"]:
|
|
57
|
+
del state["history"][0]
|
|
58
|
+
else:
|
|
59
|
+
del state["notes"][0]
|
|
60
|
+
data = json.dumps(state, ensure_ascii=False, allow_nan=False)
|
|
61
|
+
if len(data.encode()) > MAX_BYTES:
|
|
62
|
+
return None
|
|
63
|
+
return state
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save(journal, run_id, state):
|
|
67
|
+
"""Keep ``state`` as the run's latest checkpoint. False when there was nothing to keep."""
|
|
68
|
+
state = fit(state)
|
|
69
|
+
if state is None or journal is None or getattr(journal, "connection", None) is None:
|
|
70
|
+
return False
|
|
71
|
+
data = json.dumps(state, ensure_ascii=False, allow_nan=False)
|
|
72
|
+
step = int(state.get("step") or 0)
|
|
73
|
+
with journal.transaction() as connection:
|
|
74
|
+
connection.execute(
|
|
75
|
+
"INSERT INTO run_checkpoints(run_id, step, data, bytes, updated_at) VALUES (?, ?, ?, ?, ?) "
|
|
76
|
+
"ON CONFLICT(run_id) DO UPDATE SET step=excluded.step, data=excluded.data, bytes=excluded.bytes, "
|
|
77
|
+
"updated_at=excluded.updated_at", (run_id, step, data, len(data.encode()), time.time()))
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load(journal, run_id):
|
|
82
|
+
"""The run's latest checkpoint, or None."""
|
|
83
|
+
if journal is None or getattr(journal, "connection", None) is None:
|
|
84
|
+
return None
|
|
85
|
+
try:
|
|
86
|
+
with journal.transaction() as connection:
|
|
87
|
+
row = connection.execute("SELECT data FROM run_checkpoints WHERE run_id=?", (run_id,)).fetchone()
|
|
88
|
+
except Exception: # noqa: BLE001 -- the track is off (no table) or the journal is closing: nothing to pick up
|
|
89
|
+
return None
|
|
90
|
+
if not row:
|
|
91
|
+
return None
|
|
92
|
+
try:
|
|
93
|
+
state = json.loads(row[0])
|
|
94
|
+
except ValueError:
|
|
95
|
+
return None
|
|
96
|
+
return state if isinstance(state, dict) else None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def delete(journal, run_id):
|
|
100
|
+
if journal is None or getattr(journal, "connection", None) is None:
|
|
101
|
+
return
|
|
102
|
+
with journal.transaction() as connection:
|
|
103
|
+
connection.execute("DELETE FROM run_checkpoints WHERE run_id=?", (run_id,))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def stop_code(summary):
|
|
107
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
108
|
+
return summary.get("code") or summary.get("stop_code")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def unresolved_effect(run):
|
|
112
|
+
"""A durable action intent without its matching acknowledgment cannot be picked up."""
|
|
113
|
+
pending = None
|
|
114
|
+
for event in getattr(run, "events", ()) or ():
|
|
115
|
+
if not isinstance(event, dict):
|
|
116
|
+
return True
|
|
117
|
+
kind = event.get("event")
|
|
118
|
+
if kind in ("action_started", "app_launch_started"):
|
|
119
|
+
identity = event.get("operation") if kind == "action_started" else event.get("app")
|
|
120
|
+
if pending is not None or not isinstance(identity, str) or not identity:
|
|
121
|
+
return True
|
|
122
|
+
pending = (kind, identity)
|
|
123
|
+
elif kind in ("action_acknowledged", "action_not_dispatched", "app_launch_acknowledged"):
|
|
124
|
+
started = "app_launch_started" if kind == "app_launch_acknowledged" else "action_started"
|
|
125
|
+
identity = event.get("app") if started == "app_launch_started" else event.get("operation")
|
|
126
|
+
if pending != (started, identity):
|
|
127
|
+
return True
|
|
128
|
+
pending = None
|
|
129
|
+
return pending is not None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def uncertain_result(run):
|
|
133
|
+
summary = getattr(run, "summary", None)
|
|
134
|
+
if not isinstance(summary, dict):
|
|
135
|
+
return True
|
|
136
|
+
return (bool(summary.get("persistence_failed")) or summary.get("code") in ("unknown_outcome", "journal")
|
|
137
|
+
or summary.get("stop_code") in ("unknown_outcome", "journal"))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def resumable(run):
|
|
141
|
+
"""Whether ``run`` ended a way it can be picked up from (a checkpoint is checked separately)."""
|
|
142
|
+
if run is None or getattr(run, "finished_at", None) is None:
|
|
143
|
+
return False
|
|
144
|
+
if getattr(run, "mode", "live") != "live" or getattr(run, "engine", None) != "smart":
|
|
145
|
+
return False
|
|
146
|
+
status = getattr(run, "status", None)
|
|
147
|
+
return status in PICKUP_STATUSES or (status == "blocked" and stop_code(run.summary) in PICKUP_CODES)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Writer:
|
|
151
|
+
"""Keeps checkpoints off the agent's thread."""
|
|
152
|
+
|
|
153
|
+
def __init__(self):
|
|
154
|
+
self._condition = threading.Condition()
|
|
155
|
+
self._pending = {} # run id -> ("save", journal, state) | ("delete", journal, None)
|
|
156
|
+
self._busy = False
|
|
157
|
+
self._workers = RunExecutor(max_workers=1)
|
|
158
|
+
self._writing = None # the loop's Future while it runs; it ends after 30 s with nothing to write
|
|
159
|
+
|
|
160
|
+
def submit(self, run_id, kind, journal, state=None):
|
|
161
|
+
with self._condition:
|
|
162
|
+
self._pending[run_id] = (kind, journal, state)
|
|
163
|
+
if self._writing is None or self._writing.done():
|
|
164
|
+
self._writing = self._workers.submit("mobster-checkpoints", self._loop)
|
|
165
|
+
self._condition.notify_all()
|
|
166
|
+
|
|
167
|
+
def _loop(self):
|
|
168
|
+
while True:
|
|
169
|
+
with self._condition:
|
|
170
|
+
while not self._pending:
|
|
171
|
+
if not self._condition.wait(30):
|
|
172
|
+
self._writing = None
|
|
173
|
+
return
|
|
174
|
+
run_id, (kind, journal, state) = next(iter(self._pending.items()))
|
|
175
|
+
del self._pending[run_id]
|
|
176
|
+
self._busy = True
|
|
177
|
+
try:
|
|
178
|
+
if kind == "save":
|
|
179
|
+
save(journal, run_id, state)
|
|
180
|
+
else:
|
|
181
|
+
delete(journal, run_id)
|
|
182
|
+
except Exception: # noqa: BLE001 -- never reaches a run; a closed journal (shutdown) loses one turn
|
|
183
|
+
log.info("checkpoint %s failed", kind, exc_info=True)
|
|
184
|
+
finally:
|
|
185
|
+
with self._condition:
|
|
186
|
+
self._busy = False
|
|
187
|
+
self._condition.notify_all()
|
|
188
|
+
|
|
189
|
+
def flush(self, timeout=2.0):
|
|
190
|
+
"""Wait until everything submitted is written (tests, Runtime.close). True when it was."""
|
|
191
|
+
deadline = time.monotonic() + timeout
|
|
192
|
+
with self._condition:
|
|
193
|
+
while self._pending or self._busy:
|
|
194
|
+
left = deadline - time.monotonic()
|
|
195
|
+
if left <= 0:
|
|
196
|
+
return False
|
|
197
|
+
self._condition.wait(min(left, .05))
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
writer = Writer()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _journal(run_context):
|
|
205
|
+
return getattr(getattr(run_context, "runtime", None), "journal", None)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def keep(run_context, state):
|
|
209
|
+
"""The frontier's end-of-turn state for ``run_context``'s run: saved on the writer's thread."""
|
|
210
|
+
journal = _journal(run_context)
|
|
211
|
+
if journal is None or harness_api.disabled("harness") or getattr(run_context, "engine", "smart") != "smart":
|
|
212
|
+
return
|
|
213
|
+
writer.submit(run_context.run_id, "save", journal, state)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def ended(run_context, status, code=None):
|
|
217
|
+
"""The frontier's run ended with ``status``: its checkpoint goes unless the task can be picked up from it."""
|
|
218
|
+
journal = _journal(run_context)
|
|
219
|
+
if journal is None or harness_api.disabled("harness"):
|
|
220
|
+
return
|
|
221
|
+
if status in PICKUP_STATUSES or (status == "blocked" and code in PICKUP_CODES):
|
|
222
|
+
return
|
|
223
|
+
writer.submit(run_context.run_id, "delete", journal)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def close(runtime=None):
|
|
227
|
+
"""Runtime.close (a service's close): what is queued is written before the journal closes."""
|
|
228
|
+
writer.flush(1.5)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _runs(runtime):
|
|
232
|
+
with runtime.lock:
|
|
233
|
+
return dict(runtime.runs)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def picked_up_by(runtime, run_id):
|
|
237
|
+
"""The run that already picks ``run_id`` up, or None."""
|
|
238
|
+
for run in _runs(runtime).values():
|
|
239
|
+
if (getattr(run, "extras", None) or {}).get("resumeFrom") == run_id:
|
|
240
|
+
return run
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _workflow_context_missing(original, runs):
|
|
245
|
+
"""An unbound pickup cannot recover a workflow scope from its public ancestry."""
|
|
246
|
+
if getattr(original, "workflow_id", None) is not None:
|
|
247
|
+
return False
|
|
248
|
+
seen = set()
|
|
249
|
+
run = original
|
|
250
|
+
while run is not None:
|
|
251
|
+
if run.id in seen:
|
|
252
|
+
return True
|
|
253
|
+
seen.add(run.id)
|
|
254
|
+
key = getattr(run, "idempotency_key", "") or ""
|
|
255
|
+
if getattr(run, "workflow_id", None) is not None or str(key).startswith("workflow:"):
|
|
256
|
+
return True
|
|
257
|
+
parent = (getattr(run, "extras", None) or {}).get("resumeFrom")
|
|
258
|
+
if not parent:
|
|
259
|
+
return False
|
|
260
|
+
run = runs.get(parent)
|
|
261
|
+
if run is None:
|
|
262
|
+
return True
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def check(runtime, run_id):
|
|
267
|
+
"""The run ``run_id`` if it can be picked up now."""
|
|
268
|
+
runs = _runs(runtime)
|
|
269
|
+
original = runs.get(run_id)
|
|
270
|
+
if original is None:
|
|
271
|
+
raise APIError("Task not found", 404, "not_found")
|
|
272
|
+
if original.finished_at is None:
|
|
273
|
+
raise APIError("This task is still running.", 409, "run_active")
|
|
274
|
+
if not resumable(original):
|
|
275
|
+
raise APIError("Mobster can pick up a task only after it was interrupted or stopped.", 409, "not_resumable")
|
|
276
|
+
if uncertain_result(original) or unresolved_effect(original):
|
|
277
|
+
raise APIError("The last action may have happened without a confirmed result. Check the phone and start a "
|
|
278
|
+
"new task; Mobster cannot pick this one up safely.", 409, "pickup_outcome_unknown")
|
|
279
|
+
if _workflow_context_missing(original, runs):
|
|
280
|
+
raise APIError("This task's saved workflow scope cannot be verified after restart. Inspect its result "
|
|
281
|
+
"and start a new authorized task; this task was not picked up.", 409,
|
|
282
|
+
"workflow_context_missing")
|
|
283
|
+
fields = getattr(original, "extras", None) or {}
|
|
284
|
+
scoped = any(fields.get(name) is not None for name in (
|
|
285
|
+
"permissionMode", "projectEnv", "remoteGrant", "consoleMember", "consoleSession"))
|
|
286
|
+
if (original.origin in permissions.policy.OUTSIDE_ORIGINS or scoped
|
|
287
|
+
or getattr(original, "allowed_bundles", None) is not None or getattr(original, "pinned_apps", None)
|
|
288
|
+
or (getattr(original, "cap_usd", None) is not None and not getattr(original, "cap_from_month", False))):
|
|
289
|
+
raise APIError("This task's original access limits cannot be carried into a pickup. Review its result "
|
|
290
|
+
"and start a new task within the right limits.", 409, "pickup_scope")
|
|
291
|
+
if isinstance((original.extras or {}).get("account"), dict) and getattr(original, "account_context", None) is None:
|
|
292
|
+
raise APIError("This task has no saved profile context to pick up safely. Start a new task after checking "
|
|
293
|
+
"the profile on your phone.", 409, "profile_context_missing")
|
|
294
|
+
writer.flush(1.0) # the last turn's checkpoint may still be on its way
|
|
295
|
+
if load(runtime.journal, run_id) is None:
|
|
296
|
+
raise APIError("There's nothing to pick up yet: Mobster hadn't finished a step. Run the task again instead.",
|
|
297
|
+
409, "no_checkpoint")
|
|
298
|
+
return original
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def validate_resume_from(value, runtime):
|
|
302
|
+
"""The ``resumeFrom`` run field: a finished task's id that can be picked up and has a checkpoint."""
|
|
303
|
+
if not isinstance(value, str) or len(value) != 12 or any(c not in "0123456789abcdef" for c in value):
|
|
304
|
+
raise ValueError("resumeFrom is a task id")
|
|
305
|
+
if runtime is None:
|
|
306
|
+
return value
|
|
307
|
+
try:
|
|
308
|
+
check(runtime, value)
|
|
309
|
+
except APIError as error:
|
|
310
|
+
raise ValueError(str(error)) from None
|
|
311
|
+
return value
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def pick_up(runtime, run_id, *, idempotency_key=None, origin="api"):
|
|
315
|
+
"""Start a new run that picks ``run_id`` up."""
|
|
316
|
+
original = check(runtime, run_id)
|
|
317
|
+
already = picked_up_by(runtime, run_id)
|
|
318
|
+
if already is not None and (idempotency_key is None or already.idempotency_key != idempotency_key):
|
|
319
|
+
raise APIError("This task was already picked up.", 409, "picked_up", runId=already.id)
|
|
320
|
+
extras = {"resumeFrom": run_id}
|
|
321
|
+
if isinstance((original.extras or {}).get("account"), dict):
|
|
322
|
+
extras["account"] = original.extras["account"]
|
|
323
|
+
fields = harness_api.run_fields()
|
|
324
|
+
for name in ("threadId",): # the conversation it belongs to (track conversations), when that field exists
|
|
325
|
+
value = (original.extras or {}).get(name)
|
|
326
|
+
if name in fields and value is not None:
|
|
327
|
+
extras[name] = value
|
|
328
|
+
if isinstance((original.extras or {}).get("askUser"), bool):
|
|
329
|
+
extras["askUser"] = original.extras["askUser"] # its surface could (or couldn't) show a question
|
|
330
|
+
kwargs = {"engine": "smart", "extras": extras, "_resume_run": original}
|
|
331
|
+
if origin in harness_api.ORIGINS and origin != "api":
|
|
332
|
+
kwargs["origin"] = origin
|
|
333
|
+
device = getattr(original, "device_id", None)
|
|
334
|
+
created = runtime.create(original.app["id"], original.goal, "live", idempotency_key,
|
|
335
|
+
**({"device": device} if device else {}), **kwargs)
|
|
336
|
+
return created if idempotency_key else (created, False)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def user_messages(run):
|
|
340
|
+
"""What the person wrote to ``run`` while it worked, oldest first (its journaled user_message events)."""
|
|
341
|
+
out = []
|
|
342
|
+
for event in getattr(run, "events", None) or ():
|
|
343
|
+
if event.get("event") == "user_message" and isinstance(event.get("text"), str) and event["text"].strip():
|
|
344
|
+
out.append(" ".join(event["text"].split()))
|
|
345
|
+
return out
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def done_with_ok(run):
|
|
349
|
+
"""The commits the person approved in ``run``, oldest first: [(step, act, label)], from its journaled events."""
|
|
350
|
+
done = []
|
|
351
|
+
for event in getattr(run, "events", None) or ():
|
|
352
|
+
kind = event.get("event")
|
|
353
|
+
if kind == "frontier_approval" and event.get("answer") == "approved":
|
|
354
|
+
done.append([event.get("step"), event.get("act") or "other", ""])
|
|
355
|
+
elif kind == "frontier_action" and done and done[-1][0] == event.get("step") and not done[-1][2]:
|
|
356
|
+
done[-1][2] = str(event.get("target_label") or "")
|
|
357
|
+
return [tuple(item) for item in done]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
ACT_WORDS = {"send_message": "sending a message", "reply": "sending a reply", "post": "posting",
|
|
361
|
+
"comment": "commenting",
|
|
362
|
+
"react": "reacting", "pay": "paying", "request_money": "requesting money", "transfer": "making a transfer",
|
|
363
|
+
"order": "placing an order", "book": "making a booking", "follow": "following",
|
|
364
|
+
"unfollow": "unfollowing", "share": "sharing", "call": "placing a call", "delete": "deleting",
|
|
365
|
+
"cancel": "cancelling", "install": "installing an app"}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def held_acts(run):
|
|
369
|
+
"""The acts ``run`` may already have done (approved, or sent with the answer lost), oldest first and once each: a
|
|
370
|
+
pickup asks for each again in every mode, and refuses it where nobody can answer."""
|
|
371
|
+
acts = []
|
|
372
|
+
for event in getattr(run, "events", None) or ():
|
|
373
|
+
kind = event.get("event")
|
|
374
|
+
if kind == "frontier_approval" and event.get("answer") == "approved":
|
|
375
|
+
act = event.get("act")
|
|
376
|
+
elif kind == "frontier_failed" and event.get("outcome") == "unknown":
|
|
377
|
+
act = event.get("held_act")
|
|
378
|
+
else:
|
|
379
|
+
continue
|
|
380
|
+
if isinstance(act, str) and act and act not in acts:
|
|
381
|
+
acts.append(act)
|
|
382
|
+
return tuple(acts)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def handoff(state, original):
|
|
386
|
+
"""The InitialState a picked-up run starts from."""
|
|
387
|
+
notes = [str(note) for note in state.get("notes") or ()]
|
|
388
|
+
messages = user_messages(original) if original is not None else []
|
|
389
|
+
if messages:
|
|
390
|
+
text = " · ".join(messages)
|
|
391
|
+
if len(text) > MESSAGE_NOTE_CHARS:
|
|
392
|
+
text = "…" + text[-MESSAGE_NOTE_CHARS:]
|
|
393
|
+
notes.append(MESSAGE_NOTE + text)
|
|
394
|
+
feedback = PICKUP_FEEDBACK
|
|
395
|
+
done = done_with_ok(original) if original is not None else []
|
|
396
|
+
if done:
|
|
397
|
+
feedback += (" Before the interruption the user said OK to: "
|
|
398
|
+
+ "; ".join(f"step {step}: {ACT_WORDS.get(act, 'something that needed their OK')}"
|
|
399
|
+
+ (f" ({label[:60]})" if label else "") for step, act, label in done[-5:])
|
|
400
|
+
+ ". It may already have happened: check the screen (the thread, the sent or saved item) "
|
|
401
|
+
"before doing it again.")
|
|
402
|
+
held = held_acts(original) if original is not None else ()
|
|
403
|
+
lost = [act for act in held if act not in {item[1] for item in done}]
|
|
404
|
+
if lost:
|
|
405
|
+
feedback += (" The answer to an earlier attempt at " + ", ".join(ACT_WORDS.get(act, act) for act in lost)
|
|
406
|
+
+ " was lost: it may have gone through. Check the screen before doing it again; doing it again "
|
|
407
|
+
"asks the user first.")
|
|
408
|
+
return harness_api.InitialState(
|
|
409
|
+
plan=str(state.get("plan") or ""), notes=tuple(notes[-40:]),
|
|
410
|
+
history=tuple(str(line) for line in state.get("history") or ()), feedback=feedback,
|
|
411
|
+
steps_used=max(0, int(state.get("step") or 0)), cost_usd=float(state.get("cost_usd") or 0.0),
|
|
412
|
+
reason="picked up after the task was interrupted", held_acts=held)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
_resumed = {} # run id -> turns already used, for frontier_options (read once)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _pickup_unavailable(ctx, error):
|
|
419
|
+
try:
|
|
420
|
+
ctx.emit({"event": "context_error", "key": "pickup", "error": error})
|
|
421
|
+
except Exception: # a failed journal write must not turn a pickup into a fresh task
|
|
422
|
+
pass
|
|
423
|
+
uncertain = error == "pickup_outcome_unknown"
|
|
424
|
+
return harness_api.PreRun(result={
|
|
425
|
+
"event": "result", "engine": "smart", "status": "blocked", "outcome": "check",
|
|
426
|
+
"stop_code": "pickup_outcome_unknown" if uncertain else "pickup_checkpoint_missing",
|
|
427
|
+
"answer": None, "proof": [],
|
|
428
|
+
"independently_verified": False, "actions_may_have_run": False,
|
|
429
|
+
"reason": ("The earlier task has an unconfirmed action. This pickup did nothing. Check the phone before "
|
|
430
|
+
"starting a new task." if uncertain else
|
|
431
|
+
"The earlier task's checkpoint is unavailable. This pickup did nothing. Review the earlier "
|
|
432
|
+
"task and start a new one only after checking the phone.")})
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def pre_run(ctx, driver, approve):
|
|
436
|
+
"""Pre-run hook (order 0): a run with ``resumeFrom`` goes on from that run's checkpoint."""
|
|
437
|
+
source = (ctx.extras or {}).get("resumeFrom")
|
|
438
|
+
if not source:
|
|
439
|
+
return None
|
|
440
|
+
if harness_api.disabled("harness"):
|
|
441
|
+
return _pickup_unavailable(ctx, "disabled")
|
|
442
|
+
runtime = ctx.runtime
|
|
443
|
+
try:
|
|
444
|
+
original = check(runtime, source)
|
|
445
|
+
state = load(getattr(runtime, "journal", None), source)
|
|
446
|
+
if state is None:
|
|
447
|
+
return _pickup_unavailable(ctx, "no_checkpoint")
|
|
448
|
+
initial = handoff(state, original)
|
|
449
|
+
_resumed[ctx.run_id] = initial.steps_used
|
|
450
|
+
ctx.emit({"event": "picked_up", "from": source, "step": initial.steps_used})
|
|
451
|
+
except APIError as error:
|
|
452
|
+
_resumed.pop(ctx.run_id, None)
|
|
453
|
+
return _pickup_unavailable(ctx, error.code)
|
|
454
|
+
except Exception: # no malformed/missing resume state may fall through to the fresh goal
|
|
455
|
+
_resumed.pop(ctx.run_id, None)
|
|
456
|
+
return _pickup_unavailable(ctx, "unavailable")
|
|
457
|
+
return harness_api.PreRun(handoff=initial)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
pre_run.before_phone = True
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def frontier_options(ctx):
|
|
464
|
+
"""A picked-up run gets MORE_TURNS turns on top of those it used (at most MAX_TURNS)."""
|
|
465
|
+
used = _resumed.pop(ctx.run_id, None)
|
|
466
|
+
if used is None:
|
|
467
|
+
return {}
|
|
468
|
+
return {"max_steps": max(1, min(MAX_TURNS, used + MORE_TURNS))}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Pause, continue and "stop after this step" for a running Smart task: controls, never text,
|
|
2
|
+
so nothing here reads a message."""
|
|
3
|
+
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import weakref
|
|
7
|
+
|
|
8
|
+
PAUSE_LIMIT_SECONDS = 600
|
|
9
|
+
POLL_SECONDS = 0.25
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Control:
|
|
13
|
+
"""One run's controls. ``condition`` wakes a paused loop when anything changes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.condition = threading.Condition()
|
|
17
|
+
self.stop_after = False
|
|
18
|
+
self.reason = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_controls = weakref.WeakKeyDictionary()
|
|
22
|
+
_lock = threading.Lock()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def of(queue):
|
|
26
|
+
"""The controls that go with ``queue`` (made on first use); None for no queue."""
|
|
27
|
+
if queue is None:
|
|
28
|
+
return None
|
|
29
|
+
with _lock:
|
|
30
|
+
control = _controls.get(queue)
|
|
31
|
+
if control is None:
|
|
32
|
+
control = _controls[queue] = Control()
|
|
33
|
+
return control
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def paused(queue):
|
|
37
|
+
"""Whether a pause was asked for (the queue's own flag, so a pause set any other way counts too)."""
|
|
38
|
+
flag = getattr(queue, "paused", None)
|
|
39
|
+
return bool(flag is not None and flag.is_set())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def set_paused(queue, value, reason=None):
|
|
43
|
+
"""Pause (True) or continue (False) the run that owns ``queue``."""
|
|
44
|
+
control = of(queue)
|
|
45
|
+
with control.condition:
|
|
46
|
+
if value:
|
|
47
|
+
queue.paused.set()
|
|
48
|
+
else:
|
|
49
|
+
queue.paused.clear()
|
|
50
|
+
control.reason = dict(reason) if value and isinstance(reason, dict) else None
|
|
51
|
+
control.condition.notify_all()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def reason(queue):
|
|
55
|
+
"""Why Mobster paused the run itself (a copy), or None: not paused, or paused by a person."""
|
|
56
|
+
control = _controls.get(queue) if queue is not None else None
|
|
57
|
+
if control is None or not paused(queue):
|
|
58
|
+
return None
|
|
59
|
+
with control.condition:
|
|
60
|
+
return dict(control.reason) if control.reason else None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def request_stop_after(queue):
|
|
64
|
+
"""Ask the run to stop once the step in progress is done. Wakes a paused run, which then stops."""
|
|
65
|
+
control = of(queue)
|
|
66
|
+
with control.condition:
|
|
67
|
+
control.stop_after = True
|
|
68
|
+
control.condition.notify_all()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def stop_after_requested(queue):
|
|
72
|
+
control = _controls.get(queue) if queue is not None else None
|
|
73
|
+
return bool(control is not None and control.stop_after)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def wait_while_paused(queue, *, cancelled=None, limit=None, clock=time.monotonic):
|
|
77
|
+
"""Block while the run is paused."""
|
|
78
|
+
control = of(queue)
|
|
79
|
+
if limit is None:
|
|
80
|
+
with control.condition:
|
|
81
|
+
own = (control.reason or {}).get("limit")
|
|
82
|
+
limit = own if type(own) in (int, float) and own > 0 else PAUSE_LIMIT_SECONDS
|
|
83
|
+
deadline = clock() + limit
|
|
84
|
+
with control.condition:
|
|
85
|
+
while paused(queue):
|
|
86
|
+
if cancelled is not None and _true(cancelled):
|
|
87
|
+
return "stopped"
|
|
88
|
+
if control.stop_after:
|
|
89
|
+
return "stop_after"
|
|
90
|
+
left = deadline - clock()
|
|
91
|
+
if left <= 0:
|
|
92
|
+
return "timeout"
|
|
93
|
+
control.condition.wait(min(POLL_SECONDS, left))
|
|
94
|
+
return "continued"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _true(fn):
|
|
98
|
+
try:
|
|
99
|
+
return bool(fn())
|
|
100
|
+
except Exception: # noqa: BLE001 -- a broken Stop check never keeps a run paused forever
|
|
101
|
+
return False
|