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,548 @@
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_run_task."""
16
+
17
+ import logging
18
+ import os
19
+ import subprocess
20
+ import sys
21
+ import threading
22
+ import time
23
+ from typing import Any, get_args
24
+ import uuid
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.config import ExplorerVersion, checker_overrides_for_level
30
+ from artemis.config.runtime import read_ipc_port
31
+ from artemis.runtime import (
32
+ DeviceExecutionLock,
33
+ device_pool,
34
+ ensure_daemon_running,
35
+ submit_task_to_daemon,
36
+ trace_store,
37
+ )
38
+
39
+ # Seconds the spawned runner gets to finish its imports and open its log files
40
+ # before the spawn is declared dead. Normal startup creates stdout.log within a
41
+ # few seconds; a runner that produced no logs by this deadline is hung in
42
+ # interpreter/DLL startup and will never recover (observed on Windows when the
43
+ # MCP server process context degrades).
44
+ SPAWN_WATCHDOG_SECONDS = 60
45
+
46
+
47
+ def _kill_process_tree(pid: int) -> None:
48
+ """Terminate a spawned runner and its children (the venv shim re-execs python)."""
49
+ if sys.platform == "win32":
50
+ subprocess.run(
51
+ ["taskkill", "/PID", str(pid), "/T", "/F"],
52
+ stdin=subprocess.DEVNULL,
53
+ stdout=subprocess.DEVNULL,
54
+ stderr=subprocess.DEVNULL,
55
+ check=False,
56
+ )
57
+ else:
58
+ try:
59
+ os.kill(pid, 9)
60
+ except OSError:
61
+ pass
62
+
63
+
64
+ def _watch_spawn(
65
+ trace_id: str,
66
+ pid: int,
67
+ queue_ticket: str,
68
+ conversation_id: str | None,
69
+ deadline_seconds: float = SPAWN_WATCHDOG_SECONDS,
70
+ poll_interval: float = 2.0,
71
+ ) -> bool:
72
+ """Watch a freshly spawned runner; kill and fail the task if it never boots.
73
+
74
+ The runner opens stdout.log/stderr.log immediately after module imports, so
75
+ their absence after the deadline means the process is wedged before running
76
+ any task code. Without this, a wedged spawn leaves the task in 'running'
77
+ forever and holds its device-queue ticket.
78
+
79
+ Returns True when the runner booted (or was already terminal), False when it
80
+ was declared hung and killed.
81
+ """
82
+ trace_dir = trace_store.get_trace_dir(trace_id)
83
+ log_paths = (
84
+ os.path.join(trace_dir, "stdout.log"),
85
+ os.path.join(trace_dir, "stderr.log"),
86
+ )
87
+
88
+ deadline = time.monotonic() + deadline_seconds
89
+ while time.monotonic() < deadline:
90
+ if any(os.path.exists(p) for p in log_paths):
91
+ return True # runner booted normally
92
+ status_data = trace_store.read_status(trace_id)
93
+ if status_data and status_data.get("status") not in ("running", "pending"):
94
+ return True # already terminal (e.g. stopped by the user)
95
+ time.sleep(poll_interval)
96
+
97
+ if any(os.path.exists(p) for p in log_paths):
98
+ return True
99
+ status_data = trace_store.read_status(trace_id)
100
+ if status_data and status_data.get("status") not in ("running", "pending"):
101
+ return True
102
+
103
+ error_text = (
104
+ f"Background runner (pid {pid}) produced no logs within "
105
+ f"{deadline_seconds:.0f}s of spawn and was killed: the process hung "
106
+ "during interpreter startup. Restarting the MCP server usually clears "
107
+ "this; the task can be resubmitted afterwards."
108
+ )
109
+ _kill_process_tree(pid)
110
+ DeviceExecutionLock.cancel_reservation(queue_ticket)
111
+ trace_store.update_trace_status(trace_id, "failed", error=error_text)
112
+ if conversation_id:
113
+ try:
114
+ notify(
115
+ conversation_id=conversation_id,
116
+ message=f"Artemis task '{trace_id}' failed to start: {error_text}",
117
+ event_type="failed",
118
+ payload={"trace_id": trace_id, "error": error_text},
119
+ )
120
+ except Exception as exc:
121
+ # Notification dispatch is best-effort; the failure verdict is
122
+ # already persisted in status.json.
123
+ logging.getLogger("mcp_server").debug(
124
+ "Spawn-failure notification skipped for trace %s: %s",
125
+ trace_id,
126
+ exc,
127
+ exc_info=True,
128
+ )
129
+ return False
130
+
131
+
132
+ def _start_spawn_watchdog(
133
+ trace_id: str,
134
+ pid: int,
135
+ queue_ticket: str,
136
+ conversation_id: str | None,
137
+ ) -> None:
138
+ """Run _watch_spawn on a daemon thread so the tool call returns immediately."""
139
+ threading.Thread(
140
+ target=_watch_spawn,
141
+ args=(trace_id, pid, queue_ticket, conversation_id),
142
+ name=f"spawn-watchdog-{trace_id[:8]}",
143
+ daemon=True,
144
+ ).start()
145
+
146
+
147
+ def _validate_device_serial(device_serial: str) -> dict[str, Any] | None:
148
+ """Reject a task whose explicitly requested device is not attached and authorized.
149
+
150
+ Returns a failure response dict when the serial must be rejected, or None when
151
+ the device is usable. If device enumeration itself fails (no adb available),
152
+ validation is skipped rather than blocking task submission.
153
+ """
154
+ try:
155
+ # The shared validator fails open on an indeterminate/empty enumeration:
156
+ # the task proceeds and fails downstream with a clear no-device error.
157
+ detail = device_pool.validate_explicit_serial(device_serial)
158
+ except Exception:
159
+ return None
160
+ if detail is None:
161
+ return None
162
+ return {
163
+ "status": "failed",
164
+ "error": detail,
165
+ "message": (
166
+ f"{detail} Task rejected to prevent execution on an unintended device. "
167
+ "Run `adb devices -l` to inspect attached hardware, then resubmit with a "
168
+ "valid serial (or omit device_serial for automatic selection)."
169
+ ),
170
+ }
171
+
172
+
173
+ _EXPLORER_MODES: tuple[str, ...] = get_args(ExplorerVersion)
174
+
175
+
176
+ def _normalize_pro_tuning(
177
+ verification_level: str | None, explorer_mode: str | None
178
+ ) -> tuple[str | None, str | None]:
179
+ """Validate and normalise the Pro-profile tuning knobs (strip + lower).
180
+
181
+ Raises:
182
+ ValueError: with a caller-facing message when a value is not a known
183
+ preset, so the tool rejects the call before any trace is created.
184
+ """
185
+ level: str | None = None
186
+ if verification_level is not None and str(verification_level).strip():
187
+ level = str(verification_level).strip().lower()
188
+ try:
189
+ checker_overrides_for_level(level)
190
+ except ValueError as exc:
191
+ raise ValueError(f"Invalid verification_level: {exc}") from exc
192
+
193
+ mode: str | None = None
194
+ if explorer_mode is not None and str(explorer_mode).strip():
195
+ mode = str(explorer_mode).strip().lower()
196
+ if mode not in _EXPLORER_MODES:
197
+ raise ValueError(
198
+ f"Invalid explorer_mode {explorer_mode!r}. Must be one of: "
199
+ + ", ".join(_EXPLORER_MODES)
200
+ )
201
+ return level, mode
202
+
203
+
204
+ @mcp.tool()
205
+ def mobile_run_task(
206
+ task_desc: str,
207
+ conversation_id: str | None = None,
208
+ model: str = "Flash",
209
+ locked_app_package: str | None = None,
210
+ app_path: str | None = None,
211
+ expected_output_desc: str | None = None,
212
+ device_serial: str | None = None,
213
+ verification_level: str | None = None,
214
+ explorer_mode: str | None = None,
215
+ ) -> dict[str, Any]:
216
+ """Starts an autonomous mobile UI automation subagent on a connected Android device.
217
+
218
+ Delegates a mobile workflow to a background agent. Non-blocking: returns
219
+ immediately with `trace_id` (for `mobile_manage_task` / `mobile_inspect_trace`),
220
+ `device_serial`, `stdout_log`/`stderr_log` paths, and `notes_dir` (Pro only).
221
+ On completion (success or failure) a Reactive Wakeup notifies you — but only
222
+ when `conversation_id` was provided; without it, rely on your fallback timer.
223
+
224
+ ### Model selection (routing)
225
+ - **Flash** (default, preferred): simple, deterministic tasks with a clear
226
+ UI path. No step cap by default (history is compressed, and the agent
227
+ can recall earlier steps and query the session recording), but no ADB
228
+ shell, no pre-execution safety net, no persistent plan or notes, and
229
+ no verification or written report — unsuitable for exploration,
230
+ multi-branch recovery, structured monitoring/polling, or tasks that
231
+ must report back large amounts of detail.
232
+ - **Pro**: complex, exploratory, or multi-branch tasks; continuous
233
+ monitoring/polling; tasks needing ADB shell, system logs, verified
234
+ checkpoints, multi-step planning, or detailed written output (via notes
235
+ and `expected_output_desc`).
236
+
237
+ Timing is not precise: the agent's own inference adds ~5 s per step (Flash)
238
+ or ~30 s per turn (Pro) on top of any requested waits — account for this
239
+ when the task involves waiting. For recurring workflows, run once to
240
+ discover the path, then author a deterministic script instead of
241
+ re-delegating.
242
+
243
+ ### Follow-up protocol (CRITICAL)
244
+ 1. **Fallback timer**: you MUST set a background timer and check the task
245
+ status via `mobile_manage_task` at least once every 1 minute — mobile
246
+ tasks can stall silently, so never rely on the completion wakeup alone.
247
+ 2. **Post-task inspection**: after completion, inspect the execution logs
248
+ to verify success (`stderr_log` carries critical failures and Python
249
+ tracebacks — essential for debugging).
250
+ 3. **Pro notes**: for Pro runs, read `notes_dir` to extract the results and
251
+ plans the subagent recorded.
252
+
253
+ Args:
254
+ task_desc: Clear, self-contained task description with all context,
255
+ exact UI actions, and termination conditions (execution is one-shot).
256
+ For Pro, may also specify what the subagent must record in its notes.
257
+ conversation_id: Optional conversation ID used to route the completion
258
+ wakeup notification back to the caller. Omit if unknown.
259
+ model: `"Flash"` or `"Pro"` — see model selection above.
260
+ locked_app_package: Optional package name to lock execution to; the
261
+ agent auto-launches it and restricts actions to that app.
262
+ app_path: Optional local APK path to install before running.
263
+ expected_output_desc: Optional, Pro only. If set, a summarization agent
264
+ writes a report to `output.md` in `notes_dir`. Ignored for Flash.
265
+ device_serial: Optional device serial (e.g. "emulator-5554") to bind
266
+ execution to a specific device; distinct devices run concurrently.
267
+ If omitted, an available device is selected automatically. When
268
+ several devices are attached, confirm the target with the user first
269
+ (`adb devices -l` lists serials and authorization states).
270
+ verification_level: Optional, Pro only. Coarse Checker preset: `"off"`
271
+ (no audit; the Operator self-reports), `"final"` (one exit review
272
+ against the goal, the default), `"checkpoints"` (every plan
273
+ checkpoint is verified + exit review), `"strict"` (checkpoints with
274
+ a larger repair budget; a failed assert halts the run). Ignored for
275
+ Flash.
276
+ explorer_mode: Optional, Pro only. Explorer perception version used
277
+ by the Operator: `"flash"` (1-shot detection, the default),
278
+ `"pro"` (3-turn ReAct), `"ultra"` (deep pixel reasoning; slowest).
279
+ Ignored for Flash.
280
+ """
281
+ # 0. Validate and normalize model
282
+ if model.lower() not in ("flash", "pro"):
283
+ raise ValueError(f"Invalid model '{model}'. Must be either 'Flash' or 'Pro'.")
284
+ canonical_model = "Flash" if model.lower() == "flash" else "Pro"
285
+ # 0b. Validate the Pro tuning knobs before any trace exists so a typo is a
286
+ # plain tool error rather than a failed trace on disk.
287
+ verification_level, explorer_mode = _normalize_pro_tuning(verification_level, explorer_mode)
288
+
289
+ # 1. Generate a unique trace_id
290
+ trace_id = str(uuid.uuid4())
291
+
292
+ # 2. Initialize the trace store directory and status.json
293
+ trace_store.init_trace(
294
+ trace_id=trace_id,
295
+ task_desc=task_desc,
296
+ model=canonical_model,
297
+ conversation_id=conversation_id,
298
+ device_serial=device_serial,
299
+ )
300
+
301
+ # 2b. Strict device binding: an explicitly requested serial must be attached and
302
+ # authorized. Rejecting here prevents the task from silently running on a
303
+ # different device than the caller asked for. Runs after init_trace so the
304
+ # rejection carries a trace_id like every other response of this tool.
305
+ if device_serial:
306
+ rejection = _validate_device_serial(device_serial)
307
+ if rejection:
308
+ trace_store.update_trace_status(trace_id, "failed", error=rejection["error"])
309
+ return {"trace_id": trace_id, **rejection}
310
+
311
+ # 3. Dispatch via unified Artemis Daemon scheduler if available (unless standalone forced)
312
+ if os.environ.get("ARTEMIS_STANDALONE") != "1":
313
+ try:
314
+ is_running, base_url = ensure_daemon_running(timeout=2.0, wait_ready=True)
315
+ if is_running:
316
+ resp = submit_task_to_daemon(
317
+ goal=task_desc,
318
+ profile=canonical_model.lower(),
319
+ device_serial=device_serial,
320
+ expected_output=expected_output_desc if canonical_model != "Flash" else None,
321
+ locked_app_package=locked_app_package,
322
+ app_path=app_path,
323
+ session_id=trace_id,
324
+ ingress="mcp",
325
+ conversation_id=conversation_id,
326
+ verification_level=verification_level,
327
+ explorer_mode=explorer_mode,
328
+ base_url=base_url,
329
+ )
330
+ if resp and resp.get("status") == "rejected":
331
+ rejection_error = resp.get("error") or "Task rejected by Artemis Daemon."
332
+ trace_store.update_trace_status(trace_id, "failed", error=rejection_error)
333
+ return {
334
+ "trace_id": trace_id,
335
+ "status": "failed",
336
+ "error": rejection_error,
337
+ "message": f"Task rejected by Artemis Daemon: {rejection_error}",
338
+ }
339
+ if resp and resp.get("tasks"):
340
+ assigned_sid = resp["tasks"][0].get("session_id", trace_id)
341
+ trace_store.update_trace_status(
342
+ assigned_sid, "running", device_serial=device_serial
343
+ )
344
+ notes_dir = str(trace_store.get_trace_notes_dir(assigned_sid))
345
+ stdout_log = str(trace_store.get_trace_stdout_log_path(assigned_sid))
346
+ stderr_log = str(trace_store.get_trace_stderr_log_path(assigned_sid))
347
+ return {
348
+ "trace_id": assigned_sid,
349
+ "status": "running",
350
+ "message": (
351
+ f"Autonomous task '{task_desc}' enqueued via unified Artemis Daemon.\n"
352
+ f"Trace ID: {assigned_sid}\n"
353
+ f"Model: {canonical_model}\n"
354
+ f"Live telemetry: streaming to web workspace."
355
+ ),
356
+ "notes_dir": notes_dir,
357
+ "stdout_log": stdout_log,
358
+ "stderr_log": stderr_log,
359
+ }
360
+
361
+ # Check if the task was already enqueued despite a slow or None
362
+ # response. The probe reads a shared lock file, so transient
363
+ # read failures get a few retries; if it never succeeds the
364
+ # verdict is "unknown" -- reporting "failed" here for a task
365
+ # that did land in the queue would invite a duplicate submit.
366
+ probe_failed = False
367
+ for probe_attempt in range(3):
368
+ try:
369
+ queued_tasks = DeviceExecutionLock.get_queued_tasks()
370
+ except Exception as exc:
371
+ if probe_attempt < 2:
372
+ time.sleep(0.5)
373
+ continue
374
+ probe_failed = True
375
+ logging.getLogger("mcp_server").warning(
376
+ f"Could not verify daemon queue for '{trace_id}': {exc}"
377
+ )
378
+ break
379
+ for q in queued_tasks:
380
+ if str(q.get("session_id")) == str(trace_id):
381
+ trace_store.update_trace_status(
382
+ trace_id, "running", device_serial=device_serial
383
+ )
384
+ return {
385
+ "trace_id": trace_id,
386
+ "status": "running",
387
+ "message": (
388
+ f"Autonomous task '{task_desc}' enqueued via unified Artemis Daemon.\n"
389
+ f"Trace ID: {trace_id}\n"
390
+ f"Model: {canonical_model}\n"
391
+ f"Live telemetry: streaming to web workspace."
392
+ ),
393
+ "notes_dir": str(trace_store.get_trace_notes_dir(trace_id)),
394
+ "stdout_log": str(trace_store.get_trace_stdout_log_path(trace_id)),
395
+ "stderr_log": str(trace_store.get_trace_stderr_log_path(trace_id)),
396
+ }
397
+ break
398
+
399
+ if probe_failed:
400
+ # The task may or may not have been enqueued; do not mark
401
+ # the trace failed and do not let the caller resubmit
402
+ # blindly.
403
+ return {
404
+ "trace_id": trace_id,
405
+ "status": "unknown",
406
+ "message": (
407
+ f"Artemis Daemon gave no enqueue confirmation for task "
408
+ f"'{task_desc}' and the queue could not be inspected. The "
409
+ f"task may still have been enqueued. Poll "
410
+ f"mobile_manage_task(action='status', trace_id='{trace_id}') "
411
+ f"before resubmitting; if it still reports 'pending' after "
412
+ f"about a minute, treat it as not enqueued."
413
+ ),
414
+ }
415
+
416
+ # When Daemon is running, refuse to launch a conflicting standalone runner on the same device
417
+ logging.getLogger("mcp_server").warning(
418
+ f"Daemon dispatch failed for task '{task_desc}'. Aborting dispatch to prevent runner collision."
419
+ )
420
+ return {
421
+ "trace_id": trace_id,
422
+ "status": "failed",
423
+ "error": f"Failed to enqueue task '{task_desc}' to running Artemis Daemon scheduler.",
424
+ "message": "Task rejected or timed out in Artemis Daemon. Standalone fallback blocked to prevent runner collision.",
425
+ }
426
+ except Exception as exc:
427
+ logging.getLogger("mcp_server").warning(f"Daemon dispatch failed: {exc}")
428
+ return {
429
+ "trace_id": trace_id,
430
+ "status": "failed",
431
+ "error": f"Daemon dispatch error: {exc}",
432
+ "message": "Failed to dispatch task to Artemis Daemon.",
433
+ }
434
+
435
+ # 4. Standalone Fallback: Resolve project root, python executable, and background runner module
436
+ project_root = env_utils.get_project_root()
437
+ python_exe = env_utils.resolve_python_executable(project_root)
438
+ reserve_kwargs: dict[str, Any] = {
439
+ "description": f"MCP task: {task_desc[:120]}",
440
+ "session_id": trace_id,
441
+ "ingress": "mcp",
442
+ }
443
+ if device_serial:
444
+ reserve_kwargs["device_id"] = device_serial
445
+ queue_ticket = DeviceExecutionLock.reserve(**reserve_kwargs)
446
+
447
+ # 5. Spawn the background task runner as an independent subprocess
448
+ try:
449
+ cmd = [
450
+ python_exe,
451
+ "-m",
452
+ "mcp_server.background.task_runner",
453
+ "--trace-id",
454
+ trace_id,
455
+ "--task-desc",
456
+ task_desc,
457
+ "--model",
458
+ canonical_model,
459
+ "--conversation-id",
460
+ conversation_id or "",
461
+ ]
462
+ if locked_app_package:
463
+ cmd.extend(["--locked-app-package", locked_app_package])
464
+ if app_path:
465
+ cmd.extend(["--app-path", app_path])
466
+ if expected_output_desc and canonical_model != "Flash":
467
+ cmd.extend(["--expected-output-desc", expected_output_desc])
468
+ if device_serial:
469
+ cmd.extend(["--device-serial", device_serial])
470
+ if verification_level:
471
+ cmd.extend(["--verification-level", verification_level])
472
+ if explorer_mode:
473
+ cmd.extend(["--explorer-pro-mode", explorer_mode])
474
+
475
+ env = os.environ.copy()
476
+ env["ARTEMIS_SESSION_ID"] = trace_id
477
+ env["ARTEMIS_TASK_INGRESS"] = "mcp"
478
+ if device_serial:
479
+ env["ADB_DEVICE_SERIAL"] = device_serial
480
+ env["ARTEMIS_DEVICE_ID"] = device_serial
481
+ env[DeviceExecutionLock.QUEUE_TICKET_ENV] = queue_ticket
482
+ try:
483
+ ipc_port = read_ipc_port()
484
+ if ipc_port:
485
+ env["ARTEMIS_IPC_PORT"] = str(ipc_port)
486
+ except (OSError, ValueError):
487
+ # No readable IPC port file: the runner works without live telemetry.
488
+ pass
489
+
490
+ proc_kwargs = env_utils.get_detached_process_kwargs()
491
+ proc = subprocess.Popen(
492
+ cmd,
493
+ env=env,
494
+ cwd=project_root,
495
+ **proc_kwargs,
496
+ )
497
+ transfer_kwargs: dict[str, Any] = {
498
+ "description": f"MCP task: {task_desc[:120]}",
499
+ "session_id": trace_id,
500
+ "ingress": "mcp",
501
+ }
502
+ if device_serial:
503
+ transfer_kwargs["device_id"] = device_serial
504
+
505
+ DeviceExecutionLock.transfer_reservation(
506
+ queue_ticket,
507
+ proc.pid,
508
+ **transfer_kwargs,
509
+ )
510
+
511
+ # 5. Record the PID of the spawned subprocess
512
+ status_data = trace_store.read_status(trace_id)
513
+ if status_data:
514
+ status_data["pid"] = proc.pid
515
+ status_data["queue_ticket"] = queue_ticket
516
+ trace_store.write_status(trace_id, status_data)
517
+
518
+ # 6. Arm a watchdog: a runner that never opens its log files is hung in
519
+ # interpreter startup and must be killed instead of squatting on the
520
+ # queue as 'running' forever.
521
+ _start_spawn_watchdog(trace_id, proc.pid, queue_ticket, conversation_id)
522
+
523
+ trace_dir = trace_store.get_trace_dir(trace_id)
524
+ stdout_log_path = os.path.join(trace_dir, "stdout.log")
525
+ stderr_log_path = os.path.join(trace_dir, "stderr.log")
526
+
527
+ response_dict: dict[str, Any] = {
528
+ "trace_id": trace_id,
529
+ "device_serial": device_serial or "auto-select",
530
+ "message": "Successfully started background task.",
531
+ "stdout_log": stdout_log_path,
532
+ "stderr_log": stderr_log_path,
533
+ }
534
+ if canonical_model != "Flash":
535
+ response_dict["notes_dir"] = os.path.join(trace_dir, "notes")
536
+
537
+ return response_dict
538
+
539
+ except Exception as e:
540
+ DeviceExecutionLock.cancel_reservation(queue_ticket)
541
+ trace_store.update_trace_status(
542
+ trace_id, "failed", error=f"Failed to spawn background runner: {e}"
543
+ )
544
+ return {
545
+ "trace_id": trace_id,
546
+ "status": "failed",
547
+ "message": f"Error starting background task: {e}",
548
+ }
@@ -0,0 +1,15 @@
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 Server Utilities package."""