android-midscene-automation 0.2.4 → 1.1.1
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 +10 -0
- package/README.md +159 -29
- 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 +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- 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 +340 -121
- package/src/types.ts +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
"""Mock actuator for tests: real actuator code paths over an in-memory device.
|
|
16
|
+
|
|
17
|
+
Subclasses :class:`AdbActuator` deliberately, so tests exercise the same coordinate
|
|
18
|
+
conversion and controller dispatch as production -- only the driver is in-memory.
|
|
19
|
+
``capabilities``/``extensions`` are overridable to model partial and extended
|
|
20
|
+
backends.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from artemis.context import ArtemisContext, DeviceContext, DevicePlatform
|
|
24
|
+
from artemis.drivers.mock.mock_driver import MockDeviceDriver
|
|
25
|
+
from artemis.mcp.action_manifest import DEVICE_ACTIONS, ExtensionTool
|
|
26
|
+
from artemis.mcp.actuators.adb import AdbActuator
|
|
27
|
+
from artemis.platform import platform
|
|
28
|
+
|
|
29
|
+
__all__ = ["MockActuator"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MockActuator(AdbActuator):
|
|
33
|
+
"""An :class:`AdbActuator` bound to a :class:`MockDeviceDriver`."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
width: int = 1080,
|
|
38
|
+
height: int = 2400,
|
|
39
|
+
capabilities: frozenset[str] | None = None,
|
|
40
|
+
extensions: list[ExtensionTool] | None = None,
|
|
41
|
+
):
|
|
42
|
+
driver = MockDeviceDriver(width=width, height=height)
|
|
43
|
+
ctx = ArtemisContext(
|
|
44
|
+
trace_id="mock-actuator",
|
|
45
|
+
device=DeviceContext(
|
|
46
|
+
host_platform=platform.os_type.name,
|
|
47
|
+
mobile_platform=DevicePlatform.ANDROID,
|
|
48
|
+
device_id=driver.device_id,
|
|
49
|
+
device_width=width,
|
|
50
|
+
device_height=height,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
# get_driver(ctx) returns a cached `_active_driver` when present
|
|
54
|
+
# (artemis/drivers/factory.py), the established test seam.
|
|
55
|
+
ctx._active_driver = driver
|
|
56
|
+
super().__init__(ctx)
|
|
57
|
+
self.driver = driver
|
|
58
|
+
self._capabilities = capabilities if capabilities is not None else DEVICE_ACTIONS
|
|
59
|
+
self._extensions = list(extensions or [])
|
|
60
|
+
|
|
61
|
+
def capabilities(self) -> frozenset[str]:
|
|
62
|
+
return self._capabilities
|
|
63
|
+
|
|
64
|
+
def extensions(self) -> list[ExtensionTool]:
|
|
65
|
+
return self._extensions
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def action_history(self) -> list[dict]:
|
|
69
|
+
"""The mock driver's recorded actions, for assertions."""
|
|
70
|
+
return self.driver.action_history
|
|
@@ -0,0 +1,490 @@
|
|
|
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
|
+
from collections.abc import Callable
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import sys
|
|
21
|
+
import textwrap
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
# Ensure repository root is in sys.path when executed directly or via MCP runner
|
|
25
|
+
_REPO_ROOT = str(Path(__file__).resolve().parent.parent.parent)
|
|
26
|
+
if _REPO_ROOT not in sys.path:
|
|
27
|
+
sys.path.insert(0, _REPO_ROOT)
|
|
28
|
+
|
|
29
|
+
# pylint: disable=wrong-import-position
|
|
30
|
+
from adbutils import AdbClient
|
|
31
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from mcp.server.fastmcp.server import Settings as FastMCPSettings
|
|
37
|
+
|
|
38
|
+
FastMCPSettings.model_rebuild()
|
|
39
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
40
|
+
# Best-effort compatibility shim for FastMCP/pydantic version drift.
|
|
41
|
+
logger.debug("FastMCP Settings model_rebuild skipped: %s", exc, exc_info=True)
|
|
42
|
+
|
|
43
|
+
from artemis.clients.screen_client_factory import create_screen_client
|
|
44
|
+
from artemis.context import ArtemisContext, DeviceContext, DevicePlatform
|
|
45
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
46
|
+
from artemis.platform import platform
|
|
47
|
+
from third_party.mobile_use.utils.app_launch_utils import launch_app_with_retries
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def configure_stdio_mode() -> None:
|
|
51
|
+
"""Applies process-wide settings required when serving MCP over stdio.
|
|
52
|
+
|
|
53
|
+
These are destructive to the host process (console logging is removed and the
|
|
54
|
+
DataEngine IPC port is cleared), so they must only run when this module owns the
|
|
55
|
+
process -- i.e. from the ``__main__`` block. Importing this module for its tools
|
|
56
|
+
or for :func:`_get_controller` must leave the caller's logging and environment
|
|
57
|
+
untouched.
|
|
58
|
+
"""
|
|
59
|
+
# Redirect stdio to prevent logs from breaking MCP JSON-RPC protocol and causing deadlocks.
|
|
60
|
+
try:
|
|
61
|
+
from artemis.config import settings
|
|
62
|
+
|
|
63
|
+
log_path = Path(settings.TRACES_PATH) / "mcp_server.log"
|
|
64
|
+
except Exception:
|
|
65
|
+
log_path = Path("traces") / "mcp_server.log"
|
|
66
|
+
|
|
67
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
for name in ["artemis", __name__]:
|
|
70
|
+
log_instance = logging.getLogger(name)
|
|
71
|
+
log_instance.setLevel(logging.DEBUG)
|
|
72
|
+
log_instance.propagate = False
|
|
73
|
+
# Remove standard streams handlers
|
|
74
|
+
log_instance.handlers = [
|
|
75
|
+
h for h in log_instance.handlers if not isinstance(h, logging.StreamHandler)
|
|
76
|
+
]
|
|
77
|
+
fh = logging.FileHandler(log_path, encoding="utf-8")
|
|
78
|
+
fh.setFormatter(logging.Formatter("%(asctime)s | %(name)s | %(levelname)s | %(message)s"))
|
|
79
|
+
log_instance.addHandler(fh)
|
|
80
|
+
|
|
81
|
+
logger.info("MCP Server Python process fully started. Configuring FastMCP service...")
|
|
82
|
+
|
|
83
|
+
# Avoid concurrent SQLite WAL locks by blocking child processes from connecting
|
|
84
|
+
# to the primary DataEngine.
|
|
85
|
+
os.environ["ARTEMIS_IPC_PORT"] = ""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Create minimal MCP server
|
|
89
|
+
mcp = FastMCP("Android_ADB_Controller")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _tool_description(fn: Callable[..., Any]) -> str:
|
|
93
|
+
"""Returns ``fn``'s docstring normalized identically on every Python version.
|
|
94
|
+
|
|
95
|
+
FastMCP publishes ``__doc__`` verbatim as the tool description. Python 3.13+
|
|
96
|
+
strips the common indentation of docstring continuation lines at compile time,
|
|
97
|
+
while 3.12 keeps it, so the schema external MCP clients see would otherwise
|
|
98
|
+
depend on the interpreter. This reproduces the 3.13 form (first line as-is,
|
|
99
|
+
remaining lines dedented, whitespace-only lines emptied); it is a no-op there.
|
|
100
|
+
"""
|
|
101
|
+
first, sep, rest = (fn.__doc__ or "").partition("\n")
|
|
102
|
+
return first + sep + textwrap.dedent(rest)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _tool() -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
106
|
+
"""``mcp.tool()`` with a Python-version-independent description."""
|
|
107
|
+
|
|
108
|
+
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
109
|
+
return mcp.tool(description=_tool_description(fn))(fn)
|
|
110
|
+
|
|
111
|
+
return decorator
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_GLOBAL_CONTROLLER = None
|
|
115
|
+
_CONTROLLERS: dict[str, Any] = {}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _get_controller(device_serial: str | None = None):
|
|
119
|
+
"""Lazy-load device controller on-demand, caching per device serial."""
|
|
120
|
+
global _GLOBAL_CONTROLLER, _CONTROLLERS
|
|
121
|
+
target_serial = (
|
|
122
|
+
device_serial or os.environ.get("ARTEMIS_DEVICE_ID") or os.environ.get("ADB_DEVICE_SERIAL")
|
|
123
|
+
)
|
|
124
|
+
if target_serial and target_serial in _CONTROLLERS:
|
|
125
|
+
return _CONTROLLERS[target_serial]
|
|
126
|
+
if not target_serial and _GLOBAL_CONTROLLER is not None:
|
|
127
|
+
return _GLOBAL_CONTROLLER
|
|
128
|
+
|
|
129
|
+
logger.info("Initializing lazy device controller...")
|
|
130
|
+
if os.environ.get("ARTEMIS_CLOUD_MODE") == "1":
|
|
131
|
+
logger.info(
|
|
132
|
+
"GCP CLOUD MODE active: initializing CloudMobileDeviceController via UnifiedMobileController..."
|
|
133
|
+
)
|
|
134
|
+
resolved_serial = target_serial or "cloud_device"
|
|
135
|
+
ctx = ArtemisContext(
|
|
136
|
+
device=DeviceContext(
|
|
137
|
+
platform=DevicePlatform.ANDROID,
|
|
138
|
+
device_id=resolved_serial,
|
|
139
|
+
width=1080,
|
|
140
|
+
height=2400,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
controller = UnifiedMobileController(ctx=ctx)
|
|
144
|
+
if resolved_serial:
|
|
145
|
+
_CONTROLLERS[resolved_serial] = controller
|
|
146
|
+
if _GLOBAL_CONTROLLER is None:
|
|
147
|
+
_GLOBAL_CONTROLLER = controller
|
|
148
|
+
return controller
|
|
149
|
+
|
|
150
|
+
host = os.environ.get("ADB_HOST", "localhost")
|
|
151
|
+
port_str = os.environ.get("ADB_PORT", "5037")
|
|
152
|
+
port = int(port_str) if port_str.isdigit() else 5037
|
|
153
|
+
if "ADB_SERVER_SOCKET" not in os.environ and (host != "localhost" or port != 5037):
|
|
154
|
+
os.environ["ADB_SERVER_SOCKET"] = f"tcp:{host}:{port}"
|
|
155
|
+
adb = AdbClient(host=host, port=port)
|
|
156
|
+
|
|
157
|
+
devices = adb.device_list()
|
|
158
|
+
if not devices:
|
|
159
|
+
raise Exception(f"No Android devices found at {host}:{port}")
|
|
160
|
+
|
|
161
|
+
if target_serial:
|
|
162
|
+
matched = [d for d in devices if d.serial == target_serial]
|
|
163
|
+
if not matched:
|
|
164
|
+
raise Exception(
|
|
165
|
+
f"Target Android device '{target_serial}' not found at"
|
|
166
|
+
f" {host}:{port} among available devices."
|
|
167
|
+
)
|
|
168
|
+
device = matched[0]
|
|
169
|
+
else:
|
|
170
|
+
device = devices[0]
|
|
171
|
+
device_id = device.serial
|
|
172
|
+
|
|
173
|
+
# Observer path: the helper is used only when it is already installed and
|
|
174
|
+
# running; nothing is installed from here (that happens inside a task's
|
|
175
|
+
# device-lock boundary or via `artemis helper install`).
|
|
176
|
+
ui_client = create_screen_client(device_id)
|
|
177
|
+
try:
|
|
178
|
+
ui_data = ui_client.get_screen_data()
|
|
179
|
+
width, height = ui_data.width, ui_data.height
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.warning(f"Failed initial screen data check, using defaults: {e}")
|
|
182
|
+
width, height = 1080, 2400
|
|
183
|
+
|
|
184
|
+
ctx = ArtemisContext(
|
|
185
|
+
trace_id="mcp-session",
|
|
186
|
+
device=DeviceContext(
|
|
187
|
+
host_platform=platform.os_type.name,
|
|
188
|
+
mobile_platform=DevicePlatform.ANDROID,
|
|
189
|
+
device_id=device_id,
|
|
190
|
+
device_width=width,
|
|
191
|
+
device_height=height,
|
|
192
|
+
),
|
|
193
|
+
adb_client=adb,
|
|
194
|
+
ui_adb_client=ui_client,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
controller = UnifiedMobileController(ctx)
|
|
198
|
+
if device_id:
|
|
199
|
+
_CONTROLLERS[device_id] = controller
|
|
200
|
+
if _GLOBAL_CONTROLLER is None:
|
|
201
|
+
_GLOBAL_CONTROLLER = controller
|
|
202
|
+
logger.info(f"Lazy device controller fully initialized for device: {device_id}")
|
|
203
|
+
return controller
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Shared with the in-process actuator layer; this stdio server keeps its legacy
|
|
207
|
+
# pixel-space contract for external MCP clients, but the element/focus logic has a
|
|
208
|
+
# single implementation in artemis.mcp.actuators.adb.
|
|
209
|
+
from artemis.mcp.actuators.adb import ( # noqa: E402 pylint: disable=wrong-import-position
|
|
210
|
+
ensure_focus_at_coords as _ensure_focus_at_coords,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@_tool()
|
|
215
|
+
async def tap(
|
|
216
|
+
ctx: Context,
|
|
217
|
+
coordinates: list[int],
|
|
218
|
+
times: int = 1,
|
|
219
|
+
delay_ms: int = 100,
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Taps on the screen at coordinates.
|
|
222
|
+
|
|
223
|
+
'coordinates' is a list [x, y].
|
|
224
|
+
'times' is the number of consecutive clicks (default 1).
|
|
225
|
+
'delay_ms' is the delay in milliseconds between consecutive clicks (default
|
|
226
|
+
100).
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
controller = _get_controller()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
232
|
+
|
|
233
|
+
if len(coordinates) != 2:
|
|
234
|
+
return "Error: coordinates must be [x, y]"
|
|
235
|
+
|
|
236
|
+
result = await controller.tap_at(
|
|
237
|
+
x=coordinates[0], y=coordinates[1], times=times, delay_ms=delay_ms
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if result.error:
|
|
241
|
+
return f"Error: {result.error}"
|
|
242
|
+
return "Success"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@_tool()
|
|
246
|
+
async def long_press_on(
|
|
247
|
+
ctx: Context,
|
|
248
|
+
coordinates: list[int],
|
|
249
|
+
duration: int = 1000,
|
|
250
|
+
) -> str:
|
|
251
|
+
"""Long presses on the screen at coordinates.
|
|
252
|
+
|
|
253
|
+
'coordinates' is a list [x, y].
|
|
254
|
+
'duration' is in milliseconds (default 1000).
|
|
255
|
+
"""
|
|
256
|
+
try:
|
|
257
|
+
controller = _get_controller()
|
|
258
|
+
except Exception as e:
|
|
259
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
260
|
+
|
|
261
|
+
if len(coordinates) != 2:
|
|
262
|
+
return "Error: coordinates must be [x, y]"
|
|
263
|
+
|
|
264
|
+
result = await controller.tap_at(
|
|
265
|
+
x=coordinates[0],
|
|
266
|
+
y=coordinates[1],
|
|
267
|
+
long_press=True,
|
|
268
|
+
long_press_duration=duration,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if result.error:
|
|
272
|
+
return f"Error: {result.error}"
|
|
273
|
+
return "Success"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@_tool()
|
|
277
|
+
async def swipe(
|
|
278
|
+
ctx: Context,
|
|
279
|
+
coordinates: list[int],
|
|
280
|
+
duration: int = 400,
|
|
281
|
+
) -> str:
|
|
282
|
+
"""Swipes from start coordinates to end coordinates.
|
|
283
|
+
|
|
284
|
+
'coordinates' is a list [start_x, start_y, end_x, end_y].
|
|
285
|
+
'duration' is in milliseconds (default 400).
|
|
286
|
+
|
|
287
|
+
Set duration >= 1000 to drag-and-drop. Drag slightly past the target
|
|
288
|
+
position to trigger reordering.
|
|
289
|
+
"""
|
|
290
|
+
try:
|
|
291
|
+
controller = _get_controller()
|
|
292
|
+
except Exception as e:
|
|
293
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
294
|
+
|
|
295
|
+
if len(coordinates) != 4:
|
|
296
|
+
return "Error: coordinates must be [start_x, start_y, end_x, end_y]"
|
|
297
|
+
|
|
298
|
+
error = await controller.swipe_coords(
|
|
299
|
+
start_x=coordinates[0],
|
|
300
|
+
start_y=coordinates[1],
|
|
301
|
+
end_x=coordinates[2],
|
|
302
|
+
end_y=coordinates[3],
|
|
303
|
+
duration=duration,
|
|
304
|
+
)
|
|
305
|
+
if error:
|
|
306
|
+
return f"Error: {error}"
|
|
307
|
+
return "Success"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@_tool()
|
|
311
|
+
async def back(ctx: Context) -> str:
|
|
312
|
+
"""Simulates pressing the system back button."""
|
|
313
|
+
try:
|
|
314
|
+
controller = _get_controller()
|
|
315
|
+
except Exception as e:
|
|
316
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
317
|
+
|
|
318
|
+
success = await controller.go_back()
|
|
319
|
+
return "Success" if success else "Failed"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@_tool()
|
|
323
|
+
async def launch_app(ctx: Context, package_name: str) -> str:
|
|
324
|
+
"""Launches an application by its Android package name with retries and smart polling."""
|
|
325
|
+
try:
|
|
326
|
+
controller = _get_controller()
|
|
327
|
+
except Exception as e:
|
|
328
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
329
|
+
|
|
330
|
+
success, error_msg = await launch_app_with_retries(controller.ctx, package_name)
|
|
331
|
+
return "Success" if success else f"Failed: {error_msg}"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@_tool()
|
|
335
|
+
async def stop_app(ctx: Context, package_name: str) -> str:
|
|
336
|
+
"""Force stops an application by its Android package name."""
|
|
337
|
+
try:
|
|
338
|
+
controller = _get_controller()
|
|
339
|
+
except Exception as e:
|
|
340
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
341
|
+
|
|
342
|
+
success = await controller.terminate_app(package_name)
|
|
343
|
+
return "Success" if success else "Failed"
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@_tool()
|
|
347
|
+
async def open_link(ctx: Context, url: str) -> str:
|
|
348
|
+
"""Opens a URL or deep link on the device."""
|
|
349
|
+
try:
|
|
350
|
+
controller = _get_controller()
|
|
351
|
+
except Exception as e:
|
|
352
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
353
|
+
|
|
354
|
+
success = await controller.open_url(url)
|
|
355
|
+
return "Success" if success else "Failed"
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@_tool()
|
|
359
|
+
async def focus_and_input_text(
|
|
360
|
+
ctx: Context,
|
|
361
|
+
coordinates: list[int],
|
|
362
|
+
text: str,
|
|
363
|
+
clear_before_input: bool = False,
|
|
364
|
+
) -> str:
|
|
365
|
+
"""Focuses on a UI element at coordinates and inputs text.
|
|
366
|
+
|
|
367
|
+
'coordinates' is a list [x, y] to tap first to gain focus.
|
|
368
|
+
'clear_before_input' if True, clears all existing text before typing. If False, appends text at the end of existing content.
|
|
369
|
+
'text' supports multi-line content with '\\n'.
|
|
370
|
+
"""
|
|
371
|
+
try:
|
|
372
|
+
controller = _get_controller()
|
|
373
|
+
except Exception as e:
|
|
374
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
375
|
+
|
|
376
|
+
if len(coordinates) != 2:
|
|
377
|
+
return "Error: coordinates must be [x, y]"
|
|
378
|
+
|
|
379
|
+
err = await _ensure_focus_at_coords(controller, coordinates[0], coordinates[1])
|
|
380
|
+
if err:
|
|
381
|
+
return f"Error focusing element: {err}"
|
|
382
|
+
|
|
383
|
+
if clear_before_input:
|
|
384
|
+
success = await controller.erase_text()
|
|
385
|
+
if not success:
|
|
386
|
+
return "Failed to clear existing text"
|
|
387
|
+
else:
|
|
388
|
+
# Move cursor to the end for reliable append
|
|
389
|
+
await controller.press_key("123")
|
|
390
|
+
|
|
391
|
+
success = await controller.type_text(text, clear_existing=False)
|
|
392
|
+
return "Success" if success else "Failed"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@_tool()
|
|
396
|
+
async def focus_and_clear_text(
|
|
397
|
+
ctx: Context,
|
|
398
|
+
coordinates: list[int],
|
|
399
|
+
) -> str:
|
|
400
|
+
"""Focuses on a UI element at coordinates and clears its text content.
|
|
401
|
+
|
|
402
|
+
'coordinates' is a list [x, y] to tap first to gain focus.
|
|
403
|
+
"""
|
|
404
|
+
try:
|
|
405
|
+
controller = _get_controller()
|
|
406
|
+
except Exception as e:
|
|
407
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
408
|
+
|
|
409
|
+
if len(coordinates) != 2:
|
|
410
|
+
return "Error: coordinates must be [x, y]"
|
|
411
|
+
|
|
412
|
+
err = await _ensure_focus_at_coords(controller, coordinates[0], coordinates[1])
|
|
413
|
+
if err:
|
|
414
|
+
return f"Error focusing element: {err}"
|
|
415
|
+
|
|
416
|
+
success = await controller.erase_text()
|
|
417
|
+
return "Success" if success else "Failed"
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@_tool()
|
|
421
|
+
async def erase_one_char(ctx: Context) -> str:
|
|
422
|
+
"""Erases a single character (simulates Backspace)."""
|
|
423
|
+
try:
|
|
424
|
+
controller = _get_controller()
|
|
425
|
+
except Exception as e:
|
|
426
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
427
|
+
|
|
428
|
+
success = await controller.erase_text(nb_chars=1)
|
|
429
|
+
return "Success" if success else "Failed"
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@_tool()
|
|
433
|
+
async def press_key(ctx: Context, keycode: str) -> str:
|
|
434
|
+
"""Presses a specific Android key event (e.g., KEYCODE_ENTER, KEYCODE_HOME)."""
|
|
435
|
+
try:
|
|
436
|
+
controller = _get_controller()
|
|
437
|
+
except Exception as e:
|
|
438
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
success = await controller.press_key(keycode)
|
|
442
|
+
return "Success" if success else "Failed"
|
|
443
|
+
except Exception as e:
|
|
444
|
+
return f"Error: {e}"
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@_tool()
|
|
448
|
+
async def take_screenshot(ctx: Context) -> str:
|
|
449
|
+
"""Takes a screenshot of the device screen.
|
|
450
|
+
|
|
451
|
+
Returns the screenshot as a base64 encoded JPEG string.
|
|
452
|
+
"""
|
|
453
|
+
try:
|
|
454
|
+
controller = _get_controller()
|
|
455
|
+
except Exception as e:
|
|
456
|
+
return f"Error: Controller lazy initialization failed: {e}"
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
screenshot_b64 = await controller.take_screenshot()
|
|
460
|
+
return screenshot_b64
|
|
461
|
+
except Exception as e:
|
|
462
|
+
return f"Error: {e}"
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@_tool()
|
|
466
|
+
async def get_ui_hierarchy(ctx: Context) -> str:
|
|
467
|
+
"""Retrieves the current UI elements hierarchy from the device."""
|
|
468
|
+
try:
|
|
469
|
+
controller = _get_controller()
|
|
470
|
+
except Exception as e:
|
|
471
|
+
logger.exception("Failed to initialize controller in get_ui_hierarchy")
|
|
472
|
+
raise e
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
elements = await controller.get_ui_elements()
|
|
476
|
+
return json.dumps(elements)
|
|
477
|
+
except Exception as e:
|
|
478
|
+
logger.exception("Failed to get UI elements in get_ui_hierarchy")
|
|
479
|
+
raise e
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
if __name__ == "__main__":
|
|
483
|
+
from artemis.runtime import shutdown_awake_service, start_awake_service
|
|
484
|
+
|
|
485
|
+
configure_stdio_mode()
|
|
486
|
+
start_awake_service()
|
|
487
|
+
try:
|
|
488
|
+
mcp.run(transport="stdio")
|
|
489
|
+
finally:
|
|
490
|
+
shutdown_awake_service()
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
"""Screen observation: capture, fuse, index, and persist -- without LangGraph State.
|
|
16
|
+
|
|
17
|
+
This is the act-then-observe half that used to be welded into every executor action
|
|
18
|
+
(``capture_screenshot_and_parse_ui``). Split out so that actuation and observation are
|
|
19
|
+
separate calls: the Flash adapter observes after each action, while the
|
|
20
|
+
Validator -- which runs its own UI-change polling -- simply never calls it.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import base64
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
from artemis.config import get_temp_dir
|
|
28
|
+
from artemis.context import ArtemisContext
|
|
29
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
30
|
+
from artemis.mcp.action_types import ActionCode, ObserveResult
|
|
31
|
+
from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml
|
|
32
|
+
from artemis.utils.visualization import format_minimal_list_with_elements
|
|
33
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
__all__ = ["observe"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def observe(
|
|
41
|
+
ctx: ArtemisContext,
|
|
42
|
+
controller: UnifiedMobileController,
|
|
43
|
+
settle_ms: int = 400,
|
|
44
|
+
ocr_results: list | None = None,
|
|
45
|
+
) -> tuple[ObserveResult, bytes | None]:
|
|
46
|
+
"""Captures and indexes the current screen.
|
|
47
|
+
|
|
48
|
+
Returns the structured observation plus the raw screenshot bytes (an in-process
|
|
49
|
+
convenience; the serialized ``ObserveResult`` carries only the persisted path).
|
|
50
|
+
|
|
51
|
+
Mirrors the historical ``capture_screenshot_and_parse_ui`` behavior exactly:
|
|
52
|
+
a settling delay, screen capture, XML fusion and element indexing, persistence via
|
|
53
|
+
the data engine with a temp-file fallback -- and the same failure semantics: a
|
|
54
|
+
failed capture yields ``ok=False``; a failed hierarchy parse still returns the
|
|
55
|
+
screenshot with ``hierarchy_ok=False``.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
if settle_ms > 0:
|
|
59
|
+
logger.info(f"Delaying {settle_ms / 1000:.1f}s before capturing screen state...")
|
|
60
|
+
await asyncio.sleep(settle_ms / 1000.0)
|
|
61
|
+
else:
|
|
62
|
+
logger.info("Retrieving screen data directly (skip settling)...")
|
|
63
|
+
device_data = await controller.get_screen_data()
|
|
64
|
+
screenshot_bytes = base64.b64decode(device_data.base64)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"Failed to capture screen state: {e}")
|
|
67
|
+
return (
|
|
68
|
+
ObserveResult(
|
|
69
|
+
ok=False,
|
|
70
|
+
code=ActionCode.DEVICE_ERROR,
|
|
71
|
+
message=f"Failed to capture screen state: {e}",
|
|
72
|
+
hierarchy_ok=False,
|
|
73
|
+
),
|
|
74
|
+
None,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
xml_hierarchy = None
|
|
78
|
+
ocr_results = ocr_results or []
|
|
79
|
+
elements: list[dict] = []
|
|
80
|
+
hierarchy_ok = True
|
|
81
|
+
width = height = None
|
|
82
|
+
try:
|
|
83
|
+
xml_hierarchy = device_data.elements
|
|
84
|
+
width = device_data.width or 1080
|
|
85
|
+
height = device_data.height or 2400
|
|
86
|
+
if ctx.device:
|
|
87
|
+
ctx.device.device_width = width
|
|
88
|
+
ctx.device.device_height = height
|
|
89
|
+
|
|
90
|
+
fused_xml = fuse_ocr_with_xml(xml_hierarchy, ocr_results)
|
|
91
|
+
minimal_list_str, elements, _ = format_minimal_list_with_elements(fused_xml, width, height)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Failed to fetch or fuse UI hierarchy: {e}")
|
|
94
|
+
minimal_list_str = "Not available due to hierarchy error."
|
|
95
|
+
hierarchy_ok = False
|
|
96
|
+
|
|
97
|
+
screenshot_path = None
|
|
98
|
+
if ctx.data_engine:
|
|
99
|
+
try:
|
|
100
|
+
image_name = ctx.data_engine.get_or_create_image(
|
|
101
|
+
screenshot_bytes,
|
|
102
|
+
ui_tree=xml_hierarchy,
|
|
103
|
+
ocr_result=ocr_results,
|
|
104
|
+
)
|
|
105
|
+
screenshot_path = str(ctx.data_engine.get_image_path(image_name))
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Failed to save image in DataEngine: {e}")
|
|
108
|
+
|
|
109
|
+
if not screenshot_path:
|
|
110
|
+
try:
|
|
111
|
+
temp_dir = get_temp_dir("screenshots")
|
|
112
|
+
temp_file = temp_dir / f"screenshot_{int(time.time())}.jpg"
|
|
113
|
+
temp_file.write_bytes(screenshot_bytes)
|
|
114
|
+
screenshot_path = str(temp_file)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Failed to save fallback screenshot: {e}")
|
|
117
|
+
|
|
118
|
+
from pathlib import Path
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
ObserveResult(
|
|
122
|
+
ok=True,
|
|
123
|
+
message="Screen observed.",
|
|
124
|
+
screenshot_path=screenshot_path,
|
|
125
|
+
image_name=Path(screenshot_path).stem if screenshot_path else None,
|
|
126
|
+
width=width,
|
|
127
|
+
height=height,
|
|
128
|
+
elements_text=minimal_list_str,
|
|
129
|
+
elements=elements,
|
|
130
|
+
hierarchy_ok=hierarchy_ok,
|
|
131
|
+
),
|
|
132
|
+
screenshot_bytes,
|
|
133
|
+
)
|