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
mobile_agent/run.py
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
"""A task's record: its event log, its approval handshake and what it says outside. It knows no phone and no HTTP."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
|
|
10
|
+
from . import failure_line, harness_api, orchestrate, result_blocks, secret_filter, traces
|
|
11
|
+
from .api_errors import APIError
|
|
12
|
+
from .costs import NANODOLLARS
|
|
13
|
+
from .explain import explain
|
|
14
|
+
from .journal import JournalError
|
|
15
|
+
from .run_executor import CancelToken
|
|
16
|
+
from .steering import SteeringQueue
|
|
17
|
+
|
|
18
|
+
APPROVAL_TIMEOUT_SECONDS = 600
|
|
19
|
+
INSTRUCTION_MAX_CHARS = 500
|
|
20
|
+
ANSWER_MAX_CHARS = 500
|
|
21
|
+
STEER_MAX_CHARS = 2000
|
|
22
|
+
APPROVAL_PENDING = "Choose Send or Don't send above, or say what to do instead there."
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
QUIT_REASON = "Mobster quit while this task was running. Run it again to finish."
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def quit_summary(summary):
|
|
29
|
+
out = {**summary, "status": "interrupted", "reason": QUIT_REASON, "interrupted_by": "quit"}
|
|
30
|
+
if out.get("outcome") == "stopped":
|
|
31
|
+
out["outcome"] = "couldnt_finish"
|
|
32
|
+
return out
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def preapproved_matches(spec, request):
|
|
36
|
+
if isinstance(spec, dict) and spec.get("kind") == "queue_post":
|
|
37
|
+
from .content_queue.runner import preapproval_matches
|
|
38
|
+
return preapproval_matches(spec, request)
|
|
39
|
+
return result_blocks.preapproval_matches(spec, request)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
PHONE_WAIT_KINDS = frozenset({"lock_screen", "passcode_keypad", "unplugged", "wifi_lost"})
|
|
43
|
+
PHONE_WAIT_SCAN = 400
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def phone_wait(events):
|
|
47
|
+
for event in reversed(events[-PHONE_WAIT_SCAN:]):
|
|
48
|
+
name = event.get("event")
|
|
49
|
+
if name == "handoff_finished":
|
|
50
|
+
return None
|
|
51
|
+
if name != "handoff_waiting":
|
|
52
|
+
continue
|
|
53
|
+
if event.get("kind") not in PHONE_WAIT_KINDS:
|
|
54
|
+
return None
|
|
55
|
+
seconds = event.get("seconds") if isinstance(event.get("seconds"), int) and event["seconds"] > 0 else 120
|
|
56
|
+
return {"kind": event["kind"], "seconds": seconds, "since": event.get("timestamp"),
|
|
57
|
+
"midrun": event.get("midrun") is True}
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CHOICE_DETAIL = " \u2014 "
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def clarify_choice(choice):
|
|
65
|
+
# The detail after an em dash tells two people with one first name apart (CR-25).
|
|
66
|
+
label, _, after = str(choice.get("label") or choice["id"]).partition(CHOICE_DETAIL)
|
|
67
|
+
detail = " ".join(str(choice.get("detail") or after).split())[:120]
|
|
68
|
+
return {"id": str(choice["id"])[:40], "label": " ".join(label.split())[:120] or str(choice["id"])[:120],
|
|
69
|
+
**({"detail": detail} if detail else {})}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Run:
|
|
74
|
+
app: dict
|
|
75
|
+
goal: str
|
|
76
|
+
mode: str
|
|
77
|
+
id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
78
|
+
status: str = "queued"
|
|
79
|
+
created_at: float = field(default_factory=time.time)
|
|
80
|
+
finished_at: float | None = None
|
|
81
|
+
events: list = field(default_factory=list)
|
|
82
|
+
summary: dict | None = None
|
|
83
|
+
image: str | None = None
|
|
84
|
+
image_at: float = 0
|
|
85
|
+
image_lock: threading.Lock = field(default_factory=threading.Lock)
|
|
86
|
+
capture: object = None
|
|
87
|
+
image_captured_at: float | None = None
|
|
88
|
+
idempotency_key: str | None = None
|
|
89
|
+
output_schema: dict | None = None
|
|
90
|
+
output_format: str = "auto"
|
|
91
|
+
allowed_bundles: frozenset | None = None
|
|
92
|
+
journal: object = None
|
|
93
|
+
lease: object = None
|
|
94
|
+
device: object = None
|
|
95
|
+
guard: object = None
|
|
96
|
+
device_id: str | None = None
|
|
97
|
+
device_name: str | None = None
|
|
98
|
+
phone: object = None
|
|
99
|
+
group_id: str | None = None
|
|
100
|
+
# The phone's own rules, kept with the run: a later change never reaches a running task.
|
|
101
|
+
phone_apps: frozenset | None = None
|
|
102
|
+
ask_override: bool | None = None
|
|
103
|
+
stop: CancelToken = field(default_factory=CancelToken) # a token, so RunExecutor(run.stop) stops with it
|
|
104
|
+
condition: threading.Condition = field(default_factory=threading.Condition)
|
|
105
|
+
# In memory only: it can carry the exact text to be sent, which is never written to the journal.
|
|
106
|
+
approval: dict | None = None
|
|
107
|
+
approval_answer: str | None = None
|
|
108
|
+
approval_deadline: float | None = None
|
|
109
|
+
answered: dict = field(default_factory=dict)
|
|
110
|
+
engine: str | None = None
|
|
111
|
+
cost_nanodollars: int = 0
|
|
112
|
+
cost_known: bool = False
|
|
113
|
+
estimate_usd: float | None = None
|
|
114
|
+
cap_usd: float | None = None
|
|
115
|
+
cap_from_month: bool = False
|
|
116
|
+
cap_from_workflow: bool = False
|
|
117
|
+
reviewed_ok: bool = False
|
|
118
|
+
frames: object = None
|
|
119
|
+
extras: dict = field(default_factory=dict)
|
|
120
|
+
# Detached writing context; never part of public metadata, journal or repr.
|
|
121
|
+
account_context: object = field(default=None, repr=False)
|
|
122
|
+
workflow_id: str | None = field(default=None, repr=False)
|
|
123
|
+
phone_finished: bool = field(default=False, repr=False)
|
|
124
|
+
origin: str = "api"
|
|
125
|
+
# The hosted review demo (remote/review.py) takes no yes to an outside agent's approval: the sentence a yes is
|
|
126
|
+
# refused with. None everywhere else.
|
|
127
|
+
approvals_closed: str | None = None
|
|
128
|
+
steering: SteeringQueue = field(default_factory=SteeringQueue)
|
|
129
|
+
# The agent's masked notes and plan for listeners: in memory only, never journaled.
|
|
130
|
+
private: dict = field(default_factory=dict)
|
|
131
|
+
listeners: object = None
|
|
132
|
+
# A workflow loop waits for the listeners' on_finished, so round two finds what round one taught.
|
|
133
|
+
settled: threading.Event = field(default_factory=threading.Event)
|
|
134
|
+
longform: dict | None = None
|
|
135
|
+
start_on_home: bool = False
|
|
136
|
+
remember_spec: dict | None = None
|
|
137
|
+
on_remember: object = None
|
|
138
|
+
pinned_apps: list | None = None
|
|
139
|
+
plan: dict | None = None
|
|
140
|
+
plan_terms: dict = field(default_factory=dict)
|
|
141
|
+
subrun_open: str | None = None
|
|
142
|
+
subrun_said: dict = field(default_factory=dict)
|
|
143
|
+
subruns_seen: set = field(default_factory=set)
|
|
144
|
+
quitting: bool = False
|
|
145
|
+
# In memory only; a send that doesn't match one exactly asks as usual.
|
|
146
|
+
preapproved: list = field(default_factory=list)
|
|
147
|
+
# Journaled, so an approved item is never approved, and so never sent, twice.
|
|
148
|
+
block_state: dict = field(default_factory=dict)
|
|
149
|
+
# Never the page itself, never a token.
|
|
150
|
+
shares: list = field(default_factory=list)
|
|
151
|
+
# Issues filed from the result, noted before each is sent: "filing" or "unknown" is never sent again.
|
|
152
|
+
issues: list = field(default_factory=list)
|
|
153
|
+
# The MCP client's own name for itself: a label only, never an identity.
|
|
154
|
+
client: str | None = None
|
|
155
|
+
started_at: float | None = None
|
|
156
|
+
taking_back: bool = False
|
|
157
|
+
queue_info: dict | None = None
|
|
158
|
+
|
|
159
|
+
def request_approval(self, request, timeout=None, pending_out=None):
|
|
160
|
+
# approved, denied, timeout, stopped, decided, choice:<id>, redirected:<text> or answer:<text>.
|
|
161
|
+
timeout = APPROVAL_TIMEOUT_SECONDS if timeout is None else timeout
|
|
162
|
+
now = time.time()
|
|
163
|
+
pending = {"id": uuid.uuid4().hex[:12], "operation": request["operation"], "label": request["label"],
|
|
164
|
+
"role": request.get("role") or "", "text": request.get("text"),
|
|
165
|
+
"app": str(request.get("app_name") or self.app["name"])[:80],
|
|
166
|
+
"requestedAt": now * 1000, "expiresAt": (now + timeout) * 1000,
|
|
167
|
+
"actingAs": str(request["acting_as"])[:120] if request.get("acting_as") else None,
|
|
168
|
+
"recipient": str(request["recipient"])[:120] if request.get("recipient") else None}
|
|
169
|
+
kind = request.get("kind") or "action"
|
|
170
|
+
if kind == "commit":
|
|
171
|
+
with self.condition:
|
|
172
|
+
spec = next((one for one in self.preapproved if preapproved_matches(one, request)), None)
|
|
173
|
+
if spec is not None and not self.stop.is_set() and not self.approvals_closed:
|
|
174
|
+
spec["used"] = True
|
|
175
|
+
else:
|
|
176
|
+
spec = None
|
|
177
|
+
if spec is not None:
|
|
178
|
+
# Approved on its review queue item: exactly these words, to this person, once.
|
|
179
|
+
self.emit({"event": "approval_requested", "approval_id": pending["id"], "step": request.get("step"),
|
|
180
|
+
"operation": pending["operation"], "label": pending["label"],
|
|
181
|
+
"text_present": pending["text"] is not None, "kind": kind, "title": request.get("title"),
|
|
182
|
+
"act": request.get("act"), "reason": None, "target": None, "choices": [],
|
|
183
|
+
"preapproved": spec.get("via") or "review_queue"})
|
|
184
|
+
with self.condition:
|
|
185
|
+
self._remember(pending["id"], "approved")
|
|
186
|
+
self.emit({"event": "approval_resolved", "approval_id": pending["id"], "decision": "approved",
|
|
187
|
+
"via": spec.get("via") or "review_queue", "source": dict(spec.get("source") or {})})
|
|
188
|
+
return "approved"
|
|
189
|
+
target = request.get("target")
|
|
190
|
+
pending.update(kind=kind, title=str(request["title"])[:160] if request.get("title") else None,
|
|
191
|
+
act=str(request["act"])[:40] if request.get("act") else None,
|
|
192
|
+
reason=" ".join(str(request["reason"]).split())[:120] if request.get("reason") else None,
|
|
193
|
+
target=({key: float(target[key]) for key in ("x", "y", "w", "h")}
|
|
194
|
+
if isinstance(target, dict) and all(isinstance(target.get(k), (int, float))
|
|
195
|
+
for k in ("x", "y", "w", "h")) else None))
|
|
196
|
+
crop = request.get("crop")
|
|
197
|
+
if isinstance(crop, (bytes, bytearray)) and crop and self.frames is not None:
|
|
198
|
+
frame_id = self.frames.put(self.id, crop)
|
|
199
|
+
pending["crop"] = f"/api/runs/{self.id}/frames/{frame_id}" if frame_id else None
|
|
200
|
+
if isinstance(request.get("apps"), list):
|
|
201
|
+
named = [(name, bundle) for name, bundle in itertools.zip_longest(
|
|
202
|
+
request["apps"], request.get("bundles") if isinstance(request.get("bundles"), list) else ())
|
|
203
|
+
if isinstance(name, str) and name]
|
|
204
|
+
pending["apps"] = [name for name, _ in named]
|
|
205
|
+
if isinstance(request.get("bundles"), list):
|
|
206
|
+
pending["appIds"] = [bundle[:200] if isinstance(bundle, str) else None for _, bundle in named]
|
|
207
|
+
if kind in ("loop", "question"):
|
|
208
|
+
choices = [{"id": str(c["id"])[:40], "label": str(c["label"])[:120]}
|
|
209
|
+
for c in request.get("choices") or () if isinstance(c, dict) and c.get("id")][:6]
|
|
210
|
+
default = request.get("default_choice")
|
|
211
|
+
pending.update(kind=kind, question=str(request.get("question") or "")[:300], choices=choices,
|
|
212
|
+
defaultChoice=default if any(c["id"] == default for c in choices) else None,
|
|
213
|
+
limits=request.get("limits"))
|
|
214
|
+
elif kind == "clarify":
|
|
215
|
+
choices = [clarify_choice(c) for c in request.get("choices") or () if isinstance(c, dict)
|
|
216
|
+
and c.get("id")][:6]
|
|
217
|
+
pending.update(label=str(request["label"])[:80],
|
|
218
|
+
question=str(request.get("question") or request["label"])[:300], choices=choices,
|
|
219
|
+
allowText=bool(request.get("allow_text", not choices)))
|
|
220
|
+
elif kind == "practice":
|
|
221
|
+
known = {"right": "That’s right", "skip": "Skip this step", "model": "Let Mobster do it"}
|
|
222
|
+
choices = [{"id": c["id"], "label": known[c["id"]]} for c in request.get("choices") or ()
|
|
223
|
+
if isinstance(c, dict) and c.get("id") in known][:3]
|
|
224
|
+
practice = request.get("practice") if isinstance(request.get("practice"), dict) else {}
|
|
225
|
+
pending.update(label=str(request["label"])[:80], question=str(request.get("question") or "")[:300],
|
|
226
|
+
choices=choices, defaultChoice="right" if any(c["id"] == "right" for c in choices) else None,
|
|
227
|
+
allowTap=True, practice={"step": int(practice.get("step") or 0),
|
|
228
|
+
"of": int(practice.get("of") or 0),
|
|
229
|
+
"purpose": str(practice.get("purpose") or "")[:160],
|
|
230
|
+
"differs": bool(practice.get("differs"))})
|
|
231
|
+
elif kind == "plan":
|
|
232
|
+
shaped = orchestrate.plan(request.get("plan"))
|
|
233
|
+
pending.update(operation="PLAN", label=shaped["summary"][:80], title=shaped["summary"][:160], text=None,
|
|
234
|
+
plan=shaped, limits=" ".join(shaped["policy"]) or None)
|
|
235
|
+
if shaped.get("long"):
|
|
236
|
+
pending.update(choices=orchestrate.long_choices(shaped["long"]), defaultChoice="default")
|
|
237
|
+
with self.condition:
|
|
238
|
+
self.plan_terms = {}
|
|
239
|
+
elif kind == "proposals":
|
|
240
|
+
items = orchestrate.proposals(request.get("items"))
|
|
241
|
+
count = len(items)
|
|
242
|
+
title = str(request.get("title") or "").strip()[:120] or (
|
|
243
|
+
items[0]["title"] if count == 1 else f"Review {count} drafts")
|
|
244
|
+
pending.update(operation="PROPOSE", label=title[:80], title=title, text=None,
|
|
245
|
+
act=items[0]["act"] if len({item["act"] for item in items}) == 1 else None,
|
|
246
|
+
app=items[0]["app"] if len({item["app"] for item in items}) == 1 else "Several apps",
|
|
247
|
+
items=items)
|
|
248
|
+
pending["explain"] = explain(request)
|
|
249
|
+
spec = request.get("remember") if isinstance(request.get("remember"), dict) else None
|
|
250
|
+
pending["rememberable"] = spec is not None
|
|
251
|
+
pending["rememberLabel"] = str(spec["words"])[:80] if spec and spec.get("words") else None
|
|
252
|
+
if isinstance(pending_out, dict):
|
|
253
|
+
pending_out.clear()
|
|
254
|
+
pending_out.update(pending)
|
|
255
|
+
deadline = time.monotonic() + timeout
|
|
256
|
+
with self.condition:
|
|
257
|
+
self.approval, self.approval_answer, self.remember_spec = pending, None, spec
|
|
258
|
+
self.approval_deadline = deadline
|
|
259
|
+
self.emit({"event": "approval_requested", "approval_id": pending["id"], "step": request.get("step"),
|
|
260
|
+
"operation": pending["operation"], "label": pending["label"],
|
|
261
|
+
"text_present": pending["text"] is not None, "kind": kind,
|
|
262
|
+
"title": pending["title"], "act": pending["act"], "reason": pending["reason"],
|
|
263
|
+
"target": pending["target"], "explain": explain({**request, "recipient": None}),
|
|
264
|
+
**({"crop": pending["crop"]} if pending.get("crop") else {}),
|
|
265
|
+
"choices": [c["id"] for c in pending.get("choices", ())],
|
|
266
|
+
**({"acting_as": pending["actingAs"]} if pending["actingAs"] else {}),
|
|
267
|
+
**({"plan": pending["plan"]} if kind == "plan" else {}),
|
|
268
|
+
**({"items": [orchestrate.public_proposal(item) for item in pending["items"]],
|
|
269
|
+
"count": len(pending["items"])} if kind == "proposals" else {})})
|
|
270
|
+
with self.condition:
|
|
271
|
+
while self.approval_answer is None and not self.stop.is_set():
|
|
272
|
+
# The monotonic clock stands still while the Mac sleeps: the wall clock the card counts down to wins.
|
|
273
|
+
remaining = min(deadline - time.monotonic(), now + timeout - time.time())
|
|
274
|
+
if remaining <= 0:
|
|
275
|
+
break
|
|
276
|
+
self.condition.wait(timeout=min(remaining, 1))
|
|
277
|
+
# Stop wins over an answer it raced with: a stopped task never acts, whichever reached it first.
|
|
278
|
+
answer = "stopped" if self.stop.is_set() else self.approval_answer or "timeout"
|
|
279
|
+
self.approval, self.approval_answer = None, None
|
|
280
|
+
self.approval_deadline = None
|
|
281
|
+
tally = {}
|
|
282
|
+
if kind == "proposals":
|
|
283
|
+
# Fail closed: nothing nobody chose goes out, and after Stop nothing does.
|
|
284
|
+
for item in pending["items"]:
|
|
285
|
+
if item["decision"] is None or answer == "stopped":
|
|
286
|
+
item["decision"] = "skip"
|
|
287
|
+
tally = {"sent": sum(item["decision"] == "send" for item in pending["items"]),
|
|
288
|
+
"skipped": sum(item["decision"] == "skip" for item in pending["items"])}
|
|
289
|
+
self._remember(pending["id"], "redirected" if answer.startswith("redirected:") else
|
|
290
|
+
"answered" if answer.startswith("answer:") else
|
|
291
|
+
"choice" if answer.startswith("choice:") else answer)
|
|
292
|
+
# A redirect's instruction and a typed answer go to the agent, never into the journal.
|
|
293
|
+
self.emit({"event": "approval_resolved", "approval_id": pending["id"],
|
|
294
|
+
"decision": "redirected" if answer.startswith("redirected:") else
|
|
295
|
+
"answered" if answer.startswith("answer:") else answer, **tally})
|
|
296
|
+
return answer
|
|
297
|
+
|
|
298
|
+
def _remember(self, approval_id, decision):
|
|
299
|
+
self.answered.pop(approval_id, None)
|
|
300
|
+
self.answered[approval_id] = decision
|
|
301
|
+
while len(self.answered) > 20:
|
|
302
|
+
self.answered.pop(next(iter(self.answered)))
|
|
303
|
+
|
|
304
|
+
def approval_decision(self, approval_id):
|
|
305
|
+
with self.condition:
|
|
306
|
+
if self.approval is not None and self.approval.get("id") == approval_id:
|
|
307
|
+
if self.stop.is_set():
|
|
308
|
+
return "stopped"
|
|
309
|
+
answer = self.approval_answer or ""
|
|
310
|
+
return ("redirected" if answer.startswith("redirected:") else
|
|
311
|
+
"answered" if answer.startswith("answer:") else
|
|
312
|
+
"choice" if answer.startswith("choice:") else answer or None)
|
|
313
|
+
return self.answered.get(approval_id)
|
|
314
|
+
|
|
315
|
+
def _approval_waiting(self, approval_id):
|
|
316
|
+
if (self.stop.is_set() or self.approval is None or self.approval["id"] != approval_id
|
|
317
|
+
or self.approval_answer is not None):
|
|
318
|
+
return False
|
|
319
|
+
if (self.approval_deadline is None or time.monotonic() >= self.approval_deadline
|
|
320
|
+
or time.time() * 1000 >= self.approval["expiresAt"]):
|
|
321
|
+
self.approval_answer = "timeout"
|
|
322
|
+
self.condition.notify_all()
|
|
323
|
+
return False
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
def _refuse_yes(self, approval_id, approve):
|
|
327
|
+
"""A run that ``approvals_closed`` takes no yes (an approval, a choice, a send): the refusal is journaled and
|
|
328
|
+
raised, before anything is decided. A no, a redirect and any answer to a clarifying question go through."""
|
|
329
|
+
if not self.approvals_closed or approve is not True:
|
|
330
|
+
return
|
|
331
|
+
with self.condition:
|
|
332
|
+
pending = self.approval
|
|
333
|
+
if pending is None or pending["id"] != approval_id or pending.get("kind") == "clarify":
|
|
334
|
+
return
|
|
335
|
+
self.emit({"event": "approval_refused", "approval_id": approval_id, "reason": "approvals_closed"})
|
|
336
|
+
raise ValueError(self.approvals_closed)
|
|
337
|
+
|
|
338
|
+
def answer_approval(self, approval_id, approve, choice=None, instruction=None, answer=None, remember=False,
|
|
339
|
+
allow_cost=False):
|
|
340
|
+
"""Answer the pending request. ``instruction`` with approve false is a redirect: the task goes on
|
|
341
|
+
with it ("Decline and say what to do instead"), and request_approval returns "redirected:<text>".
|
|
342
|
+
``answer`` (a clarifying question only, with approve true): the user's typed answer, 1–500 characters;
|
|
343
|
+
request_approval returns "answer:<text>"."""
|
|
344
|
+
self._refuse_yes(approval_id, approve)
|
|
345
|
+
with self.condition:
|
|
346
|
+
if not self._approval_waiting(approval_id):
|
|
347
|
+
return False
|
|
348
|
+
proposals = self.approval is not None and self.approval.get("kind") == "proposals"
|
|
349
|
+
if allow_cost:
|
|
350
|
+
if (self.approval is None or self.approval.get("kind") != "plan" or not approve
|
|
351
|
+
or not (self.approval.get("plan") or {}).get("long")):
|
|
352
|
+
raise ValueError("allowCost goes with Start on a long task's plan")
|
|
353
|
+
if proposals:
|
|
354
|
+
if choice is not None or instruction is not None or answer is not None:
|
|
355
|
+
raise ValueError("Proposals take Send or Skip, for some items or for all")
|
|
356
|
+
return self.decide_proposals(approval_id, approve)
|
|
357
|
+
with self.condition:
|
|
358
|
+
# After Stop nothing is answered, though the card shows until the agent's thread wakes.
|
|
359
|
+
if not self._approval_waiting(approval_id):
|
|
360
|
+
return False
|
|
361
|
+
if answer is not None and self.approval.get("kind") == "practice":
|
|
362
|
+
# Where the person tapped on the live view: never sent to the phone by itself.
|
|
363
|
+
found = re.fullmatch(r"\s*(0(?:\.\d+)?|1(?:\.0+)?|\.\d+)\s*,\s*(0(?:\.\d+)?|1(?:\.0+)?|\.\d+)\s*",
|
|
364
|
+
str(answer))
|
|
365
|
+
if not approve or choice is not None or instruction is not None or not found:
|
|
366
|
+
raise ValueError("A practice answer is a tap: x,y in screen fractions from 0 to 1, "
|
|
367
|
+
"with approve true")
|
|
368
|
+
self.approval_answer = f"answer:{float(found.group(1)):.4f},{float(found.group(2)):.4f}"
|
|
369
|
+
elif answer is not None:
|
|
370
|
+
text = " ".join(str(answer).split())
|
|
371
|
+
if self.approval.get("kind") != "clarify":
|
|
372
|
+
raise ValueError("Only a question from Mobster's agent takes a typed answer")
|
|
373
|
+
if not approve or choice is not None or instruction is not None \
|
|
374
|
+
or not 1 <= len(text) <= ANSWER_MAX_CHARS:
|
|
375
|
+
raise ValueError("An answer says in 1–500 characters what Mobster's agent asked, with approve true")
|
|
376
|
+
self.approval_answer = f"answer:{text}"
|
|
377
|
+
elif instruction is not None:
|
|
378
|
+
text = " ".join(str(instruction).split())
|
|
379
|
+
if approve or choice is not None or not 1 <= len(text) <= INSTRUCTION_MAX_CHARS:
|
|
380
|
+
raise ValueError("A redirect declines the action and says in 1–500 characters what to do instead")
|
|
381
|
+
self.approval_answer = f"redirected:{text}"
|
|
382
|
+
elif choice is not None:
|
|
383
|
+
if not approve or choice not in {c["id"] for c in self.approval.get("choices", ())}:
|
|
384
|
+
raise ValueError("That choice is not one of this question's answers")
|
|
385
|
+
self.approval_answer = f"choice:{choice}"
|
|
386
|
+
else:
|
|
387
|
+
self.approval_answer = "approved" if approve else "denied"
|
|
388
|
+
spec = self.remember_spec if approve and remember and self.approval.get("rememberable") else None
|
|
389
|
+
if spec is not None and callable(self.on_remember):
|
|
390
|
+
try:
|
|
391
|
+
self.on_remember(spec)
|
|
392
|
+
except Exception:
|
|
393
|
+
pass # the answer stands; only the remembering failed
|
|
394
|
+
if allow_cost:
|
|
395
|
+
self.plan_terms = {"allowCost": True}
|
|
396
|
+
self.condition.notify_all()
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
def decide_proposals(self, approval_id, approve, items=None, text=None):
|
|
400
|
+
if type(approve) is not bool:
|
|
401
|
+
raise ValueError("approve is true or false")
|
|
402
|
+
self._refuse_yes(approval_id, approve)
|
|
403
|
+
if text is not None:
|
|
404
|
+
text = orchestrate._draft(text, "An edited draft")
|
|
405
|
+
if not text:
|
|
406
|
+
raise ValueError("An edited draft has text")
|
|
407
|
+
with self.condition:
|
|
408
|
+
if not self._approval_waiting(approval_id):
|
|
409
|
+
return False
|
|
410
|
+
if self.approval.get("kind") != "proposals":
|
|
411
|
+
raise ValueError("Only proposals are decided item by item")
|
|
412
|
+
pool = self.approval["items"]
|
|
413
|
+
if items is None:
|
|
414
|
+
chosen = [item for item in pool if item["decision"] is None]
|
|
415
|
+
else:
|
|
416
|
+
by_id = {item["id"]: item for item in pool}
|
|
417
|
+
if (not isinstance(items, list) or not items or len(set(map(str, items))) != len(items)
|
|
418
|
+
or not all(isinstance(key, str) and key in by_id for key in items)):
|
|
419
|
+
raise ValueError("items are ids of this batch's proposals, each once")
|
|
420
|
+
chosen = [by_id[key] for key in items]
|
|
421
|
+
if any(item["decision"] is not None for item in chosen):
|
|
422
|
+
return False
|
|
423
|
+
if not chosen:
|
|
424
|
+
return False
|
|
425
|
+
if text is not None and (not approve or len(chosen) != 1):
|
|
426
|
+
raise ValueError("An edited draft sends exactly one item")
|
|
427
|
+
for item in chosen:
|
|
428
|
+
item["decision"] = "send" if approve else "skip"
|
|
429
|
+
if text is not None and text != item["text"]:
|
|
430
|
+
item["text"], item["edited"] = text, True
|
|
431
|
+
decided = [(item["id"], item["decision"], item["edited"]) for item in chosen]
|
|
432
|
+
for item_id, decision, edited in decided:
|
|
433
|
+
self.emit({"event": "proposal_decided", "approval_id": approval_id, "item_id": item_id,
|
|
434
|
+
"decision": decision, "edited": edited})
|
|
435
|
+
with self.condition:
|
|
436
|
+
if (self.approval is not None and self.approval["id"] == approval_id and self.approval_answer is None
|
|
437
|
+
and all(item["decision"] for item in self.approval["items"])):
|
|
438
|
+
self.approval_answer = "decided"
|
|
439
|
+
self.condition.notify_all()
|
|
440
|
+
return True
|
|
441
|
+
|
|
442
|
+
def interpret(self, text, apps=(), read_only=True):
|
|
443
|
+
self.emit(orchestrate.interpretation(text, apps, read_only))
|
|
444
|
+
|
|
445
|
+
def propose_plan(self, plan, timeout=None):
|
|
446
|
+
shaped = orchestrate.plan(plan)
|
|
447
|
+
return self.request_approval({"kind": "plan", "operation": "PLAN", "label": shaped["summary"][:80],
|
|
448
|
+
"plan": shaped}, timeout=timeout)
|
|
449
|
+
|
|
450
|
+
def propose(self, items, title=None, timeout=None):
|
|
451
|
+
pending = {}
|
|
452
|
+
decision = self.request_approval({"kind": "proposals", "operation": "PROPOSE", "label": "Proposals",
|
|
453
|
+
"items": items, "title": title}, timeout=timeout, pending_out=pending)
|
|
454
|
+
return {"approvalId": pending["id"], "decision": decision,
|
|
455
|
+
"items": {item["id"]: {"decision": item["decision"], "text": item["text"], "edited": item["edited"]}
|
|
456
|
+
for item in pending["items"]}}
|
|
457
|
+
|
|
458
|
+
def proposal_result(self, approval_id, item_id, status, line=None):
|
|
459
|
+
if status not in orchestrate.PROPOSAL_STATUS:
|
|
460
|
+
raise ValueError("A proposal's result is sent, failed or skipped")
|
|
461
|
+
self.emit({"event": "proposal_result", "approval_id": str(approval_id)[:40], "item_id": str(item_id)[:40],
|
|
462
|
+
"status": status, "line": orchestrate._text(line, 200, "A result's line", required=False)})
|
|
463
|
+
|
|
464
|
+
def subruns_planned(self, subruns):
|
|
465
|
+
if not isinstance(subruns, (list, tuple)) or not 1 <= len(subruns) <= orchestrate.PLAN_APPS:
|
|
466
|
+
raise ValueError(f"Plan 1-{orchestrate.PLAN_APPS} sub-runs")
|
|
467
|
+
rows = []
|
|
468
|
+
for row in subruns:
|
|
469
|
+
if not isinstance(row, dict):
|
|
470
|
+
raise ValueError("A sub-run is a dict")
|
|
471
|
+
rows.append(orchestrate.subrun(row.get("app"), row.get("bundleId"), row.get("title"),
|
|
472
|
+
row.get("id") or uuid.uuid4().hex[:8]))
|
|
473
|
+
self.emit({"event": "subruns_planned", "subruns": rows})
|
|
474
|
+
return [row["subrun"] for row in rows]
|
|
475
|
+
|
|
476
|
+
def subrun_start(self, app, bundle_id=None, title=None, subrun_id=None, run_id=None):
|
|
477
|
+
row = orchestrate.subrun(app, bundle_id, title, subrun_id or uuid.uuid4().hex[:8], run_id)
|
|
478
|
+
with self.condition:
|
|
479
|
+
if self.subrun_open is not None:
|
|
480
|
+
raise ValueError("Finish the open sub-run first")
|
|
481
|
+
if row["subrun"] in self.subruns_seen and row["subrun"] != subrun_id:
|
|
482
|
+
raise ValueError("Each sub-run has its own id")
|
|
483
|
+
self.subrun_open = row["subrun"]
|
|
484
|
+
self.subruns_seen.add(row["subrun"])
|
|
485
|
+
self.emit({"event": "subrun_started", **row})
|
|
486
|
+
return row["subrun"]
|
|
487
|
+
|
|
488
|
+
def subrun_progress(self, subrun_id, line, counts=None):
|
|
489
|
+
shaped = {"line": orchestrate._text(line, 160, "A progress line"), "counts": orchestrate.counts(counts)}
|
|
490
|
+
now = time.monotonic()
|
|
491
|
+
with self.condition:
|
|
492
|
+
if subrun_id not in self.subruns_seen:
|
|
493
|
+
raise ValueError("No sub-run has that id")
|
|
494
|
+
if now - self.subrun_said.get(subrun_id, 0) < 0.5:
|
|
495
|
+
return False
|
|
496
|
+
self.subrun_said[subrun_id] = now
|
|
497
|
+
self.emit({"event": "subrun_progress", "subrun": subrun_id, **shaped})
|
|
498
|
+
return True
|
|
499
|
+
|
|
500
|
+
def subrun_finish(self, subrun_id, outcome="done", line=None, counts=None):
|
|
501
|
+
if outcome not in orchestrate.SUBRUN_OUTCOMES:
|
|
502
|
+
raise ValueError("A sub-run ends done, check, couldnt_finish, stopped or skipped")
|
|
503
|
+
shaped = {"line": orchestrate._text(line, 200, "A sub-run's line", required=False),
|
|
504
|
+
"counts": orchestrate.counts(counts)}
|
|
505
|
+
with self.condition:
|
|
506
|
+
if subrun_id not in self.subruns_seen:
|
|
507
|
+
raise ValueError("No sub-run has that id")
|
|
508
|
+
if self.subrun_open == subrun_id:
|
|
509
|
+
self.subrun_open = None
|
|
510
|
+
self.emit({"event": "subrun_finished", "subrun": subrun_id, "outcome": outcome, **shaped})
|
|
511
|
+
|
|
512
|
+
def emit(self, event):
|
|
513
|
+
with self.condition:
|
|
514
|
+
if "_frame" in event:
|
|
515
|
+
# Screens are stored beside the run and referenced by id; the caller's dict gets the id too.
|
|
516
|
+
data = event.pop("_frame")
|
|
517
|
+
event["frameId"] = self.frames.put(self.id, data) if self.frames is not None and data else None
|
|
518
|
+
# A call on the person's plan is no spend: History and Home show "On your Claude plan", not "0¢".
|
|
519
|
+
if (event.get("event") == "inference_finished" and type(event.get("cost_nanodollars")) is int
|
|
520
|
+
and event.get("billing") != "plan"):
|
|
521
|
+
self.cost_nanodollars += event["cost_nanodollars"]
|
|
522
|
+
self.cost_known = True
|
|
523
|
+
# The one gate for what a run stores and serves: secrets are masked in the journal and every reader.
|
|
524
|
+
entry = {**secret_filter.redact_deep(event), "seq": len(self.events), "timestamp": time.time() * 1000}
|
|
525
|
+
if self.subrun_open is not None and "subrun" not in entry:
|
|
526
|
+
entry["subrun"] = self.subrun_open
|
|
527
|
+
if self.journal:
|
|
528
|
+
self.journal.append(self.metadata(), entry)
|
|
529
|
+
self.events.append(entry)
|
|
530
|
+
self.condition.notify_all()
|
|
531
|
+
if self.listeners is not None:
|
|
532
|
+
# Posting under the lock keeps publication in saved sequence order when two threads emit.
|
|
533
|
+
self.listeners.post("on_event", self, entry)
|
|
534
|
+
|
|
535
|
+
def steer(self, text, source="app"):
|
|
536
|
+
if source not in ("app", "tui", "cli", "mcp", "voice", "remote"):
|
|
537
|
+
raise ValueError("Unknown message source")
|
|
538
|
+
allowed = harness_api.steer_sources(self.origin)
|
|
539
|
+
if source not in allowed:
|
|
540
|
+
raise APIError("This task takes messages only where it was started.", 403, "steer_not_allowed")
|
|
541
|
+
if not isinstance(text, str) or not 1 <= len(text.strip()) <= STEER_MAX_CHARS:
|
|
542
|
+
raise ValueError("A message is 1 to 2,000 characters")
|
|
543
|
+
with self.condition:
|
|
544
|
+
if self.finished_at is not None or self.steering.closed:
|
|
545
|
+
raise APIError("This task has finished.", 409, "run_not_active")
|
|
546
|
+
if self.approval is not None and self.approval.get("kind") != "clarify":
|
|
547
|
+
raise APIError(APPROVAL_PENDING, 409, "approval_pending", approvalId=self.approval["id"])
|
|
548
|
+
message = self.steering.put(text, source=source)
|
|
549
|
+
self.emit({"event": "user_message", "id": message.id, "text": message.text, "source": message.source})
|
|
550
|
+
return {"id": message.id, "text": message.text, "source": message.source, "at": message.at * 1000}
|
|
551
|
+
|
|
552
|
+
def finish(self, summary):
|
|
553
|
+
if self.finished_at is None:
|
|
554
|
+
unread = self.steering.close()
|
|
555
|
+
if unread:
|
|
556
|
+
try:
|
|
557
|
+
self.emit({"event": "steer_unread", "ids": [message.id for message in unread]})
|
|
558
|
+
except Exception:
|
|
559
|
+
pass # the terminal record below still goes in
|
|
560
|
+
with self.condition:
|
|
561
|
+
if self.finished_at is not None:
|
|
562
|
+
return
|
|
563
|
+
finished_at = time.time()
|
|
564
|
+
if summary.get("status") == "stopped" and self.quitting:
|
|
565
|
+
summary = quit_summary(summary)
|
|
566
|
+
if "deviceKind" not in summary:
|
|
567
|
+
kind = getattr(self.phone, "kind", None) or "usb"
|
|
568
|
+
summary = {**summary, "deviceKind": kind,
|
|
569
|
+
**({"deviceName": self.device_name} if self.device_name else {})}
|
|
570
|
+
if "blocks" in summary:
|
|
571
|
+
# The one gate for result blocks: bounded and redacted, else the block falls back to its text.
|
|
572
|
+
blocks, problems = result_blocks.validate(summary.get("blocks"))
|
|
573
|
+
summary = {**summary, "blocks": blocks}
|
|
574
|
+
if blocks is None:
|
|
575
|
+
summary.pop("blocks")
|
|
576
|
+
if problems:
|
|
577
|
+
summary["blocks_problems"] = [str(p)[:160] for p in problems[:8]]
|
|
578
|
+
status = summary["status"]
|
|
579
|
+
if status in {"queued", "running"}:
|
|
580
|
+
raise ValueError("A terminal run cannot remain active")
|
|
581
|
+
if traces.enabled() and "failure" not in summary:
|
|
582
|
+
failure = failure_line.failure_line(summary, self.events)
|
|
583
|
+
if failure is not None:
|
|
584
|
+
summary = {**summary, "failure": failure}
|
|
585
|
+
summary = secret_filter.redact_deep(summary)
|
|
586
|
+
status = summary["status"]
|
|
587
|
+
entry = {"event": "run_finished", "status": status, "summary": summary,
|
|
588
|
+
"seq": len(self.events), "timestamp": finished_at * 1000}
|
|
589
|
+
metadata = {**self.metadata(), "status": status, "summary": summary,
|
|
590
|
+
"finishedAt": finished_at * 1000}
|
|
591
|
+
try:
|
|
592
|
+
if self.journal:
|
|
593
|
+
self.journal.finish(metadata, entry)
|
|
594
|
+
except JournalError:
|
|
595
|
+
# Visible, never presented as durable; failed events were not appended, so seq ids stay contiguous.
|
|
596
|
+
summary = {**summary, "persistence_failed": True}
|
|
597
|
+
entry.update(summary=summary, persistence_failed=True)
|
|
598
|
+
self.status, self.summary, self.finished_at = status, summary, finished_at
|
|
599
|
+
self.events.append(entry)
|
|
600
|
+
self.condition.notify_all()
|
|
601
|
+
|
|
602
|
+
def metadata(self):
|
|
603
|
+
return {"id": self.id, "app": self.app, "appId": self.app["id"], "appName": self.app["name"],
|
|
604
|
+
"goal": self.goal, "mode": self.mode, "status": self.status,
|
|
605
|
+
"createdAt": self.created_at * 1000,
|
|
606
|
+
"finishedAt": self.finished_at * 1000 if self.finished_at is not None else None,
|
|
607
|
+
"summary": self.summary, "idempotencyKey": self.idempotency_key,
|
|
608
|
+
"outputSchema": self.output_schema, "outputFormat": self.output_format,
|
|
609
|
+
"allowedBundles": sorted(self.allowed_bundles) if self.allowed_bundles is not None else None,
|
|
610
|
+
"engine": self.engine, "costUsd": self.cost_usd,
|
|
611
|
+
"estimateUsd": self.estimate_usd, "reviewedOk": self.reviewed_ok,
|
|
612
|
+
"device": self.device_id, "deviceName": self.device_name,
|
|
613
|
+
"extras": harness_api.public_extras(self.extras), "origin": self.origin,
|
|
614
|
+
"pinnedApps": self.pinned_apps, **({"groupId": self.group_id} if self.group_id else {}),
|
|
615
|
+
**({"blockState": dict(self.block_state)} if self.block_state else {}),
|
|
616
|
+
**({"shares": [dict(item) for item in self.shares]} if self.shares else {}),
|
|
617
|
+
**({"issues": [dict(item) for item in self.issues]} if self.issues else {})}
|
|
618
|
+
|
|
619
|
+
@property
|
|
620
|
+
def steerable(self):
|
|
621
|
+
return self.engine == "smart" and self.mode == "live"
|
|
622
|
+
|
|
623
|
+
@property
|
|
624
|
+
def cost_usd(self):
|
|
625
|
+
return round(self.cost_nanodollars / NANODOLLARS, 6) if self.cost_known else None
|
|
626
|
+
|
|
627
|
+
def public(self, include_events=True):
|
|
628
|
+
with self.condition:
|
|
629
|
+
waiting = phone_wait(self.events) if self.finished_at is None else None
|
|
630
|
+
return {**{k: v for k, v in self.metadata().items() if k != "app"},
|
|
631
|
+
"approval": dict(self.approval) if self.approval else None,
|
|
632
|
+
**({"phoneWait": waiting} if waiting else {}),
|
|
633
|
+
**({"queue": dict(self.queue_info)} if self.queue_info else {}),
|
|
634
|
+
**({"takingBack": True} if self.taking_back and self.finished_at is None else {}),
|
|
635
|
+
"steerable": self.steerable,
|
|
636
|
+
"events": list(self.events) if include_events else [], "eventCount": len(self.events)}
|
|
637
|
+
|
|
638
|
+
def row(self):
|
|
639
|
+
value = self.public(include_events=False)
|
|
640
|
+
if isinstance(value["summary"], dict) and "timing" in value["summary"]:
|
|
641
|
+
value["summary"] = {k: v for k, v in value["summary"].items() if k != "timing"}
|
|
642
|
+
return value
|