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,183 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
"""Device-control facade base: the upstream ``UnifiedMobileController`` API.
|
|
20
|
+
|
|
21
|
+
Artemis extends it as ``artemis.controllers.unified_controller.UnifiedMobileController``
|
|
22
|
+
with scrcpy video recording and timeline segment extraction.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from artemis.context import ArtemisContext
|
|
28
|
+
from artemis.controllers.device_controller import ScreenDataResponse
|
|
29
|
+
from artemis.drivers.base import BaseDeviceDriver
|
|
30
|
+
from third_party.mobile_use.controllers.types import ElementQuery, TapOutput
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UnifiedMobileControllerBase:
|
|
34
|
+
def __init__(self, ctx: ArtemisContext, driver: BaseDeviceDriver):
|
|
35
|
+
self.ctx = ctx
|
|
36
|
+
self._driver: BaseDeviceDriver = driver
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def driver(self) -> BaseDeviceDriver:
|
|
40
|
+
return self._driver
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def controller(self) -> Any:
|
|
44
|
+
return self._driver
|
|
45
|
+
|
|
46
|
+
async def tap_at(
|
|
47
|
+
self,
|
|
48
|
+
x: int,
|
|
49
|
+
y: int,
|
|
50
|
+
long_press: bool = False,
|
|
51
|
+
long_press_duration: int = 1000,
|
|
52
|
+
times: int = 1,
|
|
53
|
+
delay_ms: int = 100,
|
|
54
|
+
) -> TapOutput:
|
|
55
|
+
try:
|
|
56
|
+
if long_press:
|
|
57
|
+
success = await self._driver.long_press(x, y, duration_ms=long_press_duration)
|
|
58
|
+
else:
|
|
59
|
+
success = await self._driver.tap(
|
|
60
|
+
x, y, duration_ms=100, times=times, delay_ms=delay_ms
|
|
61
|
+
)
|
|
62
|
+
return TapOutput(error=None if success else f"Tap failed at ({x}, {y})")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return TapOutput(error=str(e))
|
|
65
|
+
|
|
66
|
+
async def tap_percentage(
|
|
67
|
+
self,
|
|
68
|
+
x_percent: int,
|
|
69
|
+
y_percent: int,
|
|
70
|
+
long_press: bool = False,
|
|
71
|
+
long_press_duration: int = 1000,
|
|
72
|
+
) -> TapOutput:
|
|
73
|
+
"""Tap at percentage-based coordinates (0 to 100)."""
|
|
74
|
+
norm_x = int(x_percent * 10)
|
|
75
|
+
norm_y = int(y_percent * 10)
|
|
76
|
+
success = await self._driver.tap_normalized(
|
|
77
|
+
norm_x, norm_y, long_press=long_press, duration_ms=long_press_duration
|
|
78
|
+
)
|
|
79
|
+
return TapOutput(
|
|
80
|
+
error=None if success else f"Tap percentage failed at ({x_percent}%, {y_percent}%)"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def tap_element(
|
|
84
|
+
self,
|
|
85
|
+
resource_id: str | None = None,
|
|
86
|
+
text: str | None = None,
|
|
87
|
+
index: int = 0,
|
|
88
|
+
long_press: bool = False,
|
|
89
|
+
long_press_duration: int = 1000,
|
|
90
|
+
) -> TapOutput:
|
|
91
|
+
"""Tap on a UI element by finding it in the hierarchy."""
|
|
92
|
+
query = ElementQuery(resource_id=resource_id, text=text, index=index)
|
|
93
|
+
success = await self._driver.tap_element(
|
|
94
|
+
query, long_press=long_press, duration_ms=long_press_duration
|
|
95
|
+
)
|
|
96
|
+
return TapOutput(error=None if success else f"Failed to tap element ({query.describe()})")
|
|
97
|
+
|
|
98
|
+
async def swipe_coords(
|
|
99
|
+
self,
|
|
100
|
+
start_x: int,
|
|
101
|
+
start_y: int,
|
|
102
|
+
end_x: int,
|
|
103
|
+
end_y: int,
|
|
104
|
+
duration: int = 400,
|
|
105
|
+
) -> str | None:
|
|
106
|
+
"""Swipe between two coordinate points."""
|
|
107
|
+
success = await self._driver.swipe(start_x, start_y, end_x, end_y, duration_ms=duration)
|
|
108
|
+
return None if success else f"Swipe failed from ({start_x},{start_y}) to ({end_x},{end_y})"
|
|
109
|
+
|
|
110
|
+
async def type_text(self, text: str, clear_existing: bool = True) -> bool:
|
|
111
|
+
return await self._driver.input_text(text, clear_existing=clear_existing)
|
|
112
|
+
|
|
113
|
+
async def take_screenshot(self) -> str:
|
|
114
|
+
screen_data = await self._driver.get_screen_data()
|
|
115
|
+
return screen_data.screenshot_base64
|
|
116
|
+
|
|
117
|
+
async def launch_app(self, package_or_bundle_id: str) -> bool:
|
|
118
|
+
return await self._driver.launch_app(package_or_bundle_id)
|
|
119
|
+
|
|
120
|
+
async def terminate_app(self, package_or_bundle_id: str | None) -> bool:
|
|
121
|
+
if not package_or_bundle_id:
|
|
122
|
+
return False
|
|
123
|
+
return await self._driver.stop_app(package_or_bundle_id)
|
|
124
|
+
|
|
125
|
+
async def open_url(self, url: str) -> bool:
|
|
126
|
+
await self._driver.execute_shell(f"am start -a android.intent.action.VIEW -d '{url}'")
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
async def go_back(self) -> bool:
|
|
130
|
+
return await self._driver.press_key("back")
|
|
131
|
+
|
|
132
|
+
async def go_home(self) -> bool:
|
|
133
|
+
return await self._driver.press_key("home")
|
|
134
|
+
|
|
135
|
+
async def press_enter(self) -> bool:
|
|
136
|
+
return await self._driver.press_key("enter")
|
|
137
|
+
|
|
138
|
+
async def press_key(self, keycode: str) -> bool:
|
|
139
|
+
return await self._driver.press_key(keycode)
|
|
140
|
+
|
|
141
|
+
async def erase_text(self, nb_chars: int | None = None) -> bool:
|
|
142
|
+
if nb_chars is not None and nb_chars > 0:
|
|
143
|
+
for _ in range(nb_chars):
|
|
144
|
+
await self._driver.press_key("delete")
|
|
145
|
+
return True
|
|
146
|
+
# Best-effort full clear: End -> Ctrl+A -> Delete
|
|
147
|
+
try:
|
|
148
|
+
clear_cmd = (
|
|
149
|
+
"input keyevent 123 && "
|
|
150
|
+
"input keycombination 113 29 && input keyevent 67 && "
|
|
151
|
+
"input keyevent 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67"
|
|
152
|
+
)
|
|
153
|
+
await self._driver.execute_shell(clear_cmd)
|
|
154
|
+
except Exception:
|
|
155
|
+
for _ in range(30):
|
|
156
|
+
await self._driver.press_key("delete")
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
async def get_ui_elements(self) -> list[dict]:
|
|
160
|
+
screen_data = await self._driver.get_screen_data()
|
|
161
|
+
return screen_data.ui_elements or []
|
|
162
|
+
|
|
163
|
+
async def get_screen_data(self) -> "ScreenDataResponse":
|
|
164
|
+
"""Get screen data including screenshot, UI hierarchy, dimensions, and platform."""
|
|
165
|
+
data = await self._driver.get_screen_data()
|
|
166
|
+
return ScreenDataResponse(
|
|
167
|
+
base64=data.screenshot_base64,
|
|
168
|
+
elements=data.ui_elements or [],
|
|
169
|
+
width=data.width,
|
|
170
|
+
height=data.height,
|
|
171
|
+
platform=data.platform,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def find_element(
|
|
175
|
+
self,
|
|
176
|
+
resource_id: str | None = None,
|
|
177
|
+
text: str | None = None,
|
|
178
|
+
index: int = 0,
|
|
179
|
+
) -> tuple[dict | None, str | None]:
|
|
180
|
+
elem, _, error = await self._driver.find_element(
|
|
181
|
+
ElementQuery(resource_id=resource_id, text=text, index=index)
|
|
182
|
+
)
|
|
183
|
+
return elem, error
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
"""State reducer and convergence node used when building the agent graph."""
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable, Hashable
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from langgraph.graph import StateGraph
|
|
25
|
+
|
|
26
|
+
CONVERGENCE_NODE = "convergence"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def take_last(a, b):
|
|
30
|
+
"""Reducer function keeping the latest value."""
|
|
31
|
+
return b
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def convergence_node(state: Any):
|
|
35
|
+
"""Convergence point for parallel execution paths."""
|
|
36
|
+
return {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_convergence_node(graph_builder: StateGraph) -> None:
|
|
40
|
+
"""Add the deferred node where parallel branches join."""
|
|
41
|
+
graph_builder.add_node(node=CONVERGENCE_NODE, action=convergence_node, defer=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def add_convergence_edges(
|
|
45
|
+
graph_builder: StateGraph,
|
|
46
|
+
gate: Callable[..., Hashable],
|
|
47
|
+
path_map: dict[Hashable, str],
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Add the conditional edges leaving the convergence node, chosen by ``gate``."""
|
|
50
|
+
graph_builder.add_conditional_edges(
|
|
51
|
+
source=CONVERGENCE_NODE,
|
|
52
|
+
path=gate,
|
|
53
|
+
path_map=path_map,
|
|
54
|
+
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
"""Helpers for LLM calls, from upstream ``services/llm.py``.
|
|
20
|
+
|
|
21
|
+
``LLMWaitNotice`` waits for a call, tells the user when it is slow and enforces
|
|
22
|
+
a hard timeout. ``FallbackRunner`` retries a failed call on a fallback model.
|
|
23
|
+
``GetLLM`` is the type of ``get_llm``.
|
|
24
|
+
|
|
25
|
+
Artemis passes in its own behaviour (pause detection, error classification,
|
|
26
|
+
telemetry); this module does not import artemis at runtime.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
from collections.abc import Awaitable, Callable, Coroutine
|
|
33
|
+
from contextlib import AbstractContextManager, nullcontext
|
|
34
|
+
import logging
|
|
35
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, overload
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
39
|
+
|
|
40
|
+
from artemis.config import AgentNodeWithFallback, LLMUtilsNode, LLMUtilsNodeWithFallback
|
|
41
|
+
from artemis.context import ArtemisContext
|
|
42
|
+
|
|
43
|
+
_logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
WAITING_MESSAGE = "Waiting for LLM call response..."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LLMWaitNotice:
|
|
49
|
+
"""Wait for LLM calls, report slow ones and enforce a hard timeout.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
notify: Called with :data:`WAITING_MESSAGE` once a call outlives its notice delay.
|
|
53
|
+
on_timeout: Called with the timeout message right before ``TimeoutError`` is raised.
|
|
54
|
+
hold: While it returns ``True`` the hard-timeout clock is held at zero
|
|
55
|
+
(e.g. the task is paused by the user).
|
|
56
|
+
default_hard_timeout: Hard timeout in seconds when the call does not pass
|
|
57
|
+
one; ``None`` waits indefinitely.
|
|
58
|
+
poll_interval: Seconds between ``hold``/timeout checks after the notice.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
notify: Callable[[str], object],
|
|
65
|
+
on_timeout: Callable[[str], object],
|
|
66
|
+
hold: Callable[[], bool] = lambda: False,
|
|
67
|
+
default_hard_timeout: float | None = None,
|
|
68
|
+
poll_interval: float = 1.0,
|
|
69
|
+
) -> None:
|
|
70
|
+
self._notify = notify
|
|
71
|
+
self._on_timeout = on_timeout
|
|
72
|
+
self._hold = hold
|
|
73
|
+
self._default_hard_timeout = default_hard_timeout
|
|
74
|
+
self._poll_interval = poll_interval
|
|
75
|
+
|
|
76
|
+
async def __call__[T](
|
|
77
|
+
self,
|
|
78
|
+
llm_call: Coroutine[Any, Any, T],
|
|
79
|
+
timeout_seconds: float = 10,
|
|
80
|
+
hard_timeout: float | None = None,
|
|
81
|
+
) -> T:
|
|
82
|
+
"""Run ``llm_call``; show a notice after ``timeout_seconds``.
|
|
83
|
+
|
|
84
|
+
The call is cancelled if the caller is cancelled or the hard timeout
|
|
85
|
+
(counted from the start of the call) expires.
|
|
86
|
+
"""
|
|
87
|
+
if hard_timeout is None:
|
|
88
|
+
hard_timeout = self._default_hard_timeout
|
|
89
|
+
llm_task = asyncio.create_task(llm_call)
|
|
90
|
+
try:
|
|
91
|
+
# asyncio.wait never raises on timeout and needs no timer task.
|
|
92
|
+
done, _ = await asyncio.wait({llm_task}, timeout=timeout_seconds)
|
|
93
|
+
if llm_task in done:
|
|
94
|
+
return llm_task.result()
|
|
95
|
+
|
|
96
|
+
self._notify(WAITING_MESSAGE)
|
|
97
|
+
loop = asyncio.get_running_loop()
|
|
98
|
+
budget = None if hard_timeout is None else max(0.0, hard_timeout - timeout_seconds)
|
|
99
|
+
start_time = loop.time()
|
|
100
|
+
while True:
|
|
101
|
+
done, _ = await asyncio.wait({llm_task}, timeout=self._poll_interval)
|
|
102
|
+
if llm_task in done:
|
|
103
|
+
return llm_task.result()
|
|
104
|
+
if self._hold():
|
|
105
|
+
start_time = loop.time()
|
|
106
|
+
continue
|
|
107
|
+
if budget is not None and loop.time() - start_time > budget:
|
|
108
|
+
message = f"LLM call timed out after {hard_timeout} seconds."
|
|
109
|
+
self._on_timeout(message)
|
|
110
|
+
raise TimeoutError(message)
|
|
111
|
+
except BaseException:
|
|
112
|
+
if not llm_task.done():
|
|
113
|
+
llm_task.cancel()
|
|
114
|
+
await asyncio.gather(llm_task, return_exceptions=True)
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class FallbackRunner:
|
|
119
|
+
"""Runs a main LLM call and switches to a fallback call when it fails.
|
|
120
|
+
|
|
121
|
+
The base class falls back on every ``Exception`` and on a ``None`` result.
|
|
122
|
+
Subclasses narrow that down and report switches by overriding the hooks
|
|
123
|
+
:meth:`main_scope`, :meth:`fallback_reason` and :meth:`on_fallback`.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def main_scope(self) -> AbstractContextManager[object]:
|
|
127
|
+
"""Context active while the main call runs; exited before the fallback starts."""
|
|
128
|
+
return nullcontext()
|
|
129
|
+
|
|
130
|
+
def fallback_reason(self, error: Exception) -> tuple[str, str | None] | None:
|
|
131
|
+
"""``(reason, category)`` if ``error`` should trigger the fallback, ``None`` to re-raise."""
|
|
132
|
+
return "error", None
|
|
133
|
+
|
|
134
|
+
def on_fallback(self, reason: str, category: str | None, error: str | None) -> None:
|
|
135
|
+
"""Report a switch to the fallback call."""
|
|
136
|
+
detail = f": {error}" if error else ""
|
|
137
|
+
_logger.warning(f"❗ Main LLM inference failed ({reason}{detail}). Falling back...")
|
|
138
|
+
|
|
139
|
+
async def run[T](
|
|
140
|
+
self,
|
|
141
|
+
main_call: Callable[[], Awaitable[T]],
|
|
142
|
+
fallback_call: Callable[[], Awaitable[T]],
|
|
143
|
+
none_should_fallback: bool = True,
|
|
144
|
+
) -> T:
|
|
145
|
+
try:
|
|
146
|
+
with self.main_scope():
|
|
147
|
+
result = await main_call()
|
|
148
|
+
except Exception as e:
|
|
149
|
+
decision = self.fallback_reason(e)
|
|
150
|
+
if decision is None:
|
|
151
|
+
raise
|
|
152
|
+
self.on_fallback(decision[0], decision[1], str(e))
|
|
153
|
+
return await fallback_call()
|
|
154
|
+
|
|
155
|
+
if result is None and none_should_fallback:
|
|
156
|
+
self.on_fallback("empty_result", None, None)
|
|
157
|
+
return await fallback_call()
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class GetLLM(Protocol):
|
|
162
|
+
"""Call signature of ``get_llm``: resolve the chat model configured for a node."""
|
|
163
|
+
|
|
164
|
+
@overload
|
|
165
|
+
def __call__(
|
|
166
|
+
self,
|
|
167
|
+
ctx: ArtemisContext,
|
|
168
|
+
name: AgentNodeWithFallback,
|
|
169
|
+
*,
|
|
170
|
+
use_fallback: bool = False,
|
|
171
|
+
temperature: float | None = None,
|
|
172
|
+
) -> BaseChatModel: ...
|
|
173
|
+
|
|
174
|
+
@overload
|
|
175
|
+
def __call__(
|
|
176
|
+
self,
|
|
177
|
+
ctx: ArtemisContext,
|
|
178
|
+
name: LLMUtilsNode,
|
|
179
|
+
*,
|
|
180
|
+
is_utils: Literal[True],
|
|
181
|
+
temperature: float | None = None,
|
|
182
|
+
) -> BaseChatModel: ...
|
|
183
|
+
|
|
184
|
+
@overload
|
|
185
|
+
def __call__(
|
|
186
|
+
self,
|
|
187
|
+
ctx: ArtemisContext,
|
|
188
|
+
name: LLMUtilsNodeWithFallback,
|
|
189
|
+
*,
|
|
190
|
+
is_utils: Literal[True],
|
|
191
|
+
use_fallback: bool = False,
|
|
192
|
+
temperature: float | None = None,
|
|
193
|
+
) -> BaseChatModel: ...
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
"""Single-task automation driver (upstream ``run_automation``).
|
|
20
|
+
|
|
21
|
+
Used by the ``artemis run`` CLI command in ``artemis.interfaces.cli.commands.run``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Annotated
|
|
27
|
+
|
|
28
|
+
import typer
|
|
29
|
+
|
|
30
|
+
from artemis import Agent, Builders
|
|
31
|
+
from artemis.config import initialize_llm_config, settings
|
|
32
|
+
from artemis.sdk.builders.agent_config_builder import AgentConfigBuilder
|
|
33
|
+
from third_party.mobile_use.sdk.types.task import AgentProfile
|
|
34
|
+
from third_party.mobile_use.utils.video import check_ffmpeg_available
|
|
35
|
+
|
|
36
|
+
# CLI parameters of the ``run`` command.
|
|
37
|
+
GoalArgument = Annotated[str, typer.Argument(help="The main goal for the agent to achieve.")]
|
|
38
|
+
TestNameOption = Annotated[
|
|
39
|
+
str | None,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--test-name",
|
|
42
|
+
"-n",
|
|
43
|
+
help="Name of the test run for trace recording.",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
TracesPathOption = Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
typer.Option(
|
|
49
|
+
"--traces-path",
|
|
50
|
+
"-t",
|
|
51
|
+
help="Directory where execution traces are persisted.",
|
|
52
|
+
),
|
|
53
|
+
]
|
|
54
|
+
OutputDescriptionOption = Annotated[
|
|
55
|
+
str | None,
|
|
56
|
+
typer.Option(
|
|
57
|
+
"--output-description",
|
|
58
|
+
"-o",
|
|
59
|
+
help="Natural language or schema description of the expected output.",
|
|
60
|
+
),
|
|
61
|
+
]
|
|
62
|
+
VideoRecordingToolsOption = Annotated[
|
|
63
|
+
bool | None,
|
|
64
|
+
typer.Option(
|
|
65
|
+
"--with-video-recording-tools/--without-video-recording-tools",
|
|
66
|
+
help=(
|
|
67
|
+
"Enable or disable dynamic video recording and screen analysis "
|
|
68
|
+
"tools (auto-detected if omitted)."
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def ensure_video_recording_available(with_video_recording_tools: bool | None) -> None:
|
|
75
|
+
"""Fail early when video recording tools are requested but ffmpeg is missing."""
|
|
76
|
+
if with_video_recording_tools:
|
|
77
|
+
check_ffmpeg_available()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def new_default_config_builder() -> AgentConfigBuilder:
|
|
81
|
+
"""Return a config builder set up with the default LLM profile and ADB server."""
|
|
82
|
+
llm_config = initialize_llm_config()
|
|
83
|
+
agent_profile = AgentProfile(name="default", llm_config=llm_config)
|
|
84
|
+
config = Builders.AgentConfig.with_default_profile(profile=agent_profile)
|
|
85
|
+
if settings.ADB_HOST:
|
|
86
|
+
config.with_adb_server(host=settings.ADB_HOST, port=settings.ADB_PORT)
|
|
87
|
+
return config
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def run_automation(
|
|
91
|
+
config: AgentConfigBuilder,
|
|
92
|
+
goal: str,
|
|
93
|
+
session_id: str | None = None,
|
|
94
|
+
locked_app_package: str | None = None,
|
|
95
|
+
test_name: str | None = None,
|
|
96
|
+
traces_output_path_str: str | None = None,
|
|
97
|
+
output_description: str | None = None,
|
|
98
|
+
profile: str | None = None,
|
|
99
|
+
app_path: str | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Initialize an agent from ``config``, run ``goal`` as a single task, then clean up."""
|
|
102
|
+
agent: Agent | None = None
|
|
103
|
+
try:
|
|
104
|
+
agent = Agent(config=config.build(), session_id=session_id)
|
|
105
|
+
await agent.init(
|
|
106
|
+
retry_count=int(os.getenv("ARTEMIS_HEALTH_RETRIES", 5)),
|
|
107
|
+
retry_wait_seconds=int(os.getenv("ARTEMIS_HEALTH_DELAY", 2)),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
task = agent.new_task(goal)
|
|
111
|
+
if locked_app_package:
|
|
112
|
+
task.with_locked_app_package(locked_app_package)
|
|
113
|
+
if test_name:
|
|
114
|
+
trace_path = traces_output_path_str or str(settings.TRACES_PATH)
|
|
115
|
+
task.with_name(test_name).with_trace_recording(path=trace_path)
|
|
116
|
+
if output_description:
|
|
117
|
+
task.with_output_description(output_description)
|
|
118
|
+
if profile:
|
|
119
|
+
task.using_profile(profile)
|
|
120
|
+
if app_path:
|
|
121
|
+
task.with_app_path(Path(app_path))
|
|
122
|
+
|
|
123
|
+
llm_result_path = os.getenv("RESULTS_OUTPUT_PATH", None)
|
|
124
|
+
if llm_result_path:
|
|
125
|
+
task.with_llm_output_saving(path=llm_result_path)
|
|
126
|
+
|
|
127
|
+
await agent.run_task(request=task.build())
|
|
128
|
+
finally:
|
|
129
|
+
if agent is not None:
|
|
130
|
+
await agent.clean()
|
|
@@ -0,0 +1,13 @@
|
|
|
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.
|