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,36 @@
|
|
|
1
|
+
package com.artemis.helper;
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Receives the host's session token.
|
|
10
|
+
*
|
|
11
|
+
* The receiver is guarded in the manifest by
|
|
12
|
+
* {@code android.permission.WRITE_SECURE_SETTINGS}, a permission ordinary apps
|
|
13
|
+
* cannot hold but the adb shell user does, so only something with adb access to
|
|
14
|
+
* the device can set (or clear) the token:
|
|
15
|
+
*
|
|
16
|
+
* <pre>
|
|
17
|
+
* adb shell am broadcast -n com.artemis.helper/.TokenReceiver \
|
|
18
|
+
* -a com.artemis.helper.SET_TOKEN --es token <hex>
|
|
19
|
+
* </pre>
|
|
20
|
+
*/
|
|
21
|
+
public class TokenReceiver extends BroadcastReceiver {
|
|
22
|
+
|
|
23
|
+
private static final String TAG = "ArtemisTokenReceiver";
|
|
24
|
+
public static final String ACTION_SET_TOKEN = "com.artemis.helper.SET_TOKEN";
|
|
25
|
+
public static final String EXTRA_TOKEN = "token";
|
|
26
|
+
|
|
27
|
+
@Override
|
|
28
|
+
public void onReceive(Context context, Intent intent) {
|
|
29
|
+
if (intent == null || !ACTION_SET_TOKEN.equals(intent.getAction())) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
String value = intent.getStringExtra(EXTRA_TOKEN);
|
|
33
|
+
TokenStore.set(value);
|
|
34
|
+
Log.i(TAG, TokenStore.isSet() ? "Session token set" : "Session token cleared");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.artemis.helper;
|
|
2
|
+
|
|
3
|
+
import java.nio.charset.StandardCharsets;
|
|
4
|
+
import java.security.MessageDigest;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The session token the host pushed for this process lifetime.
|
|
8
|
+
*
|
|
9
|
+
* The loopback server is reachable by every app on the device, so requests are
|
|
10
|
+
* only served when they carry this token. It lives in memory only: a killed or
|
|
11
|
+
* re-bound service starts without one and answers 401 until the host pushes it
|
|
12
|
+
* again (which the host does on every attach and on every 401).
|
|
13
|
+
*/
|
|
14
|
+
public final class TokenStore {
|
|
15
|
+
|
|
16
|
+
private static volatile String token;
|
|
17
|
+
|
|
18
|
+
private TokenStore() {}
|
|
19
|
+
|
|
20
|
+
public static void set(String value) {
|
|
21
|
+
token = (value == null || value.isEmpty()) ? null : value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static boolean isSet() {
|
|
25
|
+
return token != null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Constant-time comparison so a local app cannot guess the token byte by byte. */
|
|
29
|
+
public static boolean matches(String candidate) {
|
|
30
|
+
String current = token;
|
|
31
|
+
if (current == null || candidate == null) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return MessageDigest.isEqual(
|
|
35
|
+
current.getBytes(StandardCharsets.UTF_8),
|
|
36
|
+
candidate.getBytes(StandardCharsets.UTF_8));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package com.artemis.helper;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* High-performance XML serialization and character sanitization utilities.
|
|
5
|
+
* Ensures the output strictly conforms to the W3C XML 1.0 specification,
|
|
6
|
+
* preventing XML parsing errors (such as ET.ParseError in Python) caused by
|
|
7
|
+
* unprintable control characters, NUL bytes, or unescaped entities.
|
|
8
|
+
*/
|
|
9
|
+
public final class XmlUtils {
|
|
10
|
+
|
|
11
|
+
private XmlUtils() {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Appends an escaped attribute to the StringBuilder: {@code name="value"}.
|
|
15
|
+
* If the value is null or empty, appends {@code name=""}.
|
|
16
|
+
*/
|
|
17
|
+
public static void appendAttribute(StringBuilder sb, String name, CharSequence value) {
|
|
18
|
+
sb.append(' ').append(name).append("=\"");
|
|
19
|
+
if (value != null && value.length() > 0) {
|
|
20
|
+
escapeXmlAttr(sb, value);
|
|
21
|
+
}
|
|
22
|
+
sb.append('"');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Appends a boolean attribute: {@code name="true"} or {@code name="false"}.
|
|
27
|
+
*/
|
|
28
|
+
public static void appendBooleanAttribute(StringBuilder sb, String name, boolean value) {
|
|
29
|
+
sb.append(' ').append(name).append(value ? "=\"true\"" : "=\"false\"");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Appends an integer attribute: {@code name="123"}.
|
|
34
|
+
*/
|
|
35
|
+
public static void appendIntAttribute(StringBuilder sb, String name, int value) {
|
|
36
|
+
sb.append(' ').append(name).append("=\"").append(value).append('"');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Escapes and sanitizes an attribute value according to XML 1.0 rules.
|
|
41
|
+
* Drops illegal XML characters (e.g. ASCII control characters 0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F)
|
|
42
|
+
* and escapes standard entities (&, <, >, ", ').
|
|
43
|
+
*/
|
|
44
|
+
public static void escapeXmlAttr(StringBuilder sb, CharSequence text) {
|
|
45
|
+
final int len = text.length();
|
|
46
|
+
for (int i = 0; i < len; i++) {
|
|
47
|
+
char c = text.charAt(i);
|
|
48
|
+
switch (c) {
|
|
49
|
+
case '&':
|
|
50
|
+
sb.append("&");
|
|
51
|
+
break;
|
|
52
|
+
case '<':
|
|
53
|
+
sb.append("<");
|
|
54
|
+
break;
|
|
55
|
+
case '>':
|
|
56
|
+
sb.append(">");
|
|
57
|
+
break;
|
|
58
|
+
case '"':
|
|
59
|
+
sb.append(""");
|
|
60
|
+
break;
|
|
61
|
+
case '\'':
|
|
62
|
+
sb.append("'");
|
|
63
|
+
break;
|
|
64
|
+
case '\t':
|
|
65
|
+
sb.append("	");
|
|
66
|
+
break;
|
|
67
|
+
case '\n':
|
|
68
|
+
sb.append(" ");
|
|
69
|
+
break;
|
|
70
|
+
case '\r':
|
|
71
|
+
sb.append(" ");
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
// XML 1.0 legal character range check:
|
|
75
|
+
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
|
76
|
+
if (c >= 0x20 && c <= 0xD7FF) {
|
|
77
|
+
sb.append(c);
|
|
78
|
+
} else if (c >= 0xE000 && c <= 0xFFFD) {
|
|
79
|
+
sb.append(c);
|
|
80
|
+
} else if (Character.isHighSurrogate(c)) {
|
|
81
|
+
if (i + 1 < len && Character.isLowSurrogate(text.charAt(i + 1))) {
|
|
82
|
+
sb.append(c).append(text.charAt(i + 1));
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Characters outside these ranges (e.g. \u0000 through \u0008, \u000B, \u000C, \u000E-\u001F,
|
|
87
|
+
// unpaired surrogates, \uFFFE, \uFFFF) are invalid XML 1.0 and silently omitted to guarantee well-formedness.
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Sanitizes a string for safe XML attribute inclusion, returning the escaped string.
|
|
95
|
+
*/
|
|
96
|
+
public static String escapeXmlAttr(CharSequence text) {
|
|
97
|
+
if (text == null || text.length() == 0) return "";
|
|
98
|
+
StringBuilder sb = new StringBuilder(text.length() + 16);
|
|
99
|
+
escapeXmlAttr(sb, text);
|
|
100
|
+
return sb.toString();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:accessibilityEventTypes="typeWindowStateChanged"
|
|
4
|
+
android:accessibilityFeedbackType="feedbackGeneric"
|
|
5
|
+
android:notificationTimeout="100"
|
|
6
|
+
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds"
|
|
7
|
+
android:canRetrieveWindowContent="true"
|
|
8
|
+
android:canPerformGestures="true"
|
|
9
|
+
android:canTakeScreenshot="true"
|
|
10
|
+
android:description="@string/accessibility_service_description" />
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Standalone build for ArtemisAccessibilityHelper.apk (no Gradle daemon).
|
|
3
|
+
#
|
|
4
|
+
# Outputs, next to this script:
|
|
5
|
+
# ArtemisAccessibilityHelper.apk the signed, zipaligned artifact
|
|
6
|
+
# helper_manifest.json {version_code, version_name, sha256, built_at}
|
|
7
|
+
#
|
|
8
|
+
# The Python runtime (artemis/runtime/helper_manager.py) reads helper_manifest.json
|
|
9
|
+
# to decide whether a device needs an install or an upgrade, so the manifest must
|
|
10
|
+
# always be regenerated together with the APK. The version is sourced from
|
|
11
|
+
# app/build.gradle.kts (defaultConfig.versionCode / versionName) so the Gradle
|
|
12
|
+
# build and this script cannot drift.
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
cd "$DIR"
|
|
17
|
+
|
|
18
|
+
_to_posix() {
|
|
19
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
20
|
+
cygpath -u "$1"
|
|
21
|
+
else
|
|
22
|
+
printf '%s' "$1"
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# 1. Locate Android SDK
|
|
27
|
+
SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
|
28
|
+
if [ -n "$SDK_ROOT" ]; then
|
|
29
|
+
SDK_ROOT="$(_to_posix "$SDK_ROOT")"
|
|
30
|
+
fi
|
|
31
|
+
if [ -z "$SDK_ROOT" ] || [ ! -d "$SDK_ROOT" ]; then
|
|
32
|
+
for candidate in \
|
|
33
|
+
"$HOME/Library/Android/sdk" \
|
|
34
|
+
"$HOME/Android/Sdk" \
|
|
35
|
+
/opt/android-sdk \
|
|
36
|
+
/usr/local/share/android-sdk \
|
|
37
|
+
"$( [ -n "$LOCALAPPDATA" ] && _to_posix "$LOCALAPPDATA/Android/Sdk" )"; do
|
|
38
|
+
if [ -n "$candidate" ] && [ -d "$candidate" ]; then
|
|
39
|
+
SDK_ROOT="$candidate"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [ -z "$SDK_ROOT" ] || [ ! -d "$SDK_ROOT" ]; then
|
|
46
|
+
echo "Error: Android SDK not found. Set ANDROID_HOME." >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
BUILD_TOOLS="$(find "$SDK_ROOT/build-tools" -maxdepth 1 -mindepth 1 | sort -V | tail -n 1)"
|
|
51
|
+
PLATFORM="$(find "$SDK_ROOT/platforms" -maxdepth 1 -mindepth 1 | sort -V | tail -n 1)"
|
|
52
|
+
|
|
53
|
+
if [ -z "$BUILD_TOOLS" ] || [ ! -d "$BUILD_TOOLS" ]; then
|
|
54
|
+
echo "Error: No build-tools found under $SDK_ROOT/build-tools" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [ -z "$PLATFORM" ] || [ ! -f "$PLATFORM/android.jar" ]; then
|
|
59
|
+
echo "Error: android.jar not found under $SDK_ROOT/platforms" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# 2. Locate Java compiler and runtime
|
|
64
|
+
if [ -z "$JAVA_HOME" ]; then
|
|
65
|
+
for candidate in \
|
|
66
|
+
"/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
|
|
67
|
+
"$(_to_posix "${PROGRAMFILES:-/c/Program Files}/Android/Android Studio/jbr")" \
|
|
68
|
+
"/opt/android-studio/jbr"; do
|
|
69
|
+
if [ -n "$candidate" ] && [ -x "$candidate/bin/javac" ] || [ -x "$candidate/bin/javac.exe" ]; then
|
|
70
|
+
export JAVA_HOME="$candidate"
|
|
71
|
+
break
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
fi
|
|
75
|
+
if [ -n "$JAVA_HOME" ]; then
|
|
76
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if ! command -v javac >/dev/null 2>&1; then
|
|
80
|
+
echo "Error: javac not found in PATH or JAVA_HOME" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
PYTHON_BIN="${PYTHON:-}"
|
|
85
|
+
if [ -z "$PYTHON_BIN" ]; then
|
|
86
|
+
# The Windows Store "python" stub is on PATH but exits 49; prefer the project venv.
|
|
87
|
+
for candidate in "$DIR/../../.venv/Scripts/python.exe" "$DIR/../../.venv/bin/python" python3 python; do
|
|
88
|
+
if "$candidate" -c "import zipfile" >/dev/null 2>&1; then
|
|
89
|
+
PYTHON_BIN="$candidate"
|
|
90
|
+
break
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
fi
|
|
94
|
+
if [ -z "$PYTHON_BIN" ]; then
|
|
95
|
+
echo "Error: python3 not found (needed to package the DEX and write helper_manifest.json)" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Build-tools ship as .exe / .bat wrappers on Windows; resolve the right name once.
|
|
100
|
+
_tool() {
|
|
101
|
+
local base="$BUILD_TOOLS/$1"
|
|
102
|
+
for suffix in "" ".exe" ".bat"; do
|
|
103
|
+
if [ -f "$base$suffix" ]; then
|
|
104
|
+
printf '%s' "$base$suffix"
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
done
|
|
108
|
+
echo "Error: $1 not found under $BUILD_TOOLS" >&2
|
|
109
|
+
exit 1
|
|
110
|
+
}
|
|
111
|
+
AAPT2="$(_tool aapt2)"
|
|
112
|
+
D8="$(_tool d8)"
|
|
113
|
+
ZIPALIGN="$(_tool zipalign)"
|
|
114
|
+
APKSIGNER="$(_tool apksigner)"
|
|
115
|
+
|
|
116
|
+
# 3. Version: single source is app/build.gradle.kts
|
|
117
|
+
VERSION_CODE="$(sed -n 's/^[[:space:]]*versionCode[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' app/build.gradle.kts | head -n 1)"
|
|
118
|
+
VERSION_NAME="$(sed -n 's/^[[:space:]]*versionName[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' app/build.gradle.kts | head -n 1)"
|
|
119
|
+
if [ -z "$VERSION_CODE" ] || [ -z "$VERSION_NAME" ]; then
|
|
120
|
+
echo "Error: could not read versionCode / versionName from app/build.gradle.kts" >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
echo "=================================================="
|
|
125
|
+
echo " Building ArtemisAccessibilityHelper.apk"
|
|
126
|
+
echo " Version: $VERSION_NAME (code $VERSION_CODE)"
|
|
127
|
+
echo " Build Tools: $(basename "$BUILD_TOOLS")"
|
|
128
|
+
echo " Platform: $(basename "$PLATFORM")"
|
|
129
|
+
echo " Java: $(javac -version 2>&1)"
|
|
130
|
+
echo "=================================================="
|
|
131
|
+
|
|
132
|
+
rm -rf build
|
|
133
|
+
mkdir -p build/gen build/obj build/apk build/res_compiled
|
|
134
|
+
|
|
135
|
+
echo "-> Compiling resources..."
|
|
136
|
+
"$AAPT2" compile --dir app/src/main/res -o build/res_compiled/res.zip
|
|
137
|
+
|
|
138
|
+
echo "-> Linking APK..."
|
|
139
|
+
"$AAPT2" link \
|
|
140
|
+
-I "$PLATFORM/android.jar" \
|
|
141
|
+
--manifest app/src/main/AndroidManifest.xml \
|
|
142
|
+
--min-sdk-version 24 \
|
|
143
|
+
--target-sdk-version 35 \
|
|
144
|
+
--version-code "$VERSION_CODE" \
|
|
145
|
+
--version-name "$VERSION_NAME" \
|
|
146
|
+
--java build/gen \
|
|
147
|
+
-o build/apk/unaligned.apk \
|
|
148
|
+
build/res_compiled/res.zip
|
|
149
|
+
|
|
150
|
+
echo "-> Compiling Java sources..."
|
|
151
|
+
JAVA_FILES="$(find build/gen app/src/main/java -name "*.java")"
|
|
152
|
+
javac -encoding UTF-8 \
|
|
153
|
+
-source 8 -target 8 \
|
|
154
|
+
-Xlint:-options \
|
|
155
|
+
-cp "$PLATFORM/android.jar" \
|
|
156
|
+
-d build/obj \
|
|
157
|
+
$JAVA_FILES
|
|
158
|
+
|
|
159
|
+
echo "-> Dexing with D8..."
|
|
160
|
+
CLASS_FILES="$(find build/obj -name "*.class")"
|
|
161
|
+
"$D8" \
|
|
162
|
+
--lib "$PLATFORM/android.jar" \
|
|
163
|
+
--output build/apk/ \
|
|
164
|
+
--min-api 24 \
|
|
165
|
+
$CLASS_FILES
|
|
166
|
+
|
|
167
|
+
echo "-> Packaging DEX..."
|
|
168
|
+
"$PYTHON_BIN" - <<'PY'
|
|
169
|
+
import zipfile
|
|
170
|
+
with zipfile.ZipFile("build/apk/unaligned.apk", "a", compression=zipfile.ZIP_DEFLATED) as apk:
|
|
171
|
+
apk.write("build/apk/classes.dex", "classes.dex")
|
|
172
|
+
PY
|
|
173
|
+
|
|
174
|
+
echo "-> Zipaligning..."
|
|
175
|
+
"$ZIPALIGN" -f -p 4 build/apk/unaligned.apk build/apk/aligned.apk
|
|
176
|
+
|
|
177
|
+
echo "-> Signing APK..."
|
|
178
|
+
if [ ! -f debug.keystore ]; then
|
|
179
|
+
echo "Creating persistent debug.keystore..."
|
|
180
|
+
keytool -genkey -v -keystore debug.keystore \
|
|
181
|
+
-storepass android -alias androiddebugkey -keypass android \
|
|
182
|
+
-keyalg RSA -keysize 2048 -validity 10000 \
|
|
183
|
+
-dname "CN=Artemis Debug,O=Artemis,C=US"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
"$APKSIGNER" sign \
|
|
187
|
+
--ks debug.keystore \
|
|
188
|
+
--ks-pass pass:android \
|
|
189
|
+
--key-pass pass:android \
|
|
190
|
+
--ks-key-alias androiddebugkey \
|
|
191
|
+
--out ArtemisAccessibilityHelper.apk \
|
|
192
|
+
build/apk/aligned.apk
|
|
193
|
+
|
|
194
|
+
echo "-> Writing helper_manifest.json..."
|
|
195
|
+
VERSION_CODE="$VERSION_CODE" VERSION_NAME="$VERSION_NAME" "$PYTHON_BIN" - <<'PY'
|
|
196
|
+
import datetime, hashlib, json, os
|
|
197
|
+
digest = hashlib.sha256(open("ArtemisAccessibilityHelper.apk", "rb").read()).hexdigest()
|
|
198
|
+
manifest = {
|
|
199
|
+
"package": "com.artemis.helper",
|
|
200
|
+
"version_code": int(os.environ["VERSION_CODE"]),
|
|
201
|
+
"version_name": os.environ["VERSION_NAME"],
|
|
202
|
+
"sha256": digest,
|
|
203
|
+
"built_at": datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat(),
|
|
204
|
+
}
|
|
205
|
+
with open("helper_manifest.json", "w", encoding="utf-8", newline="\n") as fh:
|
|
206
|
+
json.dump(manifest, fh, indent=2)
|
|
207
|
+
fh.write("\n")
|
|
208
|
+
print(json.dumps(manifest, indent=2))
|
|
209
|
+
PY
|
|
210
|
+
|
|
211
|
+
echo "=================================================="
|
|
212
|
+
echo " Build SUCCESSFUL!"
|
|
213
|
+
echo " Artifact: $DIR/ArtemisAccessibilityHelper.apk"
|
|
214
|
+
ls -lh ArtemisAccessibilityHelper.apk
|
|
215
|
+
echo "=================================================="
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pluginManagement {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
gradlePluginPortal()
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
dependencyResolutionManagement {
|
|
9
|
+
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
|
10
|
+
repositories {
|
|
11
|
+
google()
|
|
12
|
+
mavenCentral()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
rootProject.name = "artemis-accessibility-helper"
|
|
17
|
+
include(":app")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Artemis Thin Client Design
|
|
2
|
+
|
|
3
|
+
## Goals
|
|
4
|
+
|
|
5
|
+
Developer machines install only a small remote SDK, while the host connected to
|
|
6
|
+
Android devices runs the full Artemis stack. The SDK must:
|
|
7
|
+
|
|
8
|
+
- have no dependency on ADB, device drivers, agents, LLMs, web services, or image libraries;
|
|
9
|
+
- never attempt to start a local Artemis daemon;
|
|
10
|
+
- remain compatible with the existing server API;
|
|
11
|
+
- allow the server to add agent and device capabilities independently; and
|
|
12
|
+
- provide explicit capability negotiation and errors as the protocol evolves.
|
|
13
|
+
|
|
14
|
+
## Boundary
|
|
15
|
+
|
|
16
|
+
The client owns request modeling, HTTP communication, status polling, error
|
|
17
|
+
normalization, and result models. Device discovery, task scheduling, concurrency
|
|
18
|
+
control, model selection, execution, logs, videos, and artifact generation all
|
|
19
|
+
belong to the server.
|
|
20
|
+
|
|
21
|
+
The distribution is named `artemis-client`, while the Python import is
|
|
22
|
+
`artemis_client`. This prevents it from shadowing the full runtime's top-level
|
|
23
|
+
`artemis` package. The full runtime can depend on this package later and
|
|
24
|
+
re-export the client from `artemis` for compatibility.
|
|
25
|
+
|
|
26
|
+
## Phase 1: Compatibility Adapter
|
|
27
|
+
|
|
28
|
+
The first release calls the current `/api/*` administration endpoints directly.
|
|
29
|
+
The client generates a task ID and maps it to the server's existing `session_id`
|
|
30
|
+
to make retries idempotent. While a task is queued but not yet visible in the
|
|
31
|
+
session database, the client reconstructs its state from `queue` and
|
|
32
|
+
`active_tasks` in `/api/status`.
|
|
33
|
+
|
|
34
|
+
Experimental parameters live in `options`. Older servers ignore unknown fields,
|
|
35
|
+
while newer servers can consume them without requiring an immediate SDK release.
|
|
36
|
+
|
|
37
|
+
## Phase 2: Stable Remote API
|
|
38
|
+
|
|
39
|
+
The server should expose versioned endpoints dedicated to remote clients:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
POST /api/v1/tasks
|
|
43
|
+
GET /api/v1/tasks/{task_id}
|
|
44
|
+
DELETE /api/v1/tasks/{task_id}
|
|
45
|
+
GET /api/v1/tasks/{task_id}/events
|
|
46
|
+
GET /api/v1/tasks/{task_id}/artifacts
|
|
47
|
+
GET /api/v1/devices
|
|
48
|
+
GET /api/v1/capabilities
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The administration API and remote SDK API should remain separate. The remote API
|
|
52
|
+
uses a consistent error envelope:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"error": {
|
|
57
|
+
"code": "DEVICE_OFFLINE",
|
|
58
|
+
"message": "The selected device is offline",
|
|
59
|
+
"retryable": false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Compatibility Rules
|
|
65
|
+
|
|
66
|
+
1. `/api/v1` may add optional request and response fields, but must not change existing field semantics.
|
|
67
|
+
2. The client ignores unknown response fields and preserves the original response in each model's `raw` attribute.
|
|
68
|
+
3. Every new request field must have a server-side default.
|
|
69
|
+
4. The SDK checks `/api/v1/capabilities` before using a new feature.
|
|
70
|
+
5. Breaking changes ship under `/api/v2`; they are never applied in place.
|
|
71
|
+
6. The SDK and server follow semantic versioning independently.
|
|
72
|
+
|
|
73
|
+
## Security
|
|
74
|
+
|
|
75
|
+
The current daemon exposes administration and credential-related endpoints and
|
|
76
|
+
must not listen on a public interface without protection. Use an SSH or VPN tunnel
|
|
77
|
+
in the short term. In production, place a reverse proxy in front of the daemon,
|
|
78
|
+
expose only the remote API, and enforce HTTPS, bearer-token authentication,
|
|
79
|
+
request-size limits, rate limits, and audit logging. Tokens are accepted only
|
|
80
|
+
through arguments or environment variables and are never persisted by the SDK.
|
|
81
|
+
|
|
82
|
+
## Future Work
|
|
83
|
+
|
|
84
|
+
- SSE event subscriptions with reconnection support;
|
|
85
|
+
- artifact manifests and streaming downloads;
|
|
86
|
+
- batch task submission;
|
|
87
|
+
- server and API version constraints;
|
|
88
|
+
- retries limited to network failures and idempotent requests; and
|
|
89
|
+
- OpenAPI contract tests to prevent server and SDK drift.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Artemis Client
|
|
2
|
+
|
|
3
|
+
`artemis-client` is the remote Python SDK that runs on a developer machine. It
|
|
4
|
+
communicates with the Artemis daemon on a device host over HTTP and contains no
|
|
5
|
+
ADB, device-driver, agent, LLM, or image-processing code.
|
|
6
|
+
|
|
7
|
+
Runtime dependencies: **zero**. HTTP, JSON, and asynchronous adapters use only
|
|
8
|
+
the Python standard library.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Install from a local checkout:
|
|
13
|
+
|
|
14
|
+
```powershell
|
|
15
|
+
uv add --editable C:\path\to\artemis\packages\artemis-client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Install from the Git repository subdirectory:
|
|
19
|
+
|
|
20
|
+
```powershell
|
|
21
|
+
uv add "artemis-client @ git+https://github.com/google/artemis.git#subdirectory=packages/artemis-client"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Connect to a Device Host
|
|
25
|
+
|
|
26
|
+
The current Artemis daemon administration API does not include built-in remote
|
|
27
|
+
authentication. Do not expose its port directly to the public internet. During
|
|
28
|
+
the initial rollout, connect through an SSH tunnel:
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
ssh -L 8000:127.0.0.1:8000 user@artemis-host
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then connect the SDK to the local tunnel endpoint:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from artemis_client import ArtemisClient
|
|
38
|
+
|
|
39
|
+
client = ArtemisClient("http://127.0.0.1:8000")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For production, place a reverse proxy in front of the daemon that enforces HTTPS
|
|
43
|
+
and bearer-token authentication and exposes only task-related APIs.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
import os
|
|
50
|
+
|
|
51
|
+
from artemis_client import ArtemisClient
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def main() -> None:
|
|
55
|
+
client = ArtemisClient(
|
|
56
|
+
os.environ["ARTEMIS_BASE_URL"],
|
|
57
|
+
token=os.environ.get("ARTEMIS_TOKEN"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
handle = await client.submit(
|
|
61
|
+
"Open the Battery page in Settings and verify the battery percentage",
|
|
62
|
+
profile="flash",
|
|
63
|
+
device_serial="emulator-5554",
|
|
64
|
+
)
|
|
65
|
+
print("queued:", handle.task_id)
|
|
66
|
+
|
|
67
|
+
result = await client.wait_for_task(handle.task_id, timeout=600)
|
|
68
|
+
if not result.succeeded:
|
|
69
|
+
raise RuntimeError(result.error or result.status)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
asyncio.run(main())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also submit a task and wait for it in one call:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
result = await client.run("Open Android Settings and check the Wi-Fi status")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Main methods:
|
|
82
|
+
|
|
83
|
+
- `health()` quickly checks whether the server and scheduler are alive.
|
|
84
|
+
- `readiness()` runs full device and toolchain diagnostics and may take longer.
|
|
85
|
+
- `capabilities()` negotiates API features and falls back for older servers.
|
|
86
|
+
- `list_devices()` lists devices available on the host.
|
|
87
|
+
- `submit()` submits an idempotent task and returns immediately.
|
|
88
|
+
- `get_task()` retrieves a task.
|
|
89
|
+
- `wait_for_task()` waits for a terminal state.
|
|
90
|
+
- `run()` submits a task and waits for completion.
|
|
91
|
+
- `stop()` stops one task.
|
|
92
|
+
|
|
93
|
+
By default, `submit()` generates a UUID on the client and sends it as the current
|
|
94
|
+
server's `session_id`. Network retries reuse the same `task_id`, preventing the
|
|
95
|
+
server from creating duplicate tasks.
|
|
96
|
+
|
|
97
|
+
## Compatibility
|
|
98
|
+
|
|
99
|
+
Version 0.1 supports these current Artemis daemon endpoints:
|
|
100
|
+
|
|
101
|
+
- `POST /api/run`
|
|
102
|
+
- `GET /api/sessions/{session_id}`
|
|
103
|
+
- `GET /api/status`
|
|
104
|
+
- `POST /api/stop`
|
|
105
|
+
- `GET /api/devices`
|
|
106
|
+
- `GET /api/system/readiness`
|
|
107
|
+
|
|
108
|
+
Capability discovery first attempts the future `GET /api/v1/capabilities`
|
|
109
|
+
endpoint. If an older server returns 404, the SDK uses its known baseline
|
|
110
|
+
capability set.
|
|
111
|
+
|
|
112
|
+
See [DESIGN.md](DESIGN.md) for the architecture and protocol evolution plan.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Run one task against an Artemis host."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from artemis_client import ArtemisClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def main() -> None:
|
|
10
|
+
client = ArtemisClient(
|
|
11
|
+
os.environ.get("ARTEMIS_BASE_URL", "http://127.0.0.1:8000"),
|
|
12
|
+
token=os.environ.get("ARTEMIS_TOKEN"),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
devices = await client.list_devices()
|
|
16
|
+
if not devices:
|
|
17
|
+
raise RuntimeError("The Artemis host did not report an Android device")
|
|
18
|
+
|
|
19
|
+
result = await client.run(
|
|
20
|
+
"Open the Battery page in Android Settings and verify the battery percentage.",
|
|
21
|
+
profile="flash",
|
|
22
|
+
device_serial=devices[0].serial,
|
|
23
|
+
)
|
|
24
|
+
if not result.succeeded:
|
|
25
|
+
raise RuntimeError(result.error or f"Task ended with status {result.status}")
|
|
26
|
+
print(f"Task {result.task_id} completed on {result.device_serial}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
asyncio.run(main())
|