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,86 @@
|
|
|
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
|
+
"""Typed results for the device action layer.
|
|
16
|
+
|
|
17
|
+
These models replace the ``"Error" in text`` substring sniffing: success is carried by
|
|
18
|
+
``ok``/``code``, while ``message`` remains the human/LLM-facing summary and keeps its
|
|
19
|
+
historical wording. Kept dependency-free (pydantic only) so servers, adapters, and
|
|
20
|
+
tests can all import it cheaply.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from enum import Enum
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, Field
|
|
27
|
+
|
|
28
|
+
__all__ = ["ActionCode", "ActionResult", "ObserveResult"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ActionCode(str, Enum):
|
|
32
|
+
"""Machine-readable outcome classification for a device action."""
|
|
33
|
+
|
|
34
|
+
OK = "OK"
|
|
35
|
+
INVALID_ARGS = "INVALID_ARGS"
|
|
36
|
+
TARGET_NOT_FOUND = "TARGET_NOT_FOUND"
|
|
37
|
+
DEVICE_ERROR = "DEVICE_ERROR"
|
|
38
|
+
PACKAGE_NOT_FOUND = "PACKAGE_NOT_FOUND"
|
|
39
|
+
TIMEOUT = "TIMEOUT"
|
|
40
|
+
UNSUPPORTED = "UNSUPPORTED"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ActionResult(BaseModel):
|
|
44
|
+
"""Outcome of one device action, independent of any transport or agent."""
|
|
45
|
+
|
|
46
|
+
ok: bool
|
|
47
|
+
code: ActionCode
|
|
48
|
+
action: str
|
|
49
|
+
message: str
|
|
50
|
+
"""Agent-facing summary; wording matches the historical executor outcomes."""
|
|
51
|
+
detail: str | None = None
|
|
52
|
+
"""Raw driver/controller error for diagnostics; never shown to the LLM."""
|
|
53
|
+
normalized_coordinates: list[int] | None = None
|
|
54
|
+
duration_ms: int | None = None
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def success(cls, action: str, message: str, **kwargs: Any) -> "ActionResult":
|
|
58
|
+
return cls(ok=True, code=ActionCode.OK, action=action, message=message, **kwargs)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def failure(
|
|
62
|
+
cls,
|
|
63
|
+
action: str,
|
|
64
|
+
message: str,
|
|
65
|
+
code: ActionCode = ActionCode.DEVICE_ERROR,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> "ActionResult":
|
|
68
|
+
return cls(ok=False, code=code, action=action, message=message, **kwargs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ObserveResult(BaseModel):
|
|
72
|
+
"""Post-action observation: persisted screenshot plus the parsed element list."""
|
|
73
|
+
|
|
74
|
+
ok: bool
|
|
75
|
+
code: ActionCode = ActionCode.OK
|
|
76
|
+
message: str = ""
|
|
77
|
+
screenshot_path: str | None = None
|
|
78
|
+
image_name: str | None = None
|
|
79
|
+
"""Stem of ``screenshot_path``; stable key used by trace/replay consumers."""
|
|
80
|
+
width: int | None = None
|
|
81
|
+
height: int | None = None
|
|
82
|
+
elements_text: str | None = None
|
|
83
|
+
elements: list[dict[str, Any]] = Field(default_factory=list)
|
|
84
|
+
hierarchy_ok: bool = True
|
|
85
|
+
"""False when the screenshot succeeded but the UI hierarchy parse failed; callers
|
|
86
|
+
must then keep their previous element index rather than clobbering it."""
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
"""Actuator backends: pluggable implementations of the device action contract."""
|
|
16
|
+
|
|
17
|
+
from artemis.mcp.actuators.base import Actuator
|
|
18
|
+
from artemis.mcp.actuators.adb import AdbActuator
|
|
19
|
+
from artemis.mcp.actuators.mock import MockActuator
|
|
20
|
+
|
|
21
|
+
__all__ = ["Actuator", "AdbActuator", "MockActuator"]
|
|
@@ -0,0 +1,414 @@
|
|
|
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-backed actuator: the reference implementation of the actuator contract.
|
|
16
|
+
|
|
17
|
+
Outcome messages describe the dispatched command, such as
|
|
18
|
+
"Tapped at [x, y] (normalized)." The agent verifies the effect from subsequent
|
|
19
|
+
observations.
|
|
20
|
+
|
|
21
|
+
Error convention: device-level refusals the controller *reports* (an ``error`` field,
|
|
22
|
+
a falsy success flag) come back as ``ActionResult(ok=False)`` with the historical
|
|
23
|
+
message. Unexpected exceptions are allowed to propagate -- the caller (executor or MCP
|
|
24
|
+
server layer) owns the ``"Error during X: {e}"`` wrapping, exactly as the original
|
|
25
|
+
executor's ``try/except`` did.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import inspect
|
|
30
|
+
import re
|
|
31
|
+
import time
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from artemis.context import ArtemisContext
|
|
35
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
36
|
+
from artemis.mcp.action_manifest import DEVICE_ACTIONS, ExtensionTool
|
|
37
|
+
from artemis.mcp.action_types import ActionCode, ActionResult
|
|
38
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
39
|
+
|
|
40
|
+
logger = get_logger(__name__)
|
|
41
|
+
|
|
42
|
+
__all__ = ["AdbActuator", "find_element_at_coords", "ensure_focus_at_coords"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_element_at_coords(elements: list[dict], x: int, y: int) -> dict | None:
|
|
46
|
+
"""Finds the smallest (leaf-most) focusable element containing pixel [x, y].
|
|
47
|
+
|
|
48
|
+
Moved from ``artemis.mcp.adb_server`` so both the stdio server and in-process
|
|
49
|
+
actuator share one implementation.
|
|
50
|
+
"""
|
|
51
|
+
matching_element = None
|
|
52
|
+
min_area = float("inf")
|
|
53
|
+
|
|
54
|
+
for elem in elements:
|
|
55
|
+
is_focusable = (
|
|
56
|
+
elem.get("focusable") == "true"
|
|
57
|
+
or elem.get("clickable") == "true"
|
|
58
|
+
or "EditText" in str(elem.get("class", ""))
|
|
59
|
+
)
|
|
60
|
+
if not is_focusable:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
bounds_str = elem.get("bounds")
|
|
64
|
+
if bounds_str and isinstance(bounds_str, str):
|
|
65
|
+
match = re.match(r"\[(\-?\d+),(\-?\d+)\]\[(\-?\d+),(\-?\d+)\]", bounds_str)
|
|
66
|
+
if match:
|
|
67
|
+
x1, y1, x2, y2 = map(int, match.groups())
|
|
68
|
+
if x1 <= x <= x2 and y1 <= y <= y2:
|
|
69
|
+
area = (x2 - x1) * (y2 - y1)
|
|
70
|
+
if area < min_area:
|
|
71
|
+
min_area = area
|
|
72
|
+
matching_element = elem
|
|
73
|
+
|
|
74
|
+
return matching_element
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def ensure_focus_at_coords(controller, x: int, y: int) -> str | None:
|
|
78
|
+
"""Ensures the element at pixel [x, y] is focused, tapping only if needed.
|
|
79
|
+
|
|
80
|
+
Returns an error string on failure, ``None`` on success. Moved from
|
|
81
|
+
``artemis.mcp.adb_server``.
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
elements = await controller.get_ui_elements()
|
|
85
|
+
elem = find_element_at_coords(elements, x, y)
|
|
86
|
+
if elem and elem.get("focused") == "true":
|
|
87
|
+
logger.info(f"Element under [{x}, {y}] is already focused. Skipping tap.")
|
|
88
|
+
return None
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning(f"Failed to check focus status: {e}. Falling back to unconditional tap.")
|
|
91
|
+
|
|
92
|
+
result = await controller.tap_at(x=x, y=y)
|
|
93
|
+
if hasattr(result, "error") and result.error:
|
|
94
|
+
return result.error
|
|
95
|
+
|
|
96
|
+
# Wait for the UI to settle and keyboard to pop up after tapping
|
|
97
|
+
await asyncio.sleep(1.0)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AdbActuator:
|
|
102
|
+
"""Drives an Android device through ``UnifiedMobileController`` over ADB."""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
ctx: ArtemisContext,
|
|
107
|
+
controller: UnifiedMobileController | None = None,
|
|
108
|
+
):
|
|
109
|
+
self.ctx = ctx
|
|
110
|
+
self.controller = controller or UnifiedMobileController(ctx)
|
|
111
|
+
|
|
112
|
+
# --- Contract --------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
def capabilities(self) -> frozenset[str]:
|
|
115
|
+
return DEVICE_ACTIONS
|
|
116
|
+
|
|
117
|
+
def extensions(self) -> list[ExtensionTool]:
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
# --- Coordinate helpers ----------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def _dims(self) -> tuple[int, int]:
|
|
123
|
+
width = getattr(self.ctx.device, "device_width", 1080) if self.ctx.device else 1080
|
|
124
|
+
height = getattr(self.ctx.device, "device_height", 2400) if self.ctx.device else 2400
|
|
125
|
+
return width or 1080, height or 2400
|
|
126
|
+
|
|
127
|
+
def _to_px(self, nx: int, ny: int) -> tuple[int, int]:
|
|
128
|
+
width, height = self._dims()
|
|
129
|
+
x = int(max(0, min(width - 1, int(nx) * width / 1000)))
|
|
130
|
+
y = int(max(0, min(height - 1, int(ny) * height / 1000)))
|
|
131
|
+
return x, y
|
|
132
|
+
|
|
133
|
+
# --- Required action -------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
async def click_sequence(
|
|
136
|
+
self, points: list[tuple[int, int]], delay_ms: int = 50
|
|
137
|
+
) -> ActionResult:
|
|
138
|
+
outcomes = []
|
|
139
|
+
for i, (nx, ny) in enumerate(points):
|
|
140
|
+
x, y = self._to_px(nx, ny)
|
|
141
|
+
result = await self.controller.tap_at(x, y)
|
|
142
|
+
err = getattr(result, "error", None) if result else None
|
|
143
|
+
if err:
|
|
144
|
+
return ActionResult.failure(
|
|
145
|
+
"click_sequence",
|
|
146
|
+
f"Error executing click at step {i + 1}: {err}",
|
|
147
|
+
detail=str(err),
|
|
148
|
+
)
|
|
149
|
+
outcomes.append(f"[{nx}, {ny}]")
|
|
150
|
+
if i < len(points) - 1:
|
|
151
|
+
await asyncio.sleep(max(0, delay_ms) / 1000.0)
|
|
152
|
+
|
|
153
|
+
return ActionResult.success(
|
|
154
|
+
"click_sequence",
|
|
155
|
+
f"Tapped in sequence at {'; '.join(outcomes)} (normalized).",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# --- Optional actions ------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
async def click(self, nx: int, ny: int, times: int = 1, delay_ms: int = 100) -> ActionResult:
|
|
161
|
+
x, y = self._to_px(nx, ny)
|
|
162
|
+
result = await self.controller.tap_at(x, y, times=times, delay_ms=delay_ms)
|
|
163
|
+
err = getattr(result, "error", None) if result else None
|
|
164
|
+
if err is not None:
|
|
165
|
+
return ActionResult.failure("click", f"Error executing click: {err}", detail=str(err))
|
|
166
|
+
return ActionResult.success(
|
|
167
|
+
"click",
|
|
168
|
+
f"Tapped at [{nx}, {ny}] (normalized).",
|
|
169
|
+
normalized_coordinates=[int(nx), int(ny)],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
async def long_press(self, nx: int, ny: int, duration_ms: int = 1000) -> ActionResult:
|
|
173
|
+
x, y = self._to_px(nx, ny)
|
|
174
|
+
result = await self.controller.tap_at(
|
|
175
|
+
x, y, long_press=True, long_press_duration=duration_ms
|
|
176
|
+
)
|
|
177
|
+
err = getattr(result, "error", None) if result else None
|
|
178
|
+
if err is not None:
|
|
179
|
+
return ActionResult.failure(
|
|
180
|
+
"long_press", f"Error executing long press: {err}", detail=str(err)
|
|
181
|
+
)
|
|
182
|
+
return ActionResult.success(
|
|
183
|
+
"long_press",
|
|
184
|
+
f"Long-pressed at [{nx}, {ny}] (normalized) for {duration_ms}ms.",
|
|
185
|
+
normalized_coordinates=[int(nx), int(ny)],
|
|
186
|
+
duration_ms=duration_ms,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
async def input_text(
|
|
190
|
+
self,
|
|
191
|
+
text: str,
|
|
192
|
+
target: tuple[int, int] | None = None,
|
|
193
|
+
clear_exist: bool = True,
|
|
194
|
+
) -> ActionResult:
|
|
195
|
+
coords = None
|
|
196
|
+
if target:
|
|
197
|
+
nx, ny = target
|
|
198
|
+
x, y = self._to_px(nx, ny)
|
|
199
|
+
coords = [int(nx), int(ny)]
|
|
200
|
+
err = await ensure_focus_at_coords(self.controller, x, y)
|
|
201
|
+
if err:
|
|
202
|
+
return ActionResult.failure(
|
|
203
|
+
"input_text",
|
|
204
|
+
f"Error focusing element: {err}",
|
|
205
|
+
detail=str(err),
|
|
206
|
+
normalized_coordinates=coords,
|
|
207
|
+
)
|
|
208
|
+
if clear_exist:
|
|
209
|
+
if hasattr(self.controller, "erase_text") and not await self.controller.erase_text():
|
|
210
|
+
return ActionResult.failure(
|
|
211
|
+
"input_text",
|
|
212
|
+
"Failed to clear existing text",
|
|
213
|
+
normalized_coordinates=coords,
|
|
214
|
+
)
|
|
215
|
+
elif hasattr(self.controller, "press_key"):
|
|
216
|
+
# KEYCODE_MOVE_END: move cursor to the end for reliable append.
|
|
217
|
+
await self.controller.press_key("123")
|
|
218
|
+
if hasattr(self.controller, "type_text") and not await self.controller.type_text(
|
|
219
|
+
text, clear_existing=False
|
|
220
|
+
):
|
|
221
|
+
return ActionResult.failure(
|
|
222
|
+
"input_text",
|
|
223
|
+
f"Failed typing '{text}'.",
|
|
224
|
+
normalized_coordinates=coords,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return ActionResult.success(
|
|
228
|
+
"input_text",
|
|
229
|
+
f"Typed '{text}'"
|
|
230
|
+
+ (f" at [{coords[0]}, {coords[1]}] (normalized)." if coords else "."),
|
|
231
|
+
normalized_coordinates=coords,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
async def swipe(
|
|
235
|
+
self,
|
|
236
|
+
start: tuple[int, int],
|
|
237
|
+
end: tuple[int, int],
|
|
238
|
+
duration_ms: int = 800,
|
|
239
|
+
) -> ActionResult:
|
|
240
|
+
nx1, ny1 = start
|
|
241
|
+
nx2, ny2 = end
|
|
242
|
+
px1, py1 = self._to_px(nx1, ny1)
|
|
243
|
+
px2, py2 = self._to_px(nx2, ny2)
|
|
244
|
+
err = await self.controller.swipe_coords(px1, py1, px2, py2, duration_ms)
|
|
245
|
+
if err:
|
|
246
|
+
return ActionResult.failure("swipe", f"Error dragging: {err}", detail=str(err))
|
|
247
|
+
return ActionResult.success(
|
|
248
|
+
"swipe",
|
|
249
|
+
f"Swiped from [{nx1}, {ny1}] to [{nx2}, {ny2}] (normalized).",
|
|
250
|
+
normalized_coordinates=[int(nx1), int(ny1), int(nx2), int(ny2)],
|
|
251
|
+
duration_ms=duration_ms,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
async def press_key(self, key: str) -> ActionResult:
|
|
255
|
+
key_str = str(key).lower()
|
|
256
|
+
if not key_str:
|
|
257
|
+
return ActionResult.failure(
|
|
258
|
+
"press_key",
|
|
259
|
+
f"Error executing key press '{key}'.",
|
|
260
|
+
code=ActionCode.INVALID_ARGS,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if key_str == "back" and hasattr(self.controller, "go_back"):
|
|
264
|
+
await self.controller.go_back()
|
|
265
|
+
elif key_str == "home" and hasattr(self.controller, "go_home"):
|
|
266
|
+
await self.controller.go_home()
|
|
267
|
+
elif key_str == "enter" and hasattr(self.controller, "press_enter"):
|
|
268
|
+
await self.controller.press_enter()
|
|
269
|
+
elif hasattr(self.controller, "press_key"):
|
|
270
|
+
# Pass unrecognized keycodes through verbatim (KEYCODE_* names, numeric
|
|
271
|
+
# codes): the driver resolves or forwards them, matching the historical
|
|
272
|
+
# adb_server behavior of accepting any Android key event.
|
|
273
|
+
res = await self.controller.press_key(key)
|
|
274
|
+
err = getattr(res, "error", None) if res else None
|
|
275
|
+
if err:
|
|
276
|
+
return ActionResult.failure(
|
|
277
|
+
"press_key",
|
|
278
|
+
f"Error executing key press '{key}': {err}",
|
|
279
|
+
detail=str(err),
|
|
280
|
+
)
|
|
281
|
+
if not res:
|
|
282
|
+
return ActionResult.failure("press_key", f"Error executing key press '{key}'.")
|
|
283
|
+
|
|
284
|
+
return ActionResult.success("press_key", f"Pressed key '{key}'.")
|
|
285
|
+
|
|
286
|
+
async def manage_app(self, action: str, app_name: str) -> ActionResult:
|
|
287
|
+
# Imported lazily: launch_app pulls in tool wrappers that are costly at import.
|
|
288
|
+
from artemis.tools.mobile.launch_app import find_package
|
|
289
|
+
from third_party.mobile_use.utils.app_launch_utils import launch_app_with_retries
|
|
290
|
+
|
|
291
|
+
res = find_package(self.ctx, app_name, use_fallback=False)
|
|
292
|
+
pkg = await res if inspect.iscoroutine(res) else res
|
|
293
|
+
if not pkg:
|
|
294
|
+
return ActionResult.failure(
|
|
295
|
+
"manage_app",
|
|
296
|
+
f"Error finding package for app: {app_name}",
|
|
297
|
+
code=ActionCode.PACKAGE_NOT_FOUND,
|
|
298
|
+
)
|
|
299
|
+
target_pkg = pkg
|
|
300
|
+
|
|
301
|
+
if action.lower() == "launch":
|
|
302
|
+
res_launch = launch_app_with_retries(self.ctx, target_pkg)
|
|
303
|
+
if inspect.iscoroutine(res_launch):
|
|
304
|
+
res_launch = await res_launch
|
|
305
|
+
if isinstance(res_launch, tuple):
|
|
306
|
+
success, error_msg = res_launch
|
|
307
|
+
else:
|
|
308
|
+
success, error_msg = bool(res_launch), ""
|
|
309
|
+
|
|
310
|
+
if success:
|
|
311
|
+
return ActionResult.success(
|
|
312
|
+
"manage_app",
|
|
313
|
+
f"Launched app '{app_name}' ({target_pkg}); foreground confirmed.",
|
|
314
|
+
)
|
|
315
|
+
return ActionResult.failure(
|
|
316
|
+
"manage_app",
|
|
317
|
+
f"Failed to launch app '{app_name}': {error_msg}",
|
|
318
|
+
detail=error_msg or None,
|
|
319
|
+
)
|
|
320
|
+
elif action.lower() == "stop":
|
|
321
|
+
if hasattr(self.controller, "terminate_app"):
|
|
322
|
+
res_term = self.controller.terminate_app(target_pkg)
|
|
323
|
+
if inspect.iscoroutine(res_term):
|
|
324
|
+
await res_term
|
|
325
|
+
return ActionResult.success(
|
|
326
|
+
"manage_app", f"Force-stopped app '{app_name}' ({target_pkg})."
|
|
327
|
+
)
|
|
328
|
+
return ActionResult.failure(
|
|
329
|
+
"manage_app",
|
|
330
|
+
f"Invalid manage_app action: {action}",
|
|
331
|
+
code=ActionCode.INVALID_ARGS,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
async def wait_for_delay(self, time_in_ms: int) -> ActionResult:
|
|
335
|
+
delay_s = max(0.0, float(time_in_ms) / 1000.0)
|
|
336
|
+
await asyncio.sleep(delay_s)
|
|
337
|
+
return ActionResult.success(
|
|
338
|
+
"wait_for_delay",
|
|
339
|
+
f"Waited {time_in_ms}ms.",
|
|
340
|
+
duration_ms=int(time_in_ms),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
async def wait_for_text(
|
|
344
|
+
self, text: str, wait_state: str | None = None, timeout_ms: int | None = None
|
|
345
|
+
) -> ActionResult:
|
|
346
|
+
timeout_s = (timeout_ms or 5000) / 1000.0
|
|
347
|
+
start = time.time()
|
|
348
|
+
found = False
|
|
349
|
+
target_state = (wait_state or "appear").lower()
|
|
350
|
+
|
|
351
|
+
while time.time() - start < timeout_s:
|
|
352
|
+
tree = await self.controller.get_ui_elements()
|
|
353
|
+
text_present = text.lower() in str(tree or "").lower()
|
|
354
|
+
if target_state == "appear" and text_present:
|
|
355
|
+
found = True
|
|
356
|
+
break
|
|
357
|
+
elif target_state == "disappear" and not text_present:
|
|
358
|
+
found = True
|
|
359
|
+
break
|
|
360
|
+
await asyncio.sleep(0.5)
|
|
361
|
+
|
|
362
|
+
if found:
|
|
363
|
+
return ActionResult.success(
|
|
364
|
+
"wait_for_text",
|
|
365
|
+
f"Text '{text}' {'appeared' if target_state == 'appear' else 'disappeared'}"
|
|
366
|
+
f" in the UI tree after {int((time.time() - start) * 1000)}ms.",
|
|
367
|
+
)
|
|
368
|
+
return ActionResult.failure(
|
|
369
|
+
"wait_for_text",
|
|
370
|
+
f"Timed out waiting for text '{text}' to {target_state}.",
|
|
371
|
+
code=ActionCode.TIMEOUT,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
async def open_link(self, url: str) -> ActionResult:
|
|
375
|
+
success = await self.controller.open_url(url)
|
|
376
|
+
if success:
|
|
377
|
+
return ActionResult.success("open_link", f"Opened link '{url}'.")
|
|
378
|
+
return ActionResult.failure("open_link", f"Failed to open link '{url}'.")
|
|
379
|
+
|
|
380
|
+
async def erase_one_char(self) -> ActionResult:
|
|
381
|
+
success = await self.controller.erase_text(nb_chars=1)
|
|
382
|
+
if success:
|
|
383
|
+
return ActionResult.success("erase_one_char", "Erased one character.")
|
|
384
|
+
return ActionResult.failure("erase_one_char", "Failed to erase one character.")
|
|
385
|
+
|
|
386
|
+
async def focus_and_clear_text(self, nx: int, ny: int) -> ActionResult:
|
|
387
|
+
x, y = self._to_px(nx, ny)
|
|
388
|
+
err = await ensure_focus_at_coords(self.controller, x, y)
|
|
389
|
+
if err:
|
|
390
|
+
return ActionResult.failure(
|
|
391
|
+
"focus_and_clear_text",
|
|
392
|
+
f"Error focusing element: {err}",
|
|
393
|
+
detail=str(err),
|
|
394
|
+
normalized_coordinates=[int(nx), int(ny)],
|
|
395
|
+
)
|
|
396
|
+
success = await self.controller.erase_text()
|
|
397
|
+
if success:
|
|
398
|
+
return ActionResult.success(
|
|
399
|
+
"focus_and_clear_text",
|
|
400
|
+
f"Cleared text at [{nx}, {ny}] (normalized).",
|
|
401
|
+
normalized_coordinates=[int(nx), int(ny)],
|
|
402
|
+
)
|
|
403
|
+
return ActionResult.failure("focus_and_clear_text", "Failed to erase text.")
|
|
404
|
+
|
|
405
|
+
# --- Internal observation primitives ---------------------------------------------
|
|
406
|
+
|
|
407
|
+
async def take_screenshot(self) -> str:
|
|
408
|
+
return await self.controller.take_screenshot()
|
|
409
|
+
|
|
410
|
+
async def get_ui_elements(self) -> Any:
|
|
411
|
+
return await self.controller.get_ui_elements()
|
|
412
|
+
|
|
413
|
+
async def get_screen_data(self) -> Any:
|
|
414
|
+
return await self.controller.get_screen_data()
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
"""The actuator protocol: what any backend must look like to drive a device.
|
|
16
|
+
|
|
17
|
+
An actuator answers exactly one question -- *how to perform a physical action* -- in
|
|
18
|
+
the canonical 0-1000 normalized coordinate space. It knows nothing about LangGraph
|
|
19
|
+
``State``, prompts, agents, screenshots-as-observations, or MCP. Pixel geometry (or a
|
|
20
|
+
robot arm's motion planning) is a private detail behind this boundary.
|
|
21
|
+
|
|
22
|
+
A backend advertises what it can do through :meth:`Actuator.capabilities` (names from
|
|
23
|
+
``artemis.mcp.action_manifest.DEVICE_ACTIONS``) and may contribute tools nobody
|
|
24
|
+
anticipated through :meth:`Actuator.extensions`. ``validate_actuator`` in the manifest
|
|
25
|
+
enforces the contract at session start.
|
|
26
|
+
|
|
27
|
+
Methods for actions a backend does not implement should simply not exist (or raise
|
|
28
|
+
``NotImplementedError``); callers consult ``capabilities()`` first.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from typing import Any, Protocol, runtime_checkable
|
|
32
|
+
|
|
33
|
+
from artemis.mcp.action_manifest import ExtensionTool
|
|
34
|
+
from artemis.mcp.action_types import ActionResult
|
|
35
|
+
|
|
36
|
+
__all__ = ["Actuator"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@runtime_checkable
|
|
40
|
+
class Actuator(Protocol):
|
|
41
|
+
"""Protocol every actuator backend implements.
|
|
42
|
+
|
|
43
|
+
All coordinates are 0-1000 normalized. Every action coroutine returns an
|
|
44
|
+
:class:`ActionResult` and must not raise for device-level failures -- those are
|
|
45
|
+
reported through ``ok=False`` so a failed tap reaches the agent as an observation
|
|
46
|
+
rather than an exception.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def capabilities(self) -> frozenset[str]:
|
|
50
|
+
"""Names from ``DEVICE_ACTIONS`` this backend implements."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def extensions(self) -> list[ExtensionTool]:
|
|
54
|
+
"""Backend-specific tools unknown to the manifest."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
# --- Required action -------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
async def click_sequence(
|
|
60
|
+
self, points: list[tuple[int, int]], delay_ms: int = 50
|
|
61
|
+
) -> ActionResult: ...
|
|
62
|
+
|
|
63
|
+
# --- Optional actions ------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
async def click(
|
|
66
|
+
self, nx: int, ny: int, times: int = 1, delay_ms: int = 100
|
|
67
|
+
) -> ActionResult: ...
|
|
68
|
+
|
|
69
|
+
async def long_press(self, nx: int, ny: int, duration_ms: int = 1000) -> ActionResult: ...
|
|
70
|
+
|
|
71
|
+
async def input_text(
|
|
72
|
+
self,
|
|
73
|
+
text: str,
|
|
74
|
+
target: tuple[int, int] | None = None,
|
|
75
|
+
clear_exist: bool = True,
|
|
76
|
+
) -> ActionResult: ...
|
|
77
|
+
|
|
78
|
+
async def swipe(
|
|
79
|
+
self,
|
|
80
|
+
start: tuple[int, int],
|
|
81
|
+
end: tuple[int, int],
|
|
82
|
+
duration_ms: int = 800,
|
|
83
|
+
) -> ActionResult: ...
|
|
84
|
+
|
|
85
|
+
async def press_key(self, key: str) -> ActionResult: ...
|
|
86
|
+
|
|
87
|
+
async def manage_app(self, action: str, app_name: str) -> ActionResult: ...
|
|
88
|
+
|
|
89
|
+
async def wait_for_delay(self, time_in_ms: int) -> ActionResult: ...
|
|
90
|
+
|
|
91
|
+
async def wait_for_text(
|
|
92
|
+
self, text: str, wait_state: str | None = None, timeout_ms: int | None = None
|
|
93
|
+
) -> ActionResult: ...
|
|
94
|
+
|
|
95
|
+
async def open_link(self, url: str) -> ActionResult: ...
|
|
96
|
+
|
|
97
|
+
async def erase_one_char(self) -> ActionResult: ...
|
|
98
|
+
|
|
99
|
+
async def focus_and_clear_text(self, nx: int, ny: int) -> ActionResult: ...
|
|
100
|
+
|
|
101
|
+
# --- Internal observation primitives ---------------------------------------------
|
|
102
|
+
|
|
103
|
+
async def take_screenshot(self) -> str:
|
|
104
|
+
"""Returns the current screen as a base64-encoded JPEG/PNG string."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
async def get_ui_elements(self) -> Any:
|
|
108
|
+
"""Returns the raw UI hierarchy elements as provided by the device."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
async def get_screen_data(self) -> Any:
|
|
112
|
+
"""Returns combined screen data (screenshot + elements + dimensions)."""
|
|
113
|
+
...
|