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,361 @@
|
|
|
1
|
+
"""Threads and their items in the run journal (schema ``threads`` v1, SPEC §3.2)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
import sqlite3
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from ..api_errors import APIError
|
|
11
|
+
from ..journal import JournalError
|
|
12
|
+
|
|
13
|
+
SCHEMA = "threads"
|
|
14
|
+
VERSION = 1
|
|
15
|
+
|
|
16
|
+
MAX_ITEMS = 2000 # per thread, every kind
|
|
17
|
+
RESERVE = 20 # kept free for the items a running task still adds (questions, results)
|
|
18
|
+
MAX_THREAD_BYTES = 8_000_000 # per thread
|
|
19
|
+
MAX_SINK_ITEM_BYTES = 8_000 # an item another track posts (post_thread_item)
|
|
20
|
+
TITLE_CHARS = 80
|
|
21
|
+
MAX_RULES = 10
|
|
22
|
+
RULES_CHARS = 1_200 # "Rules you gave in this conversation", newest kept
|
|
23
|
+
LIST_LIMIT = 100
|
|
24
|
+
REQUEST_LIMIT = 10_000
|
|
25
|
+
KIND = re.compile(r"[a-z_]{1,40}")
|
|
26
|
+
ID = re.compile(r"[a-f0-9]{12}")
|
|
27
|
+
CORE_KINDS = frozenset({"user", "run", "clarify"})
|
|
28
|
+
STATUSES = ("idle", "running", "waiting")
|
|
29
|
+
FULL = "This conversation is full. Start a new one."
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
NOT_FOUND = "That conversation doesn't exist here. It may have been deleted, or it's in another Mobster."
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ThreadFull(APIError):
|
|
36
|
+
"""The thread has no room for what was asked (MAX_ITEMS or MAX_THREAD_BYTES): 409 thread_full."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message=FULL):
|
|
39
|
+
super().__init__(message, 409, "thread_full")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ThreadMissing(APIError):
|
|
43
|
+
"""The thread doesn't exist (deleted meanwhile): 404 thread_not_found."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message=NOT_FOUND):
|
|
46
|
+
super().__init__(message, 404, "thread_not_found")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def new_id():
|
|
50
|
+
return uuid.uuid4().hex[:12]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def encode(value):
|
|
54
|
+
return json.dumps(value, allow_nan=False, separators=(",", ":"), ensure_ascii=False)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def now_ms():
|
|
58
|
+
return time.time() * 1000
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def request_unknown():
|
|
62
|
+
return APIError("Mobster accepted this request but can't confirm its outcome. Check the conversation "
|
|
63
|
+
"and task history before starting another request.", 409, "request_outcome_unknown")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def request_result(response):
|
|
67
|
+
"""Replay a stored success or known refusal; unresolved intent never authorizes another dispatch."""
|
|
68
|
+
if response is None:
|
|
69
|
+
raise request_unknown()
|
|
70
|
+
if response["status"] >= 400:
|
|
71
|
+
error = response["response"]
|
|
72
|
+
if "code" not in error:
|
|
73
|
+
raise ValueError(error["error"])
|
|
74
|
+
raise APIError(error["error"], response["status"], error["code"],
|
|
75
|
+
**{key: value for key, value in error.items() if key not in ("error", "code")})
|
|
76
|
+
return response
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def failed_request(error):
|
|
80
|
+
body = {"error": str(error)}
|
|
81
|
+
if isinstance(error, APIError):
|
|
82
|
+
body.update(code=error.code, **error.details)
|
|
83
|
+
return {"status": error.status if isinstance(error, APIError) else 400, "response": body}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cursor_parts(value):
|
|
87
|
+
"""(stored timestamp, optional thread id) from a next cursor or a legacy millisecond timestamp."""
|
|
88
|
+
timestamp, separator, thread_id = str(value).partition(":")
|
|
89
|
+
try:
|
|
90
|
+
timestamp = float(timestamp) / 1000
|
|
91
|
+
if not math.isfinite(timestamp) or separator and not ID.fullmatch(thread_id):
|
|
92
|
+
raise ValueError
|
|
93
|
+
except ValueError:
|
|
94
|
+
raise ValueError("before is a next cursor or an updatedAt from a thread (milliseconds)") from None
|
|
95
|
+
return timestamp, thread_id if separator else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def migrate(connection, from_version):
|
|
99
|
+
"""journal.register_schema's migration: v0 -> v1 creates the tables. Runs inside BEGIN IMMEDIATE."""
|
|
100
|
+
if from_version < 1:
|
|
101
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS threads(
|
|
102
|
+
id TEXT PRIMARY KEY, data TEXT NOT NULL, updated_at REAL NOT NULL)""")
|
|
103
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS thread_items(
|
|
104
|
+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
105
|
+
seq INTEGER NOT NULL, data TEXT NOT NULL,
|
|
106
|
+
id TEXT NOT NULL, kind TEXT NOT NULL, run_id TEXT, bytes INTEGER NOT NULL,
|
|
107
|
+
PRIMARY KEY(thread_id, seq))""")
|
|
108
|
+
connection.execute("CREATE INDEX IF NOT EXISTS threads_updated ON threads(updated_at DESC)")
|
|
109
|
+
connection.execute("CREATE INDEX IF NOT EXISTS thread_items_run ON thread_items(thread_id, run_id)")
|
|
110
|
+
connection.execute("CREATE UNIQUE INDEX IF NOT EXISTS thread_items_id ON thread_items(id)")
|
|
111
|
+
# Idempotency-Key -> the answer a message got, so a retried post never starts a second task.
|
|
112
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS thread_requests(
|
|
113
|
+
key TEXT PRIMARY KEY, thread_id TEXT, fingerprint TEXT NOT NULL, response TEXT NOT NULL,
|
|
114
|
+
created_at REAL NOT NULL)""")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def public_item(item):
|
|
118
|
+
"""The item as the API and the bus show it: without its private ("_") keys."""
|
|
119
|
+
return {key: value for key, value in item.items() if not key.startswith("_")}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def title_from(text):
|
|
123
|
+
"""A thread's title from its first message: one line, at most 80 characters, cut at a word."""
|
|
124
|
+
text = " ".join(str(text or "").split())
|
|
125
|
+
if len(text) <= TITLE_CHARS:
|
|
126
|
+
return text
|
|
127
|
+
cut = text[:TITLE_CHARS - 1]
|
|
128
|
+
space = cut.rfind(" ")
|
|
129
|
+
if space >= TITLE_CHARS // 2:
|
|
130
|
+
cut = cut[:space]
|
|
131
|
+
return cut.rstrip(" ,;:.") + "…"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ThreadStore:
|
|
135
|
+
def __init__(self, journal):
|
|
136
|
+
self.journal = journal
|
|
137
|
+
|
|
138
|
+
def _read(self, sql, args=()):
|
|
139
|
+
with self.journal.lock:
|
|
140
|
+
if self.journal.connection is None:
|
|
141
|
+
raise JournalError("The run journal is closed")
|
|
142
|
+
try:
|
|
143
|
+
return self.journal.connection.execute(sql, args).fetchall()
|
|
144
|
+
except sqlite3.Error:
|
|
145
|
+
raise JournalError("Mobster could not read its conversations") from None
|
|
146
|
+
|
|
147
|
+
def create_thread(self, *, title=None, device=None, device_name=None, thread_id=None):
|
|
148
|
+
stamp = now_ms()
|
|
149
|
+
thread = {"id": thread_id or new_id(), "title": title_from(title) or "New conversation",
|
|
150
|
+
"createdAt": stamp, "updatedAt": stamp, "device": device, "deviceName": device_name,
|
|
151
|
+
"archived": False, "pinned": False, "status": "idle", "lastRunId": None,
|
|
152
|
+
"rules": []}
|
|
153
|
+
with self.journal.transaction() as connection:
|
|
154
|
+
connection.execute("INSERT INTO threads(id, data, updated_at) VALUES (?,?,?)",
|
|
155
|
+
(thread["id"], encode(thread), stamp / 1000))
|
|
156
|
+
return thread
|
|
157
|
+
|
|
158
|
+
def get(self, thread_id):
|
|
159
|
+
if not isinstance(thread_id, str) or not ID.fullmatch(thread_id):
|
|
160
|
+
return None
|
|
161
|
+
rows = self._read("SELECT data FROM threads WHERE id=?", (thread_id,))
|
|
162
|
+
return json.loads(rows[0][0]) if rows else None
|
|
163
|
+
|
|
164
|
+
def list(self, *, limit=50, before=None, device=None, query=None, archived=False):
|
|
165
|
+
"""(threads newest first, the ``before`` cursor for the next page or None)."""
|
|
166
|
+
limit = max(1, min(LIST_LIMIT, int(limit)))
|
|
167
|
+
where, args = [], []
|
|
168
|
+
if before is not None:
|
|
169
|
+
timestamp, thread_id = cursor_parts(before)
|
|
170
|
+
if thread_id is None:
|
|
171
|
+
where.append("updated_at < ?")
|
|
172
|
+
args.append(timestamp)
|
|
173
|
+
else:
|
|
174
|
+
where.append("(updated_at, id) < (?, ?)")
|
|
175
|
+
args.extend((timestamp, thread_id))
|
|
176
|
+
sql = "SELECT data FROM threads" + (" WHERE " + " AND ".join(where) if where else "")
|
|
177
|
+
sql += " ORDER BY updated_at DESC, id DESC"
|
|
178
|
+
matching = None
|
|
179
|
+
if query:
|
|
180
|
+
# Search the words themselves: JSON escaping changes quotes and backslashes, and a run's other fields
|
|
181
|
+
# include private notes and metadata that aren't part of conversation search.
|
|
182
|
+
wanted = query.casefold()
|
|
183
|
+
matching = set()
|
|
184
|
+
for thread_id, kind, data in self._read(
|
|
185
|
+
"SELECT thread_id, kind, data FROM thread_items WHERE kind IN ('user', 'run')"):
|
|
186
|
+
item = json.loads(data)
|
|
187
|
+
if wanted in str(item.get("text" if kind == "user" else "goal") or "").casefold():
|
|
188
|
+
matching.add(thread_id)
|
|
189
|
+
out = []
|
|
190
|
+
for (data,) in self._read(sql, args):
|
|
191
|
+
thread = json.loads(data)
|
|
192
|
+
if archived != "all" and bool(thread.get("archived")) != bool(archived):
|
|
193
|
+
continue
|
|
194
|
+
if device is not None and device not in (thread.get("device"), thread.get("deviceName")):
|
|
195
|
+
continue
|
|
196
|
+
if matching is not None and thread["id"] not in matching and \
|
|
197
|
+
query.casefold() not in str(thread.get("title") or "").casefold():
|
|
198
|
+
continue
|
|
199
|
+
out.append(thread)
|
|
200
|
+
if len(out) > limit:
|
|
201
|
+
break
|
|
202
|
+
more = len(out) > limit
|
|
203
|
+
out = out[:limit]
|
|
204
|
+
cursor = f"{out[-1]['updatedAt']}:{out[-1]['id']}" if more and out else None
|
|
205
|
+
return out, cursor
|
|
206
|
+
|
|
207
|
+
def update_thread(self, thread_id, changes, *, touch=True):
|
|
208
|
+
"""Merge ``changes`` into the thread; returns it, or None when it doesn't exist."""
|
|
209
|
+
with self.journal.transaction() as connection:
|
|
210
|
+
row = connection.execute("SELECT data FROM threads WHERE id=?", (thread_id,)).fetchone()
|
|
211
|
+
if row is None:
|
|
212
|
+
return None
|
|
213
|
+
thread = json.loads(row[0])
|
|
214
|
+
thread.update(changes)
|
|
215
|
+
if touch:
|
|
216
|
+
thread["updatedAt"] = max(now_ms(), thread.get("updatedAt") or 0)
|
|
217
|
+
connection.execute("UPDATE threads SET data=?, updated_at=? WHERE id=?",
|
|
218
|
+
(encode(thread), thread["updatedAt"] / 1000, thread_id))
|
|
219
|
+
return thread
|
|
220
|
+
|
|
221
|
+
def delete_thread(self, thread_id):
|
|
222
|
+
"""Remove the thread and its items. Returns True when it existed."""
|
|
223
|
+
with self.journal.transaction() as connection:
|
|
224
|
+
deleted = connection.execute("DELETE FROM threads WHERE id=?", (thread_id,)).rowcount == 1
|
|
225
|
+
connection.execute("UPDATE thread_requests SET response=? WHERE thread_id=? AND response != 'null'", (
|
|
226
|
+
encode({"status": 410, "response": {"error": "That conversation was deleted; it was not started again",
|
|
227
|
+
"code": "thread_expired"}}), thread_id))
|
|
228
|
+
return deleted
|
|
229
|
+
|
|
230
|
+
def threads_with_status(self, statuses):
|
|
231
|
+
rows = self._read("SELECT data FROM threads")
|
|
232
|
+
return [t for t in (json.loads(r[0]) for r in rows) if t.get("status") in statuses]
|
|
233
|
+
|
|
234
|
+
def items(self, thread_id, after=None):
|
|
235
|
+
"""The thread's items in order (``after``: only those with a larger seq)."""
|
|
236
|
+
rows = self._read("SELECT data FROM thread_items WHERE thread_id=? AND seq>? ORDER BY seq",
|
|
237
|
+
(thread_id, -1 if after is None else int(after)))
|
|
238
|
+
return [json.loads(r[0]) for r in rows]
|
|
239
|
+
|
|
240
|
+
def run_ids(self, thread_id):
|
|
241
|
+
rows = self._read("SELECT DISTINCT run_id FROM thread_items WHERE thread_id=? AND kind='run' "
|
|
242
|
+
"AND run_id IS NOT NULL", (thread_id,))
|
|
243
|
+
return [r[0] for r in rows]
|
|
244
|
+
|
|
245
|
+
def find(self, thread_id, *, kind=None, run_id=None, match=None):
|
|
246
|
+
"""The items of ``kind`` (and ``run_id``) for which ``match(item)`` holds, in order."""
|
|
247
|
+
sql, args = "SELECT data FROM thread_items WHERE thread_id=?", [thread_id]
|
|
248
|
+
if kind is not None:
|
|
249
|
+
sql += " AND kind=?"
|
|
250
|
+
args.append(kind)
|
|
251
|
+
if run_id is not None:
|
|
252
|
+
sql += " AND run_id=?"
|
|
253
|
+
args.append(run_id)
|
|
254
|
+
items = [json.loads(r[0]) for r in self._read(sql + " ORDER BY seq", args)]
|
|
255
|
+
return [item for item in items if match is None or match(item)]
|
|
256
|
+
|
|
257
|
+
def usage(self, thread_id):
|
|
258
|
+
"""(item count, bytes) of the thread."""
|
|
259
|
+
row = self._read("SELECT count(*), coalesce(sum(bytes), 0) FROM thread_items WHERE thread_id=?",
|
|
260
|
+
(thread_id,))[0]
|
|
261
|
+
return int(row[0]), int(row[1])
|
|
262
|
+
|
|
263
|
+
def append(self, thread_id, items, *, unless=None):
|
|
264
|
+
"""Add ``items`` (dicts with at least "kind") in one transaction, after checking there is room for them."""
|
|
265
|
+
items = [dict(item) for item in items]
|
|
266
|
+
encoded, problem = [], None
|
|
267
|
+
# Journal.transaction turns any exception into a JournalError: decide inside, raise after.
|
|
268
|
+
with self.journal.transaction() as connection:
|
|
269
|
+
if connection.execute("SELECT 1 FROM threads WHERE id=?", (thread_id,)).fetchone() is None:
|
|
270
|
+
problem = ThreadMissing()
|
|
271
|
+
elif unless is not None and unless(self._existing(connection, thread_id, items)):
|
|
272
|
+
return None
|
|
273
|
+
else:
|
|
274
|
+
count, size, last = connection.execute(
|
|
275
|
+
"SELECT count(*), coalesce(sum(bytes), 0), coalesce(max(seq), -1) FROM thread_items "
|
|
276
|
+
"WHERE thread_id=?", (thread_id,)).fetchone()
|
|
277
|
+
stamp = now_ms()
|
|
278
|
+
for offset, item in enumerate(items):
|
|
279
|
+
if not (isinstance(item.get("id"), str) and ID.fullmatch(item["id"])):
|
|
280
|
+
item["id"] = new_id()
|
|
281
|
+
item["seq"] = last + 1 + offset
|
|
282
|
+
item.setdefault("at", stamp)
|
|
283
|
+
data = encode(item)
|
|
284
|
+
encoded.append((item, data, len(data.encode())))
|
|
285
|
+
if count + len(items) > MAX_ITEMS or \
|
|
286
|
+
size + sum(e[2] for e in encoded) > MAX_THREAD_BYTES:
|
|
287
|
+
problem = ThreadFull()
|
|
288
|
+
else:
|
|
289
|
+
for item, data, size_bytes in encoded:
|
|
290
|
+
connection.execute(
|
|
291
|
+
"INSERT INTO thread_items(thread_id, seq, data, id, kind, run_id, bytes) "
|
|
292
|
+
"VALUES (?,?,?,?,?,?,?)",
|
|
293
|
+
(thread_id, item["seq"], data, item["id"], item["kind"], item.get("runId"), size_bytes))
|
|
294
|
+
row = connection.execute("SELECT data FROM threads WHERE id=?", (thread_id,)).fetchone()
|
|
295
|
+
thread = json.loads(row[0])
|
|
296
|
+
thread["updatedAt"] = max(stamp, thread.get("updatedAt") or 0)
|
|
297
|
+
connection.execute("UPDATE threads SET data=?, updated_at=? WHERE id=?",
|
|
298
|
+
(encode(thread), thread["updatedAt"] / 1000, thread_id))
|
|
299
|
+
if problem is not None:
|
|
300
|
+
raise problem
|
|
301
|
+
return [e[0] for e in encoded]
|
|
302
|
+
|
|
303
|
+
@staticmethod
|
|
304
|
+
def _existing(connection, thread_id, items):
|
|
305
|
+
"""The thread's stored items of the same kinds (and runs) as ``items``."""
|
|
306
|
+
kinds = sorted({item["kind"] for item in items})
|
|
307
|
+
runs = sorted({item.get("runId") for item in items if item.get("runId")})
|
|
308
|
+
sql = f"SELECT data FROM thread_items WHERE thread_id=? AND kind IN ({','.join('?' * len(kinds))})"
|
|
309
|
+
args = [thread_id, *kinds]
|
|
310
|
+
if runs:
|
|
311
|
+
sql += f" AND run_id IN ({','.join('?' * len(runs))})"
|
|
312
|
+
args += runs
|
|
313
|
+
return [json.loads(r[0]) for r in connection.execute(sql + " ORDER BY seq", args)]
|
|
314
|
+
|
|
315
|
+
def update_item(self, thread_id, item_id, changes, *, kinds=None, max_bytes=None):
|
|
316
|
+
"""Merge ``changes`` into an item (never its id, seq, kind, at or runId)."""
|
|
317
|
+
changes = {k: v for k, v in dict(changes).items() if k not in ("id", "seq", "kind", "at", "runId")}
|
|
318
|
+
problem = None
|
|
319
|
+
with self.journal.transaction() as connection:
|
|
320
|
+
row = connection.execute("SELECT data, bytes FROM thread_items WHERE thread_id=? AND id=?",
|
|
321
|
+
(thread_id, item_id)).fetchone()
|
|
322
|
+
if row is None:
|
|
323
|
+
return None
|
|
324
|
+
item = json.loads(row[0])
|
|
325
|
+
if kinds is not None and item.get("kind") not in kinds:
|
|
326
|
+
return None
|
|
327
|
+
item.update(changes)
|
|
328
|
+
data = encode(item)
|
|
329
|
+
size = len(data.encode())
|
|
330
|
+
total = connection.execute("SELECT coalesce(sum(bytes), 0) FROM thread_items WHERE thread_id=?",
|
|
331
|
+
(thread_id,)).fetchone()[0]
|
|
332
|
+
if max_bytes is not None and size > max_bytes:
|
|
333
|
+
problem = ValueError(f"A thread item is at most {max_bytes:,} bytes")
|
|
334
|
+
elif total - row[1] + size > MAX_THREAD_BYTES:
|
|
335
|
+
problem = ThreadFull()
|
|
336
|
+
else:
|
|
337
|
+
connection.execute("UPDATE thread_items SET data=?, bytes=? WHERE thread_id=? AND id=?",
|
|
338
|
+
(data, size, thread_id, item_id))
|
|
339
|
+
if problem is not None:
|
|
340
|
+
raise problem
|
|
341
|
+
return item
|
|
342
|
+
|
|
343
|
+
def request(self, key):
|
|
344
|
+
"""(thread id, fingerprint, response) saved under a key, or None. A null response is unresolved intent."""
|
|
345
|
+
rows = self._read("SELECT thread_id, fingerprint, response FROM thread_requests WHERE key=?", (key,))
|
|
346
|
+
return (rows[0][0], rows[0][1], json.loads(rows[0][2])) if rows else None
|
|
347
|
+
|
|
348
|
+
def remember(self, key, thread_id, fingerprint, response):
|
|
349
|
+
with self.journal.transaction() as connection:
|
|
350
|
+
if connection.execute("SELECT 1 FROM thread_requests WHERE key=?", (key,)).fetchone() is None:
|
|
351
|
+
excess = connection.execute("SELECT count(*) FROM thread_requests").fetchone()[0] - REQUEST_LIMIT + 1
|
|
352
|
+
if excess > 0:
|
|
353
|
+
# Never evict an unresolved intent: its effect may already have reached the phone.
|
|
354
|
+
connection.execute("DELETE FROM thread_requests WHERE rowid IN (SELECT rowid FROM thread_requests "
|
|
355
|
+
"WHERE response != 'null' ORDER BY created_at, rowid LIMIT ?)", (excess,))
|
|
356
|
+
if connection.execute("SELECT count(*) FROM thread_requests").fetchone()[0] >= REQUEST_LIMIT:
|
|
357
|
+
raise JournalError(
|
|
358
|
+
"Conversation request journal is full; check unresolved requests before continuing")
|
|
359
|
+
connection.execute("INSERT OR REPLACE INTO thread_requests(key, thread_id, fingerprint, response, "
|
|
360
|
+
"created_at) VALUES (?,?,?,?,?)",
|
|
361
|
+
(key, thread_id, fingerprint, encode(response), time.time()))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""What a finished run leaves in its thread: its public result, what the next task can refer to, its private context."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from .. import secret_filter
|
|
6
|
+
|
|
7
|
+
NOTE_CHARS = 600
|
|
8
|
+
ANSWER_CONTEXT_CHARS = 600
|
|
9
|
+
FACTS = 6
|
|
10
|
+
FACT_CHARS = 200
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def group_fields(group):
|
|
14
|
+
"""A group item's fields from fanout's public group: its phones (each with its run, state and why it didn't
|
|
15
|
+
start), the combined state and counts, the warnings, the cost estimate and, once every phone is done, the merged
|
|
16
|
+
summary."""
|
|
17
|
+
members = [{key: member.get(key) for key in ("device", "deviceName", "runId", "state", "error", "code")}
|
|
18
|
+
for member in group["members"]]
|
|
19
|
+
return {"groupId": group["id"], "goal": group["goal"], "members": members, "state": group["state"],
|
|
20
|
+
"counts": group["counts"], "warning": group.get("warning"), "held": group.get("held"),
|
|
21
|
+
"estimateUsd": group.get("estimateUsd"), "estimateTotalUsd": group.get("estimateTotalUsd"),
|
|
22
|
+
"summary": group.get("summary"), "finishedAt": group.get("finishedAt")}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def result_of(summary, run=None):
|
|
26
|
+
"""The run item's public result: status, outcome, the answer's headline, facts, cost and time."""
|
|
27
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
28
|
+
facts = []
|
|
29
|
+
for entry in summary.get("proof") or ():
|
|
30
|
+
# A quote of only spaces is no proof (the app's runOutcome trims it too), so it never becomes a fact.
|
|
31
|
+
if not isinstance(entry, dict) or not str(entry.get("quote") or "").strip():
|
|
32
|
+
continue
|
|
33
|
+
fact = secret_filter.redact(" ".join(str(entry["quote"]).split()))[:FACT_CHARS]
|
|
34
|
+
if entry.get("app"):
|
|
35
|
+
fact += f" ({str(entry['app'])[:40]})"
|
|
36
|
+
facts.append(fact)
|
|
37
|
+
if len(facts) >= FACTS:
|
|
38
|
+
break
|
|
39
|
+
answer = summary.get("answer")
|
|
40
|
+
cost = summary.get("costUsd")
|
|
41
|
+
if cost is None and run is not None:
|
|
42
|
+
cost = getattr(run, "cost_usd", None)
|
|
43
|
+
elapsed = summary.get("elapsed_ms")
|
|
44
|
+
return {"status": summary.get("status") or (getattr(run, "status", None) or "error"),
|
|
45
|
+
"outcome": summary.get("outcome") if isinstance(summary.get("outcome"), str) else None,
|
|
46
|
+
"answer": secret_filter.redact(answer)[:1000] if isinstance(answer, str) else None,
|
|
47
|
+
"facts": facts, "costUsd": cost if isinstance(cost, (int, float)) else None,
|
|
48
|
+
"elapsedMs": elapsed if isinstance(elapsed, (int, float)) else None}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
REFERENTS = 12
|
|
52
|
+
NUMBERED = re.compile(r"^\s*(\d+)[.)]\s+(.+)$")
|
|
53
|
+
BULLETED = re.compile(r"^\s*[-•*]\s+(.+)$")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def referents_of(summary):
|
|
57
|
+
"""Up to REFERENTS numbered things a run produced, so "the second one" resolves later (DESIGN §8.1)."""
|
|
58
|
+
summary = summary if isinstance(summary, dict) else {}
|
|
59
|
+
out = []
|
|
60
|
+
report = summary.get("report")
|
|
61
|
+
if isinstance(report, dict) and report.get("ranked"):
|
|
62
|
+
from ..longform.report import referents
|
|
63
|
+
out = referents(report)
|
|
64
|
+
if not out:
|
|
65
|
+
full = summary.get("full_answer") or (summary.get("data") if isinstance(summary.get("data"), str) else None) \
|
|
66
|
+
or summary.get("answer") or ""
|
|
67
|
+
numbered, bulleted = [], []
|
|
68
|
+
for line in str(full).splitlines():
|
|
69
|
+
found = NUMBERED.match(line)
|
|
70
|
+
if found:
|
|
71
|
+
numbered.append({"rank": int(found.group(1)), "text": found.group(2)})
|
|
72
|
+
continue
|
|
73
|
+
found = BULLETED.match(line)
|
|
74
|
+
if found:
|
|
75
|
+
bulleted.append({"rank": len(bulleted) + 1, "text": found.group(1)})
|
|
76
|
+
out = numbered or bulleted
|
|
77
|
+
if not out:
|
|
78
|
+
quotes = [entry.get("quote") for entry in summary.get("proof") or () if isinstance(entry, dict)
|
|
79
|
+
and entry.get("quote")]
|
|
80
|
+
out = [{"rank": n, "text": quote} for n, quote in enumerate(quotes, 1)] if len(quotes) >= 2 else []
|
|
81
|
+
clean = []
|
|
82
|
+
for ref in out[:REFERENTS]:
|
|
83
|
+
text = secret_filter.redact(" ".join(str(ref.get("text") or "").split()).strip("*"))[:200]
|
|
84
|
+
if text and not secret_filter.is_secret(text):
|
|
85
|
+
clean.append({"rank": int(ref.get("rank") or len(clean) + 1), "text": text})
|
|
86
|
+
return clean
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def context_of(summary, private, last_app):
|
|
90
|
+
"""What the next task in the thread reads about this one (private: never sent by the API)."""
|
|
91
|
+
full = summary.get("full_answer") or (summary.get("data") if isinstance(summary.get("data"), str) else None) \
|
|
92
|
+
or summary.get("answer")
|
|
93
|
+
notes, total = [], 0
|
|
94
|
+
for note in (private or {}).get("notes") or ():
|
|
95
|
+
if not isinstance(note, str) or not note.strip():
|
|
96
|
+
continue
|
|
97
|
+
clean = secret_filter.redact(" ".join(note.split()))
|
|
98
|
+
if secret_filter.is_secret(clean):
|
|
99
|
+
continue
|
|
100
|
+
if total + len(clean) > NOTE_CHARS:
|
|
101
|
+
clean = clean[:max(0, NOTE_CHARS - total - 1)] + "…"
|
|
102
|
+
if len(clean) > 1:
|
|
103
|
+
notes.append(clean)
|
|
104
|
+
total += len(clean)
|
|
105
|
+
if total >= NOTE_CHARS:
|
|
106
|
+
break
|
|
107
|
+
out = {"notes": notes, "lastApp": last_app}
|
|
108
|
+
if isinstance(full, str) and full.strip():
|
|
109
|
+
out["answer"] = secret_filter.redact(" ".join(full.split()))[:ANSWER_CONTEXT_CHARS]
|
|
110
|
+
report = summary.get("report")
|
|
111
|
+
if isinstance(report, dict):
|
|
112
|
+
# A long task's report outlives the run's retention here: its TL;DR, headings and shortlist (DESIGN §8.3).
|
|
113
|
+
from ..longform.report import short_form
|
|
114
|
+
short = short_form(report)
|
|
115
|
+
if short:
|
|
116
|
+
out["report"] = secret_filter.redact(short)
|
|
117
|
+
return out
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""The terminal under `mobster chat`: lines printed above a prompt that keeps what you're typing."""
|
|
2
|
+
|
|
3
|
+
import codecs
|
|
4
|
+
import os
|
|
5
|
+
import select
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Term:
|
|
11
|
+
"""Lines printed above a prompt that keeps what you're typing."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, stdin=None, stdout=None, prompt="› "):
|
|
14
|
+
self.stdin, self.stdout = stdin or sys.stdin, stdout or sys.stdout
|
|
15
|
+
self.prompt, self.buffer = prompt, ""
|
|
16
|
+
self.tty = isatty(self.stdin) and isatty(self.stdout)
|
|
17
|
+
self.saved = None
|
|
18
|
+
self.lock = threading.RLock()
|
|
19
|
+
self.shown = False
|
|
20
|
+
self.prompting = True # whether the prompt line is drawn (one message: only while something waits)
|
|
21
|
+
# A character split across two reads (an emoji typed or pasted) decodes once, whole.
|
|
22
|
+
self.decoder = codecs.getincrementaldecoder("utf-8")("replace")
|
|
23
|
+
|
|
24
|
+
def __enter__(self):
|
|
25
|
+
if self.tty:
|
|
26
|
+
import termios
|
|
27
|
+
import tty
|
|
28
|
+
try:
|
|
29
|
+
fd = self.stdin.fileno()
|
|
30
|
+
self.saved = termios.tcgetattr(fd)
|
|
31
|
+
tty.setcbreak(fd)
|
|
32
|
+
mode = termios.tcgetattr(fd)
|
|
33
|
+
mode[3] &= ~termios.ECHO # this prompt draws what you type itself
|
|
34
|
+
termios.tcsetattr(fd, termios.TCSANOW, mode)
|
|
35
|
+
except (termios.error, OSError, ValueError):
|
|
36
|
+
self.saved = None
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def __exit__(self, *exc):
|
|
40
|
+
self.clear()
|
|
41
|
+
if self.saved is not None:
|
|
42
|
+
import termios
|
|
43
|
+
try:
|
|
44
|
+
termios.tcsetattr(self.stdin.fileno(), termios.TCSADRAIN, self.saved)
|
|
45
|
+
except (termios.error, OSError, ValueError):
|
|
46
|
+
pass
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
def write(self, text):
|
|
50
|
+
try:
|
|
51
|
+
self.stdout.write(text)
|
|
52
|
+
self.stdout.flush()
|
|
53
|
+
except (OSError, ValueError):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def clear(self):
|
|
57
|
+
with self.lock:
|
|
58
|
+
if self.tty and self.shown:
|
|
59
|
+
self.write("\r\033[2K")
|
|
60
|
+
self.shown = False
|
|
61
|
+
|
|
62
|
+
def show(self):
|
|
63
|
+
with self.lock:
|
|
64
|
+
if self.tty and self.prompting:
|
|
65
|
+
self.write("\r\033[2K" + self.prompt + self.buffer)
|
|
66
|
+
self.shown = True
|
|
67
|
+
|
|
68
|
+
def print(self, text=""):
|
|
69
|
+
with self.lock:
|
|
70
|
+
self.clear()
|
|
71
|
+
self.write(str(text) + "\n")
|
|
72
|
+
self.show()
|
|
73
|
+
|
|
74
|
+
def keys(self, timeout):
|
|
75
|
+
"""What was typed within ``timeout`` seconds ("" for nothing; None at the end of input)."""
|
|
76
|
+
if not self.tty:
|
|
77
|
+
line = self.stdin.readline()
|
|
78
|
+
return None if line == "" else line if line.endswith("\n") else line + "\n"
|
|
79
|
+
ready, _, _ = select.select([self.stdin], [], [], timeout)
|
|
80
|
+
if not ready:
|
|
81
|
+
return ""
|
|
82
|
+
data = os.read(self.stdin.fileno(), 1024)
|
|
83
|
+
return None if not data else self.decoder.decode(data)
|
|
84
|
+
|
|
85
|
+
def discard_typeahead(self):
|
|
86
|
+
"""Forget every key typed before now that this prompt hasn't read yet (the terminal's input queue)."""
|
|
87
|
+
if not self.tty:
|
|
88
|
+
return
|
|
89
|
+
import termios
|
|
90
|
+
try:
|
|
91
|
+
termios.tcflush(self.stdin.fileno(), termios.TCIFLUSH)
|
|
92
|
+
except (termios.error, OSError, ValueError):
|
|
93
|
+
pass
|
|
94
|
+
self.decoder.reset()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def isatty(stream):
|
|
98
|
+
try:
|
|
99
|
+
return stream.isatty()
|
|
100
|
+
except (AttributeError, ValueError):
|
|
101
|
+
return False
|