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,548 @@
|
|
|
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
|
+
"""MCP Tool: mobile_run_task."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from typing import Any, get_args
|
|
24
|
+
import uuid
|
|
25
|
+
|
|
26
|
+
from mcp_server.base import mcp
|
|
27
|
+
from mcp_server.notifiers import notify
|
|
28
|
+
from mcp_server.utils import env_utils
|
|
29
|
+
from artemis.config import ExplorerVersion, checker_overrides_for_level
|
|
30
|
+
from artemis.config.runtime import read_ipc_port
|
|
31
|
+
from artemis.runtime import (
|
|
32
|
+
DeviceExecutionLock,
|
|
33
|
+
device_pool,
|
|
34
|
+
ensure_daemon_running,
|
|
35
|
+
submit_task_to_daemon,
|
|
36
|
+
trace_store,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Seconds the spawned runner gets to finish its imports and open its log files
|
|
40
|
+
# before the spawn is declared dead. Normal startup creates stdout.log within a
|
|
41
|
+
# few seconds; a runner that produced no logs by this deadline is hung in
|
|
42
|
+
# interpreter/DLL startup and will never recover (observed on Windows when the
|
|
43
|
+
# MCP server process context degrades).
|
|
44
|
+
SPAWN_WATCHDOG_SECONDS = 60
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _kill_process_tree(pid: int) -> None:
|
|
48
|
+
"""Terminate a spawned runner and its children (the venv shim re-execs python)."""
|
|
49
|
+
if sys.platform == "win32":
|
|
50
|
+
subprocess.run(
|
|
51
|
+
["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
52
|
+
stdin=subprocess.DEVNULL,
|
|
53
|
+
stdout=subprocess.DEVNULL,
|
|
54
|
+
stderr=subprocess.DEVNULL,
|
|
55
|
+
check=False,
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
try:
|
|
59
|
+
os.kill(pid, 9)
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _watch_spawn(
|
|
65
|
+
trace_id: str,
|
|
66
|
+
pid: int,
|
|
67
|
+
queue_ticket: str,
|
|
68
|
+
conversation_id: str | None,
|
|
69
|
+
deadline_seconds: float = SPAWN_WATCHDOG_SECONDS,
|
|
70
|
+
poll_interval: float = 2.0,
|
|
71
|
+
) -> bool:
|
|
72
|
+
"""Watch a freshly spawned runner; kill and fail the task if it never boots.
|
|
73
|
+
|
|
74
|
+
The runner opens stdout.log/stderr.log immediately after module imports, so
|
|
75
|
+
their absence after the deadline means the process is wedged before running
|
|
76
|
+
any task code. Without this, a wedged spawn leaves the task in 'running'
|
|
77
|
+
forever and holds its device-queue ticket.
|
|
78
|
+
|
|
79
|
+
Returns True when the runner booted (or was already terminal), False when it
|
|
80
|
+
was declared hung and killed.
|
|
81
|
+
"""
|
|
82
|
+
trace_dir = trace_store.get_trace_dir(trace_id)
|
|
83
|
+
log_paths = (
|
|
84
|
+
os.path.join(trace_dir, "stdout.log"),
|
|
85
|
+
os.path.join(trace_dir, "stderr.log"),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
deadline = time.monotonic() + deadline_seconds
|
|
89
|
+
while time.monotonic() < deadline:
|
|
90
|
+
if any(os.path.exists(p) for p in log_paths):
|
|
91
|
+
return True # runner booted normally
|
|
92
|
+
status_data = trace_store.read_status(trace_id)
|
|
93
|
+
if status_data and status_data.get("status") not in ("running", "pending"):
|
|
94
|
+
return True # already terminal (e.g. stopped by the user)
|
|
95
|
+
time.sleep(poll_interval)
|
|
96
|
+
|
|
97
|
+
if any(os.path.exists(p) for p in log_paths):
|
|
98
|
+
return True
|
|
99
|
+
status_data = trace_store.read_status(trace_id)
|
|
100
|
+
if status_data and status_data.get("status") not in ("running", "pending"):
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
error_text = (
|
|
104
|
+
f"Background runner (pid {pid}) produced no logs within "
|
|
105
|
+
f"{deadline_seconds:.0f}s of spawn and was killed: the process hung "
|
|
106
|
+
"during interpreter startup. Restarting the MCP server usually clears "
|
|
107
|
+
"this; the task can be resubmitted afterwards."
|
|
108
|
+
)
|
|
109
|
+
_kill_process_tree(pid)
|
|
110
|
+
DeviceExecutionLock.cancel_reservation(queue_ticket)
|
|
111
|
+
trace_store.update_trace_status(trace_id, "failed", error=error_text)
|
|
112
|
+
if conversation_id:
|
|
113
|
+
try:
|
|
114
|
+
notify(
|
|
115
|
+
conversation_id=conversation_id,
|
|
116
|
+
message=f"Artemis task '{trace_id}' failed to start: {error_text}",
|
|
117
|
+
event_type="failed",
|
|
118
|
+
payload={"trace_id": trace_id, "error": error_text},
|
|
119
|
+
)
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
# Notification dispatch is best-effort; the failure verdict is
|
|
122
|
+
# already persisted in status.json.
|
|
123
|
+
logging.getLogger("mcp_server").debug(
|
|
124
|
+
"Spawn-failure notification skipped for trace %s: %s",
|
|
125
|
+
trace_id,
|
|
126
|
+
exc,
|
|
127
|
+
exc_info=True,
|
|
128
|
+
)
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _start_spawn_watchdog(
|
|
133
|
+
trace_id: str,
|
|
134
|
+
pid: int,
|
|
135
|
+
queue_ticket: str,
|
|
136
|
+
conversation_id: str | None,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Run _watch_spawn on a daemon thread so the tool call returns immediately."""
|
|
139
|
+
threading.Thread(
|
|
140
|
+
target=_watch_spawn,
|
|
141
|
+
args=(trace_id, pid, queue_ticket, conversation_id),
|
|
142
|
+
name=f"spawn-watchdog-{trace_id[:8]}",
|
|
143
|
+
daemon=True,
|
|
144
|
+
).start()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _validate_device_serial(device_serial: str) -> dict[str, Any] | None:
|
|
148
|
+
"""Reject a task whose explicitly requested device is not attached and authorized.
|
|
149
|
+
|
|
150
|
+
Returns a failure response dict when the serial must be rejected, or None when
|
|
151
|
+
the device is usable. If device enumeration itself fails (no adb available),
|
|
152
|
+
validation is skipped rather than blocking task submission.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
# The shared validator fails open on an indeterminate/empty enumeration:
|
|
156
|
+
# the task proceeds and fails downstream with a clear no-device error.
|
|
157
|
+
detail = device_pool.validate_explicit_serial(device_serial)
|
|
158
|
+
except Exception:
|
|
159
|
+
return None
|
|
160
|
+
if detail is None:
|
|
161
|
+
return None
|
|
162
|
+
return {
|
|
163
|
+
"status": "failed",
|
|
164
|
+
"error": detail,
|
|
165
|
+
"message": (
|
|
166
|
+
f"{detail} Task rejected to prevent execution on an unintended device. "
|
|
167
|
+
"Run `adb devices -l` to inspect attached hardware, then resubmit with a "
|
|
168
|
+
"valid serial (or omit device_serial for automatic selection)."
|
|
169
|
+
),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_EXPLORER_MODES: tuple[str, ...] = get_args(ExplorerVersion)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _normalize_pro_tuning(
|
|
177
|
+
verification_level: str | None, explorer_mode: str | None
|
|
178
|
+
) -> tuple[str | None, str | None]:
|
|
179
|
+
"""Validate and normalise the Pro-profile tuning knobs (strip + lower).
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError: with a caller-facing message when a value is not a known
|
|
183
|
+
preset, so the tool rejects the call before any trace is created.
|
|
184
|
+
"""
|
|
185
|
+
level: str | None = None
|
|
186
|
+
if verification_level is not None and str(verification_level).strip():
|
|
187
|
+
level = str(verification_level).strip().lower()
|
|
188
|
+
try:
|
|
189
|
+
checker_overrides_for_level(level)
|
|
190
|
+
except ValueError as exc:
|
|
191
|
+
raise ValueError(f"Invalid verification_level: {exc}") from exc
|
|
192
|
+
|
|
193
|
+
mode: str | None = None
|
|
194
|
+
if explorer_mode is not None and str(explorer_mode).strip():
|
|
195
|
+
mode = str(explorer_mode).strip().lower()
|
|
196
|
+
if mode not in _EXPLORER_MODES:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"Invalid explorer_mode {explorer_mode!r}. Must be one of: "
|
|
199
|
+
+ ", ".join(_EXPLORER_MODES)
|
|
200
|
+
)
|
|
201
|
+
return level, mode
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@mcp.tool()
|
|
205
|
+
def mobile_run_task(
|
|
206
|
+
task_desc: str,
|
|
207
|
+
conversation_id: str | None = None,
|
|
208
|
+
model: str = "Flash",
|
|
209
|
+
locked_app_package: str | None = None,
|
|
210
|
+
app_path: str | None = None,
|
|
211
|
+
expected_output_desc: str | None = None,
|
|
212
|
+
device_serial: str | None = None,
|
|
213
|
+
verification_level: str | None = None,
|
|
214
|
+
explorer_mode: str | None = None,
|
|
215
|
+
) -> dict[str, Any]:
|
|
216
|
+
"""Starts an autonomous mobile UI automation subagent on a connected Android device.
|
|
217
|
+
|
|
218
|
+
Delegates a mobile workflow to a background agent. Non-blocking: returns
|
|
219
|
+
immediately with `trace_id` (for `mobile_manage_task` / `mobile_inspect_trace`),
|
|
220
|
+
`device_serial`, `stdout_log`/`stderr_log` paths, and `notes_dir` (Pro only).
|
|
221
|
+
On completion (success or failure) a Reactive Wakeup notifies you — but only
|
|
222
|
+
when `conversation_id` was provided; without it, rely on your fallback timer.
|
|
223
|
+
|
|
224
|
+
### Model selection (routing)
|
|
225
|
+
- **Flash** (default, preferred): simple, deterministic tasks with a clear
|
|
226
|
+
UI path. No step cap by default (history is compressed, and the agent
|
|
227
|
+
can recall earlier steps and query the session recording), but no ADB
|
|
228
|
+
shell, no pre-execution safety net, no persistent plan or notes, and
|
|
229
|
+
no verification or written report — unsuitable for exploration,
|
|
230
|
+
multi-branch recovery, structured monitoring/polling, or tasks that
|
|
231
|
+
must report back large amounts of detail.
|
|
232
|
+
- **Pro**: complex, exploratory, or multi-branch tasks; continuous
|
|
233
|
+
monitoring/polling; tasks needing ADB shell, system logs, verified
|
|
234
|
+
checkpoints, multi-step planning, or detailed written output (via notes
|
|
235
|
+
and `expected_output_desc`).
|
|
236
|
+
|
|
237
|
+
Timing is not precise: the agent's own inference adds ~5 s per step (Flash)
|
|
238
|
+
or ~30 s per turn (Pro) on top of any requested waits — account for this
|
|
239
|
+
when the task involves waiting. For recurring workflows, run once to
|
|
240
|
+
discover the path, then author a deterministic script instead of
|
|
241
|
+
re-delegating.
|
|
242
|
+
|
|
243
|
+
### Follow-up protocol (CRITICAL)
|
|
244
|
+
1. **Fallback timer**: you MUST set a background timer and check the task
|
|
245
|
+
status via `mobile_manage_task` at least once every 1 minute — mobile
|
|
246
|
+
tasks can stall silently, so never rely on the completion wakeup alone.
|
|
247
|
+
2. **Post-task inspection**: after completion, inspect the execution logs
|
|
248
|
+
to verify success (`stderr_log` carries critical failures and Python
|
|
249
|
+
tracebacks — essential for debugging).
|
|
250
|
+
3. **Pro notes**: for Pro runs, read `notes_dir` to extract the results and
|
|
251
|
+
plans the subagent recorded.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
task_desc: Clear, self-contained task description with all context,
|
|
255
|
+
exact UI actions, and termination conditions (execution is one-shot).
|
|
256
|
+
For Pro, may also specify what the subagent must record in its notes.
|
|
257
|
+
conversation_id: Optional conversation ID used to route the completion
|
|
258
|
+
wakeup notification back to the caller. Omit if unknown.
|
|
259
|
+
model: `"Flash"` or `"Pro"` — see model selection above.
|
|
260
|
+
locked_app_package: Optional package name to lock execution to; the
|
|
261
|
+
agent auto-launches it and restricts actions to that app.
|
|
262
|
+
app_path: Optional local APK path to install before running.
|
|
263
|
+
expected_output_desc: Optional, Pro only. If set, a summarization agent
|
|
264
|
+
writes a report to `output.md` in `notes_dir`. Ignored for Flash.
|
|
265
|
+
device_serial: Optional device serial (e.g. "emulator-5554") to bind
|
|
266
|
+
execution to a specific device; distinct devices run concurrently.
|
|
267
|
+
If omitted, an available device is selected automatically. When
|
|
268
|
+
several devices are attached, confirm the target with the user first
|
|
269
|
+
(`adb devices -l` lists serials and authorization states).
|
|
270
|
+
verification_level: Optional, Pro only. Coarse Checker preset: `"off"`
|
|
271
|
+
(no audit; the Operator self-reports), `"final"` (one exit review
|
|
272
|
+
against the goal, the default), `"checkpoints"` (every plan
|
|
273
|
+
checkpoint is verified + exit review), `"strict"` (checkpoints with
|
|
274
|
+
a larger repair budget; a failed assert halts the run). Ignored for
|
|
275
|
+
Flash.
|
|
276
|
+
explorer_mode: Optional, Pro only. Explorer perception version used
|
|
277
|
+
by the Operator: `"flash"` (1-shot detection, the default),
|
|
278
|
+
`"pro"` (3-turn ReAct), `"ultra"` (deep pixel reasoning; slowest).
|
|
279
|
+
Ignored for Flash.
|
|
280
|
+
"""
|
|
281
|
+
# 0. Validate and normalize model
|
|
282
|
+
if model.lower() not in ("flash", "pro"):
|
|
283
|
+
raise ValueError(f"Invalid model '{model}'. Must be either 'Flash' or 'Pro'.")
|
|
284
|
+
canonical_model = "Flash" if model.lower() == "flash" else "Pro"
|
|
285
|
+
# 0b. Validate the Pro tuning knobs before any trace exists so a typo is a
|
|
286
|
+
# plain tool error rather than a failed trace on disk.
|
|
287
|
+
verification_level, explorer_mode = _normalize_pro_tuning(verification_level, explorer_mode)
|
|
288
|
+
|
|
289
|
+
# 1. Generate a unique trace_id
|
|
290
|
+
trace_id = str(uuid.uuid4())
|
|
291
|
+
|
|
292
|
+
# 2. Initialize the trace store directory and status.json
|
|
293
|
+
trace_store.init_trace(
|
|
294
|
+
trace_id=trace_id,
|
|
295
|
+
task_desc=task_desc,
|
|
296
|
+
model=canonical_model,
|
|
297
|
+
conversation_id=conversation_id,
|
|
298
|
+
device_serial=device_serial,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# 2b. Strict device binding: an explicitly requested serial must be attached and
|
|
302
|
+
# authorized. Rejecting here prevents the task from silently running on a
|
|
303
|
+
# different device than the caller asked for. Runs after init_trace so the
|
|
304
|
+
# rejection carries a trace_id like every other response of this tool.
|
|
305
|
+
if device_serial:
|
|
306
|
+
rejection = _validate_device_serial(device_serial)
|
|
307
|
+
if rejection:
|
|
308
|
+
trace_store.update_trace_status(trace_id, "failed", error=rejection["error"])
|
|
309
|
+
return {"trace_id": trace_id, **rejection}
|
|
310
|
+
|
|
311
|
+
# 3. Dispatch via unified Artemis Daemon scheduler if available (unless standalone forced)
|
|
312
|
+
if os.environ.get("ARTEMIS_STANDALONE") != "1":
|
|
313
|
+
try:
|
|
314
|
+
is_running, base_url = ensure_daemon_running(timeout=2.0, wait_ready=True)
|
|
315
|
+
if is_running:
|
|
316
|
+
resp = submit_task_to_daemon(
|
|
317
|
+
goal=task_desc,
|
|
318
|
+
profile=canonical_model.lower(),
|
|
319
|
+
device_serial=device_serial,
|
|
320
|
+
expected_output=expected_output_desc if canonical_model != "Flash" else None,
|
|
321
|
+
locked_app_package=locked_app_package,
|
|
322
|
+
app_path=app_path,
|
|
323
|
+
session_id=trace_id,
|
|
324
|
+
ingress="mcp",
|
|
325
|
+
conversation_id=conversation_id,
|
|
326
|
+
verification_level=verification_level,
|
|
327
|
+
explorer_mode=explorer_mode,
|
|
328
|
+
base_url=base_url,
|
|
329
|
+
)
|
|
330
|
+
if resp and resp.get("status") == "rejected":
|
|
331
|
+
rejection_error = resp.get("error") or "Task rejected by Artemis Daemon."
|
|
332
|
+
trace_store.update_trace_status(trace_id, "failed", error=rejection_error)
|
|
333
|
+
return {
|
|
334
|
+
"trace_id": trace_id,
|
|
335
|
+
"status": "failed",
|
|
336
|
+
"error": rejection_error,
|
|
337
|
+
"message": f"Task rejected by Artemis Daemon: {rejection_error}",
|
|
338
|
+
}
|
|
339
|
+
if resp and resp.get("tasks"):
|
|
340
|
+
assigned_sid = resp["tasks"][0].get("session_id", trace_id)
|
|
341
|
+
trace_store.update_trace_status(
|
|
342
|
+
assigned_sid, "running", device_serial=device_serial
|
|
343
|
+
)
|
|
344
|
+
notes_dir = str(trace_store.get_trace_notes_dir(assigned_sid))
|
|
345
|
+
stdout_log = str(trace_store.get_trace_stdout_log_path(assigned_sid))
|
|
346
|
+
stderr_log = str(trace_store.get_trace_stderr_log_path(assigned_sid))
|
|
347
|
+
return {
|
|
348
|
+
"trace_id": assigned_sid,
|
|
349
|
+
"status": "running",
|
|
350
|
+
"message": (
|
|
351
|
+
f"Autonomous task '{task_desc}' enqueued via unified Artemis Daemon.\n"
|
|
352
|
+
f"Trace ID: {assigned_sid}\n"
|
|
353
|
+
f"Model: {canonical_model}\n"
|
|
354
|
+
f"Live telemetry: streaming to web workspace."
|
|
355
|
+
),
|
|
356
|
+
"notes_dir": notes_dir,
|
|
357
|
+
"stdout_log": stdout_log,
|
|
358
|
+
"stderr_log": stderr_log,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Check if the task was already enqueued despite a slow or None
|
|
362
|
+
# response. The probe reads a shared lock file, so transient
|
|
363
|
+
# read failures get a few retries; if it never succeeds the
|
|
364
|
+
# verdict is "unknown" -- reporting "failed" here for a task
|
|
365
|
+
# that did land in the queue would invite a duplicate submit.
|
|
366
|
+
probe_failed = False
|
|
367
|
+
for probe_attempt in range(3):
|
|
368
|
+
try:
|
|
369
|
+
queued_tasks = DeviceExecutionLock.get_queued_tasks()
|
|
370
|
+
except Exception as exc:
|
|
371
|
+
if probe_attempt < 2:
|
|
372
|
+
time.sleep(0.5)
|
|
373
|
+
continue
|
|
374
|
+
probe_failed = True
|
|
375
|
+
logging.getLogger("mcp_server").warning(
|
|
376
|
+
f"Could not verify daemon queue for '{trace_id}': {exc}"
|
|
377
|
+
)
|
|
378
|
+
break
|
|
379
|
+
for q in queued_tasks:
|
|
380
|
+
if str(q.get("session_id")) == str(trace_id):
|
|
381
|
+
trace_store.update_trace_status(
|
|
382
|
+
trace_id, "running", device_serial=device_serial
|
|
383
|
+
)
|
|
384
|
+
return {
|
|
385
|
+
"trace_id": trace_id,
|
|
386
|
+
"status": "running",
|
|
387
|
+
"message": (
|
|
388
|
+
f"Autonomous task '{task_desc}' enqueued via unified Artemis Daemon.\n"
|
|
389
|
+
f"Trace ID: {trace_id}\n"
|
|
390
|
+
f"Model: {canonical_model}\n"
|
|
391
|
+
f"Live telemetry: streaming to web workspace."
|
|
392
|
+
),
|
|
393
|
+
"notes_dir": str(trace_store.get_trace_notes_dir(trace_id)),
|
|
394
|
+
"stdout_log": str(trace_store.get_trace_stdout_log_path(trace_id)),
|
|
395
|
+
"stderr_log": str(trace_store.get_trace_stderr_log_path(trace_id)),
|
|
396
|
+
}
|
|
397
|
+
break
|
|
398
|
+
|
|
399
|
+
if probe_failed:
|
|
400
|
+
# The task may or may not have been enqueued; do not mark
|
|
401
|
+
# the trace failed and do not let the caller resubmit
|
|
402
|
+
# blindly.
|
|
403
|
+
return {
|
|
404
|
+
"trace_id": trace_id,
|
|
405
|
+
"status": "unknown",
|
|
406
|
+
"message": (
|
|
407
|
+
f"Artemis Daemon gave no enqueue confirmation for task "
|
|
408
|
+
f"'{task_desc}' and the queue could not be inspected. The "
|
|
409
|
+
f"task may still have been enqueued. Poll "
|
|
410
|
+
f"mobile_manage_task(action='status', trace_id='{trace_id}') "
|
|
411
|
+
f"before resubmitting; if it still reports 'pending' after "
|
|
412
|
+
f"about a minute, treat it as not enqueued."
|
|
413
|
+
),
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
# When Daemon is running, refuse to launch a conflicting standalone runner on the same device
|
|
417
|
+
logging.getLogger("mcp_server").warning(
|
|
418
|
+
f"Daemon dispatch failed for task '{task_desc}'. Aborting dispatch to prevent runner collision."
|
|
419
|
+
)
|
|
420
|
+
return {
|
|
421
|
+
"trace_id": trace_id,
|
|
422
|
+
"status": "failed",
|
|
423
|
+
"error": f"Failed to enqueue task '{task_desc}' to running Artemis Daemon scheduler.",
|
|
424
|
+
"message": "Task rejected or timed out in Artemis Daemon. Standalone fallback blocked to prevent runner collision.",
|
|
425
|
+
}
|
|
426
|
+
except Exception as exc:
|
|
427
|
+
logging.getLogger("mcp_server").warning(f"Daemon dispatch failed: {exc}")
|
|
428
|
+
return {
|
|
429
|
+
"trace_id": trace_id,
|
|
430
|
+
"status": "failed",
|
|
431
|
+
"error": f"Daemon dispatch error: {exc}",
|
|
432
|
+
"message": "Failed to dispatch task to Artemis Daemon.",
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# 4. Standalone Fallback: Resolve project root, python executable, and background runner module
|
|
436
|
+
project_root = env_utils.get_project_root()
|
|
437
|
+
python_exe = env_utils.resolve_python_executable(project_root)
|
|
438
|
+
reserve_kwargs: dict[str, Any] = {
|
|
439
|
+
"description": f"MCP task: {task_desc[:120]}",
|
|
440
|
+
"session_id": trace_id,
|
|
441
|
+
"ingress": "mcp",
|
|
442
|
+
}
|
|
443
|
+
if device_serial:
|
|
444
|
+
reserve_kwargs["device_id"] = device_serial
|
|
445
|
+
queue_ticket = DeviceExecutionLock.reserve(**reserve_kwargs)
|
|
446
|
+
|
|
447
|
+
# 5. Spawn the background task runner as an independent subprocess
|
|
448
|
+
try:
|
|
449
|
+
cmd = [
|
|
450
|
+
python_exe,
|
|
451
|
+
"-m",
|
|
452
|
+
"mcp_server.background.task_runner",
|
|
453
|
+
"--trace-id",
|
|
454
|
+
trace_id,
|
|
455
|
+
"--task-desc",
|
|
456
|
+
task_desc,
|
|
457
|
+
"--model",
|
|
458
|
+
canonical_model,
|
|
459
|
+
"--conversation-id",
|
|
460
|
+
conversation_id or "",
|
|
461
|
+
]
|
|
462
|
+
if locked_app_package:
|
|
463
|
+
cmd.extend(["--locked-app-package", locked_app_package])
|
|
464
|
+
if app_path:
|
|
465
|
+
cmd.extend(["--app-path", app_path])
|
|
466
|
+
if expected_output_desc and canonical_model != "Flash":
|
|
467
|
+
cmd.extend(["--expected-output-desc", expected_output_desc])
|
|
468
|
+
if device_serial:
|
|
469
|
+
cmd.extend(["--device-serial", device_serial])
|
|
470
|
+
if verification_level:
|
|
471
|
+
cmd.extend(["--verification-level", verification_level])
|
|
472
|
+
if explorer_mode:
|
|
473
|
+
cmd.extend(["--explorer-pro-mode", explorer_mode])
|
|
474
|
+
|
|
475
|
+
env = os.environ.copy()
|
|
476
|
+
env["ARTEMIS_SESSION_ID"] = trace_id
|
|
477
|
+
env["ARTEMIS_TASK_INGRESS"] = "mcp"
|
|
478
|
+
if device_serial:
|
|
479
|
+
env["ADB_DEVICE_SERIAL"] = device_serial
|
|
480
|
+
env["ARTEMIS_DEVICE_ID"] = device_serial
|
|
481
|
+
env[DeviceExecutionLock.QUEUE_TICKET_ENV] = queue_ticket
|
|
482
|
+
try:
|
|
483
|
+
ipc_port = read_ipc_port()
|
|
484
|
+
if ipc_port:
|
|
485
|
+
env["ARTEMIS_IPC_PORT"] = str(ipc_port)
|
|
486
|
+
except (OSError, ValueError):
|
|
487
|
+
# No readable IPC port file: the runner works without live telemetry.
|
|
488
|
+
pass
|
|
489
|
+
|
|
490
|
+
proc_kwargs = env_utils.get_detached_process_kwargs()
|
|
491
|
+
proc = subprocess.Popen(
|
|
492
|
+
cmd,
|
|
493
|
+
env=env,
|
|
494
|
+
cwd=project_root,
|
|
495
|
+
**proc_kwargs,
|
|
496
|
+
)
|
|
497
|
+
transfer_kwargs: dict[str, Any] = {
|
|
498
|
+
"description": f"MCP task: {task_desc[:120]}",
|
|
499
|
+
"session_id": trace_id,
|
|
500
|
+
"ingress": "mcp",
|
|
501
|
+
}
|
|
502
|
+
if device_serial:
|
|
503
|
+
transfer_kwargs["device_id"] = device_serial
|
|
504
|
+
|
|
505
|
+
DeviceExecutionLock.transfer_reservation(
|
|
506
|
+
queue_ticket,
|
|
507
|
+
proc.pid,
|
|
508
|
+
**transfer_kwargs,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
# 5. Record the PID of the spawned subprocess
|
|
512
|
+
status_data = trace_store.read_status(trace_id)
|
|
513
|
+
if status_data:
|
|
514
|
+
status_data["pid"] = proc.pid
|
|
515
|
+
status_data["queue_ticket"] = queue_ticket
|
|
516
|
+
trace_store.write_status(trace_id, status_data)
|
|
517
|
+
|
|
518
|
+
# 6. Arm a watchdog: a runner that never opens its log files is hung in
|
|
519
|
+
# interpreter startup and must be killed instead of squatting on the
|
|
520
|
+
# queue as 'running' forever.
|
|
521
|
+
_start_spawn_watchdog(trace_id, proc.pid, queue_ticket, conversation_id)
|
|
522
|
+
|
|
523
|
+
trace_dir = trace_store.get_trace_dir(trace_id)
|
|
524
|
+
stdout_log_path = os.path.join(trace_dir, "stdout.log")
|
|
525
|
+
stderr_log_path = os.path.join(trace_dir, "stderr.log")
|
|
526
|
+
|
|
527
|
+
response_dict: dict[str, Any] = {
|
|
528
|
+
"trace_id": trace_id,
|
|
529
|
+
"device_serial": device_serial or "auto-select",
|
|
530
|
+
"message": "Successfully started background task.",
|
|
531
|
+
"stdout_log": stdout_log_path,
|
|
532
|
+
"stderr_log": stderr_log_path,
|
|
533
|
+
}
|
|
534
|
+
if canonical_model != "Flash":
|
|
535
|
+
response_dict["notes_dir"] = os.path.join(trace_dir, "notes")
|
|
536
|
+
|
|
537
|
+
return response_dict
|
|
538
|
+
|
|
539
|
+
except Exception as e:
|
|
540
|
+
DeviceExecutionLock.cancel_reservation(queue_ticket)
|
|
541
|
+
trace_store.update_trace_status(
|
|
542
|
+
trace_id, "failed", error=f"Failed to spawn background runner: {e}"
|
|
543
|
+
)
|
|
544
|
+
return {
|
|
545
|
+
"trace_id": trace_id,
|
|
546
|
+
"status": "failed",
|
|
547
|
+
"message": f"Error starting background task: {e}",
|
|
548
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
"""MCP Server Utilities package."""
|