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,517 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
from contextlib import suppress
|
|
17
|
+
import json
|
|
18
|
+
from typing import Any
|
|
19
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
20
|
+
from fastapi.responses import StreamingResponse
|
|
21
|
+
from artemis.core.diagnostics import readiness_engine
|
|
22
|
+
from artemis.runtime import DeviceExecutionLock, device_pool
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from admin_console.core.state import state
|
|
26
|
+
from admin_console.database.repositories.session_repository import session_repo
|
|
27
|
+
from admin_console.schemas.task_schema import RunRequest
|
|
28
|
+
from admin_console.services.ipc_service import ipc_service
|
|
29
|
+
from admin_console.services.model_service import model_service
|
|
30
|
+
from admin_console.services.task_preset_catalog import task_recommendation_engine
|
|
31
|
+
from admin_console.services.task_queue_service import task_queue_service
|
|
32
|
+
except ImportError:
|
|
33
|
+
from apps.admin_console.core.state import state
|
|
34
|
+
from apps.admin_console.database.repositories.session_repository import session_repo
|
|
35
|
+
from apps.admin_console.schemas.task_schema import RunRequest
|
|
36
|
+
from apps.admin_console.services.ipc_service import ipc_service
|
|
37
|
+
from apps.admin_console.services.model_service import model_service
|
|
38
|
+
from apps.admin_console.services.task_preset_catalog import task_recommendation_engine
|
|
39
|
+
from apps.admin_console.services.task_queue_service import task_queue_service
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
router = APIRouter(tags=["tasks"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("/api/tasks/presets")
|
|
46
|
+
async def get_task_presets(
|
|
47
|
+
category: str = "recommended",
|
|
48
|
+
packages: str | None = None,
|
|
49
|
+
limit: int = 24,
|
|
50
|
+
):
|
|
51
|
+
"""Retrieve smart task recommendations optionally tailored to detected device packages."""
|
|
52
|
+
pkg_list = [p.strip() for p in packages.split(",") if p.strip()] if packages else None
|
|
53
|
+
return task_recommendation_engine.recommend_tasks(
|
|
54
|
+
installed_packages=pkg_list,
|
|
55
|
+
category=category,
|
|
56
|
+
limit=limit,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.get("/api/tasks/catalog")
|
|
61
|
+
async def get_task_catalog():
|
|
62
|
+
"""Retrieve full catalog of predefined tasks and app package registry."""
|
|
63
|
+
return {
|
|
64
|
+
"tasks": [t.model_dump() for t in task_recommendation_engine.get_all_tasks()],
|
|
65
|
+
"app_registry": task_recommendation_engine.get_app_registry(),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@router.post("/api/run")
|
|
70
|
+
async def run_task(request: RunRequest):
|
|
71
|
+
incoming_goals = []
|
|
72
|
+
if request.goals:
|
|
73
|
+
incoming_goals = request.goals
|
|
74
|
+
elif request.goal:
|
|
75
|
+
incoming_goals = [request.goal]
|
|
76
|
+
|
|
77
|
+
if not incoming_goals:
|
|
78
|
+
raise HTTPException(
|
|
79
|
+
status_code=400,
|
|
80
|
+
detail="Either 'goal' or 'goals' list must be provided.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Idempotent SDK retries must never re-run device readiness checks. A task
|
|
84
|
+
# can hold the device while its admission response is lost in transit; in
|
|
85
|
+
# that state, probing the same device again may fail or block even though
|
|
86
|
+
# the original task was accepted successfully.
|
|
87
|
+
if request.session_id and len(incoming_goals) == 1:
|
|
88
|
+
requested_sid = str(request.session_id)
|
|
89
|
+
existing_item = next(
|
|
90
|
+
(
|
|
91
|
+
item
|
|
92
|
+
for item in state.queue_items
|
|
93
|
+
if isinstance(item, dict) and str(item.get("session_id")) == requested_sid
|
|
94
|
+
),
|
|
95
|
+
None,
|
|
96
|
+
)
|
|
97
|
+
persisted_session = session_repo.get_session_by_id(requested_sid)
|
|
98
|
+
is_active = (
|
|
99
|
+
str(state.active_session_id) == requested_sid
|
|
100
|
+
or requested_sid in state.active_connections
|
|
101
|
+
)
|
|
102
|
+
if existing_item or persisted_session or is_active:
|
|
103
|
+
task_payload = dict(existing_item or persisted_session or {})
|
|
104
|
+
task_payload.setdefault("session_id", requested_sid)
|
|
105
|
+
task_payload.setdefault("goal", incoming_goals[0])
|
|
106
|
+
task_payload.setdefault("profile", request.profile or "flash")
|
|
107
|
+
task_payload.setdefault("device_serial", request.device_serial)
|
|
108
|
+
task_payload.setdefault("status", "running" if is_active else "queued")
|
|
109
|
+
return {
|
|
110
|
+
"status": task_payload["status"],
|
|
111
|
+
"tasks": [task_payload],
|
|
112
|
+
"enqueued_count": 0,
|
|
113
|
+
"total_queued": len(state.queue_tasks),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Reject an explicit unknown/offline target before running the more
|
|
117
|
+
# expensive readiness probe. Besides producing a stable SDK response,
|
|
118
|
+
# this avoids probing the currently active device for a serial that can
|
|
119
|
+
# never be selected. Only a successful, non-empty enumeration may reject:
|
|
120
|
+
# an indeterminate one (adb blip, startup) lets the submission queue and
|
|
121
|
+
# fail downstream with a clear error instead.
|
|
122
|
+
if request.device_serial:
|
|
123
|
+
try:
|
|
124
|
+
rejection = await device_pool.validate_explicit_serial_async(request.device_serial)
|
|
125
|
+
except Exception:
|
|
126
|
+
rejection = None
|
|
127
|
+
if rejection:
|
|
128
|
+
return {
|
|
129
|
+
"status": "rejected",
|
|
130
|
+
"error": rejection,
|
|
131
|
+
"tasks": [],
|
|
132
|
+
"enqueued_count": 0,
|
|
133
|
+
"total_queued": len(state.queue_tasks),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Re-check immediately before enqueueing so a device locked between UI
|
|
137
|
+
# polling intervals cannot start through a stale Ready state. Use the
|
|
138
|
+
# bounded submission probe: the full diagnostics path also scans packages,
|
|
139
|
+
# emulator installations, Android version, and screen size.
|
|
140
|
+
# With no explicit serial the probe itself resolves a live target (it
|
|
141
|
+
# prefers the diagnostics target preference, then any unlocked ready
|
|
142
|
+
# device); the verified serial is bound below.
|
|
143
|
+
target_serial = request.device_serial
|
|
144
|
+
device_probe = await readiness_engine.run_device_submission_probe(target_serial=target_serial)
|
|
145
|
+
if device_probe and device_probe.summary in {"Device Locked", "Lock State Unknown"}:
|
|
146
|
+
locked_serial = (
|
|
147
|
+
device_probe.metadata.get("active_device", {}).get("serial") or target_serial or ""
|
|
148
|
+
)
|
|
149
|
+
detail = (
|
|
150
|
+
f"Android device {locked_serial} is locked. Unlock it and enter the home screen before running a task.".replace(
|
|
151
|
+
" ", " "
|
|
152
|
+
).strip()
|
|
153
|
+
if device_probe.summary == "Device Locked"
|
|
154
|
+
else f"Android device {locked_serial} lock state could not be verified. Keep it unlocked on the home screen and try again.".replace(
|
|
155
|
+
" ", " "
|
|
156
|
+
).strip()
|
|
157
|
+
)
|
|
158
|
+
raise HTTPException(status_code=409, detail=detail)
|
|
159
|
+
|
|
160
|
+
if device_probe and device_probe.metadata.get("active_device"):
|
|
161
|
+
verified_serial = device_probe.metadata["active_device"].get("serial")
|
|
162
|
+
# Only auto-selected targets may be re-bound to the probed device. An
|
|
163
|
+
# explicitly requested serial is never silently replaced -- if it is
|
|
164
|
+
# invalid, enqueue_tasks rejects the submission with a clear error.
|
|
165
|
+
if verified_serial and not request.device_serial:
|
|
166
|
+
target_serial = verified_serial
|
|
167
|
+
|
|
168
|
+
return await task_queue_service.enqueue_tasks(
|
|
169
|
+
incoming_goals,
|
|
170
|
+
profile=request.profile or "flash",
|
|
171
|
+
expected_output=request.expected_output,
|
|
172
|
+
enable_outputter=request.enable_outputter,
|
|
173
|
+
verification_level=request.verification_level,
|
|
174
|
+
explorer_mode=request.explorer_mode,
|
|
175
|
+
locked_app_package=request.locked_app_package,
|
|
176
|
+
app_path=request.app_path,
|
|
177
|
+
device_serial=target_serial,
|
|
178
|
+
ingress=request.ingress or "frontend",
|
|
179
|
+
session_id=request.session_id,
|
|
180
|
+
conversation_id=request.conversation_id,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@router.get("/api/run/defaults")
|
|
185
|
+
async def get_run_defaults():
|
|
186
|
+
"""Effective Pro-profile tuning defaults from the agent config.
|
|
187
|
+
|
|
188
|
+
The launcher's sliders start here so they reflect ``artemis.jsonc`` (and the
|
|
189
|
+
``ARTEMIS_EXPLORER_VERSION`` override) instead of a hard-coded guess.
|
|
190
|
+
"""
|
|
191
|
+
from artemis.config import load_agent_config, verification_level_for_checker
|
|
192
|
+
|
|
193
|
+
agent_cfg = load_agent_config()
|
|
194
|
+
return {
|
|
195
|
+
"verification_level": verification_level_for_checker(agent_cfg.checker),
|
|
196
|
+
"explorer_mode": agent_cfg.explorer.resolve(profile="pro"),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@router.get("/api/devices")
|
|
201
|
+
async def list_devices():
|
|
202
|
+
"""List all connected Android devices with their busy / idle status."""
|
|
203
|
+
devices = await device_pool.list_devices_async()
|
|
204
|
+
return {"devices": [d.to_dict() for d in devices]}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@router.post("/api/stop")
|
|
208
|
+
async def stop_task(
|
|
209
|
+
request: Request,
|
|
210
|
+
all: bool = False,
|
|
211
|
+
session_id: str | None = None,
|
|
212
|
+
device_id: str | None = None,
|
|
213
|
+
):
|
|
214
|
+
target_all = all
|
|
215
|
+
target_sid = session_id
|
|
216
|
+
target_dev = device_id
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
body = await request.json()
|
|
220
|
+
if isinstance(body, dict):
|
|
221
|
+
if "all" in body:
|
|
222
|
+
target_all = bool(body["all"]) or target_all
|
|
223
|
+
if body.get("session_id"):
|
|
224
|
+
target_sid = str(body["session_id"])
|
|
225
|
+
if body.get("device_id"):
|
|
226
|
+
target_dev = str(body["device_id"])
|
|
227
|
+
except ValueError:
|
|
228
|
+
# Empty or non-JSON body: fall back to the query parameters.
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
# stop_tasks updates scheduler state and asyncio events owned by this loop.
|
|
232
|
+
stopped = task_queue_service.stop_tasks(
|
|
233
|
+
clear_all=target_all,
|
|
234
|
+
session_id=target_sid,
|
|
235
|
+
device_id=target_dev,
|
|
236
|
+
)
|
|
237
|
+
if stopped:
|
|
238
|
+
return {"status": "stopped", "session_id": target_sid}
|
|
239
|
+
return {"status": "no_running_task"}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@router.post("/api/resume")
|
|
243
|
+
async def resume_task():
|
|
244
|
+
resumed = task_queue_service.resume_task()
|
|
245
|
+
if resumed:
|
|
246
|
+
return {"status": "resumed"}
|
|
247
|
+
return {"status": "not_paused"}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@router.get("/api/status")
|
|
251
|
+
async def get_status():
|
|
252
|
+
# Watchdog check to ensure background worker is alive
|
|
253
|
+
task_queue_service.ensure_worker_running()
|
|
254
|
+
|
|
255
|
+
latest_session = session_repo.get_latest_session()
|
|
256
|
+
latest_session_id = latest_session.get("session_id") if latest_session else None
|
|
257
|
+
bg_tasks = session_repo.get_background_tasks(latest_session_id) if latest_session_id else []
|
|
258
|
+
|
|
259
|
+
global_owner = DeviceExecutionLock.get_active_owner()
|
|
260
|
+
is_running = state.is_running or global_owner is not None
|
|
261
|
+
running_task = next(
|
|
262
|
+
(t for t in state.queue_items if isinstance(t, dict) and t.get("status") == "running"), None
|
|
263
|
+
)
|
|
264
|
+
if not running_task and is_running:
|
|
265
|
+
running_task = next(
|
|
266
|
+
(t for t in state.queue_items if isinstance(t, dict) and t.get("status") == "pending"),
|
|
267
|
+
None,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
active_owners = DeviceExecutionLock.get_active_owners()
|
|
271
|
+
active_tasks = [
|
|
272
|
+
{
|
|
273
|
+
"device_id": owner.device_id,
|
|
274
|
+
"session_id": owner.session_id,
|
|
275
|
+
"goal": owner.description,
|
|
276
|
+
"pid": owner.pid,
|
|
277
|
+
"ingress": owner.ingress,
|
|
278
|
+
"acquired_at": owner.acquired_at,
|
|
279
|
+
}
|
|
280
|
+
for owner in active_owners.values()
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
running_sid = (
|
|
284
|
+
(global_owner.session_id if global_owner else None)
|
|
285
|
+
or state.active_session_id
|
|
286
|
+
or (running_task.get("session_id") if running_task else None)
|
|
287
|
+
or (
|
|
288
|
+
active_tasks[0]["session_id"]
|
|
289
|
+
if active_tasks and active_tasks[0].get("session_id")
|
|
290
|
+
else None
|
|
291
|
+
)
|
|
292
|
+
or (session_repo.get_running_session_id() if is_running else None)
|
|
293
|
+
)
|
|
294
|
+
owner_connection = state.active_connections.get(str(running_sid), {}) if running_sid else {}
|
|
295
|
+
running_goal = (
|
|
296
|
+
owner_connection.get("goal")
|
|
297
|
+
or (global_owner.description if global_owner else None)
|
|
298
|
+
or state.current_goal
|
|
299
|
+
or (running_task.get("goal") if running_task else None)
|
|
300
|
+
or (active_tasks[0]["goal"] if active_tasks else None)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
active_profile = (
|
|
304
|
+
owner_connection.get("profile")
|
|
305
|
+
or state.current_profile
|
|
306
|
+
or (running_task.get("profile") if running_task and not global_owner else None)
|
|
307
|
+
)
|
|
308
|
+
if not active_profile and (running_sid or latest_session_id):
|
|
309
|
+
check_sid = running_sid or latest_session_id
|
|
310
|
+
sess_row = session_repo.get_session_by_id(check_sid)
|
|
311
|
+
if sess_row:
|
|
312
|
+
llm_traces = session_repo.get_llm_traces_for_profile(check_sid)
|
|
313
|
+
agent_names = session_repo.get_agent_trace_names(check_sid)
|
|
314
|
+
active_profile = model_service.resolve_session_profile(
|
|
315
|
+
sess_row, llm_traces, agent_names=agent_names
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
model_info = model_service.get_active_model_info(active_profile)
|
|
319
|
+
|
|
320
|
+
# Unified Global Queue: merge web tasks and external SDK/CLI device queue tickets
|
|
321
|
+
global_queued = DeviceExecutionLock.get_queued_tasks()
|
|
322
|
+
seen_ids = set()
|
|
323
|
+
queue_data: list[dict[str, Any]] = []
|
|
324
|
+
|
|
325
|
+
for item in state.queue_tasks:
|
|
326
|
+
sid = item.get("session_id")
|
|
327
|
+
if sid:
|
|
328
|
+
seen_ids.add(str(sid))
|
|
329
|
+
queue_data.append(item)
|
|
330
|
+
|
|
331
|
+
for g_item in global_queued:
|
|
332
|
+
sid = str(g_item.get("session_id"))
|
|
333
|
+
if sid not in seen_ids:
|
|
334
|
+
seen_ids.add(sid)
|
|
335
|
+
queue_data.append(g_item)
|
|
336
|
+
|
|
337
|
+
if is_running:
|
|
338
|
+
is_paused = state.is_paused
|
|
339
|
+
return {
|
|
340
|
+
"status": "paused" if is_paused else "running",
|
|
341
|
+
"paused_error": state.paused_error if is_paused else None,
|
|
342
|
+
"goal": running_goal,
|
|
343
|
+
"pid": (
|
|
344
|
+
global_owner.pid
|
|
345
|
+
if global_owner
|
|
346
|
+
else state.current_process.pid
|
|
347
|
+
if state.current_process
|
|
348
|
+
else None
|
|
349
|
+
),
|
|
350
|
+
"session_id": running_sid,
|
|
351
|
+
"background_tasks": bg_tasks,
|
|
352
|
+
"queue": queue_data,
|
|
353
|
+
"model_info": model_info,
|
|
354
|
+
"active_tasks": active_tasks,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if latest_session_id and str(latest_session_id) in state.active_connections:
|
|
358
|
+
conn_info = state.active_connections[str(latest_session_id)]
|
|
359
|
+
is_paused = state.is_paused
|
|
360
|
+
conn_profile = conn_info.get("profile") or active_profile
|
|
361
|
+
conn_model_info = model_service.get_active_model_info(conn_profile)
|
|
362
|
+
return {
|
|
363
|
+
"status": "paused" if is_paused else "running",
|
|
364
|
+
"paused_error": state.paused_error if is_paused else None,
|
|
365
|
+
"goal": conn_info.get("goal"),
|
|
366
|
+
"pid": conn_info.get("pid"),
|
|
367
|
+
"session_id": latest_session_id,
|
|
368
|
+
"background_tasks": bg_tasks,
|
|
369
|
+
"queue": queue_data,
|
|
370
|
+
"model_info": conn_model_info,
|
|
371
|
+
"active_tasks": active_tasks,
|
|
372
|
+
"ipc_port": state.ipc_port,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
"status": "idle",
|
|
377
|
+
"session_id": latest_session_id,
|
|
378
|
+
"background_tasks": bg_tasks,
|
|
379
|
+
"queue": queue_data,
|
|
380
|
+
"active_tasks": active_tasks,
|
|
381
|
+
"model_info": model_info,
|
|
382
|
+
"ipc_port": state.ipc_port,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@router.get("/api/stream")
|
|
387
|
+
@router.get("/api/stream/{session_id}")
|
|
388
|
+
async def stream_events(session_id: str = "active", client: str | None = None):
|
|
389
|
+
async def event_generator():
|
|
390
|
+
queue = asyncio.Queue()
|
|
391
|
+
event_loop = asyncio.get_running_loop()
|
|
392
|
+
|
|
393
|
+
def callback(event_type, data):
|
|
394
|
+
try:
|
|
395
|
+
# Global queue lifecycle events should always be delivered
|
|
396
|
+
if event_type not in (
|
|
397
|
+
"session_started",
|
|
398
|
+
"session_ended",
|
|
399
|
+
"background_tasks_updated",
|
|
400
|
+
):
|
|
401
|
+
# Filter events by session_id when subscribed to a specific session
|
|
402
|
+
if session_id and session_id not in ("all", "active"):
|
|
403
|
+
evt_session_id = None
|
|
404
|
+
if isinstance(data, dict):
|
|
405
|
+
evt_session_id = data.get("session_id")
|
|
406
|
+
|
|
407
|
+
if evt_session_id and str(evt_session_id) != str(session_id):
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
if (
|
|
411
|
+
not evt_session_id
|
|
412
|
+
and state.active_session_id
|
|
413
|
+
and str(state.active_session_id) != str(session_id)
|
|
414
|
+
):
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
sanitized_data = ipc_service.sanitize_event_data(event_type, data)
|
|
418
|
+
event_loop.call_soon_threadsafe(queue.put_nowait, (event_type, sanitized_data))
|
|
419
|
+
except RuntimeError:
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
state.add_subscriber(callback)
|
|
423
|
+
yield f'event: info\ndata: {{"message": "Subscribed to session {session_id}"}}\n\n'
|
|
424
|
+
|
|
425
|
+
if session_id in ("all", "active"):
|
|
426
|
+
active_sid = state.active_session_id
|
|
427
|
+
if not active_sid:
|
|
428
|
+
running_item = next(
|
|
429
|
+
(
|
|
430
|
+
t
|
|
431
|
+
for t in state.queue_items
|
|
432
|
+
if isinstance(t, dict) and t.get("status") == "running"
|
|
433
|
+
),
|
|
434
|
+
None,
|
|
435
|
+
)
|
|
436
|
+
if running_item:
|
|
437
|
+
active_sid = running_item.get("session_id")
|
|
438
|
+
if not active_sid:
|
|
439
|
+
owner = DeviceExecutionLock.get_active_owner()
|
|
440
|
+
if owner and owner.session_id:
|
|
441
|
+
active_sid = owner.session_id
|
|
442
|
+
|
|
443
|
+
if active_sid:
|
|
444
|
+
goal = state.current_goal or ""
|
|
445
|
+
profile = state.current_profile or "flash"
|
|
446
|
+
yield (
|
|
447
|
+
"event: session_started\n"
|
|
448
|
+
f"data: {json.dumps({'session_id': str(active_sid), 'initial_goal': goal, 'profile': profile}, default=str)}\n\n"
|
|
449
|
+
)
|
|
450
|
+
for progress_event in state.get_startup_progress(str(active_sid)):
|
|
451
|
+
yield (
|
|
452
|
+
"event: startup_progress\n"
|
|
453
|
+
f"data: {json.dumps(progress_event, default=str)}\n\n"
|
|
454
|
+
)
|
|
455
|
+
try:
|
|
456
|
+
from apps.admin_console.database.repositories.step_repository import step_repo
|
|
457
|
+
|
|
458
|
+
recorded_steps = step_repo.get_session_steps(str(active_sid))
|
|
459
|
+
for step_dict in recorded_steps:
|
|
460
|
+
yield (
|
|
461
|
+
f"event: step_recorded\ndata: {json.dumps(step_dict, default=str)}\n\n"
|
|
462
|
+
)
|
|
463
|
+
except Exception as exc:
|
|
464
|
+
print(f"[Stream] Could not replay active steps: {exc}")
|
|
465
|
+
else:
|
|
466
|
+
for progress_event in state.get_startup_progress(session_id):
|
|
467
|
+
yield (
|
|
468
|
+
f"event: startup_progress\ndata: {json.dumps(progress_event, default=str)}\n\n"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
shutdown_waiter = asyncio.create_task(state.shutdown_event.wait())
|
|
472
|
+
queue_waiter = None
|
|
473
|
+
try:
|
|
474
|
+
while not state.is_shutting_down:
|
|
475
|
+
queue_waiter = asyncio.create_task(queue.get())
|
|
476
|
+
done, _ = await asyncio.wait(
|
|
477
|
+
{queue_waiter, shutdown_waiter},
|
|
478
|
+
timeout=5.0,
|
|
479
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
480
|
+
)
|
|
481
|
+
if shutdown_waiter in done:
|
|
482
|
+
queue_waiter.cancel()
|
|
483
|
+
with suppress(asyncio.CancelledError):
|
|
484
|
+
await queue_waiter
|
|
485
|
+
queue_waiter = None
|
|
486
|
+
break
|
|
487
|
+
if queue_waiter in done:
|
|
488
|
+
event_type, data = queue_waiter.result()
|
|
489
|
+
queue_waiter = None
|
|
490
|
+
yield f"event: {event_type}\ndata: {json.dumps(data, default=str)}\n\n"
|
|
491
|
+
else:
|
|
492
|
+
queue_waiter.cancel()
|
|
493
|
+
with suppress(asyncio.CancelledError):
|
|
494
|
+
await queue_waiter
|
|
495
|
+
queue_waiter = None
|
|
496
|
+
yield "event: keep-alive\ndata: {}\n\n"
|
|
497
|
+
except asyncio.CancelledError:
|
|
498
|
+
raise
|
|
499
|
+
finally:
|
|
500
|
+
waiters = (queue_waiter, shutdown_waiter)
|
|
501
|
+
for waiter in waiters:
|
|
502
|
+
if waiter is not None and not waiter.done():
|
|
503
|
+
waiter.cancel()
|
|
504
|
+
for waiter in waiters:
|
|
505
|
+
if waiter is not None:
|
|
506
|
+
with suppress(asyncio.CancelledError):
|
|
507
|
+
await waiter
|
|
508
|
+
state.remove_subscriber(callback)
|
|
509
|
+
|
|
510
|
+
return StreamingResponse(
|
|
511
|
+
event_generator(),
|
|
512
|
+
media_type="text/event-stream",
|
|
513
|
+
headers={
|
|
514
|
+
"Cache-Control": "no-cache, no-transform",
|
|
515
|
+
"X-Accel-Buffering": "no",
|
|
516
|
+
},
|
|
517
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RunRequest(BaseModel):
|
|
19
|
+
goal: str | None = None
|
|
20
|
+
goals: list[str] | None = None
|
|
21
|
+
profile: str | None = "flash"
|
|
22
|
+
expected_output: str | None = None
|
|
23
|
+
enable_outputter: bool | None = None
|
|
24
|
+
# Pro-profile tuning (ignored by the Flash profile): a coarse Checker preset
|
|
25
|
+
# ('off' | 'final' | 'checkpoints' | 'strict') and the Explorer perception
|
|
26
|
+
# version used by the Operator ('flash' | 'pro' | 'ultra').
|
|
27
|
+
verification_level: str | None = None
|
|
28
|
+
explorer_mode: str | None = None
|
|
29
|
+
locked_app_package: str | None = None
|
|
30
|
+
app_path: str | None = None
|
|
31
|
+
device_serial: str | None = None
|
|
32
|
+
ingress: str | None = "frontend"
|
|
33
|
+
session_id: str | None = None
|
|
34
|
+
conversation_id: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ReplayRequest(BaseModel):
|
|
38
|
+
device_id: str
|
|
39
|
+
user_submits: dict
|
|
40
|
+
tool_name: str = "ask_explorer"
|
|
41
|
+
replay_id: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class StopRequest(BaseModel):
|
|
45
|
+
session_id: str | None = None
|
|
46
|
+
device_id: str | None = None
|
|
47
|
+
all: bool = False
|