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,117 @@
|
|
|
1
|
+
"""A long task's results as result blocks: the report's ``blocks`` envelope, which the run view draws as cards."""
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
|
|
5
|
+
from .notebook import cut
|
|
6
|
+
|
|
7
|
+
VIDEO_KINDS = ("reel", "tiktok", "short")
|
|
8
|
+
NOUNS = {"reel": "Reels", "tiktok": "TikToks", "short": "Shorts", "post": "posts"}
|
|
9
|
+
MAX_ITEMS = 60
|
|
10
|
+
MAX_BLOCKS = 8
|
|
11
|
+
MIN_FORMAT_COUNT = 2
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def review_queue(session, app_names=None):
|
|
15
|
+
"""The inbox sweep's review queue as a review_queue block, or None when it read no conversation."""
|
|
16
|
+
from .inbox import OPENER_BUCKETS, REPLY_BUCKETS, queue_report, title, wants_openers
|
|
17
|
+
queue = queue_report(session, app_names)
|
|
18
|
+
items = []
|
|
19
|
+
for row in queue["items"]:
|
|
20
|
+
if not row["to"]:
|
|
21
|
+
continue # a queue item goes to someone the screen named; without one it can't be a draft to approve
|
|
22
|
+
items.append({key: value for key, value in {
|
|
23
|
+
"id": row["id"], "bucket": row["bucket"], "to": row["to"], "handle": row.get("handle"), "app": row["app"],
|
|
24
|
+
"bundleId": row["bundleId"],
|
|
25
|
+
"context": row["summary"], "lastMessage": row["lastMessage"], "draft": row["draft"],
|
|
26
|
+
"decision": row["decision"], "frameId": row["frameId"]}.items() if value is not None})
|
|
27
|
+
if not items:
|
|
28
|
+
return None
|
|
29
|
+
budget = getattr(session, "budget", None)
|
|
30
|
+
return {"type": "review_queue", "id": "queue", "title": title(session, getattr(budget, "item_noun", None)),
|
|
31
|
+
"action": "send_message", "items": items,
|
|
32
|
+
"buckets": [{"id": bucket, "label": label} for bucket, label in (
|
|
33
|
+
OPENER_BUCKETS if wants_openers(getattr(session, "request", "")) else REPLY_BUCKETS)]}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _kept(session, kinds):
|
|
37
|
+
book = getattr(session, "notebook", None)
|
|
38
|
+
return [item for item in (book.kept_items() if book is not None else ()) if item.kind in kinds]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def media_list(session):
|
|
42
|
+
"""The kept videos as a media_list block: what each was about, its creator, its key frame and its sound."""
|
|
43
|
+
kept = _kept(session, VIDEO_KINDS)[:MAX_ITEMS]
|
|
44
|
+
items = []
|
|
45
|
+
for item in kept:
|
|
46
|
+
extract = item.extract or {}
|
|
47
|
+
title = extract.get("topic") or extract.get("summary") or item.text.get("caption")
|
|
48
|
+
if extract.get("format"):
|
|
49
|
+
title = f"{title}: {extract['format']}" if title else extract["format"]
|
|
50
|
+
entry = {"id": item.id, "title": cut(title, 200) or None, "creator": item.who() or None,
|
|
51
|
+
"frameId": item.frame_id, "sounds": [cut(item.text["audio"], 80)] if item.text.get("audio") else None}
|
|
52
|
+
entry = {key: value for key, value in entry.items() if value}
|
|
53
|
+
if len(entry) > 1:
|
|
54
|
+
items.append(entry)
|
|
55
|
+
if not items:
|
|
56
|
+
return None
|
|
57
|
+
noun = NOUNS.get(kept[0].kind, "videos")
|
|
58
|
+
return {"type": "media_list", "id": "media", "title": f"{len(items)} {noun} watched", "items": items}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def formats_chart(session):
|
|
62
|
+
"""How many kept videos share each format, when at least two formats repeat: a bar chart."""
|
|
63
|
+
counts = Counter(" ".join(str((item.extract or {}).get("format") or "").casefold().split())
|
|
64
|
+
for item in _kept(session, VIDEO_KINDS))
|
|
65
|
+
counts.pop("", None)
|
|
66
|
+
top = [(name, count) for name, count in counts.most_common(8) if count >= MIN_FORMAT_COUNT]
|
|
67
|
+
if len(top) < 2:
|
|
68
|
+
return None
|
|
69
|
+
total = len(_kept(session, VIDEO_KINDS))
|
|
70
|
+
noun = NOUNS.get(_kept(session, VIDEO_KINDS)[0].kind, "videos")
|
|
71
|
+
return {"type": "chart", "id": "formats", "title": f"Formats in {total} {noun}", "kind": "bar",
|
|
72
|
+
"x": [name[:1].upper() + name[1:] for name, _ in top],
|
|
73
|
+
"series": [{"label": noun, "values": [c for _, c in top]}]}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def posts_table(session):
|
|
77
|
+
"""The kept posts as a table: creator, topic and what each says, each row with its frame."""
|
|
78
|
+
kept = _kept(session, ("post",))[:MAX_ITEMS]
|
|
79
|
+
rows = []
|
|
80
|
+
for item in kept:
|
|
81
|
+
extract = item.extract or {}
|
|
82
|
+
row = {"cells": {"creator": item.who() or "", "topic": cut(extract.get("topic"), 80),
|
|
83
|
+
"says": cut(extract.get("summary") or item.text.get("caption"), 300)}}
|
|
84
|
+
if item.frame_id:
|
|
85
|
+
row["frameId"] = item.frame_id
|
|
86
|
+
if any(row["cells"].values()):
|
|
87
|
+
rows.append(row)
|
|
88
|
+
if not rows:
|
|
89
|
+
return None
|
|
90
|
+
return {"type": "table", "id": "posts", "title": f"{len(rows)} posts", "rows": rows,
|
|
91
|
+
"columns": [{"key": "creator", "label": "Creator", "type": "text"},
|
|
92
|
+
{"key": "topic", "label": "Topic", "type": "text"},
|
|
93
|
+
{"key": "says", "label": "What it says", "type": "text"}]}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def for_session(session, app_names=None):
|
|
97
|
+
"""The blocks a long task's report carries, in the order the run view shows them; [] when it has none."""
|
|
98
|
+
from .inbox import wants_queue
|
|
99
|
+
blocks = []
|
|
100
|
+
if wants_queue(getattr(session, "request", "")):
|
|
101
|
+
blocks.append(review_queue(session, app_names))
|
|
102
|
+
blocks += [formats_chart(session), media_list(session), posts_table(session)]
|
|
103
|
+
return [block for block in blocks if block][:MAX_BLOCKS]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def attach(session, report, app_names=None):
|
|
107
|
+
"""The report with ``blocks`` (the envelope {"v": 1, "blocks": [...]})
|
|
108
|
+
when the task has any; unchanged otherwise."""
|
|
109
|
+
if not isinstance(report, dict):
|
|
110
|
+
return report
|
|
111
|
+
try:
|
|
112
|
+
blocks = for_session(session, app_names)
|
|
113
|
+
except Exception: # noqa: BLE001 -- the report stands without its cards
|
|
114
|
+
blocks = []
|
|
115
|
+
if blocks:
|
|
116
|
+
report["blocks"] = {"v": 1, "blocks": blocks}
|
|
117
|
+
return report
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Budgets for long tasks (DESIGN §3): what "for 10 minutes", "30 reels" or "every match" means, decided by code."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import asdict, dataclass, replace
|
|
6
|
+
|
|
7
|
+
# What a person spends on one item, in seconds: the human-equivalent conversion.
|
|
8
|
+
HUMAN_SECONDS_PER_ITEM = {"reel": 12, "tiktok": 10, "short": 12, "post": 8, "profile": 45, "chat": 30, "row": 20,
|
|
9
|
+
"result": 20}
|
|
10
|
+
MINUTES_RANGE = (1.0, 120.0)
|
|
11
|
+
ITEMS_RANGE = (1, 500)
|
|
12
|
+
# "every match", "all my DMs": up to ALL_CAP items, which the plan card can raise to ALL_MAX.
|
|
13
|
+
ALL_CAP, ALL_MAX = 40, 100
|
|
14
|
+
DEFAULT_ITEMS = {"feed": 30, "browse": 20}
|
|
15
|
+
# The longest a long task may hold the phone when nothing else bounds it (a chain, a survey).
|
|
16
|
+
DEFAULT_PHONE_SECONDS = {"feed": 600.0, "survey": 900.0, "chain": 900.0, "browse": 600.0}
|
|
17
|
+
|
|
18
|
+
NUMBER_WORDS = {"a": 1, "an": 1, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7,
|
|
19
|
+
"eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, "fifteen": 15, "twenty": 20,
|
|
20
|
+
"thirty": 30, "forty": 40, "forty-five": 45, "fifty": 50, "sixty": 60, "ninety": 90, "hundred": 100,
|
|
21
|
+
"a dozen": 12, "a couple": 2, "a couple of": 2, "a few": 5, "several": 5}
|
|
22
|
+
UNITS = {"s": 1 / 60, "sec": 1 / 60, "secs": 1 / 60, "second": 1 / 60, "seconds": 1 / 60, "m": 1.0, "min": 1.0,
|
|
23
|
+
"mins": 1.0, "minute": 1.0, "minutes": 1.0, "h": 60.0, "hr": 60.0, "hrs": 60.0, "hour": 60.0,
|
|
24
|
+
"hours": 60.0}
|
|
25
|
+
# Collection nouns -> (item kind, the noun the plan card shows).
|
|
26
|
+
NOUNS = {"reels": ("reel", "Reels"), "reel": ("reel", "Reels"), "videos": ("reel", "videos"),
|
|
27
|
+
"video": ("reel", "videos"), "shorts": ("short", "Shorts"), "tiktoks": ("tiktok", "TikToks"),
|
|
28
|
+
"tiktok": ("tiktok", "TikToks"), "posts": ("post", "posts"), "profiles": ("profile", "profiles"),
|
|
29
|
+
"matches": ("profile", "matches"), "messages": ("chat", "messages"), "chats": ("chat", "chats"),
|
|
30
|
+
"conversations": ("chat", "chats"), "dms": ("chat", "DMs"), "emails": ("row", "emails"),
|
|
31
|
+
"results": ("result", "results"), "places": ("result", "places"), "restaurants": ("result", "restaurants"),
|
|
32
|
+
"listings": ("result", "listings"), "items": ("row", "items"), "match": ("profile", "matches"),
|
|
33
|
+
"profile": ("profile", "profiles"), "dm": ("chat", "DMs"), "chat": ("chat", "chats"),
|
|
34
|
+
"message": ("chat", "messages"), "email": ("row", "emails"), "result": ("result", "results"),
|
|
35
|
+
"place": ("result", "places"), "restaurant": ("result", "restaurants"), "listing": ("result", "listings"),
|
|
36
|
+
"post": ("post", "posts")}
|
|
37
|
+
|
|
38
|
+
_NUM = r"(\d+(?:\.\d+)?|" + "|".join(sorted((re.escape(w) for w in NUMBER_WORDS), key=len, reverse=True)) + r")"
|
|
39
|
+
# Item counts take digits or a real number word: "a short video" or "a profile" is no count.
|
|
40
|
+
_COUNT = r"(\d+|" + "|".join(sorted((re.escape(w) for w in NUMBER_WORDS if w not in (
|
|
41
|
+
"a", "an", "a couple", "a couple of", "a few", "several")), key=len, reverse=True)) + r")"
|
|
42
|
+
_UNIT = r"(" + "|".join(sorted(UNITS, key=len, reverse=True)) + r")\b"
|
|
43
|
+
_LEAD = (
|
|
44
|
+
r"(?:for|about|around|spend|spending|scroll(?:ing)? for|watch(?:ing)? for|over|during|keep going for|"
|
|
45
|
+
r"nonstop for)")
|
|
46
|
+
TIME = re.compile(rf"\b{_LEAD}\s+(?:the\s+(?:full|whole|entire)\s+|a\s+good\s+)?{_NUM}\s*-?\s*{_UNIT}", re.I)
|
|
47
|
+
TIME_PHRASES = ((re.compile(r"\b(?:for|about|around|spend|over)\s+(?:the\s+(?:full|whole|entire)\s+)?half an hour\b",
|
|
48
|
+
re.I), 30.0),
|
|
49
|
+
(re.compile(r"\b(?:for|about|around|spend|over)\s+(?:the\s+(?:full|whole|entire)\s+)?an hour\b", re.I),
|
|
50
|
+
60.0),
|
|
51
|
+
(re.compile(r"\b(?:for|about|around|spend|over)\s+a few minutes\b", re.I), 5.0),
|
|
52
|
+
(re.compile(r"\b(?:for|about|around|spend|over)\s+a couple (?:of )?minutes\b", re.I), 2.0))
|
|
53
|
+
STRICT = re.compile(r"\b(?:the\s+(?:full|whole|entire)|keep going for|nonstop for)\b", re.I)
|
|
54
|
+
# "the whole 10 minutes" with no lead word ("watch the whole 10 minutes of Reels"): strict time on its own.
|
|
55
|
+
STRICT_TIME = re.compile(rf"\bthe\s+(?:full|whole|entire)\s+{_NUM}\s*-?\s*{_UNIT}", re.I)
|
|
56
|
+
ITEMS = re.compile(r"\b(?:the\s+(?:next|first|last|top)\s+|next\s+|first\s+|top\s+)?" + _COUNT
|
|
57
|
+
+ r"\s+(?:[a-z-]+\s+){0,2}?("
|
|
58
|
+
+ "|".join(sorted(NOUNS, key=len, reverse=True)) + r")\b", re.I)
|
|
59
|
+
ALL = re.compile(r"\b(?:every|each of my|all (?:of )?my|go(?:ing)? through (?:all (?:of )?)?my|my matches|my dms|"
|
|
60
|
+
r"all the|all\s+(?:" + "|".join(sorted(NOUNS, key=len, reverse=True)) + r"))\b", re.I)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class Budget:
|
|
65
|
+
kind: str # "time" | "items" | "all" | "default"
|
|
66
|
+
minutes: float | None # what the person said (time)
|
|
67
|
+
items: int | None # item target (time is converted)
|
|
68
|
+
strict: bool # True: run the whole time, no item target
|
|
69
|
+
phone_seconds_cap: float # never hold the phone longer than this
|
|
70
|
+
item_noun: str # "Reels", "TikToks", "profiles", "chats", "results"
|
|
71
|
+
label: str # "About 50 Reels", "Up to 40 matches", "10 minutes"
|
|
72
|
+
item_kind: str = "reel" # HUMAN_SECONDS_PER_ITEM's key
|
|
73
|
+
clamped: bool = False # the request's number was outside the range and was moved into it
|
|
74
|
+
|
|
75
|
+
def public(self):
|
|
76
|
+
"""The plan card's camelCase shape."""
|
|
77
|
+
return {"kind": self.kind, "minutes": self.minutes, "items": self.items, "strict": self.strict,
|
|
78
|
+
"phoneSecondsCap": round(self.phone_seconds_cap), "itemNoun": self.item_noun, "label": self.label,
|
|
79
|
+
"clamped": self.clamped}
|
|
80
|
+
|
|
81
|
+
def to_dict(self):
|
|
82
|
+
return asdict(self)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_dict(cls, value):
|
|
86
|
+
fields = {name: value.get(name) for name in ("kind", "minutes", "items", "strict", "phone_seconds_cap",
|
|
87
|
+
"item_noun", "label", "item_kind", "clamped")}
|
|
88
|
+
fields["strict"] = bool(fields["strict"])
|
|
89
|
+
fields["clamped"] = bool(fields["clamped"])
|
|
90
|
+
fields["item_kind"] = fields["item_kind"] or "reel"
|
|
91
|
+
fields["phone_seconds_cap"] = float(fields["phone_seconds_cap"] or 600.0)
|
|
92
|
+
return cls(**fields)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _number(word):
|
|
96
|
+
word = word.casefold().strip()
|
|
97
|
+
if word in NUMBER_WORDS:
|
|
98
|
+
return float(NUMBER_WORDS[word])
|
|
99
|
+
try:
|
|
100
|
+
return float(word)
|
|
101
|
+
except ValueError:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def noun_in(goal):
|
|
106
|
+
"""(item kind, card noun) for the collection the request names, or (None, None)."""
|
|
107
|
+
folded = goal.casefold()
|
|
108
|
+
best = None
|
|
109
|
+
for word, value in NOUNS.items():
|
|
110
|
+
found = re.search(rf"\b{re.escape(word)}\b", folded)
|
|
111
|
+
if found and (best is None or found.start() < best[0]):
|
|
112
|
+
best = (found.start(), value)
|
|
113
|
+
return best[1] if best else (None, None)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# A time that is the object of the task, not its budget: "set a timer for 10 minutes", "snooze for 5 minutes".
|
|
117
|
+
NOT_A_BUDGET = re.compile(r"\b(timers?|alarms?|countdown|snooze|remind(?:er|ers)?(?: me)?)\b[^,;.]{0,24}$", re.I)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _minutes(goal):
|
|
121
|
+
"""(minutes, strict) the request asks for, or (None, False)."""
|
|
122
|
+
for pattern, minutes in TIME_PHRASES:
|
|
123
|
+
found = pattern.search(goal)
|
|
124
|
+
if found and not NOT_A_BUDGET.search(goal[:found.start()]):
|
|
125
|
+
return minutes, bool(STRICT.search(found.group(0)))
|
|
126
|
+
found = TIME.search(goal) or STRICT_TIME.search(goal)
|
|
127
|
+
if not found or NOT_A_BUDGET.search(goal[:found.start()]):
|
|
128
|
+
return None, False
|
|
129
|
+
number = _number(found.group(1))
|
|
130
|
+
if number is None:
|
|
131
|
+
return None, False
|
|
132
|
+
return number * UNITS[found.group(2).casefold()], bool(STRICT.search(found.group(0)))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def parse_budget(goal):
|
|
136
|
+
"""The request's budget, or None when it names none (the plan then picks its kind's default)."""
|
|
137
|
+
text = " ".join(str(goal or "").split())
|
|
138
|
+
if not text:
|
|
139
|
+
return None
|
|
140
|
+
kind_noun = noun_in(text)
|
|
141
|
+
item_kind, noun = kind_noun if kind_noun[0] else ("reel", "items")
|
|
142
|
+
minutes, strict = _minutes(text)
|
|
143
|
+
if minutes is not None:
|
|
144
|
+
return time_budget(minutes, item_kind, noun, strict=strict)
|
|
145
|
+
found = ITEMS.search(text)
|
|
146
|
+
if found:
|
|
147
|
+
number = _number(found.group(1))
|
|
148
|
+
if number is not None and number >= 1:
|
|
149
|
+
item_kind, noun = NOUNS[found.group(2).casefold()]
|
|
150
|
+
return items_budget(int(number), item_kind, noun)
|
|
151
|
+
if ALL.search(text):
|
|
152
|
+
return all_budget(item_kind, noun)
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def time_budget(minutes, item_kind="reel", noun="Reels", *, strict=False):
|
|
157
|
+
clamped = not MINUTES_RANGE[0] <= minutes <= MINUTES_RANGE[1]
|
|
158
|
+
minutes = min(max(minutes, MINUTES_RANGE[0]), MINUTES_RANGE[1])
|
|
159
|
+
minutes = round(minutes, 2)
|
|
160
|
+
cap = minutes * 60
|
|
161
|
+
if strict:
|
|
162
|
+
return Budget("time", minutes, None, True, cap, noun, minutes_label(minutes), item_kind, clamped)
|
|
163
|
+
items = human_items(cap, item_kind)
|
|
164
|
+
return Budget("time", minutes, items, False, cap, noun, f"About {items} {noun}", item_kind, clamped)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def items_budget(count, item_kind="reel", noun="Reels"):
|
|
168
|
+
clamped = not ITEMS_RANGE[0] <= count <= ITEMS_RANGE[1]
|
|
169
|
+
count = min(max(int(count), ITEMS_RANGE[0]), ITEMS_RANGE[1])
|
|
170
|
+
from .costs import ITEM_S
|
|
171
|
+
cap = max(120.0, min(7200.0 - 300, 120 + count * ITEM_S.get(item_kind, 3.0) * 3))
|
|
172
|
+
return Budget("items", None, count, False, cap, noun, f"{count} {noun}", item_kind, clamped)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def all_budget(item_kind="profile", noun="matches", cap_items=ALL_CAP):
|
|
176
|
+
cap_items = min(max(int(cap_items), 1), ALL_MAX)
|
|
177
|
+
from .costs import ITEM_S
|
|
178
|
+
cap = max(300.0, 120 + cap_items * ITEM_S.get(item_kind, 3.0) * 3)
|
|
179
|
+
return Budget("all", None, cap_items, False, cap, noun, f"Up to {cap_items} {noun}", item_kind)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def human_items(seconds, item_kind):
|
|
183
|
+
"""How many items a person sees in ``seconds``."""
|
|
184
|
+
return max(1, math.ceil(seconds / HUMAN_SECONDS_PER_ITEM.get(item_kind, 12)))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def minutes_label(minutes):
|
|
188
|
+
if minutes >= 60 and minutes % 60 == 0:
|
|
189
|
+
hours = int(minutes // 60)
|
|
190
|
+
return f"{hours} hour{'s' if hours != 1 else ''}"
|
|
191
|
+
if minutes < 1:
|
|
192
|
+
return f"{round(minutes * 60)} seconds"
|
|
193
|
+
whole = int(minutes) if float(minutes).is_integer() else minutes
|
|
194
|
+
return f"{whole} minute{'s' if whole != 1 else ''}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def for_kind(kind, parsed, item_kind, noun):
|
|
198
|
+
"""The budget a plan of ``kind`` runs with: the request's own (``parsed``), else the kind's default."""
|
|
199
|
+
if parsed is not None:
|
|
200
|
+
if parsed.kind == "time" and parsed.item_noun == "items" and noun != "items":
|
|
201
|
+
# "for 10 minutes" with the noun named elsewhere ("Scroll TikTok for 10 minutes")
|
|
202
|
+
return time_budget(parsed.minutes, item_kind, noun, strict=parsed.strict)
|
|
203
|
+
if parsed.kind == "all" and parsed.item_noun == "items" and noun != "items":
|
|
204
|
+
return all_budget(item_kind, noun)
|
|
205
|
+
return parsed
|
|
206
|
+
if kind == "survey":
|
|
207
|
+
return all_budget(item_kind, noun)
|
|
208
|
+
if kind == "chain":
|
|
209
|
+
return Budget("default", None, None, False, DEFAULT_PHONE_SECONDS["chain"], noun, "Each step once",
|
|
210
|
+
item_kind)
|
|
211
|
+
count = DEFAULT_ITEMS.get(kind, 20)
|
|
212
|
+
budget = items_budget(count, item_kind, noun)
|
|
213
|
+
return replace(budget, kind="default", label=f"About {count} {noun}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def override(budget, change):
|
|
217
|
+
"""``budget`` changed by the plan card or the API's ``longform.budget`` ({items?, minutes?, strict?})."""
|
|
218
|
+
if not change:
|
|
219
|
+
return budget
|
|
220
|
+
if not isinstance(change, dict) or set(change) - {"items", "minutes", "strict"}:
|
|
221
|
+
raise ValueError("longform.budget takes items, minutes and strict")
|
|
222
|
+
items, minutes, strict = change.get("items"), change.get("minutes"), change.get("strict")
|
|
223
|
+
if strict is not None and type(strict) is not bool:
|
|
224
|
+
raise ValueError("longform.budget.strict is true or false")
|
|
225
|
+
if minutes is not None:
|
|
226
|
+
if isinstance(minutes, bool) or not isinstance(minutes, (int, float)) or \
|
|
227
|
+
not MINUTES_RANGE[0] <= minutes <= MINUTES_RANGE[1]:
|
|
228
|
+
raise ValueError("longform.budget.minutes is between 1 and 120")
|
|
229
|
+
return time_budget(float(minutes), budget.item_kind, budget.item_noun, strict=bool(strict))
|
|
230
|
+
if items is not None:
|
|
231
|
+
if isinstance(items, bool) or not isinstance(items, int) or not ITEMS_RANGE[0] <= items <= ITEMS_RANGE[1]:
|
|
232
|
+
raise ValueError("longform.budget.items is a whole number from 1 to 500")
|
|
233
|
+
if budget.kind == "all":
|
|
234
|
+
return all_budget(budget.item_kind, budget.item_noun, min(items, ALL_MAX))
|
|
235
|
+
return items_budget(items, budget.item_kind, budget.item_noun)
|
|
236
|
+
if strict is not None and budget.minutes is not None:
|
|
237
|
+
return time_budget(budget.minutes, budget.item_kind, budget.item_noun, strict=strict)
|
|
238
|
+
return budget
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def from_cli(value):
|
|
242
|
+
"""``--budget``: 10m, 90s, 1h, 30 (items) or all. ValueError with a sentence otherwise."""
|
|
243
|
+
text = str(value or "").strip().casefold()
|
|
244
|
+
if text == "all":
|
|
245
|
+
return {"items": ALL_CAP}
|
|
246
|
+
found = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr)?", text)
|
|
247
|
+
if not found:
|
|
248
|
+
raise ValueError("--budget is a time (10m, 90s, 1h), a number of items (30) or all")
|
|
249
|
+
number, unit = float(found.group(1)), found.group(2)
|
|
250
|
+
if unit is None:
|
|
251
|
+
if not number.is_integer() or not ITEMS_RANGE[0] <= number <= ITEMS_RANGE[1]:
|
|
252
|
+
raise ValueError("--budget takes 1 to 500 items")
|
|
253
|
+
return {"items": int(number)}
|
|
254
|
+
minutes = number * UNITS[unit]
|
|
255
|
+
if not MINUTES_RANGE[0] <= minutes <= MINUTES_RANGE[1]:
|
|
256
|
+
raise ValueError("--budget takes 1 minute to 2 hours")
|
|
257
|
+
return {"minutes": round(minutes, 2)}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def turn_caps(budget, steps=3):
|
|
261
|
+
"""(max_steps, max_seconds) for a long run (DESIGN §3.3)."""
|
|
262
|
+
items = budget.items or 0
|
|
263
|
+
max_steps = min(600, 60 + 12 * max(1, steps) + math.ceil(items / 10))
|
|
264
|
+
max_seconds = min(7200.0, budget.phone_seconds_cap + 300)
|
|
265
|
+
return max_steps, max_seconds
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""What the navigation model sees of a long task (DESIGN §5.2, §5.3, §6.1, §6.2)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from .notebook import cut
|
|
6
|
+
|
|
7
|
+
LONGFORM_SYSTEM = """This is a long task with a budget and a notebook.
|
|
8
|
+
- The code keeps a notebook for you and writes the final report from it after you finish. Your DONE answer needs only \
|
|
9
|
+
one or two sentences; the report does the rest.
|
|
10
|
+
- Use the macros for repeated work. On a full-screen vertical video feed (Reels, TikTok, Shorts) use WATCH_FEED; do \
|
|
11
|
+
not swipe through videos one by one. For a list whose rows each open a page (matches, chats, results) use READ_EACH. \
|
|
12
|
+
Navigate yourself to where a macro starts: search the topic, open the right tab, tap the first video.
|
|
13
|
+
- When a macro returns, read its summary: if the feed drifted off the topic, search again; if the budget is covered, \
|
|
14
|
+
finish.
|
|
15
|
+
- Put what you learn in notes_add, citing items by id ("i12, i15: two drills for footwork"). Facts, not narration.
|
|
16
|
+
- Set step to the plan step you are working on. If a step can't be done, say why in notes_add and go on to the next \
|
|
17
|
+
step unless it depends on this one.
|
|
18
|
+
- The budget line says how much is left. When it says wrap up, finish with DONE."""
|
|
19
|
+
|
|
20
|
+
EPOCH_HEAD = "Notebook (the code keeps everything here for the report; you see a summary):"
|
|
21
|
+
DELTA_HEAD = "Notebook since the summary above:"
|
|
22
|
+
EPOCH_FACTS, EPOCH_ITEMS = 15, 6
|
|
23
|
+
DELTA_FACTS, DELTA_ITEMS = 10, 6
|
|
24
|
+
EPOCH_CHARS, DELTA_CHARS = 9000, 3000
|
|
25
|
+
EPOCH_EVERY = 10 # new items or facts that make a new epoch
|
|
26
|
+
COMPACT_EVERY = 8 # history lines per log line
|
|
27
|
+
LOG_SHOWN = 6
|
|
28
|
+
STEP_WORDS = {"done": "done", "now": "now", "next": "next", "skipped": "skipped", "failed": "failed"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def plan_steps(plan):
|
|
32
|
+
"""The stable prompt's lines for the plan's steps."""
|
|
33
|
+
steps = (plan or {}).get("steps") or ()
|
|
34
|
+
if not steps:
|
|
35
|
+
return ""
|
|
36
|
+
lines = [f"{s['id']}. {s['title']}" + (f" ({s['app']})" if s.get("app") else "") for s in steps]
|
|
37
|
+
text = ("\n\nPlan steps (set step to the one you work on; the code marks them done as you move on):\n"
|
|
38
|
+
+ "\n".join(lines))
|
|
39
|
+
from .inbox import wants_drafts, wants_openers
|
|
40
|
+
if (plan or {}).get("kind") == "survey" and wants_drafts((plan or {}).get("title")):
|
|
41
|
+
# Real iPhone, 13 Oct: a DM sweep read the inbox's previews with READ_LIST and finished without opening one
|
|
42
|
+
# conversation, so it drafted replies to messages it never saw in full.
|
|
43
|
+
text += SWEEP_NOTE
|
|
44
|
+
elif (plan or {}).get("kind") == "survey" and wants_openers((plan or {}).get("title")):
|
|
45
|
+
text += OPENERS_NOTE
|
|
46
|
+
return text
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
SWEEP_NOTE = ("\nThis is an inbox sweep: sort and draft from the conversations themselves. Use READ_EACH on the first "
|
|
50
|
+
"conversation row of each list (newest first) so code opens every one; READ_LIST reads only the "
|
|
51
|
+
"previews, which are not enough to sort or draft from. Check the other inbox tabs (requests, general) "
|
|
52
|
+
"the same way. Finish when READ_EACH reaches the end of the lists or the budget.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
OPENERS_NOTE = ("\nThis drafts a first message to each person who fits: code drafts them from the posts themselves. "
|
|
56
|
+
"Use READ_EACH on the first post row of the list so code opens every post; READ_LIST reads only the "
|
|
57
|
+
"previews. Never open a conversation or send anything: the person reviews every draft. Finish when "
|
|
58
|
+
"READ_EACH reaches the end of the list or the budget.")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def counts_line(notebook):
|
|
62
|
+
c = notebook.counters
|
|
63
|
+
return (f"Counts: {c['seen']} seen, {c['kept']} kept, {c['ads']} ads skipped, {c['repeats']} repeats skipped, "
|
|
64
|
+
f"{c['off_topic']} off topic.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def steps_line(notebook):
|
|
68
|
+
if not notebook.steps:
|
|
69
|
+
return ""
|
|
70
|
+
parts = []
|
|
71
|
+
for step in notebook.steps:
|
|
72
|
+
parts.append(f"{step['id']} {STEP_WORDS.get(step['state'], step['state'])}")
|
|
73
|
+
return "Steps: " + ". ".join(parts) + "."
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def item_line(item):
|
|
77
|
+
who = item.who()
|
|
78
|
+
label = f"{who}: " if who else ""
|
|
79
|
+
state = {None: "kept", "ad": "ad, skipped", "repeat": "repeat, skipped", "off_topic": "off topic",
|
|
80
|
+
"unreadable": "unreadable"}[item.skip]
|
|
81
|
+
summary = item.summary()
|
|
82
|
+
return f"- {item.id} {label}" + (f"\"{cut(summary, 120)}\" " if summary else "") + f"({state})"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def fact_line(fact):
|
|
86
|
+
ids = f"[{', '.join(fact.item_ids[:6])}] " if fact.item_ids else ""
|
|
87
|
+
return f"- {fact.id} {ids}{cut(fact.text, 220)}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def render_epoch(notebook):
|
|
91
|
+
"""The notebook's summary for the cached epoch (at most about 2,500 tokens)."""
|
|
92
|
+
with notebook.lock:
|
|
93
|
+
lines = [EPOCH_HEAD, counts_line(notebook)]
|
|
94
|
+
digest = notebook.digest or {}
|
|
95
|
+
if digest.get("text"):
|
|
96
|
+
lines.append("Digest: " + cut(digest["text"], 700))
|
|
97
|
+
themes = digest.get("themes") or ()
|
|
98
|
+
if themes:
|
|
99
|
+
lines.append("Themes: " + ", ".join(f"{t.get('name')} ({len(t.get('item_ids') or ())})"
|
|
100
|
+
for t in themes[:10]))
|
|
101
|
+
facts = notebook.facts[-EPOCH_FACTS:]
|
|
102
|
+
if facts:
|
|
103
|
+
lines.append(f"Facts (newest {len(facts)}):")
|
|
104
|
+
lines += [fact_line(f) for f in reversed(facts)]
|
|
105
|
+
items = notebook.items[-EPOCH_ITEMS:]
|
|
106
|
+
if items:
|
|
107
|
+
lines.append(f"Recent items (newest {len(items)}):")
|
|
108
|
+
lines += [item_line(i) for i in reversed(items)]
|
|
109
|
+
steps = steps_line(notebook)
|
|
110
|
+
if steps:
|
|
111
|
+
lines.append(steps)
|
|
112
|
+
text = "\n".join(lines)
|
|
113
|
+
return text[:EPOCH_CHARS]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def render_delta(notebook, since):
|
|
117
|
+
"""What changed since the epoch ``since`` = (items, facts) counts: new facts and items, counts and steps now."""
|
|
118
|
+
items_at, facts_at = since
|
|
119
|
+
with notebook.lock:
|
|
120
|
+
new_items = notebook.items[items_at:]
|
|
121
|
+
new_facts = notebook.facts[facts_at:]
|
|
122
|
+
lines = [DELTA_HEAD, counts_line(notebook).replace("Counts:", "Counts now:")]
|
|
123
|
+
if new_facts:
|
|
124
|
+
shown = new_facts[-DELTA_FACTS:]
|
|
125
|
+
lines.append(f"New facts ({len(new_facts)}):" if len(shown) == len(new_facts)
|
|
126
|
+
else f"New facts (newest {len(shown)} of {len(new_facts)}):")
|
|
127
|
+
lines += [fact_line(f) for f in reversed(shown)]
|
|
128
|
+
if new_items:
|
|
129
|
+
shown = new_items[-DELTA_ITEMS:]
|
|
130
|
+
lines.append(f"New items ({len(new_items)}):" if len(shown) == len(new_items)
|
|
131
|
+
else f"New items (newest {len(shown)} of {len(new_items)}):")
|
|
132
|
+
lines += [item_line(i) for i in reversed(shown)]
|
|
133
|
+
steps = steps_line(notebook)
|
|
134
|
+
if steps:
|
|
135
|
+
lines.append(steps)
|
|
136
|
+
if notebook.log:
|
|
137
|
+
lines.append(f"Earlier turns, compacted (newest {min(LOG_SHOWN, len(notebook.log))}):")
|
|
138
|
+
lines += [f"- {line}" for line in notebook.log[-LOG_SHOWN:]]
|
|
139
|
+
text = "\n".join(lines)
|
|
140
|
+
return text[:DELTA_CHARS]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Epochs:
|
|
144
|
+
"""When the cached epoch is re-rendered (DESIGN §6.2)."""
|
|
145
|
+
|
|
146
|
+
def __init__(self):
|
|
147
|
+
self.text = None
|
|
148
|
+
self.since = (0, 0)
|
|
149
|
+
self.digest_key = None
|
|
150
|
+
self.steps_key = None
|
|
151
|
+
self.renders = 0
|
|
152
|
+
|
|
153
|
+
def current(self, notebook):
|
|
154
|
+
"""(epoch text, (items, facts) at that epoch): the same text until a refresh trigger."""
|
|
155
|
+
with notebook.lock:
|
|
156
|
+
digest = notebook.digest or {}
|
|
157
|
+
digest_key = (digest.get("turn"), digest.get("up_to_item"), digest.get("text"))
|
|
158
|
+
steps_key = tuple((s["id"], s["state"]) for s in notebook.steps)
|
|
159
|
+
items, facts = len(notebook.items), len(notebook.facts)
|
|
160
|
+
fresh = (self.text is None or digest_key != self.digest_key or steps_key != self.steps_key
|
|
161
|
+
or items - self.since[0] >= EPOCH_EVERY or facts - self.since[1] >= EPOCH_EVERY)
|
|
162
|
+
if fresh:
|
|
163
|
+
self.text = render_epoch(notebook)
|
|
164
|
+
self.since = (items, facts)
|
|
165
|
+
self.digest_key, self.steps_key = digest_key, steps_key
|
|
166
|
+
self.renders += 1
|
|
167
|
+
return self.text, self.since
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def money(usd):
|
|
171
|
+
return f"${usd:,.2f}"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def duration(seconds):
|
|
175
|
+
seconds = max(0, int(round(seconds)))
|
|
176
|
+
minutes, rest = divmod(seconds, 60)
|
|
177
|
+
if minutes and rest:
|
|
178
|
+
return f"{minutes} min {rest} s"
|
|
179
|
+
if minutes:
|
|
180
|
+
return f"{minutes} min"
|
|
181
|
+
return f"{rest} s"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def budget_line(session):
|
|
185
|
+
""""Budget: 41 of about 50 Reels. 1 min 12 s of phone time used (cap 10 min). $0.08 spent of $1.00 (report
|
|
186
|
+
reserve $0.06). Wrap up when the items are covered.\""""
|
|
187
|
+
budget = session.budget
|
|
188
|
+
kept = session.notebook.watched()
|
|
189
|
+
parts = []
|
|
190
|
+
if budget.items and not budget.strict:
|
|
191
|
+
about = "about " if budget.kind in ("time", "default") else "up to " if budget.kind == "all" else ""
|
|
192
|
+
parts.append(f"{kept} of {about}{budget.items} {budget.item_noun}.")
|
|
193
|
+
else:
|
|
194
|
+
parts.append(f"{kept} {budget.item_noun} so far.")
|
|
195
|
+
parts.append(f"{duration(session.phone_seconds())} of phone time used (cap {duration(budget.phone_seconds_cap)}).")
|
|
196
|
+
cap = session.cap_usd
|
|
197
|
+
spent, reserve = session.spent_usd(), session.reserve_usd()
|
|
198
|
+
if cap is not None:
|
|
199
|
+
parts.append(f"{money(spent)} spent of {money(cap)} (report reserve {money(reserve)}).")
|
|
200
|
+
else:
|
|
201
|
+
parts.append(f"{money(spent)} spent (report reserve {money(reserve)}).")
|
|
202
|
+
advice = session.advice()
|
|
203
|
+
if advice:
|
|
204
|
+
parts.append(advice)
|
|
205
|
+
return "Budget: " + " ".join(parts)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
HISTORY = re.compile(r"^(\d+)(?:\.(\d+))?: (.*)$")
|
|
209
|
+
OP = re.compile(r"^([A-Z][A-Z_]+)\b")
|
|
210
|
+
TEXT = re.compile(r"text='([^']{1,60})")
|
|
211
|
+
WATCHED = re.compile(r"(?:Watched|Read) (\d+) ")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def compact_lines(lines, apps_by_turn=None):
|
|
215
|
+
"""One log line for ``lines`` (history lines that left the window): the turns, the apps, op counts, macro
|
|
216
|
+
outcomes, searches, refusals and failures."""
|
|
217
|
+
turns, ops, searches, refusals, failures, covered = [], {}, [], 0, 0, 0
|
|
218
|
+
for line in lines:
|
|
219
|
+
found = HISTORY.match(str(line))
|
|
220
|
+
if not found:
|
|
221
|
+
continue
|
|
222
|
+
turns.append(int(found.group(1)))
|
|
223
|
+
rest = found.group(3)
|
|
224
|
+
op = OP.match(rest)
|
|
225
|
+
name = op.group(1) if op else None
|
|
226
|
+
if "refused" in rest:
|
|
227
|
+
refusals += 1
|
|
228
|
+
elif "failed" in rest:
|
|
229
|
+
failures += 1
|
|
230
|
+
if name:
|
|
231
|
+
ops[name] = ops.get(name, 0) + 1
|
|
232
|
+
typed = TEXT.search(rest)
|
|
233
|
+
if typed and name in ("TYPE_SUBMIT", "TYPE", "SET_TEXT"):
|
|
234
|
+
searches.append(typed.group(1))
|
|
235
|
+
watched = WATCHED.search(rest)
|
|
236
|
+
if watched and name in ("WATCH_FEED", "READ_EACH"):
|
|
237
|
+
covered += int(watched.group(1))
|
|
238
|
+
if not turns:
|
|
239
|
+
return None
|
|
240
|
+
first, last = min(turns), max(turns)
|
|
241
|
+
apps = []
|
|
242
|
+
for turn in range(first, last + 1):
|
|
243
|
+
app = (apps_by_turn or {}).get(turn)
|
|
244
|
+
if app and app not in apps:
|
|
245
|
+
apps.append(app)
|
|
246
|
+
head = f"Turns {first}-{last}" if first != last else f"Turn {first}"
|
|
247
|
+
if apps:
|
|
248
|
+
head += ", " + "/".join(apps[:3])
|
|
249
|
+
parts = []
|
|
250
|
+
if searches:
|
|
251
|
+
parts.append("typed " + ", ".join(f"'{s}'" for s in searches[:3]))
|
|
252
|
+
for name in ("WATCH_FEED", "READ_EACH", "READ_LIST", "RECALL"):
|
|
253
|
+
if ops.get(name):
|
|
254
|
+
times = {1: "once", 2: "twice"}.get(ops[name], f"{ops[name]} times")
|
|
255
|
+
parts.append(f"{name} {times}" + (f" ({covered} items)" if name in ("WATCH_FEED", "READ_EACH")
|
|
256
|
+
and covered else ""))
|
|
257
|
+
rest = {k: v for k, v in ops.items() if k not in ("WATCH_FEED", "READ_EACH", "READ_LIST", "RECALL", "TYPE_SUBMIT",
|
|
258
|
+
"TYPE", "SET_TEXT")}
|
|
259
|
+
if rest:
|
|
260
|
+
parts.append(", ".join(f"{k} x{v}" for k, v in sorted(rest.items(), key=lambda kv: -kv[1])[:4]))
|
|
261
|
+
if refusals:
|
|
262
|
+
parts.append(f"{refusals} refused")
|
|
263
|
+
if failures:
|
|
264
|
+
parts.append(f"{failures} failed")
|
|
265
|
+
return head + ": " + ("; ".join(parts) if parts else "no actions") + "."
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class Compactor:
|
|
269
|
+
"""Folds history lines that leave the prompt's window into the notebook's log, 8 at a time."""
|
|
270
|
+
|
|
271
|
+
def __init__(self, window=16):
|
|
272
|
+
self.window = window
|
|
273
|
+
self.done = 0
|
|
274
|
+
|
|
275
|
+
def fold(self, history, notebook, apps_by_turn=None):
|
|
276
|
+
"""Compact what is due; returns the number of new log lines."""
|
|
277
|
+
added = 0
|
|
278
|
+
while len(history) - self.window - self.done >= COMPACT_EVERY:
|
|
279
|
+
chunk = history[self.done:self.done + COMPACT_EVERY]
|
|
280
|
+
self.done += COMPACT_EVERY
|
|
281
|
+
line = compact_lines(chunk, apps_by_turn)
|
|
282
|
+
if line:
|
|
283
|
+
notebook.add_log(line)
|
|
284
|
+
added += 1
|
|
285
|
+
return added
|