android-midscene-automation 0.2.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +159 -29
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +340 -121
- package/src/types.ts +1 -1
|
@@ -0,0 +1,627 @@
|
|
|
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
|
+
"""MCP Tool: mobile_manage_task."""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import signal
|
|
21
|
+
import sqlite3
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from mcp_server.base import mcp
|
|
27
|
+
from mcp_server.notifiers import notify
|
|
28
|
+
from mcp_server.utils import env_utils
|
|
29
|
+
from artemis.runtime import (
|
|
30
|
+
DeviceExecutionLock,
|
|
31
|
+
is_daemon_running,
|
|
32
|
+
process_supervisor,
|
|
33
|
+
stop_task_on_daemon,
|
|
34
|
+
trace_store,
|
|
35
|
+
)
|
|
36
|
+
from artemis.runtime.process_probe import pid_is_alive
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
_LIVENESS_FAILURE_ERROR = "Task runner process terminated unexpectedly."
|
|
41
|
+
# Grace window covering the spawn race: the launcher pid may already have exited
|
|
42
|
+
# while the real runner has not yet registered its own pid in the DataEngine DB.
|
|
43
|
+
_STARTUP_GRACE_SECONDS = 45.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _find_data_engine_db() -> str | None:
|
|
47
|
+
db_path = os.path.join(trace_store.TRACES_DIR, "data_engine.db")
|
|
48
|
+
if os.path.exists(db_path):
|
|
49
|
+
return db_path
|
|
50
|
+
db_path = os.path.join(env_utils.get_project_root(), "traces", "data_engine.db")
|
|
51
|
+
return db_path if os.path.exists(db_path) else None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _pid_alive(pid: int | None) -> bool:
|
|
55
|
+
if not pid:
|
|
56
|
+
return False
|
|
57
|
+
try:
|
|
58
|
+
pid_int = int(pid)
|
|
59
|
+
except (TypeError, ValueError):
|
|
60
|
+
return False
|
|
61
|
+
return pid_is_alive(pid_int)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _session_tracked_by_lock(trace_id: str) -> bool | None:
|
|
65
|
+
"""Whether the device-lock layer still tracks this session (queued or active owner).
|
|
66
|
+
|
|
67
|
+
Returns ``True`` when tracked, ``False`` when both probes succeeded and the
|
|
68
|
+
session is definitively absent, and ``None`` when a probe failed — an
|
|
69
|
+
unreadable queue or lock dir may hide a live session, so the caller must
|
|
70
|
+
not treat a failed probe as evidence of death (same "uncertain defaults to
|
|
71
|
+
alive" stance as ``pid_is_alive``).
|
|
72
|
+
"""
|
|
73
|
+
uncertain = False
|
|
74
|
+
try:
|
|
75
|
+
for q_item in DeviceExecutionLock.get_queued_tasks():
|
|
76
|
+
if str(q_item.get("session_id")) == trace_id:
|
|
77
|
+
return True
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
uncertain = True
|
|
80
|
+
print(f"Could not read device queue for {trace_id}: {exc}", file=sys.stderr)
|
|
81
|
+
try:
|
|
82
|
+
for owner in DeviceExecutionLock.get_active_owners().values():
|
|
83
|
+
if owner.session_id and str(owner.session_id) == trace_id:
|
|
84
|
+
return True
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
uncertain = True
|
|
87
|
+
print(f"Could not read device owners for {trace_id}: {exc}", file=sys.stderr)
|
|
88
|
+
return None if uncertain else False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _reconcile_task_state(
|
|
92
|
+
trace_id: str, status_data: dict[str, Any]
|
|
93
|
+
) -> tuple[str, int | None, bool]:
|
|
94
|
+
"""Reconcile status.json with the DataEngine DB and real process liveness.
|
|
95
|
+
|
|
96
|
+
The DB session row is written by the runner process itself (``os.getpid()``),
|
|
97
|
+
so its pid and terminal status outrank the pid recorded at spawn time -- on
|
|
98
|
+
Windows that spawn pid is a short-lived venv launcher, not the runner. A dead
|
|
99
|
+
or missing pid alone never fails a task that the DB, the device-lock queue,
|
|
100
|
+
or an active lock owner still reports as alive.
|
|
101
|
+
|
|
102
|
+
Returns ``(current_status, pid, is_alive)`` and persists any correction back
|
|
103
|
+
to status.json (including recovering from a previously misreported failure).
|
|
104
|
+
"""
|
|
105
|
+
current_status = status_data.get("status", "unknown")
|
|
106
|
+
pid = status_data.get("pid")
|
|
107
|
+
dirty = False
|
|
108
|
+
|
|
109
|
+
# "success" is a legacy alias for the canonical "completed" terminal
|
|
110
|
+
# status; consumers of this reconcile must only ever see "completed".
|
|
111
|
+
if current_status == "success":
|
|
112
|
+
current_status = "completed"
|
|
113
|
+
status_data["status"] = "completed"
|
|
114
|
+
dirty = True
|
|
115
|
+
|
|
116
|
+
db_status: str | None = None
|
|
117
|
+
db_pid: int | None = None
|
|
118
|
+
db_path = _find_data_engine_db()
|
|
119
|
+
if db_path:
|
|
120
|
+
try:
|
|
121
|
+
conn = sqlite3.connect(db_path)
|
|
122
|
+
conn.row_factory = sqlite3.Row
|
|
123
|
+
row = conn.execute(
|
|
124
|
+
"SELECT status, pid FROM sessions WHERE session_id = ? ORDER BY start_time DESC LIMIT 1",
|
|
125
|
+
(trace_id,),
|
|
126
|
+
).fetchone()
|
|
127
|
+
conn.close()
|
|
128
|
+
if row:
|
|
129
|
+
db_status = row["status"]
|
|
130
|
+
db_pid = row["pid"]
|
|
131
|
+
except sqlite3.Error as exc:
|
|
132
|
+
# Reconciliation then relies on status.json and lock evidence only.
|
|
133
|
+
print(f"Could not read DB session row for {trace_id}: {exc}", file=sys.stderr)
|
|
134
|
+
|
|
135
|
+
if db_pid and db_pid != pid:
|
|
136
|
+
pid = db_pid
|
|
137
|
+
status_data["pid"] = pid
|
|
138
|
+
dirty = True
|
|
139
|
+
|
|
140
|
+
# The DB terminal verdict only fills in a status.json that has not reached a
|
|
141
|
+
# terminal state itself, or corrects a liveness-inferred failure. It never
|
|
142
|
+
# overrides a completed result or an explicit user cancellation.
|
|
143
|
+
liveness_failed = (
|
|
144
|
+
current_status == "failed" and status_data.get("error") == _LIVENESS_FAILURE_ERROR
|
|
145
|
+
)
|
|
146
|
+
if db_status in ("completed", "success", "failed", "cancelled") and (
|
|
147
|
+
current_status in ("running", "pending") or liveness_failed
|
|
148
|
+
):
|
|
149
|
+
canonical = "completed" if db_status in ("completed", "success") else db_status
|
|
150
|
+
if current_status != canonical:
|
|
151
|
+
current_status = canonical
|
|
152
|
+
status_data["status"] = canonical
|
|
153
|
+
if canonical != "failed" and status_data.get("error") == _LIVENESS_FAILURE_ERROR:
|
|
154
|
+
status_data["error"] = None
|
|
155
|
+
if not status_data.get("end_time"):
|
|
156
|
+
status_data["end_time"] = time.time()
|
|
157
|
+
dirty = True
|
|
158
|
+
is_alive = False
|
|
159
|
+
elif current_status in ("running", "pending"):
|
|
160
|
+
if not pid:
|
|
161
|
+
# No pid recorded (daemon dispatch never writes one): liveness cannot be
|
|
162
|
+
# inferred, so the task is assumed alive -- matching the historical
|
|
163
|
+
# behavior of only checking liveness when a pid exists.
|
|
164
|
+
is_alive = True
|
|
165
|
+
else:
|
|
166
|
+
is_alive = _pid_alive(pid)
|
|
167
|
+
if not is_alive:
|
|
168
|
+
tracked = _session_tracked_by_lock(trace_id)
|
|
169
|
+
if tracked or tracked is None:
|
|
170
|
+
# Still queued for a device or owned by a live lock holder --
|
|
171
|
+
# or the lock probe failed, in which case the session may be
|
|
172
|
+
# hidden and must not be declared dead on this poll.
|
|
173
|
+
is_alive = True
|
|
174
|
+
elif db_status in ("running", "pending") and db_pid:
|
|
175
|
+
# The runner registered itself and its process is gone: truly dead.
|
|
176
|
+
current_status = "failed"
|
|
177
|
+
_mark_liveness_failure(trace_id, status_data)
|
|
178
|
+
elif (time.time() - (status_data.get("start_time") or 0)) < _STARTUP_GRACE_SECONDS:
|
|
179
|
+
is_alive = True
|
|
180
|
+
else:
|
|
181
|
+
current_status = "failed"
|
|
182
|
+
_mark_liveness_failure(trace_id, status_data)
|
|
183
|
+
else:
|
|
184
|
+
is_alive = False
|
|
185
|
+
|
|
186
|
+
if dirty:
|
|
187
|
+
trace_store.write_status(trace_id, status_data)
|
|
188
|
+
return current_status, pid, is_alive
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _mark_liveness_failure(trace_id: str, status_data: dict[str, Any]) -> None:
|
|
192
|
+
trace_store.update_trace_status(trace_id, "failed", error=_LIVENESS_FAILURE_ERROR)
|
|
193
|
+
status_data["status"] = "failed"
|
|
194
|
+
status_data["error"] = _LIVENESS_FAILURE_ERROR
|
|
195
|
+
conv_id = status_data.get("conversation_id")
|
|
196
|
+
if conv_id:
|
|
197
|
+
try:
|
|
198
|
+
notify(
|
|
199
|
+
conversation_id=conv_id,
|
|
200
|
+
message=(
|
|
201
|
+
f"Artemis background task died unexpectedly for trace '{trace_id}'. "
|
|
202
|
+
f"Error: {_LIVENESS_FAILURE_ERROR}"
|
|
203
|
+
),
|
|
204
|
+
event_type="failed",
|
|
205
|
+
payload={"trace_id": trace_id, "error": _LIVENESS_FAILURE_ERROR},
|
|
206
|
+
)
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
# Notification dispatch is best-effort; the failure verdict is
|
|
209
|
+
# already persisted in status.json.
|
|
210
|
+
logger.debug(
|
|
211
|
+
"Liveness-failure notification skipped for trace %s: %s",
|
|
212
|
+
trace_id,
|
|
213
|
+
exc,
|
|
214
|
+
exc_info=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@mcp.tool()
|
|
219
|
+
def mobile_manage_task(
|
|
220
|
+
action: str,
|
|
221
|
+
trace_id: str,
|
|
222
|
+
instruction: str | None = None,
|
|
223
|
+
release_loop: bool = False,
|
|
224
|
+
) -> dict[str, Any]:
|
|
225
|
+
"""Manages the lifecycle and retrieves the status of a background mobile automation task.
|
|
226
|
+
|
|
227
|
+
This is your primary diagnostic and control tool for tasks started by
|
|
228
|
+
`mobile_run_task`. You MUST use it to poll the task status when your
|
|
229
|
+
1-minute fallback timer triggers (as required by `mobile_run_task`). You
|
|
230
|
+
can also steer a subagent that is stuck or off-track, or abort a task.
|
|
231
|
+
|
|
232
|
+
### Actions
|
|
233
|
+
- **'status'**: Returns `trace_id`, `status` ('running'/'completed'/'failed'/
|
|
234
|
+
'cancelled'), `device_serial` (which phone owns this task in multi-device
|
|
235
|
+
setups), `task_desc`, `model`, `elapsed_seconds`, a `test_summary` (when
|
|
236
|
+
the run declared verification check items: machine-readable
|
|
237
|
+
passed/failed/inconclusive/unchecked counts plus failed-item details —
|
|
238
|
+
no need to parse report prose), and `progress` (Flash:
|
|
239
|
+
current turn, latest thought/action — intervene if turns climb without
|
|
240
|
+
progress or the thought indicates it is stuck; Pro: the active task plan
|
|
241
|
+
— check it still aligns with your goal).
|
|
242
|
+
- **'inject_instruction'**: Injects real-time guidance mid-flight when the
|
|
243
|
+
subagent errs, stalls, or loops. Applied at the start of the next
|
|
244
|
+
planning turn (Pro) or the next reactive loop (Flash). Requires
|
|
245
|
+
`instruction` (unless `release_loop=True`). To end a continuous
|
|
246
|
+
monitoring task ([Loop:continuous] milestone) gracefully, you MUST pass
|
|
247
|
+
`release_loop=True` — this is the only signal that unlocks the
|
|
248
|
+
milestone's completion; natural-language "please stop" phrasing in
|
|
249
|
+
`instruction` is NOT interpreted as a stop signal.
|
|
250
|
+
- **'stop'**: Forcefully terminates the subagent, immediately halting
|
|
251
|
+
device interactions and releasing the device. Use when the task is done,
|
|
252
|
+
irreparably broken, or running out of control. Prefer
|
|
253
|
+
`inject_instruction` with `release_loop=True` when a monitoring task
|
|
254
|
+
should wind down cleanly instead of being killed.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
action: `"status"`, `"inject_instruction"`, or `"stop"`.
|
|
258
|
+
trace_id: The task's session identifier from `mobile_run_task`.
|
|
259
|
+
instruction: Guidance string; required for `inject_instruction`
|
|
260
|
+
(optional when `release_loop=True`), omit otherwise.
|
|
261
|
+
release_loop: With `inject_instruction`: explicit user stop signal
|
|
262
|
+
that authorizes the subagent to complete continuous monitoring
|
|
263
|
+
loops and finish the task gracefully.
|
|
264
|
+
"""
|
|
265
|
+
status_data = trace_store.read_status(trace_id)
|
|
266
|
+
if not status_data:
|
|
267
|
+
return {
|
|
268
|
+
"trace_id": trace_id,
|
|
269
|
+
"status": "unknown",
|
|
270
|
+
"message": f"Trace ID '{trace_id}' not found.",
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
current_status, pid, is_alive = _reconcile_task_state(trace_id, status_data)
|
|
274
|
+
|
|
275
|
+
trace_dir = trace_store.get_trace_dir(trace_id)
|
|
276
|
+
|
|
277
|
+
if action == "status":
|
|
278
|
+
start_time = status_data.get("start_time")
|
|
279
|
+
end_time = status_data.get("end_time") or time.time()
|
|
280
|
+
elapsed = round(end_time - start_time, 1) if start_time else 0
|
|
281
|
+
|
|
282
|
+
project_root = env_utils.get_project_root()
|
|
283
|
+
db_path = os.path.join(trace_store.TRACES_DIR, "data_engine.db")
|
|
284
|
+
if not os.path.exists(db_path):
|
|
285
|
+
db_path = os.path.join(project_root, "traces", "data_engine.db")
|
|
286
|
+
|
|
287
|
+
device_serial = status_data.get("device_serial")
|
|
288
|
+
if not device_serial and os.path.exists(db_path):
|
|
289
|
+
try:
|
|
290
|
+
conn_dev = sqlite3.connect(db_path)
|
|
291
|
+
conn_dev.row_factory = sqlite3.Row
|
|
292
|
+
cur_dev = conn_dev.cursor()
|
|
293
|
+
cur_dev.execute(
|
|
294
|
+
"SELECT device_info FROM sessions WHERE session_id = ? OR pid = ? ORDER BY start_time DESC LIMIT 1",
|
|
295
|
+
(trace_id, pid),
|
|
296
|
+
)
|
|
297
|
+
row_dev = cur_dev.fetchone()
|
|
298
|
+
if row_dev and row_dev["device_info"]:
|
|
299
|
+
try:
|
|
300
|
+
d_info = json.loads(row_dev["device_info"])
|
|
301
|
+
if isinstance(d_info, dict) and d_info.get("device_id"):
|
|
302
|
+
device_serial = d_info["device_id"]
|
|
303
|
+
status_data["device_serial"] = device_serial
|
|
304
|
+
trace_store.write_status(trace_id, status_data)
|
|
305
|
+
except (ValueError, TypeError, OSError):
|
|
306
|
+
# Malformed device_info or failed cache write: the
|
|
307
|
+
# serial simply stays unknown for this poll.
|
|
308
|
+
pass
|
|
309
|
+
conn_dev.close()
|
|
310
|
+
except sqlite3.Error:
|
|
311
|
+
# DB probe for the serial is optional enrichment.
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
if not device_serial and pid:
|
|
315
|
+
try:
|
|
316
|
+
owners = DeviceExecutionLock.get_active_owners()
|
|
317
|
+
for clean_id, owner in owners.items():
|
|
318
|
+
if owner.pid == pid or owner.session_id == trace_id:
|
|
319
|
+
device_serial = owner.device_id
|
|
320
|
+
status_data["device_serial"] = device_serial
|
|
321
|
+
trace_store.write_status(trace_id, status_data)
|
|
322
|
+
break
|
|
323
|
+
except OSError:
|
|
324
|
+
# Lock-owner probe / cache write failed: serial stays unknown.
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
response: dict[str, Any] = {
|
|
328
|
+
"trace_id": trace_id,
|
|
329
|
+
"status": current_status,
|
|
330
|
+
"device_serial": device_serial,
|
|
331
|
+
"elapsed_seconds": elapsed,
|
|
332
|
+
"task_desc": status_data.get("task_desc"),
|
|
333
|
+
"model": status_data.get("model"),
|
|
334
|
+
"stdout_log": os.path.join(trace_dir, "stdout.log"),
|
|
335
|
+
"stderr_log": os.path.join(trace_dir, "stderr.log"),
|
|
336
|
+
}
|
|
337
|
+
# Which UI-hierarchy source served the run ("helper" or "uiautomator"),
|
|
338
|
+
# plus any mid-run switch; written by the agent once the device connects.
|
|
339
|
+
if status_data.get("hierarchy_backend"):
|
|
340
|
+
response["hierarchy_backend"] = status_data.get("hierarchy_backend")
|
|
341
|
+
if status_data.get("hierarchy_backend_note"):
|
|
342
|
+
response["hierarchy_backend_note"] = status_data.get("hierarchy_backend_note")
|
|
343
|
+
if status_data.get("model", "").lower() != "flash":
|
|
344
|
+
response["notes_dir"] = os.path.join(trace_dir, "notes")
|
|
345
|
+
|
|
346
|
+
if current_status == "failed":
|
|
347
|
+
response["error"] = status_data.get("error")
|
|
348
|
+
elif current_status == "completed":
|
|
349
|
+
response["result"] = status_data.get("result")
|
|
350
|
+
|
|
351
|
+
# Machine-readable test summary (written by exit settlement): callers
|
|
352
|
+
# get assertion results without parsing report prose.
|
|
353
|
+
for candidate in (
|
|
354
|
+
os.path.join(trace_store.TRACES_DIR, trace_id, "run_outcome.json"),
|
|
355
|
+
os.path.join(trace_dir, "run_outcome.json"),
|
|
356
|
+
):
|
|
357
|
+
if os.path.exists(candidate):
|
|
358
|
+
try:
|
|
359
|
+
with open(candidate, encoding="utf-8") as f:
|
|
360
|
+
run_outcome = json.load(f)
|
|
361
|
+
tests = run_outcome.get("tests") or {}
|
|
362
|
+
response["test_summary"] = {
|
|
363
|
+
"task_status": run_outcome.get("task_status"),
|
|
364
|
+
**tests,
|
|
365
|
+
}
|
|
366
|
+
except (OSError, ValueError, TypeError):
|
|
367
|
+
# Unreadable or malformed run_outcome.json: omit the summary.
|
|
368
|
+
pass
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
if current_status in ("running", "pending") and is_alive:
|
|
372
|
+
progress: dict[str, Any] = {}
|
|
373
|
+
|
|
374
|
+
project_root = env_utils.get_project_root()
|
|
375
|
+
db_path = os.path.join(trace_store.TRACES_DIR, "data_engine.db")
|
|
376
|
+
if not os.path.exists(db_path):
|
|
377
|
+
db_path = os.path.join(project_root, "traces", "data_engine.db")
|
|
378
|
+
|
|
379
|
+
session_id = None
|
|
380
|
+
if os.path.exists(db_path):
|
|
381
|
+
try:
|
|
382
|
+
conn = sqlite3.connect(db_path)
|
|
383
|
+
conn.row_factory = sqlite3.Row
|
|
384
|
+
cursor = conn.cursor()
|
|
385
|
+
cursor.execute(
|
|
386
|
+
"SELECT session_id FROM sessions WHERE pid = ? ORDER BY start_time DESC LIMIT 1",
|
|
387
|
+
(pid,),
|
|
388
|
+
)
|
|
389
|
+
session_row = cursor.fetchone()
|
|
390
|
+
if session_row:
|
|
391
|
+
session_id = session_row["session_id"]
|
|
392
|
+
conn.close()
|
|
393
|
+
except Exception as db_err:
|
|
394
|
+
print(f"Error querying data_engine.db for session: {db_err}", file=sys.stderr)
|
|
395
|
+
|
|
396
|
+
if session_id:
|
|
397
|
+
if status_data.get("model", "").lower() == "flash":
|
|
398
|
+
progress["phase"] = "running_flash_loop"
|
|
399
|
+
if os.path.exists(db_path):
|
|
400
|
+
try:
|
|
401
|
+
conn2 = sqlite3.connect(db_path)
|
|
402
|
+
conn2.row_factory = sqlite3.Row
|
|
403
|
+
cur2 = conn2.cursor()
|
|
404
|
+
cur2.execute(
|
|
405
|
+
"SELECT count(*) as cnt FROM traces WHERE session_id = ? AND type = 'llm_call'",
|
|
406
|
+
(session_id,),
|
|
407
|
+
)
|
|
408
|
+
row_cnt = cur2.fetchone()
|
|
409
|
+
if row_cnt:
|
|
410
|
+
progress["current_turn"] = row_cnt["cnt"]
|
|
411
|
+
|
|
412
|
+
cur2.execute(
|
|
413
|
+
"SELECT payload FROM traces WHERE session_id = ? AND type in ('raw_thinking', 'llm_call') "
|
|
414
|
+
"AND status = 'success' ORDER BY timestamp DESC LIMIT 1",
|
|
415
|
+
(session_id,),
|
|
416
|
+
)
|
|
417
|
+
row_thought = cur2.fetchone()
|
|
418
|
+
if row_thought and row_thought["payload"]:
|
|
419
|
+
try:
|
|
420
|
+
p_obj = json.loads(row_thought["payload"])
|
|
421
|
+
if isinstance(p_obj, dict):
|
|
422
|
+
if "thought" in p_obj:
|
|
423
|
+
progress["latest_thought"] = p_obj["thought"]
|
|
424
|
+
elif (
|
|
425
|
+
"response" in p_obj
|
|
426
|
+
and isinstance(p_obj["response"], list)
|
|
427
|
+
and p_obj["response"]
|
|
428
|
+
):
|
|
429
|
+
progress["latest_thought"] = p_obj["response"][0].get(
|
|
430
|
+
"text", ""
|
|
431
|
+
)
|
|
432
|
+
except (ValueError, TypeError, AttributeError):
|
|
433
|
+
# Unparseable thought payload: omit it.
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
cur2.execute(
|
|
437
|
+
"SELECT name, payload FROM traces WHERE session_id = ? AND type = 'tool' "
|
|
438
|
+
"AND status = 'success' ORDER BY timestamp DESC LIMIT 1",
|
|
439
|
+
(session_id,),
|
|
440
|
+
)
|
|
441
|
+
row_action = cur2.fetchone()
|
|
442
|
+
if row_action:
|
|
443
|
+
act_name = row_action["name"]
|
|
444
|
+
act_payload = {}
|
|
445
|
+
if row_action["payload"]:
|
|
446
|
+
try:
|
|
447
|
+
act_payload = json.loads(row_action["payload"]).get(
|
|
448
|
+
"args", {}
|
|
449
|
+
)
|
|
450
|
+
except (ValueError, TypeError, AttributeError):
|
|
451
|
+
# Unparseable action payload: show the
|
|
452
|
+
# bare action name.
|
|
453
|
+
pass
|
|
454
|
+
progress["latest_action"] = (
|
|
455
|
+
f"{act_name}({act_payload})" if act_payload else act_name
|
|
456
|
+
)
|
|
457
|
+
conn2.close()
|
|
458
|
+
except Exception as q_err:
|
|
459
|
+
print(
|
|
460
|
+
f"Error querying flash progress from data_engine.db: {q_err}",
|
|
461
|
+
file=sys.stderr,
|
|
462
|
+
)
|
|
463
|
+
else:
|
|
464
|
+
plan_content = None
|
|
465
|
+
plan_path = os.path.join(
|
|
466
|
+
trace_store.TRACES_DIR, session_id, "notes", "task_plan.md"
|
|
467
|
+
)
|
|
468
|
+
if not os.path.exists(plan_path):
|
|
469
|
+
plan_path = os.path.join(trace_dir, "notes", "task_plan.md")
|
|
470
|
+
if os.path.exists(plan_path):
|
|
471
|
+
try:
|
|
472
|
+
with open(plan_path, encoding="utf-8") as f:
|
|
473
|
+
plan_content = f.read()
|
|
474
|
+
except OSError:
|
|
475
|
+
# Unreadable plan file: progress omits the plan.
|
|
476
|
+
pass
|
|
477
|
+
progress["task_plan"] = plan_content
|
|
478
|
+
else:
|
|
479
|
+
progress["phase"] = "initializing"
|
|
480
|
+
progress["status_message"] = "Spawning background runner..."
|
|
481
|
+
|
|
482
|
+
response["progress"] = progress
|
|
483
|
+
|
|
484
|
+
return response
|
|
485
|
+
|
|
486
|
+
elif action == "stop":
|
|
487
|
+
if current_status not in ("running", "pending"):
|
|
488
|
+
return {
|
|
489
|
+
"trace_id": trace_id,
|
|
490
|
+
"status": current_status,
|
|
491
|
+
"message": f"Cannot stop task because it is already in a terminal state: {current_status}",
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# 1. First attempt graceful cancellation via unified Daemon if available
|
|
495
|
+
stopped_via_daemon = False
|
|
496
|
+
if os.environ.get("ARTEMIS_STANDALONE") != "1":
|
|
497
|
+
try:
|
|
498
|
+
if is_daemon_running():
|
|
499
|
+
stopped_via_daemon = stop_task_on_daemon(trace_id)
|
|
500
|
+
except Exception as exc:
|
|
501
|
+
# Fall through to direct process termination below.
|
|
502
|
+
print(f"Daemon stop attempt failed for {trace_id}: {exc}", file=sys.stderr)
|
|
503
|
+
|
|
504
|
+
# 2. Cancel queue reservation if present
|
|
505
|
+
queue_ticket = status_data.get("queue_ticket")
|
|
506
|
+
if queue_ticket:
|
|
507
|
+
try:
|
|
508
|
+
DeviceExecutionLock.cancel_reservation(queue_ticket)
|
|
509
|
+
except OSError as exc:
|
|
510
|
+
# A stale reservation expires on its own; note the failure.
|
|
511
|
+
print(f"Could not cancel reservation {queue_ticket}: {exc}", file=sys.stderr)
|
|
512
|
+
|
|
513
|
+
# 3. If process PID is missing, try to resolve from active device owners
|
|
514
|
+
if not pid:
|
|
515
|
+
try:
|
|
516
|
+
active_owners = DeviceExecutionLock.get_active_owners()
|
|
517
|
+
for dev_owner in active_owners.values():
|
|
518
|
+
if dev_owner.session_id and str(dev_owner.session_id) == trace_id:
|
|
519
|
+
pid = dev_owner.pid
|
|
520
|
+
status_data["pid"] = pid
|
|
521
|
+
trace_store.write_status(trace_id, status_data)
|
|
522
|
+
break
|
|
523
|
+
except OSError as exc:
|
|
524
|
+
# Without a resolved pid the stop falls back to the
|
|
525
|
+
# daemon/queue outcome, so surface the probe failure.
|
|
526
|
+
print(f"Could not resolve owner pid for {trace_id}: {exc}", file=sys.stderr)
|
|
527
|
+
|
|
528
|
+
# 4. If process PID could not be determined and not stopped via daemon or queue
|
|
529
|
+
if not pid and not stopped_via_daemon and not queue_ticket:
|
|
530
|
+
return {
|
|
531
|
+
"trace_id": trace_id,
|
|
532
|
+
"status": current_status,
|
|
533
|
+
"message": "Process ID (PID) is missing from the task status. Cannot stop task.",
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
# 5. If process PID is available, terminate the process tree
|
|
537
|
+
if pid:
|
|
538
|
+
try:
|
|
539
|
+
if sys.platform == "win32":
|
|
540
|
+
if not process_supervisor.terminate_tree(pid):
|
|
541
|
+
import psutil
|
|
542
|
+
|
|
543
|
+
if psutil.pid_exists(pid):
|
|
544
|
+
raise RuntimeError(
|
|
545
|
+
f"Failed to terminate Windows process tree rooted at {pid}"
|
|
546
|
+
)
|
|
547
|
+
DeviceExecutionLock.cleanup_stale_locks()
|
|
548
|
+
else:
|
|
549
|
+
os.kill(pid, signal.SIGTERM)
|
|
550
|
+
except ProcessLookupError:
|
|
551
|
+
pass
|
|
552
|
+
except Exception as e:
|
|
553
|
+
if not stopped_via_daemon:
|
|
554
|
+
return {
|
|
555
|
+
"trace_id": trace_id,
|
|
556
|
+
"status": current_status,
|
|
557
|
+
"message": f"Failed to terminate process {pid}: {e}",
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
trace_store.update_trace_status(
|
|
561
|
+
trace_id, "cancelled", error="Task aborted by user request."
|
|
562
|
+
)
|
|
563
|
+
return {
|
|
564
|
+
"trace_id": trace_id,
|
|
565
|
+
"status": "cancelled",
|
|
566
|
+
"message": f"Successfully stopped background task '{trace_id}'.",
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
elif action == "inject_instruction":
|
|
570
|
+
if not instruction and release_loop:
|
|
571
|
+
instruction = (
|
|
572
|
+
"The user has requested to stop the continuous monitoring loop."
|
|
573
|
+
" Wrap up gracefully and complete the task."
|
|
574
|
+
)
|
|
575
|
+
if not instruction:
|
|
576
|
+
return {
|
|
577
|
+
"trace_id": trace_id,
|
|
578
|
+
"status": current_status,
|
|
579
|
+
"message": "Missing required argument 'instruction' for action 'inject_instruction'.",
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if current_status not in ("running", "pending"):
|
|
583
|
+
return {
|
|
584
|
+
"trace_id": trace_id,
|
|
585
|
+
"status": current_status,
|
|
586
|
+
"message": f"Cannot inject instruction because task is in a terminal state: {current_status}",
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if not os.path.exists(trace_dir):
|
|
590
|
+
return {
|
|
591
|
+
"trace_id": trace_id,
|
|
592
|
+
"status": current_status,
|
|
593
|
+
"message": f"Trace directory not found: {trace_dir}",
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
instruction_path = os.path.join(trace_dir, "injected_instruction.json")
|
|
597
|
+
try:
|
|
598
|
+
with open(instruction_path, "w", encoding="utf-8") as f:
|
|
599
|
+
json.dump(
|
|
600
|
+
{
|
|
601
|
+
"instruction": instruction,
|
|
602
|
+
"release_loop": bool(release_loop),
|
|
603
|
+
"timestamp": time.time(),
|
|
604
|
+
"status": "pending",
|
|
605
|
+
},
|
|
606
|
+
f,
|
|
607
|
+
indent=2,
|
|
608
|
+
)
|
|
609
|
+
suffix = " (with release_loop stop signal)" if release_loop else ""
|
|
610
|
+
return {
|
|
611
|
+
"trace_id": trace_id,
|
|
612
|
+
"status": current_status,
|
|
613
|
+
"message": f"Successfully injected instruction{suffix}: '{instruction}'",
|
|
614
|
+
}
|
|
615
|
+
except Exception as e:
|
|
616
|
+
return {
|
|
617
|
+
"trace_id": trace_id,
|
|
618
|
+
"status": current_status,
|
|
619
|
+
"message": f"Failed to inject instruction: {e}",
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
else:
|
|
623
|
+
return {
|
|
624
|
+
"trace_id": trace_id,
|
|
625
|
+
"status": current_status,
|
|
626
|
+
"message": f"Invalid action '{action}'. Supported actions are 'status', 'stop', and 'inject_instruction'.",
|
|
627
|
+
}
|