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,614 @@
|
|
|
1
|
+
"""Detection and the plan card (DESIGN §3.5, §4.1): deterministic and model-free, so nothing is spent before Start."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import weakref
|
|
8
|
+
|
|
9
|
+
from . import budget as B
|
|
10
|
+
from . import costs as C
|
|
11
|
+
from .notebook import FEED_KINDS
|
|
12
|
+
|
|
13
|
+
KINDS = ("feed", "survey", "chain", "browse")
|
|
14
|
+
# The collections a survey drafts openers over (inbox.wants_openers): posts, results and rows (READ_EACH reads them
|
|
15
|
+
# as rows, which the extractor reads as openers).
|
|
16
|
+
OPENER_KINDS = ("row", "result", "post")
|
|
17
|
+
PLAN_TTL_S = 15 * 60
|
|
18
|
+
MAX_PLANS = 64
|
|
19
|
+
|
|
20
|
+
FEED_APPS = {"instagram": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
|
|
21
|
+
"ig": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
|
|
22
|
+
"reels": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
|
|
23
|
+
"tiktok": ("com.zhiliaoapp.musically", "TikTok", "tiktok", "TikToks"),
|
|
24
|
+
"tiktoks": ("com.zhiliaoapp.musically", "TikTok", "tiktok", "TikToks"),
|
|
25
|
+
"youtube shorts": ("com.google.ios.youtube", "YouTube", "short", "Shorts"),
|
|
26
|
+
"shorts": ("com.google.ios.youtube", "YouTube", "short", "Shorts"),
|
|
27
|
+
"youtube": ("com.google.ios.youtube", "YouTube", "short", "Shorts")}
|
|
28
|
+
FEED_NOUN = re.compile(r"\b(reels?|tiktoks?|youtube shorts|shorts|my feed|for you page|fyp)\b", re.I)
|
|
29
|
+
FEED_APP = re.compile(r"\b(instagram|ig|tiktok|youtube)\b", re.I)
|
|
30
|
+
WATCH_VERB = re.compile(r"\b(scroll(?:ing)?|doom ?scroll(?:ing)?|watch(?:ing)?|brows(?:e|ing)|go(?:ing)? through|"
|
|
31
|
+
r"look(?:ing)? through|binge)\b", re.I)
|
|
32
|
+
# A survey reads a collection item by item: these verbs always mean that over a collection...
|
|
33
|
+
STRONG_SURVEY = re.compile(r"\b(go(?:ing)? through|look(?:ing)? through|review(?:ing)?|rank(?:ing)?|shortlist|triage|"
|
|
34
|
+
r"sort through|find (?:the |my )?(?:best|most|top)|most promising|which of my)\b", re.I)
|
|
35
|
+
# ...these only with every, all, each or a count ("read all my emails", "check 10 listings").
|
|
36
|
+
WEAK_SURVEY = re.compile(r"\b(read(?:ing)?|check(?:ing)?|look(?:ing)? at|summari[sz]e)\b", re.I)
|
|
37
|
+
EVERY = re.compile(r"\b(all|every|each)\b", re.I)
|
|
38
|
+
# "error messages" or "status messages" name what a screen shows, not a collection to read (found 13 Oct: a build check
|
|
39
|
+
# that looks for "error messages" on each screen was planned as a survey of 40 messages).
|
|
40
|
+
COLLECTION = re.compile(r"(?<!error )(?<!errors )(?<!warning )(?<!status )(?<!system )(?<!validation )(?<!alert )"
|
|
41
|
+
r"\b(matches|profiles|dms|direct messages|messages|chats|conversations|emails|inbox|"
|
|
42
|
+
r"listings|results|reviews|options|restaurants|places)\b", re.I)
|
|
43
|
+
# Verbs that change things: a bulk change is never a long read.
|
|
44
|
+
# A possessive ("the post's link", found 14 Oct) is a noun, never the verb.
|
|
45
|
+
CHANGE_VERB = re.compile(r"\b(delete|archive|remove|unsubscribe|mark|move|like|follow|unfollow|send|reply to all|"
|
|
46
|
+
r"block|report|unmatch|swipe (?:left|right)|post|pay|buy|order|favou?rite|hide|mute|accept|"
|
|
47
|
+
r"decline|approve|skip)\b(?!['’]s\b)", re.I)
|
|
48
|
+
# Words that say the person wants to be told something: with them, a change verb doesn't make a request a loop.
|
|
49
|
+
REPORT_INTENT = re.compile(r"\b(find|which|rank|shortlist|summari[sz]e|summary|draft|tell me|send me|what|best|most|"
|
|
50
|
+
r"promising|learn(?:ed|t)?|write up|list (?:each|every|all))\b", re.I)
|
|
51
|
+
BROWSE_VERB = re.compile(r"\b(scroll(?:ing)?|doom ?scroll(?:ing)?|brows(?:e|ing)|watch(?:ing)?|read(?:ing)?|"
|
|
52
|
+
r"look(?:ing)? (?:at|through)|go(?:ing)? through|explore|check out)\b", re.I)
|
|
53
|
+
RESEARCH = re.compile(r"\b(best|top \d+|compare|comparing|across|options|cheapest|highest[- ]rated|most promising)\b",
|
|
54
|
+
re.I)
|
|
55
|
+
CHAIN_SPLIT = re.compile(r"\s*(?:,?\s*\band then\b|,?\s*\bthen\b|"
|
|
56
|
+
r",?\s*\bafter that\b|;|\n\s*\d+[.)]\s+|^\s*\d+[.)]\s+)\s*",
|
|
57
|
+
re.I)
|
|
58
|
+
VERB = re.compile(r"^(?:and\s+)?(?:please\s+)?([a-z]+)", re.I)
|
|
59
|
+
FOCUS = (re.compile(r"\babout ([a-z0-9][\w' &-]{1,40}?)(?=\s+(?:for|and|then|on|in|from|to|,)\b|[,.;!?]|$)", re.I),
|
|
60
|
+
re.compile(r"\bon ([a-z0-9][\w' &-]{1,30}?) (?:reels|tiktoks|videos|shorts)\b", re.I),
|
|
61
|
+
re.compile(r"\b([a-z][\w'-]{2,24}(?: [a-z][\w'-]{2,24})?) (?:reels|tiktoks|videos|shorts)\b", re.I))
|
|
62
|
+
# One video named on its own ("the Reel that's open", "this TikTok", "a short about cats", "1 reel"): a normal task that
|
|
63
|
+
# watches it, never a feed plan. A plural ("Reels", "my feed") or a time budget is a feed.
|
|
64
|
+
ONE_ITEM = re.compile(r"\b(?:the|this|that|one|a|1)\s+(?:(?:open|current|next|first|last|same|playing)\s+)?"
|
|
65
|
+
r"(?:reel|tiktok|youtube short|short|video)\b", re.I)
|
|
66
|
+
MANY_ITEMS = re.compile(r"\b(?:reels|tiktoks|shorts|videos|my feed|for you page|fyp)\b", re.I)
|
|
67
|
+
NOT_FOCUS = re.compile(r"^(instagram|ig|tiktok|youtube|my|the|some|more|short|all|every|few|watch|scroll|doomscroll|"
|
|
68
|
+
r"go|through|see|ten|\d+.*|the next|next|first|top)$", re.I)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Apps named with an everyday word (orchestrate._EVERYDAY): after another app's name, the word is that app's noun.
|
|
72
|
+
EVERYDAY_NAMES = frozenset({"news", "photos", "music", "clock", "files", "phone", "mail", "messages", "notes", "maps",
|
|
73
|
+
"books", "health", "wallet", "camera", "weather", "calendar", "contacts", "reminders"})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fold(text):
|
|
77
|
+
return " ".join(str(text or "").split())
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def chain_parts(goal):
|
|
81
|
+
"""The request's parts in order, split on then / and then / after that / ; / a numbered list."""
|
|
82
|
+
parts = [p.strip(" ,.") for p in CHAIN_SPLIT.split(_fold(goal) if "\n" not in str(goal) else str(goal))]
|
|
83
|
+
return [p for p in parts if p and len(p.split()) >= 2]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _verb(part):
|
|
87
|
+
found = VERB.match(part)
|
|
88
|
+
return found.group(1).casefold() if found else ""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def named_apps(goal, apps):
|
|
92
|
+
"""[{bundleId, name}] of ``apps`` the request names (whole words), in the request's order."""
|
|
93
|
+
folded = _fold(goal).casefold()
|
|
94
|
+
hits = []
|
|
95
|
+
for app in apps or ():
|
|
96
|
+
name = str(app.get("name") or "")
|
|
97
|
+
if len(name) > 2 and app.get("bundleId"):
|
|
98
|
+
hit = re.search(rf"\b{re.escape(name.casefold())}\b", folded)
|
|
99
|
+
if hit:
|
|
100
|
+
hits.append((hit.start(), hit.end(), {"bundleId": app["bundleId"], "name": name}))
|
|
101
|
+
found = []
|
|
102
|
+
for start, end, app in hits:
|
|
103
|
+
if app["name"].casefold() in EVERYDAY_NAMES and any(
|
|
104
|
+
other is not app and re.fullmatch(r"(?:['’]s)?\s+", folded[other_end:start])
|
|
105
|
+
for _, other_end, other in hits):
|
|
106
|
+
continue
|
|
107
|
+
found.append((start, app))
|
|
108
|
+
found.sort(key=lambda pair: pair[0])
|
|
109
|
+
out, seen = [], set()
|
|
110
|
+
for _, app in found:
|
|
111
|
+
if app["bundleId"] not in seen:
|
|
112
|
+
seen.add(app["bundleId"])
|
|
113
|
+
out.append(app)
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def one_item(text, parsed=None):
|
|
118
|
+
"""Whether the request is about one video ("Watch the Reel that's open"): no feed plan for it (real-device QA,
|
|
119
|
+
13 Oct: one open Reel got "Watches about 30 Reels")."""
|
|
120
|
+
if parsed is not None and not (parsed.kind == "items" and parsed.items == 1):
|
|
121
|
+
return False
|
|
122
|
+
return bool(ONE_ITEM.search(text)) and not MANY_ITEMS.search(text)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _single(goal, parsed):
|
|
126
|
+
"""feed | survey | browse | None for one part of a request."""
|
|
127
|
+
text = _fold(goal)
|
|
128
|
+
if CHANGE_VERB.search(text) and not REPORT_INTENT.search(text):
|
|
129
|
+
return None
|
|
130
|
+
collection = COLLECTION.search(text)
|
|
131
|
+
counted = parsed is not None and parsed.kind in ("items", "all")
|
|
132
|
+
one = one_item(text, parsed)
|
|
133
|
+
if FEED_NOUN.search(text) and (WATCH_VERB.search(text) or parsed is not None) and not one:
|
|
134
|
+
return "feed"
|
|
135
|
+
if collection and (STRONG_SURVEY.search(text) or WEAK_SURVEY.search(text) and (EVERY.search(text) or counted)):
|
|
136
|
+
return "survey"
|
|
137
|
+
if FEED_APP.search(text) and WATCH_VERB.search(text) and not collection and not one:
|
|
138
|
+
return "feed"
|
|
139
|
+
if parsed is not None and parsed.kind in ("time", "items") and BROWSE_VERB.search(text) and not one:
|
|
140
|
+
return "feed" if FEED_APP.search(text) else "browse"
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def detect(goal, apps=()):
|
|
145
|
+
""""feed" | "survey" | "chain" | "browse" | None."""
|
|
146
|
+
text = _fold(goal)
|
|
147
|
+
if not text:
|
|
148
|
+
return None
|
|
149
|
+
parsed = B.parse_budget(text)
|
|
150
|
+
parts = chain_parts(goal)
|
|
151
|
+
if len(parts) >= 2:
|
|
152
|
+
verbs = {_verb(p) for p in parts}
|
|
153
|
+
long_part = any(_single(p, B.parse_budget(p)) for p in parts)
|
|
154
|
+
if len(verbs) >= 2 and (long_part or RESEARCH.search(text) or len(_part_apps(parts, apps)) >= 2):
|
|
155
|
+
return "chain"
|
|
156
|
+
# A goal that names two or more apps and passes nothing between them ("go through my messages across Messages,
|
|
157
|
+
# WhatsApp and Instagram") is one plan across apps, the orchestrator's (orchestrate.apps_plan), not a long read of
|
|
158
|
+
# one of them.
|
|
159
|
+
from ..catalog import APPS
|
|
160
|
+
from ..orchestrate import named_apps
|
|
161
|
+
if len(named_apps(goal, [a for a in apps or () if a.get("name")] or APPS)) >= 2:
|
|
162
|
+
return None
|
|
163
|
+
return _single(text, parsed)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _part_app(part, apps):
|
|
167
|
+
"""The app one part of a request works in (app_choice.infer_app over the phone's apps, else the catalog), or
|
|
168
|
+
None: "check my calendar for tomorrow" is Calendar, "text the first attendee" is Messages."""
|
|
169
|
+
from ..app_choice import infer_app
|
|
170
|
+
from ..catalog import APPS
|
|
171
|
+
known = [a for a in apps or () if a.get("id") and a.get("name")] or APPS
|
|
172
|
+
try:
|
|
173
|
+
found = infer_app(part, known)
|
|
174
|
+
except Exception: # noqa: BLE001 -- no app: the part names none
|
|
175
|
+
return None
|
|
176
|
+
return found if isinstance(found, dict) else None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _part_apps(parts, apps):
|
|
180
|
+
"""The distinct apps a request's parts work in, in order."""
|
|
181
|
+
out = []
|
|
182
|
+
for part in parts:
|
|
183
|
+
app = _part_app(part, apps)
|
|
184
|
+
if app is not None and app.get("bundleId") not in [a.get("bundleId") for a in out]:
|
|
185
|
+
out.append(app)
|
|
186
|
+
return out
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def focus_of(goal):
|
|
190
|
+
"""What the feed is about ("basketball"), or None."""
|
|
191
|
+
text = _fold(goal)
|
|
192
|
+
for pattern in FOCUS:
|
|
193
|
+
for found in pattern.finditer(text):
|
|
194
|
+
words = found.group(1).strip(" '-")
|
|
195
|
+
words = re.sub(r"^(?:(?:the|some|my|scroll|scrolling|doomscroll|watch|watching|browse|instagram|ig|tiktok|"
|
|
196
|
+
r"youtube|through|more)\s+)+", "", words, flags=re.I)
|
|
197
|
+
if words and not NOT_FOCUS.match(words) and not FEED_APP.fullmatch(words):
|
|
198
|
+
return words.casefold()
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _feed_app(goal, apps):
|
|
203
|
+
"""(bundleId, name, item kind, noun) of the feed the request means."""
|
|
204
|
+
folded = _fold(goal).casefold()
|
|
205
|
+
for word, value in FEED_APPS.items():
|
|
206
|
+
if re.search(rf"\b{re.escape(word)}\b", folded):
|
|
207
|
+
bundle, name = value[0], value[1]
|
|
208
|
+
installed = next((a for a in apps or () if a.get("bundleId") == bundle), None)
|
|
209
|
+
return bundle, (installed or {}).get("name") or name, value[2], value[3]
|
|
210
|
+
named = named_apps(goal, apps)
|
|
211
|
+
if named:
|
|
212
|
+
return named[0]["bundleId"], named[0]["name"], "reel", "videos"
|
|
213
|
+
return "com.burbn.instagram", "Instagram", "reel", "Reels"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _survey_app(goal, apps):
|
|
217
|
+
"""(bundleId, name) of the app a survey reads in: one the request names among the phone's apps, else the one
|
|
218
|
+
app_choice infers from the request (the catalog's names and words), else (None, None)."""
|
|
219
|
+
named = named_apps(goal, apps)
|
|
220
|
+
if named:
|
|
221
|
+
return named[0]["bundleId"], named[0]["name"]
|
|
222
|
+
inferred = _part_app(goal, apps)
|
|
223
|
+
if inferred is not None and inferred.get("bundleId"):
|
|
224
|
+
return inferred["bundleId"], inferred.get("name")
|
|
225
|
+
return None, None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _collection(goal):
|
|
229
|
+
found = COLLECTION.search(_fold(goal))
|
|
230
|
+
return found.group(1).casefold() if found else "items"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _models(nav_model, kind, items):
|
|
234
|
+
from .models import extract_model, synth_model_for
|
|
235
|
+
return {"navigate": nav_model, "extract": extract_model(nav_model), "report": synth_model_for(kind, items,
|
|
236
|
+
nav_model)}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# A collection the person keeps ("my saved posts", "the reels I liked"): the plan goes there, not to the app's feed.
|
|
240
|
+
KEPT = re.compile(r"\b(saved|bookmarked|liked|favou?rited?)\s+(posts|reels|videos|tiktoks|shorts|pins|photos)\b|"
|
|
241
|
+
r"\b(posts|reels|videos|tiktoks|shorts|pins|photos)\s+(?:that\s+)?i(?:'ve|\s+have)?\s+"
|
|
242
|
+
r"(saved|bookmarked|liked)\b", re.I)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def kept_collection(goal):
|
|
246
|
+
""""your saved posts" for a request over a collection the person kept, else None."""
|
|
247
|
+
found = KEPT.search(_fold(goal))
|
|
248
|
+
if not found:
|
|
249
|
+
return None
|
|
250
|
+
how, noun = (found.group(1), found.group(2)) if found.group(1) else (found.group(4), found.group(3))
|
|
251
|
+
return f"your {how.casefold()} {noun.casefold()}"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _steps(kind, goal, apps, budget, focus):
|
|
255
|
+
if kind == "feed":
|
|
256
|
+
_bundle, name, _, _ = _feed_app(goal, apps)
|
|
257
|
+
kept = kept_collection(goal)
|
|
258
|
+
where = f"find {focus} {budget.item_noun}" if focus else f"go to {kept or budget.item_noun}"
|
|
259
|
+
count = f"about {budget.items}" if budget.items else f"for {B.minutes_label(budget.minutes or 10)}"
|
|
260
|
+
verb = "Go through" if kept else "Watch"
|
|
261
|
+
return [{"id": 1, "title": f"Open {name} and {where}", "app": name},
|
|
262
|
+
{"id": 2, "title": f"{verb} {count} {budget.item_noun}" if budget.items else
|
|
263
|
+
f"{verb} {budget.item_noun} {count}", "app": name},
|
|
264
|
+
{"id": 3, "title": "Write up what it learned", "app": None}]
|
|
265
|
+
if kind == "survey":
|
|
266
|
+
_bundle, name = _survey_app(goal, apps)
|
|
267
|
+
place = name or "the app"
|
|
268
|
+
collection = _collection(goal)
|
|
269
|
+
collection = {"dms": "your DMs", "matches": "your matches", "messages": "your messages",
|
|
270
|
+
"chats": "your chats", "emails": "your inbox", "inbox": "your inbox"}.get(collection,
|
|
271
|
+
f"the {collection}")
|
|
272
|
+
from .inbox import wants_drafts, wants_openers
|
|
273
|
+
sweep = wants_drafts(goal) and budget.item_kind in ("chat", "row")
|
|
274
|
+
openers = wants_openers(goal) and budget.item_kind in OPENER_KINDS
|
|
275
|
+
return [{"id": 1, "title": f"Open {place} and go to {collection}", "app": name},
|
|
276
|
+
{"id": 2, "title": f"Read each of up to {budget.items} {budget.item_noun}", "app": name},
|
|
277
|
+
{"id": 3, "title": "Sort them and draft replies, sending nothing" if sweep else
|
|
278
|
+
"Draft a DM to each person who fits, sending nothing" if openers else
|
|
279
|
+
"Rank them and write it up", "app": None}]
|
|
280
|
+
if kind == "chain":
|
|
281
|
+
out = []
|
|
282
|
+
for index, part in enumerate(chain_parts(goal)[:8], 1):
|
|
283
|
+
named = named_apps(part, apps)
|
|
284
|
+
inferred = None if named else _part_app(part, apps)
|
|
285
|
+
app = named[0]["name"] if named else (inferred or {}).get("name")
|
|
286
|
+
title = part[:1].upper() + part[1:]
|
|
287
|
+
out.append({"id": index, "title": title[:120], "app": app})
|
|
288
|
+
return out
|
|
289
|
+
named = named_apps(goal, apps)
|
|
290
|
+
name = named[0]["name"] if named else None
|
|
291
|
+
return [{"id": 1, "title": f"Browse {name}" if name else "Browse", "app": name},
|
|
292
|
+
{"id": 2, "title": "Write up what it found", "app": None}]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# A commit the request asks for is a verb that opens a clause: "and like the best ones", "then book Friday at 8",
|
|
296
|
+
# "reply to each one", "Text Sam ...". "I'd like to know" or "send me a summary" are not commits.
|
|
297
|
+
CLAUSE = r"(?:^|[,;.:!?]\s*|\b(?:and|then|also|please|to|after that)\s+)"
|
|
298
|
+
NOT_COMMIT = re.compile(r"^(?:send|share|post) (?:me|it to me|them to me|us|what|back)\b", re.I)
|
|
299
|
+
MESSAGE_WORDS = ("text", "message", "dm", "email", "e-mail", "imessage")
|
|
300
|
+
# The act a verb means when several families hold it; else the first act (by name) whose family does.
|
|
301
|
+
PREFERRED = {"reply": "reply", "comment": "comment", "like": "react", "react": "react", "send": "send_message",
|
|
302
|
+
"post": "post", "publish": "post", "tweet": "post", "buy": "order", "purchase": "order",
|
|
303
|
+
"confirm": "other", "submit": "other", "request": "request_money", "share": "share",
|
|
304
|
+
**{word: "send_message" for word in MESSAGE_WORDS}}
|
|
305
|
+
# What a card says it asks before: "Asks before each reply." for a feed, survey or browse; "Asks before it books." for
|
|
306
|
+
# a chain.
|
|
307
|
+
EACH_NOUN = {"reply": "reply", "comment": "comment", "react": "like", "send_message": "message", "post": "post",
|
|
308
|
+
"share": "share", "follow": "follow", "unfollow": "unfollow", "book": "booking", "order": "order",
|
|
309
|
+
"pay": "payment", "transfer": "transfer", "request_money": "money request", "call": "call",
|
|
310
|
+
"delete": "deletion", "cancel": "cancellation", "install": "install", "other": "action"}
|
|
311
|
+
CHAIN_VERB = {"send_message": "send", "react": "like", "other": "confirm"}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _commit_verbs():
|
|
315
|
+
"""[(verb, act)] for every act that asks (contract_asks.ASK_ACTS, "like" among them) and the message verbs, longest
|
|
316
|
+
first."""
|
|
317
|
+
from ..contract_asks import ASK_ACTS
|
|
318
|
+
from ..contract_screen import FAMILIES
|
|
319
|
+
verbs = {}
|
|
320
|
+
for act in sorted(ASK_ACTS):
|
|
321
|
+
for verb in FAMILIES.get(act, ()):
|
|
322
|
+
if verb != "get":
|
|
323
|
+
verbs.setdefault(verb, PREFERRED.get(verb, act))
|
|
324
|
+
for word in MESSAGE_WORDS:
|
|
325
|
+
verbs.setdefault(word, "send_message")
|
|
326
|
+
return sorted(verbs.items(), key=lambda pair: -len(pair[0]))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# A negation that opens a list of acts the request rules out: "don't like, follow, reply to or message anyone",
|
|
330
|
+
# "never post", "without liking or replying". Only the verbs listed right after it are ruled out ("don't forget to send
|
|
331
|
+
# it" rules nothing out, and "if it's not there, book it" still books).
|
|
332
|
+
NEGATION = re.compile(r"\b(?:don['’]t|dont|do not|never|without|no need to|nor)\s+(?:to\s+)?", re.I)
|
|
333
|
+
LIST_GAP = re.compile(r"\s*(?:,\s*(?:or\s+|and\s+|nor\s+)?|\s+or\s+|\s+and\s+|\s+nor\s+|/)\s*")
|
|
334
|
+
PARTICLE = re.compile(r"\s+(?:to|on|at|back|out)\b")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _ing(verb):
|
|
338
|
+
head, _, rest = verb.partition(" ")
|
|
339
|
+
stem = head[:-1] if head.endswith("e") and not head.endswith("ee") else head
|
|
340
|
+
return (stem + "ing") + (" " + rest if rest else "")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def negated_spans(folded):
|
|
344
|
+
"""[(start, end)] of the verbs a negation rules out in ``folded`` (casefolded text)."""
|
|
345
|
+
words = sorted({form for verb, _ in _commit_verbs() for form in (verb, _ing(verb))}, key=len, reverse=True)
|
|
346
|
+
verb = re.compile("(?:" + "|".join(re.escape(w) for w in words) + r")\b")
|
|
347
|
+
spans = []
|
|
348
|
+
for cue in NEGATION.finditer(folded):
|
|
349
|
+
at = cue.end()
|
|
350
|
+
while True:
|
|
351
|
+
hit = verb.match(folded, at)
|
|
352
|
+
if hit is None:
|
|
353
|
+
break
|
|
354
|
+
spans.append((hit.start(), hit.end()))
|
|
355
|
+
at = hit.end()
|
|
356
|
+
particle = PARTICLE.match(folded, at)
|
|
357
|
+
if particle is not None:
|
|
358
|
+
at = particle.end()
|
|
359
|
+
gap = LIST_GAP.match(folded, at)
|
|
360
|
+
if gap is None:
|
|
361
|
+
break
|
|
362
|
+
at = gap.end()
|
|
363
|
+
return spans
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def commits_in(text):
|
|
367
|
+
"""[(act, verb)] the text asks Mobster to commit, in the text's order (each act once)."""
|
|
368
|
+
folded = _fold(text).casefold()
|
|
369
|
+
ruled_out = negated_spans(folded)
|
|
370
|
+
found = []
|
|
371
|
+
for verb, act in _commit_verbs():
|
|
372
|
+
for hit in re.finditer(CLAUSE + "(" + re.escape(verb) + r")\b", folded):
|
|
373
|
+
if NOT_COMMIT.match(folded[hit.start(1):]):
|
|
374
|
+
continue
|
|
375
|
+
if any(start <= hit.start(1) < end for start, end in ruled_out):
|
|
376
|
+
continue
|
|
377
|
+
found.append((hit.start(1), act, verb))
|
|
378
|
+
break
|
|
379
|
+
out, acts = [], set()
|
|
380
|
+
for _, act, verb in sorted(found):
|
|
381
|
+
if act not in acts:
|
|
382
|
+
acts.add(act)
|
|
383
|
+
out.append((act, verb))
|
|
384
|
+
return out
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def bulk_change(goal):
|
|
388
|
+
"""A request that changes things and asks to be told nothing ("delete every email")."""
|
|
389
|
+
text = _fold(goal)
|
|
390
|
+
return bool(CHANGE_VERB.search(text) and not REPORT_INTENT.search(text))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _commits(kind, goal, steps):
|
|
394
|
+
"""[{act, app, verb}] the task will commit: every part of a chain (each with its step's app), the whole request
|
|
395
|
+
for a feed, survey or browse."""
|
|
396
|
+
out = []
|
|
397
|
+
if kind == "chain":
|
|
398
|
+
parts = chain_parts(goal)
|
|
399
|
+
for index, part in enumerate(parts):
|
|
400
|
+
step = steps[index] if index < len(steps) else {}
|
|
401
|
+
app = step.get("app") or next((s.get("app") for s in reversed(steps) if s.get("app")), None)
|
|
402
|
+
out += [{"act": act, "app": app, "verb": verb} for act, verb in commits_in(part)
|
|
403
|
+
if act not in [c["act"] for c in out]]
|
|
404
|
+
return out
|
|
405
|
+
app = next((s.get("app") for s in steps if s.get("app")), None)
|
|
406
|
+
return [{"act": act, "app": app, "verb": verb} for act, verb in commits_in(goal)]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def commit_lines(kind, commits):
|
|
410
|
+
"""The card's lines for its commits: "Asks before each reply." (a feed, survey or browse repeats the act),
|
|
411
|
+
"Asks before it books." (a chain's step)."""
|
|
412
|
+
lines = []
|
|
413
|
+
for commit in commits[:3]:
|
|
414
|
+
if kind == "chain":
|
|
415
|
+
verb = CHAIN_VERB.get(commit["act"]) or commit["verb"]
|
|
416
|
+
lines.append(f"Asks before it {_third_person(verb)}.")
|
|
417
|
+
else:
|
|
418
|
+
lines.append(f"Asks before each {EACH_NOUN.get(commit['act'], 'action')}.")
|
|
419
|
+
return list(dict.fromkeys(lines))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _third_person(verb):
|
|
423
|
+
if " " in verb:
|
|
424
|
+
head, _, rest = verb.partition(" ")
|
|
425
|
+
return f"{_third_person(head)} {rest}"
|
|
426
|
+
if re.search(r"[^aeiou]y$", verb):
|
|
427
|
+
return verb[:-1] + "ies"
|
|
428
|
+
if re.search(r"(s|sh|ch|x|z)$", verb):
|
|
429
|
+
return verb + "es"
|
|
430
|
+
return verb + "s"
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _summary(kind, goal, budget, focus, steps):
|
|
434
|
+
if kind == "feed":
|
|
435
|
+
topic = f"{focus} " if focus else ""
|
|
436
|
+
kept = kept_collection(goal)
|
|
437
|
+
if kept and budget.items and not budget.strict:
|
|
438
|
+
return f"Goes through up to {budget.items} of {kept}, then writes up what it found."
|
|
439
|
+
if budget.strict:
|
|
440
|
+
return (f"Watches {topic}{budget.item_noun} for the full {B.minutes_label(budget.minutes)}, "
|
|
441
|
+
"then writes up what it learned.")
|
|
442
|
+
said = (f", the number you'd scroll in {B.minutes_label(budget.minutes)}" if budget.kind == "time" else "")
|
|
443
|
+
return f"Watches about {budget.items} {topic}{budget.item_noun}{said}, then writes up what it learned."
|
|
444
|
+
if kind == "survey":
|
|
445
|
+
place = next((s["app"] for s in steps if s.get("app")), None)
|
|
446
|
+
where = f" in {place}" if place else ""
|
|
447
|
+
from .inbox import wants_drafts, wants_openers
|
|
448
|
+
if wants_drafts(goal) and budget.item_kind in ("chat", "row"):
|
|
449
|
+
# An inbox sweep (longform/inbox.py): drafts for review, none sent.
|
|
450
|
+
return (f"Reads up to {budget.items} {budget.item_noun}{where} and drafts replies to the ones that need "
|
|
451
|
+
"one, for you to review. Nothing is sent.")
|
|
452
|
+
if wants_openers(goal) and budget.item_kind in OPENER_KINDS:
|
|
453
|
+
# Openers (longform/inbox.py): a first message to each person who fits, for review, none sent.
|
|
454
|
+
return (f"Reads up to {budget.items} {budget.item_noun}{where} and drafts a DM to each person who fits, "
|
|
455
|
+
"for you to review. Nothing is sent.")
|
|
456
|
+
return f"Reads up to {budget.items} {budget.item_noun}{where}, then ranks the most promising and says why."
|
|
457
|
+
if kind == "chain":
|
|
458
|
+
return f"Does {len(steps)} steps in order, with a checkpoint after each."
|
|
459
|
+
return f"Browses for about {budget.items or 20} {budget.item_noun}, then writes up what it found."
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def plan_id(goal, kind, budget, limit_usd, nav_model):
|
|
463
|
+
raw = f"{_fold(goal)}|{kind}|{budget.to_dict()}|{limit_usd}|{nav_model}"
|
|
464
|
+
return "p_" + hashlib.sha1(raw.encode()).hexdigest()[:10]
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def make_plan(goal, *, apps=(), limit_usd=None, nav_model="claude-sonnet-5-5",
|
|
468
|
+
kind=None, change=None, alternative=None, now=None):
|
|
469
|
+
"""The plan card (a dict, DESIGN §4.1) for ``goal``, or None if it isn't a long task and ``kind`` isn't forced."""
|
|
470
|
+
goal = _fold(goal)
|
|
471
|
+
kind = kind or detect(goal, apps)
|
|
472
|
+
if kind is None:
|
|
473
|
+
return None
|
|
474
|
+
if kind not in KINDS:
|
|
475
|
+
raise ValueError("kind is feed, survey, chain or browse")
|
|
476
|
+
parsed = B.parse_budget(goal)
|
|
477
|
+
if kind == "feed":
|
|
478
|
+
_bundle, _name, item_kind, noun = _feed_app(goal, apps)
|
|
479
|
+
if parsed is not None and parsed.item_noun not in ("items",) and parsed.item_kind in FEED_KINDS:
|
|
480
|
+
noun = parsed.item_noun if parsed.item_noun != "videos" else noun
|
|
481
|
+
elif kind == "survey":
|
|
482
|
+
item_kind, noun = B.noun_in(goal)
|
|
483
|
+
if item_kind is None:
|
|
484
|
+
item_kind, noun = "row", "items"
|
|
485
|
+
else:
|
|
486
|
+
item_kind, noun = B.noun_in(goal)
|
|
487
|
+
item_kind, noun = (item_kind or "result"), (noun or "results")
|
|
488
|
+
budget = B.for_kind(kind, parsed, item_kind, noun)
|
|
489
|
+
if alternative == "full" and budget.minutes:
|
|
490
|
+
budget = B.time_budget(budget.minutes, budget.item_kind, budget.item_noun, strict=True)
|
|
491
|
+
elif alternative == "more" and budget.kind == "all":
|
|
492
|
+
budget = B.all_budget(budget.item_kind, budget.item_noun, B.ALL_MAX)
|
|
493
|
+
budget = B.override(budget, change)
|
|
494
|
+
focus = focus_of(goal) if kind in ("feed", "browse") else None
|
|
495
|
+
steps = _steps(kind, goal, apps, budget, focus)
|
|
496
|
+
models = _models(nav_model, kind, budget.items or 60)
|
|
497
|
+
estimate = C.estimate(kind, items=budget.items, item_kind=budget.item_kind, steps=len(steps),
|
|
498
|
+
nav_model=nav_model, synth_model=models["report"],
|
|
499
|
+
phone_seconds_cap=budget.phone_seconds_cap)
|
|
500
|
+
alternatives = []
|
|
501
|
+
if kind in ("feed", "browse") and budget.kind == "time" and not budget.strict:
|
|
502
|
+
full = C.estimate(kind, items=None, item_kind=budget.item_kind, steps=len(steps), nav_model=nav_model,
|
|
503
|
+
synth_model=models["report"], phone_seconds_cap=budget.phone_seconds_cap)
|
|
504
|
+
alternatives.append({"id": "full", "label": f"Use the full {B.minutes_label(budget.minutes)}", "items": None,
|
|
505
|
+
"phoneSeconds": round(budget.phone_seconds_cap), "lowUsd": full["lowUsd"],
|
|
506
|
+
"highUsd": full["highUsd"]})
|
|
507
|
+
if budget.kind == "all" and (budget.items or 0) < B.ALL_MAX:
|
|
508
|
+
more = C.estimate(kind, items=B.ALL_MAX, item_kind=budget.item_kind, steps=len(steps), nav_model=nav_model,
|
|
509
|
+
synth_model=models["report"])
|
|
510
|
+
alternatives.append({"id": "more", "label": f"Up to {B.ALL_MAX} {budget.item_noun}", "items": B.ALL_MAX,
|
|
511
|
+
"phoneSeconds": more["phoneSeconds"], "lowUsd": more["lowUsd"],
|
|
512
|
+
"highUsd": more["highUsd"]})
|
|
513
|
+
cap = cap_check(estimate["highUsd"], limit_usd)
|
|
514
|
+
for option in alternatives:
|
|
515
|
+
# Each alternative carries its own cap check: the card proposes the raise for the one the person picks.
|
|
516
|
+
option.update({k: v for k, v in cap_check(option["highUsd"], limit_usd).items() if k != "limitUsd"})
|
|
517
|
+
commits = _commits(kind, goal, steps)
|
|
518
|
+
# A long run asks before every act that asks, with Ask before acting off too (frontier._long_asks), so the card
|
|
519
|
+
# says so whatever the setting.
|
|
520
|
+
policy_lines = {"readOnly": not commits, "lines": commit_lines(kind, commits)}
|
|
521
|
+
now = time.time() if now is None else now
|
|
522
|
+
card = {"id": plan_id(goal, kind, budget, limit_usd, nav_model), "kind": kind, "title": goal[:300],
|
|
523
|
+
"focus": focus, "summary": _summary(kind, goal, budget, focus, steps),
|
|
524
|
+
"apps": list(_plan_apps(kind, goal, apps)), "steps": steps, "budget": budget.public(),
|
|
525
|
+
"alternatives": alternatives,
|
|
526
|
+
"estimate": {k: estimate[k] for k in ("phoneSeconds", "reportSeconds", "usd", "lowUsd", "highUsd")},
|
|
527
|
+
"cap": cap, "policy": policy_lines, "commits": [{"act": c["act"], "app": c["app"]} for c in commits],
|
|
528
|
+
"models": models, "expiresAt": int((now + PLAN_TTL_S) * 1000)}
|
|
529
|
+
card["_budget"] = budget.to_dict()
|
|
530
|
+
return card
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def cap_check(high_usd, limit_usd):
|
|
534
|
+
"""{"limitUsd", "exceeds", "proposedUsd"}: a plan whose high estimate passes the per-task limit proposes a raise to
|
|
535
|
+
ceil_to_0.50(1.2 x high), which the person switches on before Start."""
|
|
536
|
+
exceeds = limit_usd is not None and high_usd > limit_usd
|
|
537
|
+
return {"limitUsd": limit_usd, "exceeds": bool(exceeds),
|
|
538
|
+
"proposedUsd": C.ceil_half(1.2 * high_usd) if exceeds else None}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _plan_apps(kind, goal, apps):
|
|
542
|
+
named = named_apps(goal, apps)
|
|
543
|
+
if named:
|
|
544
|
+
return named
|
|
545
|
+
if kind == "feed":
|
|
546
|
+
bundle, name, _, _ = _feed_app(goal, apps)
|
|
547
|
+
return [{"bundleId": bundle, "name": name}]
|
|
548
|
+
if kind == "survey":
|
|
549
|
+
bundle, name = _survey_app(goal, apps)
|
|
550
|
+
return [{"bundleId": bundle, "name": name}] if bundle else []
|
|
551
|
+
return []
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def public(card):
|
|
555
|
+
"""The card without its private keys (``_budget``)."""
|
|
556
|
+
return {k: v for k, v in (card or {}).items() if not k.startswith("_")}
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def budget_of(card):
|
|
560
|
+
return B.Budget.from_dict(card["_budget"]) if card and card.get("_budget") else None
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class PlanStore:
|
|
564
|
+
"""Plans shown on cards, per Runtime: at most MAX_PLANS, each for PLAN_TTL_S."""
|
|
565
|
+
|
|
566
|
+
def __init__(self):
|
|
567
|
+
self.plans = {}
|
|
568
|
+
self.lock = threading.Lock()
|
|
569
|
+
|
|
570
|
+
def put(self, card, now=None):
|
|
571
|
+
now = time.time() if now is None else now
|
|
572
|
+
with self.lock:
|
|
573
|
+
self.plans = {k: v for k, v in self.plans.items() if v[0] > now}
|
|
574
|
+
self.plans[card["id"]] = (now + PLAN_TTL_S, card)
|
|
575
|
+
while len(self.plans) > MAX_PLANS:
|
|
576
|
+
oldest = min(self.plans, key=lambda key: self.plans[key][0])
|
|
577
|
+
del self.plans[oldest]
|
|
578
|
+
return card
|
|
579
|
+
|
|
580
|
+
def get(self, plan_id, now=None):
|
|
581
|
+
now = time.time() if now is None else now
|
|
582
|
+
with self.lock:
|
|
583
|
+
found = self.plans.get(plan_id)
|
|
584
|
+
if found is None or found[0] <= now:
|
|
585
|
+
self.plans.pop(plan_id, None)
|
|
586
|
+
return None
|
|
587
|
+
return found[1]
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
_stores = weakref.WeakKeyDictionary()
|
|
591
|
+
_stores_lock = threading.Lock()
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def store_for(runtime):
|
|
595
|
+
with _stores_lock:
|
|
596
|
+
store = _stores.get(runtime)
|
|
597
|
+
if store is None:
|
|
598
|
+
store = _stores[runtime] = PlanStore()
|
|
599
|
+
return store
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def nav_model():
|
|
603
|
+
from ..engines import smart_model
|
|
604
|
+
try:
|
|
605
|
+
return smart_model()
|
|
606
|
+
except Exception: # noqa: BLE001
|
|
607
|
+
return "claude-sonnet-5-5"
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def runtime_apps(runtime):
|
|
611
|
+
try:
|
|
612
|
+
return [a for a in runtime.apps() if a.get("installed") is not False]
|
|
613
|
+
except Exception: # noqa: BLE001
|
|
614
|
+
return []
|