android-midscene-automation 0.2.4 → 1.1.0
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 +7 -0
- package/README.md +35 -0
- 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,398 @@
|
|
|
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
|
+
"""Shared history search, step replay, and screenshot tools.
|
|
16
|
+
|
|
17
|
+
Each tool's args_schema defines both its LangChain interface for Pro and its
|
|
18
|
+
ToolDeclaration for Flash. The MCP trace inspector calls the underlying
|
|
19
|
+
functions with an OfflineHistoryReader.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, Literal
|
|
25
|
+
|
|
26
|
+
from langchain_core.tools import BaseTool
|
|
27
|
+
from pydantic import BaseModel, Field
|
|
28
|
+
|
|
29
|
+
from artemis.core.tool_failure import ToolFailure
|
|
30
|
+
from artemis.core.tool_declaration import ToolDeclaration
|
|
31
|
+
from artemis.data_engine.trace import trace_langchain_tool
|
|
32
|
+
from artemis.tools.base import ArtemisTool
|
|
33
|
+
from artemis.tools.history.replay import (
|
|
34
|
+
DEFAULT_MAX_STEPS as DEFAULT_REPLAY_MAX_STEPS,
|
|
35
|
+
)
|
|
36
|
+
from artemis.tools.history.replay import (
|
|
37
|
+
DEFAULT_MAX_TOKENS as DEFAULT_REPLAY_MAX_TOKENS,
|
|
38
|
+
)
|
|
39
|
+
from artemis.tools.history.replay import replay_steps_text
|
|
40
|
+
from artemis.tools.history.screenshot import ScreenshotResult, load_step_screenshot
|
|
41
|
+
from artemis.tools.history.search import search_history_text
|
|
42
|
+
from artemis.tools.tool_wrapper import ToolWrapper
|
|
43
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
44
|
+
|
|
45
|
+
logger = get_logger(__name__)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"HISTORY_TOOLS",
|
|
49
|
+
"HISTORY_TOOL_DECLARATIONS",
|
|
50
|
+
"HISTORY_TOOL_NAMES",
|
|
51
|
+
"HISTORY_TOOL_WRAPPERS",
|
|
52
|
+
"GET_STEP_SCREENSHOT_TOOL",
|
|
53
|
+
"REPLAY_STEPS_TOOL",
|
|
54
|
+
"SEARCH_HISTORY_TOOL",
|
|
55
|
+
"GetStepScreenshotArgs",
|
|
56
|
+
"GetStepScreenshotTool",
|
|
57
|
+
"ReplayStepsArgs",
|
|
58
|
+
"ReplayStepsTool",
|
|
59
|
+
"ScreenshotResult",
|
|
60
|
+
"SearchHistoryArgs",
|
|
61
|
+
"SearchHistoryTool",
|
|
62
|
+
"get_history_tools",
|
|
63
|
+
"get_replay_steps_tool",
|
|
64
|
+
"get_search_history_tool",
|
|
65
|
+
"get_step_screenshot",
|
|
66
|
+
"get_step_screenshot_tool",
|
|
67
|
+
"history_available",
|
|
68
|
+
"history_tool_by_name",
|
|
69
|
+
"history_tool_declarations",
|
|
70
|
+
"load_step_screenshot",
|
|
71
|
+
"replay_steps",
|
|
72
|
+
"replay_steps_text",
|
|
73
|
+
"search_history",
|
|
74
|
+
"search_history_available",
|
|
75
|
+
"search_history_text",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# --- Configuration -----------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _memory_config(warn: bool = True) -> Any:
|
|
83
|
+
try:
|
|
84
|
+
from artemis.config import load_agent_config
|
|
85
|
+
|
|
86
|
+
return load_agent_config().memory
|
|
87
|
+
except Exception as e:
|
|
88
|
+
if warn:
|
|
89
|
+
logger.debug(f"memory config unavailable, using defaults: {e}")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _recall_config(warn: bool = True) -> Any:
|
|
94
|
+
return getattr(_memory_config(warn), "recall", None)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _replay_config(warn: bool = True) -> Any:
|
|
98
|
+
return getattr(_memory_config(warn), "replay", None)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def history_available(ctx: Any) -> bool:
|
|
102
|
+
"""History tools need a stored history to read (a DataEngine session)."""
|
|
103
|
+
return ctx is not None and getattr(ctx, "data_engine", None) is not None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def search_history_available(ctx: Any) -> bool:
|
|
107
|
+
"""``search_history`` is additionally gated by ``agent.memory.recall.enabled``."""
|
|
108
|
+
if not history_available(ctx):
|
|
109
|
+
return False
|
|
110
|
+
return bool(getattr(_recall_config(warn=False), "enabled", True))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --- search_history ----------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class SearchHistoryArgs(BaseModel):
|
|
117
|
+
"""Arguments schema for the search_history tool."""
|
|
118
|
+
|
|
119
|
+
query: str = Field(
|
|
120
|
+
default="",
|
|
121
|
+
description=(
|
|
122
|
+
"Keywords to search for across the whole execution history"
|
|
123
|
+
" (screen descriptions, actions, results, reasoning, tool calls,"
|
|
124
|
+
" notes, on-screen text, package/activity names). Case-insensitive;"
|
|
125
|
+
" every whitespace-separated term is matched independently. May be"
|
|
126
|
+
" empty when step_range is given."
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
step_range: list[int] | None = Field(
|
|
130
|
+
default=None,
|
|
131
|
+
description=(
|
|
132
|
+
"Optional [start, end] step-number range (inclusive) to restrict the"
|
|
133
|
+
" search — e.g. the range shown in a compressed-history block. Also"
|
|
134
|
+
" returns the full per-step action ledger of the range."
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
max_results: int = Field(
|
|
138
|
+
default=5,
|
|
139
|
+
ge=1,
|
|
140
|
+
description="Maximum results to return (server-side cap still applies).",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
SEARCH_HISTORY_DESCRIPTION = (
|
|
145
|
+
"Deterministic keyword / step-range lookup over the full stored execution"
|
|
146
|
+
" history: step summaries, exact actions and results, reasoning, tool"
|
|
147
|
+
" calls, notes, on-screen text (OCR/UI tree, incl. package/activity names)"
|
|
148
|
+
" and compressed-history ledgers. Every hit carries its step number; use"
|
|
149
|
+
" `replay_steps` for the full record of a step and `get_step_screenshot`"
|
|
150
|
+
" for its image."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class SearchHistoryTool(ArtemisTool):
|
|
155
|
+
"""Deterministic search over the stored execution history."""
|
|
156
|
+
|
|
157
|
+
def __init__(self):
|
|
158
|
+
super().__init__(
|
|
159
|
+
name="search_history",
|
|
160
|
+
description=SEARCH_HISTORY_DESCRIPTION,
|
|
161
|
+
args_schema=SearchHistoryArgs,
|
|
162
|
+
category="memory",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def is_available(self, ctx: Any = None) -> bool:
|
|
166
|
+
return search_history_available(ctx)
|
|
167
|
+
|
|
168
|
+
# pylint: disable=too-many-arguments
|
|
169
|
+
async def execute(
|
|
170
|
+
self,
|
|
171
|
+
driver: Any = None, # pylint: disable=unused-argument
|
|
172
|
+
ctx: Any = None,
|
|
173
|
+
query: str = "",
|
|
174
|
+
step_range: list[int] | None = None,
|
|
175
|
+
max_results: int = 5,
|
|
176
|
+
**kwargs: Any,
|
|
177
|
+
) -> str:
|
|
178
|
+
reader = getattr(ctx, "data_engine", None) if ctx else None
|
|
179
|
+
if reader is None:
|
|
180
|
+
return ToolFailure("search_history unavailable: no active execution history.")
|
|
181
|
+
if not query and not step_range:
|
|
182
|
+
return ToolFailure(
|
|
183
|
+
"search_history needs a query and/or a step_range — e.g."
|
|
184
|
+
' search_history(query="login timeout") or'
|
|
185
|
+
' search_history(query="", step_range=[1, 40]).'
|
|
186
|
+
)
|
|
187
|
+
try:
|
|
188
|
+
return search_history_text(
|
|
189
|
+
reader,
|
|
190
|
+
query=query or "",
|
|
191
|
+
step_range=step_range,
|
|
192
|
+
max_results=int(max_results or 5),
|
|
193
|
+
recall_config=_recall_config(),
|
|
194
|
+
)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"search_history failed: {e}")
|
|
197
|
+
return ToolFailure(f"search_history failed: {e}")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# --- replay_steps ------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ReplayStepsArgs(BaseModel):
|
|
204
|
+
"""Arguments schema for the replay_steps tool."""
|
|
205
|
+
|
|
206
|
+
start_step: int = Field(..., description="First step to replay (1-based step number).")
|
|
207
|
+
end_step: int | None = Field(
|
|
208
|
+
default=None,
|
|
209
|
+
description=(
|
|
210
|
+
"Last step of the inclusive range to replay; omit to replay the single"
|
|
211
|
+
" start_step. Reversed bounds are swapped."
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
REPLAY_STEPS_DESCRIPTION = (
|
|
217
|
+
"Replays recorded steps exactly as the executing agent's own context showed"
|
|
218
|
+
" them: what the screen showed, its reasoning, every tool call (name,"
|
|
219
|
+
" arguments, result), the planned action, any pre-execution interception"
|
|
220
|
+
" and the execution result. Coordinates normalized [x, y]. Up to"
|
|
221
|
+
f" {DEFAULT_REPLAY_MAX_STEPS} steps per call. Screenshots are not included"
|
|
222
|
+
" — fetch them with `get_step_screenshot`."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ReplayStepsTool(ArtemisTool):
|
|
227
|
+
"""Full replay of a range of recorded steps."""
|
|
228
|
+
|
|
229
|
+
def __init__(self):
|
|
230
|
+
super().__init__(
|
|
231
|
+
name="replay_steps",
|
|
232
|
+
description=REPLAY_STEPS_DESCRIPTION,
|
|
233
|
+
args_schema=ReplayStepsArgs,
|
|
234
|
+
category="memory",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def is_available(self, ctx: Any = None) -> bool:
|
|
238
|
+
return history_available(ctx)
|
|
239
|
+
|
|
240
|
+
async def execute(
|
|
241
|
+
self,
|
|
242
|
+
driver: Any = None, # pylint: disable=unused-argument
|
|
243
|
+
ctx: Any = None,
|
|
244
|
+
start_step: Any = None,
|
|
245
|
+
end_step: Any = None,
|
|
246
|
+
**kwargs: Any,
|
|
247
|
+
) -> str:
|
|
248
|
+
reader = getattr(ctx, "data_engine", None) if ctx else None
|
|
249
|
+
if reader is None:
|
|
250
|
+
return ToolFailure("Error: no execution history available.")
|
|
251
|
+
cfg = _replay_config()
|
|
252
|
+
try:
|
|
253
|
+
return replay_steps_text(
|
|
254
|
+
reader,
|
|
255
|
+
start_step,
|
|
256
|
+
end_step,
|
|
257
|
+
max_steps=int(getattr(cfg, "max_steps", None) or DEFAULT_REPLAY_MAX_STEPS),
|
|
258
|
+
max_tokens=int(getattr(cfg, "max_tokens", None) or DEFAULT_REPLAY_MAX_TOKENS),
|
|
259
|
+
)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.error(f"replay_steps failed: {e}")
|
|
262
|
+
return ToolFailure(f"replay_steps failed: {e}")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# --- get_step_screenshot -----------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class GetStepScreenshotArgs(BaseModel):
|
|
269
|
+
"""Arguments schema for the get_step_screenshot tool."""
|
|
270
|
+
|
|
271
|
+
step_number: int = Field(..., description="The step whose screenshot to attach (1-based).")
|
|
272
|
+
which: Literal["pre", "post", "overlay"] = Field(
|
|
273
|
+
default="pre",
|
|
274
|
+
description=(
|
|
275
|
+
"'pre' = screen observed at the start of the step; 'post' = screen"
|
|
276
|
+
" after the action (when recorded); 'overlay' = the pre screenshot"
|
|
277
|
+
" with the step's action drawn on it."
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
GET_STEP_SCREENSHOT_DESCRIPTION = (
|
|
283
|
+
"Attaches one recorded screenshot of a step: 'pre' (screen observed at the"
|
|
284
|
+
" start of the step), 'post' (screen after the action, when recorded) or"
|
|
285
|
+
" 'overlay' (the pre screenshot with the step's action drawn on it — tap"
|
|
286
|
+
" point / swipe path — to check the action landed on the intended element)."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class GetStepScreenshotTool(ArtemisTool):
|
|
291
|
+
"""One recorded step screenshot, attached to the conversation."""
|
|
292
|
+
|
|
293
|
+
def __init__(self):
|
|
294
|
+
super().__init__(
|
|
295
|
+
name="get_step_screenshot",
|
|
296
|
+
description=GET_STEP_SCREENSHOT_DESCRIPTION,
|
|
297
|
+
args_schema=GetStepScreenshotArgs,
|
|
298
|
+
category="memory",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def is_available(self, ctx: Any = None) -> bool:
|
|
302
|
+
return history_available(ctx)
|
|
303
|
+
|
|
304
|
+
async def execute(
|
|
305
|
+
self,
|
|
306
|
+
driver: Any = None, # pylint: disable=unused-argument
|
|
307
|
+
ctx: Any = None,
|
|
308
|
+
step_number: Any = None,
|
|
309
|
+
which: str = "pre",
|
|
310
|
+
**kwargs: Any,
|
|
311
|
+
) -> list[dict[str, Any]] | str:
|
|
312
|
+
reader = getattr(ctx, "data_engine", None) if ctx else None
|
|
313
|
+
if reader is None:
|
|
314
|
+
return ToolFailure("Error: no execution history available.")
|
|
315
|
+
try:
|
|
316
|
+
return load_step_screenshot(reader, step_number, which).to_content_blocks()
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.error(f"get_step_screenshot failed: {e}")
|
|
319
|
+
return ToolFailure(f"get_step_screenshot failed: {e}")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# --- Instances, declarations, exports ------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
search_history = SearchHistoryTool()
|
|
325
|
+
replay_steps = ReplayStepsTool()
|
|
326
|
+
get_step_screenshot = GetStepScreenshotTool()
|
|
327
|
+
|
|
328
|
+
HISTORY_TOOLS: tuple[ArtemisTool, ...] = (search_history, replay_steps, get_step_screenshot)
|
|
329
|
+
HISTORY_TOOL_NAMES: frozenset[str] = frozenset(t.name for t in HISTORY_TOOLS)
|
|
330
|
+
|
|
331
|
+
#: Flash-profile declarations, derived from the very same args schemas.
|
|
332
|
+
SEARCH_HISTORY_TOOL: ToolDeclaration = search_history.to_tool_declaration()
|
|
333
|
+
REPLAY_STEPS_TOOL: ToolDeclaration = replay_steps.to_tool_declaration()
|
|
334
|
+
GET_STEP_SCREENSHOT_TOOL: ToolDeclaration = get_step_screenshot.to_tool_declaration()
|
|
335
|
+
HISTORY_TOOL_DECLARATIONS: tuple[ToolDeclaration, ...] = (
|
|
336
|
+
SEARCH_HISTORY_TOOL,
|
|
337
|
+
REPLAY_STEPS_TOOL,
|
|
338
|
+
GET_STEP_SCREENSHOT_TOOL,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def history_tool_by_name(name: str) -> ArtemisTool | None:
|
|
343
|
+
"""The shared tool instance behind a history tool name (Flash dispatch)."""
|
|
344
|
+
raw = name.split(":")[-1] if ":" in name else name
|
|
345
|
+
return next((t for t in HISTORY_TOOLS if t.name == raw), None)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def history_tool_declarations(ctx: Any) -> list[ToolDeclaration]:
|
|
349
|
+
"""Declarations of the history tools available for ``ctx`` (Flash runner)."""
|
|
350
|
+
return [tool.to_tool_declaration() for tool in HISTORY_TOOLS if tool.is_available(ctx)]
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def get_search_history_tool(ctx: Any) -> BaseTool:
|
|
354
|
+
"""Exports search_history as a traced LangChain BaseTool."""
|
|
355
|
+
return trace_langchain_tool(search_history.to_langchain_tool(ctx), ctx)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def get_replay_steps_tool(ctx: Any) -> BaseTool:
|
|
359
|
+
"""Exports replay_steps as a traced LangChain BaseTool."""
|
|
360
|
+
return trace_langchain_tool(replay_steps.to_langchain_tool(ctx), ctx)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_step_screenshot_tool(ctx: Any) -> BaseTool:
|
|
364
|
+
"""Exports get_step_screenshot as a traced LangChain BaseTool."""
|
|
365
|
+
return trace_langchain_tool(get_step_screenshot.to_langchain_tool(ctx), ctx)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_history_tools(ctx: Any) -> list[BaseTool]:
|
|
369
|
+
"""All three history tools as LangChain tools (read-only agents mount them as a set)."""
|
|
370
|
+
return [get_search_history_tool(ctx), get_replay_steps_tool(ctx), get_step_screenshot_tool(ctx)]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
search_history_wrapper = ToolWrapper(
|
|
374
|
+
tool_fn_getter=get_search_history_tool,
|
|
375
|
+
on_success_fn=lambda *a, **k: "Searched history",
|
|
376
|
+
on_failure_fn=lambda err: f"search_history failed: {err}",
|
|
377
|
+
is_available_fn=search_history_available,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
replay_steps_wrapper = ToolWrapper(
|
|
381
|
+
tool_fn_getter=get_replay_steps_tool,
|
|
382
|
+
on_success_fn=lambda *a, **k: "Replayed steps",
|
|
383
|
+
on_failure_fn=lambda err: f"replay_steps failed: {err}",
|
|
384
|
+
is_available_fn=history_available,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
get_step_screenshot_wrapper = ToolWrapper(
|
|
388
|
+
tool_fn_getter=get_step_screenshot_tool,
|
|
389
|
+
on_success_fn=lambda *a, **k: "Fetched step screenshot",
|
|
390
|
+
on_failure_fn=lambda err: f"get_step_screenshot failed: {err}",
|
|
391
|
+
is_available_fn=history_available,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
HISTORY_TOOL_WRAPPERS: tuple[ToolWrapper, ...] = (
|
|
395
|
+
search_history_wrapper,
|
|
396
|
+
replay_steps_wrapper,
|
|
397
|
+
get_step_screenshot_wrapper,
|
|
398
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
"""replay_steps: full replay of a step range, exactly as the executing agent saw it.
|
|
16
|
+
|
|
17
|
+
Each step is rendered with :func:`~artemis.utils.task_tree.render_step_replay`
|
|
18
|
+
(``T+mm:ss`` step header, ``[Screen]`` description, reasoning, every
|
|
19
|
+
non-internal tool call with name / arguments / result, the executed action,
|
|
20
|
+
safety-net interception and the execution result). Two loose bounds keep one tool result finite (config
|
|
21
|
+
``agent.memory.replay``): at most ``max_steps`` steps per call, and a
|
|
22
|
+
``max_tokens`` budget applied by dropping *whole trailing steps* — a replayed
|
|
23
|
+
step is never cut mid-sentence. Screenshots are deliberately not part of the
|
|
24
|
+
replay; ``get_step_screenshot`` fetches them one at a time.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from artemis.core.tool_failure import ToolFailure
|
|
32
|
+
from artemis.utils.task_tree import render_step_replay
|
|
33
|
+
|
|
34
|
+
DEFAULT_MAX_STEPS = 5
|
|
35
|
+
DEFAULT_MAX_TOKENS = 12000
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _estimate_tokens(text: str) -> int:
|
|
39
|
+
return len(text) // 4
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def replay_steps_text(
|
|
43
|
+
reader: Any,
|
|
44
|
+
start_step: Any,
|
|
45
|
+
end_step: Any = None,
|
|
46
|
+
*,
|
|
47
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
48
|
+
max_tokens: int = DEFAULT_MAX_TOKENS,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Replays steps ``start_step``..``end_step`` (inclusive, 1-based) from ``reader``."""
|
|
51
|
+
if reader is None:
|
|
52
|
+
return ToolFailure("Error: no execution history available.")
|
|
53
|
+
try:
|
|
54
|
+
start = int(start_step)
|
|
55
|
+
end = start if end_step is None else int(end_step)
|
|
56
|
+
except (TypeError, ValueError):
|
|
57
|
+
return ToolFailure(
|
|
58
|
+
f"Error: start_step and end_step must be integers, got {start_step!r} and {end_step!r}."
|
|
59
|
+
)
|
|
60
|
+
if start > end:
|
|
61
|
+
start, end = end, start
|
|
62
|
+
|
|
63
|
+
max_steps = max(1, int(max_steps or DEFAULT_MAX_STEPS))
|
|
64
|
+
notes: list[str] = []
|
|
65
|
+
if end - start + 1 > max_steps:
|
|
66
|
+
end = start + max_steps - 1
|
|
67
|
+
notes.append(
|
|
68
|
+
f"(Only {max_steps} steps are replayed per call: showing Steps {start}–{end}."
|
|
69
|
+
f" Call again from Step {end + 1} for the rest.)"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
steps: list[dict] = reader.get_agent_friendly_steps_in_range(start, end) or []
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return ToolFailure(f"Error loading steps {start}–{end}: {e}")
|
|
76
|
+
if not steps:
|
|
77
|
+
if start == end:
|
|
78
|
+
return ToolFailure(f"Error: step {start} not found.")
|
|
79
|
+
return ToolFailure(f"Error: no recorded steps in range {start}–{end}.")
|
|
80
|
+
|
|
81
|
+
budget_tokens = max(1, int(max_tokens or DEFAULT_MAX_TOKENS))
|
|
82
|
+
# Step headers carry the session clock (``T+mm:ss``) every agent prompt
|
|
83
|
+
# and search_history use; without a session start they fall back to the
|
|
84
|
+
# stored relative time.
|
|
85
|
+
session_start = getattr(reader, "session_start_time", None)
|
|
86
|
+
rendered: list[str] = []
|
|
87
|
+
shown: list[dict] = []
|
|
88
|
+
used = 0
|
|
89
|
+
dropped = 0
|
|
90
|
+
for step in steps:
|
|
91
|
+
if dropped:
|
|
92
|
+
dropped += 1
|
|
93
|
+
continue
|
|
94
|
+
try:
|
|
95
|
+
text = render_step_replay(step, session_start=session_start)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
return ToolFailure(f"Error rendering steps {start}–{end}: {e}")
|
|
98
|
+
cost = _estimate_tokens(text) + 1
|
|
99
|
+
# The first step is always replayed; afterwards the budget drops whole
|
|
100
|
+
# trailing steps rather than truncating a step in the middle.
|
|
101
|
+
if rendered and used + cost > budget_tokens:
|
|
102
|
+
dropped = 1
|
|
103
|
+
continue
|
|
104
|
+
rendered.append(text)
|
|
105
|
+
shown.append(step)
|
|
106
|
+
used += cost
|
|
107
|
+
|
|
108
|
+
if dropped:
|
|
109
|
+
last_shown = shown[-1].get("step_number", start)
|
|
110
|
+
first_shown = shown[0].get("step_number", start)
|
|
111
|
+
notes.append(
|
|
112
|
+
f"(Token budget reached: Steps {first_shown}–{last_shown} replayed;"
|
|
113
|
+
f" {dropped} more step(s) in the range not shown."
|
|
114
|
+
f" Call again from Step {last_shown + 1}.)"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
text = "\n\n".join(rendered)
|
|
118
|
+
if notes:
|
|
119
|
+
text += "\n\n" + "\n".join(notes)
|
|
120
|
+
return text
|
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
"""get_step_screenshot: one recorded screenshot of one step, on demand.
|
|
16
|
+
|
|
17
|
+
``pre`` is the screen observed at the start of the step, ``post`` the screen
|
|
18
|
+
after its action (when recorded), ``overlay`` the pre screenshot with the
|
|
19
|
+
step's action drawn on it (tap point / swipe path) so a reviewer can check
|
|
20
|
+
that the action landed on the intended element. The overlay is drawn from the
|
|
21
|
+
step's *raw* stored action (physical pixels), never from the normalized twin.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import base64
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
import sqlite3
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from artemis.core.tool_failure import ToolFailure
|
|
33
|
+
from artemis.utils.visualization import overlay_action_on_screenshot
|
|
34
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
35
|
+
|
|
36
|
+
logger = get_logger(__name__)
|
|
37
|
+
|
|
38
|
+
WHICH_CHOICES = ("pre", "post", "overlay")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ScreenshotResult:
|
|
43
|
+
"""One screenshot lookup: a caption plus the JPEG bytes when a file exists."""
|
|
44
|
+
|
|
45
|
+
step_number: int
|
|
46
|
+
which: str
|
|
47
|
+
description: str
|
|
48
|
+
image_bytes: bytes | None = None
|
|
49
|
+
#: True when ``which == "overlay"`` and the action was actually drawn.
|
|
50
|
+
overlay_drawn: bool = False
|
|
51
|
+
#: True when the lookup could not serve the request (bad arguments, no
|
|
52
|
+
#: history, unknown step, unreadable file). A step that simply has no
|
|
53
|
+
#: screenshot of the requested kind is an answer, not an error.
|
|
54
|
+
is_error: bool = False
|
|
55
|
+
|
|
56
|
+
def to_content_blocks(self) -> list[dict[str, Any]] | str:
|
|
57
|
+
"""The tool's return value: content blocks with the image, the
|
|
58
|
+
plain-text answer when there is no image to attach, or a
|
|
59
|
+
:class:`ToolFailure` (status ``error`` downstream) when the lookup
|
|
60
|
+
failed."""
|
|
61
|
+
if self.is_error:
|
|
62
|
+
return ToolFailure(self.description)
|
|
63
|
+
if not self.image_bytes:
|
|
64
|
+
return self.description
|
|
65
|
+
encoded = base64.b64encode(self.image_bytes).decode("utf-8")
|
|
66
|
+
return [
|
|
67
|
+
{"type": "text", "text": self.description},
|
|
68
|
+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encoded}"}},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _failure(step_number: int, which: str, description: str) -> ScreenshotResult:
|
|
73
|
+
return ScreenshotResult(step_number, which, description, is_error=True)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def load_step_screenshot(reader: Any, step_number: Any, which: str = "pre") -> ScreenshotResult:
|
|
77
|
+
"""Loads (and for ``overlay`` annotates) one step screenshot from ``reader``."""
|
|
78
|
+
which = str(which or "pre").lower()
|
|
79
|
+
try:
|
|
80
|
+
number = int(step_number)
|
|
81
|
+
except (TypeError, ValueError):
|
|
82
|
+
return _failure(-1, which, f"Error: step_number must be an integer, got {step_number!r}.")
|
|
83
|
+
if which not in WHICH_CHOICES:
|
|
84
|
+
return _failure(
|
|
85
|
+
number, which, f"Error: 'which' must be 'pre', 'post' or 'overlay', got '{which}'."
|
|
86
|
+
)
|
|
87
|
+
if reader is None:
|
|
88
|
+
return _failure(number, which, "Error: no execution history available.")
|
|
89
|
+
|
|
90
|
+
source = "pre" if which == "overlay" else which
|
|
91
|
+
try:
|
|
92
|
+
path = reader.get_step_image_path(number, source)
|
|
93
|
+
except (sqlite3.Error, OSError, ValueError) as e:
|
|
94
|
+
return _failure(number, which, f"Error looking up step {number}: {e}")
|
|
95
|
+
if path is None:
|
|
96
|
+
try:
|
|
97
|
+
record = reader.get_step_record(number)
|
|
98
|
+
except (sqlite3.Error, ValueError):
|
|
99
|
+
record = None
|
|
100
|
+
if record is None:
|
|
101
|
+
return _failure(number, which, f"Error: step {number} not found.")
|
|
102
|
+
return ScreenshotResult(
|
|
103
|
+
number, which, f"No {source}-action screenshot recorded for step {number}."
|
|
104
|
+
)
|
|
105
|
+
try:
|
|
106
|
+
image_bytes = Path(path).read_bytes()
|
|
107
|
+
except OSError as e:
|
|
108
|
+
return _failure(number, which, f"Error reading screenshot: {e}")
|
|
109
|
+
|
|
110
|
+
if which != "overlay":
|
|
111
|
+
return ScreenshotResult(
|
|
112
|
+
number, which, f"Screenshot of step {number} ({which}-action) is attached.", image_bytes
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
record = reader.get_step_record(number)
|
|
117
|
+
except (sqlite3.Error, ValueError):
|
|
118
|
+
record = None
|
|
119
|
+
action = getattr(record, "action_taken", None) if record is not None else None
|
|
120
|
+
try:
|
|
121
|
+
annotated = overlay_action_on_screenshot(image_bytes, action)
|
|
122
|
+
except (TypeError, ValueError, OSError) as e:
|
|
123
|
+
logger.debug(f"Action overlay for step {number} failed: {e}")
|
|
124
|
+
annotated = None
|
|
125
|
+
if annotated is None:
|
|
126
|
+
return ScreenshotResult(
|
|
127
|
+
number,
|
|
128
|
+
which,
|
|
129
|
+
f"Screenshot of step {number} (pre-action; the step's action could not be"
|
|
130
|
+
" drawn, plain screenshot) is attached.",
|
|
131
|
+
image_bytes,
|
|
132
|
+
)
|
|
133
|
+
return ScreenshotResult(
|
|
134
|
+
number,
|
|
135
|
+
which,
|
|
136
|
+
f"Screenshot of step {number} (pre-action with the step's action drawn on it) is attached.",
|
|
137
|
+
annotated,
|
|
138
|
+
overlay_drawn=True,
|
|
139
|
+
)
|