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,234 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import base64
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from artemis.context import ArtemisContext
|
|
22
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
23
|
+
from artemis.data_engine.trace import trace
|
|
24
|
+
from artemis.graph.state import State
|
|
25
|
+
from artemis.graph.visibility import strict_state
|
|
26
|
+
from artemis.utils.ocr_api import is_ocr_configured, perform_ocr
|
|
27
|
+
from artemis.utils.ocr_xml_fusion import (
|
|
28
|
+
_crop_image_remove_status_bar,
|
|
29
|
+
_detect_status_bar_height,
|
|
30
|
+
_map_coordinates_back,
|
|
31
|
+
fuse_ocr_with_xml,
|
|
32
|
+
)
|
|
33
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _should_skip_settling(state: State) -> bool:
|
|
39
|
+
"""Determine if we should skip screen settling based on the last executed action.
|
|
40
|
+
|
|
41
|
+
Purely logical, non-UI-drawing, or wait operations don't require dynamic
|
|
42
|
+
settling checks.
|
|
43
|
+
"""
|
|
44
|
+
if not state or not state.structured_decisions:
|
|
45
|
+
# First turn or no prior action, skip settling
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
actions = json.loads(state.structured_decisions)
|
|
50
|
+
if not actions or not isinstance(actions, list):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
last_action = actions[-1]
|
|
54
|
+
action_name = last_action.get("action")
|
|
55
|
+
|
|
56
|
+
# Actions that are known to have already completed full wait loops or don't render UI
|
|
57
|
+
skip_actions = {
|
|
58
|
+
"wait_for_delay",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if action_name in skip_actions:
|
|
62
|
+
logger.info(f"Last action was '{action_name}'. Skipping dynamic screen settling.")
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
return False
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.warning(
|
|
68
|
+
f"Failed to parse last action for settling heuristic: {e}."
|
|
69
|
+
" Defaulting to settling check."
|
|
70
|
+
)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@trace(type="agent", name="perception")
|
|
75
|
+
def _check_injected_instruction_file(base_dir: str) -> dict | None:
|
|
76
|
+
"""Consumes a pending injected-instruction payload, if any.
|
|
77
|
+
|
|
78
|
+
Returns the payload dict: `instruction` (str | None) plus the explicit
|
|
79
|
+
`release_loop` stop signal (bool) declared by the injecting caller.
|
|
80
|
+
"""
|
|
81
|
+
instruction_path = Path(base_dir) / "injected_instruction.json"
|
|
82
|
+
if instruction_path.exists():
|
|
83
|
+
with open(instruction_path, encoding="utf-8") as f:
|
|
84
|
+
data = json.load(f)
|
|
85
|
+
instruction_path.unlink()
|
|
86
|
+
payload = {
|
|
87
|
+
"instruction": data.get("instruction"),
|
|
88
|
+
"release_loop": bool(data.get("release_loop")),
|
|
89
|
+
}
|
|
90
|
+
logger.info(f"Read and deleted injected instruction payload: {payload}")
|
|
91
|
+
return payload
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _unprotect_current_check_lines(ctx: ArtemisContext) -> None:
|
|
96
|
+
"""Record the plan's current check lines as editable under user guidance."""
|
|
97
|
+
from artemis.utils.notes import get_note_file_path
|
|
98
|
+
from artemis.utils.plan_grammar import parse_plan
|
|
99
|
+
|
|
100
|
+
unprotected = getattr(ctx, "guidance_unprotected_checks", None)
|
|
101
|
+
if not isinstance(unprotected, set):
|
|
102
|
+
unprotected = set()
|
|
103
|
+
try:
|
|
104
|
+
plan_path = get_note_file_path(ctx.data_engine.base_dir, "task_plan")
|
|
105
|
+
text = plan_path.read_text(encoding="utf-8") if plan_path.exists() else ""
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.warning(f"Could not read task_plan for the guidance check-line waiver: {e}")
|
|
108
|
+
text = ""
|
|
109
|
+
added = {(ci.kind, ci.text) for ci in parse_plan(text).check_items}
|
|
110
|
+
unprotected |= added
|
|
111
|
+
ctx.guidance_unprotected_checks = unprotected
|
|
112
|
+
logger.info(
|
|
113
|
+
f"User guidance received: {len(added)} declared check line(s) may now be edited"
|
|
114
|
+
" by the Operator."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def perception_node(state: State, ctx: ArtemisContext) -> dict:
|
|
119
|
+
"""Perception node (fast path): captures screenshot, extracts XML hierarchy, and performs OCR.
|
|
120
|
+
|
|
121
|
+
Always executes at the start of an operator cycle.
|
|
122
|
+
"""
|
|
123
|
+
state = strict_state(state, "perception")
|
|
124
|
+
logger.info("Entering Perception Node...")
|
|
125
|
+
|
|
126
|
+
if not ctx.data_engine.current_step_id:
|
|
127
|
+
ctx.data_engine.allocate_step_id()
|
|
128
|
+
logger.info(f"Pre-allocated step ID in Perception Node: {ctx.data_engine.current_step_id}")
|
|
129
|
+
|
|
130
|
+
# Check for injected instruction without blocking event loop
|
|
131
|
+
injected_instruction = None
|
|
132
|
+
user_stop_requested = False
|
|
133
|
+
if ctx.data_engine and ctx.data_engine.base_dir:
|
|
134
|
+
injected_payload = await asyncio.to_thread(
|
|
135
|
+
_check_injected_instruction_file, str(ctx.data_engine.base_dir)
|
|
136
|
+
)
|
|
137
|
+
if injected_payload:
|
|
138
|
+
injected_instruction = injected_payload.get("instruction")
|
|
139
|
+
user_stop_requested = bool(injected_payload.get("release_loop"))
|
|
140
|
+
if isinstance(injected_instruction, str) and injected_instruction.strip():
|
|
141
|
+
# User guidance outranks the plan's declared standards: every
|
|
142
|
+
# check line that exists right now loses its machine
|
|
143
|
+
# restoration for the rest of the run (kept on the run context,
|
|
144
|
+
# not the per-turn State), so the Operator can drop or reword
|
|
145
|
+
# it in whichever later write it gets to it. Lines added after
|
|
146
|
+
# this moment stay protected until the next instruction.
|
|
147
|
+
_unprotect_current_check_lines(ctx)
|
|
148
|
+
|
|
149
|
+
controller = UnifiedMobileController(ctx)
|
|
150
|
+
|
|
151
|
+
# 1. Capture screenshot with hybrid settling strategy
|
|
152
|
+
if _should_skip_settling(state):
|
|
153
|
+
logger.info("Skipping settling. Retrieving screen data directly...")
|
|
154
|
+
device_data = await controller.get_screen_data()
|
|
155
|
+
latest_screenshot_b64 = device_data.base64
|
|
156
|
+
xml_hierarchy = device_data.elements
|
|
157
|
+
else:
|
|
158
|
+
logger.info("Screen settling required. Delaying 0.4s and capturing screen data...")
|
|
159
|
+
await asyncio.sleep(0.4)
|
|
160
|
+
device_data = await controller.get_screen_data()
|
|
161
|
+
latest_screenshot_b64 = device_data.base64
|
|
162
|
+
xml_hierarchy = device_data.elements
|
|
163
|
+
|
|
164
|
+
latest_screenshot_bytes = base64.b64decode(latest_screenshot_b64)
|
|
165
|
+
|
|
166
|
+
# Update context device dimensions dynamically
|
|
167
|
+
if ctx.device:
|
|
168
|
+
ctx.device.device_width = device_data.width
|
|
169
|
+
ctx.device.device_height = device_data.height
|
|
170
|
+
logger.info(f"Updated context device dimensions: {device_data.width}x{device_data.height}")
|
|
171
|
+
|
|
172
|
+
# 3. Perform OCR (if configured, else fallback to pure XML layout)
|
|
173
|
+
ocr_results = []
|
|
174
|
+
if is_ocr_configured():
|
|
175
|
+
screen_height = device_data.height
|
|
176
|
+
status_bar_height = _detect_status_bar_height(xml_hierarchy, screen_height)
|
|
177
|
+
try:
|
|
178
|
+
if status_bar_height > 0:
|
|
179
|
+
logger.info(
|
|
180
|
+
f"Detected status bar height: {status_bar_height}. Cropping screenshot for OCR."
|
|
181
|
+
)
|
|
182
|
+
cropped_b64, _, _ = await asyncio.to_thread(
|
|
183
|
+
_crop_image_remove_status_bar,
|
|
184
|
+
latest_screenshot_b64,
|
|
185
|
+
status_bar_height,
|
|
186
|
+
)
|
|
187
|
+
raw_ocr_results = await perform_ocr(cropped_b64)
|
|
188
|
+
ocr_results = _map_coordinates_back(raw_ocr_results, status_bar_height)
|
|
189
|
+
else:
|
|
190
|
+
logger.info("No status bar detected. Performing OCR on full screenshot.")
|
|
191
|
+
ocr_results = await perform_ocr(latest_screenshot_b64)
|
|
192
|
+
except Exception as ocr_err:
|
|
193
|
+
logger.warning(
|
|
194
|
+
f"Perception OCR execution encountered error: {ocr_err}. Proceeding with XML-only layout."
|
|
195
|
+
)
|
|
196
|
+
ocr_results = []
|
|
197
|
+
else:
|
|
198
|
+
logger.debug("OCR is not configured. Proceeding with XML-only layout.")
|
|
199
|
+
|
|
200
|
+
# 4. Fuse OCR with XML (if ocr_results is empty, returns original xml_hierarchy intact)
|
|
201
|
+
fused_xml = fuse_ocr_with_xml(xml_hierarchy, ocr_results)
|
|
202
|
+
|
|
203
|
+
# 5. Save to Data Engine in background thread
|
|
204
|
+
image_name = None
|
|
205
|
+
screenshot_path = None
|
|
206
|
+
if ctx.data_engine:
|
|
207
|
+
image_name = hashlib.sha256(latest_screenshot_bytes).hexdigest()
|
|
208
|
+
screenshot_path = str(ctx.data_engine.get_image_path(image_name))
|
|
209
|
+
asyncio.create_task(
|
|
210
|
+
asyncio.to_thread(
|
|
211
|
+
ctx.data_engine.get_or_create_image,
|
|
212
|
+
latest_screenshot_bytes,
|
|
213
|
+
ui_tree=xml_hierarchy,
|
|
214
|
+
ocr_result=ocr_results,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
logger.info(f"Offloaded perception data saving to background with image_name: {image_name}")
|
|
218
|
+
|
|
219
|
+
# Return state update
|
|
220
|
+
update = {
|
|
221
|
+
"latest_screenshot": screenshot_path,
|
|
222
|
+
"latest_ui_hierarchy": fused_xml,
|
|
223
|
+
"operator_raw_data": {
|
|
224
|
+
"screenshot_b64": latest_screenshot_b64,
|
|
225
|
+
"xml_hierarchy": xml_hierarchy if xml_hierarchy is not None else [],
|
|
226
|
+
"ocr_results": ocr_results,
|
|
227
|
+
"width": device_data.width,
|
|
228
|
+
"height": device_data.height,
|
|
229
|
+
},
|
|
230
|
+
"injected_instruction": injected_instruction,
|
|
231
|
+
# sticky_or reducer: once latched True, later False updates keep it True
|
|
232
|
+
"user_stop_requested": user_stop_requested,
|
|
233
|
+
}
|
|
234
|
+
return update
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Annotated
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict
|
|
18
|
+
|
|
19
|
+
from third_party.mobile_use.graph import take_last
|
|
20
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def sticky_or(a, b):
|
|
26
|
+
"""Reducer that latches True: once set, the flag survives later updates."""
|
|
27
|
+
return bool(a) or bool(b)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class State(BaseModel):
|
|
31
|
+
"""Graph channel state.
|
|
32
|
+
|
|
33
|
+
Every field is explicit (``extra="forbid"``): an undeclared write or an
|
|
34
|
+
unknown constructor key fails loudly instead of becoming a silent ghost
|
|
35
|
+
key. Undeclared *reads* (``getattr(state, k, default)``) are guarded by the
|
|
36
|
+
node visibility manifest in ``artemis.graph.visibility`` instead.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
model_config = ConfigDict(
|
|
40
|
+
arbitrary_types_allowed=True,
|
|
41
|
+
extra="forbid",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# ── Control plane (cross-turn signals & routing) ─────────────────────
|
|
45
|
+
initial_goal: Annotated[str, "Initial goal given by the user"]
|
|
46
|
+
injected_instruction: Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
"Injected instruction from user for micro-adjustment",
|
|
49
|
+
take_last,
|
|
50
|
+
] = None
|
|
51
|
+
user_stop_requested: Annotated[
|
|
52
|
+
bool,
|
|
53
|
+
"Latched True once the user externally signals stop (release_loop),"
|
|
54
|
+
" unlocking completion of [Loop:continuous] milestones",
|
|
55
|
+
sticky_or,
|
|
56
|
+
] = False
|
|
57
|
+
checker_success: Annotated[
|
|
58
|
+
bool | None,
|
|
59
|
+
"True if checker succeeded or not triggered, False if failed",
|
|
60
|
+
take_last,
|
|
61
|
+
] = None
|
|
62
|
+
operator_feedback: Annotated[
|
|
63
|
+
list[str] | None,
|
|
64
|
+
"Source-tagged findings ([checker]/[planner]/[final check]) injected"
|
|
65
|
+
" append-only into the Operator's next prompt (never switches the"
|
|
66
|
+
" prompt template)",
|
|
67
|
+
take_last,
|
|
68
|
+
] = None
|
|
69
|
+
run_outcome: Annotated[
|
|
70
|
+
dict | None,
|
|
71
|
+
"Machine-readable run outcome (task_status + test summary), populated"
|
|
72
|
+
" by exit settlement before END",
|
|
73
|
+
take_last,
|
|
74
|
+
] = None
|
|
75
|
+
exit_settlement_route: Annotated[
|
|
76
|
+
str | None,
|
|
77
|
+
"Routing decision produced by exit_settlement_node ('continue' | 'end')",
|
|
78
|
+
take_last,
|
|
79
|
+
] = None
|
|
80
|
+
|
|
81
|
+
# ── Perception (moves behind PerceptionStore in Phase 2) ─────────────
|
|
82
|
+
latest_ui_hierarchy: Annotated[
|
|
83
|
+
list[dict] | None, "Latest UI hierarchy of the device", take_last
|
|
84
|
+
] = None
|
|
85
|
+
latest_screenshot: Annotated[str | None, "Path to the latest screenshot", take_last] = None
|
|
86
|
+
operator_raw_data: Annotated[
|
|
87
|
+
dict | None,
|
|
88
|
+
"Raw perception data from operator (screenshot, xml, ocr)",
|
|
89
|
+
take_last,
|
|
90
|
+
] = None
|
|
91
|
+
indexed_points: Annotated[
|
|
92
|
+
list[list[int]] | None,
|
|
93
|
+
"Active coordinate mappings matching indexed text elements",
|
|
94
|
+
take_last,
|
|
95
|
+
] = None
|
|
96
|
+
indexed_elements: Annotated[
|
|
97
|
+
list[dict] | None,
|
|
98
|
+
"Active interactable elements matching indexed text elements,"
|
|
99
|
+
" containing text, bounds, class, and center coordinates.",
|
|
100
|
+
take_last,
|
|
101
|
+
] = None
|
|
102
|
+
|
|
103
|
+
# ── Turn products (previous turn's decisions & results) ──────────────
|
|
104
|
+
current_step_id: Annotated[str | None, "Current step ID from data engine", take_last] = None
|
|
105
|
+
structured_decisions: Annotated[
|
|
106
|
+
str | None,
|
|
107
|
+
"Structured decisions made by the operator, for the validator to follow",
|
|
108
|
+
take_last,
|
|
109
|
+
] = None
|
|
110
|
+
operator_raw_thinking: Annotated[
|
|
111
|
+
str | None,
|
|
112
|
+
"Raw thinking process of the operator",
|
|
113
|
+
take_last,
|
|
114
|
+
] = None
|
|
115
|
+
operator_native_thinking: Annotated[
|
|
116
|
+
str | None,
|
|
117
|
+
"Native/implicit thinking process of the operator",
|
|
118
|
+
take_last,
|
|
119
|
+
] = None
|
|
120
|
+
last_execution_result: Annotated[
|
|
121
|
+
dict | None, "Last execution result from validator", take_last
|
|
122
|
+
] = None
|
|
123
|
+
open_incident: Annotated[
|
|
124
|
+
dict | None,
|
|
125
|
+
"Execution incident (blocked/failed terminal action) still awaiting the"
|
|
126
|
+
" Operator's resolution; None once a later action executes successfully",
|
|
127
|
+
take_last,
|
|
128
|
+
] = None
|
|
129
|
+
last_closed_incident: Annotated[
|
|
130
|
+
dict | None,
|
|
131
|
+
"The incident the most recent successful terminal action closed (with"
|
|
132
|
+
" closed_at_step); rendered once so the Operator settles the original intent",
|
|
133
|
+
take_last,
|
|
134
|
+
] = None
|
|
135
|
+
subagent_calls: Annotated[
|
|
136
|
+
list[str], "List of sub-agent calls in current session", take_last
|
|
137
|
+
] = []
|
|
138
|
+
operator_tool_limit_exceeded: Annotated[
|
|
139
|
+
bool | None,
|
|
140
|
+
"True if operator exceeded tool call limit in the previous turn",
|
|
141
|
+
take_last,
|
|
142
|
+
] = None
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def initial(cls, goal: str) -> "State":
|
|
146
|
+
"""Single source for the graph's initial state.
|
|
147
|
+
|
|
148
|
+
Every entrypoint (SDK, engine runners, tests) must construct the
|
|
149
|
+
first State through here so required fields stay in one place.
|
|
150
|
+
"""
|
|
151
|
+
return cls(initial_goal=goal)
|
|
@@ -0,0 +1,238 @@
|
|
|
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
|
+
"""Per-node State visibility manifests with test-time enforcement.
|
|
16
|
+
|
|
17
|
+
``extra="forbid"`` on :class:`~artemis.graph.state.State` catches ghost
|
|
18
|
+
*writes*; ghost *reads* (``getattr(state, key, default)`` silently returning
|
|
19
|
+
the default) can only be caught by policing reads against a declared manifest.
|
|
20
|
+
|
|
21
|
+
The manifest below is the declared visible domain of every graph node — the
|
|
22
|
+
authoritative answer to "which State fields does this agent see / produce".
|
|
23
|
+
Enforcement is zero-cost in production: :func:`strict_state` returns the state
|
|
24
|
+
unchanged unless ``ARTEMIS_STRICT_STATE=1`` (set in tests), in which case field
|
|
25
|
+
reads outside the node's manifest raise :class:`VisibilityError`.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from artemis.graph.state import State
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class VisibilityError(Exception):
|
|
38
|
+
"""A node touched a State field outside its declared visible domain."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class NodeVisibility:
|
|
43
|
+
reads: frozenset[str]
|
|
44
|
+
writes: frozenset[str]
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def visible(self) -> frozenset[str]:
|
|
48
|
+
return self.reads | self.writes
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _vis(reads: set[str], writes: set[str]) -> NodeVisibility:
|
|
52
|
+
return NodeVisibility(reads=frozenset(reads), writes=frozenset(writes))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
#: Declared visible domain per node. Reads include fields touched by the tools
|
|
56
|
+
#: a node invokes with ``InjectedState`` (they run inside the node's turn).
|
|
57
|
+
#: Start generous, tighten over time — an entry here is a documented decision.
|
|
58
|
+
NODE_VISIBILITY: dict[str, NodeVisibility] = {
|
|
59
|
+
"perception": _vis(
|
|
60
|
+
reads={"structured_decisions"},
|
|
61
|
+
writes={
|
|
62
|
+
"latest_screenshot",
|
|
63
|
+
"latest_ui_hierarchy",
|
|
64
|
+
"operator_raw_data",
|
|
65
|
+
"injected_instruction",
|
|
66
|
+
"user_stop_requested",
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
"planner": _vis(
|
|
70
|
+
reads={"initial_goal", "latest_screenshot"},
|
|
71
|
+
# Mutates latest_screenshot in place when it captures its own frame
|
|
72
|
+
# (planner runs before perception on the first turn).
|
|
73
|
+
writes={"latest_screenshot"},
|
|
74
|
+
),
|
|
75
|
+
"operator": _vis(
|
|
76
|
+
reads={
|
|
77
|
+
"initial_goal",
|
|
78
|
+
"injected_instruction",
|
|
79
|
+
"user_stop_requested",
|
|
80
|
+
"operator_feedback",
|
|
81
|
+
"operator_raw_data",
|
|
82
|
+
"latest_ui_hierarchy",
|
|
83
|
+
"latest_screenshot",
|
|
84
|
+
"indexed_points",
|
|
85
|
+
"indexed_elements",
|
|
86
|
+
"subagent_calls",
|
|
87
|
+
"current_step_id",
|
|
88
|
+
"operator_tool_limit_exceeded",
|
|
89
|
+
"structured_decisions",
|
|
90
|
+
"operator_raw_thinking",
|
|
91
|
+
"operator_native_thinking",
|
|
92
|
+
# Transcript path (M2): committing the previous turn into the
|
|
93
|
+
# ledger appends its validator result message.
|
|
94
|
+
"last_execution_result",
|
|
95
|
+
# Execution incident hand-off from the Validator (open block /
|
|
96
|
+
# one-turn CLOSED notice in the observation tail).
|
|
97
|
+
"open_incident",
|
|
98
|
+
"last_closed_incident",
|
|
99
|
+
},
|
|
100
|
+
writes={
|
|
101
|
+
"structured_decisions",
|
|
102
|
+
"operator_raw_thinking",
|
|
103
|
+
"operator_native_thinking",
|
|
104
|
+
"indexed_points",
|
|
105
|
+
"indexed_elements",
|
|
106
|
+
"current_step_id",
|
|
107
|
+
"subagent_calls",
|
|
108
|
+
"operator_tool_limit_exceeded",
|
|
109
|
+
},
|
|
110
|
+
),
|
|
111
|
+
"execution_check": _vis(
|
|
112
|
+
reads={
|
|
113
|
+
"operator_raw_data",
|
|
114
|
+
"structured_decisions",
|
|
115
|
+
"operator_raw_thinking",
|
|
116
|
+
"operator_native_thinking",
|
|
117
|
+
"user_stop_requested",
|
|
118
|
+
"initial_goal",
|
|
119
|
+
# The turn's injected instruction is stamped verbatim onto the
|
|
120
|
+
# step record so the chunk ledger can preserve it (never-evict).
|
|
121
|
+
"injected_instruction",
|
|
122
|
+
},
|
|
123
|
+
writes={
|
|
124
|
+
"checker_success",
|
|
125
|
+
"structured_decisions",
|
|
126
|
+
"current_step_id",
|
|
127
|
+
"operator_feedback",
|
|
128
|
+
"subagent_calls",
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
"validator": _vis(
|
|
132
|
+
reads={
|
|
133
|
+
"open_incident",
|
|
134
|
+
"initial_goal",
|
|
135
|
+
"structured_decisions",
|
|
136
|
+
"latest_screenshot",
|
|
137
|
+
"latest_ui_hierarchy",
|
|
138
|
+
"current_step_id",
|
|
139
|
+
"operator_raw_data",
|
|
140
|
+
"operator_raw_thinking",
|
|
141
|
+
"operator_native_thinking",
|
|
142
|
+
"indexed_points",
|
|
143
|
+
"indexed_elements",
|
|
144
|
+
},
|
|
145
|
+
writes={
|
|
146
|
+
"open_incident",
|
|
147
|
+
"last_closed_incident",
|
|
148
|
+
"last_execution_result",
|
|
149
|
+
# In-place turn-scoped mutations (formalized as TurnWorkspace in
|
|
150
|
+
# Phase 2): post-action screenshot and re-derived index mappings.
|
|
151
|
+
"latest_screenshot",
|
|
152
|
+
"indexed_points",
|
|
153
|
+
"indexed_elements",
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
"summarizer": _vis(
|
|
157
|
+
reads={
|
|
158
|
+
"current_step_id",
|
|
159
|
+
"structured_decisions",
|
|
160
|
+
"operator_raw_thinking",
|
|
161
|
+
"operator_native_thinking",
|
|
162
|
+
"last_execution_result",
|
|
163
|
+
},
|
|
164
|
+
writes=set(),
|
|
165
|
+
),
|
|
166
|
+
"convergence": _vis(
|
|
167
|
+
reads={"checker_success", "user_stop_requested"},
|
|
168
|
+
writes=set(),
|
|
169
|
+
),
|
|
170
|
+
"exit_settlement": _vis(
|
|
171
|
+
reads={"user_stop_requested", "initial_goal"},
|
|
172
|
+
writes={"exit_settlement_route", "operator_feedback", "run_outcome"},
|
|
173
|
+
),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_STATE_FIELDS = frozenset(State.model_fields.keys())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def strict_enabled() -> bool:
|
|
180
|
+
return os.environ.get("ARTEMIS_STRICT_STATE", "") == "1"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class StateView:
|
|
184
|
+
"""Read-policing proxy handed to nodes under ``ARTEMIS_STRICT_STATE=1``.
|
|
185
|
+
|
|
186
|
+
Only declared State *fields* are policed; methods and pydantic internals
|
|
187
|
+
pass through. Attribute writes delegate to the underlying State (in-place
|
|
188
|
+
turn mutations are Phase 2 scope) but are still checked against the
|
|
189
|
+
node's write set.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
__slots__ = ("_state", "_node", "_visibility")
|
|
193
|
+
|
|
194
|
+
def __init__(self, state: State, node: str):
|
|
195
|
+
object.__setattr__(self, "_state", state)
|
|
196
|
+
object.__setattr__(self, "_node", node)
|
|
197
|
+
object.__setattr__(self, "_visibility", NODE_VISIBILITY[node])
|
|
198
|
+
|
|
199
|
+
def __getattr__(self, name: str) -> Any:
|
|
200
|
+
if name in _STATE_FIELDS and name not in self._visibility.visible:
|
|
201
|
+
raise VisibilityError(
|
|
202
|
+
f"Node '{self._node}' read undeclared State field '{name}'."
|
|
203
|
+
" Declare it in NODE_VISIBILITY or stop reading it."
|
|
204
|
+
)
|
|
205
|
+
return getattr(self._state, name)
|
|
206
|
+
|
|
207
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
208
|
+
if name in _STATE_FIELDS and name not in self._visibility.writes:
|
|
209
|
+
raise VisibilityError(
|
|
210
|
+
f"Node '{self._node}' mutated undeclared State field '{name}'."
|
|
211
|
+
" Declare it in NODE_VISIBILITY or stop writing it."
|
|
212
|
+
)
|
|
213
|
+
setattr(self._state, name, value)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def strict_state(state: State, node: str) -> State | StateView:
|
|
217
|
+
"""Wraps ``state`` in a policing view when strict mode is on (tests)."""
|
|
218
|
+
if strict_enabled() and isinstance(state, State):
|
|
219
|
+
return StateView(state, node)
|
|
220
|
+
return state
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def check_update(node: str, update: dict | None) -> None:
|
|
224
|
+
"""Validates a node's returned update keys against its write manifest."""
|
|
225
|
+
if not update:
|
|
226
|
+
return
|
|
227
|
+
illegal = set(update) & _STATE_FIELDS - NODE_VISIBILITY[node].writes
|
|
228
|
+
if illegal:
|
|
229
|
+
raise VisibilityError(f"Node '{node}' returned undeclared update keys: {sorted(illegal)}.")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def validate_manifest() -> list[str]:
|
|
233
|
+
"""Returns problems in the manifest itself (ghost fields). Empty = clean."""
|
|
234
|
+
problems = []
|
|
235
|
+
for node, vis in NODE_VISIBILITY.items():
|
|
236
|
+
for field in vis.visible - _STATE_FIELDS:
|
|
237
|
+
problems.append(f"{node}: '{field}' is not a declared State field")
|
|
238
|
+
return problems
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
"""ARTEMIS CLI interface package."""
|
|
16
|
+
|
|
17
|
+
from artemis.interfaces.cli.main import app, cli
|
|
18
|
+
|
|
19
|
+
__all__ = ["app", "cli"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""CLI command modules."""
|