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,110 @@
|
|
|
1
|
+
"""Bounded five-field cron validation and timezone-aware occurrence calculation."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
6
|
+
|
|
7
|
+
MIN_INTERVAL_MS = 300_000
|
|
8
|
+
DAILY_LIMIT = 60
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def timezone(value):
|
|
12
|
+
if not isinstance(value, str) or not 1 <= len(value) <= 128:
|
|
13
|
+
raise ValueError("Choose an IANA timezone")
|
|
14
|
+
try:
|
|
15
|
+
return ZoneInfo(value)
|
|
16
|
+
except (ZoneInfoNotFoundError, ValueError):
|
|
17
|
+
raise ValueError("Choose a recognized IANA timezone, such as America/Los_Angeles") from None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def local_timezone(environ=None, localtime="/etc/localtime"):
|
|
21
|
+
"""The Mac's IANA zone ("America/Los_Angeles"), so a new schedule reads in the user's own time."""
|
|
22
|
+
import os
|
|
23
|
+
environ = os.environ if environ is None else environ
|
|
24
|
+
candidates = [environ.get("TZ", "").lstrip(":")]
|
|
25
|
+
for resolve in (os.readlink, os.path.realpath):
|
|
26
|
+
try:
|
|
27
|
+
target = resolve(localtime)
|
|
28
|
+
except OSError:
|
|
29
|
+
continue
|
|
30
|
+
if "/zoneinfo/" in target:
|
|
31
|
+
candidates.append(target.split("/zoneinfo/", 1)[1])
|
|
32
|
+
for candidate in candidates:
|
|
33
|
+
if candidate and ("/" in candidate or candidate == "UTC"):
|
|
34
|
+
try:
|
|
35
|
+
timezone(candidate)
|
|
36
|
+
return candidate
|
|
37
|
+
except ValueError:
|
|
38
|
+
continue
|
|
39
|
+
return "UTC"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_cron(expression, zone, daily_limit=DAILY_LIMIT):
|
|
43
|
+
timezone(zone)
|
|
44
|
+
if not isinstance(expression, str) or len(expression) > 128 or len(expression.split()) != 5:
|
|
45
|
+
raise ValueError("Use a standard five-field cron expression")
|
|
46
|
+
expression = " ".join(expression.lower().split())
|
|
47
|
+
# Support numeric fields and standard month/weekday names, not seconds, years,
|
|
48
|
+
# random/hash expressions, nearest weekdays, or nth/last-day extensions.
|
|
49
|
+
for index, field in enumerate(expression.split()):
|
|
50
|
+
permitted = ("jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec" if index == 3 else
|
|
51
|
+
"sun|mon|tue|wed|thu|fri|sat" if index == 4 else "")
|
|
52
|
+
stripped = re.sub(permitted, "0", field) if permitted else field
|
|
53
|
+
if not re.fullmatch(r"[0-9*,/\-]+", stripped):
|
|
54
|
+
raise ValueError("Use standard cron fields without seconds, aliases, or special extensions")
|
|
55
|
+
from croniter import CroniterBadCronError, croniter # On first use: ~17 ms the agent's start does not need.
|
|
56
|
+
try:
|
|
57
|
+
expanded, _ = croniter.expand(expression, strict=True)
|
|
58
|
+
except (CroniterBadCronError, ValueError, KeyError):
|
|
59
|
+
raise ValueError("The cron expression is invalid or cannot match a calendar date") from None
|
|
60
|
+
minutes = range(60) if expanded[0] == ["*"] else expanded[0]
|
|
61
|
+
hours = range(24) if expanded[1] == ["*"] else expanded[1]
|
|
62
|
+
daily = sorted(hour * 60 + minute for hour in hours for minute in minutes)
|
|
63
|
+
if len(daily) > daily_limit:
|
|
64
|
+
raise ValueError(f"Schedules may contain at most {daily_limit} occurrences per day")
|
|
65
|
+
if any(b - a < 5 for a, b in zip(daily, daily[1:] + [daily[0] + 1440])):
|
|
66
|
+
raise ValueError("Scheduled occurrences must be at least five minutes apart")
|
|
67
|
+
return expression
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Bounds the skipping below: clocks going back repeat an hour or two, at most 12 five-minute slots an hour.
|
|
71
|
+
MAX_REPEATS_SKIPPED = 288
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def runs_through_repeated_hours(expression):
|
|
75
|
+
"""Whether a schedule runs again when a wall-clock time repeats as clocks go back."""
|
|
76
|
+
hour = expression.split()[1]
|
|
77
|
+
return hour.startswith("*") or "/" in hour
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def next_occurrences(expression, zone, after_ms, count=1):
|
|
81
|
+
if not 1 <= count <= 3:
|
|
82
|
+
raise ValueError("Preview supports one to three occurrences")
|
|
83
|
+
from croniter import CroniterBadCronError, CroniterBadDateError, croniter
|
|
84
|
+
try:
|
|
85
|
+
zone_info = timezone(zone)
|
|
86
|
+
iterator = croniter(expression, datetime.fromtimestamp(after_ms / 1000, zone_info),
|
|
87
|
+
max_years_between_matches=8)
|
|
88
|
+
repeats = runs_through_repeated_hours(expression)
|
|
89
|
+
values, skipped = [], 0
|
|
90
|
+
while len(values) < count:
|
|
91
|
+
moment = iterator.get_next(datetime).timestamp()
|
|
92
|
+
# fold=1 is the second of two identical wall-clock times; the first one matched the same fields.
|
|
93
|
+
if not repeats and datetime.fromtimestamp(moment, zone_info).fold:
|
|
94
|
+
skipped += 1
|
|
95
|
+
if skipped > MAX_REPEATS_SKIPPED:
|
|
96
|
+
raise ValueError("too many repeated wall-clock times")
|
|
97
|
+
continue
|
|
98
|
+
values.append(round(moment * 1000))
|
|
99
|
+
except (CroniterBadCronError, CroniterBadDateError, ValueError, OverflowError):
|
|
100
|
+
raise ValueError("The schedule has no valid occurrence within eight years") from None
|
|
101
|
+
if any(b <= a for a, b in zip([after_ms] + values, values)):
|
|
102
|
+
raise ValueError("The schedule did not advance in time")
|
|
103
|
+
return values
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def preview(body, now_ms):
|
|
107
|
+
if not isinstance(body, dict) or set(body) != {"cron", "timezone"}:
|
|
108
|
+
raise ValueError("Supply cron and timezone")
|
|
109
|
+
expression = validate_cron(body["cron"], body["timezone"])
|
|
110
|
+
return {"next": next_occurrences(expression, body["timezone"], now_ms, 3)}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""What a workflow's row says about it (GET /api/workflows, each workflow's ``health``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from . import starters
|
|
6
|
+
|
|
7
|
+
OUTCOMES = frozenset({"done", "check", "couldnt_finish", "stopped", "declined"})
|
|
8
|
+
# Every status as an outcome (None while the run goes). A status that means "it finished" is check here: only proof
|
|
9
|
+
# makes it done. An unknown status is check, never done.
|
|
10
|
+
STATUS_OUTCOMES = {
|
|
11
|
+
"queued": None, "running": None,
|
|
12
|
+
"completed": "check", "completed_unverified": "check", "expected_text_visible": "check", "demo_complete": "check",
|
|
13
|
+
"inconsistent_completion": "check", "completion_not_confirmed": "check",
|
|
14
|
+
"stopped": "stopped", "user_condition_met": "stopped", "approval_denied": "declined",
|
|
15
|
+
"interrupted": "couldnt_finish", "needs_clarification": "couldnt_finish", "error": "couldnt_finish",
|
|
16
|
+
"blocked": "couldnt_finish", "needs_text_helper": "couldnt_finish", "no_progress": "couldnt_finish",
|
|
17
|
+
"max_steps": "couldnt_finish", "timeout": "couldnt_finish", "approval_timeout": "couldnt_finish",
|
|
18
|
+
"spend_cap": "couldnt_finish", "duplicate_effect_blocked": "couldnt_finish",
|
|
19
|
+
"duplicate_text_blocked": "couldnt_finish",
|
|
20
|
+
"invalid_action": "couldnt_finish", "invalid_target": "couldnt_finish", "expected_text_missing": "couldnt_finish",
|
|
21
|
+
"preview": "stopped",
|
|
22
|
+
}
|
|
23
|
+
FINISHED = frozenset({"completed", "completed_unverified", "expected_text_visible"})
|
|
24
|
+
# Stops by design (nobody answered, the spending limit, a repeat refused): never a failure to repair.
|
|
25
|
+
SAFE_STOPS = frozenset({"approval_timeout", "spend_cap", "duplicate_effect_blocked"})
|
|
26
|
+
# A run that ended because the iPhone wasn't there to use (agent_hooks.STOP_CODES that a person fixes at the phone).
|
|
27
|
+
PHONE_CODES = frozenset({"phone_locked", "unlock_declined", "passcode_failed", "face_id", "unplugged",
|
|
28
|
+
"keychain_locked"})
|
|
29
|
+
|
|
30
|
+
# Dispatch outcomes that are not run statuses (service.py, records.py): how each reads on the row.
|
|
31
|
+
PHONE_SKIPS = {
|
|
32
|
+
"skipped_busy": "Skipped: your iPhone was busy with another task",
|
|
33
|
+
"busy": "Skipped: your iPhone was busy with another task",
|
|
34
|
+
"skipped_offline": "Skipped: your iPhone wasn’t connected",
|
|
35
|
+
"offline": "Skipped: your iPhone wasn’t connected",
|
|
36
|
+
}
|
|
37
|
+
LIMIT_SKIPS = {
|
|
38
|
+
"skipped_monthly_limit": "Skipped: this month’s spending limit is reached",
|
|
39
|
+
"monthly_limit": "Skipped: this month’s spending limit is reached",
|
|
40
|
+
"skipped_daily_limit": "Skipped: the daily limit for scheduled runs is reached",
|
|
41
|
+
"skipped_interval_limit": "Skipped: too soon after the last scheduled run",
|
|
42
|
+
}
|
|
43
|
+
FAILURES = {
|
|
44
|
+
"dispatch_failed": "Last run couldn’t start",
|
|
45
|
+
"interrupted": "Last run was interrupted",
|
|
46
|
+
"time_limit": "Last run reached its time limit",
|
|
47
|
+
"loop_limit": "Last run reached its repeat limit",
|
|
48
|
+
"condition_unknown": "Last run couldn’t check its condition",
|
|
49
|
+
"delivery_failed": "Last run couldn’t send its result",
|
|
50
|
+
"error": "Last run couldn’t finish",
|
|
51
|
+
}
|
|
52
|
+
# A run that ended, then left before Mobster read how (its task deleted, on its own or with its conversation, or out of
|
|
53
|
+
# the history it keeps): Mobster can't say how it ended, so it is never a failure. The row says so in grey and Needs you
|
|
54
|
+
# doesn't list it (the lead, 5 Nov 2026: expired isn't failed); a failure before it still leads (workflow_health).
|
|
55
|
+
UNKNOWN_ENDS = {"run_expired": "Last run is no longer kept"}
|
|
56
|
+
GOING = frozenset({"claimed", "started"})
|
|
57
|
+
# Outcomes that say nothing about the workflow itself: they never raise attention and never hide an older one.
|
|
58
|
+
QUIET = frozenset({"skipped_missed"})
|
|
59
|
+
LINE_MAX = 140
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _record(value):
|
|
63
|
+
return isinstance(value, dict)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _text(value):
|
|
67
|
+
return value.strip() if isinstance(value, str) and value.strip() else None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def result_fields(run):
|
|
71
|
+
"""The result's fields: the run's summary over its last ``result`` event."""
|
|
72
|
+
events = getattr(run, "events", None) or ()
|
|
73
|
+
result = next((event for event in reversed(events) if _record(event) and event.get("event") == "result"), None)
|
|
74
|
+
summary = getattr(run, "summary", None)
|
|
75
|
+
source = {**(result or {}), **summary} if _record(summary) else dict(result or {})
|
|
76
|
+
proof = [item for item in source.get("proof") or () if _record(item) and _text(item.get("quote"))] \
|
|
77
|
+
if isinstance(source.get("proof"), list) else []
|
|
78
|
+
outcome = source.get("outcome") if source.get("outcome") in OUTCOMES else None
|
|
79
|
+
return {"outcome": outcome, "proof": proof, "answer": _text(source.get("answer")),
|
|
80
|
+
"reason": _text(source.get("reason")), "stopCode": _text(source.get("stop_code"))}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_outcome(run):
|
|
84
|
+
"""done | check | couldnt_finish | stopped | declined, or None while the run goes (result-status.ts runOutcome)."""
|
|
85
|
+
status = getattr(run, "status", None)
|
|
86
|
+
if status in ("queued", "running") or getattr(run, "finished_at", None) is None:
|
|
87
|
+
return None
|
|
88
|
+
fields = result_fields(run)
|
|
89
|
+
outcome = fields["outcome"] or STATUS_OUTCOMES.get(status, "check")
|
|
90
|
+
if not fields["outcome"] and outcome == "check" and status in FINISHED and fields["proof"]:
|
|
91
|
+
outcome = "done"
|
|
92
|
+
if outcome == "done" and not fields["proof"]:
|
|
93
|
+
outcome = "check"
|
|
94
|
+
if outcome == "check" and getattr(run, "reviewed_ok", False):
|
|
95
|
+
outcome = "done"
|
|
96
|
+
return outcome
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def verified(run):
|
|
100
|
+
"""The run finished and it is proven (or a person marked it OK)."""
|
|
101
|
+
return run is not None and run_outcome(run) == "done"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def first_line(text, limit=LINE_MAX):
|
|
105
|
+
"""The first line of an answer, cut at a word to ``limit`` characters."""
|
|
106
|
+
line = next((part.strip() for part in (text or "").splitlines() if part.strip()), None)
|
|
107
|
+
if line is None or len(line) <= limit:
|
|
108
|
+
return line
|
|
109
|
+
return line[:limit - 1].rsplit(" ", 1)[0].rstrip(" ,;:") + "…"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def success_of(run):
|
|
113
|
+
"""{"at", "runId", "line"} for a verified run: when it finished (ms) and its answer's first line."""
|
|
114
|
+
finished = getattr(run, "finished_at", None)
|
|
115
|
+
return {"at": round(finished * 1000) if finished is not None else None, "runId": run.id,
|
|
116
|
+
"line": first_line(result_fields(run)["answer"])}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _attention(kind, words, detail=None, run_id=None, at=None):
|
|
120
|
+
return {"kind": kind, "words": words, "detail": detail, "runId": run_id, "at": at}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def attention_for(outcome, run, at=None, plain=True):
|
|
124
|
+
"""What the newest ended attempt asks of you, or None."""
|
|
125
|
+
run_id = getattr(run, "id", None)
|
|
126
|
+
if outcome is None or outcome in GOING or outcome in QUIET:
|
|
127
|
+
return None
|
|
128
|
+
if outcome in PHONE_SKIPS:
|
|
129
|
+
return _attention("phone", PHONE_SKIPS[outcome], "Keep it plugged into this Mac and unlocked.", run_id, at)
|
|
130
|
+
if outcome in LIMIT_SKIPS:
|
|
131
|
+
return _attention("limit", LIMIT_SKIPS[outcome], None, run_id, at)
|
|
132
|
+
if outcome == "missing_values":
|
|
133
|
+
return _attention("input", "A value it needs is missing", "Edit the workflow and give each blank a value.",
|
|
134
|
+
run_id, at)
|
|
135
|
+
if outcome in UNKNOWN_ENDS:
|
|
136
|
+
return _attention("unknown", UNKNOWN_ENDS[outcome], None, None, at)
|
|
137
|
+
fields = result_fields(run) if run is not None and getattr(run, "finished_at", None) is not None else None
|
|
138
|
+
if fields and fields["stopCode"] in PHONE_CODES:
|
|
139
|
+
return _attention("phone", "Last run stopped: your iPhone wasn’t available", fields["reason"], run_id, at)
|
|
140
|
+
if not plain:
|
|
141
|
+
# A batch ends completed only when every step ended done; stopped is your own Stop.
|
|
142
|
+
if outcome in FAILURES or STATUS_OUTCOMES.get(outcome) == "couldnt_finish" and outcome not in SAFE_STOPS:
|
|
143
|
+
return _attention("failed", FAILURES.get(outcome, "Last run couldn’t finish"),
|
|
144
|
+
fields["reason"] if fields else None, run_id, at)
|
|
145
|
+
return None
|
|
146
|
+
if fields is not None:
|
|
147
|
+
if run_outcome(run) == "couldnt_finish" and getattr(run, "status", None) not in SAFE_STOPS:
|
|
148
|
+
return _attention("failed", "Last run couldn’t finish", fields["reason"], run_id, at)
|
|
149
|
+
return None
|
|
150
|
+
if outcome in FAILURES or STATUS_OUTCOMES.get(outcome) == "couldnt_finish" and outcome not in SAFE_STOPS:
|
|
151
|
+
return _attention("failed", FAILURES.get(outcome, "Last run couldn’t finish"), None, run_id, at)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def missing_input(workflow, filled=None):
|
|
156
|
+
"""Attention for a scheduled workflow that can't fill what it needs"""
|
|
157
|
+
if not workflow.get("enabled"):
|
|
158
|
+
return None
|
|
159
|
+
repeat = workflow.get("repeat") or {}
|
|
160
|
+
if repeat.get("kind") == "each" and not repeat.get("rows"):
|
|
161
|
+
return _attention("input", "Its rows are missing", "Edit the workflow and save the rows it repeats over.")
|
|
162
|
+
keys = set(filled or ())
|
|
163
|
+
if repeat.get("rows"):
|
|
164
|
+
keys |= set.intersection(*[set(starters.clean_values(row)) for row in repeat["rows"]])
|
|
165
|
+
missing = [name for name in starters.unfilled(workflow.get("goal")) if starters._key(name) not in keys]
|
|
166
|
+
if missing:
|
|
167
|
+
return _attention("input", f"It needs a value for {missing[0]}",
|
|
168
|
+
"Edit the workflow and give each blank a default.")
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_plain(workflow):
|
|
173
|
+
"""One task and no repeat: it runs as one task, whose own result is the workflow's."""
|
|
174
|
+
from ..graph.chain import record_is_plain
|
|
175
|
+
return record_is_plain(workflow)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def latest_success(workflow, attempts, runs):
|
|
179
|
+
"""The newest verified success among ``attempts``, else the one the record keeps (``lastSuccess``)."""
|
|
180
|
+
plain = is_plain(workflow)
|
|
181
|
+
success = None
|
|
182
|
+
for outcome, run_id, claimed_at in attempts:
|
|
183
|
+
run = runs.get(run_id) if isinstance(run_id, str) else None
|
|
184
|
+
if plain and verified(run):
|
|
185
|
+
success = success_of(run)
|
|
186
|
+
break
|
|
187
|
+
if not plain and outcome == "completed":
|
|
188
|
+
success = success_of(run) if run is not None else {"at": claimed_at, "runId": None, "line": None}
|
|
189
|
+
break
|
|
190
|
+
stored = workflow.get("lastSuccess")
|
|
191
|
+
if (_record(stored) and isinstance(stored.get("at"), (int, float))
|
|
192
|
+
and (success is None or (success["at"] or 0) < stored["at"])):
|
|
193
|
+
success = {"at": stored["at"], "runId": stored.get("runId"), "line": stored.get("line")}
|
|
194
|
+
return success
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
RECENT = 10
|
|
198
|
+
RUN_WORDS = {"done": "done", "check": "check", "couldnt_finish": "failed", "stopped": "stopped", "declined": "stopped"}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def outcome_word(outcome, run, plain=True):
|
|
202
|
+
"""One attempt as the row's history reads it: done, check, failed, stopped, skipped or running."""
|
|
203
|
+
if outcome in GOING:
|
|
204
|
+
return "running"
|
|
205
|
+
if outcome in PHONE_SKIPS or outcome in LIMIT_SKIPS or outcome == "missing_values":
|
|
206
|
+
return "skipped"
|
|
207
|
+
if outcome in UNKNOWN_ENDS:
|
|
208
|
+
return "unknown"
|
|
209
|
+
if plain and run is not None:
|
|
210
|
+
found = run_outcome(run)
|
|
211
|
+
return RUN_WORDS.get(found, "check") if found else "running"
|
|
212
|
+
if not plain and outcome == "completed":
|
|
213
|
+
return "done"
|
|
214
|
+
if outcome in SAFE_STOPS or STATUS_OUTCOMES.get(outcome) in ("stopped", "declined") or outcome == "cancelled":
|
|
215
|
+
return "stopped"
|
|
216
|
+
if outcome in FAILURES or STATUS_OUTCOMES.get(outcome) == "couldnt_finish":
|
|
217
|
+
return "failed"
|
|
218
|
+
return "check"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def recent_of(workflow, attempts, runs):
|
|
222
|
+
"""Its last ``RECENT`` attempts, newest first, as {"outcome", "at", "runId"}."""
|
|
223
|
+
plain = is_plain(workflow)
|
|
224
|
+
recent = []
|
|
225
|
+
for outcome, run_id, claimed_at in attempts:
|
|
226
|
+
if outcome in QUIET:
|
|
227
|
+
continue
|
|
228
|
+
run = runs.get(run_id) if isinstance(run_id, str) else None
|
|
229
|
+
recent.append({"outcome": outcome_word(outcome, run, plain), "at": claimed_at,
|
|
230
|
+
"runId": run_id if isinstance(run_id, str) else None})
|
|
231
|
+
if len(recent) == RECENT:
|
|
232
|
+
break
|
|
233
|
+
return recent
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def workflow_health(workflow, attempts, runs, filled=None):
|
|
237
|
+
"""A workflow's ``health``."""
|
|
238
|
+
plain = is_plain(workflow)
|
|
239
|
+
attention = missing_input(workflow, filled)
|
|
240
|
+
if attention is None:
|
|
241
|
+
unknown = None
|
|
242
|
+
for outcome, run_id, claimed_at in attempts:
|
|
243
|
+
if outcome in QUIET:
|
|
244
|
+
continue
|
|
245
|
+
if outcome in UNKNOWN_ENDS:
|
|
246
|
+
# An end Mobster can't read never hides an older one: a failure under it still leads, a success
|
|
247
|
+
# or nothing leaves the quiet line (review of #2089: fail closed).
|
|
248
|
+
unknown = unknown or attention_for(outcome, None, claimed_at, plain)
|
|
249
|
+
continue
|
|
250
|
+
run = runs.get(run_id) if isinstance(run_id, str) else None
|
|
251
|
+
found = attention_for(outcome, run, claimed_at, plain)
|
|
252
|
+
attention = found if unknown is None or (found and found["kind"] == "failed") else unknown
|
|
253
|
+
break
|
|
254
|
+
else:
|
|
255
|
+
attention = unknown
|
|
256
|
+
return {"lastSuccess": latest_success(workflow, attempts, runs), "attention": attention,
|
|
257
|
+
"recent": recent_of(workflow, attempts, runs)}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""A workflow's history (docs/workflows.mdx › Drafts, publishing and versions)"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from ..api_errors import APIError
|
|
6
|
+
from ..graph import edit
|
|
7
|
+
from ..journal import encode
|
|
8
|
+
|
|
9
|
+
KEPT = 100
|
|
10
|
+
# What a rollback brings back from an old version; whether the schedule is on, the home conversation and the webhook
|
|
11
|
+
# stay as they are now.
|
|
12
|
+
RESTORED = ("graph", "name", "appId", "appName", "engine", "device", "account", "sendTo", "sameConversation",
|
|
13
|
+
"outputSchema", "outputFormat", "produces")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorkflowHistory:
|
|
17
|
+
def __init__(self, owner):
|
|
18
|
+
self.owner = owner
|
|
19
|
+
with owner.journal.transaction() as connection:
|
|
20
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS workflow_versions (
|
|
21
|
+
workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, saved_at INTEGER NOT NULL, data TEXT NOT NULL,
|
|
22
|
+
published_at INTEGER, PRIMARY KEY (workflow_id, revision))""")
|
|
23
|
+
|
|
24
|
+
def forget(self, connection, identifier):
|
|
25
|
+
connection.execute("DELETE FROM workflow_versions WHERE workflow_id=?", (identifier,))
|
|
26
|
+
|
|
27
|
+
def _version_row(self, identifier, revision):
|
|
28
|
+
"""Read a saved version under the journal lock, after checking the workflow still exists."""
|
|
29
|
+
row = self.owner.journal.connection.execute(
|
|
30
|
+
"SELECT data, published_at FROM workflow_versions WHERE workflow_id=? AND revision=?",
|
|
31
|
+
(identifier, revision)).fetchone()
|
|
32
|
+
if row is None:
|
|
33
|
+
raise APIError("That version isn't kept any more.", 404, "version_not_found")
|
|
34
|
+
return row
|
|
35
|
+
|
|
36
|
+
def publish(self, identifier, body):
|
|
37
|
+
"""POST /api/workflows/{id}/publish {revision}"""
|
|
38
|
+
if not isinstance(body, dict) or set(body) != {"revision"} or type(body["revision"]) is not int:
|
|
39
|
+
raise ValueError("Give the revision to publish")
|
|
40
|
+
with self.owner.lock, self.owner.journal.lock:
|
|
41
|
+
workflow = self.owner._get(identifier)
|
|
42
|
+
row = self._version_row(identifier, body["revision"])
|
|
43
|
+
try:
|
|
44
|
+
graph = self.owner.check_graph(json.loads(row[0]).get("graph"), identifier)
|
|
45
|
+
except ValueError as error:
|
|
46
|
+
raise APIError(f"That version can't run now: {error}", 409, "version_invalid") from None
|
|
47
|
+
updated = {**workflow, "published": {"revision": body["revision"], "graph": graph}}
|
|
48
|
+
self.owner.derive(updated)
|
|
49
|
+
next_at = self.owner._schedule(updated)
|
|
50
|
+
if any(updated.get(key) != workflow.get(key) for key in ("cron", "timezone")):
|
|
51
|
+
updated["nextAt"] = next_at if updated["enabled"] else None
|
|
52
|
+
with self.owner.journal.transaction() as connection:
|
|
53
|
+
self.owner._save(connection, updated)
|
|
54
|
+
connection.execute("UPDATE workflow_versions SET published_at=? WHERE workflow_id=? AND revision=?",
|
|
55
|
+
(self.owner.now(), identifier, body["revision"]))
|
|
56
|
+
self.owner.triggers.changed()
|
|
57
|
+
return self.owner.public(updated)
|
|
58
|
+
|
|
59
|
+
def keep(self, connection, workflow):
|
|
60
|
+
published = (workflow.get("published") or {}).get("revision") == workflow["revision"]
|
|
61
|
+
connection.execute("INSERT OR REPLACE INTO workflow_versions VALUES (?,?,?,?,?)",
|
|
62
|
+
(workflow["id"], workflow["revision"], workflow["updatedAt"], encode(workflow),
|
|
63
|
+
workflow["updatedAt"] if published else None))
|
|
64
|
+
connection.execute("""DELETE FROM workflow_versions WHERE workflow_id=? AND revision NOT IN (
|
|
65
|
+
SELECT revision FROM workflow_versions WHERE workflow_id=? ORDER BY revision DESC LIMIT ?)""",
|
|
66
|
+
(workflow["id"], workflow["id"], KEPT))
|
|
67
|
+
|
|
68
|
+
def versions(self, identifier):
|
|
69
|
+
"""GET /api/workflows/{id}/versions"""
|
|
70
|
+
with self.owner.journal.lock:
|
|
71
|
+
live = (self.owner._get(identifier).get("published") or {}).get("revision")
|
|
72
|
+
rows = self.owner.journal.connection.execute("""SELECT revision, saved_at, data, published_at FROM workflow_versions
|
|
73
|
+
WHERE workflow_id=? ORDER BY revision""", (identifier,)).fetchall() # noqa: E501
|
|
74
|
+
out, before = [], None
|
|
75
|
+
for revision, saved_at, data, published_at in rows:
|
|
76
|
+
graph = (json.loads(data).get("graph") or {})
|
|
77
|
+
out.append({"revision": revision, "savedAt": saved_at, "nodes": len(graph.get("nodes") or []),
|
|
78
|
+
"edges": len(graph.get("edges") or []), "change": edit.change_line(before, graph),
|
|
79
|
+
"published": published_at is not None, "live": revision == live})
|
|
80
|
+
before = graph
|
|
81
|
+
return out[::-1]
|
|
82
|
+
|
|
83
|
+
def diff(self, identifier, a, b):
|
|
84
|
+
"""GET /api/workflows/{id}/versions/{a}/diff/{b}: the nodes and edges added, removed and changed from a to b."""
|
|
85
|
+
return edit.diff(self.version(identifier, a)["graph"], self.version(identifier, b)["graph"])
|
|
86
|
+
|
|
87
|
+
def version(self, identifier, revision):
|
|
88
|
+
"""GET /api/workflows/{id}/versions/{revision}: that version as the API shows a workflow."""
|
|
89
|
+
with self.owner.journal.lock:
|
|
90
|
+
self.owner._get(identifier)
|
|
91
|
+
row = self._version_row(identifier, revision)
|
|
92
|
+
return self.owner.public(json.loads(row[0]))
|
|
93
|
+
|
|
94
|
+
def call_graph(self, identifier, revision, *, allow_draft=False):
|
|
95
|
+
"""The saved graph a child call may run, with publication provenance checked under the same lock."""
|
|
96
|
+
with self.owner.journal.lock:
|
|
97
|
+
self.owner._get(identifier)
|
|
98
|
+
row = self._version_row(identifier, revision)
|
|
99
|
+
if row[1] is None and not allow_draft:
|
|
100
|
+
raise APIError("Publish that saved version before another workflow runs it.", 409, "not_published")
|
|
101
|
+
return json.loads(row[0])["graph"]
|
|
102
|
+
|
|
103
|
+
def rollback(self, identifier, body):
|
|
104
|
+
"""POST /api/workflows/{id}/rollback {revision, current}: save version ``revision`` as the newest."""
|
|
105
|
+
if not isinstance(body, dict) or set(body) != {"revision", "current"} or any(
|
|
106
|
+
type(body[key]) is not int for key in body):
|
|
107
|
+
raise ValueError("Give the revision to bring back and the current revision")
|
|
108
|
+
with self.owner.lock, self.owner.journal.lock:
|
|
109
|
+
workflow = self.owner._get(identifier)
|
|
110
|
+
if body["current"] != workflow["revision"]:
|
|
111
|
+
raise APIError("The workflow changed. Reload before rolling it back.", 409, "revision_conflict")
|
|
112
|
+
row = self._version_row(identifier, body["revision"])
|
|
113
|
+
old = json.loads(row[0])
|
|
114
|
+
updated = dict(workflow)
|
|
115
|
+
for key in RESTORED:
|
|
116
|
+
if key in old:
|
|
117
|
+
updated[key] = old[key]
|
|
118
|
+
else:
|
|
119
|
+
updated.pop(key, None)
|
|
120
|
+
try:
|
|
121
|
+
updated["graph"] = self.owner.check_graph(old.get("graph"), identifier)
|
|
122
|
+
except ValueError as error:
|
|
123
|
+
raise APIError(f"That version can't be saved now: {error}", 409, "version_invalid") from None
|
|
124
|
+
updated["published"] = {"revision": workflow["revision"] + 1, "graph": updated["graph"]}
|
|
125
|
+
self.owner.derive(updated)
|
|
126
|
+
return self.owner._commit(workflow, updated)
|