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,185 @@
|
|
|
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
|
+
"""Multi-tier cross-platform binary resolution and discovery engine."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import shutil
|
|
21
|
+
|
|
22
|
+
from artemis.platform import OSType, platform
|
|
23
|
+
from artemis.toolchain.descriptors import TOOLS, ToolDescriptor
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ToolchainResolver:
|
|
29
|
+
"""Discovers, validates, and resolves platform-specific binaries with graceful fallbacks."""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._cache: dict[str, str | None] = {}
|
|
33
|
+
|
|
34
|
+
def clear_cache(self, tool_name: str | None = None) -> None:
|
|
35
|
+
"""Invalidate cached tool paths to force fresh discovery."""
|
|
36
|
+
if tool_name:
|
|
37
|
+
self._cache.pop(tool_name.lower(), None)
|
|
38
|
+
else:
|
|
39
|
+
self._cache.clear()
|
|
40
|
+
|
|
41
|
+
def resolve(self, tool_name: str, force_refresh: bool = False) -> str | None:
|
|
42
|
+
"""Resolve absolute path to a tool across all discovery tiers."""
|
|
43
|
+
key = tool_name.lower()
|
|
44
|
+
if not force_refresh and key in self._cache:
|
|
45
|
+
cached_val = self._cache[key]
|
|
46
|
+
if cached_val is not None and Path(cached_val).exists():
|
|
47
|
+
return cached_val
|
|
48
|
+
# Cache miss or binary removed: proceed to re-discover
|
|
49
|
+
|
|
50
|
+
desc = TOOLS.get(key)
|
|
51
|
+
if desc is None:
|
|
52
|
+
# Generic binary lookup
|
|
53
|
+
res = shutil.which(tool_name)
|
|
54
|
+
if res and Path(res).exists():
|
|
55
|
+
self._cache[key] = str(Path(res).resolve())
|
|
56
|
+
return self._cache[key]
|
|
57
|
+
self._cache.pop(key, None)
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
binary_name = (
|
|
61
|
+
desc.win_binary_name if platform.os_type == OSType.WINDOWS else desc.binary_name
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Tier 1: Explicit environment variable override (e.g. ARTEMIS_ADB_PATH)
|
|
65
|
+
env_var = f"ARTEMIS_{desc.name.upper()}_PATH"
|
|
66
|
+
env_override = os.getenv(env_var)
|
|
67
|
+
if env_override and Path(env_override).exists():
|
|
68
|
+
resolved = str(Path(env_override).resolve())
|
|
69
|
+
self._cache[key] = resolved
|
|
70
|
+
return resolved
|
|
71
|
+
|
|
72
|
+
# Tier 2: SDK Environment Variables (for Android tools)
|
|
73
|
+
if desc.sdk_relative_path:
|
|
74
|
+
for sdk_env in ("ANDROID_HOME", "ANDROID_SDK_ROOT"):
|
|
75
|
+
sdk_dir = os.getenv(sdk_env)
|
|
76
|
+
if sdk_dir:
|
|
77
|
+
candidate = Path(sdk_dir) / desc.sdk_relative_path
|
|
78
|
+
if platform.os_type == OSType.WINDOWS:
|
|
79
|
+
candidate = candidate.with_suffix(".exe")
|
|
80
|
+
if candidate.exists():
|
|
81
|
+
resolved = str(candidate.resolve())
|
|
82
|
+
self._cache[key] = resolved
|
|
83
|
+
return resolved
|
|
84
|
+
|
|
85
|
+
# Tier 3: Common OS default installation paths
|
|
86
|
+
common_candidates = self._get_os_common_paths(desc)
|
|
87
|
+
for cand in common_candidates:
|
|
88
|
+
if cand.exists():
|
|
89
|
+
resolved = str(cand.resolve())
|
|
90
|
+
self._cache[key] = resolved
|
|
91
|
+
return resolved
|
|
92
|
+
|
|
93
|
+
# Tier 4: System PATH
|
|
94
|
+
which_path = shutil.which(binary_name) or shutil.which(desc.name)
|
|
95
|
+
if which_path:
|
|
96
|
+
resolved = str(Path(which_path).resolve())
|
|
97
|
+
self._cache[key] = resolved
|
|
98
|
+
return resolved
|
|
99
|
+
|
|
100
|
+
# Tier 5: Python library embedded binary fallback
|
|
101
|
+
if desc.embedded_fallback:
|
|
102
|
+
try:
|
|
103
|
+
embedded = desc.embedded_fallback()
|
|
104
|
+
if embedded and Path(embedded).exists():
|
|
105
|
+
resolved = str(Path(embedded).resolve())
|
|
106
|
+
self._cache[key] = resolved
|
|
107
|
+
return resolved
|
|
108
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
109
|
+
# Probing an optional third-party embedded binary; any failure
|
|
110
|
+
# simply means "tool not found" and the caller handles None.
|
|
111
|
+
logger.debug(
|
|
112
|
+
"Embedded binary fallback for %s skipped: %s", desc.name, exc, exc_info=True
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Do not permanently cache negative lookups so dynamic installs are detected immediately
|
|
116
|
+
self._cache.pop(key, None)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def _get_os_common_paths(self, desc: ToolDescriptor) -> list[Path]:
|
|
120
|
+
candidates: list[Path] = []
|
|
121
|
+
home = Path.home()
|
|
122
|
+
|
|
123
|
+
if platform.os_type == OSType.WINDOWS:
|
|
124
|
+
local_appdata = os.getenv("LOCALAPPDATA", "")
|
|
125
|
+
program_files = os.getenv("ProgramFiles", "C:\\Program Files")
|
|
126
|
+
program_data = os.getenv("ProgramData", "C:\\ProgramData")
|
|
127
|
+
|
|
128
|
+
if desc.name == "adb":
|
|
129
|
+
if local_appdata:
|
|
130
|
+
candidates.append(
|
|
131
|
+
Path(local_appdata) / "Android" / "Sdk" / "platform-tools" / "adb.exe"
|
|
132
|
+
)
|
|
133
|
+
candidates.append(Path(program_files) / "Android" / "platform-tools" / "adb.exe")
|
|
134
|
+
elif desc.name == "scrcpy":
|
|
135
|
+
candidates.append(Path(program_data) / "chocolatey" / "bin" / "scrcpy.exe")
|
|
136
|
+
if local_appdata:
|
|
137
|
+
candidates.append(
|
|
138
|
+
Path(local_appdata) / "Microsoft" / "WinGet" / "Links" / "scrcpy.exe"
|
|
139
|
+
)
|
|
140
|
+
elif desc.name == "ffmpeg":
|
|
141
|
+
candidates.append(Path(program_data) / "chocolatey" / "bin" / "ffmpeg.exe")
|
|
142
|
+
if local_appdata:
|
|
143
|
+
candidates.append(
|
|
144
|
+
Path(local_appdata) / "Microsoft" / "WinGet" / "Links" / "ffmpeg.exe"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
elif platform.os_type == OSType.MACOS:
|
|
148
|
+
if desc.name == "adb":
|
|
149
|
+
candidates.append(home / "Library" / "Android" / "sdk" / "platform-tools" / "adb")
|
|
150
|
+
candidates.append(Path("/opt/homebrew/bin/adb"))
|
|
151
|
+
candidates.append(Path("/usr/local/bin/adb"))
|
|
152
|
+
elif desc.name in ("ffmpeg", "scrcpy"):
|
|
153
|
+
candidates.append(Path(f"/opt/homebrew/bin/{desc.name}"))
|
|
154
|
+
candidates.append(Path(f"/usr/local/bin/{desc.name}"))
|
|
155
|
+
|
|
156
|
+
elif platform.os_type == OSType.LINUX:
|
|
157
|
+
if desc.name == "adb":
|
|
158
|
+
candidates.append(home / "Android" / "Sdk" / "platform-tools" / "adb")
|
|
159
|
+
candidates.append(Path("/usr/lib/android-sdk/platform-tools/adb"))
|
|
160
|
+
candidates.append(Path("/usr/bin/adb"))
|
|
161
|
+
elif desc.name in ("ffmpeg", "scrcpy"):
|
|
162
|
+
candidates.append(Path(f"/usr/bin/{desc.name}"))
|
|
163
|
+
candidates.append(Path(f"/usr/local/bin/{desc.name}"))
|
|
164
|
+
|
|
165
|
+
return candidates
|
|
166
|
+
|
|
167
|
+
def find_adb(self) -> str:
|
|
168
|
+
"""Find ADB executable or fallback to default name."""
|
|
169
|
+
return self.resolve("adb") or "adb"
|
|
170
|
+
|
|
171
|
+
def find_ffmpeg(self) -> str:
|
|
172
|
+
"""Find FFmpeg executable or fallback to default name."""
|
|
173
|
+
return self.resolve("ffmpeg") or "ffmpeg"
|
|
174
|
+
|
|
175
|
+
def find_scrcpy(self) -> str:
|
|
176
|
+
"""Find scrcpy executable or fallback to default name."""
|
|
177
|
+
return self.resolve("scrcpy") or "scrcpy"
|
|
178
|
+
|
|
179
|
+
def is_installed(self, tool_name: str) -> bool:
|
|
180
|
+
"""Check whether a tool is found on this system."""
|
|
181
|
+
return self.resolve(tool_name) is not None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Global toolchain resolver singleton
|
|
185
|
+
toolchain: ToolchainResolver = ToolchainResolver()
|
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
"""Universal Tool Protocol for ARTEMIS.
|
|
16
|
+
|
|
17
|
+
Enables tools to be authored once using standard Pydantic schemas and async handlers,
|
|
18
|
+
then automatically exported to LangChain Tools (Pro Graph), Google GenAI FunctionDeclarations
|
|
19
|
+
(FlashRunner), and MCP Tools (FastMCP) with zero code duplication.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
import concurrent.futures
|
|
25
|
+
import inspect
|
|
26
|
+
from typing import Annotated, Any, Literal
|
|
27
|
+
|
|
28
|
+
from google.genai import types as genai_types
|
|
29
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
30
|
+
from langgraph.prebuilt import InjectedState
|
|
31
|
+
from pydantic import BaseModel
|
|
32
|
+
|
|
33
|
+
from artemis.core.tool_declaration import ToolDeclaration
|
|
34
|
+
from artemis.drivers.base import BaseDeviceDriver
|
|
35
|
+
from artemis.drivers.factory import get_driver
|
|
36
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
ToolCategory = Literal["action", "perception", "system", "memory", "custom"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ArtemisTool:
|
|
44
|
+
"""Unified tool wrapper encapsulating schema, execution logic, and multi-protocol export."""
|
|
45
|
+
|
|
46
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
name: str,
|
|
50
|
+
description: str,
|
|
51
|
+
args_schema: type[BaseModel],
|
|
52
|
+
handler: Callable[..., Any] | None = None,
|
|
53
|
+
category: ToolCategory = "action",
|
|
54
|
+
):
|
|
55
|
+
self.name = name
|
|
56
|
+
self.description = description
|
|
57
|
+
self.args_schema = args_schema
|
|
58
|
+
self.handler = handler
|
|
59
|
+
self.category = category
|
|
60
|
+
|
|
61
|
+
def is_available(self, ctx: Any = None) -> bool:
|
|
62
|
+
"""Determines if this tool is currently available and should be exposed to agents."""
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
async def execute(self, driver: BaseDeviceDriver, ctx: Any, **kwargs: Any) -> Any:
|
|
66
|
+
"""Executes the tool handler with injected driver and context."""
|
|
67
|
+
# Inspect handler signature to only pass supported arguments
|
|
68
|
+
if self.handler is not None:
|
|
69
|
+
sig = inspect.signature(self.handler)
|
|
70
|
+
call_kwargs = {}
|
|
71
|
+
for param_name in sig.parameters:
|
|
72
|
+
if param_name == "driver":
|
|
73
|
+
call_kwargs["driver"] = driver
|
|
74
|
+
elif param_name in ("ctx", "context"):
|
|
75
|
+
call_kwargs[param_name] = ctx
|
|
76
|
+
elif param_name in kwargs:
|
|
77
|
+
call_kwargs[param_name] = kwargs[param_name]
|
|
78
|
+
|
|
79
|
+
if inspect.iscoroutinefunction(self.handler):
|
|
80
|
+
return await self.handler(**call_kwargs)
|
|
81
|
+
return self.handler(**call_kwargs)
|
|
82
|
+
raise NotImplementedError("Subclasses must implement execute() or provide a handler.")
|
|
83
|
+
|
|
84
|
+
async def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
85
|
+
"""Allows ArtemisTool instances to be invoked directly as callables."""
|
|
86
|
+
driver = kwargs.pop("driver", None)
|
|
87
|
+
ctx = kwargs.pop("ctx", None) or kwargs.pop("context", None)
|
|
88
|
+
return await self.execute(driver=driver, ctx=ctx, *args, **kwargs)
|
|
89
|
+
|
|
90
|
+
def to_langchain_tool(self, ctx: Any, name: str | None = None) -> BaseTool:
|
|
91
|
+
"""Exports this tool to a LangChain StructuredTool."""
|
|
92
|
+
|
|
93
|
+
def _get_context_driver() -> BaseDeviceDriver | None:
|
|
94
|
+
if (
|
|
95
|
+
ctx is not None
|
|
96
|
+
and hasattr(ctx, "device")
|
|
97
|
+
and ctx.device
|
|
98
|
+
and getattr(ctx.device, "device_id", None)
|
|
99
|
+
):
|
|
100
|
+
try:
|
|
101
|
+
return get_driver(ctx)
|
|
102
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
103
|
+
logger.debug(f"Failed to get driver: {e}")
|
|
104
|
+
return None
|
|
105
|
+
return getattr(ctx, "_active_driver", None) if ctx is not None else None
|
|
106
|
+
|
|
107
|
+
def _func(state: Annotated[Any, InjectedState] = None, **kwargs):
|
|
108
|
+
driver = _get_context_driver()
|
|
109
|
+
if state is not None:
|
|
110
|
+
kwargs["state"] = state
|
|
111
|
+
try:
|
|
112
|
+
loop = asyncio.get_running_loop()
|
|
113
|
+
except RuntimeError:
|
|
114
|
+
loop = None
|
|
115
|
+
|
|
116
|
+
if loop and loop.is_running():
|
|
117
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
118
|
+
return pool.submit(
|
|
119
|
+
asyncio.run, self.execute(driver=driver, ctx=ctx, **kwargs)
|
|
120
|
+
).result()
|
|
121
|
+
return asyncio.run(self.execute(driver=driver, ctx=ctx, **kwargs))
|
|
122
|
+
|
|
123
|
+
async def _coroutine(state: Annotated[Any, InjectedState] = None, **kwargs):
|
|
124
|
+
driver = _get_context_driver()
|
|
125
|
+
if state is not None:
|
|
126
|
+
kwargs["state"] = state
|
|
127
|
+
return await self.execute(driver=driver, ctx=ctx, **kwargs)
|
|
128
|
+
|
|
129
|
+
return StructuredTool.from_function(
|
|
130
|
+
func=_func,
|
|
131
|
+
coroutine=_coroutine,
|
|
132
|
+
name=name or self.name,
|
|
133
|
+
description=self.description,
|
|
134
|
+
args_schema=self.args_schema,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _flat_schema(self) -> tuple[dict[str, dict[str, Any]], list[str]]:
|
|
138
|
+
if not self.args_schema:
|
|
139
|
+
return {}, []
|
|
140
|
+
return _flatten_json_schema_properties(self.args_schema.model_json_schema())
|
|
141
|
+
|
|
142
|
+
def to_tool_declaration(self) -> ToolDeclaration:
|
|
143
|
+
"""Build the Flash declaration from the args_schema used by LangChain."""
|
|
144
|
+
properties, required = self._flat_schema()
|
|
145
|
+
return ToolDeclaration(
|
|
146
|
+
name=self.name,
|
|
147
|
+
description=self.description,
|
|
148
|
+
parameters={"type": "object", "properties": properties, "required": required},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def to_genai_declaration(self) -> genai_types.FunctionDeclaration:
|
|
152
|
+
"""Exports this tool to a Google GenAI FunctionDeclaration schema."""
|
|
153
|
+
flat_properties, required = self._flat_schema()
|
|
154
|
+
properties = {}
|
|
155
|
+
for prop_name, prop in flat_properties.items():
|
|
156
|
+
genai_type = _GENAI_TYPES.get(prop["type"], genai_types.Type.STRING)
|
|
157
|
+
items_schema = None
|
|
158
|
+
if genai_type == genai_types.Type.ARRAY:
|
|
159
|
+
item_type = (prop.get("items") or {}).get("type", "string")
|
|
160
|
+
items_schema = genai_types.Schema(
|
|
161
|
+
type=_GENAI_TYPES.get(item_type, genai_types.Type.STRING)
|
|
162
|
+
)
|
|
163
|
+
properties[prop_name] = genai_types.Schema(
|
|
164
|
+
type=genai_type,
|
|
165
|
+
description=prop.get("description", ""),
|
|
166
|
+
items=items_schema,
|
|
167
|
+
enum=[str(v) for v in prop["enum"]] if prop.get("enum") else None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return genai_types.FunctionDeclaration(
|
|
171
|
+
name=self.name,
|
|
172
|
+
description=self.description,
|
|
173
|
+
parameters=genai_types.Schema(
|
|
174
|
+
type=genai_types.Type.OBJECT,
|
|
175
|
+
properties=properties,
|
|
176
|
+
required=required,
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_GENAI_TYPES = {
|
|
182
|
+
"string": genai_types.Type.STRING,
|
|
183
|
+
"integer": genai_types.Type.INTEGER,
|
|
184
|
+
"number": genai_types.Type.NUMBER,
|
|
185
|
+
"boolean": genai_types.Type.BOOLEAN,
|
|
186
|
+
"array": genai_types.Type.ARRAY,
|
|
187
|
+
"object": genai_types.Type.OBJECT,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _flatten_property(prop_data: dict[str, Any]) -> dict[str, Any]:
|
|
192
|
+
"""One Pydantic JSON-schema property as a flat single-type entry.
|
|
193
|
+
|
|
194
|
+
``anyOf`` unions (``X | None``) collapse to their first non-null member;
|
|
195
|
+
``items`` (array element type) and ``enum`` (``Literal`` choices) are
|
|
196
|
+
carried over; unknown shapes fall back to ``string``.
|
|
197
|
+
"""
|
|
198
|
+
p_type = prop_data.get("type")
|
|
199
|
+
items = prop_data.get("items")
|
|
200
|
+
enum = prop_data.get("enum")
|
|
201
|
+
if not p_type and "anyOf" in prop_data:
|
|
202
|
+
for sub_schema in prop_data["anyOf"]:
|
|
203
|
+
sub_type = sub_schema.get("type")
|
|
204
|
+
if sub_type and sub_type != "null":
|
|
205
|
+
p_type = sub_type
|
|
206
|
+
items = items or sub_schema.get("items")
|
|
207
|
+
enum = enum or sub_schema.get("enum")
|
|
208
|
+
break
|
|
209
|
+
if not p_type:
|
|
210
|
+
p_type = "string"
|
|
211
|
+
entry: dict[str, Any] = {"type": p_type}
|
|
212
|
+
if p_type == "array":
|
|
213
|
+
entry["items"] = {"type": (items or {}).get("type") or "string"}
|
|
214
|
+
if enum:
|
|
215
|
+
entry["enum"] = list(enum)
|
|
216
|
+
return entry
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _flatten_json_schema_properties(
|
|
220
|
+
schema: dict[str, Any],
|
|
221
|
+
) -> tuple[dict[str, dict[str, Any]], list[str]]:
|
|
222
|
+
"""Flattens a Pydantic model JSON schema into plain JSON-schema properties
|
|
223
|
+
plus the required-field list. Descriptions are copied verbatim; non-null
|
|
224
|
+
defaults are kept under ``default``."""
|
|
225
|
+
properties: dict[str, dict[str, Any]] = {}
|
|
226
|
+
for prop_name, prop_data in (schema.get("properties") or {}).items():
|
|
227
|
+
entry = _flatten_property(prop_data)
|
|
228
|
+
entry["description"] = prop_data.get("description", "")
|
|
229
|
+
if prop_data.get("default") is not None:
|
|
230
|
+
entry["default"] = prop_data["default"]
|
|
231
|
+
properties[prop_name] = entry
|
|
232
|
+
return properties, list(schema.get("required") or [])
|