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,563 @@
|
|
|
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
|
+
"""search_history: deterministic keyword / step-range lookup over stored history.
|
|
16
|
+
|
|
17
|
+
One search surface over a :class:`~artemis.data_engine.history_reader.HistoryReader`'s
|
|
18
|
+
raw records — step summaries, history chunks, exact actions and execution
|
|
19
|
+
results, reasoning, the tool calls a step made (name, arguments, result
|
|
20
|
+
text), notes, the OCR/UI-tree text of the step's pre and post screenshots
|
|
21
|
+
(which also carries package/activity strings) plus any screenshot a tool
|
|
22
|
+
result embedded, and the foreground-app stamp. Keyword + step-range filtering
|
|
23
|
+
in Python; no vector database, no model call.
|
|
24
|
+
|
|
25
|
+
Hard boundaries (config ``agent.memory.recall``):
|
|
26
|
+
|
|
27
|
+
- at most ``max_results`` results (default 5);
|
|
28
|
+
- one response is capped at ``max_text_tokens`` estimated tokens (char/4);
|
|
29
|
+
- screen text is scanned for the most recent ``screen_scan_steps`` steps;
|
|
30
|
+
- every result carries a step number / step id;
|
|
31
|
+
- large raw sources return only excerpts around the match plus a reference.
|
|
32
|
+
|
|
33
|
+
Two surfaces per step: the *scoring* haystack keeps the raw record (action
|
|
34
|
+
JSON, execution result JSON, UI-tree XML) so resource ids, bounds and package
|
|
35
|
+
names stay searchable; the *excerpt* shown as ``Match:`` is built from the
|
|
36
|
+
readable twin (the ledger action phrase, result status/error words, element
|
|
37
|
+
text / content descriptions / OCR text) so the model never reads JSON.
|
|
38
|
+
|
|
39
|
+
A ``step_range`` additionally returns the full-width ``build_action_ledger``
|
|
40
|
+
rows of that range — the re-entry point for compressed-history marker lines.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import json
|
|
46
|
+
import sqlite3
|
|
47
|
+
from typing import Any
|
|
48
|
+
|
|
49
|
+
from artemis.core.tool_failure import ToolFailure
|
|
50
|
+
from artemis.utils.task_tree import format_actions_clean
|
|
51
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
52
|
+
|
|
53
|
+
logger = get_logger(__name__)
|
|
54
|
+
|
|
55
|
+
#: Character clamp for one matched excerpt.
|
|
56
|
+
EXCERPT_CHARS = 240
|
|
57
|
+
|
|
58
|
+
#: Per-tool-result clamp on the *search* surface (scoring/excerpts only; the
|
|
59
|
+
#: replay rendering has its own, looser clamp).
|
|
60
|
+
RESULT_SEARCH_CHARS = 8000
|
|
61
|
+
|
|
62
|
+
DEFAULT_MAX_RESULTS = 5
|
|
63
|
+
DEFAULT_MAX_TEXT_TOKENS = 2000
|
|
64
|
+
DEFAULT_SCREEN_SCAN_STEPS = 150
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _terms(query: str) -> list[str]:
|
|
68
|
+
return [t for t in (query or "").lower().split() if t]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _estimate_tokens(text: str) -> int:
|
|
72
|
+
return len(text) // 4
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _clamp(text: str, limit: int) -> str:
|
|
76
|
+
text = (text or "").strip()
|
|
77
|
+
if len(text) <= limit:
|
|
78
|
+
return text
|
|
79
|
+
return text[: limit - 1].rstrip() + "…"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _excerpt(text: str, terms: list[str], limit: int) -> str:
|
|
83
|
+
"""A window of ``text`` around the first term hit (flattened whitespace)."""
|
|
84
|
+
flat = " ".join((text or "").split())
|
|
85
|
+
lower = flat.lower()
|
|
86
|
+
pos = -1
|
|
87
|
+
for term in terms:
|
|
88
|
+
pos = lower.find(term)
|
|
89
|
+
if pos >= 0:
|
|
90
|
+
break
|
|
91
|
+
if pos < 0:
|
|
92
|
+
return _clamp(flat, limit)
|
|
93
|
+
start = max(0, pos - limit // 3)
|
|
94
|
+
window = flat[start : start + limit]
|
|
95
|
+
prefix = "…" if start > 0 else ""
|
|
96
|
+
suffix = "…" if start + limit < len(flat) else ""
|
|
97
|
+
return f"{prefix}{window.strip()}{suffix}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _score(haystack: str, terms: list[str]) -> int:
|
|
101
|
+
lower = haystack.lower()
|
|
102
|
+
return sum(1 for term in terms if term in lower)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def result_search_text(result: Any) -> str:
|
|
106
|
+
"""Searchable text of a tool result. Content-block lists contribute their
|
|
107
|
+
text blocks — image blocks have already been rewritten into
|
|
108
|
+
``[screenshot: …]`` descriptions by the DataEngine; any raw image block
|
|
109
|
+
that slipped through contributes nothing (its OCR/XML description joins
|
|
110
|
+
the haystack via :func:`_screen_text`), so base64 never enters the search."""
|
|
111
|
+
if result is None:
|
|
112
|
+
return ""
|
|
113
|
+
# A content-block list stored as its JSON string (trace serialization)
|
|
114
|
+
# is unwrapped first so its text blocks render, not the JSON.
|
|
115
|
+
if isinstance(result, str) and result.lstrip().startswith("[{"):
|
|
116
|
+
try:
|
|
117
|
+
parsed = json.loads(result)
|
|
118
|
+
except ValueError:
|
|
119
|
+
parsed = None
|
|
120
|
+
if isinstance(parsed, list) and any(isinstance(b, dict) and "type" in b for b in parsed):
|
|
121
|
+
return result_search_text(parsed)
|
|
122
|
+
if isinstance(result, list):
|
|
123
|
+
texts = [
|
|
124
|
+
str(block.get("text") or "")
|
|
125
|
+
for block in result
|
|
126
|
+
if isinstance(block, dict) and block.get("type") == "text"
|
|
127
|
+
]
|
|
128
|
+
if texts or any(
|
|
129
|
+
isinstance(block, dict) and block.get("type") in ("image_url", "image")
|
|
130
|
+
for block in result
|
|
131
|
+
):
|
|
132
|
+
return "\n".join(texts)[:RESULT_SEARCH_CHARS]
|
|
133
|
+
if isinstance(result, dict):
|
|
134
|
+
return json.dumps(result, ensure_ascii=False, default=str)[:RESULT_SEARCH_CHARS]
|
|
135
|
+
return str(result)[:RESULT_SEARCH_CHARS]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _events_text(step: dict) -> str:
|
|
139
|
+
parts: list[str] = []
|
|
140
|
+
for event in step.get("interleaved_events") or []:
|
|
141
|
+
etype = event.get("type")
|
|
142
|
+
if etype == "tool_call":
|
|
143
|
+
parts.append(str(event.get("name") or ""))
|
|
144
|
+
parts.append(json.dumps(event.get("args") or {}, ensure_ascii=False, default=str))
|
|
145
|
+
result_text = result_search_text(event.get("result"))
|
|
146
|
+
if result_text:
|
|
147
|
+
parts.append(result_text)
|
|
148
|
+
elif etype and "thought" in etype:
|
|
149
|
+
parts.append(str(event.get("content") or ""))
|
|
150
|
+
return "\n".join(parts)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _rendered_action(action_taken: Any) -> str:
|
|
154
|
+
"""The shared ledger phrase of a step's action (``format_actions_clean``)."""
|
|
155
|
+
if not action_taken:
|
|
156
|
+
return ""
|
|
157
|
+
try:
|
|
158
|
+
return format_actions_clean(action_taken)
|
|
159
|
+
except Exception:
|
|
160
|
+
return ""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _action_text(action_taken: Any) -> str:
|
|
164
|
+
"""Combine the rendered action with raw fields for *scoring*.
|
|
165
|
+
|
|
166
|
+
Rendered text identifies self-described targets. Raw JSON also makes
|
|
167
|
+
resource IDs, bounds, and other fields searchable. Never shown as-is:
|
|
168
|
+
the excerpt comes from :func:`_step_display_text`.
|
|
169
|
+
"""
|
|
170
|
+
if not action_taken:
|
|
171
|
+
return ""
|
|
172
|
+
rendered = _rendered_action(action_taken)
|
|
173
|
+
raw = json.dumps(action_taken, ensure_ascii=False, default=str)
|
|
174
|
+
return f"{rendered}\n{raw}" if rendered else raw
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _step_haystack(step: dict) -> str:
|
|
178
|
+
"""The scoring surface of a step: every recorded field, raw JSON included."""
|
|
179
|
+
extra = step.get("extra_metadata") or {}
|
|
180
|
+
fields = [
|
|
181
|
+
str(step.get("summary") or ""),
|
|
182
|
+
_action_text(step.get("action_taken")),
|
|
183
|
+
json.dumps(step.get("last_execution_result"), ensure_ascii=False, default=str)
|
|
184
|
+
if step.get("last_execution_result")
|
|
185
|
+
else "",
|
|
186
|
+
str(step.get("operator_raw_thinking") or ""),
|
|
187
|
+
_events_text(step),
|
|
188
|
+
str(extra.get("injected_instruction") or ""),
|
|
189
|
+
# The foreground app package stamped by record_step joins the search
|
|
190
|
+
# surface, so package-name queries never depend on the screen-text
|
|
191
|
+
# scan (capped to recent steps) alone.
|
|
192
|
+
str(extra.get("foreground_app") or ""),
|
|
193
|
+
]
|
|
194
|
+
return "\n".join(f for f in fields if f)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _result_display_text(result: Any) -> str:
|
|
198
|
+
"""The execution result as words: its status and result/error strings
|
|
199
|
+
(an execution incident renders as its one-line phrase), never JSON."""
|
|
200
|
+
if not result:
|
|
201
|
+
return ""
|
|
202
|
+
if not isinstance(result, dict):
|
|
203
|
+
return str(result)
|
|
204
|
+
parts: list[str] = []
|
|
205
|
+
status = result.get("status")
|
|
206
|
+
if status:
|
|
207
|
+
parts.append(f"status {status}")
|
|
208
|
+
try:
|
|
209
|
+
from artemis.utils.task_tree import format_result_clean
|
|
210
|
+
|
|
211
|
+
incident_or_error = format_result_clean(result)
|
|
212
|
+
except Exception:
|
|
213
|
+
incident_or_error = None
|
|
214
|
+
if incident_or_error:
|
|
215
|
+
parts.append(str(incident_or_error))
|
|
216
|
+
else:
|
|
217
|
+
for key in ("result", "error", "error_msg", "message"):
|
|
218
|
+
value = result.get(key)
|
|
219
|
+
if isinstance(value, str) and value.strip():
|
|
220
|
+
parts.append(value.strip())
|
|
221
|
+
return " | ".join(parts)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _events_display_text(step: dict) -> str:
|
|
225
|
+
"""Tool calls and reasoning of a step as the replay shows them (name,
|
|
226
|
+
arguments, result text), without the raw event JSON."""
|
|
227
|
+
parts: list[str] = []
|
|
228
|
+
for event in step.get("interleaved_events") or []:
|
|
229
|
+
etype = event.get("type")
|
|
230
|
+
if etype == "tool_call":
|
|
231
|
+
name = str(event.get("name") or "")
|
|
232
|
+
args = event.get("args") or {}
|
|
233
|
+
result_text = result_search_text(event.get("result"))
|
|
234
|
+
try:
|
|
235
|
+
call = f"{name}({json.dumps(args, ensure_ascii=False, default=str)})"
|
|
236
|
+
except (TypeError, ValueError):
|
|
237
|
+
call = name
|
|
238
|
+
parts.append(f"{call} -> {result_text}" if result_text else call)
|
|
239
|
+
elif etype and "thought" in etype:
|
|
240
|
+
parts.append(str(event.get("content") or ""))
|
|
241
|
+
return "\n".join(p for p in parts if p)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _step_display_text(step: dict) -> str:
|
|
245
|
+
"""The *excerpt* surface of a step: the same fields as the haystack, as the
|
|
246
|
+
agent would read them. The summary is left out — it already heads the
|
|
247
|
+
result as the ``Screen:`` line."""
|
|
248
|
+
extra = step.get("extra_metadata") or {}
|
|
249
|
+
fields = [
|
|
250
|
+
_rendered_action(step.get("action_taken")),
|
|
251
|
+
_result_display_text(step.get("last_execution_result")),
|
|
252
|
+
str(step.get("operator_raw_thinking") or ""),
|
|
253
|
+
_events_display_text(step),
|
|
254
|
+
str(extra.get("injected_instruction") or ""),
|
|
255
|
+
str(extra.get("foreground_app") or ""),
|
|
256
|
+
]
|
|
257
|
+
# Excerpts flatten whitespace, so the field boundary must survive as text.
|
|
258
|
+
return " | ".join(f.strip() for f in fields if f and f.strip())
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _image_text(reader: Any, image_name: str | None) -> str:
|
|
262
|
+
"""Raw XML/OCR text stored for one screenshot — the textual description
|
|
263
|
+
of that image (package/activity strings live inside the XML blob)."""
|
|
264
|
+
if not image_name:
|
|
265
|
+
return ""
|
|
266
|
+
try:
|
|
267
|
+
record = reader.storage.get_image(image_name)
|
|
268
|
+
except (sqlite3.Error, ValueError):
|
|
269
|
+
return ""
|
|
270
|
+
if record is None:
|
|
271
|
+
return ""
|
|
272
|
+
parts = []
|
|
273
|
+
for blob in (record.ui_tree, record.ocr_result):
|
|
274
|
+
if blob:
|
|
275
|
+
parts.append(
|
|
276
|
+
blob if isinstance(blob, str) else json.dumps(blob, ensure_ascii=False, default=str)
|
|
277
|
+
)
|
|
278
|
+
return "\n".join(parts)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def referenced_image_names(step: dict) -> list[str]:
|
|
282
|
+
"""Screenshots that tool results of this step embedded (rewritten by the
|
|
283
|
+
DataEngine into ``[screenshot: …]`` text blocks carrying ``image_name``)."""
|
|
284
|
+
names: list[str] = []
|
|
285
|
+
|
|
286
|
+
def walk(value: Any) -> None:
|
|
287
|
+
if isinstance(value, dict):
|
|
288
|
+
name = value.get("image_name")
|
|
289
|
+
if value.get("type") == "text" and name and name not in names:
|
|
290
|
+
names.append(str(name))
|
|
291
|
+
for child in value.values():
|
|
292
|
+
walk(child)
|
|
293
|
+
elif isinstance(value, list):
|
|
294
|
+
for child in value:
|
|
295
|
+
walk(child)
|
|
296
|
+
|
|
297
|
+
for event in step.get("interleaved_events") or []:
|
|
298
|
+
if event.get("type") == "tool_call":
|
|
299
|
+
walk(event.get("result"))
|
|
300
|
+
return names
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
#: UI-tree node attributes whose values are human-readable screen content.
|
|
304
|
+
_UI_NODE_TEXT_KEYS = ("text", "content-desc", "content_desc", "resource-id", "resource_id")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _collect_screen_strings(value: Any, out: list[str], depth: int = 0) -> None:
|
|
308
|
+
"""Walks a stored UI tree / OCR blob and appends its readable values
|
|
309
|
+
(element text, content description, resource id, OCR text) in order."""
|
|
310
|
+
if depth > 50:
|
|
311
|
+
return
|
|
312
|
+
if isinstance(value, str):
|
|
313
|
+
text = " ".join(value.split())
|
|
314
|
+
if text and text not in out:
|
|
315
|
+
out.append(text)
|
|
316
|
+
elif isinstance(value, dict):
|
|
317
|
+
for key in _UI_NODE_TEXT_KEYS:
|
|
318
|
+
text = value.get(key)
|
|
319
|
+
if isinstance(text, str):
|
|
320
|
+
text = " ".join(text.split())
|
|
321
|
+
if text and text not in out:
|
|
322
|
+
out.append(text)
|
|
323
|
+
for child_key in ("children", "nodes", "elements"):
|
|
324
|
+
children = value.get(child_key)
|
|
325
|
+
if isinstance(children, (list, tuple)):
|
|
326
|
+
for child in children:
|
|
327
|
+
_collect_screen_strings(child, out, depth + 1)
|
|
328
|
+
elif isinstance(value, (list, tuple)):
|
|
329
|
+
for child in value:
|
|
330
|
+
_collect_screen_strings(child, out, depth + 1)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _screen_names(step: dict) -> list[str]:
|
|
334
|
+
names: list[str] = []
|
|
335
|
+
for name in (
|
|
336
|
+
step.get("pre_image_name"),
|
|
337
|
+
step.get("post_image_name"),
|
|
338
|
+
*referenced_image_names(step),
|
|
339
|
+
):
|
|
340
|
+
if name and name not in names:
|
|
341
|
+
names.append(name)
|
|
342
|
+
return names
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _screen_text(reader: Any, step: dict) -> str:
|
|
346
|
+
"""Description text of the screenshots behind a step (its own pre- and
|
|
347
|
+
post-action screenshots plus any screenshot a tool result embedded) —
|
|
348
|
+
the raw XML/OCR blobs, for *scoring* only."""
|
|
349
|
+
return "\n".join(t for t in (_image_text(reader, n) for n in _screen_names(step)) if t)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _screen_display_text(reader: Any, step: dict) -> str:
|
|
353
|
+
"""The readable content of the same screenshots for the *excerpt*: element
|
|
354
|
+
text / content descriptions / resource ids and OCR text joined by
|
|
355
|
+
``" | "``, never the UI-tree JSON."""
|
|
356
|
+
strings: list[str] = []
|
|
357
|
+
for name in _screen_names(step):
|
|
358
|
+
try:
|
|
359
|
+
record = reader.storage.get_image(name)
|
|
360
|
+
except (sqlite3.Error, ValueError, AttributeError):
|
|
361
|
+
continue
|
|
362
|
+
if record is None:
|
|
363
|
+
continue
|
|
364
|
+
for blob in (record.ui_tree, record.ocr_result):
|
|
365
|
+
if blob:
|
|
366
|
+
_collect_screen_strings(blob, strings)
|
|
367
|
+
return " | ".join(strings)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _step_label(step: dict, session_start: float | None) -> str:
|
|
371
|
+
from artemis.memory.chunking import step_offset_label
|
|
372
|
+
|
|
373
|
+
return f"Step {step.get('step_number')} ({step_offset_label(step, session_start)})"
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _note_writer_steps(steps: list[dict], key: str) -> list[int]:
|
|
377
|
+
writers: list[int] = []
|
|
378
|
+
for step in steps:
|
|
379
|
+
for event in step.get("interleaved_events") or []:
|
|
380
|
+
if (
|
|
381
|
+
event.get("type") == "tool_call"
|
|
382
|
+
and event.get("name") in ("save_note", "update_note", "append_note")
|
|
383
|
+
and (event.get("args") or {}).get("key") == key
|
|
384
|
+
):
|
|
385
|
+
number = step.get("step_number")
|
|
386
|
+
if isinstance(number, int):
|
|
387
|
+
writers.append(number)
|
|
388
|
+
return writers
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _parse_range(step_range: list[int] | None) -> tuple[int | None, int | None]:
|
|
392
|
+
if not step_range:
|
|
393
|
+
return None, None
|
|
394
|
+
try:
|
|
395
|
+
start, end = int(step_range[0]), int(step_range[-1])
|
|
396
|
+
except (TypeError, ValueError, IndexError):
|
|
397
|
+
return None, None
|
|
398
|
+
if start > end:
|
|
399
|
+
start, end = end, start
|
|
400
|
+
return start, end
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# pylint: disable=too-many-branches,too-many-locals,too-many-statements
|
|
404
|
+
def search_history_text(
|
|
405
|
+
reader: Any,
|
|
406
|
+
*,
|
|
407
|
+
query: str = "",
|
|
408
|
+
step_range: list[int] | None = None,
|
|
409
|
+
max_results: int = DEFAULT_MAX_RESULTS,
|
|
410
|
+
recall_config: Any = None,
|
|
411
|
+
) -> str:
|
|
412
|
+
"""Deterministic history search over ``reader``; always returns text."""
|
|
413
|
+
cfg_max_results = int(getattr(recall_config, "max_results", None) or DEFAULT_MAX_RESULTS)
|
|
414
|
+
cfg_max_tokens = int(getattr(recall_config, "max_text_tokens", None) or DEFAULT_MAX_TEXT_TOKENS)
|
|
415
|
+
scan_steps = int(getattr(recall_config, "screen_scan_steps", None) or DEFAULT_SCREEN_SCAN_STEPS)
|
|
416
|
+
max_results = max(1, min(int(max_results or cfg_max_results), cfg_max_results))
|
|
417
|
+
char_budget = cfg_max_tokens * 4
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
steps: list[dict] = reader.get_agent_friendly_steps() or []
|
|
421
|
+
except Exception as e:
|
|
422
|
+
return ToolFailure(f"search_history failed to load history: {e}")
|
|
423
|
+
|
|
424
|
+
range_start, range_end = _parse_range(step_range)
|
|
425
|
+
|
|
426
|
+
def in_range(number: Any) -> bool:
|
|
427
|
+
if range_start is None:
|
|
428
|
+
return True
|
|
429
|
+
return isinstance(number, int) and range_start <= number <= range_end
|
|
430
|
+
|
|
431
|
+
scoped_steps = [s for s in steps if in_range(s.get("step_number"))]
|
|
432
|
+
terms = _terms(query)
|
|
433
|
+
session_start = getattr(reader, "session_start_time", None)
|
|
434
|
+
|
|
435
|
+
if not steps:
|
|
436
|
+
return "search_history: no recorded steps yet."
|
|
437
|
+
if step_range and not scoped_steps:
|
|
438
|
+
return f"search_history: no recorded steps in range {range_start}–{range_end}."
|
|
439
|
+
|
|
440
|
+
sections: list[str] = []
|
|
441
|
+
|
|
442
|
+
# --- Range ledger: the explicit re-entry point for compressed-history
|
|
443
|
+
# marker lines — full-width build_action_ledger rows.
|
|
444
|
+
if range_start is not None and scoped_steps:
|
|
445
|
+
from artemis.memory.chunking import build_action_ledger
|
|
446
|
+
|
|
447
|
+
ledger = build_action_ledger(scoped_steps, session_start)
|
|
448
|
+
sections.append(
|
|
449
|
+
f"Action ledger for Steps {range_start}–{range_end}"
|
|
450
|
+
f" ({len(scoped_steps)} recorded steps):\n{ledger}"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# --- Keyword search over steps.
|
|
454
|
+
results: list[tuple[int, int, str]] = [] # (score, step_number, rendered)
|
|
455
|
+
if terms:
|
|
456
|
+
screen_eligible = {id(s) for s in scoped_steps[-scan_steps:]} if scan_steps > 0 else set()
|
|
457
|
+
for step in scoped_steps:
|
|
458
|
+
haystack = _step_haystack(step)
|
|
459
|
+
screen = _screen_text(reader, step) if id(step) in screen_eligible else ""
|
|
460
|
+
score = _score(haystack, terms)
|
|
461
|
+
screen_score = _score(screen, terms) if screen else 0
|
|
462
|
+
total = score + screen_score
|
|
463
|
+
if total <= 0:
|
|
464
|
+
continue
|
|
465
|
+
lines = [f"[{_step_label(step, session_start)} | id {step.get('step_id')}]"]
|
|
466
|
+
summary = step.get("summary")
|
|
467
|
+
if summary:
|
|
468
|
+
lines.append(f" Screen: {_clamp(str(summary), EXCERPT_CHARS)}")
|
|
469
|
+
# The excerpt comes from the readable twin of whichever surface
|
|
470
|
+
# scored higher; the raw JSON only ever scores. When the hit sits
|
|
471
|
+
# in the summary alone, the Screen line already shows it.
|
|
472
|
+
display = (
|
|
473
|
+
_step_display_text(step)
|
|
474
|
+
if score >= screen_score
|
|
475
|
+
else _screen_display_text(reader, step)
|
|
476
|
+
)
|
|
477
|
+
display_hit = _score(display, terms) > 0
|
|
478
|
+
summary_hit = bool(summary) and _score(str(summary), terms) > 0
|
|
479
|
+
if display and (display_hit or not summary_hit):
|
|
480
|
+
lines.append(f" Match: {_excerpt(display, terms, EXCERPT_CHARS)}")
|
|
481
|
+
number = step.get("step_number") or 0
|
|
482
|
+
results.append((total, number, "\n".join(lines)))
|
|
483
|
+
|
|
484
|
+
# --- History chunks (bands ①②③ / rendered text).
|
|
485
|
+
try:
|
|
486
|
+
chunk_rows = reader.get_history_chunks() or []
|
|
487
|
+
except (sqlite3.Error, ValueError):
|
|
488
|
+
chunk_rows = []
|
|
489
|
+
for row in chunk_rows:
|
|
490
|
+
start_n = getattr(row, "start_step_number", None)
|
|
491
|
+
end_n = getattr(row, "end_step_number", None)
|
|
492
|
+
if range_start is not None and not (
|
|
493
|
+
isinstance(start_n, int)
|
|
494
|
+
and isinstance(end_n, int)
|
|
495
|
+
and start_n <= range_end
|
|
496
|
+
and end_n >= range_start
|
|
497
|
+
):
|
|
498
|
+
continue
|
|
499
|
+
text = getattr(row, "rendered_text", None) or "\n".join(
|
|
500
|
+
filter(None, [getattr(row, "band2", None), getattr(row, "band3", None)])
|
|
501
|
+
)
|
|
502
|
+
score = _score(text or "", terms)
|
|
503
|
+
if score <= 0:
|
|
504
|
+
continue
|
|
505
|
+
rendered = (
|
|
506
|
+
f"[History chunk | Steps {start_n}–{end_n} | status"
|
|
507
|
+
f" {getattr(row, 'status', '?')}]\n Match: {_excerpt(text, terms, EXCERPT_CHARS)}"
|
|
508
|
+
)
|
|
509
|
+
results.append((score, int(end_n or 0), rendered))
|
|
510
|
+
|
|
511
|
+
# --- Notes (filesystem; attributed to their writing steps).
|
|
512
|
+
try:
|
|
513
|
+
from artemis.utils.notes import list_notes_keys, read_note_content
|
|
514
|
+
|
|
515
|
+
base_dir = getattr(reader, "base_dir", None)
|
|
516
|
+
note_keys = list_notes_keys(base_dir) if base_dir else []
|
|
517
|
+
except OSError:
|
|
518
|
+
note_keys = []
|
|
519
|
+
for key in note_keys:
|
|
520
|
+
try:
|
|
521
|
+
content = read_note_content(base_dir, key)
|
|
522
|
+
except (OSError, UnicodeError):
|
|
523
|
+
continue
|
|
524
|
+
score = _score(content, terms) + _score(key, terms)
|
|
525
|
+
if score <= 0:
|
|
526
|
+
continue
|
|
527
|
+
writers = _note_writer_steps(steps, key)
|
|
528
|
+
if (
|
|
529
|
+
range_start is not None
|
|
530
|
+
and writers
|
|
531
|
+
and not any(range_start <= n <= range_end for n in writers)
|
|
532
|
+
):
|
|
533
|
+
continue
|
|
534
|
+
anchor = writers[-1] if writers else (steps[-1].get("step_number") or 0)
|
|
535
|
+
origin = f"last written at Step {writers[-1]}" if writers else f"as of Step {anchor}"
|
|
536
|
+
results.append(
|
|
537
|
+
(
|
|
538
|
+
score,
|
|
539
|
+
int(anchor),
|
|
540
|
+
f"[Note '{key}' | {origin}]\n Match: {_excerpt(content, terms, EXCERPT_CHARS)}",
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
results.sort(key=lambda r: (-r[0], -r[1]))
|
|
545
|
+
kept = results[:max_results]
|
|
546
|
+
dropped = len(results) - len(kept)
|
|
547
|
+
|
|
548
|
+
if kept:
|
|
549
|
+
header = f"Top {len(kept)} match(es) for {terms}:"
|
|
550
|
+
if dropped > 0:
|
|
551
|
+
header += f" ({dropped} more not shown; narrow the query or step_range)"
|
|
552
|
+
sections.append(header + "\n\n" + "\n".join(r[2] for r in kept))
|
|
553
|
+
elif terms:
|
|
554
|
+
sections.append(f"No matches for {terms} in the searched history.")
|
|
555
|
+
|
|
556
|
+
text = "\n\n".join(sections)
|
|
557
|
+
if _estimate_tokens(text) > cfg_max_tokens:
|
|
558
|
+
text = (
|
|
559
|
+
text[:char_budget].rstrip()
|
|
560
|
+
+ "\n… (response truncated at the search token budget; narrow the"
|
|
561
|
+
" query or step_range)"
|
|
562
|
+
)
|
|
563
|
+
return text
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Universal Image Processor tool for executing image transformations."""
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from langchain_core.tools import BaseTool
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
|
|
22
|
+
from artemis.context import ArtemisContext
|
|
23
|
+
from artemis.data_engine.trace import trace, trace_langchain_tool
|
|
24
|
+
from artemis.drivers.base import BaseDeviceDriver
|
|
25
|
+
from artemis.graph.state import State
|
|
26
|
+
from artemis.tools.base import ArtemisTool
|
|
27
|
+
from artemis.tools.tool_wrapper import ToolWrapper
|
|
28
|
+
from artemis.utils.cython_compat import CyFunctionDetector
|
|
29
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# pylint: disable=too-few-public-methods
|
|
35
|
+
class AskVisionCoderArgs(BaseModel):
|
|
36
|
+
"""Arguments schema for invoking the Image Processor subagent."""
|
|
37
|
+
|
|
38
|
+
model_config = {"ignored_types": (CyFunctionDetector,)}
|
|
39
|
+
instruction: str = Field(
|
|
40
|
+
...,
|
|
41
|
+
description=(
|
|
42
|
+
"Step-by-step instructions on what image processing or"
|
|
43
|
+
" modifications should be performed."
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
target_image_id: str = Field(
|
|
47
|
+
...,
|
|
48
|
+
description=(
|
|
49
|
+
"The ID of the image in the Image Pool to start from (e.g. 'img_0', 'img_1')."
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Alias for naming consistency
|
|
55
|
+
AskImageProcessorArgs = AskVisionCoderArgs
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AskImageProcessorTool(ArtemisTool):
|
|
59
|
+
"""Universal tool for executing image transformations via ImageProcessor subagent."""
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
super().__init__(
|
|
63
|
+
name="ask_image_processor",
|
|
64
|
+
description=(
|
|
65
|
+
"Call this tool to ask the image processor agent to run"
|
|
66
|
+
" complex image transformations or modifications."
|
|
67
|
+
),
|
|
68
|
+
args_schema=AskVisionCoderArgs,
|
|
69
|
+
category="custom",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
73
|
+
async def execute(
|
|
74
|
+
self,
|
|
75
|
+
driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
|
|
76
|
+
ctx: ArtemisContext | None = None,
|
|
77
|
+
instruction: str | None = None,
|
|
78
|
+
target_image_id: str | None = None,
|
|
79
|
+
state: State | None = None,
|
|
80
|
+
**kwargs: Any,
|
|
81
|
+
) -> Any:
|
|
82
|
+
inst = instruction or kwargs.get("Instruction") or ""
|
|
83
|
+
tid = target_image_id or kwargs.get("TargetImageId") or kwargs.get("target_image_id") or ""
|
|
84
|
+
st = state if state is not None else kwargs.get("state")
|
|
85
|
+
return await _run_image_processor_logic(ctx, st, inst, tid)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Universal tool instance & aliases
|
|
89
|
+
ask_image_processor = AskImageProcessorTool()
|
|
90
|
+
AskImageProcessor = AskImageProcessorTool
|
|
91
|
+
AskVisionCoderTool = AskImageProcessorTool
|
|
92
|
+
ImageProcessorTool = AskImageProcessorTool
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_ask_image_processor_tool(ctx: ArtemisContext) -> BaseTool:
|
|
96
|
+
"""Exports ask_image_processor as a LangChain BaseTool."""
|
|
97
|
+
return trace_langchain_tool(ask_image_processor.to_langchain_tool(ctx), ctx)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def _run_image_processor_logic(
|
|
101
|
+
ctx: ArtemisContext | None,
|
|
102
|
+
state: State | None,
|
|
103
|
+
instruction: str,
|
|
104
|
+
target_image_id: str, # pylint: disable=unused-argument
|
|
105
|
+
) -> Any:
|
|
106
|
+
@trace(type="agent", name="image_processor", ctx=ctx)
|
|
107
|
+
async def run_image_processor_agent(inst: str, img_path: str) -> dict:
|
|
108
|
+
from artemis.agents.image_processor.image_processor import ( # pylint: disable=import-outside-toplevel
|
|
109
|
+
ImageProcessor,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
agent = ImageProcessor(ctx)
|
|
113
|
+
return await agent.run(inst, img_path)
|
|
114
|
+
|
|
115
|
+
screenshot_path = state.latest_screenshot if state else None
|
|
116
|
+
result = await run_image_processor_agent(instruction, screenshot_path)
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
ask_image_processor_wrapper = ToolWrapper(
|
|
121
|
+
tool_fn_getter=get_ask_image_processor_tool,
|
|
122
|
+
on_success_fn=lambda output: f"Image Processor replied:\n{output}",
|
|
123
|
+
on_failure_fn=lambda error: f"Image Processor failed: {error}",
|
|
124
|
+
)
|