android-midscene-automation 0.2.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +35 -0
  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,468 @@
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
+ """System, environment, and device diagnostics (artemis doctor).
16
+
17
+ The command renders the same readiness probes the web console's device wizard
18
+ and the ``mobile_diagnose`` MCP tool run (:mod:`artemis.core.diagnostics`), so
19
+ the terminal, the browser and the IDE agree on one verdict. Only two rows are
20
+ CLI-specific and not engine probes: Node.js/npm and the Showcase UI build.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import asyncio
26
+ from dataclasses import dataclass
27
+ import json
28
+ import platform as sys_platform
29
+ import shutil
30
+ import subprocess
31
+ from typing import Any
32
+
33
+ from rich.console import Console
34
+ from rich.markup import escape
35
+ from rich.panel import Panel
36
+ from rich.table import Table
37
+ import typer
38
+
39
+ from artemis.config.paths import ROOT_DIR
40
+ from artemis.core.diagnostics.engine import readiness_engine
41
+ from artemis.core.diagnostics.readiness import (
42
+ Verdict,
43
+ adb_keys_corrupted,
44
+ base_verdict,
45
+ collect_readiness,
46
+ sort_by_fix_order,
47
+ )
48
+ from artemis.core.diagnostics.schema import ProbeResult, ProbeStatus
49
+ from artemis.platform import OSType, platform
50
+ from artemis.runtime.device_lock import DeviceExecutionLock
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class ExtraRow:
55
+ """A CLI-only diagnostic row that is not backed by an engine probe."""
56
+
57
+ key: str
58
+ title: str
59
+ status: str # "pass" | "missing"
60
+ status_markup: str
61
+ summary: str
62
+ detail: str
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class FixOutcome:
67
+ """Result of one ``--fix`` repair step."""
68
+
69
+ fix: str
70
+ success: bool
71
+ message: str
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Collection
76
+ # ---------------------------------------------------------------------------
77
+
78
+
79
+ async def _collect() -> list[ProbeResult]:
80
+ report, host = await collect_readiness()
81
+ return sort_by_fix_order([*report.probes, host])
82
+
83
+
84
+ async def _apply_fixes(results: list[ProbeResult]) -> list[FixOutcome]:
85
+ """Repair corrupted ADB keys and sweep stale device locks left by dead runners."""
86
+ outcomes: list[FixOutcome] = []
87
+
88
+ if adb_keys_corrupted(results):
89
+ result = await readiness_engine.heal_adb_keys()
90
+ outcomes.append(
91
+ FixOutcome(
92
+ fix="heal_adb_keys",
93
+ success=bool(result.get("success")),
94
+ message=str(result.get("message") or "")
95
+ or ("Corrupted RSA keys regenerated." if result.get("success") else "Heal failed."),
96
+ )
97
+ )
98
+ else:
99
+ outcomes.append(
100
+ FixOutcome(
101
+ fix="heal_adb_keys",
102
+ success=True,
103
+ message="ADB authentication keys are healthy; nothing to repair.",
104
+ )
105
+ )
106
+
107
+ removed = await asyncio.to_thread(DeviceExecutionLock.cleanup_stale_locks)
108
+ outcomes.append(
109
+ FixOutcome(
110
+ fix="cleanup_stale_locks",
111
+ success=True,
112
+ message=(
113
+ f"Removed {removed} stale device lock(s)/queue ticket(s)."
114
+ if removed
115
+ else "No stale device locks or queue tickets found."
116
+ ),
117
+ )
118
+ )
119
+ return outcomes
120
+
121
+
122
+ async def _diagnose(fix: bool) -> tuple[list[ProbeResult], list[FixOutcome]]:
123
+ results = await _collect()
124
+ fixes: list[FixOutcome] = []
125
+ if fix:
126
+ fixes = await _apply_fixes(results)
127
+ results = await _collect()
128
+ return results, fixes
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # CLI-only extra rows (not engine probes)
133
+ # ---------------------------------------------------------------------------
134
+
135
+
136
+ def _npm_row() -> ExtraRow:
137
+ npm_path = shutil.which("npm")
138
+ if npm_path:
139
+ return ExtraRow(
140
+ key="nodejs_npm",
141
+ title="Node.js / npm",
142
+ status="pass",
143
+ status_markup="[bold green]✔ Installed[/bold green]",
144
+ summary="Installed",
145
+ detail=f"{npm_path} (Ready to build Showcase UI)",
146
+ )
147
+ hint = (
148
+ "winget install OpenJS.NodeJS.LTS"
149
+ if platform.os_type == OSType.WINDOWS
150
+ else "brew install node"
151
+ if platform.os_type == OSType.MACOS
152
+ else "sudo apt-get install -y nodejs npm"
153
+ )
154
+ return ExtraRow(
155
+ key="nodejs_npm",
156
+ title="Node.js / npm",
157
+ status="missing",
158
+ status_markup="[dim]⚪ Optional[/dim]",
159
+ summary="Not Found",
160
+ detail=f"Not found in PATH. ({hint})",
161
+ )
162
+
163
+
164
+ def _showcase_row() -> ExtraRow:
165
+ from artemis.resources import get_bundled_showcase_dist
166
+
167
+ base_dist = ROOT_DIR / "apps" / "showcase_ui" / "dist"
168
+ candidates = [
169
+ base_dist / "frontend" / "browser" / "index.html",
170
+ base_dist / "browser" / "index.html",
171
+ base_dist / "frontend" / "index.html",
172
+ base_dist / "index.html",
173
+ ]
174
+ found_showcase = next((p for p in candidates if p.exists()), None)
175
+ if found_showcase is None:
176
+ bundled_showcase = get_bundled_showcase_dist()
177
+ if bundled_showcase is not None:
178
+ found_showcase = bundled_showcase / "index.html"
179
+ if found_showcase:
180
+ return ExtraRow(
181
+ key="showcase_ui",
182
+ title="Showcase UI",
183
+ status="pass",
184
+ status_markup="[bold green]✔ Compiled[/bold green]",
185
+ summary="Compiled",
186
+ detail=f"{found_showcase.parent} (Angular ready)",
187
+ )
188
+ return ExtraRow(
189
+ key="showcase_ui",
190
+ title="Showcase UI",
191
+ status="missing",
192
+ status_markup="[bold yellow]○ Not Compiled[/bold yellow]",
193
+ summary="Not Compiled",
194
+ detail="Run ./start.sh or artemis ui to auto-compile.",
195
+ )
196
+
197
+
198
+ def _helper_row(results: list[ProbeResult]) -> ExtraRow | None:
199
+ """State of the Artemis accessibility helper on the one ready, idle device.
200
+
201
+ Tasks install the helper themselves when they take a device; this row lets a
202
+ person pre-install it so the first task does not pay the install delay, and
203
+ tells them the helper is a thing they can remove.
204
+ """
205
+ from artemis.clients.screen_client_factory import resolve_backend
206
+ from artemis.runtime.helper_manager import helper_manager
207
+
208
+ if resolve_backend().value == "uiautomator":
209
+ return None
210
+ adb = next((r for r in results if r.id == "android_adb"), None)
211
+ devices = (adb.metadata.get("devices") if adb else None) or []
212
+ ready = [str(d.get("serial")) for d in devices if d.get("state") == "device"]
213
+ if len(ready) != 1:
214
+ return None
215
+ serial = ready[0]
216
+ try:
217
+ status = helper_manager.status(serial)
218
+ except (OSError, ValueError, subprocess.SubprocessError) as exc:
219
+ return ExtraRow(
220
+ key="accessibility_helper",
221
+ title="Artemis Accessibility Helper",
222
+ status="missing",
223
+ status_markup="[dim]⚪ Unknown[/dim]",
224
+ summary="Could not read",
225
+ detail=f"{serial}: {exc}",
226
+ )
227
+ healthy = status["installed"] and status["enabled"] and not status["outdated"]
228
+ if healthy and status["reachable"]:
229
+ version = status.get("installed_version")
230
+ note = " (newer than the bundled build)" if status.get("newer_than_bundled") else ""
231
+ return ExtraRow(
232
+ key="accessibility_helper",
233
+ title="Artemis Accessibility Helper",
234
+ status="pass",
235
+ status_markup="[bold green]✔ Ready[/bold green]",
236
+ summary=f"v{version} on {serial}{note}",
237
+ detail=f"Remove any time with: artemis helper uninstall --serial {serial}",
238
+ )
239
+ if not status["installed"]:
240
+ summary, why = "Not installed", "the first task on this device will install it (about 3 s)"
241
+ elif status["outdated"]:
242
+ summary = f"Outdated (v{status['installed_version']} < v{status['bundled_version']})"
243
+ why = "the next task will upgrade it"
244
+ elif not status["enabled"]:
245
+ summary, why = "Service disabled", "the next task will try to enable it"
246
+ else:
247
+ summary, why = "Not answering", "unlock the phone or reinstall with --force"
248
+ return ExtraRow(
249
+ key="accessibility_helper",
250
+ title="Artemis Accessibility Helper",
251
+ status="missing",
252
+ status_markup="[bold yellow]○ Pending[/bold yellow]",
253
+ summary=summary,
254
+ detail=f"{why}; pre-install now: artemis helper install --serial {serial}",
255
+ )
256
+
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # Rendering
260
+ # ---------------------------------------------------------------------------
261
+
262
+
263
+ def _status_markup(result: ProbeResult) -> str:
264
+ status = result.status
265
+ if status is ProbeStatus.PASS:
266
+ return "[bold green]✔ OK[/bold green]"
267
+ if status is ProbeStatus.SKIPPED:
268
+ return "[dim]○ Skipped[/dim]"
269
+ summary = escape(result.summary)
270
+ if status is ProbeStatus.WARN:
271
+ # A warning keeps its summary whether or not it blocks (the host
272
+ # probe's warnings degrade the verdict without blocking it).
273
+ return f"[bold yellow]⚠ {summary}[/bold yellow]"
274
+ if not result.is_blocker:
275
+ return "[dim]⚪ Optional[/dim]"
276
+ return f"[bold red]✖ {summary}[/bold red]"
277
+
278
+
279
+ def _action_lines(result: ProbeResult) -> list[str]:
280
+ """One plain-text line per remediation; ``&&`` chains split for PowerShell 5.1."""
281
+ lines: list[str] = []
282
+ for action in result.actions:
283
+ payload = action.payload.strip()
284
+ if not payload:
285
+ continue
286
+ if action.action_type == "command":
287
+ for part in payload.split("&&"):
288
+ if part.strip():
289
+ lines.append(f"Run: {part.strip()}")
290
+ else:
291
+ lines.append(payload)
292
+ return lines
293
+
294
+
295
+ def _details_markup(result: ProbeResult) -> str:
296
+ # Only summary/description/actions are ever rendered. Probe metadata can
297
+ # carry raw credentials (the console prefills its settings form from it)
298
+ # and must never reach a terminal or a log.
299
+ parts = [escape(result.description)]
300
+ if result.status is not ProbeStatus.PASS:
301
+ for line in _action_lines(result):
302
+ if line.startswith("Run: "):
303
+ parts.append(f"[bold cyan]Run:[/bold cyan] {escape(line[len('Run: ') :])}")
304
+ else:
305
+ parts.append(escape(line))
306
+ return "\n".join(parts)
307
+
308
+
309
+ def _render_table(results: list[ProbeResult], extras: list[ExtraRow]) -> Table:
310
+ table = Table(show_header=True, header_style="bold magenta")
311
+ table.add_column("Component", style="dim", min_width=22, max_width=30)
312
+ table.add_column("Status", min_width=12, max_width=28)
313
+ table.add_column("Details & Recommendations", overflow="fold")
314
+ for result in results:
315
+ table.add_row(escape(result.title), _status_markup(result), _details_markup(result))
316
+ for extra in extras:
317
+ table.add_row(extra.title, extra.status_markup, escape(extra.detail))
318
+ return table
319
+
320
+
321
+ def _render_fixes(console: Console, fixes: list[FixOutcome]) -> None:
322
+ lines = []
323
+ for outcome in fixes:
324
+ mark = "[bold green]✔[/bold green]" if outcome.success else "[bold red]✖[/bold red]"
325
+ lines.append(f"{mark} {escape(outcome.fix)}: {escape(outcome.message)}")
326
+ console.print(Panel("\n".join(lines), title="Repairs (--fix)", expand=False))
327
+ console.print()
328
+
329
+
330
+ def _render_footer(console: Console, verdict: Verdict, results: list[ProbeResult]) -> None:
331
+ if verdict == "ready":
332
+ console.print(
333
+ Panel(
334
+ "[bold green]🎉 All system checks passed![/bold green]\n\n"
335
+ "Run your first automation task now:\n"
336
+ ' [bold cyan]artemis run "Open Settings and check Battery level"[/bold cyan]',
337
+ title="Status: Ready",
338
+ expand=False,
339
+ )
340
+ )
341
+ return
342
+
343
+ if verdict == "degraded":
344
+ console.print(
345
+ Panel(
346
+ "[bold green]Required checks passed.[/bold green] Optional components are "
347
+ "missing; tasks can run, but some features (video replay, OCR) are off.\n\n"
348
+ "Run your first automation task now:\n"
349
+ ' [bold cyan]artemis run "Open Settings and check Battery level"[/bold cyan]',
350
+ title="Status: Ready (optional gaps)",
351
+ expand=False,
352
+ )
353
+ )
354
+ return
355
+
356
+ failing = [r.title for r in results if r.is_blocker and r.status is not ProbeStatus.PASS]
357
+ tips = [
358
+ "[bold yellow]💡 Blocking issues:[/bold yellow] " + escape(", ".join(failing)),
359
+ "",
360
+ "• API keys & device setup: run [bold cyan]artemis init[/bold cyan].",
361
+ "• Follow the [bold cyan]Run:[/bold cyan] lines above, then re-run "
362
+ "[bold cyan]artemis doctor[/bold cyan].",
363
+ "• Using an IDE (Claude Code, Cursor, ...)? Ask it to call "
364
+ "[bold cyan]mobile_diagnose[/bold cyan] - it runs these same checks and can "
365
+ "apply safe fixes from the assistant.",
366
+ ]
367
+ if adb_keys_corrupted(results):
368
+ tips.append(
369
+ "• Corrupted ADB keys detected: run [bold cyan]artemis doctor --fix[/bold cyan]."
370
+ )
371
+ console.print(Panel("\n".join(tips), title="Action Required", expand=False))
372
+
373
+
374
+ def _json_document(
375
+ results: list[ProbeResult],
376
+ extras: list[ExtraRow],
377
+ fixes: list[FixOutcome],
378
+ verdict: Verdict,
379
+ ) -> dict[str, Any]:
380
+ # Metadata is intentionally omitted: it may hold raw API keys.
381
+ checks = [
382
+ {
383
+ "id": r.id,
384
+ "title": r.title,
385
+ "status": r.status.value,
386
+ "required": r.is_blocker,
387
+ "summary": r.summary,
388
+ "detail": r.description,
389
+ "fix": []
390
+ if r.status is ProbeStatus.PASS
391
+ else [
392
+ {"type": a.action_type, "label": a.label, "payload": a.payload} for a in r.actions
393
+ ],
394
+ }
395
+ for r in results
396
+ ]
397
+ return {
398
+ "verdict": verdict,
399
+ "checks": checks,
400
+ "extras": {
401
+ e.key: {
402
+ "title": e.title,
403
+ "status": e.status,
404
+ "summary": e.summary,
405
+ "detail": e.detail,
406
+ }
407
+ for e in extras
408
+ },
409
+ "fixes": [{"fix": f.fix, "success": f.success, "message": f.message} for f in fixes],
410
+ }
411
+
412
+
413
+ # ---------------------------------------------------------------------------
414
+ # Command
415
+ # ---------------------------------------------------------------------------
416
+
417
+
418
+ def doctor_command(
419
+ fix: bool = typer.Option(
420
+ False,
421
+ "--fix",
422
+ "-f",
423
+ help=(
424
+ "Automatically repair fixable environment issues (corrupted ADB authentication "
425
+ "keys, stale device locks left by crashed runners)."
426
+ ),
427
+ ),
428
+ json_output: bool = typer.Option(
429
+ False,
430
+ "--json",
431
+ help="Print the diagnosis as a JSON document instead of a table.",
432
+ ),
433
+ ) -> None:
434
+ """Run diagnostics to inspect system dependencies, device connectivity, and configuration."""
435
+ results, fixes = asyncio.run(_diagnose(fix))
436
+ extras = [_npm_row(), _showcase_row()]
437
+ helper_row = _helper_row(results)
438
+ if helper_row is not None:
439
+ extras.append(helper_row)
440
+ verdict = base_verdict(results)
441
+
442
+ if json_output:
443
+ typer.echo(
444
+ json.dumps(
445
+ _json_document(results, extras, fixes, verdict), indent=2, ensure_ascii=False
446
+ )
447
+ )
448
+ else:
449
+ console = Console()
450
+ console.print()
451
+ os_name = sys_platform.system()
452
+ arch_name = sys_platform.machine()
453
+ console.print(
454
+ Panel(
455
+ f"[bold cyan]☕ Artemis System & Environment Doctor[/bold cyan]\n"
456
+ f"[dim]Platform: {os_name} ({arch_name}) | PAL: {platform.os_type.value}[/dim]",
457
+ expand=False,
458
+ )
459
+ )
460
+ if fixes:
461
+ _render_fixes(console, fixes)
462
+ console.print(_render_table(results, extras))
463
+ console.print()
464
+ _render_footer(console, verdict, results)
465
+ console.print()
466
+
467
+ if verdict == "blocked":
468
+ raise typer.Exit(code=1)
@@ -0,0 +1,226 @@
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
+ """``artemis helper``: manage the Accessibility Helper APK on attached devices.
16
+
17
+ Tasks provision the helper themselves when they take a device, so these
18
+ commands exist for the cases the automatic path deliberately leaves alone:
19
+ inspecting a device, forcing a reinstall, and removing the helper (which is
20
+ never done automatically).
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ from typing import Annotated
27
+
28
+ from rich.console import Console
29
+ from rich.table import Table
30
+ import typer
31
+
32
+ from artemis.runtime.device_pool import device_pool
33
+ from artemis.runtime.helper_manager import helper_manager
34
+ from third_party.mobile_use.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+ helper_app = typer.Typer(help="Install, inspect, or remove the Artemis Accessibility Helper APK.")
38
+
39
+ SerialOption = Annotated[
40
+ str | None,
41
+ typer.Option("--serial", "-s", help="Device serial. Defaults to the only ready device."),
42
+ ]
43
+
44
+
45
+ def _resolve_serial(serial: str | None) -> str:
46
+ if serial:
47
+ return serial
48
+ ready = [d.serial for d in device_pool.get_ready_devices()]
49
+ if len(ready) == 1:
50
+ return ready[0]
51
+ if not ready:
52
+ typer.secho("No authorized device is attached.", fg=typer.colors.RED)
53
+ else:
54
+ typer.secho(
55
+ "Several devices are ready; pass --serial, or --all to target every idle one: "
56
+ + ", ".join(ready),
57
+ fg=typer.colors.RED,
58
+ )
59
+ raise typer.Exit(code=1)
60
+
61
+
62
+ def _idle_ready_serials() -> list[str]:
63
+ """Every authorized device that no Artemis task currently holds."""
64
+ return [d.serial for d in device_pool.get_ready_devices() if not d.is_busy]
65
+
66
+
67
+ def _bool(value: object) -> str:
68
+ return "[green]yes[/green]" if value else "[red]no[/red]"
69
+
70
+
71
+ @helper_app.command("status")
72
+ def helper_status(
73
+ serial: SerialOption = None,
74
+ as_json: Annotated[bool, typer.Option("--json", help="Print raw JSON.")] = False,
75
+ ) -> None:
76
+ """Show install, version, service and tunnel state of the helper on a device."""
77
+ target = _resolve_serial(serial)
78
+ status = helper_manager.status(target)
79
+ if as_json:
80
+ typer.echo(json.dumps(status, indent=2))
81
+ return
82
+ table = Table(title=f"Accessibility helper on {target}")
83
+ table.add_column("Field", style="cyan")
84
+ table.add_column("Value")
85
+ table.add_row("Installed", _bool(status["installed"]))
86
+ version = str(status["installed_version"])
87
+ if status.get("newer_than_bundled"):
88
+ version += f" [yellow](newer than the bundled v{status['bundled_version']}; not downgraded)[/yellow]"
89
+ elif status.get("outdated"):
90
+ version += f" [yellow](bundled is v{status['bundled_version']}; the next task upgrades it)[/yellow]"
91
+ table.add_row("Installed version", version)
92
+ table.add_row("Bundled version", str(status["bundled_version"]))
93
+ table.add_row("Service enabled", _bool(status["enabled"]))
94
+ tunnel = {
95
+ "session": f"this process, host port {status['forward_port']}",
96
+ "shared": f"another Artemis process, host port {status['forward_port']}",
97
+ "probe": "none (probed through a temporary forward)",
98
+ None: "none",
99
+ }[status.get("tunnel")]
100
+ table.add_row("Tunnel", tunnel)
101
+ table.add_row("Service answering", _bool(status["reachable"]))
102
+ if status.get("reachable"):
103
+ protocol = status.get("protocol_version")
104
+ proto_text = str(protocol)
105
+ if not status.get("protocol_supported"):
106
+ proto_text += " [red](too old for this host; reinstall with --force)[/red]"
107
+ table.add_row("Protocol", proto_text)
108
+ table.add_row("Session token pushed", _bool(status.get("token_set")))
109
+ table.add_row("Auto-install by tasks", _bool(status.get("auto_install", True)))
110
+ table.add_row("Transport id", str(status["transport_id"]))
111
+ Console().print(table)
112
+
113
+
114
+ def _install_one(target: str, force: bool) -> bool:
115
+ def on_event(event: str, details: dict) -> None:
116
+ if event == "installing":
117
+ typer.echo(f"{target}: installing v{details.get('version_name')}...")
118
+ elif event == "upgrading":
119
+ typer.echo(
120
+ f"{target}: upgrading v{details.get('from_version')} -> "
121
+ f"v{details.get('to_version')}..."
122
+ )
123
+
124
+ result = helper_manager.provision(target, force=force, on_event=on_event)
125
+ if result.ok:
126
+ typer.secho(
127
+ f"{target}: {result.action} (version {result.installed_version}, service enabled).",
128
+ fg=typer.colors.GREEN,
129
+ )
130
+ return True
131
+ typer.secho(f"{target}: {result.action}: {result.error}", fg=typer.colors.RED)
132
+ return False
133
+
134
+
135
+ @helper_app.command("install")
136
+ def helper_install(
137
+ serial: SerialOption = None,
138
+ force: Annotated[bool, typer.Option("--force", help="Reinstall even when up to date.")] = False,
139
+ all_devices: Annotated[
140
+ bool, typer.Option("--all", help="Every authorized device that no task is using.")
141
+ ] = False,
142
+ ) -> None:
143
+ """Install or upgrade the helper to the bundled version and enable its service."""
144
+ if all_devices:
145
+ targets = _idle_ready_serials()
146
+ if not targets:
147
+ typer.secho("No idle, authorized device is attached.", fg=typer.colors.RED)
148
+ raise typer.Exit(code=1)
149
+ else:
150
+ targets = [_resolve_serial(serial)]
151
+ outcomes = [_install_one(target, force) for target in targets]
152
+ if not all(outcomes):
153
+ raise typer.Exit(code=1)
154
+
155
+
156
+ @helper_app.command("uninstall")
157
+ def helper_uninstall(
158
+ serial: SerialOption = None,
159
+ yes: Annotated[bool, typer.Option("--yes", "-y", help="Do not ask for confirmation.")] = False,
160
+ ) -> None:
161
+ """Disable the service and remove the helper package from a device."""
162
+ target = _resolve_serial(serial)
163
+ if not yes and not typer.confirm(f"Remove the Artemis accessibility helper from {target}?"):
164
+ raise typer.Exit(code=0)
165
+ if helper_manager.uninstall(target):
166
+ typer.secho(f"{target}: helper removed.", fg=typer.colors.GREEN)
167
+ return
168
+ typer.secho(f"{target}: uninstall failed (is the package installed?).", fg=typer.colors.RED)
169
+ raise typer.Exit(code=1)
170
+
171
+
172
+ @helper_app.command("parity")
173
+ def helper_parity(
174
+ serial: SerialOption = None,
175
+ rounds: Annotated[int, typer.Option("--rounds", help="Timed dumps per backend.")] = 3,
176
+ as_json: Annotated[bool, typer.Option("--json", help="Print raw JSON.")] = False,
177
+ ) -> None:
178
+ """Dump the current screen with the helper and with UIAutomator2 and compare them.
179
+
180
+ The two backends must describe the same visible screen: every labelled
181
+ UIAutomator element should have a helper element with the same label at
182
+ the same place, the helper must emit no off-screen or negative bounds, and
183
+ its dump should not be slower. Run it on a busy screen (a scrolled list, a
184
+ dialog over an app) to catch regressions the unit tests cannot see.
185
+ """
186
+ from artemis.core.diagnostics.hierarchy_parity import compare_backends
187
+
188
+ target = _resolve_serial(serial)
189
+ report = compare_backends(target, rounds=max(1, rounds))
190
+ if as_json:
191
+ typer.echo(json.dumps(report, indent=2, ensure_ascii=False))
192
+ raise typer.Exit(code=0 if report["ok"] else 1)
193
+
194
+ table = Table(title=f"Hierarchy parity on {target}")
195
+ table.add_column("Metric", style="cyan")
196
+ table.add_column("Helper")
197
+ table.add_column("UIAutomator2")
198
+ h, u = report["helper"], report["uiautomator"]
199
+ table.add_row("Dump time, median", f"{h['median_ms']:.0f} ms", f"{u['median_ms']:.0f} ms")
200
+ table.add_row("Nodes", str(h["nodes"]), str(u["nodes"]))
201
+ table.add_row("Labelled nodes (text / desc)", str(h["labelled"]), str(u["labelled"]))
202
+ table.add_row("Negative bounds", str(h["negative_bounds"]), str(u["negative_bounds"]))
203
+ table.add_row("Off-screen bounds", str(h["offscreen_bounds"]), str(u["offscreen_bounds"]))
204
+ table.add_row("Foreground package", str(h["package"]), str(u["package"]))
205
+ Console().print(table)
206
+
207
+ m = report["match"]
208
+ typer.echo(
209
+ f"UIAutomator labels found in helper dump: {m['uiautomator_in_helper']}/{m['uiautomator_labelled']}"
210
+ f" ({m['recall']:.0%}); helper labels found in UIAutomator dump: "
211
+ f"{m['helper_in_uiautomator']}/{m['helper_labelled']} ({m['precision']:.0%})"
212
+ )
213
+ for label in m["missing_in_helper"][:10]:
214
+ typer.echo(f" helper lacks: {label}")
215
+ for label in m["extra_in_helper"][:10]:
216
+ typer.echo(f" helper adds: {label}")
217
+ for problem in report["problems"]:
218
+ typer.secho(f" ! {problem}", fg=typer.colors.RED)
219
+ typer.secho(
220
+ "PARITY OK" if report["ok"] else "PARITY FAILED",
221
+ fg=typer.colors.GREEN if report["ok"] else typer.colors.RED,
222
+ )
223
+ raise typer.Exit(code=0 if report["ok"] else 1)
224
+
225
+
226
+ __all__ = ["helper_app"]