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,403 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Annotated, Any
|
|
19
|
+
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
|
|
20
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
from artemis.utils.cython_compat import CyFunctionDetector
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpawnLogReaderArgs(BaseModel):
|
|
26
|
+
model_config = {"ignored_types": (CyFunctionDetector,)}
|
|
27
|
+
specific_query: str = Field(
|
|
28
|
+
...,
|
|
29
|
+
description=(
|
|
30
|
+
"Specific instructions for the sub-agent, including what to look for and analyze."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
from langgraph.prebuilt import InjectedState
|
|
36
|
+
|
|
37
|
+
from artemis.core.tool_failure import ToolFailure, is_tool_failure
|
|
38
|
+
from artemis.context import ArtemisContext
|
|
39
|
+
from artemis.data_engine.trace import (
|
|
40
|
+
CURRENT_TRACE_ID,
|
|
41
|
+
TraceSpan,
|
|
42
|
+
trace,
|
|
43
|
+
)
|
|
44
|
+
from artemis.graph.state import State
|
|
45
|
+
from artemis.services.llm import acomplete, get_llm, invoke_llm_with_timeout_message
|
|
46
|
+
from artemis.tools.index import get_tool_by_name
|
|
47
|
+
from artemis.tools.mobile.log_utils import fetch_and_filter_logs
|
|
48
|
+
from artemis.tools.mobile.read_logs import get_read_logs_tool
|
|
49
|
+
from artemis.tools.mobile.search_logs import (
|
|
50
|
+
get_search_logs_tool,
|
|
51
|
+
search_and_merge_logs,
|
|
52
|
+
)
|
|
53
|
+
from artemis.tools.tool_wrapper import (
|
|
54
|
+
get_tool_result_content,
|
|
55
|
+
invoke_tool_with_injection,
|
|
56
|
+
)
|
|
57
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
58
|
+
|
|
59
|
+
logger = get_logger(__name__)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LogAnalyzerNode:
|
|
63
|
+
def __init__(self, ctx: ArtemisContext):
|
|
64
|
+
self.ctx = ctx
|
|
65
|
+
self._log_cache = {}
|
|
66
|
+
self._raw_log_cache = {}
|
|
67
|
+
|
|
68
|
+
def _wrap_with_caching(self, tool: BaseTool) -> BaseTool:
|
|
69
|
+
"""Wraps a tool with caching logic based on tool name and arguments."""
|
|
70
|
+
original_coroutine = tool.coroutine
|
|
71
|
+
|
|
72
|
+
async def cached_coroutine(**kwargs):
|
|
73
|
+
if tool.name == "read_logs":
|
|
74
|
+
# Intercept read_logs to cache raw logs
|
|
75
|
+
lines = kwargs.get("lines", 200)
|
|
76
|
+
# Since we might have since_time/until_time, let's keep it simple or key by all.
|
|
77
|
+
# The instruction says: "cache the returned string based on the lines parameter."
|
|
78
|
+
# We can also include since_time and until_time in the cache key to be safe, but let's follow the lines-based requirement.
|
|
79
|
+
cache_key = (
|
|
80
|
+
lines,
|
|
81
|
+
kwargs.get("since_time"),
|
|
82
|
+
kwargs.get("until_time"),
|
|
83
|
+
)
|
|
84
|
+
if cache_key in self._raw_log_cache:
|
|
85
|
+
logger.info(f"Raw log cache hit for read_logs with key {cache_key}")
|
|
86
|
+
return self._raw_log_cache[cache_key]
|
|
87
|
+
|
|
88
|
+
result = await original_coroutine(**kwargs)
|
|
89
|
+
if isinstance(result, str) and not is_tool_failure(result):
|
|
90
|
+
self._raw_log_cache[cache_key] = result
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
elif tool.name == "search_logs":
|
|
94
|
+
lines = kwargs.get("lines", 10000)
|
|
95
|
+
since_time = kwargs.get("since_time")
|
|
96
|
+
until_time = kwargs.get("until_time")
|
|
97
|
+
raw_cache_key = (lines, since_time, until_time)
|
|
98
|
+
|
|
99
|
+
# Check if we have the raw logs cached
|
|
100
|
+
if raw_cache_key in self._raw_log_cache:
|
|
101
|
+
logger.info(
|
|
102
|
+
"Raw log cache hit for search_logs with key"
|
|
103
|
+
f" {raw_cache_key}. Running local search."
|
|
104
|
+
)
|
|
105
|
+
raw_logs = self._raw_log_cache[raw_cache_key]
|
|
106
|
+
# Run search_and_merge_logs locally
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
return search_and_merge_logs(
|
|
110
|
+
logs=raw_logs,
|
|
111
|
+
keyword=kwargs.get("keyword"),
|
|
112
|
+
context_lines=kwargs.get("context_lines", 0),
|
|
113
|
+
is_regex=kwargs.get("is_regex", False),
|
|
114
|
+
)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Failed to search logs locally: {e}")
|
|
117
|
+
return ToolFailure(f"Error searching logs: {e}")
|
|
118
|
+
|
|
119
|
+
# If not cached, fetch them once via read_logs (or the original tool, which will fetch them)
|
|
120
|
+
# Note: search_logs itself fetches and filters. We can let the original_coroutine run,
|
|
121
|
+
# but to cache the raw logs, we need the raw logs.
|
|
122
|
+
# Wait, search_logs returns the SEARCHED results, not the raw logs.
|
|
123
|
+
# So if we just run original_coroutine, we don't get the raw logs to cache them!
|
|
124
|
+
# To cache them, we should fetch them using get_read_logs_tool or fetch_and_filter_logs,
|
|
125
|
+
# cache them, and then run search_and_merge_logs.
|
|
126
|
+
logger.info(
|
|
127
|
+
"Raw log cache miss for search_logs with key"
|
|
128
|
+
f" {raw_cache_key}. Fetching raw logs first."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
raw_logs = fetch_and_filter_logs(
|
|
133
|
+
ctx=self.ctx,
|
|
134
|
+
lines=lines,
|
|
135
|
+
since_time=since_time,
|
|
136
|
+
until_time=until_time,
|
|
137
|
+
)
|
|
138
|
+
self._raw_log_cache[raw_cache_key] = raw_logs
|
|
139
|
+
|
|
140
|
+
return search_and_merge_logs(
|
|
141
|
+
logs=raw_logs,
|
|
142
|
+
keyword=kwargs.get("keyword"),
|
|
143
|
+
context_lines=kwargs.get("context_lines", 0),
|
|
144
|
+
is_regex=kwargs.get("is_regex", False),
|
|
145
|
+
)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Failed to fetch and search logs: {e}")
|
|
148
|
+
return ToolFailure(f"Error searching logs: {e}")
|
|
149
|
+
|
|
150
|
+
# Fallback to standard tool caching for other tools
|
|
151
|
+
serialized_args = json.dumps(kwargs, sort_keys=True)
|
|
152
|
+
cache_key = f"{tool.name}:{serialized_args}"
|
|
153
|
+
if cache_key in self._log_cache:
|
|
154
|
+
logger.info(f"Cache hit for tool {tool.name} with args {kwargs}")
|
|
155
|
+
return self._log_cache[cache_key]
|
|
156
|
+
|
|
157
|
+
logger.info(
|
|
158
|
+
f"Cache miss for tool {tool.name} with args {kwargs}. Invoking original tool."
|
|
159
|
+
)
|
|
160
|
+
result = await original_coroutine(**kwargs)
|
|
161
|
+
self._log_cache[cache_key] = result
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
return StructuredTool.from_function(
|
|
165
|
+
coroutine=cached_coroutine,
|
|
166
|
+
name=tool.name,
|
|
167
|
+
description=tool.description,
|
|
168
|
+
args_schema=tool.args_schema,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def _run_agent_loop(
|
|
172
|
+
self,
|
|
173
|
+
base_llm: Any,
|
|
174
|
+
tools_for_binding: list[Any],
|
|
175
|
+
tools: list[BaseTool],
|
|
176
|
+
current_messages: list[Any],
|
|
177
|
+
state: State,
|
|
178
|
+
max_iterations: int = 10,
|
|
179
|
+
agent_name: str = "Agent",
|
|
180
|
+
) -> str:
|
|
181
|
+
outcome = "No result"
|
|
182
|
+
|
|
183
|
+
for i in range(max_iterations):
|
|
184
|
+
if i == max_iterations - 1:
|
|
185
|
+
current_messages.append(
|
|
186
|
+
HumanMessage(
|
|
187
|
+
content=(
|
|
188
|
+
"[WARNING] This is your final iteration; all tools"
|
|
189
|
+
" are stripped, and you must provide your final"
|
|
190
|
+
" answer/summary directly."
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
llm = base_llm.bind_tools(tools=[])
|
|
195
|
+
else:
|
|
196
|
+
if i > 0:
|
|
197
|
+
current_messages.append(
|
|
198
|
+
HumanMessage(
|
|
199
|
+
content=(
|
|
200
|
+
"[WARNING] You have not completed the analysis"
|
|
201
|
+
f" yet (iteration {i + 1} of {max_iterations})."
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
llm = base_llm.bind_tools(tools=tools_for_binding)
|
|
206
|
+
|
|
207
|
+
response = await invoke_llm_with_timeout_message(acomplete(llm, current_messages))
|
|
208
|
+
|
|
209
|
+
if response.content:
|
|
210
|
+
outcome = response.content
|
|
211
|
+
|
|
212
|
+
if not response.tool_calls:
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
if i == max_iterations - 1:
|
|
216
|
+
tool_names = ", ".join(tc["name"] for tc in response.tool_calls)
|
|
217
|
+
warning = (
|
|
218
|
+
f"\n\n[Warning: {agent_name} reached the maximum iteration"
|
|
219
|
+
f" limit of {max_iterations} and could not execute tool(s):"
|
|
220
|
+
f" {tool_names}]"
|
|
221
|
+
)
|
|
222
|
+
if outcome == "No result":
|
|
223
|
+
outcome = (
|
|
224
|
+
f"{agent_name} reached the maximum iteration limit of"
|
|
225
|
+
f" {max_iterations} while attempting to call tools:"
|
|
226
|
+
f" {tool_names}"
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
outcome += warning
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
current_messages.append(response)
|
|
233
|
+
|
|
234
|
+
# Handle tool calls in parallel
|
|
235
|
+
async def run_tool(tc) -> ToolMessage:
|
|
236
|
+
tool_name = tc["name"].split(":")[-1] if ":" in tc["name"] else tc["name"]
|
|
237
|
+
logger.info(f"{agent_name} requested tool: {tool_name}")
|
|
238
|
+
|
|
239
|
+
result = ""
|
|
240
|
+
status = "success"
|
|
241
|
+
try:
|
|
242
|
+
if ":" in tool_name:
|
|
243
|
+
tool_to_run = get_tool_by_name(tool_name, tools)
|
|
244
|
+
else:
|
|
245
|
+
tool_to_run = next((t for t in tools if t.name == tool_name), None)
|
|
246
|
+
if tool_to_run:
|
|
247
|
+
args = dict(tc["args"])
|
|
248
|
+
with TraceSpan(name=tool_name, ctx=self.ctx) as span:
|
|
249
|
+
span.payload = {"args": args}
|
|
250
|
+
try:
|
|
251
|
+
result_obj = await invoke_tool_with_injection(
|
|
252
|
+
tool=tool_to_run,
|
|
253
|
+
args=args,
|
|
254
|
+
tool_call_id=tc["id"],
|
|
255
|
+
state=state,
|
|
256
|
+
)
|
|
257
|
+
result = get_tool_result_content(result_obj)
|
|
258
|
+
if isinstance(result, list):
|
|
259
|
+
result = "\n".join(map(str, result))
|
|
260
|
+
elif not isinstance(result, str):
|
|
261
|
+
result = str(result)
|
|
262
|
+
span.result = result
|
|
263
|
+
if is_tool_failure(result_obj):
|
|
264
|
+
status = "error"
|
|
265
|
+
span.status = "failed"
|
|
266
|
+
span.error = result
|
|
267
|
+
except Exception as e:
|
|
268
|
+
status = "error"
|
|
269
|
+
span.status = "failed"
|
|
270
|
+
span.error = str(e)
|
|
271
|
+
raise e
|
|
272
|
+
else:
|
|
273
|
+
result = f"Error: Tool {tool_name} not supported"
|
|
274
|
+
status = "error"
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error(f"Error running tool {tool_name}: {e}")
|
|
277
|
+
result = f"Error running tool {tool_name}: {e}"
|
|
278
|
+
status = "error"
|
|
279
|
+
|
|
280
|
+
return ToolMessage(
|
|
281
|
+
tool_call_id=tc["id"],
|
|
282
|
+
content=result,
|
|
283
|
+
status=status,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
active_tool_calls = [
|
|
287
|
+
tc
|
|
288
|
+
for tc in response.tool_calls
|
|
289
|
+
if (tc["name"].split(":")[-1] if ":" in tc["name"] else tc["name"])
|
|
290
|
+
!= "google_search"
|
|
291
|
+
]
|
|
292
|
+
tool_outputs = await asyncio.gather(*(run_tool(tc) for tc in active_tool_calls))
|
|
293
|
+
|
|
294
|
+
for tm in tool_outputs:
|
|
295
|
+
current_messages.append(tm)
|
|
296
|
+
|
|
297
|
+
return outcome
|
|
298
|
+
|
|
299
|
+
@trace(type="agent", name="log_analyzer")
|
|
300
|
+
async def run(self, prompt: str, state: State) -> str:
|
|
301
|
+
"""Runs the main Log Analyzer and returns its summary."""
|
|
302
|
+
prompt_path = Path(__file__).parent.joinpath("log_analyzer.md")
|
|
303
|
+
if not prompt_path.exists():
|
|
304
|
+
raise FileNotFoundError(f"System prompt file not found at {prompt_path}")
|
|
305
|
+
|
|
306
|
+
system_prompt = prompt_path.read_text(encoding="utf-8")
|
|
307
|
+
|
|
308
|
+
llm = get_llm(ctx=self.ctx, name="log_analyzer")
|
|
309
|
+
|
|
310
|
+
all_tools = [
|
|
311
|
+
self._wrap_with_caching(get_read_logs_tool(self.ctx)),
|
|
312
|
+
self._wrap_with_caching(get_search_logs_tool(self.ctx)),
|
|
313
|
+
self._get_spawn_log_reader_tool(),
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
current_messages = [
|
|
317
|
+
SystemMessage(content=system_prompt),
|
|
318
|
+
HumanMessage(content=prompt),
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
return await self._run_agent_loop(
|
|
322
|
+
base_llm=llm,
|
|
323
|
+
tools_for_binding=all_tools,
|
|
324
|
+
tools=all_tools,
|
|
325
|
+
current_messages=current_messages,
|
|
326
|
+
state=state,
|
|
327
|
+
max_iterations=10,
|
|
328
|
+
agent_name="Log Analyzer",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
@trace(type="agent", name="log_reader_sub_agent")
|
|
332
|
+
async def _run_log_reader(self, specific_query: str, state: State) -> str:
|
|
333
|
+
"""Runs the Log Reader Sub-Agent and returns its summary."""
|
|
334
|
+
sub_prompt_path = Path(__file__).parent.joinpath("log_reader_sub_agent.md")
|
|
335
|
+
if not sub_prompt_path.exists():
|
|
336
|
+
sub_prompt_path.write_text(
|
|
337
|
+
"# Log Reader Sub-Agent\nYou are a specialized agent for"
|
|
338
|
+
" reading and analyzing Android logs.\nYour job is to fulfill"
|
|
339
|
+
" the specific prompt given by the Main Agent.\nYou have access"
|
|
340
|
+
" to tools: `read_logs`.\nAnalyze the logs carefully and"
|
|
341
|
+
" provide a clear, structured summary of your findings.\n",
|
|
342
|
+
encoding="utf-8",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
sub_system_prompt = sub_prompt_path.read_text(encoding="utf-8")
|
|
346
|
+
|
|
347
|
+
llm = get_llm(ctx=self.ctx, name="log_reader_sub_agent")
|
|
348
|
+
|
|
349
|
+
sub_tools = [self._wrap_with_caching(get_read_logs_tool(self.ctx))]
|
|
350
|
+
|
|
351
|
+
# Extract the list of keys (metadata) currently stored in _log_cache.
|
|
352
|
+
cache_keys = list(self._log_cache.keys())
|
|
353
|
+
|
|
354
|
+
current_messages = [
|
|
355
|
+
SystemMessage(content=sub_system_prompt),
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
# Append a system message listing the available cache keys so the sub-agent knows what data can be retrieved instantly.
|
|
359
|
+
if cache_keys:
|
|
360
|
+
current_messages.append(
|
|
361
|
+
SystemMessage(
|
|
362
|
+
content=(
|
|
363
|
+
"The following log reads are already cached and can be"
|
|
364
|
+
" retrieved instantly without cost:\n"
|
|
365
|
+
)
|
|
366
|
+
+ "\n".join(f"- {k}" for k in cache_keys)
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
current_messages.append(HumanMessage(content=specific_query))
|
|
371
|
+
|
|
372
|
+
return await self._run_agent_loop(
|
|
373
|
+
base_llm=llm,
|
|
374
|
+
tools_for_binding=sub_tools,
|
|
375
|
+
tools=sub_tools,
|
|
376
|
+
current_messages=current_messages,
|
|
377
|
+
state=state,
|
|
378
|
+
max_iterations=10,
|
|
379
|
+
agent_name="Log Reader Sub-Agent",
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def _get_spawn_log_reader_tool(self) -> BaseTool:
|
|
383
|
+
|
|
384
|
+
async def spawn_log_reader(
|
|
385
|
+
specific_query: str,
|
|
386
|
+
state: Annotated[State, InjectedState] = None,
|
|
387
|
+
) -> str:
|
|
388
|
+
"""[SPAWN SUB-AGENT] Delegates complex log analysis to a sub-agent.
|
|
389
|
+
|
|
390
|
+
- Use when: Task requires deep analysis, cross-referencing, or
|
|
391
|
+
reading huge log volumes. - Note: The sub-agent has no search tools
|
|
392
|
+
and can only read raw logs. - Input: specific_query (str):
|
|
393
|
+
Instructions for the sub-agent. - Output: A structured summary
|
|
394
|
+
report from the sub-agent.
|
|
395
|
+
"""
|
|
396
|
+
logger.info(f"Spawning Log Reader Sub-Agent with query: '{specific_query}'")
|
|
397
|
+
return await self._run_log_reader(specific_query, state)
|
|
398
|
+
|
|
399
|
+
return StructuredTool.from_function(
|
|
400
|
+
coroutine=spawn_log_reader,
|
|
401
|
+
name="spawn_log_reader",
|
|
402
|
+
args_schema=SpawnLogReaderArgs,
|
|
403
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Log Deep Analysis Instructions
|
|
2
|
+
|
|
3
|
+
Fulfill the specific log analysis prompt by reading and synthesizing Android logs.
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
1. **Direct Answer**: Address the exact questions or patterns requested in the prompt.
|
|
7
|
+
2. **Synthesis**: Do not output raw, unsorted logs. Provide a structured summary of findings.
|
|
8
|
+
3. **Precision**: Include relevant timestamps, line numbers, and error codes to support your conclusions.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Task Output Analyzer
|
|
2
|
+
|
|
3
|
+
You are a specialized agent for analyzing the stdout/stderr output of a command executed on an Android device.
|
|
4
|
+
The user (another agent) will ask you a question about the command's execution output.
|
|
5
|
+
Analyze the output carefully to answer their question.
|
|
6
|
+
|
|
7
|
+
You have access to the following tools to read and search the full output:
|
|
8
|
+
- `read_task_output`: Reads a range of lines from the output.
|
|
9
|
+
- `search_task_output`: Searches the output for a keyword or regex.
|
|
10
|
+
|
|
11
|
+
Be concise, accurate, and direct in your answer.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
|
|
18
|
+
from langchain_core.tools import tool
|
|
19
|
+
from artemis.core.tool_failure import ToolFailure, is_tool_failure
|
|
20
|
+
from artemis.context import ArtemisContext
|
|
21
|
+
from artemis.services.llm import get_llm, invoke_llm_with_timeout_message
|
|
22
|
+
from artemis.tools.mobile.search_logs import search_and_merge_logs
|
|
23
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TaskOutputAnalyzerNode:
|
|
29
|
+
"""Agent that analyzes large command/task execution outputs using LLM and tools."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, ctx: ArtemisContext):
|
|
32
|
+
self.ctx = ctx
|
|
33
|
+
|
|
34
|
+
async def run(
|
|
35
|
+
self,
|
|
36
|
+
command: str,
|
|
37
|
+
output_text: str,
|
|
38
|
+
query: str,
|
|
39
|
+
) -> str:
|
|
40
|
+
prompt_path = Path(__file__).parent.joinpath("output_analyzer.md")
|
|
41
|
+
if not prompt_path.exists():
|
|
42
|
+
raise FileNotFoundError(f"System prompt file not found at {prompt_path}")
|
|
43
|
+
|
|
44
|
+
system_prompt = prompt_path.read_text(encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
# Get the LLM for output_analyzer (falls back to log_analyzer)
|
|
47
|
+
llm = get_llm(ctx=self.ctx, name="output_analyzer")
|
|
48
|
+
|
|
49
|
+
# Local tools created with closure on output_text
|
|
50
|
+
@tool
|
|
51
|
+
def read_task_output(
|
|
52
|
+
start_line: Annotated[int, "The 1-indexed line number to start reading from."],
|
|
53
|
+
end_line: Annotated[int, "The 1-indexed line number to stop reading at (inclusive)."],
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Reads a specific range of lines from the task output.
|
|
56
|
+
|
|
57
|
+
Use this tool when you know the line number range to read a specific
|
|
58
|
+
portion of the output.
|
|
59
|
+
"""
|
|
60
|
+
lines = output_text.splitlines()
|
|
61
|
+
if not lines:
|
|
62
|
+
return "Task output is empty."
|
|
63
|
+
start = max(1, start_line)
|
|
64
|
+
end = min(len(lines), end_line)
|
|
65
|
+
if start > end:
|
|
66
|
+
return (
|
|
67
|
+
f"Error: start_line {start_line} is greater than end_line"
|
|
68
|
+
f" {end_line} or task output length {len(lines)}."
|
|
69
|
+
)
|
|
70
|
+
segment = lines[start - 1 : end]
|
|
71
|
+
return "\n".join(segment)
|
|
72
|
+
|
|
73
|
+
@tool
|
|
74
|
+
def search_task_output(
|
|
75
|
+
keyword: Annotated[
|
|
76
|
+
str,
|
|
77
|
+
"The keyword or regular expression to search for in the output.",
|
|
78
|
+
],
|
|
79
|
+
is_regex: Annotated[
|
|
80
|
+
bool,
|
|
81
|
+
"Whether to treat the keyword as a regular expression. Default is False.",
|
|
82
|
+
] = False,
|
|
83
|
+
context_lines: Annotated[
|
|
84
|
+
int,
|
|
85
|
+
"Number of context lines to include before and after the"
|
|
86
|
+
" matched line. Default is 5.",
|
|
87
|
+
] = 5,
|
|
88
|
+
) -> str:
|
|
89
|
+
"""Searches the task output for a keyword or regular expression, returning matching lines and context.
|
|
90
|
+
|
|
91
|
+
Use this tool to quickly locate error messages, warnings, or
|
|
92
|
+
specific key-value pairs in large outputs.
|
|
93
|
+
"""
|
|
94
|
+
return search_and_merge_logs(
|
|
95
|
+
logs=output_text,
|
|
96
|
+
keyword=keyword,
|
|
97
|
+
context_lines=context_lines,
|
|
98
|
+
is_regex=is_regex,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
tools = [read_task_output, search_task_output]
|
|
102
|
+
llm = llm.bind_tools(tools=tools)
|
|
103
|
+
|
|
104
|
+
# Optimize prompt context with preview if output is large
|
|
105
|
+
lines = output_text.splitlines()
|
|
106
|
+
if len(lines) <= 400:
|
|
107
|
+
log_preview = f"Full Output:\n{output_text}"
|
|
108
|
+
else:
|
|
109
|
+
first_part = "\n".join(lines[:200])
|
|
110
|
+
last_part = "\n".join(lines[-200:])
|
|
111
|
+
log_preview = (
|
|
112
|
+
f"Note: Output is very large ({len(lines)} lines). Here is a"
|
|
113
|
+
" preview of the start and end of the output:\n\n--- START OF"
|
|
114
|
+
f" OUTPUT ---\n{first_part}\n--- TRUNCATED {len(lines) - 400}"
|
|
115
|
+
f" LINES ---\n--- END OF OUTPUT ---\n{last_part}\n\nYou can use"
|
|
116
|
+
" `read_task_output` or `search_task_output` to fetch any"
|
|
117
|
+
" lines in the middle if needed."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
user_content = f"Command Executed: {command}\n\n{log_preview}\n\nQuestion: {query}"
|
|
121
|
+
|
|
122
|
+
current_messages = [
|
|
123
|
+
SystemMessage(content=system_prompt),
|
|
124
|
+
HumanMessage(content=user_content),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
max_iterations = 5
|
|
128
|
+
outcome = "Failed to analyze output."
|
|
129
|
+
|
|
130
|
+
for _ in range(max_iterations):
|
|
131
|
+
response = await invoke_llm_with_timeout_message(llm.ainvoke(current_messages))
|
|
132
|
+
|
|
133
|
+
if response.content:
|
|
134
|
+
outcome = response.content
|
|
135
|
+
|
|
136
|
+
if not response.tool_calls:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
current_messages.append(response)
|
|
140
|
+
|
|
141
|
+
for tc in response.tool_calls:
|
|
142
|
+
tool_name = tc["name"]
|
|
143
|
+
args = tc["args"]
|
|
144
|
+
logger.info(f"Output Analyzer requested tool: {tool_name}")
|
|
145
|
+
|
|
146
|
+
tool_to_run = next((t for t in tools if t.name == tool_name), None)
|
|
147
|
+
if tool_to_run:
|
|
148
|
+
try:
|
|
149
|
+
result = tool_to_run.invoke(args)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
result = ToolFailure(f"Failed to run tool {tool_name}: {e}")
|
|
152
|
+
else:
|
|
153
|
+
result = ToolFailure(f"Error: Tool {tool_name} not found.")
|
|
154
|
+
|
|
155
|
+
current_messages.append(
|
|
156
|
+
ToolMessage(
|
|
157
|
+
tool_call_id=tc["id"],
|
|
158
|
+
content=str(result),
|
|
159
|
+
status="error" if is_tool_failure(result) else "success",
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return outcome
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"templates": [
|
|
3
|
+
"Point to the following objects in the provided image: {labels_str}.",
|
|
4
|
+
"Detect the location of the following objects in the image: {labels_str}.",
|
|
5
|
+
"Locate the following UI components on the screen: {labels_str}."
|
|
6
|
+
],
|
|
7
|
+
"global_timeout": 30.0,
|
|
8
|
+
"instructions": "Return a JSON list containing the detected objects. Each object must have a 'point' array [y, x] (normalized to 0-1000) and a 'label' string.\n\nExample output:\n[\n {\"point\": [500, 250], \"label\": \"search icon\"}\n]\n\nIf no objects are found, return an empty list []."
|
|
9
|
+
}
|