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,269 @@
|
|
|
1
|
+
"""Comprehensive Edge-Case Test Suite for Artemis Multi-Device Concurrency & Unified Queue.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
1. Single Device FIFO Serial Queueing & Relay
|
|
5
|
+
2. Multi-Device True Parallel Execution
|
|
6
|
+
3. Global Concurrency Serialization across Distinct Devices
|
|
7
|
+
4. Mixed Multi-Device Queueing
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
# Ensure workspace in path
|
|
17
|
+
WORKSPACE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
18
|
+
if WORKSPACE_ROOT not in sys.path:
|
|
19
|
+
sys.path.insert(0, WORKSPACE_ROOT)
|
|
20
|
+
|
|
21
|
+
from artemis.interfaces.sdk.client import ArtemisClient
|
|
22
|
+
from artemis.runtime.device_lock import DeviceExecutionLock
|
|
23
|
+
from artemis.toolchain import ensure_toolchain_in_path
|
|
24
|
+
|
|
25
|
+
ensure_toolchain_in_path()
|
|
26
|
+
|
|
27
|
+
from artemis.runtime import device_pool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _resolve_devices() -> tuple[str, str]:
|
|
31
|
+
all_devs = device_pool.list_devices()
|
|
32
|
+
real_dev = next((d for d in all_devs if not d.is_emulator and d.state == "device"), None)
|
|
33
|
+
emu_dev = next((d for d in all_devs if d.is_emulator and d.state == "device"), None)
|
|
34
|
+
phone = os.environ.get("ARTEMIS_PHONE_SERIAL") or (
|
|
35
|
+
real_dev.serial if real_dev else "63191FDKX00062"
|
|
36
|
+
)
|
|
37
|
+
emu = os.environ.get("ARTEMIS_EMULATOR_SERIAL") or (
|
|
38
|
+
emu_dev.serial if emu_dev else "emulator-5554"
|
|
39
|
+
)
|
|
40
|
+
return phone, emu
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
PHONE_SERIAL, EMULATOR_SERIAL = _resolve_devices()
|
|
44
|
+
API_STATUS_URL = "http://127.0.0.1:8000/api/status"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def query_api_status():
|
|
48
|
+
try:
|
|
49
|
+
res = requests.get(API_STATUS_URL, timeout=3).json()
|
|
50
|
+
active = res.get("active_tasks", [])
|
|
51
|
+
queue = res.get("queue", [])
|
|
52
|
+
return active, queue
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return [], []
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def print_status_snapshot(stage_name: str):
|
|
58
|
+
active, queue = query_api_status()
|
|
59
|
+
print(f"\n[{stage_name}] Live Snapshot from Web Console (/api/status):")
|
|
60
|
+
print(f" ⚡ Running Tasks ({len(active)}):")
|
|
61
|
+
for a in active:
|
|
62
|
+
print(f" - Device: {a.get('device_id')} | Goal: {a.get('goal')} (PID: {a.get('pid')})")
|
|
63
|
+
print(f" ⏳ Queued Tasks ({len(queue)}):")
|
|
64
|
+
for q in queue:
|
|
65
|
+
dev = q.get("device_serial") or q.get("device_id")
|
|
66
|
+
print(f" - Device: {dev} | Goal: {q.get('goal')} [status={q.get('status')}]")
|
|
67
|
+
print(f" 📊 Total in Active Queue (Running + Pending) = {len(active) + len(queue)}\n")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def stage_1_single_device_queueing():
|
|
71
|
+
print("\n" + "=" * 80)
|
|
72
|
+
print("STAGE 1: Single Device FIFO Queueing & Relay")
|
|
73
|
+
print(f"Target Device: {PHONE_SERIAL} (Pixel 11 Pro)")
|
|
74
|
+
print("=" * 80)
|
|
75
|
+
|
|
76
|
+
client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
|
|
77
|
+
|
|
78
|
+
t1_started = False
|
|
79
|
+
t2_queued = False
|
|
80
|
+
|
|
81
|
+
async def task_1a():
|
|
82
|
+
print(f"🚀 [Task 1A] Launching on {PHONE_SERIAL}: Press HOME and open Settings...")
|
|
83
|
+
res = await client_phone.run("Press HOME key and open Settings", profile="flash")
|
|
84
|
+
print(f"✅ [Task 1A] Finished with status: {res.status}")
|
|
85
|
+
return res
|
|
86
|
+
|
|
87
|
+
async def task_1b():
|
|
88
|
+
# Small delay so 1A is definitely acquired first
|
|
89
|
+
await asyncio.sleep(0.4)
|
|
90
|
+
print(
|
|
91
|
+
f"🚀 [Task 1B] Launching on {PHONE_SERIAL}: Press HOME and open Clock (Should queue!)..."
|
|
92
|
+
)
|
|
93
|
+
res = await client_phone.run("Press HOME key and open Clock", profile="flash")
|
|
94
|
+
print(f"✅ [Task 1B] Finished with status: {res.status}")
|
|
95
|
+
return res
|
|
96
|
+
|
|
97
|
+
# Run both simultaneously
|
|
98
|
+
task_a_coro = asyncio.create_task(task_1a())
|
|
99
|
+
task_b_coro = asyncio.create_task(task_1b())
|
|
100
|
+
|
|
101
|
+
# Wait 1.5s and inspect status while 1A is running and 1B is queued
|
|
102
|
+
await asyncio.sleep(1.2)
|
|
103
|
+
print_status_snapshot("STAGE 1 In-Progress")
|
|
104
|
+
active, queue = query_api_status()
|
|
105
|
+
assert len(active) >= 1, "Expected Task 1A to be actively running!"
|
|
106
|
+
# 1B should be in the queue or 1A just finished
|
|
107
|
+
print(f"✓ Verified: Task 1A is running on {PHONE_SERIAL}")
|
|
108
|
+
|
|
109
|
+
res_a, res_b = await asyncio.gather(task_a_coro, task_b_coro)
|
|
110
|
+
print("✓ Stage 1 Complete: Both tasks executed in order and completed successfully!")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def stage_2_multi_device_parallel():
|
|
114
|
+
print("\n" + "=" * 80)
|
|
115
|
+
print("STAGE 2: Multi-Device True Parallel Execution")
|
|
116
|
+
print(f"Devices: {PHONE_SERIAL} (Pixel 11 Pro) AND {EMULATOR_SERIAL} (Emulator)")
|
|
117
|
+
print("Concurrency Mode: per_device (Should run simultaneously!)")
|
|
118
|
+
print("=" * 80)
|
|
119
|
+
|
|
120
|
+
client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
|
|
121
|
+
client_emu = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="per_device")
|
|
122
|
+
|
|
123
|
+
start_time = time.time()
|
|
124
|
+
|
|
125
|
+
async def task_phone():
|
|
126
|
+
print(f"📱 [Phone Task] Running on {PHONE_SERIAL}: Open Settings...")
|
|
127
|
+
res = await client_phone.run("Press HOME key and open Settings", profile="flash")
|
|
128
|
+
print(f"✅ [Phone Task] Completed in {time.time() - start_time:.2f}s")
|
|
129
|
+
return res
|
|
130
|
+
|
|
131
|
+
async def task_emu():
|
|
132
|
+
print(f"💻 [Emulator Task] Running on {EMULATOR_SERIAL}: Open Settings...")
|
|
133
|
+
res = await client_emu.run("Press HOME key and open Settings", profile="flash")
|
|
134
|
+
print(f"✅ [Emulator Task] Completed in {time.time() - start_time:.2f}s")
|
|
135
|
+
return res
|
|
136
|
+
|
|
137
|
+
p_task = asyncio.create_task(task_phone())
|
|
138
|
+
e_task = asyncio.create_task(task_emu())
|
|
139
|
+
|
|
140
|
+
# Check during run
|
|
141
|
+
await asyncio.sleep(1.2)
|
|
142
|
+
print_status_snapshot("STAGE 2 Parallel In-Progress")
|
|
143
|
+
active, queue = query_api_status()
|
|
144
|
+
print(f"Active devices count: {len(active)}")
|
|
145
|
+
active_devices = {a.get("device_id") for a in active}
|
|
146
|
+
print(f"Active devices: {active_devices}")
|
|
147
|
+
|
|
148
|
+
await asyncio.gather(p_task, e_task)
|
|
149
|
+
total_duration = time.time() - start_time
|
|
150
|
+
print(f"✓ Stage 2 Complete: Both devices completed parallel tasks in {total_duration:.2f}s!")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def stage_3_global_concurrency_mode():
|
|
154
|
+
print("\n" + "=" * 80)
|
|
155
|
+
print("STAGE 3: Global Concurrency Mode Serialization")
|
|
156
|
+
print("Concurrency Mode: global (max 1 task system-wide even across different devices)")
|
|
157
|
+
print("=" * 80)
|
|
158
|
+
|
|
159
|
+
client_phone_global = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="global")
|
|
160
|
+
client_emu_global = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="global")
|
|
161
|
+
|
|
162
|
+
async def task_phone_g():
|
|
163
|
+
print(f"📱 [Task 3A - Phone] Acquiring global lock on {PHONE_SERIAL}...")
|
|
164
|
+
res = await client_phone_global.run("Press HOME key and open Settings", profile="flash")
|
|
165
|
+
print("✅ [Task 3A - Phone] Finished.")
|
|
166
|
+
return res
|
|
167
|
+
|
|
168
|
+
async def task_emu_g():
|
|
169
|
+
await asyncio.sleep(0.4)
|
|
170
|
+
print(
|
|
171
|
+
f"💻 [Task 3B - Emulator] Requesting run on {EMULATOR_SERIAL} under global mode (Should queue!)..."
|
|
172
|
+
)
|
|
173
|
+
res = await client_emu_global.run("Press HOME key and open Settings", profile="flash")
|
|
174
|
+
print("✅ [Task 3B - Emulator] Finished.")
|
|
175
|
+
return res
|
|
176
|
+
|
|
177
|
+
p_task = asyncio.create_task(task_phone_g())
|
|
178
|
+
e_task = asyncio.create_task(task_emu_g())
|
|
179
|
+
|
|
180
|
+
await asyncio.sleep(1.2)
|
|
181
|
+
print_status_snapshot("STAGE 3 Global Mode In-Progress")
|
|
182
|
+
active, queue = query_api_status()
|
|
183
|
+
print(f"Active count: {len(active)}, Queue count: {len(queue)}")
|
|
184
|
+
|
|
185
|
+
await asyncio.gather(p_task, e_task)
|
|
186
|
+
print(
|
|
187
|
+
"✓ Stage 3 Complete: Global lock correctly serialized execution across physical phone and emulator!"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
async def stage_4_mixed_queue_and_parallel():
|
|
192
|
+
print("\n" + "=" * 80)
|
|
193
|
+
print("STAGE 4: Mixed Multi-Device Queueing")
|
|
194
|
+
print("Scenario: Phone gets 2 tasks (4A, 4B), Emulator gets 1 task (4C).")
|
|
195
|
+
print("Expectation: 4A (Phone) and 4C (Emulator) run concurrently; 4B waits for Phone.")
|
|
196
|
+
print("=" * 80)
|
|
197
|
+
|
|
198
|
+
client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
|
|
199
|
+
client_emu = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="per_device")
|
|
200
|
+
|
|
201
|
+
async def task_4a():
|
|
202
|
+
print("📱 [Task 4A - Phone] Running...")
|
|
203
|
+
return await client_phone.run("Press HOME key and open Settings", profile="flash")
|
|
204
|
+
|
|
205
|
+
async def task_4b():
|
|
206
|
+
await asyncio.sleep(0.4)
|
|
207
|
+
print("📱 [Task 4B - Phone] Queued behind 4A...")
|
|
208
|
+
return await client_phone.run("Press HOME key and open Clock", profile="flash")
|
|
209
|
+
|
|
210
|
+
async def task_4c():
|
|
211
|
+
print("💻 [Task 4C - Emulator] Running in parallel with 4A...")
|
|
212
|
+
return await client_emu.run("Press HOME key and open Settings", profile="flash")
|
|
213
|
+
|
|
214
|
+
t4a = asyncio.create_task(task_4a())
|
|
215
|
+
t4b = asyncio.create_task(task_4b())
|
|
216
|
+
t4c = asyncio.create_task(task_4c())
|
|
217
|
+
|
|
218
|
+
await asyncio.sleep(1.2)
|
|
219
|
+
print_status_snapshot("STAGE 4 Mixed Multi-Device In-Progress")
|
|
220
|
+
|
|
221
|
+
await asyncio.gather(t4a, t4b, t4c)
|
|
222
|
+
print("✓ Stage 4 Complete: 4A and 4C ran in parallel, 4B queued and picked up phone relay!")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def main():
|
|
226
|
+
import argparse
|
|
227
|
+
|
|
228
|
+
global PHONE_SERIAL, EMULATOR_SERIAL
|
|
229
|
+
|
|
230
|
+
parser = argparse.ArgumentParser(description="ARTEMIS All Edge-Cases Comprehensive Test Suite")
|
|
231
|
+
parser.add_argument("--phone", default=None, help="Target physical phone serial number")
|
|
232
|
+
parser.add_argument("--emulator", default=None, help="Target Android emulator serial number")
|
|
233
|
+
args = parser.parse_args()
|
|
234
|
+
|
|
235
|
+
if args.phone:
|
|
236
|
+
PHONE_SERIAL = args.phone
|
|
237
|
+
if args.emulator:
|
|
238
|
+
EMULATOR_SERIAL = args.emulator
|
|
239
|
+
|
|
240
|
+
print("\n" + "#" * 80)
|
|
241
|
+
print("# ARTEMIS ALL EDGE-CASES COMPREHENSIVE CONCURRENCY & QUEUE TEST SUITE")
|
|
242
|
+
print(f"# Devices: Real Phone ({PHONE_SERIAL}) + Android Emulator ({EMULATOR_SERIAL})")
|
|
243
|
+
print("# Web Console API: http://127.0.0.1:8000/api/status")
|
|
244
|
+
print("#" * 80)
|
|
245
|
+
|
|
246
|
+
# Initial cleanup
|
|
247
|
+
DeviceExecutionLock.cleanup_stale_locks()
|
|
248
|
+
print_status_snapshot("Initial System State")
|
|
249
|
+
|
|
250
|
+
# Run all 4 stages
|
|
251
|
+
await stage_1_single_device_queueing()
|
|
252
|
+
await asyncio.sleep(1)
|
|
253
|
+
|
|
254
|
+
await stage_2_multi_device_parallel()
|
|
255
|
+
await asyncio.sleep(1)
|
|
256
|
+
|
|
257
|
+
await stage_3_global_concurrency_mode()
|
|
258
|
+
await asyncio.sleep(1)
|
|
259
|
+
|
|
260
|
+
await stage_4_mixed_queue_and_parallel()
|
|
261
|
+
|
|
262
|
+
print("\n" + "=" * 80)
|
|
263
|
+
print("🎉 ALL EDGE-CASE STAGES COMPLETED SUCCESSFULLY!")
|
|
264
|
+
print("=" * 80)
|
|
265
|
+
print_status_snapshot("Final System State")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2026 Google LLC
|
|
3
|
+
"""Live experiment demonstrating Artemis concurrency modes and device targeting.
|
|
4
|
+
|
|
5
|
+
Covers:
|
|
6
|
+
1. Single-device queueing and relay (FIFO mutual exclusion on the same device).
|
|
7
|
+
2. Multi-device parallel execution (per_device concurrency mode across real device & emulator).
|
|
8
|
+
3. Cross-device global serialization (global concurrency mode restricting all devices to 1 task).
|
|
9
|
+
4. Full parameter coverage (device_serial, set_device, Task objects, list_devices, get_idle_devices).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
# Add project root to sys.path
|
|
19
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
20
|
+
|
|
21
|
+
from artemis import ArtemisClient, ConcurrencyMode, Task
|
|
22
|
+
from artemis.runtime import DeviceBusyError, device_pool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def print_header(title: str):
|
|
26
|
+
print("\n" + "=" * 76)
|
|
27
|
+
print(f" {title}")
|
|
28
|
+
print("=" * 76)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def now_str() -> str:
|
|
32
|
+
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def run_experiment_1_single_device_relay(real_serial: str):
|
|
36
|
+
"""Experiment 1: Single device FIFO queueing & relay."""
|
|
37
|
+
print_header("[Experiment 1] Single-Device Task Queueing & Relay (FIFO Relay on Single Device)")
|
|
38
|
+
print(f"Target Device: {real_serial} (Physical Pixel 11 Pro)")
|
|
39
|
+
|
|
40
|
+
client = ArtemisClient(
|
|
41
|
+
device_serial=real_serial, default_profile="flash", concurrency_mode="per_device"
|
|
42
|
+
)
|
|
43
|
+
print(
|
|
44
|
+
f"Client Init: device_serial='{client.device_serial}', concurrency_mode='{client.concurrency_mode}'"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
task_1_launched = 0.0
|
|
48
|
+
task_1_start = 0.0
|
|
49
|
+
task_1_end = 0.0
|
|
50
|
+
|
|
51
|
+
task_2_launched = 0.0
|
|
52
|
+
task_2_start = 0.0
|
|
53
|
+
task_2_end = 0.0
|
|
54
|
+
|
|
55
|
+
async def execute_task_1():
|
|
56
|
+
nonlocal task_1_launched, task_1_start, task_1_end
|
|
57
|
+
task_1_launched = time.time()
|
|
58
|
+
print(f"[{now_str()}] ▶ [Task 1] Submitted for scheduling (target device: {real_serial})")
|
|
59
|
+
task_1_start = time.time()
|
|
60
|
+
res = await client.run(
|
|
61
|
+
"Open Settings and check battery level",
|
|
62
|
+
device_serial=real_serial,
|
|
63
|
+
max_turns=3,
|
|
64
|
+
)
|
|
65
|
+
task_1_end = time.time()
|
|
66
|
+
print(
|
|
67
|
+
f"[{now_str()}] ◀ [Task 1] Execution completed! Status={res.status}, "
|
|
68
|
+
f"Trace={res.trace_id[:8]}..., Elapsed={task_1_end - task_1_start:.3f}s"
|
|
69
|
+
)
|
|
70
|
+
return res
|
|
71
|
+
|
|
72
|
+
async def execute_task_2():
|
|
73
|
+
nonlocal task_2_launched, task_2_start, task_2_end
|
|
74
|
+
task_2_launched = time.time()
|
|
75
|
+
print(
|
|
76
|
+
f"[{now_str()}] ▶ [Task 2] Submitted concurrently (detects device occupied by Task 1, entering FIFO queue...)"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
task_2_start = time.time()
|
|
80
|
+
res = await client.run(
|
|
81
|
+
"Return to home screen and open Clock",
|
|
82
|
+
device_serial=real_serial,
|
|
83
|
+
max_turns=3,
|
|
84
|
+
)
|
|
85
|
+
task_2_end = time.time()
|
|
86
|
+
print(
|
|
87
|
+
f"[{now_str()}] ◀ [Task 2] Relayed execution completed! Status={res.status}, "
|
|
88
|
+
f"Trace={res.trace_id[:8]}..., Elapsed={task_2_end - task_2_start:.3f}s"
|
|
89
|
+
)
|
|
90
|
+
return res
|
|
91
|
+
|
|
92
|
+
# Launch Task 1 first, then Task 2 40ms later while Task 1 is holding the lock
|
|
93
|
+
t_coro_1 = asyncio.create_task(execute_task_1())
|
|
94
|
+
await asyncio.sleep(0.04)
|
|
95
|
+
t_coro_2 = asyncio.create_task(execute_task_2())
|
|
96
|
+
res1, res2 = await asyncio.gather(t_coro_1, t_coro_2)
|
|
97
|
+
|
|
98
|
+
print("\n[Timing & Relay Analysis]:")
|
|
99
|
+
print(
|
|
100
|
+
f" Task 1 runtime interval: [{task_1_start:.3f}s ~ {task_1_end:.3f}s] (elapsed {task_1_end - task_1_start:.3f}s)"
|
|
101
|
+
)
|
|
102
|
+
print(f" Task 2 submission time: {task_2_launched:.3f}s")
|
|
103
|
+
print(f" Task 2 actual start: {task_2_start:.3f}s (waited for Task 1 lock release)")
|
|
104
|
+
print(f" Task 2 end time: {task_2_end:.3f}s (elapsed {task_2_end - task_2_start:.3f}s)")
|
|
105
|
+
|
|
106
|
+
assert task_2_end > task_1_end, "Task 2 did not complete after Task 1!"
|
|
107
|
+
assert task_2_end - task_1_start >= (task_1_end - task_1_start) + 0.1, (
|
|
108
|
+
"Tasks failed to execute in sequential queue!"
|
|
109
|
+
)
|
|
110
|
+
print(
|
|
111
|
+
" ✅ Verification successful: Task 2 queued properly and relayed immediately after Task 1 released the lock!"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def run_experiment_2_multi_device_parallel(real_serial: str, emu_serial: str):
|
|
116
|
+
"""Experiment 2: Multi-device parallel execution under per_device concurrency."""
|
|
117
|
+
print_header("[Experiment 2] Multi-Device Parallel Execution Test")
|
|
118
|
+
print(f"Device A (Physical): {real_serial}")
|
|
119
|
+
print(f"Device B (Emulator): {emu_serial}")
|
|
120
|
+
|
|
121
|
+
client_a = ArtemisClient(
|
|
122
|
+
device_serial=real_serial, default_profile="flash", concurrency_mode="per_device"
|
|
123
|
+
)
|
|
124
|
+
client_b = ArtemisClient(
|
|
125
|
+
device_serial=emu_serial, default_profile="flash", concurrency_mode="per_device"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
a_start, a_end = 0.0, 0.0
|
|
129
|
+
b_start, b_end = 0.0, 0.0
|
|
130
|
+
|
|
131
|
+
async def run_on_device_a():
|
|
132
|
+
nonlocal a_start, a_end
|
|
133
|
+
print(f"[{now_str()}] ▶ [Device A Task] Starting on {real_serial}")
|
|
134
|
+
a_start = time.time()
|
|
135
|
+
res = await client_a.run("Test operation A on physical device", max_turns=3)
|
|
136
|
+
a_end = time.time()
|
|
137
|
+
print(
|
|
138
|
+
f"[{now_str()}] ◀ [Device A Task] Completed! Elapsed: {a_end - a_start:.3f}s, Device: {res.device_id}"
|
|
139
|
+
)
|
|
140
|
+
return res
|
|
141
|
+
|
|
142
|
+
async def run_on_device_b():
|
|
143
|
+
nonlocal b_start, b_end
|
|
144
|
+
print(f"[{now_str()}] ▶ [Device B Task] Starting on {emu_serial}")
|
|
145
|
+
b_start = time.time()
|
|
146
|
+
res = await client_b.run("Test operation B on emulator", max_turns=3)
|
|
147
|
+
b_end = time.time()
|
|
148
|
+
print(
|
|
149
|
+
f"[{now_str()}] ◀ [Device B Task] Completed! Elapsed: {b_end - b_start:.3f}s, Device: {res.device_id}"
|
|
150
|
+
)
|
|
151
|
+
return res
|
|
152
|
+
|
|
153
|
+
t_start = time.time()
|
|
154
|
+
task_a = asyncio.create_task(run_on_device_a())
|
|
155
|
+
task_b = asyncio.create_task(run_on_device_b())
|
|
156
|
+
res_a, res_b = await asyncio.gather(task_a, task_b)
|
|
157
|
+
t_total = time.time() - t_start
|
|
158
|
+
|
|
159
|
+
print("\n[Dual-Device Concurrency Analysis]:")
|
|
160
|
+
print(
|
|
161
|
+
f" Device A execution interval: {a_start:.3f}s ~ {a_end:.3f}s (elapsed {a_end - a_start:.3f}s)"
|
|
162
|
+
)
|
|
163
|
+
print(
|
|
164
|
+
f" Device B execution interval: {b_start:.3f}s ~ {b_end:.3f}s (elapsed {b_end - b_start:.3f}s)"
|
|
165
|
+
)
|
|
166
|
+
print(
|
|
167
|
+
f" Total concurrent duration: {t_total:.3f}s (sum of sequential durations: {(a_end - a_start) + (b_end - b_start):.3f}s)"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Overlap verification
|
|
171
|
+
overlap = min(a_end, b_end) - max(a_start, b_start)
|
|
172
|
+
print(f" Parallel overlap duration: {overlap:.3f}s")
|
|
173
|
+
assert overlap > 0.0, "Dual devices failed to run concurrently with overlap!"
|
|
174
|
+
assert t_total < ((a_end - a_start) + (b_end - b_start) * 0.95), (
|
|
175
|
+
"Total duration should be less than sum of both tasks!"
|
|
176
|
+
)
|
|
177
|
+
print(" ✅ Verification successful: Tasks on both devices ran concurrently without blocking!")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def run_experiment_3_global_concurrency_serialization(real_serial: str, emu_serial: str):
|
|
181
|
+
"""Experiment 3: Global concurrency serialization across devices."""
|
|
182
|
+
print_header("[Experiment 3] Global Concurrency Mode Test")
|
|
183
|
+
print(
|
|
184
|
+
"Mode description: concurrency_mode='global', entire system allows only 1 task across all devices."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
client_a = ArtemisClient(
|
|
188
|
+
device_serial=real_serial, default_profile="flash", concurrency_mode="global"
|
|
189
|
+
)
|
|
190
|
+
client_b = ArtemisClient(
|
|
191
|
+
device_serial=emu_serial, default_profile="flash", concurrency_mode="global"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# 3.1 Test non-blocking mode rejection
|
|
195
|
+
print("\n[Subtest 3.1: Non-blocking rejection limit]")
|
|
196
|
+
from artemis.runtime import DeviceExecutionLock
|
|
197
|
+
|
|
198
|
+
lock_a = DeviceExecutionLock(real_serial, "Placeholder test lock", concurrency_mode="global")
|
|
199
|
+
await asyncio.to_thread(lock_a.acquire)
|
|
200
|
+
try:
|
|
201
|
+
print(f"[{now_str()}] Device A ({real_serial}) holds the global lock...")
|
|
202
|
+
try:
|
|
203
|
+
print(f"[{now_str()}] Device B ({emu_serial}) attempts run with blocking=False...")
|
|
204
|
+
await client_b.run("Emulator non-blocking task", blocking=False)
|
|
205
|
+
print(" ❌ Exception: Was not intercepted by global lock!")
|
|
206
|
+
except DeviceBusyError as exc:
|
|
207
|
+
print(f" ✅ Correctly intercepted with DeviceBusyError: {exc}")
|
|
208
|
+
finally:
|
|
209
|
+
await asyncio.to_thread(lock_a.release)
|
|
210
|
+
|
|
211
|
+
# 3.2 Test cross-device queueing and relay
|
|
212
|
+
print("\n[Subtest 3.2: Cross-device global queueing & relay]")
|
|
213
|
+
t1_submitted, t1_end = 0.0, 0.0
|
|
214
|
+
t2_submitted, t2_end = 0.0, 0.0
|
|
215
|
+
|
|
216
|
+
async def run_global_task_1():
|
|
217
|
+
nonlocal t1_submitted, t1_end
|
|
218
|
+
t1_submitted = time.time()
|
|
219
|
+
print(f"[{now_str()}] ▶ [Global Task 1] Started on physical device ({real_serial})...")
|
|
220
|
+
res = await client_a.run("Global task 1", max_turns=3)
|
|
221
|
+
t1_end = time.time()
|
|
222
|
+
print(f"[{now_str()}] ◀ [Global Task 1] Completed! Elapsed: {t1_end - t1_submitted:.3f}s")
|
|
223
|
+
return res
|
|
224
|
+
|
|
225
|
+
async def run_global_task_2():
|
|
226
|
+
nonlocal t2_submitted, t2_end
|
|
227
|
+
# Submit task 2 shortly after task 1
|
|
228
|
+
await asyncio.sleep(0.04)
|
|
229
|
+
t2_submitted = time.time()
|
|
230
|
+
print(
|
|
231
|
+
f"[{now_str()}] ▶ [Global Task 2] Submitted to emulator ({emu_serial}), entering queue due to global limit..."
|
|
232
|
+
)
|
|
233
|
+
res = await client_b.run("Global task 2", max_turns=3)
|
|
234
|
+
t2_end = time.time()
|
|
235
|
+
print(
|
|
236
|
+
f"[{now_str()}] ◀ [Global Task 2] Relayed completion! Total time (including queue): {t2_end - t2_submitted:.3f}s"
|
|
237
|
+
)
|
|
238
|
+
return res
|
|
239
|
+
|
|
240
|
+
res1, res2 = await asyncio.gather(run_global_task_1(), run_global_task_2())
|
|
241
|
+
print(
|
|
242
|
+
f" Global Task 1 interval: {t1_submitted:.3f}s ~ {t1_end:.3f}s (elapsed {t1_end - t1_submitted:.3f}s)"
|
|
243
|
+
)
|
|
244
|
+
print(f" Global Task 2 submit time: {t2_submitted:.3f}s")
|
|
245
|
+
print(
|
|
246
|
+
f" Global Task 2 end time: {t2_end:.3f}s (waited for Task 1 completion before executing)"
|
|
247
|
+
)
|
|
248
|
+
assert t2_end > t1_end, "Global Task 2 did not complete after Global Task 1!"
|
|
249
|
+
assert (t2_end - t1_end) >= 0.08, (
|
|
250
|
+
"Global Task 2 did not execute sequentially after Task 1 released global lock!"
|
|
251
|
+
)
|
|
252
|
+
print(
|
|
253
|
+
" ✅ Verification successful: Under global mode, strictly 1 task executed sequentially across different devices!"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
async def run_experiment_4_parameters_and_utilities(real_serial: str, emu_serial: str):
|
|
258
|
+
"""Experiment 4: Comprehensive SDK parameter and utility testing."""
|
|
259
|
+
print_header("[Experiment 4] SDK Parameter & Utility Comprehensive Test")
|
|
260
|
+
|
|
261
|
+
client = ArtemisClient()
|
|
262
|
+
print(f"1. Default idle device selection: client.device_serial = '{client.device_serial}'")
|
|
263
|
+
|
|
264
|
+
# Chain switch device
|
|
265
|
+
client.set_device(real_serial)
|
|
266
|
+
print(
|
|
267
|
+
f"2. Chain switch to physical device: client.set_device('{real_serial}') -> '{client.device_serial}'"
|
|
268
|
+
)
|
|
269
|
+
assert client.device_serial == real_serial
|
|
270
|
+
|
|
271
|
+
# Attribute switch
|
|
272
|
+
client.device_serial = emu_serial
|
|
273
|
+
print(
|
|
274
|
+
f"3. Property assignment to emulator: client.device_serial = '{emu_serial}' -> '{client.device_id}'"
|
|
275
|
+
)
|
|
276
|
+
assert client.device_id == emu_serial
|
|
277
|
+
|
|
278
|
+
# Dynamically change concurrency mode
|
|
279
|
+
client.set_concurrency_mode(ConcurrencyMode.GLOBAL)
|
|
280
|
+
print(
|
|
281
|
+
f"4. Chain change concurrency mode: client.concurrency_mode = '{client.concurrency_mode}'"
|
|
282
|
+
)
|
|
283
|
+
assert client.concurrency_mode == "global"
|
|
284
|
+
client.concurrency_mode = "per_device"
|
|
285
|
+
|
|
286
|
+
# Query all available devices
|
|
287
|
+
print("5. client.list_devices() status:")
|
|
288
|
+
devices = client.list_devices()
|
|
289
|
+
for d in devices:
|
|
290
|
+
print(
|
|
291
|
+
f" • [{d.serial}] State: {d.state}, Model: {d.model}, IsEmulator: {d.is_emulator}, Busy: {d.is_busy}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Query idle devices
|
|
295
|
+
idle = client.get_idle_devices()
|
|
296
|
+
print(f"6. client.get_idle_devices(): {[d.serial for d in idle]}")
|
|
297
|
+
|
|
298
|
+
# Use Task structured object with run_task
|
|
299
|
+
print("7. Calling client.run_task() with structured Task entity:")
|
|
300
|
+
task_obj = Task(
|
|
301
|
+
goal="Verify physical device call via Task object",
|
|
302
|
+
profile="flash",
|
|
303
|
+
device_serial=real_serial,
|
|
304
|
+
concurrency_mode="per_device",
|
|
305
|
+
max_turns=3,
|
|
306
|
+
)
|
|
307
|
+
result = await client.run_task(task_obj)
|
|
308
|
+
print(
|
|
309
|
+
f" Task result: Status={result.status}, Device={result.device_id}, Trace={result.trace_id[:8]}..."
|
|
310
|
+
)
|
|
311
|
+
assert result.device_id == real_serial
|
|
312
|
+
|
|
313
|
+
# Use stream_run for live event stream
|
|
314
|
+
print("8. Using client.stream_run() for live event stream:")
|
|
315
|
+
events_received = 0
|
|
316
|
+
async for ev in client.stream_run(
|
|
317
|
+
"Stream operation test",
|
|
318
|
+
device_serial=emu_serial,
|
|
319
|
+
profile="flash",
|
|
320
|
+
max_turns=3,
|
|
321
|
+
):
|
|
322
|
+
events_received += 1
|
|
323
|
+
print(f" • [{ev.event_type}] payload={ev.payload}")
|
|
324
|
+
assert events_received >= 2
|
|
325
|
+
print(f" ✅ Stream events captured successfully! Captured {events_received} events.")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
async def main():
|
|
329
|
+
# Detect devices
|
|
330
|
+
devices = device_pool.list_devices()
|
|
331
|
+
print("=" * 76)
|
|
332
|
+
print("[ARTEMIS Python SDK Concurrency & Device Targeting Experiment]")
|
|
333
|
+
print(f"Detected connected devices count: {len(devices)}")
|
|
334
|
+
for d in devices:
|
|
335
|
+
print(
|
|
336
|
+
f" • Serial: {d.serial} | Model: {d.model} | State: {d.state} | IsEmulator: {d.is_emulator}"
|
|
337
|
+
)
|
|
338
|
+
print("=" * 76)
|
|
339
|
+
|
|
340
|
+
import argparse
|
|
341
|
+
|
|
342
|
+
parser = argparse.ArgumentParser(
|
|
343
|
+
description="Live experiment demonstrating Artemis concurrency modes and device targeting."
|
|
344
|
+
)
|
|
345
|
+
parser.add_argument(
|
|
346
|
+
"--phone",
|
|
347
|
+
default=os.environ.get("ARTEMIS_PHONE_SERIAL"),
|
|
348
|
+
help="Target physical phone serial number",
|
|
349
|
+
)
|
|
350
|
+
parser.add_argument(
|
|
351
|
+
"--emulator",
|
|
352
|
+
default=os.environ.get("ARTEMIS_EMULATOR_SERIAL"),
|
|
353
|
+
help="Target Android emulator serial number",
|
|
354
|
+
)
|
|
355
|
+
args, _ = parser.parse_known_args()
|
|
356
|
+
|
|
357
|
+
# Auto-detect connected devices if not explicitly provided
|
|
358
|
+
real_dev = next((d for d in devices if not d.is_emulator and d.state == "device"), None)
|
|
359
|
+
emu_dev = next((d for d in devices if d.is_emulator and d.state == "device"), None)
|
|
360
|
+
|
|
361
|
+
real_serial = args.phone or (real_dev.serial if real_dev else "63191FDKX00062")
|
|
362
|
+
emu_serial = args.emulator or (emu_dev.serial if emu_dev else "emulator-5554")
|
|
363
|
+
|
|
364
|
+
# Verify both devices are available
|
|
365
|
+
serials = [d.serial for d in devices]
|
|
366
|
+
if real_serial not in serials or emu_serial not in serials:
|
|
367
|
+
print(
|
|
368
|
+
f"Error: Both physical device '{real_serial}' and emulator '{emu_serial}' must be connected for full experiment."
|
|
369
|
+
)
|
|
370
|
+
print(f"Currently online devices: {serials}")
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
# Run experiments in sequence
|
|
374
|
+
await run_experiment_1_single_device_relay(real_serial)
|
|
375
|
+
await run_experiment_2_multi_device_parallel(real_serial, emu_serial)
|
|
376
|
+
await run_experiment_3_global_concurrency_serialization(real_serial, emu_serial)
|
|
377
|
+
await run_experiment_4_parameters_and_utilities(real_serial, emu_serial)
|
|
378
|
+
|
|
379
|
+
print_header(
|
|
380
|
+
"🎉 All experiments completed successfully! All concurrency and device targeting verified!"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
asyncio.run(main())
|