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,647 @@
|
|
|
1
|
+
"""L0: trace analytics on runs we already have. $0, seconds, no device, no model.
|
|
2
|
+
|
|
3
|
+
python -m mobile_agent.bench.evals.l0 --runs DIR... [--batches aa1,ac1] [--app-groups groups.json] \\
|
|
4
|
+
[--labels labels.jsonl] --out OUT_DIR
|
|
5
|
+
|
|
6
|
+
Mines every recorded run (``traces.discover``) for four things and writes one ranked table:
|
|
7
|
+
|
|
8
|
+
1. **Wasted actions** (per turn, so nothing is counted twice within a mechanism):
|
|
9
|
+
- ``round_trip``: a turn opened a screen and the next turn left it for the screen before, with nothing written down
|
|
10
|
+
on the way (no note, no checklist item found or done, no answer).
|
|
11
|
+
- ``repeat_action``: the same action on the same screen as earlier in the run, with nothing gained in between.
|
|
12
|
+
- ``no_change``: an action the driver ran that changed nothing (typing excepted).
|
|
13
|
+
- ``guard_refusal`` and ``loop_refusal``: the code refused the turn's action (commit guard; loop, repeat, idle).
|
|
14
|
+
- ``failed_action``: the driver raised.
|
|
15
|
+
- ``done_deferred``: a DONE the code sent back (proof gate, verify before done).
|
|
16
|
+
- ``chainable_turn``: a turn on the same screen as the turn before, whose target was already on that screen and
|
|
17
|
+
whose turn before ran cleanly: one more action the previous turn could have chained.
|
|
18
|
+
- ``done_only_final``: the run's last turn has only DONE: the fixed ~2 s finishing call.
|
|
19
|
+
2. **Step inefficiency**: a passing run's turns and actions beyond the fewest any passing run of the same task took.
|
|
20
|
+
3. **Time sinks** by step type (ground, type, scroll, switch, end) and by app group: model, generation, exec, settle,
|
|
21
|
+
refresh. Settle after the screen stopped moving comes from ``frameclock.jsonl`` where a run has it.
|
|
22
|
+
4. **First divergence** of each failing run from the closest passing run of the same task: actions are aligned by
|
|
23
|
+
their longest common subsequence; the divergence is where the failing run leaves the passing path for good, and
|
|
24
|
+
its kind (stopped early, a different app or order, a different element, scrolled instead...).
|
|
25
|
+
|
|
26
|
+
The mechanisms overlap across rows (a refused repeat is both): each row is a ceiling on what removing that one
|
|
27
|
+
mechanism could save, not a share of a partition.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import collections
|
|
32
|
+
import json
|
|
33
|
+
import pathlib
|
|
34
|
+
import statistics
|
|
35
|
+
|
|
36
|
+
from . import traces as T
|
|
37
|
+
|
|
38
|
+
# Which hypothesis (the tuning plan's ranked list) each mechanism would feed.
|
|
39
|
+
HYPOTHESES = {
|
|
40
|
+
"round_trip": "H2 stall recovery, H5 screen-diff history",
|
|
41
|
+
"repeat_action": "H2 stall recovery",
|
|
42
|
+
"loop_refusal": "H2 stall recovery",
|
|
43
|
+
"no_change": "H10 device time, grounding",
|
|
44
|
+
"guard_refusal": "commit guard (#224)",
|
|
45
|
+
"failed_action": "infra, H10 device time",
|
|
46
|
+
"done_deferred": "H3 completion check, contract quality",
|
|
47
|
+
"chainable_turn": "H7 token diet, H9 speculative call (chain more)",
|
|
48
|
+
"done_only_final": "H8 DONE folded into the last action",
|
|
49
|
+
"excess_turns": "H1 typed slots, H2, H5 (shorter paths)",
|
|
50
|
+
"settle_after_still": "H10 settle on frame stability",
|
|
51
|
+
"typing_exec": "H10 device time (typing)",
|
|
52
|
+
"source_reads": "H4 fewer device reads (overlaps settle, refresh, exec)",
|
|
53
|
+
"repeated_reads": "H4 fewer device reads",
|
|
54
|
+
"generation": "H7 token diet, H6 streaming dispatch",
|
|
55
|
+
"first_token": "H11 tail control, H6",
|
|
56
|
+
}
|
|
57
|
+
WASTE = ("round_trip", "repeat_action", "no_change", "guard_refusal", "loop_refusal", "failed_action",
|
|
58
|
+
"done_deferred", "chainable_turn", "done_only_final", "repeated_reads")
|
|
59
|
+
LOOP_LABELS = frozenset({"loop", "repeat", "idle_wait", "dismiss_exhausted"})
|
|
60
|
+
DEFER_STOPS = frozenset({"proof_gate", "verify_before_done", "done_not_arrived"})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _labels_on(turn_text_rows):
|
|
64
|
+
return {T.norm(label) for _, _, label in turn_text_rows}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def wasted(run, screens=None):
|
|
68
|
+
"""[(mechanism, step, turns, actions, seconds)] for one run. ``screens``: {step: set of labels on that screen}."""
|
|
69
|
+
out, turns = [], run.turns
|
|
70
|
+
screens = screens or {}
|
|
71
|
+
seen = {}
|
|
72
|
+
gained_since = 0
|
|
73
|
+
for i, t in enumerate(turns):
|
|
74
|
+
executed = [a for a in t.actions if a.executed]
|
|
75
|
+
model_s = ((t.model_ms or 0.0) + (t.prep_ms or 0.0)) / 1000
|
|
76
|
+
nothing_ran = not executed
|
|
77
|
+
guards = T.guard_refusals(t)
|
|
78
|
+
loops = [r for r in t.refused if r in LOOP_LABELS]
|
|
79
|
+
if guards:
|
|
80
|
+
out.append(("guard_refusal", t.step, 1 if nothing_ran else 0, len(guards), model_s if nothing_ran else 0.0))
|
|
81
|
+
if loops:
|
|
82
|
+
out.append(("loop_refusal", t.step, 1 if nothing_ran else 0, len(loops), model_s if nothing_ran else 0.0))
|
|
83
|
+
if t.failed:
|
|
84
|
+
out.append(("failed_action", t.step, 1 if nothing_ran else 0, len(t.failed),
|
|
85
|
+
(model_s if nothing_ran else 0.0)))
|
|
86
|
+
if set(t.stops) & DEFER_STOPS and t.done:
|
|
87
|
+
out.append(("done_deferred", t.step, 1, 0, t.seconds))
|
|
88
|
+
dead = [a for a in executed if a.changed is False and a.op not in T.TYPED_OPS
|
|
89
|
+
and a.op not in ("WAIT", "READ_LIST")
|
|
90
|
+
and a.op not in T.END_OPS]
|
|
91
|
+
if dead:
|
|
92
|
+
whole = len(dead) == len(executed) and not t.gained
|
|
93
|
+
out.append(("no_change", t.step, 1 if whole else 0, len(dead),
|
|
94
|
+
sum(a.device_ms for a in dead) / 1000 + (model_s if whole else 0.0)))
|
|
95
|
+
# the same action on the same screen as before, nothing gained in between
|
|
96
|
+
for a in executed:
|
|
97
|
+
key = (t.screen, a.token())
|
|
98
|
+
if t.screen and key in seen and seen[key] >= gained_since and not t.gained:
|
|
99
|
+
out.append(("repeat_action", t.step, 1, 1, t.seconds))
|
|
100
|
+
break
|
|
101
|
+
for a in executed:
|
|
102
|
+
seen.setdefault((t.screen, a.token()), i)
|
|
103
|
+
if t.gained:
|
|
104
|
+
gained_since = i
|
|
105
|
+
# A -> B -> A, nothing written down at B, B's way out a back control (or a tap on A's own title)
|
|
106
|
+
if 0 < i < len(turns) - 1:
|
|
107
|
+
a_turn, b_turn, c_turn = turns[i - 1], t, turns[i + 1]
|
|
108
|
+
if (a_turn.screen and b_turn.screen and c_turn.screen == a_turn.screen and b_turn.screen != a_turn.screen
|
|
109
|
+
and not b_turn.gained and b_turn.actions
|
|
110
|
+
and all(x.is_back or (a_turn.title and T.norm(x.label) == T.norm(a_turn.title))
|
|
111
|
+
or x.op in ("SWIPE_RIGHT", "HOME") for x in b_turn.actions)):
|
|
112
|
+
# the opening turn's model call counts only when it wrote nothing down about its own screen
|
|
113
|
+
a_cost = a_turn.device_ms / 1000 if a_turn.gained else a_turn.seconds
|
|
114
|
+
out.append(("round_trip", b_turn.step, 1 + (not a_turn.gained),
|
|
115
|
+
len(a_turn.actions) + len(b_turn.actions), a_cost + b_turn.seconds))
|
|
116
|
+
# one more action the previous turn could have chained
|
|
117
|
+
if i > 0 and not t.done:
|
|
118
|
+
prev = turns[i - 1]
|
|
119
|
+
labels = screens.get(prev.step)
|
|
120
|
+
if (prev.screen and t.screen == prev.screen and prev.actions and not prev.refused and not prev.failed
|
|
121
|
+
and all(x.changed is not False or x.op in T.TYPED_OPS for x in prev.actions)
|
|
122
|
+
and labels is not None and executed and all(T.norm(x.label) in labels for x in executed
|
|
123
|
+
if x.op in ("TAP", "LONG_PRESS")
|
|
124
|
+
or x.op in T.TYPED_OPS)
|
|
125
|
+
and any(x.op in ("TAP", "LONG_PRESS") or x.op in T.TYPED_OPS for x in executed)):
|
|
126
|
+
out.append(("chainable_turn", t.step, 1, 0, model_s))
|
|
127
|
+
if turns and turns[-1].done_only and run.passed:
|
|
128
|
+
last = turns[-1]
|
|
129
|
+
out.append(("done_only_final", last.step, 1, 0, ((last.model_ms or 0.0) + (last.prep_ms or 0.0)) / 1000))
|
|
130
|
+
return out
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def screen_labels(run):
|
|
134
|
+
"""{step: labels on the screen the model saw} from the prompts (the loader keeps only the hash)."""
|
|
135
|
+
out = {}
|
|
136
|
+
for p in T.read_jsonl(pathlib.Path(run.folder) / "prompts.jsonl"):
|
|
137
|
+
out[p.get("step")] = _labels_on(T.screen_rows(p.get("text") or ""))
|
|
138
|
+
return out
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def settle_after_still(run):
|
|
142
|
+
"""(seconds, actions) of settle wait after the screen's last motion, from the run's frameclock.jsonl."""
|
|
143
|
+
total, n = 0.0, 0
|
|
144
|
+
for row in T.read_jsonl(pathlib.Path(run.folder) / "frameclock.jsonl"):
|
|
145
|
+
if row.get("kind") != "settle":
|
|
146
|
+
continue
|
|
147
|
+
settle, motion = row.get("settle_ms"), row.get("motion_after_dispatch_ms")
|
|
148
|
+
if isinstance(settle, (int, float)) and isinstance(motion, (int, float)):
|
|
149
|
+
total += max(0.0, settle - max(motion, 0.0))
|
|
150
|
+
n += 1
|
|
151
|
+
return total / 1000, n
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def shortest(runs):
|
|
155
|
+
"""{(suite, task): (turns, actions)}: the fewest of any passing run."""
|
|
156
|
+
best = {}
|
|
157
|
+
for r in runs:
|
|
158
|
+
if r.passed and r.turns:
|
|
159
|
+
key = (r.suite, r.task)
|
|
160
|
+
cand = (len(r.turns), len(r.actions))
|
|
161
|
+
if key not in best:
|
|
162
|
+
best[key] = cand
|
|
163
|
+
else:
|
|
164
|
+
best[key] = (min(best[key][0], cand[0]), min(best[key][1], cand[1]))
|
|
165
|
+
return best
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def excess(run, best):
|
|
169
|
+
b = best.get((run.suite, run.task))
|
|
170
|
+
if not b or not run.passed:
|
|
171
|
+
return None
|
|
172
|
+
turns, actions = len(run.turns) - b[0], len(run.actions) - b[1]
|
|
173
|
+
per_turn = statistics.mean([t.seconds for t in run.turns if t.timed] or [0.0])
|
|
174
|
+
return {"turns": turns, "actions": actions, "seconds": max(turns, 0) * per_turn, "best_turns": b[0]}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def lcs_pairs(a, b):
|
|
178
|
+
"""Index pairs of a longest common subsequence of ``a`` and ``b``."""
|
|
179
|
+
n, m = len(a), len(b)
|
|
180
|
+
dp = [[0] * (m + 1) for _ in range(n + 1)]
|
|
181
|
+
for i in range(n - 1, -1, -1):
|
|
182
|
+
for j in range(m - 1, -1, -1):
|
|
183
|
+
dp[i][j] = dp[i + 1][j + 1] + 1 if a[i] == b[j] else max(dp[i + 1][j], dp[i][j + 1])
|
|
184
|
+
pairs, i, j = [], 0, 0
|
|
185
|
+
while i < n and j < m:
|
|
186
|
+
if a[i] == b[j]:
|
|
187
|
+
pairs.append((i, j))
|
|
188
|
+
i, j = i + 1, j + 1
|
|
189
|
+
elif dp[i + 1][j] >= dp[i][j + 1]:
|
|
190
|
+
i += 1
|
|
191
|
+
else:
|
|
192
|
+
j += 1
|
|
193
|
+
return pairs
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _steps(run):
|
|
197
|
+
"""[(turn, action)] in order, ending actions excluded."""
|
|
198
|
+
return [(t, a) for t in run.turns for a in (t.actions or t.planned) if a.op not in T.END_OPS]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
OP_CLASS = {"TYPE": "TYPE", "TYPE_SUBMIT": "TYPE", "SET_TEXT": "TYPE", "TAP": "TAP", "LONG_PRESS": "TAP",
|
|
202
|
+
"LAUNCH_APP": "OPEN", "OPEN_URL": "OPEN", "SUBMIT": "SUBMIT", "READ_NOTIFICATIONS": "READ_NOTIFICATIONS"}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def milestone(action):
|
|
206
|
+
"""The action as a milestone, or None for moves that carry no decision (scrolls, dismissals, home, waits)."""
|
|
207
|
+
kind = OP_CLASS.get(action.op)
|
|
208
|
+
if kind is None:
|
|
209
|
+
return None
|
|
210
|
+
app, op, label, text = action.token()
|
|
211
|
+
if kind == "OPEN":
|
|
212
|
+
return ("", "OPEN", label, "")
|
|
213
|
+
return (app, kind, label, text)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def milestones(passing, closest):
|
|
217
|
+
"""The milestones every passing run reached, in the order ``closest`` reached them."""
|
|
218
|
+
sets = [{milestone(a) for _, a in _steps(r)} - {None} for r in passing]
|
|
219
|
+
common = set.intersection(*sets) if sets else set()
|
|
220
|
+
order = []
|
|
221
|
+
for _, a in _steps(closest):
|
|
222
|
+
m = milestone(a)
|
|
223
|
+
if m in common and m not in order:
|
|
224
|
+
order.append(m)
|
|
225
|
+
return order
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def divergence(fail, passing):
|
|
229
|
+
"""Where ``fail`` first leaves the passing path: the first milestone (an action every passing run of the task
|
|
230
|
+
took) it never reached, and what it did instead at that point. ``passing``: the task's passing runs."""
|
|
231
|
+
f = _steps(fail)
|
|
232
|
+
f_ms = [milestone(a) for _, a in f]
|
|
233
|
+
closest = max(passing, key=lambda p: (len(lcs_pairs([m for m in f_ms if m], [m for m in (milestone(a) for _, a in
|
|
234
|
+
_steps(p)) if m])),
|
|
235
|
+
p.rubric or 0, -len(p.actions)))
|
|
236
|
+
order = milestones(passing, closest)
|
|
237
|
+
reached = set(f_ms)
|
|
238
|
+
missed = next((m for m in order if m not in reached), None)
|
|
239
|
+
p_steps = _steps(closest)
|
|
240
|
+
ending = fail.turns[-1] if fail.turns else None
|
|
241
|
+
turn = f_action = p_turn = p_action = None
|
|
242
|
+
if missed is None:
|
|
243
|
+
i0, kind = len(f), "reached_every_milestone" # the miss is in the answer, or in an action it added
|
|
244
|
+
turn = ending
|
|
245
|
+
else:
|
|
246
|
+
before = order[:order.index(missed)]
|
|
247
|
+
hits = [i for i, m in enumerate(f_ms) if m in before]
|
|
248
|
+
i0 = hits[-1] + 1 if hits else 0
|
|
249
|
+
p_turn, p_action = next(((t, a) for t, a in p_steps if milestone(a) == missed), (None, None))
|
|
250
|
+
# skip the failing run's moves that carry no decision: the divergence is its next real choice
|
|
251
|
+
while i0 < len(f) and f_ms[i0] is None:
|
|
252
|
+
i0 += 1
|
|
253
|
+
if i0 >= len(f):
|
|
254
|
+
turn = ending
|
|
255
|
+
if ending is not None and any(a.op == "BLOCKED" for a in ending.planned):
|
|
256
|
+
kind = "gave_up"
|
|
257
|
+
elif ending is not None and ending.done:
|
|
258
|
+
kind = "stopped_early_done"
|
|
259
|
+
else:
|
|
260
|
+
kind = "stopped_early_no_done"
|
|
261
|
+
else:
|
|
262
|
+
turn, f_action = f[i0]
|
|
263
|
+
fm = f_ms[i0]
|
|
264
|
+
if fm[1] == "OPEN" or missed[1] == "OPEN" or T.app_key(f_action.app) != T.app_key(p_action.app):
|
|
265
|
+
kind = "different_app_or_order"
|
|
266
|
+
elif fm[1] == "TYPE" and missed[1] == "TYPE" and fm[2] == missed[2]:
|
|
267
|
+
kind = "different_text"
|
|
268
|
+
elif fm[1] == missed[1]:
|
|
269
|
+
kind = "different_element"
|
|
270
|
+
else:
|
|
271
|
+
kind = "different_operation"
|
|
272
|
+
if p_turn is not None and turn.screen and turn.screen == p_turn.screen:
|
|
273
|
+
kind += "@same_screen"
|
|
274
|
+
return {"run": fail.id, "ref": closest.id, "task": fail.task, "suite": fail.suite, "category": fail.category,
|
|
275
|
+
"kind": kind, "index": i0, "of": len(f), "milestones": len(order),
|
|
276
|
+
"reached": sum(m in reached for m in order),
|
|
277
|
+
"position": round(i0 / len(f), 3) if f else None,
|
|
278
|
+
"step": turn.step if turn else None, "ref_step": p_turn.step if p_turn else None,
|
|
279
|
+
"app": turn.app if turn else None, "screen": turn.title if turn else None,
|
|
280
|
+
"same_screen": bool(p_turn is not None and turn is not None and turn.screen
|
|
281
|
+
and turn.screen == p_turn.screen),
|
|
282
|
+
"fail_action": [f_action.op, f_action.label[:80], f_action.app] if f_action else None,
|
|
283
|
+
"ref_action": [p_action.op, p_action.label[:80], p_action.app] if p_action else None}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def divergences(runs, only_clean=True):
|
|
287
|
+
"""One divergence per failing judged run that has a passing run of the same task to compare with."""
|
|
288
|
+
passing = collections.defaultdict(list)
|
|
289
|
+
for r in runs:
|
|
290
|
+
if r.passed and r.turns:
|
|
291
|
+
passing[(r.suite, r.task)].append(r)
|
|
292
|
+
out = []
|
|
293
|
+
for r in runs:
|
|
294
|
+
if not r.judged or r.passed or not r.turns or (only_clean and not r.clean):
|
|
295
|
+
continue
|
|
296
|
+
refs = passing.get((r.suite, r.task))
|
|
297
|
+
if refs:
|
|
298
|
+
out.append(divergence(r, refs))
|
|
299
|
+
return out
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def time_sinks(runs, groups=None):
|
|
303
|
+
"""{"by_step_type": {...}, "by_app_group": {...}}: seconds by bucket, over timed turns."""
|
|
304
|
+
groups = {T.app_key(k): v for k, v in (groups or {}).items()}
|
|
305
|
+
by_kind, by_group = collections.defaultdict(collections.Counter), collections.defaultdict(collections.Counter)
|
|
306
|
+
for r in runs:
|
|
307
|
+
for t in r.turns:
|
|
308
|
+
if not t.timed:
|
|
309
|
+
continue
|
|
310
|
+
group = groups.get(T.app_key(t.app), "other") if t.app else "none"
|
|
311
|
+
for table, key in ((by_kind, t.kind), (by_group, group)):
|
|
312
|
+
c = table[key]
|
|
313
|
+
c["turns"] += 1
|
|
314
|
+
c["model_s"] += (t.model_ms or 0.0) / 1000
|
|
315
|
+
c["prep_s"] += (t.prep_ms or 0.0) / 1000
|
|
316
|
+
if t.ttft_ms is not None:
|
|
317
|
+
c["ttft_turns"] += 1
|
|
318
|
+
c["ttft_s"] += t.ttft_ms / 1000
|
|
319
|
+
c["gen_s"] += (t.gen_ms or 0.0) / 1000
|
|
320
|
+
if t.out_tokens:
|
|
321
|
+
c["out_tokens"] += t.out_tokens
|
|
322
|
+
for a in t.actions:
|
|
323
|
+
c["actions"] += 1
|
|
324
|
+
c["exec_s"] += (a.exec_ms or 0.0) / 1000
|
|
325
|
+
c["settle_s"] += (a.settle_ms or 0.0) / 1000
|
|
326
|
+
c["refresh_s"] += (a.refresh_ms or 0.0) / 1000
|
|
327
|
+
def finish(table):
|
|
328
|
+
out = {}
|
|
329
|
+
for key, c in table.items():
|
|
330
|
+
total = c["model_s"] + c["prep_s"] + c["exec_s"] + c["settle_s"] + c["refresh_s"]
|
|
331
|
+
out[key] = {"turns": c["turns"], "actions": c["actions"], "total_s": round(total, 1),
|
|
332
|
+
"s_per_turn": round(total / c["turns"], 2) if c["turns"] else None,
|
|
333
|
+
"model_s_per_turn": round(c["model_s"] / c["turns"], 2) if c["turns"] else None,
|
|
334
|
+
"ttft_s_per_turn": round(c["ttft_s"] / c["ttft_turns"], 2) if c["ttft_turns"] else None,
|
|
335
|
+
"gen_s_per_turn": round(c["gen_s"] / c["ttft_turns"], 2) if c["ttft_turns"] else None,
|
|
336
|
+
"out_tokens_per_turn": round(c["out_tokens"] / c["turns"], 1) if c["turns"] else None,
|
|
337
|
+
"exec_s_per_action": round(c["exec_s"] / c["actions"], 2) if c["actions"] else None,
|
|
338
|
+
"settle_s_per_action": round(c["settle_s"] / c["actions"], 2) if c["actions"] else None,
|
|
339
|
+
"refresh_s_per_action": round(c["refresh_s"] / c["actions"], 2) if c["actions"] else None,
|
|
340
|
+
"share": None, **{k: round(c[k], 1) for k in ("model_s", "ttft_s", "gen_s", "exec_s",
|
|
341
|
+
"settle_s", "refresh_s")}}
|
|
342
|
+
grand = sum(v["total_s"] for v in out.values()) or 1.0
|
|
343
|
+
for v in out.values():
|
|
344
|
+
v["share"] = round(v["total_s"] / grand, 3)
|
|
345
|
+
return dict(sorted(out.items(), key=lambda kv: -kv[1]["total_s"]))
|
|
346
|
+
return {"by_step_type": finish(by_kind), "by_app_group": finish(by_group)}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def partition(runs):
|
|
350
|
+
"""Where the timed seconds go, without overlap: model (first token, generation, or whole where the run has no
|
|
351
|
+
stream timing), prep, and per action kind exec, settle and refresh."""
|
|
352
|
+
c = collections.Counter()
|
|
353
|
+
for r in runs:
|
|
354
|
+
for t in r.turns:
|
|
355
|
+
if not t.timed:
|
|
356
|
+
continue
|
|
357
|
+
if t.ttft_ms is not None:
|
|
358
|
+
c["model: first token"] += t.ttft_ms / 1000
|
|
359
|
+
c["model: generation"] += (t.gen_ms or 0.0) / 1000
|
|
360
|
+
c["model: rest of call"] += max(0.0, (t.model_ms or 0.0) - t.ttft_ms - (t.gen_ms or 0.0)) / 1000
|
|
361
|
+
else:
|
|
362
|
+
c["model: whole call"] += (t.model_ms or 0.0) / 1000
|
|
363
|
+
c["prep"] += (t.prep_ms or 0.0) / 1000
|
|
364
|
+
for a in t.actions:
|
|
365
|
+
kind = T.step_type(a.op)
|
|
366
|
+
c[f"exec: {kind}"] += (a.exec_ms or 0.0) / 1000
|
|
367
|
+
c["settle"] += (a.settle_ms or 0.0) / 1000
|
|
368
|
+
c["refresh"] += (a.refresh_ms or 0.0) / 1000
|
|
369
|
+
total = sum(c.values()) or 1.0
|
|
370
|
+
return [{"bucket": k, "seconds": round(v, 1), "share": round(v / total, 3)} for k, v in c.most_common() if v > 0]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _fit(xs, ys):
|
|
374
|
+
"""(intercept, slope, r) of a least-squares line, or Nones."""
|
|
375
|
+
if len(xs) < 3 or len(set(xs)) < 2:
|
|
376
|
+
return None, None, None
|
|
377
|
+
mx, my = statistics.mean(xs), statistics.mean(ys)
|
|
378
|
+
sxx = sum((x - mx) ** 2 for x in xs)
|
|
379
|
+
slope = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / sxx
|
|
380
|
+
r = statistics.correlation(xs, ys) if len(set(ys)) > 1 else None
|
|
381
|
+
return my - slope * mx, slope, r
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def ttft_table(runs, chars_per_token=3.0):
|
|
385
|
+
"""Where time to first token goes, from turns with streamed timing: cache hits, the fit on uncached tokens, and
|
|
386
|
+
the turns whose output tokens exceed what their visible reply needs (hidden thinking, estimated at
|
|
387
|
+
``chars_per_token``)."""
|
|
388
|
+
turns = [t for r in runs for t in r.turns if t.ttft_ms is not None and t.in_tokens]
|
|
389
|
+
if not turns:
|
|
390
|
+
return None
|
|
391
|
+
uncached = [t.in_tokens - (t.cached_tokens or 0) - (t.write_tokens or 0) for t in turns]
|
|
392
|
+
ttft = [t.ttft_ms for t in turns]
|
|
393
|
+
hidden = [(t.out_tokens or 0) - (t.out_chars or 0) / chars_per_token if t.out_chars else None for t in turns]
|
|
394
|
+
a, b, r = _fit(uncached, ttft)
|
|
395
|
+
plain = [(u, y) for u, y, h in zip(uncached, ttft, hidden) if h is not None and h < 20]
|
|
396
|
+
a2, b2, r2 = _fit([u for u, _ in plain], [y for _, y in plain])
|
|
397
|
+
bands = []
|
|
398
|
+
for lo, hi in ((-1e9, 20), (20, 120), (120, 250), (250, 1e9)):
|
|
399
|
+
sel = [y for y, h in zip(ttft, hidden) if h is not None and lo <= h < hi]
|
|
400
|
+
if sel:
|
|
401
|
+
bands.append({"hidden_tokens": [None if lo < -1e8 else lo, None if hi > 1e8 else hi], "turns": len(sel),
|
|
402
|
+
"ttft_p50_ms": round(statistics.median(sel)), "ttft_mean_ms": round(statistics.mean(sel))})
|
|
403
|
+
by_step = []
|
|
404
|
+
for lo, hi in ((0, 1), (1, 10), (10, 20), (20, 35), (35, 10**6)):
|
|
405
|
+
sel = [t.ttft_ms for t in turns if t.step is not None and lo <= t.step < hi]
|
|
406
|
+
if sel:
|
|
407
|
+
by_step.append({"turns_from": lo, "turns_to": hi, "n": len(sel),
|
|
408
|
+
"ttft_p50_ms": round(statistics.median(sel))})
|
|
409
|
+
p75 = statistics.quantiles(ttft, n=4)[2]
|
|
410
|
+
return {"turns": len(turns), "cache_missed_turns": sum(not (t.cached_tokens or 0) for t in turns),
|
|
411
|
+
"cache_write_turns": sum(bool(t.write_tokens) for t in turns),
|
|
412
|
+
"cached_share_mean": round(statistics.mean((t.cached_tokens or 0) / t.in_tokens for t in turns), 3),
|
|
413
|
+
"uncached_p50": statistics.median(uncached), "ttft_p50_ms": statistics.median(ttft),
|
|
414
|
+
"ttft_p90_ms": statistics.quantiles(ttft, n=10)[-1], "ttft_mean_ms": round(statistics.mean(ttft)),
|
|
415
|
+
"fit_ms_per_1k_uncached": round(b * 1000, 1) if b is not None else None,
|
|
416
|
+
"fit_intercept_ms": round(a) if a is not None else None, "fit_r": round(r, 3) if r is not None else None,
|
|
417
|
+
"fit_no_thinking_ms_per_1k": round(b2 * 1000, 1) if b2 is not None else None,
|
|
418
|
+
"fit_no_thinking_r": round(r2, 3) if r2 is not None else None,
|
|
419
|
+
"ms_above_p75_per_turn": round(sum(max(0.0, y - p75) for y in ttft) / len(ttft)),
|
|
420
|
+
"by_hidden_thinking": bands, "by_turn_index": by_step}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def mechanism_table(runs, use_screens=True):
|
|
424
|
+
"""Rows {mechanism, runs, occurrences, turns, actions, seconds, s_per_run, share_time, share_turns}, ranked."""
|
|
425
|
+
rows = collections.defaultdict(lambda: {"runs": set(), "occurrences": 0, "turns": 0, "actions": 0, "seconds": 0.0})
|
|
426
|
+
best = shortest(runs)
|
|
427
|
+
total_s = total_turns = 0.0
|
|
428
|
+
timed = [r for r in runs if r.timed]
|
|
429
|
+
for r in timed:
|
|
430
|
+
total_s += sum(t.seconds for t in r.turns)
|
|
431
|
+
total_turns += len(r.turns)
|
|
432
|
+
screens = screen_labels(r) if use_screens else None
|
|
433
|
+
for mech, _, turns, actions, seconds in wasted(r, screens):
|
|
434
|
+
row = rows[mech]
|
|
435
|
+
row["runs"].add(r.id)
|
|
436
|
+
row["occurrences"] += 1
|
|
437
|
+
row["turns"] += turns
|
|
438
|
+
row["actions"] += actions
|
|
439
|
+
row["seconds"] += seconds
|
|
440
|
+
ex = excess(r, best)
|
|
441
|
+
if ex and ex["turns"] > 0:
|
|
442
|
+
row = rows["excess_turns"]
|
|
443
|
+
row["runs"].add(r.id)
|
|
444
|
+
row["occurrences"] += 1
|
|
445
|
+
row["turns"] += ex["turns"]
|
|
446
|
+
row["actions"] += max(ex["actions"], 0)
|
|
447
|
+
row["seconds"] += ex["seconds"]
|
|
448
|
+
settle_s, n = settle_after_still(r)
|
|
449
|
+
if n:
|
|
450
|
+
row = rows["settle_after_still"]
|
|
451
|
+
row["runs"].add(r.id)
|
|
452
|
+
row["occurrences"] += n
|
|
453
|
+
row["seconds"] += settle_s
|
|
454
|
+
typing = [a for t in r.turns for a in t.actions if a.op in T.TYPED_OPS]
|
|
455
|
+
if typing:
|
|
456
|
+
row = rows["typing_exec"]
|
|
457
|
+
row["runs"].add(r.id)
|
|
458
|
+
row["occurrences"] += len(typing)
|
|
459
|
+
row["actions"] += len(typing)
|
|
460
|
+
row["seconds"] += sum((a.exec_ms or 0.0) for a in typing) / 1000
|
|
461
|
+
src, reads = (r.timing or {}).get("source_seconds"), int((r.timing or {}).get("source_reads") or 0)
|
|
462
|
+
if src:
|
|
463
|
+
row = rows["source_reads"]
|
|
464
|
+
row["runs"].add(r.id)
|
|
465
|
+
row["occurrences"] += reads
|
|
466
|
+
row["seconds"] += float(src)
|
|
467
|
+
extra = reads - len(r.turns) # one read per turn is the observation; the rest re-read the screen
|
|
468
|
+
if extra > 0:
|
|
469
|
+
row = rows["repeated_reads"]
|
|
470
|
+
row["runs"].add(r.id)
|
|
471
|
+
row["occurrences"] += extra
|
|
472
|
+
row["seconds"] += float(src) * extra / reads
|
|
473
|
+
gen = [t.gen_ms for t in r.turns if t.gen_ms is not None]
|
|
474
|
+
ttft = [t.ttft_ms for t in r.turns if t.ttft_ms is not None]
|
|
475
|
+
if gen:
|
|
476
|
+
row = rows["generation"]
|
|
477
|
+
row["runs"].add(r.id)
|
|
478
|
+
row["occurrences"] += len(gen)
|
|
479
|
+
row["seconds"] += sum(gen) / 1000
|
|
480
|
+
if ttft:
|
|
481
|
+
row = rows["first_token"]
|
|
482
|
+
row["runs"].add(r.id)
|
|
483
|
+
row["occurrences"] += len(ttft)
|
|
484
|
+
row["seconds"] += sum(ttft) / 1000
|
|
485
|
+
out = []
|
|
486
|
+
for mech, row in rows.items():
|
|
487
|
+
out.append({"mechanism": mech, "kind": "waste" if mech in WASTE or mech == "excess_turns" else "sink",
|
|
488
|
+
"runs": len(row["runs"]), "occurrences": row["occurrences"], "turns": row["turns"],
|
|
489
|
+
"actions": row["actions"], "seconds": round(row["seconds"], 1),
|
|
490
|
+
"s_per_run": round(row["seconds"] / len(timed), 2) if timed else None,
|
|
491
|
+
"share_time": round(row["seconds"] / total_s, 3) if total_s else None,
|
|
492
|
+
"share_turns": round(row["turns"] / total_turns, 3) if total_turns else None,
|
|
493
|
+
"feeds": HYPOTHESES.get(mech, "")})
|
|
494
|
+
out.sort(key=lambda r: -r["seconds"])
|
|
495
|
+
return out, {"runs": len(timed), "turns": int(total_turns), "seconds": round(total_s, 1)}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def outcome_table(runs, labels=None):
|
|
499
|
+
"""Failing runs: their seconds and turns by labelled cause (when labels are given) and by divergence kind."""
|
|
500
|
+
by_cause = collections.defaultdict(lambda: {"runs": 0, "turns": 0, "seconds": 0.0})
|
|
501
|
+
index = {pathlib.Path(r.folder).resolve(): r for r in runs}
|
|
502
|
+
for lab in labels or ():
|
|
503
|
+
folder = lab.get("folder")
|
|
504
|
+
run = None
|
|
505
|
+
for cand in (pathlib.Path(folder) if folder else None,):
|
|
506
|
+
if cand is not None:
|
|
507
|
+
run = index.get(cand.resolve()) if cand.is_absolute() else None
|
|
508
|
+
if run is None:
|
|
509
|
+
run = next((r for r in runs if r.task == lab.get("task") and r.arm == lab.get("arm")
|
|
510
|
+
and r.repeat == lab.get("run") and r.batch == lab.get("batch", r.batch)), None)
|
|
511
|
+
row = by_cause[lab.get("cause")]
|
|
512
|
+
row["runs"] += 1
|
|
513
|
+
if run is not None:
|
|
514
|
+
row["turns"] += len(run.turns)
|
|
515
|
+
row["seconds"] += sum(t.seconds for t in run.turns)
|
|
516
|
+
return {k: {**v, "seconds": round(v["seconds"], 1)} for k,
|
|
517
|
+
v in sorted(by_cause.items(), key=lambda kv: -kv[1]["runs"])}
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _md_table(rows, cols):
|
|
521
|
+
head = "| " + " | ".join(c for c, _ in cols) + " |\n|" + "---|" * len(cols) + "\n"
|
|
522
|
+
return head + "".join("| " + " | ".join(fmt(r) for _, fmt in cols) + " |\n" for r in rows)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def report(runs, groups=None, labels=None, title="L0 trace analytics"):
|
|
526
|
+
table, totals = mechanism_table(runs)
|
|
527
|
+
sinks = time_sinks(runs, groups)
|
|
528
|
+
divs = divergences(runs)
|
|
529
|
+
kinds = collections.Counter(d["kind"] for d in divs)
|
|
530
|
+
positions = [d["position"] for d in divs if d["position"] is not None]
|
|
531
|
+
judged = [r for r in runs if r.judged]
|
|
532
|
+
best = shortest(runs)
|
|
533
|
+
ex = [excess(r, best) for r in runs if r.passed]
|
|
534
|
+
ex = [e for e in ex if e]
|
|
535
|
+
lines = [f"# {title}", "",
|
|
536
|
+
f"Runs: {len(runs)} ({len(judged)} judged, {sum(bool(r.passed) for r in judged)} passing); "
|
|
537
|
+
f"timed: {totals['runs']} runs, {totals['turns']} turns, {totals['seconds']:.0f} s "
|
|
538
|
+
"of model and device time.",
|
|
539
|
+
"",
|
|
540
|
+
"## Mechanisms ranked by seconds", "",
|
|
541
|
+
"Each row is a ceiling on what removing that one mechanism could save; rows overlap.", ""]
|
|
542
|
+
lines.append(_md_table(table, [
|
|
543
|
+
("mechanism", lambda r: r["mechanism"]), ("kind", lambda r: r["kind"]), ("runs", lambda r: str(r["runs"])),
|
|
544
|
+
("occurrences", lambda r: str(r["occurrences"])), ("turns", lambda r: str(r["turns"])),
|
|
545
|
+
("seconds", lambda r: f"{r['seconds']:.0f}"), ("s/run", lambda r: f"{r['s_per_run']:.1f}"),
|
|
546
|
+
("% time", lambda r: f"{100 * (r['share_time'] or 0):.1f}"),
|
|
547
|
+
("% turns", lambda r: f"{100 * (r['share_turns'] or 0):.1f}"), ("feeds", lambda r: r["feeds"])]))
|
|
548
|
+
lines += ["", "## Where the seconds go (no overlap)", ""]
|
|
549
|
+
lines.append(_md_table(partition([r for r in runs if r.timed]), [
|
|
550
|
+
("bucket", lambda r: r["bucket"]), ("seconds", lambda r: f"{r['seconds']:.0f}"),
|
|
551
|
+
("share", lambda r: f"{100 * r['share']:.1f}%")]))
|
|
552
|
+
lines += ["", "## Step inefficiency (passing runs against the fewest turns of any passing run of the task)", ""]
|
|
553
|
+
if ex:
|
|
554
|
+
lines.append(f"Passing runs: {len(ex)}; at the shortest: {sum(e['turns'] <= 0 for e in ex)}; "
|
|
555
|
+
f"excess turns mean {statistics.mean(max(e['turns'], 0) for e in ex):.1f}, "
|
|
556
|
+
f"p50 {statistics.median(max(e['turns'], 0) for e in ex):.0f}; "
|
|
557
|
+
f"excess seconds {sum(e['seconds'] for e in ex):.0f} s in all.")
|
|
558
|
+
lines += ["", "## Time sinks by step type", ""]
|
|
559
|
+
cols = [("", lambda kv: kv[0]), ("turns", lambda kv: str(kv[1]["turns"])),
|
|
560
|
+
("total s", lambda kv: f"{kv[1]['total_s']:.0f}"),
|
|
561
|
+
("share", lambda kv: f"{100 * kv[1]['share']:.0f}%"), ("s/turn", lambda kv: str(kv[1]["s_per_turn"])),
|
|
562
|
+
("model s/turn", lambda kv: str(kv[1]["model_s_per_turn"])),
|
|
563
|
+
("ttft s", lambda kv: str(kv[1]["ttft_s_per_turn"])), ("gen s", lambda kv: str(kv[1]["gen_s_per_turn"])),
|
|
564
|
+
("out tok", lambda kv: str(kv[1]["out_tokens_per_turn"])),
|
|
565
|
+
("exec s/act", lambda kv: str(kv[1]["exec_s_per_action"])),
|
|
566
|
+
("settle s/act", lambda kv: str(kv[1]["settle_s_per_action"])),
|
|
567
|
+
("refresh s/act", lambda kv: str(kv[1]["refresh_s_per_action"]))]
|
|
568
|
+
lines.append(_md_table(list(sinks["by_step_type"].items()), cols))
|
|
569
|
+
lines += ["", "## Time sinks by app group", ""]
|
|
570
|
+
lines.append(_md_table(list(sinks["by_app_group"].items()), cols))
|
|
571
|
+
lines += ["", f"## First divergence of failing runs ({len(divs)} clean failing runs with a passing run to compare)",
|
|
572
|
+
""]
|
|
573
|
+
if divs:
|
|
574
|
+
lines.append(_md_table(kinds.most_common(), [("kind", lambda kv: kv[0]), ("runs", lambda kv: str(kv[1])),
|
|
575
|
+
("share", lambda kv: f"{100 * kv[1] / len(divs):.0f}%")]))
|
|
576
|
+
lines.append(f"\nWhere in the run (share of its actions done before leaving the path): "
|
|
577
|
+
f"p25 {statistics.quantiles(positions, n=4)[0]:.2f}, median {statistics.median(positions):.2f}, "
|
|
578
|
+
f"p75 {statistics.quantiles(positions, n=4)[2]:.2f}." if len(positions) >= 4 else "")
|
|
579
|
+
ttft = ttft_table(runs)
|
|
580
|
+
if ttft:
|
|
581
|
+
lines += ["", "## Time to first token", "",
|
|
582
|
+
f"{ttft['turns']} turns with streamed timing; {ttft['cache_missed_turns']} missed the cache; "
|
|
583
|
+
f"cached share of input {100 * ttft['cached_share_mean']:.0f}%. TTFT p50 {ttft['ttft_p50_ms']:.0f} "
|
|
584
|
+
f"ms, p90 {ttft['ttft_p90_ms']:.0f} ms, mean {ttft['ttft_mean_ms']} ms. Fit on uncached tokens: "
|
|
585
|
+
f"{ttft['fit_intercept_ms']} ms + {ttft['fit_ms_per_1k_uncached']} ms per 1k (r {ttft['fit_r']}); on "
|
|
586
|
+
f"turns without hidden thinking {ttft['fit_no_thinking_ms_per_1k']} ms per 1k (r "
|
|
587
|
+
f"{ttft['fit_no_thinking_r']}).", ""]
|
|
588
|
+
lines.append(_md_table(ttft["by_hidden_thinking"], [
|
|
589
|
+
("hidden thinking tokens (est.)", lambda b: f"{b['hidden_tokens'][0]}..{b['hidden_tokens'][1]}"),
|
|
590
|
+
("turns", lambda b: str(b["turns"])), ("TTFT p50 ms", lambda b: str(b["ttft_p50_ms"])),
|
|
591
|
+
("TTFT mean ms", lambda b: str(b["ttft_mean_ms"]))]))
|
|
592
|
+
if labels:
|
|
593
|
+
lines += ["", "## Failing runs by labelled cause", ""]
|
|
594
|
+
causes = outcome_table(runs, labels)
|
|
595
|
+
lines.append(_md_table(list(causes.items()),
|
|
596
|
+
[("cause", lambda kv: str(kv[0])), ("runs", lambda kv: str(kv[1]["runs"])),
|
|
597
|
+
("turns", lambda kv: str(kv[1]["turns"])),
|
|
598
|
+
("seconds", lambda kv: f"{kv[1]['seconds']:.0f}")]))
|
|
599
|
+
return "\n".join(lines) + "\n", {"mechanisms": table, "totals": totals, "time_sinks": sinks,
|
|
600
|
+
"partition": partition([r for r in runs if r.timed]), "ttft": ttft,
|
|
601
|
+
"divergences": divs, "divergence_kinds": dict(kinds),
|
|
602
|
+
"excess": {"passing_runs": len(ex),
|
|
603
|
+
"at_shortest": sum(e["turns"] <= 0 for e in ex),
|
|
604
|
+
"excess_seconds": round(sum(e["seconds"] for e in ex), 1)}}
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def select(runs, batches=None, suites=None, clean=None, models=None):
|
|
608
|
+
out = []
|
|
609
|
+
for r in runs:
|
|
610
|
+
if batches and r.batch not in batches:
|
|
611
|
+
continue
|
|
612
|
+
if suites and r.suite not in suites:
|
|
613
|
+
continue
|
|
614
|
+
if clean is not None and r.clean != clean:
|
|
615
|
+
continue
|
|
616
|
+
if models and r.model not in models:
|
|
617
|
+
continue
|
|
618
|
+
out.append(r)
|
|
619
|
+
return out
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def main(argv=None):
|
|
623
|
+
parser = argparse.ArgumentParser(prog="python -m mobile_agent.bench.evals.l0", description=__doc__.split("\n\n")[0])
|
|
624
|
+
parser.add_argument("--runs", nargs="+", required=True, help="batch folders, or folders holding them")
|
|
625
|
+
parser.add_argument("--batches", help="comma-separated batch names to keep (default: all)")
|
|
626
|
+
parser.add_argument("--suite", choices=("iosworld", "mobsterbench"), help="keep one suite")
|
|
627
|
+
parser.add_argument("--app-groups", help="JSON {app name: group} for the by-app table")
|
|
628
|
+
parser.add_argument("--labels", help="failure labels JSONL (the tuning loop's labeller) for the cause table")
|
|
629
|
+
parser.add_argument("--title", default="L0 trace analytics")
|
|
630
|
+
parser.add_argument("--out", required=True, help="folder for l0.md and l0.json")
|
|
631
|
+
args = parser.parse_args(argv)
|
|
632
|
+
runs = T.discover(args.runs)
|
|
633
|
+
runs = select(runs, batches=set(args.batches.split(",")) if args.batches else None,
|
|
634
|
+
suites={args.suite} if args.suite else None)
|
|
635
|
+
groups = json.loads(pathlib.Path(args.app_groups).read_text()) if args.app_groups else None
|
|
636
|
+
labels = T.read_jsonl(args.labels) if args.labels else None
|
|
637
|
+
text, data = report(runs, groups, labels, args.title)
|
|
638
|
+
out = pathlib.Path(args.out)
|
|
639
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
640
|
+
(out / "l0.md").write_text(text)
|
|
641
|
+
(out / "l0.json").write_text(json.dumps(data, indent=1, ensure_ascii=False))
|
|
642
|
+
print(text)
|
|
643
|
+
return 0
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
if __name__ == "__main__":
|
|
647
|
+
raise SystemExit(main())
|