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,436 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import base64
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from jinja2 import Template
|
|
20
|
+
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
|
|
21
|
+
from langchain_core.tools import BaseTool
|
|
22
|
+
from pydantic import BaseModel, Field
|
|
23
|
+
|
|
24
|
+
from artemis.context import ArtemisContext
|
|
25
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
26
|
+
from artemis.data_engine.trace import CURRENT_TRACE_ID, TraceSpan, trace
|
|
27
|
+
from artemis.graph.state import State
|
|
28
|
+
from artemis.graph.visibility import strict_state
|
|
29
|
+
from artemis.services.llm import (
|
|
30
|
+
acomplete,
|
|
31
|
+
get_llm,
|
|
32
|
+
invoke_llm_with_timeout_message,
|
|
33
|
+
with_fallback,
|
|
34
|
+
)
|
|
35
|
+
from artemis.tools.index import get_tool_by_name, get_tools_from_wrappers
|
|
36
|
+
from artemis.tools.scratchpad import (
|
|
37
|
+
list_notes_wrapper,
|
|
38
|
+
read_note_wrapper,
|
|
39
|
+
save_note_wrapper,
|
|
40
|
+
update_note_wrapper,
|
|
41
|
+
)
|
|
42
|
+
from artemis.tools.tool_wrapper import (
|
|
43
|
+
get_tool_result_content,
|
|
44
|
+
invoke_tool_with_injection,
|
|
45
|
+
)
|
|
46
|
+
from artemis.utils.notes import get_note_file_path
|
|
47
|
+
from artemis.utils.plan_grammar import (
|
|
48
|
+
CHECKBOX_LINE_RE,
|
|
49
|
+
STATUS_CHARS,
|
|
50
|
+
parse_plan,
|
|
51
|
+
render_plan_grammar_spec,
|
|
52
|
+
)
|
|
53
|
+
from artemis.memory.context_policy import build_history_for
|
|
54
|
+
from artemis.utils.cython_compat import CyFunctionDetector
|
|
55
|
+
from third_party.mobile_use.utils.decorators import agent_lifecycle_logging
|
|
56
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
57
|
+
|
|
58
|
+
logger = get_logger(__name__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def validate_plan_format(content: str) -> tuple[bool, str]:
|
|
62
|
+
"""Validates a freshly written plan against the machine-channel grammar.
|
|
63
|
+
|
|
64
|
+
Single-sourced from :mod:`artemis.utils.plan_grammar`: any status character
|
|
65
|
+
the grammar accepts (including in-progress ``[/]``) is legal here too, so
|
|
66
|
+
the Planner can never reject a line the rest of the harness would parse.
|
|
67
|
+
"""
|
|
68
|
+
for line in content.splitlines():
|
|
69
|
+
stripped = line.strip()
|
|
70
|
+
if stripped.startswith("- [") and not CHECKBOX_LINE_RE.match(stripped):
|
|
71
|
+
allowed = ", ".join(f"'[{c}]'" for c in STATUS_CHARS)
|
|
72
|
+
return (
|
|
73
|
+
False,
|
|
74
|
+
f"Invalid task status in line: {line}. Must be one of {allowed}.",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if not parse_plan(content).items:
|
|
78
|
+
return (
|
|
79
|
+
False,
|
|
80
|
+
"Plan must contain at least one subgoal starting with '- [ ]'.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return True, ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _check_gates(ctx: ArtemisContext) -> tuple[bool, bool]:
|
|
87
|
+
"""(midway_checks, final_check): the two check gates, read separately so
|
|
88
|
+
the planner is taught the judgment moment that actually applies. With both
|
|
89
|
+
disabled, no check-related instruction reaches any planner prompt."""
|
|
90
|
+
setup = getattr(ctx, "execution_setup", None)
|
|
91
|
+
midway = bool(setup and getattr(setup, "midway_checks_enabled", False))
|
|
92
|
+
final = bool(setup and getattr(setup, "final_check_enabled", False))
|
|
93
|
+
return midway, final
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _checks_enabled(ctx: ArtemisContext) -> bool:
|
|
97
|
+
"""Config-driven prompt assembly gate (either check gate active)."""
|
|
98
|
+
return any(_check_gates(ctx))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_planner_system_blocks(prompts_data: dict, mode: str, include_checks: bool) -> list[str]:
|
|
102
|
+
"""Assembly point for the planner prompt's component list (§1.3): the
|
|
103
|
+
check-related block mounts only while the checking feature is active."""
|
|
104
|
+
blocks = list(prompts_data["modes"][mode]["system"])
|
|
105
|
+
if include_checks:
|
|
106
|
+
extra = "check_generation" if mode == "initial_plan" else "check_audit"
|
|
107
|
+
if extra in prompts_data.get("blocks", {}):
|
|
108
|
+
blocks.append(extra)
|
|
109
|
+
return blocks
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ValidationResult(BaseModel):
|
|
113
|
+
model_config = {"ignored_types": (CyFunctionDetector,)}
|
|
114
|
+
is_approved: bool = Field(
|
|
115
|
+
description=(
|
|
116
|
+
"True if the new plan still serves the initial goal and is coherent."
|
|
117
|
+
" False if you have a concrete concern (goal drift, broken loop"
|
|
118
|
+
" contract, weakened check standard, logical dead end)."
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
feedback: str = Field(
|
|
122
|
+
description=(
|
|
123
|
+
"If not approved: one or two sentences naming the concern and the"
|
|
124
|
+
" suggested correction. This is advisory feedback shown to the"
|
|
125
|
+
" Operator, not a rejection — the plan change stays applied."
|
|
126
|
+
" If approved, leave empty."
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def run_async_planner_validation(
|
|
132
|
+
ctx: ArtemisContext,
|
|
133
|
+
initial_goal: str,
|
|
134
|
+
content_before: str,
|
|
135
|
+
content_after: str,
|
|
136
|
+
operator_raw_thinking: str | None = None,
|
|
137
|
+
operator_native_thinking: str | None = None,
|
|
138
|
+
) -> dict:
|
|
139
|
+
"""Asynchronously validates a top-level plan change proposed by the Operator."""
|
|
140
|
+
logger.info("Starting Async Planner Validation...")
|
|
141
|
+
try:
|
|
142
|
+
prompts_path = Path(__file__).parent / "planner.json"
|
|
143
|
+
with open(prompts_path, encoding="utf-8") as f:
|
|
144
|
+
prompts_data = json.load(f)
|
|
145
|
+
|
|
146
|
+
midway, final = _check_gates(ctx)
|
|
147
|
+
include_checks = midway or final
|
|
148
|
+
mode_config = prompts_data["modes"]["validator"]
|
|
149
|
+
system_blocks = build_planner_system_blocks(prompts_data, "validator", include_checks)
|
|
150
|
+
system_content = "\n\n".join(prompts_data["blocks"][block] for block in system_blocks)
|
|
151
|
+
system_message = Template(system_content).render(
|
|
152
|
+
plan_grammar=render_plan_grammar_spec(midway=midway, final=final),
|
|
153
|
+
midway_checks=midway,
|
|
154
|
+
final_check=final,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
history_str = "No history available."
|
|
158
|
+
if ctx.data_engine:
|
|
159
|
+
try:
|
|
160
|
+
steps = ctx.data_engine.get_agent_friendly_steps()
|
|
161
|
+
history_str = build_history_for(
|
|
162
|
+
"planner",
|
|
163
|
+
"", # Pass empty string to avoid rendering the task plan in the history
|
|
164
|
+
steps,
|
|
165
|
+
"default",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"Failed to fetch execution history for validator: {e}")
|
|
170
|
+
|
|
171
|
+
human_content = mode_config["human"]
|
|
172
|
+
human_message = Template(human_content).render(
|
|
173
|
+
initial_goal=initial_goal,
|
|
174
|
+
content_before=content_before,
|
|
175
|
+
content_after=content_after,
|
|
176
|
+
operator_raw_thinking=operator_raw_thinking,
|
|
177
|
+
operator_native_thinking=operator_native_thinking,
|
|
178
|
+
history_str=history_str,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
messages = [
|
|
182
|
+
SystemMessage(content=system_message),
|
|
183
|
+
HumanMessage(content=human_message),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# Advisory review on the lightweight judge node (shared default with
|
|
187
|
+
# the pixel safety net), never the heavyweight Planner model.
|
|
188
|
+
llm = get_llm(ctx=ctx, name="planner_validation").with_structured_output(ValidationResult)
|
|
189
|
+
|
|
190
|
+
result: ValidationResult = await invoke_llm_with_timeout_message(llm.ainvoke(messages))
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"status": "success" if result.is_approved else "failed",
|
|
194
|
+
"feedback": result.feedback,
|
|
195
|
+
}
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error(f"Planner async validation failed: {e}")
|
|
198
|
+
# In case of error, we fallback to approving so we don't block the agent unnecessarily
|
|
199
|
+
return {
|
|
200
|
+
"status": "success",
|
|
201
|
+
"feedback": f"Validation failed with error: {e}",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class PlannerNode:
|
|
206
|
+
def __init__(self, ctx: ArtemisContext, tools: list[BaseTool] | None = None):
|
|
207
|
+
self.ctx = ctx
|
|
208
|
+
self.tools = tools or []
|
|
209
|
+
|
|
210
|
+
@agent_lifecycle_logging("Planner", logger)
|
|
211
|
+
@trace(type="agent", name="planner")
|
|
212
|
+
async def __call__(self, state: State):
|
|
213
|
+
state = strict_state(state, "planner")
|
|
214
|
+
prompts_path = Path(__file__).parent / "planner.json"
|
|
215
|
+
with open(prompts_path, encoding="utf-8") as f:
|
|
216
|
+
prompts_data = json.load(f)
|
|
217
|
+
|
|
218
|
+
mode = "initial_plan"
|
|
219
|
+
mode_config = prompts_data["modes"][mode]
|
|
220
|
+
|
|
221
|
+
midway, final = _check_gates(self.ctx)
|
|
222
|
+
include_checks = midway or final
|
|
223
|
+
# Assembly point (not in-template conditionals): check-generation
|
|
224
|
+
# instructions only mount while the checking feature is active, so a
|
|
225
|
+
# fully-disabled configuration yields a byte-identical planner prompt.
|
|
226
|
+
# Inside the mounted block, the judgment moment is worded per gate.
|
|
227
|
+
system_blocks = build_planner_system_blocks(prompts_data, mode, include_checks)
|
|
228
|
+
system_content = "\n\n".join(prompts_data["blocks"][block] for block in system_blocks)
|
|
229
|
+
|
|
230
|
+
system_message = Template(system_content).render(
|
|
231
|
+
plan_grammar=render_plan_grammar_spec(midway=midway, final=final),
|
|
232
|
+
midway_checks=midway,
|
|
233
|
+
final_check=final,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Get screenshot
|
|
237
|
+
screenshot_b64 = None
|
|
238
|
+
screenshot_path = state.latest_screenshot if hasattr(state, "latest_screenshot") else None
|
|
239
|
+
|
|
240
|
+
if screenshot_path and Path(screenshot_path).exists():
|
|
241
|
+
try:
|
|
242
|
+
with open(screenshot_path, "rb") as f:
|
|
243
|
+
screenshot_b64 = base64.b64encode(f.read()).decode("utf-8")
|
|
244
|
+
logger.info(f"Planner loaded screenshot from file: {screenshot_path}")
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"Failed to read screenshot from {screenshot_path}: {e}")
|
|
247
|
+
|
|
248
|
+
# Fallback for Initial Plan: if no screenshot in state, take one on the spot
|
|
249
|
+
if not screenshot_b64:
|
|
250
|
+
logger.info("No screenshot found in state. Taking screenshot for Initial Plan...")
|
|
251
|
+
try:
|
|
252
|
+
controller = UnifiedMobileController(self.ctx)
|
|
253
|
+
screenshot_b64 = await controller.take_screenshot()
|
|
254
|
+
|
|
255
|
+
# Save to Data Engine if available to keep trace
|
|
256
|
+
if self.ctx.data_engine:
|
|
257
|
+
img_bytes = base64.b64decode(screenshot_b64)
|
|
258
|
+
image_name = self.ctx.data_engine.get_or_create_image(img_bytes)
|
|
259
|
+
saved_path = str(self.ctx.data_engine.get_image_path(image_name))
|
|
260
|
+
# Update local state object so it can be used later if needed
|
|
261
|
+
state.latest_screenshot = saved_path
|
|
262
|
+
logger.info(f"Saved initial plan screenshot to {saved_path}")
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"Failed to take screenshot for Initial Plan: {e}")
|
|
265
|
+
|
|
266
|
+
if not screenshot_b64:
|
|
267
|
+
logger.error("Planner failed to obtain a screenshot.")
|
|
268
|
+
raise ValueError("Planner failed to obtain a screenshot.")
|
|
269
|
+
|
|
270
|
+
human_content = mode_config["human"]
|
|
271
|
+
render_kwargs = {
|
|
272
|
+
"initial_goal": state.initial_goal,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
human_message = Template(human_content).render(**render_kwargs)
|
|
276
|
+
|
|
277
|
+
human_message_content = [{"type": "text", "text": human_message}]
|
|
278
|
+
if screenshot_b64:
|
|
279
|
+
human_message_content.append(
|
|
280
|
+
{
|
|
281
|
+
"type": "image_url",
|
|
282
|
+
"image_url": {"url": f"data:image/jpeg;base64,{screenshot_b64}"},
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
messages = [
|
|
286
|
+
SystemMessage(content=system_message),
|
|
287
|
+
HumanMessage(content=human_message_content),
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
llm = get_llm(ctx=self.ctx, name="planner")
|
|
291
|
+
llm_fallback = get_llm(ctx=self.ctx, name="planner", use_fallback=True)
|
|
292
|
+
|
|
293
|
+
# Ensure tools are populated (fallback if not passed in __init__, e.g. in tests)
|
|
294
|
+
if not self.tools:
|
|
295
|
+
note_wrappers = [
|
|
296
|
+
save_note_wrapper,
|
|
297
|
+
read_note_wrapper,
|
|
298
|
+
update_note_wrapper,
|
|
299
|
+
list_notes_wrapper,
|
|
300
|
+
]
|
|
301
|
+
self.tools = get_tools_from_wrappers(self.ctx, note_wrappers)
|
|
302
|
+
|
|
303
|
+
all_tools = list(self.tools)
|
|
304
|
+
|
|
305
|
+
if all_tools:
|
|
306
|
+
llm = llm.bind_tools(all_tools)
|
|
307
|
+
llm_fallback = llm_fallback.bind_tools(all_tools)
|
|
308
|
+
|
|
309
|
+
current_messages = messages
|
|
310
|
+
max_validation_attempts = 3
|
|
311
|
+
max_iterations = 10
|
|
312
|
+
|
|
313
|
+
for validation_attempt in range(max_validation_attempts):
|
|
314
|
+
tool_called = False
|
|
315
|
+
|
|
316
|
+
for _ in range(max_iterations):
|
|
317
|
+
response = await with_fallback(
|
|
318
|
+
main_call=lambda: invoke_llm_with_timeout_message(
|
|
319
|
+
acomplete(llm, current_messages)
|
|
320
|
+
),
|
|
321
|
+
fallback_call=lambda: invoke_llm_with_timeout_message(
|
|
322
|
+
acomplete(llm_fallback, current_messages)
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if response is None or not response.tool_calls:
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
current_messages.append(response)
|
|
330
|
+
|
|
331
|
+
for tc in response.tool_calls:
|
|
332
|
+
tool_name = tc["name"]
|
|
333
|
+
logger.info(f"Planner called tool: {tool_name} with args: {tc['args']}")
|
|
334
|
+
if ":" in tool_name:
|
|
335
|
+
tool_to_run = get_tool_by_name(tool_name, all_tools)
|
|
336
|
+
else:
|
|
337
|
+
tool_to_run = next((t for t in all_tools if t.name == tool_name), None)
|
|
338
|
+
|
|
339
|
+
if tool_to_run and tool_to_run.name in [
|
|
340
|
+
"save_note",
|
|
341
|
+
"update_note",
|
|
342
|
+
]:
|
|
343
|
+
tool_called = True
|
|
344
|
+
if tool_to_run:
|
|
345
|
+
args = dict(tc["args"])
|
|
346
|
+
with TraceSpan(name=tool_name, ctx=self.ctx) as span:
|
|
347
|
+
span.payload = {"args": args}
|
|
348
|
+
try:
|
|
349
|
+
logger.info(
|
|
350
|
+
f"Tool {tool_name} expected args:"
|
|
351
|
+
f" {list(tool_to_run.args.keys())}"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
logger.info(f"Invoking {tool_name} with args: {list(args.keys())}")
|
|
355
|
+
result_obj = await invoke_tool_with_injection(
|
|
356
|
+
tool=tool_to_run,
|
|
357
|
+
args=args,
|
|
358
|
+
tool_call_id=tc["id"],
|
|
359
|
+
state=state,
|
|
360
|
+
)
|
|
361
|
+
result_content = get_tool_result_content(result_obj)
|
|
362
|
+
span.result = result_content
|
|
363
|
+
|
|
364
|
+
current_messages.append(
|
|
365
|
+
ToolMessage(
|
|
366
|
+
tool_call_id=tc["id"],
|
|
367
|
+
content=result_content,
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
except Exception as e:
|
|
371
|
+
span.status = "failed"
|
|
372
|
+
span.error = str(e)
|
|
373
|
+
current_messages.append(
|
|
374
|
+
ToolMessage(
|
|
375
|
+
tool_call_id=tc["id"],
|
|
376
|
+
content=f"Error: {e}",
|
|
377
|
+
status="error",
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
current_messages.append(
|
|
382
|
+
ToolMessage(
|
|
383
|
+
tool_call_id=tc["id"],
|
|
384
|
+
content=f"Error: Tool {tool_name} not found",
|
|
385
|
+
status="error",
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Post-execution validation
|
|
390
|
+
plan_exists = False
|
|
391
|
+
plan_valid = False
|
|
392
|
+
error_message = ""
|
|
393
|
+
|
|
394
|
+
if not tool_called:
|
|
395
|
+
error_message = (
|
|
396
|
+
"You did not call `save_note` or `update_note` to submit/update the plan."
|
|
397
|
+
)
|
|
398
|
+
elif self.ctx.data_engine:
|
|
399
|
+
task_plan_path = get_note_file_path(self.ctx.data_engine.base_dir, "task_plan")
|
|
400
|
+
if task_plan_path.exists():
|
|
401
|
+
plan_exists = True
|
|
402
|
+
try:
|
|
403
|
+
content = task_plan_path.read_text(encoding="utf-8")
|
|
404
|
+
plan_valid, error_message = validate_plan_format(content)
|
|
405
|
+
except Exception as e:
|
|
406
|
+
error_message = f"Failed to read task plan for validation: {e}"
|
|
407
|
+
else:
|
|
408
|
+
error_message = (
|
|
409
|
+
"Task plan under key 'task_plan' does not exist despite tool call."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if tool_called and plan_exists and plan_valid:
|
|
413
|
+
logger.info("Plan submitted and validated successfully.")
|
|
414
|
+
break
|
|
415
|
+
|
|
416
|
+
logger.warning(
|
|
417
|
+
f"Plan validation failed (attempt {validation_attempt + 1}): {error_message}"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Feedback to model
|
|
421
|
+
current_messages.append(
|
|
422
|
+
HumanMessage(
|
|
423
|
+
content=(
|
|
424
|
+
f"Plan validation failed: {error_message}\nPlease"
|
|
425
|
+
" correct the plan and try again by calling the"
|
|
426
|
+
" appropriate tool."
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
else:
|
|
432
|
+
logger.error(
|
|
433
|
+
f"Failed to generate a valid plan after {max_validation_attempts} attempts."
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return {}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
"""Helpers for assembling tool references into otherwise-static prompts.
|
|
16
|
+
|
|
17
|
+
Prompts stay hand-written text; only the spots that mention tools are assembled:
|
|
18
|
+
|
|
19
|
+
* **Enumeration slots** -- lists like ``(`click`, `swipe`, ... , or `wait_for_delay`)``
|
|
20
|
+
are re-rendered from the actually-available tool set, so an absent tool never
|
|
21
|
+
appears. With the full tool set the rendering reproduces the original wording
|
|
22
|
+
byte-for-byte (ordering, backticks, Oxford ``or``/``and``), which is what the
|
|
23
|
+
regression tests pin.
|
|
24
|
+
* **Instruction segments** -- self-contained blocks that teach one tool (e.g. the
|
|
25
|
+
``manage_app`` app-launching rule) are included only while that tool exists.
|
|
26
|
+
|
|
27
|
+
An unavailable tool therefore leaves no trace in the prompt at all -- the executable
|
|
28
|
+
definition of "an unimplemented tool must cost the model nothing".
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from collections.abc import Iterable, Sequence, Set
|
|
32
|
+
|
|
33
|
+
__all__ = ["gate_segment", "render_tool_enum", "resolve_available"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def render_tool_enum(
|
|
37
|
+
tools: Sequence[str],
|
|
38
|
+
available: Set[str],
|
|
39
|
+
final_sep: str | None = None,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Renders an ordered, backticked tool enumeration limited to available tools.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
tools: Tool names in the exact order the prompt historically used.
|
|
45
|
+
available: Tools that currently exist.
|
|
46
|
+
final_sep: ``"or"``/``"and"`` to join the last item Oxford-style
|
|
47
|
+
(``"`a`, `b`, or `c`"``); ``None`` for a plain comma list.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The rendered enumeration, or ``""`` when none of the tools are available.
|
|
51
|
+
"""
|
|
52
|
+
ticked = [f"`{name}`" for name in tools if name in available]
|
|
53
|
+
if not ticked:
|
|
54
|
+
return ""
|
|
55
|
+
if len(ticked) == 1:
|
|
56
|
+
return ticked[0]
|
|
57
|
+
if final_sep is None:
|
|
58
|
+
return ", ".join(ticked)
|
|
59
|
+
if len(ticked) == 2:
|
|
60
|
+
return f"{ticked[0]} {final_sep} {ticked[1]}"
|
|
61
|
+
return ", ".join(ticked[:-1]) + f", {final_sep} " + ticked[-1]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def gate_segment(segment: str, available: Set[str], *tools: str) -> str:
|
|
65
|
+
"""Returns ``segment`` when every tool it teaches is available, else ``""``."""
|
|
66
|
+
if all(name in available for name in tools):
|
|
67
|
+
return segment
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_available(available: Iterable[str] | None, full_set: Set[str]) -> Set[str]:
|
|
72
|
+
"""Normalizes an optional availability argument.
|
|
73
|
+
|
|
74
|
+
``None`` means "everything" -- callers that have no actuator wired yet keep
|
|
75
|
+
today's output unchanged. Passing an explicit set opts into assembly.
|
|
76
|
+
"""
|
|
77
|
+
if available is None:
|
|
78
|
+
return full_set
|
|
79
|
+
return frozenset(available)
|