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.
Files changed (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. 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
+ }