android-midscene-automation 0.2.3 → 1.1.0
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.
- package/CHANGELOG.md +11 -1
- package/README.md +63 -1
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +10 -8
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +13 -1
- package/src/App.vue +72 -23
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +369 -186
- package/src/types.ts +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Artemis Diagnostics & System Readiness Package."""
|
|
16
|
+
|
|
17
|
+
from artemis.core.diagnostics.adb_keys import (
|
|
18
|
+
AdbKeyStatus,
|
|
19
|
+
get_adb_key_paths,
|
|
20
|
+
heal_adb_keys,
|
|
21
|
+
inspect_adb_keys,
|
|
22
|
+
)
|
|
23
|
+
from artemis.core.diagnostics.device_smoke import smoke_test_device
|
|
24
|
+
from artemis.core.diagnostics.emulator_manager import (
|
|
25
|
+
EmulatorLaunchStage,
|
|
26
|
+
EmulatorLaunchState,
|
|
27
|
+
emulator_manager,
|
|
28
|
+
)
|
|
29
|
+
from artemis.core.diagnostics.engine import ReadinessEngine, readiness_engine
|
|
30
|
+
from artemis.core.diagnostics.probes.adb_probe import AdbDeviceProbe
|
|
31
|
+
from artemis.core.diagnostics.probes.base import BaseProbe
|
|
32
|
+
from artemis.core.diagnostics.probes.credentials_probe import (
|
|
33
|
+
LLMCredentialsProbe,
|
|
34
|
+
VisionOCRProbe,
|
|
35
|
+
)
|
|
36
|
+
from artemis.core.diagnostics.probes.host_probe import IntegrationHostProbe
|
|
37
|
+
from artemis.core.diagnostics.probes.runtime_probe import (
|
|
38
|
+
PythonRuntimeProbe,
|
|
39
|
+
SystemConfigProbe,
|
|
40
|
+
)
|
|
41
|
+
from artemis.core.diagnostics.probes.toolchain_probe import ToolchainProbe
|
|
42
|
+
from artemis.core.diagnostics.readiness import (
|
|
43
|
+
CHECK_ORDER,
|
|
44
|
+
Verdict,
|
|
45
|
+
adb_keys_corrupted,
|
|
46
|
+
base_verdict,
|
|
47
|
+
collect_readiness,
|
|
48
|
+
sort_by_fix_order,
|
|
49
|
+
)
|
|
50
|
+
from artemis.core.diagnostics.schema import (
|
|
51
|
+
DeviceInfo,
|
|
52
|
+
ProbeAction,
|
|
53
|
+
ProbeCategory,
|
|
54
|
+
ProbeResult,
|
|
55
|
+
ProbeStatus,
|
|
56
|
+
SystemReadinessReport,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"ReadinessEngine",
|
|
61
|
+
"readiness_engine",
|
|
62
|
+
"emulator_manager",
|
|
63
|
+
"EmulatorLaunchStage",
|
|
64
|
+
"EmulatorLaunchState",
|
|
65
|
+
"BaseProbe",
|
|
66
|
+
"IntegrationHostProbe",
|
|
67
|
+
"PythonRuntimeProbe",
|
|
68
|
+
"SystemConfigProbe",
|
|
69
|
+
"AdbDeviceProbe",
|
|
70
|
+
"LLMCredentialsProbe",
|
|
71
|
+
"VisionOCRProbe",
|
|
72
|
+
"ToolchainProbe",
|
|
73
|
+
"DeviceInfo",
|
|
74
|
+
"ProbeAction",
|
|
75
|
+
"ProbeCategory",
|
|
76
|
+
"ProbeResult",
|
|
77
|
+
"ProbeStatus",
|
|
78
|
+
"SystemReadinessReport",
|
|
79
|
+
"AdbKeyStatus",
|
|
80
|
+
"get_adb_key_paths",
|
|
81
|
+
"inspect_adb_keys",
|
|
82
|
+
"heal_adb_keys",
|
|
83
|
+
"smoke_test_device",
|
|
84
|
+
"CHECK_ORDER",
|
|
85
|
+
"Verdict",
|
|
86
|
+
"collect_readiness",
|
|
87
|
+
"base_verdict",
|
|
88
|
+
"adb_keys_corrupted",
|
|
89
|
+
"sort_by_fix_order",
|
|
90
|
+
]
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""ADB Authentication Key Diagnostic and Automated Self-Healing Module.
|
|
16
|
+
|
|
17
|
+
Android 4.2.2+ requires RSA authentication keys (~/.android/adbkey) for USB debugging.
|
|
18
|
+
When key files become 0-bytes or corrupted (due to aborted writes, touch commands,
|
|
19
|
+
or disk sync issues), ADB fails to send the public key, causing connected devices
|
|
20
|
+
to silently remain in an 'unauthorized' state without ever displaying the authorization prompt.
|
|
21
|
+
|
|
22
|
+
This module provides detection and self-healing for corrupted ADB keys.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from dataclasses import asdict, dataclass
|
|
26
|
+
import os
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
import shutil
|
|
29
|
+
import subprocess
|
|
30
|
+
import time
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from artemis.toolchain import toolchain
|
|
34
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
35
|
+
|
|
36
|
+
logger = get_logger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class AdbKeyStatus:
|
|
41
|
+
"""Status details for ADB authentication keys."""
|
|
42
|
+
|
|
43
|
+
is_valid: bool
|
|
44
|
+
is_corrupted: bool
|
|
45
|
+
exists: bool
|
|
46
|
+
key_path: str
|
|
47
|
+
pub_key_path: str
|
|
48
|
+
key_size_bytes: int = 0
|
|
49
|
+
pub_key_size_bytes: int = 0
|
|
50
|
+
error_reason: str | None = None
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
return asdict(self)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_adb_key_paths() -> tuple[Path, Path]:
|
|
57
|
+
"""Resolve the paths to the active ADB private and public key files."""
|
|
58
|
+
adb_vendor_keys = os.environ.get("ADB_VENDOR_KEYS")
|
|
59
|
+
if adb_vendor_keys:
|
|
60
|
+
custom_priv = Path(adb_vendor_keys.split(os.pathsep)[0])
|
|
61
|
+
custom_pub = Path(f"{custom_priv}.pub")
|
|
62
|
+
return custom_priv, custom_pub
|
|
63
|
+
|
|
64
|
+
android_dir = Path.home() / ".android"
|
|
65
|
+
return android_dir / "adbkey", android_dir / "adbkey.pub"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def inspect_adb_keys() -> AdbKeyStatus:
|
|
69
|
+
"""Inspect ADB authentication RSA key files for corruption or invalid state."""
|
|
70
|
+
priv_path, pub_path = get_adb_key_paths()
|
|
71
|
+
|
|
72
|
+
if not priv_path.exists() and not pub_path.exists():
|
|
73
|
+
# Fresh environment: ADB will automatically generate keys on first start
|
|
74
|
+
return AdbKeyStatus(
|
|
75
|
+
is_valid=True,
|
|
76
|
+
is_corrupted=False,
|
|
77
|
+
exists=False,
|
|
78
|
+
key_path=str(priv_path),
|
|
79
|
+
pub_key_path=str(pub_path),
|
|
80
|
+
key_size_bytes=0,
|
|
81
|
+
pub_key_size_bytes=0,
|
|
82
|
+
error_reason=None,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
priv_size = priv_path.stat().st_size if priv_path.exists() else 0
|
|
86
|
+
pub_size = pub_path.stat().st_size if pub_path.exists() else 0
|
|
87
|
+
|
|
88
|
+
# 1. Check for 0-byte or truncated private key file
|
|
89
|
+
if priv_path.exists():
|
|
90
|
+
if priv_size == 0:
|
|
91
|
+
return AdbKeyStatus(
|
|
92
|
+
is_valid=False,
|
|
93
|
+
is_corrupted=True,
|
|
94
|
+
exists=True,
|
|
95
|
+
key_path=str(priv_path),
|
|
96
|
+
pub_key_path=str(pub_path),
|
|
97
|
+
key_size_bytes=0,
|
|
98
|
+
pub_key_size_bytes=pub_size,
|
|
99
|
+
error_reason="Private key file is 0 bytes (empty). ADB cannot authenticate devices.",
|
|
100
|
+
)
|
|
101
|
+
if priv_size < 100:
|
|
102
|
+
return AdbKeyStatus(
|
|
103
|
+
is_valid=False,
|
|
104
|
+
is_corrupted=True,
|
|
105
|
+
exists=True,
|
|
106
|
+
key_path=str(priv_path),
|
|
107
|
+
pub_key_path=str(pub_path),
|
|
108
|
+
key_size_bytes=priv_size,
|
|
109
|
+
pub_key_size_bytes=pub_size,
|
|
110
|
+
error_reason=f"Private key file is suspiciously small ({priv_size} bytes).",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Validate PEM header structure
|
|
114
|
+
try:
|
|
115
|
+
content = priv_path.read_text(encoding="utf-8", errors="replace")
|
|
116
|
+
if "BEGIN" not in content or "PRIVATE KEY" not in content:
|
|
117
|
+
return AdbKeyStatus(
|
|
118
|
+
is_valid=False,
|
|
119
|
+
is_corrupted=True,
|
|
120
|
+
exists=True,
|
|
121
|
+
key_path=str(priv_path),
|
|
122
|
+
pub_key_path=str(pub_path),
|
|
123
|
+
key_size_bytes=priv_size,
|
|
124
|
+
pub_key_size_bytes=pub_size,
|
|
125
|
+
error_reason="Private key does not contain a valid PEM private key header.",
|
|
126
|
+
)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return AdbKeyStatus(
|
|
129
|
+
is_valid=False,
|
|
130
|
+
is_corrupted=True,
|
|
131
|
+
exists=True,
|
|
132
|
+
key_path=str(priv_path),
|
|
133
|
+
pub_key_path=str(pub_path),
|
|
134
|
+
key_size_bytes=priv_size,
|
|
135
|
+
pub_key_size_bytes=pub_size,
|
|
136
|
+
error_reason=f"Cannot read private key: {e}",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 2. Check for 0-byte public key if it exists
|
|
140
|
+
if pub_path.exists() and pub_size == 0:
|
|
141
|
+
return AdbKeyStatus(
|
|
142
|
+
is_valid=False,
|
|
143
|
+
is_corrupted=True,
|
|
144
|
+
exists=True,
|
|
145
|
+
key_path=str(priv_path),
|
|
146
|
+
pub_key_path=str(pub_path),
|
|
147
|
+
key_size_bytes=priv_size,
|
|
148
|
+
pub_key_size_bytes=0,
|
|
149
|
+
error_reason="Public key file (adbkey.pub) is 0 bytes (empty).",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return AdbKeyStatus(
|
|
153
|
+
is_valid=True,
|
|
154
|
+
is_corrupted=False,
|
|
155
|
+
exists=True,
|
|
156
|
+
key_path=str(priv_path),
|
|
157
|
+
pub_key_path=str(pub_path),
|
|
158
|
+
key_size_bytes=priv_size,
|
|
159
|
+
pub_key_size_bytes=pub_size,
|
|
160
|
+
error_reason=None,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def heal_adb_keys(adb_path: str | None = None, force: bool = False) -> dict[str, Any]:
|
|
165
|
+
"""Auto-heal corrupted ADB authentication keys.
|
|
166
|
+
|
|
167
|
+
If keys are corrupted (or if `force=True`), this function:
|
|
168
|
+
1. Backs up or removes corrupted key files.
|
|
169
|
+
2. Restarts the ADB server to cleanly generate a fresh RSA 2048-bit key pair.
|
|
170
|
+
3. Verifies that the new key pair is healthy and valid.
|
|
171
|
+
"""
|
|
172
|
+
status = inspect_adb_keys()
|
|
173
|
+
if not status.is_corrupted and not force:
|
|
174
|
+
return {
|
|
175
|
+
"success": True,
|
|
176
|
+
"repaired": False,
|
|
177
|
+
"message": "ADB keys are healthy, no repair required.",
|
|
178
|
+
"status": status.to_dict(),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
priv_path, pub_path = get_adb_key_paths()
|
|
182
|
+
priv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
|
|
184
|
+
# Backup corrupted non-empty files before removal
|
|
185
|
+
timestamp = int(time.time())
|
|
186
|
+
if priv_path.exists() and priv_path.stat().st_size > 0:
|
|
187
|
+
backup_path = priv_path.with_name(f"adbkey.corrupted.{timestamp}")
|
|
188
|
+
try:
|
|
189
|
+
shutil.copy2(priv_path, backup_path)
|
|
190
|
+
logger.info(f"Backed up corrupted adbkey to {backup_path}")
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.warning(f"Could not backup corrupted adbkey: {e}")
|
|
193
|
+
|
|
194
|
+
# Remove corrupted key files
|
|
195
|
+
try:
|
|
196
|
+
if priv_path.exists():
|
|
197
|
+
priv_path.unlink(missing_ok=True)
|
|
198
|
+
if pub_path.exists():
|
|
199
|
+
pub_path.unlink(missing_ok=True)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Failed to remove corrupted adb keys: {e}")
|
|
202
|
+
return {
|
|
203
|
+
"success": False,
|
|
204
|
+
"repaired": False,
|
|
205
|
+
"message": f"Failed to remove corrupted ADB key files: {e}",
|
|
206
|
+
"status": status.to_dict(),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Resolve ADB executable
|
|
210
|
+
resolved_adb = adb_path or toolchain.resolve("adb") or shutil.which("adb") or "adb"
|
|
211
|
+
|
|
212
|
+
# Restart ADB server to trigger key generation
|
|
213
|
+
try:
|
|
214
|
+
clean_env = os.environ.copy()
|
|
215
|
+
clean_env.pop("ADB_SERVER_SOCKET", None)
|
|
216
|
+
subprocess.run(
|
|
217
|
+
[resolved_adb, "kill-server"],
|
|
218
|
+
stdout=subprocess.DEVNULL,
|
|
219
|
+
stderr=subprocess.DEVNULL,
|
|
220
|
+
timeout=5,
|
|
221
|
+
env=clean_env,
|
|
222
|
+
)
|
|
223
|
+
subprocess.run(
|
|
224
|
+
[resolved_adb, "start-server"],
|
|
225
|
+
stdout=subprocess.DEVNULL,
|
|
226
|
+
stderr=subprocess.DEVNULL,
|
|
227
|
+
timeout=5,
|
|
228
|
+
env=clean_env,
|
|
229
|
+
)
|
|
230
|
+
# Execute adb devices to trigger initial handshake & key material generation if needed
|
|
231
|
+
subprocess.run(
|
|
232
|
+
[resolved_adb, "devices"],
|
|
233
|
+
stdout=subprocess.DEVNULL,
|
|
234
|
+
stderr=subprocess.DEVNULL,
|
|
235
|
+
timeout=5,
|
|
236
|
+
env=clean_env,
|
|
237
|
+
)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Error restarting ADB server during key healing: {e}")
|
|
240
|
+
return {
|
|
241
|
+
"success": False,
|
|
242
|
+
"repaired": False,
|
|
243
|
+
"message": f"Failed to restart ADB server: {e}",
|
|
244
|
+
"status": status.to_dict(),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Re-inspect to verify health
|
|
248
|
+
new_status = inspect_adb_keys()
|
|
249
|
+
if new_status.is_valid and new_status.key_size_bytes > 0:
|
|
250
|
+
logger.info("Successfully healed corrupted ADB keys.")
|
|
251
|
+
return {
|
|
252
|
+
"success": True,
|
|
253
|
+
"repaired": True,
|
|
254
|
+
"message": "Corrupted ADB keys were successfully repaired and regenerated.",
|
|
255
|
+
"status": new_status.to_dict(),
|
|
256
|
+
}
|
|
257
|
+
elif new_status.is_valid and not new_status.is_corrupted:
|
|
258
|
+
return {
|
|
259
|
+
"success": True,
|
|
260
|
+
"repaired": True,
|
|
261
|
+
"message": "Corrupted ADB keys cleared. Fresh keys will be created on first device connection.",
|
|
262
|
+
"status": new_status.to_dict(),
|
|
263
|
+
}
|
|
264
|
+
else:
|
|
265
|
+
return {
|
|
266
|
+
"success": False,
|
|
267
|
+
"repaired": False,
|
|
268
|
+
"message": f"Key healing completed, but keys remain invalid: {new_status.error_reason}",
|
|
269
|
+
"status": new_status.to_dict(),
|
|
270
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Process-wide ADB server endpoint discovery and activation."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
import re
|
|
23
|
+
import subprocess
|
|
24
|
+
from typing import Any, Callable
|
|
25
|
+
|
|
26
|
+
from dotenv import set_key
|
|
27
|
+
|
|
28
|
+
from artemis.config import settings
|
|
29
|
+
from artemis.config.paths import ROOT_DIR, get_app_dir
|
|
30
|
+
from artemis.runtime.adb_endpoint import (
|
|
31
|
+
AdbEndpoint,
|
|
32
|
+
AdbSession,
|
|
33
|
+
InvalidAdbEndpoint,
|
|
34
|
+
current_adb_endpoint,
|
|
35
|
+
)
|
|
36
|
+
from artemis.toolchain import toolchain
|
|
37
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
38
|
+
|
|
39
|
+
logger = get_logger(__name__)
|
|
40
|
+
|
|
41
|
+
AdbServerEndpoint = AdbEndpoint
|
|
42
|
+
InvalidAdbServerEndpoint = InvalidAdbEndpoint
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AdbServerConnectionManager:
|
|
46
|
+
"""Validate, probe, activate, and persist the selected ADB server endpoint."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
adb_resolver: Callable[[], str | None] | None = None,
|
|
52
|
+
env_files: list[Path] | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._adb_resolver = adb_resolver or (lambda: toolchain.resolve("adb"))
|
|
55
|
+
self._env_files = env_files
|
|
56
|
+
self._activation_lock = asyncio.Lock()
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def validate_endpoint(host: str, port: int) -> AdbServerEndpoint:
|
|
60
|
+
return AdbEndpoint.create(host, port)
|
|
61
|
+
|
|
62
|
+
def current_endpoint(self) -> AdbServerEndpoint:
|
|
63
|
+
return current_adb_endpoint()
|
|
64
|
+
|
|
65
|
+
def status(self) -> dict[str, Any]:
|
|
66
|
+
return {"endpoint": self.current_endpoint().to_dict()}
|
|
67
|
+
|
|
68
|
+
def synchronize_environment(self) -> AdbServerEndpoint:
|
|
69
|
+
"""Align subprocess and Python ADB clients with the configured endpoint."""
|
|
70
|
+
socket_endpoint = self._endpoint_from_socket(os.environ.get("ADB_SERVER_SOCKET"))
|
|
71
|
+
has_explicit_host = bool(os.environ.get("ADB_HOST") or os.environ.get("ADB_PORT"))
|
|
72
|
+
endpoint = (
|
|
73
|
+
socket_endpoint
|
|
74
|
+
if socket_endpoint is not None and not has_explicit_host
|
|
75
|
+
else self.current_endpoint()
|
|
76
|
+
)
|
|
77
|
+
self._activate(endpoint)
|
|
78
|
+
return endpoint
|
|
79
|
+
|
|
80
|
+
async def connect(
|
|
81
|
+
self,
|
|
82
|
+
host: str,
|
|
83
|
+
port: int,
|
|
84
|
+
*,
|
|
85
|
+
persist: bool = True,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
"""Probe an endpoint, then make it the process-wide ADB server on success."""
|
|
88
|
+
endpoint = self.validate_endpoint(host, port)
|
|
89
|
+
probe_result = await self.probe(host, port)
|
|
90
|
+
if not probe_result["success"]:
|
|
91
|
+
return probe_result
|
|
92
|
+
|
|
93
|
+
persisted = False
|
|
94
|
+
persistence_error: str | None = None
|
|
95
|
+
async with self._activation_lock:
|
|
96
|
+
self._activate(endpoint)
|
|
97
|
+
if persist:
|
|
98
|
+
try:
|
|
99
|
+
await asyncio.to_thread(self._persist, endpoint)
|
|
100
|
+
persisted = True
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
persistence_error = str(exc)
|
|
103
|
+
logger.warning(f"Unable to persist ADB server endpoint: {exc}")
|
|
104
|
+
|
|
105
|
+
message = probe_result["message"]
|
|
106
|
+
if persist and not persisted:
|
|
107
|
+
message = (
|
|
108
|
+
f"{message} The connection is active for this session, but Artemis could not "
|
|
109
|
+
"save it for the next launch."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
**probe_result,
|
|
114
|
+
"endpoint": endpoint.to_dict(),
|
|
115
|
+
"message": message,
|
|
116
|
+
"persisted": persisted,
|
|
117
|
+
"persistence_error": persistence_error,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async def probe(self, host: str, port: int) -> dict[str, Any]:
|
|
121
|
+
"""Test an endpoint without changing the process preference."""
|
|
122
|
+
endpoint = self.validate_endpoint(host, port)
|
|
123
|
+
return await asyncio.to_thread(self._probe_sync, endpoint)
|
|
124
|
+
|
|
125
|
+
async def use_local_server(self, *, persist: bool = True) -> dict[str, Any]:
|
|
126
|
+
"""Restore the standard local ADB server without killing any remote daemon."""
|
|
127
|
+
endpoint = AdbEndpoint.local()
|
|
128
|
+
persisted = False
|
|
129
|
+
persistence_error: str | None = None
|
|
130
|
+
async with self._activation_lock:
|
|
131
|
+
self._activate(endpoint)
|
|
132
|
+
if persist:
|
|
133
|
+
try:
|
|
134
|
+
await asyncio.to_thread(self._persist, endpoint)
|
|
135
|
+
persisted = True
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
persistence_error = str(exc)
|
|
138
|
+
logger.warning(f"Unable to persist local ADB server endpoint: {exc}")
|
|
139
|
+
|
|
140
|
+
message = "Artemis is now using the local ADB server."
|
|
141
|
+
if persist and not persisted:
|
|
142
|
+
message += " The setting could not be saved for the next launch."
|
|
143
|
+
return {
|
|
144
|
+
"success": True,
|
|
145
|
+
"endpoint": endpoint.to_dict(),
|
|
146
|
+
"persisted": persisted,
|
|
147
|
+
"persistence_error": persistence_error,
|
|
148
|
+
"message": message,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def _probe_sync(self, endpoint: AdbServerEndpoint) -> dict[str, Any]:
|
|
152
|
+
adb_path = self._adb_resolver()
|
|
153
|
+
if not adb_path:
|
|
154
|
+
return {
|
|
155
|
+
"success": False,
|
|
156
|
+
"error_code": "adb_not_found",
|
|
157
|
+
"message": "Android Platform Tools (adb) is not installed or not on PATH.",
|
|
158
|
+
"endpoint": endpoint.to_dict(),
|
|
159
|
+
"devices": [],
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
session = AdbSession(endpoint, adb_path=adb_path)
|
|
163
|
+
command = session.command(["devices", "-l"])
|
|
164
|
+
try:
|
|
165
|
+
result = subprocess.run(
|
|
166
|
+
command,
|
|
167
|
+
capture_output=True,
|
|
168
|
+
text=True,
|
|
169
|
+
encoding="utf-8",
|
|
170
|
+
errors="replace",
|
|
171
|
+
timeout=8,
|
|
172
|
+
env=self._probe_environment(),
|
|
173
|
+
)
|
|
174
|
+
except subprocess.TimeoutExpired:
|
|
175
|
+
return {
|
|
176
|
+
"success": False,
|
|
177
|
+
"error_code": "timeout",
|
|
178
|
+
"message": (
|
|
179
|
+
"The ADB server did not respond. Check that the host and port are correct "
|
|
180
|
+
"and accessible from this computer."
|
|
181
|
+
),
|
|
182
|
+
"endpoint": endpoint.to_dict(),
|
|
183
|
+
"devices": [],
|
|
184
|
+
}
|
|
185
|
+
except OSError as exc:
|
|
186
|
+
logger.warning(f"Failed to execute ADB endpoint probe: {exc}")
|
|
187
|
+
return {
|
|
188
|
+
"success": False,
|
|
189
|
+
"error_code": "adb_execution_failed",
|
|
190
|
+
"message": f"Unable to run adb: {exc}",
|
|
191
|
+
"endpoint": endpoint.to_dict(),
|
|
192
|
+
"devices": [],
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stdout = result.stdout or ""
|
|
196
|
+
stderr = result.stderr or ""
|
|
197
|
+
output = "\n".join(part.strip() for part in (stdout, stderr) if part.strip())
|
|
198
|
+
devices = self._parse_devices(stdout)
|
|
199
|
+
if result.returncode != 0:
|
|
200
|
+
return {
|
|
201
|
+
"success": False,
|
|
202
|
+
"error_code": "server_unreachable",
|
|
203
|
+
"message": self._connection_error_message(output),
|
|
204
|
+
"output": output,
|
|
205
|
+
"endpoint": endpoint.to_dict(),
|
|
206
|
+
"devices": [],
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
ready_devices = [device for device in devices if device["state"] == "device"]
|
|
210
|
+
if ready_devices:
|
|
211
|
+
message = (
|
|
212
|
+
f"Connected to {endpoint.host}:{endpoint.port} and found "
|
|
213
|
+
f"{len(ready_devices)} ready device{'s' if len(ready_devices) != 1 else ''}."
|
|
214
|
+
)
|
|
215
|
+
elif devices:
|
|
216
|
+
message = (
|
|
217
|
+
"The ADB server is reachable, but its devices require attention. "
|
|
218
|
+
"Unlock the phone and approve USB debugging if prompted."
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
message = (
|
|
222
|
+
"The ADB server is reachable, but no devices are attached to the remote computer."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"success": True,
|
|
227
|
+
"message": message,
|
|
228
|
+
"output": output,
|
|
229
|
+
"endpoint": endpoint.to_dict(),
|
|
230
|
+
"devices": devices,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _parse_devices(output: str) -> list[dict[str, str | None]]:
|
|
235
|
+
devices: list[dict[str, str | None]] = []
|
|
236
|
+
for raw_line in output.splitlines():
|
|
237
|
+
line = raw_line.strip()
|
|
238
|
+
if not line or line.lower().startswith("list of devices") or line.startswith("*"):
|
|
239
|
+
continue
|
|
240
|
+
parts = line.split()
|
|
241
|
+
if len(parts) < 2:
|
|
242
|
+
continue
|
|
243
|
+
metadata = {
|
|
244
|
+
key: value.replace("_", " ")
|
|
245
|
+
for token in parts[2:]
|
|
246
|
+
if ":" in token
|
|
247
|
+
for key, value in [token.split(":", 1)]
|
|
248
|
+
}
|
|
249
|
+
devices.append(
|
|
250
|
+
{
|
|
251
|
+
"serial": parts[0],
|
|
252
|
+
"state": parts[1],
|
|
253
|
+
"model": metadata.get("model"),
|
|
254
|
+
"product": metadata.get("product"),
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
return devices
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _connection_error_message(output: str) -> str:
|
|
261
|
+
lowered = output.lower()
|
|
262
|
+
if "cannot connect" in lowered or "failed to connect" in lowered:
|
|
263
|
+
return (
|
|
264
|
+
"No ADB server is listening at this address. Verify that the host and port are "
|
|
265
|
+
"correct and accessible from this computer."
|
|
266
|
+
)
|
|
267
|
+
if "version" in lowered and "mismatch" in lowered:
|
|
268
|
+
return (
|
|
269
|
+
"The local and remote ADB versions are incompatible. Use matching Android "
|
|
270
|
+
"Platform Tools versions on both computers."
|
|
271
|
+
)
|
|
272
|
+
return output or "Unable to reach the ADB server endpoint."
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _probe_environment() -> dict[str, str]:
|
|
276
|
+
environment = os.environ.copy()
|
|
277
|
+
environment.pop("ADB_SERVER_SOCKET", None)
|
|
278
|
+
return environment
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def _endpoint_from_socket(cls, value: str | None) -> AdbServerEndpoint | None:
|
|
282
|
+
if not value or not value.startswith("tcp:"):
|
|
283
|
+
return None
|
|
284
|
+
address = value.removeprefix("tcp:")
|
|
285
|
+
if address.startswith("["):
|
|
286
|
+
match = re.fullmatch(r"\[([^]]+)]:(\d+)", address)
|
|
287
|
+
else:
|
|
288
|
+
match = re.fullmatch(r"(.+):(\d+)", address)
|
|
289
|
+
if match is None:
|
|
290
|
+
return None
|
|
291
|
+
try:
|
|
292
|
+
return cls.validate_endpoint(match.group(1), int(match.group(2)))
|
|
293
|
+
except (InvalidAdbServerEndpoint, ValueError):
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _activate(endpoint: AdbServerEndpoint) -> None:
|
|
298
|
+
settings.ADB_HOST = endpoint.host
|
|
299
|
+
settings.ADB_PORT = endpoint.port
|
|
300
|
+
endpoint.apply_to_environment()
|
|
301
|
+
logger.info(
|
|
302
|
+
f"Activated {endpoint.mode} ADB server endpoint {endpoint.host}:{endpoint.port}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def _persist(self, endpoint: AdbServerEndpoint) -> None:
|
|
306
|
+
for env_file in self._environment_files():
|
|
307
|
+
env_file.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
if not env_file.exists():
|
|
309
|
+
env_file.touch()
|
|
310
|
+
set_key(str(env_file), "ADB_HOST", endpoint.host, quote_mode="never")
|
|
311
|
+
set_key(str(env_file), "ADB_PORT", str(endpoint.port), quote_mode="never")
|
|
312
|
+
|
|
313
|
+
def _environment_files(self) -> list[Path]:
|
|
314
|
+
if self._env_files is not None:
|
|
315
|
+
candidates = self._env_files
|
|
316
|
+
else:
|
|
317
|
+
candidates = [ROOT_DIR / ".env", get_app_dir() / ".env"]
|
|
318
|
+
|
|
319
|
+
unique_files: list[Path] = []
|
|
320
|
+
seen: set[Path] = set()
|
|
321
|
+
for candidate in candidates:
|
|
322
|
+
resolved = candidate.resolve()
|
|
323
|
+
if resolved not in seen:
|
|
324
|
+
seen.add(resolved)
|
|
325
|
+
unique_files.append(candidate)
|
|
326
|
+
return unique_files
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
adb_server_connection = AdbServerConnectionManager()
|
|
330
|
+
adb_server_connection.synchronize_environment()
|