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,36 @@
|
|
|
1
|
+
# Context & Inputs
|
|
2
|
+
|
|
3
|
+
## User's Initial Goal
|
|
4
|
+
> {{ initial_goal }}
|
|
5
|
+
|
|
6
|
+
{% if plan_and_history %}
|
|
7
|
+
## Execution History & Plan
|
|
8
|
+
```markdown
|
|
9
|
+
{{ plan_and_history }}
|
|
10
|
+
```
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
{% if output_description and not structured_output %}
|
|
14
|
+
## Target Information to Extract
|
|
15
|
+
The user explicitly requested to extract the following information:
|
|
16
|
+
> "{{ output_description }}"
|
|
17
|
+
{% endif %}
|
|
18
|
+
|
|
19
|
+
{% if target_schema %}
|
|
20
|
+
## Target Output Fields
|
|
21
|
+
Your final answer will afterwards be formatted into the JSON schema below. Gather evidence for every field, and make sure your final answer states each value you found (or that it could not be verified). You do not need to write JSON yourself.
|
|
22
|
+
```json
|
|
23
|
+
{{ target_schema }}
|
|
24
|
+
```
|
|
25
|
+
{% endif %}
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Instruction
|
|
30
|
+
|
|
31
|
+
1. **Assess Confidence & Verify**: Based on the execution history, final state, and the target information to extract, determine if you can confidently formulate the final answer. If the current evidence is insufficient or you require more granular proof, you **MUST** actively gather information and details using your tools first. If your tools fail (e.g., return errors or time out), or if the steps were skipped/not observed, you **MUST** report this lack of evidence honestly. Do not assume, guess, or fabricate any unobserved steps.
|
|
32
|
+
2. **Write Persistent Report**: Once your investigation is complete, you **MUST** synthesize your findings into a Markdown summary report (detailing Status [SUCCESS/FAILED/PARTIAL], Evidence, and a clear explanation). You must write this report to persistent memory under the key "output" by calling:
|
|
33
|
+
`save_note(key="output", content="...your markdown report...")`
|
|
34
|
+
3. **Final Response**: Only after saving the report, write a direct, helpful, and jargon-free final summary response to the user. Do not use any XML tags, code block wrappers, or special delimiters—just state the conclusion clearly and naturally, ensuring any extracted target information is clearly presented.
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,552 @@
|
|
|
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 functools import lru_cache
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from jinja2 import Template
|
|
22
|
+
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, ToolMessage
|
|
23
|
+
from langchain_core.tools import BaseTool
|
|
24
|
+
from artemis.core.tool_failure import ToolFailure, is_tool_failure
|
|
25
|
+
from artemis.config import OutputConfig
|
|
26
|
+
from artemis.context import ArtemisContext
|
|
27
|
+
from artemis.data_engine.trace import trace
|
|
28
|
+
from artemis.graph.state import State
|
|
29
|
+
from artemis.llm.structured import ParseFailure, content_to_text, parse_structured
|
|
30
|
+
from artemis.services.llm import acomplete, get_llm, invoke_llm_with_timeout_message, with_fallback
|
|
31
|
+
from artemis.tools.scratchpad import (
|
|
32
|
+
get_append_note_tool_pure,
|
|
33
|
+
get_list_notes_tool_pure,
|
|
34
|
+
get_read_note_tool_pure,
|
|
35
|
+
get_save_note_tool_pure,
|
|
36
|
+
get_update_note_tool_pure,
|
|
37
|
+
)
|
|
38
|
+
from artemis.tools.video_tool import get_video_analyzer_tool_pure
|
|
39
|
+
from artemis.tools.history import get_history_tools
|
|
40
|
+
from artemis.tools.tool_wrapper import (
|
|
41
|
+
invoke_tool_with_injection,
|
|
42
|
+
tool_result_messages,
|
|
43
|
+
)
|
|
44
|
+
from artemis.memory.context_policy import build_history_for
|
|
45
|
+
from artemis.utils.notes import read_note_content
|
|
46
|
+
from artemis.utils.task_tree import get_active_subgoal_hashes
|
|
47
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
48
|
+
from pydantic import BaseModel
|
|
49
|
+
|
|
50
|
+
logger = get_logger(__name__)
|
|
51
|
+
|
|
52
|
+
MAX_TURNS = 20
|
|
53
|
+
|
|
54
|
+
# Note key of the report the prompt asks for. The UI Task Report card and the
|
|
55
|
+
# MCP docs read it from notes/output.md.
|
|
56
|
+
OUTPUT_NOTE_KEY = "output"
|
|
57
|
+
|
|
58
|
+
# Note writes run one by one in the order issued; all other tools only read
|
|
59
|
+
# and can run in parallel.
|
|
60
|
+
_WRITE_TOOLS = frozenset({"save_note", "update_note", "append_note"})
|
|
61
|
+
|
|
62
|
+
_MAX_TURNS_ERROR = "Error: Outputter failed to resolve the query within maximum turns."
|
|
63
|
+
|
|
64
|
+
_SYSTEM_MESSAGE = (
|
|
65
|
+
"You are the Output Synthesis Agent for an Android UI automation"
|
|
66
|
+
" system. Your sole objective is to verify whether the user's initial"
|
|
67
|
+
" goal was achieved and synthesize the final report.\n\n## Core"
|
|
68
|
+
" Principles\n1. STRICTLY EVIDENCE-BASED & NO HALLUCINATION: Rely ONLY"
|
|
69
|
+
" on the actual observed execution history and successful tool outputs."
|
|
70
|
+
" Do not assume, reconstruct, or hallucinate any actions, elements, or"
|
|
71
|
+
" steps that did not actually occur during this specific run. If a step"
|
|
72
|
+
" was skipped or not observed (e.g., because the device was already in"
|
|
73
|
+
" the target state), you must explicitly state that it was 'Not"
|
|
74
|
+
" Observed / Already in State' rather than fabricating the interaction."
|
|
75
|
+
" If a tool call (like `video_analyzer`) fails or returns an error, you"
|
|
76
|
+
" must report the failure and the resulting lack of evidence, rather"
|
|
77
|
+
" than assuming success or guessing the details. If the user requests a"
|
|
78
|
+
" step-by-step guide, you must only include steps that were actually"
|
|
79
|
+
" executed and observed; do not invent 'standard' steps to fill in"
|
|
80
|
+
" gaps.\n2. NO JARGON IN FINAL ANSWER: The 'Final Answer' must be"
|
|
81
|
+
" written in clear, user-friendly language, completely free of"
|
|
82
|
+
" agent-specific jargon (e.g., do not mention 'nodes', 'XPath',"
|
|
83
|
+
" 'selectors', 'ReAct', or 'tools').\n\n## Tool Usage Guidelines\n-"
|
|
84
|
+
" LAZY VERIFICATION: If the execution history and the final screenshot"
|
|
85
|
+
" already in your context provide undeniable proof of the outcome, do"
|
|
86
|
+
" not call tools. Directly output your conclusion.\n- ACTIVE RETRIEVAL:"
|
|
87
|
+
" You should only call tools (e.g., `search_history` to locate the"
|
|
88
|
+
" step, `replay_steps` for its full record, `get_step_screenshot` for"
|
|
89
|
+
" its image) if you need to extract specific hidden data (like"
|
|
90
|
+
" verification codes, tracking numbers) or if the final state is"
|
|
91
|
+
" ambiguous.\n- BATCH INDEPENDENT LOOKUPS: When you need several"
|
|
92
|
+
" lookups that do not depend on each other, request them together in"
|
|
93
|
+
" the same turn instead of one per turn.\n- VIDEO ANALYSIS COST:"
|
|
94
|
+
" The `video_analyzer` tool is expensive. Use it only when necessary"
|
|
95
|
+
" (e.g., to verify video playback or motion).\n- PERSISTENT WRITE: If"
|
|
96
|
+
" you extract important information requested by the user (such as a"
|
|
97
|
+
" verification code, tracking number, or a text summary), you should"
|
|
98
|
+
" use `save_note` or `update_note` to write it directly into persistent"
|
|
99
|
+
" notes so it can be retrieved by other agents or the system."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
_FINAL_TURN_NOTICE = (
|
|
103
|
+
"This is your final turn and tools are no longer available. Write your"
|
|
104
|
+
" final answer to the user now, using only the evidence gathered so far."
|
|
105
|
+
" State plainly anything you could not verify."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
_EMPTY_REPLY_NUDGE = (
|
|
109
|
+
"Your previous reply was empty. If you still need evidence, call the"
|
|
110
|
+
" relevant tools; otherwise write your final answer to the user now."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
_FORMAT_SYSTEM_MESSAGE = (
|
|
114
|
+
"You are a helpful assistant. Your task is to take the"
|
|
115
|
+
" raw execution summary and format it into the"
|
|
116
|
+
" requested structured output schema. Do not invent any"
|
|
117
|
+
" information. If the raw summary does not contain the"
|
|
118
|
+
" required info, leave those fields empty or null."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@lru_cache(maxsize=1)
|
|
123
|
+
def _prompt_template() -> Template:
|
|
124
|
+
return Template(Path(__file__).with_name("outputter.md").read_text(encoding="utf-8"))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _notes_base_dir(ctx: ArtemisContext) -> str | Path | None:
|
|
128
|
+
base_dir = getattr(ctx.data_engine, "base_dir", None) if ctx.data_engine else None
|
|
129
|
+
return base_dir if isinstance(base_dir, (str, Path)) else None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _resolve_plan_and_history(ctx: ArtemisContext) -> str | None:
|
|
133
|
+
"""Build the plan and history text passed to the outputter prompt."""
|
|
134
|
+
try:
|
|
135
|
+
history = ctx.data_engine.get_agent_friendly_steps()
|
|
136
|
+
|
|
137
|
+
# Read current plan if it exists and base_dir is valid
|
|
138
|
+
current_plan = ""
|
|
139
|
+
base_dir = _notes_base_dir(ctx)
|
|
140
|
+
if base_dir:
|
|
141
|
+
current_path = Path(base_dir) / "notes" / "task_plan.md"
|
|
142
|
+
if current_path.exists():
|
|
143
|
+
try:
|
|
144
|
+
current_plan = current_path.read_text(encoding="utf-8")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"Failed to read current plan for outputter: {e}")
|
|
147
|
+
|
|
148
|
+
if not history:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
active_subgoal_hash = "default"
|
|
152
|
+
if current_plan:
|
|
153
|
+
try:
|
|
154
|
+
active_subgoal_hash, _ = get_active_subgoal_hashes(current_plan)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Failed to parse active subgoal in outputter: {e}")
|
|
157
|
+
|
|
158
|
+
return build_history_for(
|
|
159
|
+
"outputter",
|
|
160
|
+
current_plan,
|
|
161
|
+
history,
|
|
162
|
+
active_subgoal_hash,
|
|
163
|
+
engine=ctx.data_engine,
|
|
164
|
+
)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"Failed to resolve plan and history in outputter: {e}")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _verification_block(ctx: ArtemisContext, graph_output: State) -> str | None:
|
|
171
|
+
"""Format the verdict ledger and test summary, or None if the run had no check items."""
|
|
172
|
+
try:
|
|
173
|
+
from artemis.graph.checkpoints import read_ledger, read_run_outcome
|
|
174
|
+
|
|
175
|
+
ledger_records = read_ledger(ctx.data_engine.base_dir)
|
|
176
|
+
run_outcome = read_run_outcome(ctx.data_engine.base_dir) or getattr(
|
|
177
|
+
graph_output, "run_outcome", None
|
|
178
|
+
)
|
|
179
|
+
if not (ledger_records or run_outcome):
|
|
180
|
+
return None
|
|
181
|
+
lines = ["--- Verification Verdict Ledger (append-only) ---"]
|
|
182
|
+
for r in ledger_records:
|
|
183
|
+
lines.append(
|
|
184
|
+
f"- attempt {r.get('attempt_id')} [{r.get('kind')}]"
|
|
185
|
+
f" '{r.get('item_text')}' -> {r.get('status')}:"
|
|
186
|
+
f" {r.get('evidence', '')}"
|
|
187
|
+
)
|
|
188
|
+
if run_outcome:
|
|
189
|
+
tests = run_outcome.get("tests") or {}
|
|
190
|
+
lines.append(
|
|
191
|
+
"--- Test Summary ---\n"
|
|
192
|
+
f"task_status={run_outcome.get('task_status')},"
|
|
193
|
+
f" passed={tests.get('passed', 0)},"
|
|
194
|
+
f" failed={tests.get('failed', 0)},"
|
|
195
|
+
f" inconclusive={tests.get('inconclusive', 0)},"
|
|
196
|
+
f" unchecked={tests.get('unchecked', 0)}"
|
|
197
|
+
)
|
|
198
|
+
lines.append(
|
|
199
|
+
"Report every declared check item with its final state"
|
|
200
|
+
" (passed / failed / inconclusive / unchecked) and include"
|
|
201
|
+
" the complete verdict sequence above. A failed assertion"
|
|
202
|
+
" is a test result to report verbatim, not something to"
|
|
203
|
+
" explain away. Items reported unchecked were never"
|
|
204
|
+
" judged — never present them as passed."
|
|
205
|
+
)
|
|
206
|
+
return "\n".join(lines)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Failed to attach verdict ledger to outputter: {e}")
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _environment_block(ctx: ArtemisContext) -> str | None:
|
|
213
|
+
"""Describe which UI hierarchy backend served the run.
|
|
214
|
+
|
|
215
|
+
Mentions a mid-run switch too: falling back to UIAutomator2 explains slower
|
|
216
|
+
steps and conflicts with other automation tools.
|
|
217
|
+
"""
|
|
218
|
+
from artemis.clients.screen_client_factory import hierarchy_backend_sentence
|
|
219
|
+
|
|
220
|
+
relative = ctx.data_engine.get_relative_time if ctx.data_engine else None
|
|
221
|
+
environment_line = hierarchy_backend_sentence(
|
|
222
|
+
getattr(ctx, "ui_adb_client", None), relative_time=relative
|
|
223
|
+
)
|
|
224
|
+
if not environment_line:
|
|
225
|
+
return None
|
|
226
|
+
return (
|
|
227
|
+
"## Run Environment (facts, include verbatim under a short "
|
|
228
|
+
"'Environment' note only if the source changed mid-run or the "
|
|
229
|
+
"user asked about it)" + chr(10) + environment_line
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _target_schema_json(output_config: OutputConfig) -> str | None:
|
|
234
|
+
"""Return the structured output schema as JSON text for the prompt."""
|
|
235
|
+
schema = output_config.structured_output_schema()
|
|
236
|
+
if schema is None:
|
|
237
|
+
return None
|
|
238
|
+
try:
|
|
239
|
+
if isinstance(schema, type) and issubclass(schema, BaseModel):
|
|
240
|
+
schema = schema.model_json_schema()
|
|
241
|
+
return json.dumps(schema, indent=2, ensure_ascii=False)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.warning(f"Could not render the structured output schema for outputter: {e}")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _initial_messages(
|
|
248
|
+
ctx: ArtemisContext,
|
|
249
|
+
output_config: OutputConfig,
|
|
250
|
+
graph_output: State,
|
|
251
|
+
plan_and_history: str | None,
|
|
252
|
+
) -> list[BaseMessage]:
|
|
253
|
+
human_message = _prompt_template().render(
|
|
254
|
+
initial_goal=graph_output.initial_goal,
|
|
255
|
+
structured_output=output_config.structured_output,
|
|
256
|
+
output_description=output_config.output_description,
|
|
257
|
+
target_schema=_target_schema_json(output_config),
|
|
258
|
+
plan_and_history=plan_and_history,
|
|
259
|
+
)
|
|
260
|
+
content: list[dict[str, Any]] = [{"type": "text", "text": human_message}]
|
|
261
|
+
|
|
262
|
+
if ctx.data_engine:
|
|
263
|
+
verification = _verification_block(ctx, graph_output)
|
|
264
|
+
if verification:
|
|
265
|
+
content.append({"type": "text", "text": verification})
|
|
266
|
+
|
|
267
|
+
environment = _environment_block(ctx)
|
|
268
|
+
if environment:
|
|
269
|
+
content.append({"type": "text", "text": environment})
|
|
270
|
+
|
|
271
|
+
raw_data = graph_output.operator_raw_data
|
|
272
|
+
screenshot_b64 = raw_data.get("screenshot_b64") if raw_data else None
|
|
273
|
+
if screenshot_b64:
|
|
274
|
+
content.append(
|
|
275
|
+
{
|
|
276
|
+
"type": "image_url",
|
|
277
|
+
"image_url": {"url": f"data:image/jpeg;base64,{screenshot_b64}"},
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return [SystemMessage(content=_SYSTEM_MESSAGE), HumanMessage(content=content)]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _outputter_tools(ctx: ArtemisContext) -> list[BaseTool]:
|
|
285
|
+
"""Return the history, notes and video tools available to the outputter."""
|
|
286
|
+
search_tool, replay_tool, screenshot_tool = get_history_tools(ctx)
|
|
287
|
+
return [
|
|
288
|
+
search_tool,
|
|
289
|
+
replay_tool,
|
|
290
|
+
screenshot_tool,
|
|
291
|
+
get_list_notes_tool_pure(ctx),
|
|
292
|
+
get_read_note_tool_pure(ctx),
|
|
293
|
+
get_save_note_tool_pure(ctx),
|
|
294
|
+
get_update_note_tool_pure(ctx),
|
|
295
|
+
get_append_note_tool_pure(ctx),
|
|
296
|
+
get_video_analyzer_tool_pure(ctx),
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _tool_name(tool_call: dict) -> str:
|
|
301
|
+
name = tool_call["name"]
|
|
302
|
+
return name.split(":")[-1] if ":" in name else name
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class _Investigation:
|
|
306
|
+
"""Runs the outputter's tool calls and tracks whether the report was saved."""
|
|
307
|
+
|
|
308
|
+
def __init__(self, tool_map: dict[str, BaseTool], graph_output: State, llm: Any):
|
|
309
|
+
self.tool_map = tool_map
|
|
310
|
+
self.graph_output = graph_output
|
|
311
|
+
self.llm = llm
|
|
312
|
+
self.report_written = False
|
|
313
|
+
|
|
314
|
+
async def _run_tool(self, tc: dict) -> list[BaseMessage]:
|
|
315
|
+
tool_name = _tool_name(tc)
|
|
316
|
+
args = tc["args"]
|
|
317
|
+
logger.info(f"Outputter executing tool {tool_name} with args: {args}")
|
|
318
|
+
try:
|
|
319
|
+
selected_tool = self.tool_map.get(tool_name)
|
|
320
|
+
if selected_tool:
|
|
321
|
+
result = await invoke_tool_with_injection(
|
|
322
|
+
tool=selected_tool,
|
|
323
|
+
args=args,
|
|
324
|
+
tool_call_id=tc["id"],
|
|
325
|
+
state=self.graph_output,
|
|
326
|
+
record_trace=True,
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
result = ToolFailure(f"Error: Tool {tool_name} is not supported.")
|
|
330
|
+
status = "error" if is_tool_failure(result) else "success"
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error(f"Error running tool {tool_name}: {e}")
|
|
333
|
+
result = f"Error running tool {tool_name}: {e}"
|
|
334
|
+
status = "error"
|
|
335
|
+
|
|
336
|
+
if (
|
|
337
|
+
tool_name in _WRITE_TOOLS
|
|
338
|
+
and status == "success"
|
|
339
|
+
and isinstance(args, dict)
|
|
340
|
+
and args.get("key") == OUTPUT_NOTE_KEY
|
|
341
|
+
):
|
|
342
|
+
self.report_written = True
|
|
343
|
+
|
|
344
|
+
# Screenshots are wrapped in whatever message type the provider accepts
|
|
345
|
+
# for images; text results stay a plain ToolMessage.
|
|
346
|
+
return tool_result_messages(tc["id"], result, name=tool_name, status=status, llm=self.llm)
|
|
347
|
+
|
|
348
|
+
async def execute_tool_calls(self, tool_calls: list[dict]) -> list[BaseMessage]:
|
|
349
|
+
"""Run the tool calls of one turn and return the reply messages.
|
|
350
|
+
|
|
351
|
+
Consecutive reads run in parallel; each note write runs on its own, in
|
|
352
|
+
order. All ToolMessages come first, in call order, followed by any
|
|
353
|
+
HumanMessages carrying images: some providers reject a tool call whose
|
|
354
|
+
results are not directly after it.
|
|
355
|
+
"""
|
|
356
|
+
per_call: list[list[BaseMessage]] = []
|
|
357
|
+
batch: list[dict] = []
|
|
358
|
+
|
|
359
|
+
async def flush() -> None:
|
|
360
|
+
if batch:
|
|
361
|
+
per_call.extend(await asyncio.gather(*(self._run_tool(tc) for tc in batch)))
|
|
362
|
+
batch.clear()
|
|
363
|
+
|
|
364
|
+
for tc in tool_calls:
|
|
365
|
+
if _tool_name(tc) in _WRITE_TOOLS:
|
|
366
|
+
await flush()
|
|
367
|
+
per_call.append(await self._run_tool(tc))
|
|
368
|
+
else:
|
|
369
|
+
batch.append(tc)
|
|
370
|
+
await flush()
|
|
371
|
+
|
|
372
|
+
produced = [m for msgs in per_call for m in msgs]
|
|
373
|
+
tool_messages = [m for m in produced if isinstance(m, ToolMessage)]
|
|
374
|
+
carriers = [m for m in produced if not isinstance(m, ToolMessage)]
|
|
375
|
+
return tool_messages + carriers
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _read_saved_report(ctx: ArtemisContext) -> str | None:
|
|
379
|
+
base_dir = _notes_base_dir(ctx)
|
|
380
|
+
if not base_dir:
|
|
381
|
+
return None
|
|
382
|
+
try:
|
|
383
|
+
report = read_note_content(base_dir, OUTPUT_NOTE_KEY).strip()
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.warning(f"Could not read the saved outputter report: {e}")
|
|
386
|
+
return None
|
|
387
|
+
return report or None
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
async def _run_react_loop(
|
|
391
|
+
investigation: _Investigation,
|
|
392
|
+
messages: list[BaseMessage],
|
|
393
|
+
llm_with_tools: Any,
|
|
394
|
+
llm_fallback_with_tools: Any,
|
|
395
|
+
) -> str | None:
|
|
396
|
+
"""Call the model and its tools until it answers; return the answer or None."""
|
|
397
|
+
nudged = False
|
|
398
|
+
for turn in range(MAX_TURNS):
|
|
399
|
+
logger.info(f"Outputter ReAct turn {turn + 1}")
|
|
400
|
+
final_turn = turn == MAX_TURNS - 1
|
|
401
|
+
if final_turn:
|
|
402
|
+
messages.append(HumanMessage(content=_FINAL_TURN_NOTICE))
|
|
403
|
+
|
|
404
|
+
response = await with_fallback(
|
|
405
|
+
main_call=lambda: invoke_llm_with_timeout_message(acomplete(llm_with_tools, messages)),
|
|
406
|
+
fallback_call=lambda: invoke_llm_with_timeout_message(
|
|
407
|
+
acomplete(llm_fallback_with_tools, messages)
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
text = content_to_text(response.content).strip()
|
|
411
|
+
|
|
412
|
+
if response.tool_calls and not final_turn:
|
|
413
|
+
messages.append(response)
|
|
414
|
+
messages.extend(await investigation.execute_tool_calls(response.tool_calls))
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
if text:
|
|
418
|
+
messages.append(response)
|
|
419
|
+
return text
|
|
420
|
+
|
|
421
|
+
if final_turn:
|
|
422
|
+
logger.warning("Outputter final turn produced no answer text.")
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
if nudged:
|
|
426
|
+
logger.warning("Outputter reply was empty again after a nudge.")
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
# An empty model turn is not kept: Gemini rejects messages without parts.
|
|
430
|
+
logger.warning("Outputter reply was empty; asking once more for an answer.")
|
|
431
|
+
messages.append(HumanMessage(content=_EMPTY_REPLY_NUDGE))
|
|
432
|
+
nudged = True
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
async def _format_structured(
|
|
437
|
+
output_config: OutputConfig,
|
|
438
|
+
graph_output: State,
|
|
439
|
+
raw_answer: str,
|
|
440
|
+
saved_report: str | None,
|
|
441
|
+
llm: Any,
|
|
442
|
+
llm_fallback: Any,
|
|
443
|
+
) -> Any:
|
|
444
|
+
"""Convert the answer to the configured schema with a second model call.
|
|
445
|
+
|
|
446
|
+
Returns None if no schema is configured. If the call fails, falls back to
|
|
447
|
+
parsing the answer locally, and then to the answer text. Never raises.
|
|
448
|
+
"""
|
|
449
|
+
bound = output_config.bind_structured_output(llm, llm_fallback)
|
|
450
|
+
if bound is None:
|
|
451
|
+
return None
|
|
452
|
+
structured_llm, structured_llm_fallback = bound
|
|
453
|
+
logger.info("Formatting raw output to structured format...")
|
|
454
|
+
|
|
455
|
+
format_messages: list[BaseMessage] = [SystemMessage(content=_FORMAT_SYSTEM_MESSAGE)]
|
|
456
|
+
if saved_report:
|
|
457
|
+
format_messages.append(
|
|
458
|
+
HumanMessage(content=f"Supporting Evidence (saved report):\n{saved_report}")
|
|
459
|
+
)
|
|
460
|
+
format_messages.append(
|
|
461
|
+
HumanMessage(
|
|
462
|
+
content=(f"Initial Goal: {graph_output.initial_goal}\nRaw Summary:\n{raw_answer}")
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
try:
|
|
467
|
+
response = await with_fallback(
|
|
468
|
+
main_call=lambda: invoke_llm_with_timeout_message(
|
|
469
|
+
structured_llm.ainvoke(format_messages)
|
|
470
|
+
),
|
|
471
|
+
fallback_call=lambda: invoke_llm_with_timeout_message(
|
|
472
|
+
structured_llm_fallback.ainvoke(format_messages)
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
if response is None:
|
|
476
|
+
raise ValueError("the model returned no structured output")
|
|
477
|
+
except Exception as e:
|
|
478
|
+
logger.warning(f"Structured formatting failed ({e}); parsing the answer locally.")
|
|
479
|
+
schema = output_config.structured_output_schema()
|
|
480
|
+
parsed = parse_structured(
|
|
481
|
+
raw_answer,
|
|
482
|
+
schema=schema if isinstance(schema, type) and issubclass(schema, BaseModel) else None,
|
|
483
|
+
)
|
|
484
|
+
if isinstance(parsed, ParseFailure):
|
|
485
|
+
logger.warning("Local parse failed as well; returning the answer as text.")
|
|
486
|
+
return raw_answer
|
|
487
|
+
response = parsed
|
|
488
|
+
|
|
489
|
+
if isinstance(response, BaseModel):
|
|
490
|
+
return response.model_dump()
|
|
491
|
+
return response
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _coerce_text_answer(raw_answer: str) -> Any:
|
|
495
|
+
"""Parse the answer if it looks like JSON; otherwise return it unchanged."""
|
|
496
|
+
stripped = raw_answer.strip()
|
|
497
|
+
if stripped.startswith(("{", "[", "```")):
|
|
498
|
+
parsed = parse_structured(stripped)
|
|
499
|
+
if not isinstance(parsed, ParseFailure):
|
|
500
|
+
return parsed
|
|
501
|
+
# The raw text is a legitimate final answer; the miss is only
|
|
502
|
+
# noteworthy because the answer *looked* like JSON.
|
|
503
|
+
logger.warning(
|
|
504
|
+
"Outputter answer looked like JSON but could not be parsed"
|
|
505
|
+
f" ({parsed.error}); returning it as plain text."
|
|
506
|
+
)
|
|
507
|
+
return raw_answer
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@trace(type="agent", name="outputter")
|
|
511
|
+
async def outputter(
|
|
512
|
+
ctx: ArtemisContext,
|
|
513
|
+
output_config: OutputConfig,
|
|
514
|
+
graph_output: State,
|
|
515
|
+
plan_and_history: str | None = None,
|
|
516
|
+
) -> dict:
|
|
517
|
+
logger.info("Starting Outputter Agent")
|
|
518
|
+
|
|
519
|
+
if plan_and_history is None and ctx.data_engine:
|
|
520
|
+
plan_and_history = _resolve_plan_and_history(ctx)
|
|
521
|
+
|
|
522
|
+
messages = _initial_messages(ctx, output_config, graph_output, plan_and_history)
|
|
523
|
+
|
|
524
|
+
tools = _outputter_tools(ctx)
|
|
525
|
+
llm = get_llm(ctx=ctx, name="outputter", is_utils=True)
|
|
526
|
+
llm_fallback = get_llm(ctx=ctx, name="outputter", is_utils=True, use_fallback=True)
|
|
527
|
+
investigation = _Investigation({t.name: t for t in tools}, graph_output, llm)
|
|
528
|
+
|
|
529
|
+
raw_answer = await _run_react_loop(
|
|
530
|
+
investigation,
|
|
531
|
+
messages,
|
|
532
|
+
llm.bind_tools(tools=tools),
|
|
533
|
+
llm_fallback.bind_tools(tools=tools),
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
saved_report = _read_saved_report(ctx) if investigation.report_written else None
|
|
537
|
+
if raw_answer is None:
|
|
538
|
+
if saved_report:
|
|
539
|
+
logger.warning("Outputter produced no answer; using its saved report instead.")
|
|
540
|
+
raw_answer = saved_report
|
|
541
|
+
else:
|
|
542
|
+
raw_answer = _MAX_TURNS_ERROR
|
|
543
|
+
logger.error(raw_answer)
|
|
544
|
+
|
|
545
|
+
if output_config.structured_output:
|
|
546
|
+
formatted = await _format_structured(
|
|
547
|
+
output_config, graph_output, raw_answer, saved_report, llm, llm_fallback
|
|
548
|
+
)
|
|
549
|
+
if formatted is not None:
|
|
550
|
+
return formatted
|
|
551
|
+
|
|
552
|
+
return _coerce_text_answer(raw_answer)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"blocks": {
|
|
3
|
+
"role": "## Role\nYou are a Senior Test Engineer leading a team of mobile execution agents. Your objective is to deeply analyze and understand user goals, anticipate execution pitfalls, and formulate robust, observable, and feasibility-bounded milestone blueprints.",
|
|
4
|
+
"thinking_process": "## Systematic Thinking Process & Operating Principles\nBefore calling any tools or formulating your final milestones, you MUST systematically analyze the task following these 5 sequential thinking steps in your reasoning:\n\n### 1. Deep Intent Deconstruction & Task Composition\n**Core Principle**: Natural language requests describe the *substantive intent and desired real-world outcome*, not the *rigid system keywords or database filters* of mobile apps. Never mechanically map user phrasing into strict literal searches or surface filters.\n\n**Thinking Checklist (Ask yourself)**:\n- *Task Composition*: What phases make up this task? Is it a sequence of one-off linear steps (e.g., open app, configure settings), an iterative loop over multiple items (e.g., inspecting a list of search results), or a periodic polling loop (e.g., refreshing to monitor new messages, waiting for battery level)?\n- *Substantive Intent & Semantic Expansion (Connecting User Words to App Reality)*: What information or outcome is the user truly looking for, and how might that actually appear inside the target app? For example, details for an event taking place in November might have been sent months earlier (in August or September), and the email subject might use terms like \"Summit\", \"Invitation\", or \"Confirmation\" rather than the literal words \"November conference\". Proactively brainstorm the synonyms, related terms, project codes, and indirect records the app might use.\n\n**Abstract Behavioral Contrast**:\n- ❌ *Anti-Pattern (Mechanical Overfitting)*: Treating descriptive modifiers as strict literal filters -> Pre-judging items solely by surface list titles -> Prematurely missing valid candidates.\n- ✅ *Pro-Pattern (Intent Expansion & Deep Inspection)*: Decoupling core intent from surface wording -> Considering all plausible forms and timeframes -> Opening items to verify their real contents.\n\n### 2. Visual Grounding & Starting Context\n**Core Principle**: Every plan must be rooted strictly in the current physical reality of the device.\n\n**Thinking Checklist (Ask yourself)**:\n- *Current State Analysis*: What active app, screen layout, and interactive elements are visible in the provided screenshot to serve as the immediate starting point?\n\n### 3. Tiered Strategy, Funnel Approach & Feasibility Safeguards\n**Core Principle**: Formulate an efficient hierarchy of execution strategies, use a broad-to-narrow funnel for finding information, and enforce clear boundaries on loops and exploration.\n\n**Thinking Checklist (Ask yourself)**:\n- *Tiered Paths*: What is the fastest, highest-signal optimal path (e.g., targeted in-app search, direct shortcuts)? If that fails or yields no results, what is the pragmatic fallback path (e.g., browsing categories, chronological scanning)?\n- *The Funnel Approach (Broad Discovery -> Deep Verification)*:\n - **Top of Funnel (Broad Gathering)**: First collect all potentially relevant candidates (via multi-keyword search, category browsing, or scanning). Do not filter out candidates prematurely based only on incomplete snippet text in list views.\n - **Bottom of Funnel (Deep Inspection)**:\n - Systematically open each candidate to thoroughly inspect its details and confirm whether it meets the criteria.\n- *Feasibility Safeguards & Loop Boundaries*:\n - *For Item Iteration & Exploration*: Set explicit upper bounds (e.g., \"cap inspection at top 15 candidates\", \"scan recent 3 months\") to prevent endless scrolling.\n - *For Periodic Monitoring / Polling*: Specify the check action (e.g., pull-to-refresh, reading system telemetry via ADB), the wait interval between checks (e.g., check every 5 minutes to conserve resources), and the exit criteria (e.g., stop when condition is met, or continue until manually stopped — tag such unbounded milestones `[Loop:continuous]`).\n\n### 4. Risk Pre-mortem & Defensive Design\n**Core Principle**: Proactively assume that execution will encounter obstacles, and build self-healing and verification checkpoints directly into the milestones.\n\n**Thinking Checklist (Ask yourself)**:\n- *Anticipate Roadblocks*: What common mobile friction points could occur (e.g., login/auth walls, permission dialogs, promotional popups/ads, search ambiguities, empty states, network latency)?\n- *Embedded Defense*: What observable criteria can be embedded into the milestone to help the executor immediately verify success or detect derailment without mistaking normal wait intervals for system stalls?\n\n### 5. Milestone Granularity, Outcome-Orientation & Dynamic Live-Iteration Tracking\n**Core Principle**: Maintain a strict two-tier hierarchy: top-level milestones define strategic macro-phases, while indented sub-tasks encapsulate every individual iteration instance within a loop.\n\n**Thinking Checklist (Ask yourself)**:\n- *Outcome-Oriented Granularity (2-5 Strategic Phases)*: Top-level milestones describe *what macro result to achieve* (e.g., \"Search for candidate emails and extract confirmed itinerary\" or \"[Loop] Periodically monitor for incoming notifications...\") rather than micromanaging atomic UI actions.\n- *Phase vs. Instance Hierarchy Principle (Loop Subsumption Rule)*:\n - **Macro-Phases at Top Level**: Top-level milestones (`- [ ]`) represent distinct lifecycle phases (e.g., Setup/Prerequisites -> Iterative/Polling Loop -> Final Consolidation).\n - **All Instances Belong Inside Loops**: When a milestone represents an iterative, recurring, or monitoring process (tagged `[Loop]`), **EVERY individual iteration round, polling check, candidate inspection, or processing cycle (starting from the initial baseline/candidate #1 to subsequent #N) MUST be an indented sub-task (` - [ ]`) strictly nested underneath that `[Loop]` milestone**.\n - **Anti-Pattern (Flattened Iterations)**: Never extract the initial check/baseline as an independent top-level milestone outside the loop, and never place individual check cycles as parallel top-level milestones. The top-level `[Loop]` encapsulates the entire recurring lifecycle.\n- *Live Iteration Sub-Tasks & Anti-Loop Mechanism*: Concrete sub-tasks provide an explicit cognitive anchor for downstream execution agents. By initializing the plan with a concrete first-instance sub-task (e.g., ` - [ ] Inspect Candidate #1: ...` or ` - [ ] Polling Check #1 (Baseline & Initial Sync): ...`), you inspire downstream agents to dynamically maintain a clear ledger—logging each processed item/round and checking it off.\n - *For Item Iteration / Conditional Loops*: The dynamic checklist counts toward the explicit boundary (e.g., Candidates #1 to #N until all required info is found or max items reached).\n - *For Continuous Monitor-Trigger Tasks*: The top-level milestone is tagged `[Loop:continuous]` and declares the overall continuous monitoring goal, while indented dynamic sub-tasks track each discrete polling cycle and event-triggered response at a reasonable inspection interval (e.g., check every 5 minutes).\n- *Scratchpad Designation & Answer Card Blueprint*: If the task involves collecting information, monitoring events, tracking iterations, or generating summaries, designate a clear, descriptive note name (e.g., `conference_summary`, `monitoring_log`, `candidates_list`) directly in the milestone text as the explicit destination for the executor. Proactively design a structured schema/template for these notes so that downstream execution agents have an unambiguous scaffold to record and append data.",
|
|
5
|
+
"capabilities_operator": "## Execution Capabilities\n\nThe execution agent primarily interacts with the mobile device and analyzes screen recordings and logs. It can perform the following types of actions:\n- **UI Interaction**: Tapping, swiping, and entering text on the screen.\n- **App Management**: Launching and stopping specific applications.\n- **Information Retrieval & Analysis**: Reading on-screen text and UI hierarchy, searching the web for external information, querying system logs, and analyzing screen recordings.\n- **Terminal & Background Operations**: Executing arbitrary ADB shell commands on the device for exploration. It is capable of running long-running ADB commands asynchronously in the background as tasks.\n\n### Capability Boundaries & Planning Rules\n- **Cross-App Operations**: When a task requires interacting with multiple applications, split the operations into distinct milestones for each app. Avoid mixing actions for different apps in a single milestone.",
|
|
6
|
+
"output_protocol_initial": "## Task Plan & Scratchpad Protocol\n\n1. **Articulate Thinking**: Begin by reasoning through the 5-step **Systematic Thinking Process** to establish your strategy, funnel approach, bounds, and pre-mortem checks.\n2. **Designate Notes in Milestones & Pre-generate Answer Cards**: If the task involves collecting findings, logging periodic monitoring events, recording candidates, or analytical summaries, designate clear, descriptive note names directly in the milestone descriptions (e.g., `candidates_list`, `monitoring_log`, `schedule_summary`). You can and should invoke the `save_note` tool to pre-generate these structured template files (answer card / scratchpad skeletons with Markdown headers, sections, or tables) before submitting `task_plan`. This provides downstream executors with a clear, ready-to-fill schema.\n3. **Submit Plan**: Submit your initial plan by invoking the `save_note` tool with `key='task_plan'`. Avoid writing the plan only in your text response, as it will only be recognized by the system if submitted via the tool.\n\n### Plan Formatting Rules\n- Format the plan strictly as a Markdown task list starting with `- [ ]` for uncompleted items.\n{{ plan_grammar }}\n- For linear macro-milestones: `- [ ] <Phase Description>`\n- For bounded repetitive, iterative, or periodic polling milestones, tag the item with `[Loop]` and declare its Exit Boundary and Interval (e.g., `- [ ] [Loop] <Overall Loop / Monitoring Goal> (Exit: <Condition>; Interval: <e.g., every 5 minutes>)`).\n- For unbounded continuous monitoring that only the user can stop, tag the item with `[Loop:continuous]` instead (e.g., `- [ ] [Loop:continuous] <Monitoring Goal> (Interval: <e.g., every 5 minutes>)`); never declare a self-serve exit condition for it.\n- **Strict Two-Tier Hierarchy & Loop Subtask Contract**:\n 1. **Subtask Nesting Rule**: ALL individual iteration instances, polling checks, candidate inspections, or baseline setups MUST be formatted as 2-space indented sub-tasks (` - [ ]`) directly nested under their parent `[Loop]` milestone.\n 2. **Mandatory First-Instance Initialization**: Every `[Loop]` milestone MUST be initialized with its first concrete child sub-task (e.g., ` - [ ] Inspect Candidate #1: ...` or ` - [ ] Polling Check #1 (Baseline & Initial Sync): ...`).\n 3. **No Flattened Cycles**: NEVER elevate individual checks, recurring polling cycles, or candidate inspections to top-level milestones outside the `[Loop]`. Top-level items are reserved exclusively for strategic macro-phases.\n- Ensure the content of the `task_plan` note contains only the task list, excluding introductions, explanations, conversational text, or headers.",
|
|
7
|
+
"examples_initial": "## Master Example: Demonstrating the Complete Cognitive Paradigm\n\n**Goal**: \"Find and summarize all schedule arrangements and venue details for the upcoming November tech conference from my emails.\"\n\n### Naive / Incorrect Approaches:\n1. *Flawed Semantics & Too Granular*:\n- [ ] Tap search icon in Gmail\n- [ ] Type \"November\"\n- [ ] Open first email and tap back\n*(Fails: Micromanages atomic UI taps; assumes emails for November arrangements must have \"November\" in the title or be received in November)*\n2. *Vague & Unbounded Trap*:\n- [ ] Open email and check all emails for conference details\n*(Fails: Unbounded scan trap; causes infinite scrolling and step budget exhaustion on mobile)*\n\n---\n\n### Correct Approach (Full Cognitive Thinking & Robust Plan):\n\n#### Step-by-Step Thinking:\n1. **Intent & Semantic Expansion**:\n - *Core Objective*: Extract confirmed schedule dates, sessions, and venue info for the November tech conference.\n - *Semantic Insight*: Conference communications are often sent months in advance (e.g., ticket confirmations or registration emails in August/September). The subject line may say \"Global Developer Summit\", \"Tech Expo Agenda\", or \"Registration Confirmed\" rather than literally \"November conference\".\n - *Task Composition*: Linear setup (open email app and search) -> Iterative candidate inspection loop (examine each promising email) -> Final consolidation.\n2. **Visual Grounding**:\n - The device screenshot shows the Home screen with the Gmail app icon visible. The plan must start by opening the email client.\n3. **Tiered Strategy, Funnel Approach & Safeguards**:\n - *Tier 1 (Optimal)*: Use in-app search with rich semantic keywords (\"conference\", \"summit\", \"schedule\", \"agenda\", \"venue\", \"November\").\n - *Tier 2 (Fallback)*: If search is unavailable or yields zero results, fallback to chronological scanning of recent 3 months.\n - *Funnel (Broad Gathering -> Deep Inspection)*: Gather candidate emails from search results; do not discard candidates based solely on list previews. Open each candidate thread to inspect full body details.\n - *Feasibility Boundary*: Cap the candidate email inspection pool at top 15 emails to prevent infinite scrolling.\n4. **Risk Pre-mortem**:\n - *Risks*: Promotional marketing spam; conflicting drafts with preliminary dates; cancelled sessions.\n - *Defensive Check*: Instruct downstream executor to filter out promotional newsletters, verify session timestamps, and confirm final venue address.\n5. **Milestone Granularity & Scratchpad Blueprint**:\n - Focus milestones on business outcomes (search, inspect candidates, consolidate), avoiding micromanaged click-by-click steps.\n - Use indented sub-tasks to guide the loop of inspecting each candidate email, initializing with Item #1 and adding subsequent items as they are identified. This prevents the agent from losing state or re-inspecting the same items.\n - Designate note `november_conference_summary` as the destination for recording findings and pre-generate the structured template note.\n\n#### Tool Invocation:\n1. Pre-generate designated answer card note template:\n`save_note(key='november_conference_summary', content='# November Tech Conference Summary\\n\\n## Key Details\\n- Conference Name: \\n- Dates: \\n- Venue / Address: \\n\\n## Daily Schedule & Sessions\\n(To be populated during email inspection)\\n')`\n\n2. Submit milestone plan:\n`save_note(key='task_plan', content='- [ ] Open email application and search for candidate emails regarding the November tech conference (Search targeted keywords like \"conference\", \"summit\", \"agenda\", \"venue\"; if search is unavailable, fallback to scanning recent 3 months, capped at top 15 emails)\\n- [ ] [Loop] Inspect candidate emails and record verified schedule sessions and venue details into note `november_conference_summary` (Exit: All key conference details identified OR candidate pool exhausted / max 15 inspected)\\n - [ ] Inspect Candidate #1: Open candidate, filter out non-relevant content, check body for scheduling/venue, and log findings if present\\n- [ ] Review and consolidate all extracted information in `november_conference_summary` into a clear, finalized conference itinerary')`\n\n#### Content of `task_plan` Note:\n- [ ] Open email application and search for candidate emails regarding the November tech conference (Search targeted keywords like \"conference\", \"summit\", \"agenda\", \"venue\"; if search is unavailable, fallback to scanning recent 3 months, capped at top 15 emails)\n- [ ] [Loop] Inspect candidate emails and record verified schedule sessions and venue details into note `november_conference_summary` (Exit: All key conference details identified OR candidate pool exhausted / max 15 inspected)\n - [ ] Inspect Candidate #1: Open candidate, filter out non-relevant content, check body for scheduling/venue, and log findings if present\n- [ ] Review and consolidate all extracted information in `november_conference_summary` into a clear, finalized conference itinerary",
|
|
8
|
+
"check_generation": "## Declaring Verification Standards (check lines)\nYou may attach check lines to the plan. {% if midway_checks %}An independent Checker judges each line in the background when its milestone completes, and a failed verify reopens that milestone for repair{% if final_check %}; a final review at task exit then audits the whole run against the user's goal{% endif %}.{% else %}A final review at task exit judges every line once, from the recorded evidence around each milestone's completion; there is no midway repair loop.{% endif %}\n\n- **User-declared expectations become `- assert:` lines.** Whenever the user's goal states an explicit expectation (\"should show X\", \"must not crash\", \"the toast Y should appear\"), declare it as an assert line preserving the user's wording verbatim. Expectations about the whole run or the final state use the `@end` suffix (task-level lines at the end of the plan).\n- **`- verify:` lines only for milestones that genuinely need acceptance checks** — completion points of irreversible operations, or milestones that directly contribute to the final goal. Do NOT attach a verify line to every subgoal: restraint is an explicit requirement (checking cost scales with the number of check lines).\n- **Do not fabricate assertions** for subgoals that have nothing verifiable. Rewrite transient expectations as persistently probeable state wherever possible (e.g., \"alarm exists in the alarm list\" instead of \"a toast flashed\") — evidence for transient moments only exists if the execution history happened to capture it.\n- **Ordering requirements are post-hoc.** If the user demands \"A must be confirmed before B runs\", still generate the assertion preserving their semantics, but annotate in the plan that ordering is verified retroactively from history — the system performs post-hoc audits and CANNOT block B from executing. Never silently promise enforcement that does not exist.",
|
|
9
|
+
"check_audit": "- **Check Standard Protection**: Check lines (`- verify:` / `- assert:`) declared in the plan are the task's verification standards. FLAG any proposed plan that deletes, weakens, or rewords existing check lines — softening a declared standard is never an acceptable adaptation. Adding new check lines is allowed.",
|
|
10
|
+
"role_validator": "## Role\nYou are the Task Plan Reviewer, a fast advisory second opinion. The Operator has modified the top-level task plan and the change is already applied. Your job is to spot a concrete concern — goal drift, a broken loop contract, a weakened check standard, a logical dead end — and, if there is one, state it with its reason so the Operator can decide. You never block or roll back anything.",
|
|
11
|
+
"validator_guidelines": "## Validation Principles\n- **Goal Alignment**: Ensure the new plan ultimately accomplishes the `Initial Goal` requested by the user. If the Operator has abandoned the core objective or fundamentally altered it, flag the plan.\n- **Logical Coherence**: Verify that the proposed milestones form a logical, achievable sequence to reach the goal. Support valid ongoing iteration/polling subgoals and trigger branches without misconstruing periodic checks as infinite loops. Flag the plan if it introduces contradictory steps, unhandled deadlocks, or obvious logical fallacies.\n{{ plan_grammar }}\n- **Loop & Monitoring Protection**: If the existing plan contains an active `[Loop]` or `[Loop:continuous]` milestone, you MUST FLAG any proposed plan that deletes the milestone or removes its loop tag, as well as any plan that marks a `[Loop:continuous]` continuous monitoring milestone as completed (`[x]`) without an explicit external user stop signal.\n- **Adaptive Acceptance**: The Operator operates in a dynamic environment and may encounter unexpected UI states or dynamic event triggers. You should approve alternative strategies, extra intermediate steps, loop status tracking, or necessary workarounds as long as they serve the primary goal effectively.\n- **Advisory Verdict & Constructive Feedback**: Your verdict is advisory — the change stays applied whether or not you approve it. Approve unless you have a concrete, evidence-backed concern; a vague unease is not a concern. If you flag the plan, give one or two sentences naming the flaw or goal deviation and the correction you suggest, concrete enough for the Operator to weigh it against its own observations."
|
|
12
|
+
},
|
|
13
|
+
"modes": {
|
|
14
|
+
"initial_plan": {
|
|
15
|
+
"system": [
|
|
16
|
+
"role",
|
|
17
|
+
"thinking_process",
|
|
18
|
+
"capabilities_operator",
|
|
19
|
+
"output_protocol_initial",
|
|
20
|
+
"examples_initial"
|
|
21
|
+
],
|
|
22
|
+
"human": "Goal: {{ initial_goal }}"
|
|
23
|
+
},
|
|
24
|
+
"validator": {
|
|
25
|
+
"system": [
|
|
26
|
+
"role_validator",
|
|
27
|
+
"validator_guidelines"
|
|
28
|
+
],
|
|
29
|
+
"human": "Initial Goal: {{ initial_goal }}\n\n### Execution History\n{{ history_str }}\n\n### Operator's Thoughts Before Modifying\n{% if operator_native_thinking %}Native Thinking:\n{{ operator_native_thinking }}\n\n{% endif %}{% if operator_raw_thinking %}Raw Thinking:\n{{ operator_raw_thinking }}\n\n{% endif %}### Old Plan\n{{ content_before }}\n\n### Proposed New Plan\n{{ content_after }}\n\nPlease evaluate if the proposed new plan should be approved."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|