android-midscene-automation 0.2.3 → 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 +11 -1
  2. package/README.md +63 -1
  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 +10 -8
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +13 -1
  507. package/src/App.vue +72 -23
  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 +369 -186
  525. package/src/types.ts +1 -1
@@ -0,0 +1,90 @@
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 Diagnostics & System Readiness Package."""
16
+
17
+ from artemis.core.diagnostics.adb_keys import (
18
+ AdbKeyStatus,
19
+ get_adb_key_paths,
20
+ heal_adb_keys,
21
+ inspect_adb_keys,
22
+ )
23
+ from artemis.core.diagnostics.device_smoke import smoke_test_device
24
+ from artemis.core.diagnostics.emulator_manager import (
25
+ EmulatorLaunchStage,
26
+ EmulatorLaunchState,
27
+ emulator_manager,
28
+ )
29
+ from artemis.core.diagnostics.engine import ReadinessEngine, readiness_engine
30
+ from artemis.core.diagnostics.probes.adb_probe import AdbDeviceProbe
31
+ from artemis.core.diagnostics.probes.base import BaseProbe
32
+ from artemis.core.diagnostics.probes.credentials_probe import (
33
+ LLMCredentialsProbe,
34
+ VisionOCRProbe,
35
+ )
36
+ from artemis.core.diagnostics.probes.host_probe import IntegrationHostProbe
37
+ from artemis.core.diagnostics.probes.runtime_probe import (
38
+ PythonRuntimeProbe,
39
+ SystemConfigProbe,
40
+ )
41
+ from artemis.core.diagnostics.probes.toolchain_probe import ToolchainProbe
42
+ from artemis.core.diagnostics.readiness import (
43
+ CHECK_ORDER,
44
+ Verdict,
45
+ adb_keys_corrupted,
46
+ base_verdict,
47
+ collect_readiness,
48
+ sort_by_fix_order,
49
+ )
50
+ from artemis.core.diagnostics.schema import (
51
+ DeviceInfo,
52
+ ProbeAction,
53
+ ProbeCategory,
54
+ ProbeResult,
55
+ ProbeStatus,
56
+ SystemReadinessReport,
57
+ )
58
+
59
+ __all__ = [
60
+ "ReadinessEngine",
61
+ "readiness_engine",
62
+ "emulator_manager",
63
+ "EmulatorLaunchStage",
64
+ "EmulatorLaunchState",
65
+ "BaseProbe",
66
+ "IntegrationHostProbe",
67
+ "PythonRuntimeProbe",
68
+ "SystemConfigProbe",
69
+ "AdbDeviceProbe",
70
+ "LLMCredentialsProbe",
71
+ "VisionOCRProbe",
72
+ "ToolchainProbe",
73
+ "DeviceInfo",
74
+ "ProbeAction",
75
+ "ProbeCategory",
76
+ "ProbeResult",
77
+ "ProbeStatus",
78
+ "SystemReadinessReport",
79
+ "AdbKeyStatus",
80
+ "get_adb_key_paths",
81
+ "inspect_adb_keys",
82
+ "heal_adb_keys",
83
+ "smoke_test_device",
84
+ "CHECK_ORDER",
85
+ "Verdict",
86
+ "collect_readiness",
87
+ "base_verdict",
88
+ "adb_keys_corrupted",
89
+ "sort_by_fix_order",
90
+ ]
@@ -0,0 +1,270 @@
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
+ """ADB Authentication Key Diagnostic and Automated Self-Healing Module.
16
+
17
+ Android 4.2.2+ requires RSA authentication keys (~/.android/adbkey) for USB debugging.
18
+ When key files become 0-bytes or corrupted (due to aborted writes, touch commands,
19
+ or disk sync issues), ADB fails to send the public key, causing connected devices
20
+ to silently remain in an 'unauthorized' state without ever displaying the authorization prompt.
21
+
22
+ This module provides detection and self-healing for corrupted ADB keys.
23
+ """
24
+
25
+ from dataclasses import asdict, dataclass
26
+ import os
27
+ from pathlib import Path
28
+ import shutil
29
+ import subprocess
30
+ import time
31
+ from typing import Any
32
+
33
+ from artemis.toolchain import toolchain
34
+ from third_party.mobile_use.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+
38
+
39
+ @dataclass
40
+ class AdbKeyStatus:
41
+ """Status details for ADB authentication keys."""
42
+
43
+ is_valid: bool
44
+ is_corrupted: bool
45
+ exists: bool
46
+ key_path: str
47
+ pub_key_path: str
48
+ key_size_bytes: int = 0
49
+ pub_key_size_bytes: int = 0
50
+ error_reason: str | None = None
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ return asdict(self)
54
+
55
+
56
+ def get_adb_key_paths() -> tuple[Path, Path]:
57
+ """Resolve the paths to the active ADB private and public key files."""
58
+ adb_vendor_keys = os.environ.get("ADB_VENDOR_KEYS")
59
+ if adb_vendor_keys:
60
+ custom_priv = Path(adb_vendor_keys.split(os.pathsep)[0])
61
+ custom_pub = Path(f"{custom_priv}.pub")
62
+ return custom_priv, custom_pub
63
+
64
+ android_dir = Path.home() / ".android"
65
+ return android_dir / "adbkey", android_dir / "adbkey.pub"
66
+
67
+
68
+ def inspect_adb_keys() -> AdbKeyStatus:
69
+ """Inspect ADB authentication RSA key files for corruption or invalid state."""
70
+ priv_path, pub_path = get_adb_key_paths()
71
+
72
+ if not priv_path.exists() and not pub_path.exists():
73
+ # Fresh environment: ADB will automatically generate keys on first start
74
+ return AdbKeyStatus(
75
+ is_valid=True,
76
+ is_corrupted=False,
77
+ exists=False,
78
+ key_path=str(priv_path),
79
+ pub_key_path=str(pub_path),
80
+ key_size_bytes=0,
81
+ pub_key_size_bytes=0,
82
+ error_reason=None,
83
+ )
84
+
85
+ priv_size = priv_path.stat().st_size if priv_path.exists() else 0
86
+ pub_size = pub_path.stat().st_size if pub_path.exists() else 0
87
+
88
+ # 1. Check for 0-byte or truncated private key file
89
+ if priv_path.exists():
90
+ if priv_size == 0:
91
+ return AdbKeyStatus(
92
+ is_valid=False,
93
+ is_corrupted=True,
94
+ exists=True,
95
+ key_path=str(priv_path),
96
+ pub_key_path=str(pub_path),
97
+ key_size_bytes=0,
98
+ pub_key_size_bytes=pub_size,
99
+ error_reason="Private key file is 0 bytes (empty). ADB cannot authenticate devices.",
100
+ )
101
+ if priv_size < 100:
102
+ return AdbKeyStatus(
103
+ is_valid=False,
104
+ is_corrupted=True,
105
+ exists=True,
106
+ key_path=str(priv_path),
107
+ pub_key_path=str(pub_path),
108
+ key_size_bytes=priv_size,
109
+ pub_key_size_bytes=pub_size,
110
+ error_reason=f"Private key file is suspiciously small ({priv_size} bytes).",
111
+ )
112
+
113
+ # Validate PEM header structure
114
+ try:
115
+ content = priv_path.read_text(encoding="utf-8", errors="replace")
116
+ if "BEGIN" not in content or "PRIVATE KEY" not in content:
117
+ return AdbKeyStatus(
118
+ is_valid=False,
119
+ is_corrupted=True,
120
+ exists=True,
121
+ key_path=str(priv_path),
122
+ pub_key_path=str(pub_path),
123
+ key_size_bytes=priv_size,
124
+ pub_key_size_bytes=pub_size,
125
+ error_reason="Private key does not contain a valid PEM private key header.",
126
+ )
127
+ except Exception as e:
128
+ return AdbKeyStatus(
129
+ is_valid=False,
130
+ is_corrupted=True,
131
+ exists=True,
132
+ key_path=str(priv_path),
133
+ pub_key_path=str(pub_path),
134
+ key_size_bytes=priv_size,
135
+ pub_key_size_bytes=pub_size,
136
+ error_reason=f"Cannot read private key: {e}",
137
+ )
138
+
139
+ # 2. Check for 0-byte public key if it exists
140
+ if pub_path.exists() and pub_size == 0:
141
+ return AdbKeyStatus(
142
+ is_valid=False,
143
+ is_corrupted=True,
144
+ exists=True,
145
+ key_path=str(priv_path),
146
+ pub_key_path=str(pub_path),
147
+ key_size_bytes=priv_size,
148
+ pub_key_size_bytes=0,
149
+ error_reason="Public key file (adbkey.pub) is 0 bytes (empty).",
150
+ )
151
+
152
+ return AdbKeyStatus(
153
+ is_valid=True,
154
+ is_corrupted=False,
155
+ exists=True,
156
+ key_path=str(priv_path),
157
+ pub_key_path=str(pub_path),
158
+ key_size_bytes=priv_size,
159
+ pub_key_size_bytes=pub_size,
160
+ error_reason=None,
161
+ )
162
+
163
+
164
+ def heal_adb_keys(adb_path: str | None = None, force: bool = False) -> dict[str, Any]:
165
+ """Auto-heal corrupted ADB authentication keys.
166
+
167
+ If keys are corrupted (or if `force=True`), this function:
168
+ 1. Backs up or removes corrupted key files.
169
+ 2. Restarts the ADB server to cleanly generate a fresh RSA 2048-bit key pair.
170
+ 3. Verifies that the new key pair is healthy and valid.
171
+ """
172
+ status = inspect_adb_keys()
173
+ if not status.is_corrupted and not force:
174
+ return {
175
+ "success": True,
176
+ "repaired": False,
177
+ "message": "ADB keys are healthy, no repair required.",
178
+ "status": status.to_dict(),
179
+ }
180
+
181
+ priv_path, pub_path = get_adb_key_paths()
182
+ priv_path.parent.mkdir(parents=True, exist_ok=True)
183
+
184
+ # Backup corrupted non-empty files before removal
185
+ timestamp = int(time.time())
186
+ if priv_path.exists() and priv_path.stat().st_size > 0:
187
+ backup_path = priv_path.with_name(f"adbkey.corrupted.{timestamp}")
188
+ try:
189
+ shutil.copy2(priv_path, backup_path)
190
+ logger.info(f"Backed up corrupted adbkey to {backup_path}")
191
+ except Exception as e:
192
+ logger.warning(f"Could not backup corrupted adbkey: {e}")
193
+
194
+ # Remove corrupted key files
195
+ try:
196
+ if priv_path.exists():
197
+ priv_path.unlink(missing_ok=True)
198
+ if pub_path.exists():
199
+ pub_path.unlink(missing_ok=True)
200
+ except Exception as e:
201
+ logger.error(f"Failed to remove corrupted adb keys: {e}")
202
+ return {
203
+ "success": False,
204
+ "repaired": False,
205
+ "message": f"Failed to remove corrupted ADB key files: {e}",
206
+ "status": status.to_dict(),
207
+ }
208
+
209
+ # Resolve ADB executable
210
+ resolved_adb = adb_path or toolchain.resolve("adb") or shutil.which("adb") or "adb"
211
+
212
+ # Restart ADB server to trigger key generation
213
+ try:
214
+ clean_env = os.environ.copy()
215
+ clean_env.pop("ADB_SERVER_SOCKET", None)
216
+ subprocess.run(
217
+ [resolved_adb, "kill-server"],
218
+ stdout=subprocess.DEVNULL,
219
+ stderr=subprocess.DEVNULL,
220
+ timeout=5,
221
+ env=clean_env,
222
+ )
223
+ subprocess.run(
224
+ [resolved_adb, "start-server"],
225
+ stdout=subprocess.DEVNULL,
226
+ stderr=subprocess.DEVNULL,
227
+ timeout=5,
228
+ env=clean_env,
229
+ )
230
+ # Execute adb devices to trigger initial handshake & key material generation if needed
231
+ subprocess.run(
232
+ [resolved_adb, "devices"],
233
+ stdout=subprocess.DEVNULL,
234
+ stderr=subprocess.DEVNULL,
235
+ timeout=5,
236
+ env=clean_env,
237
+ )
238
+ except Exception as e:
239
+ logger.error(f"Error restarting ADB server during key healing: {e}")
240
+ return {
241
+ "success": False,
242
+ "repaired": False,
243
+ "message": f"Failed to restart ADB server: {e}",
244
+ "status": status.to_dict(),
245
+ }
246
+
247
+ # Re-inspect to verify health
248
+ new_status = inspect_adb_keys()
249
+ if new_status.is_valid and new_status.key_size_bytes > 0:
250
+ logger.info("Successfully healed corrupted ADB keys.")
251
+ return {
252
+ "success": True,
253
+ "repaired": True,
254
+ "message": "Corrupted ADB keys were successfully repaired and regenerated.",
255
+ "status": new_status.to_dict(),
256
+ }
257
+ elif new_status.is_valid and not new_status.is_corrupted:
258
+ return {
259
+ "success": True,
260
+ "repaired": True,
261
+ "message": "Corrupted ADB keys cleared. Fresh keys will be created on first device connection.",
262
+ "status": new_status.to_dict(),
263
+ }
264
+ else:
265
+ return {
266
+ "success": False,
267
+ "repaired": False,
268
+ "message": f"Key healing completed, but keys remain invalid: {new_status.error_reason}",
269
+ "status": new_status.to_dict(),
270
+ }
@@ -0,0 +1,330 @@
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
+ """Process-wide ADB server endpoint discovery and activation."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+ import os
21
+ from pathlib import Path
22
+ import re
23
+ import subprocess
24
+ from typing import Any, Callable
25
+
26
+ from dotenv import set_key
27
+
28
+ from artemis.config import settings
29
+ from artemis.config.paths import ROOT_DIR, get_app_dir
30
+ from artemis.runtime.adb_endpoint import (
31
+ AdbEndpoint,
32
+ AdbSession,
33
+ InvalidAdbEndpoint,
34
+ current_adb_endpoint,
35
+ )
36
+ from artemis.toolchain import toolchain
37
+ from third_party.mobile_use.utils.logger import get_logger
38
+
39
+ logger = get_logger(__name__)
40
+
41
+ AdbServerEndpoint = AdbEndpoint
42
+ InvalidAdbServerEndpoint = InvalidAdbEndpoint
43
+
44
+
45
+ class AdbServerConnectionManager:
46
+ """Validate, probe, activate, and persist the selected ADB server endpoint."""
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ adb_resolver: Callable[[], str | None] | None = None,
52
+ env_files: list[Path] | None = None,
53
+ ) -> None:
54
+ self._adb_resolver = adb_resolver or (lambda: toolchain.resolve("adb"))
55
+ self._env_files = env_files
56
+ self._activation_lock = asyncio.Lock()
57
+
58
+ @staticmethod
59
+ def validate_endpoint(host: str, port: int) -> AdbServerEndpoint:
60
+ return AdbEndpoint.create(host, port)
61
+
62
+ def current_endpoint(self) -> AdbServerEndpoint:
63
+ return current_adb_endpoint()
64
+
65
+ def status(self) -> dict[str, Any]:
66
+ return {"endpoint": self.current_endpoint().to_dict()}
67
+
68
+ def synchronize_environment(self) -> AdbServerEndpoint:
69
+ """Align subprocess and Python ADB clients with the configured endpoint."""
70
+ socket_endpoint = self._endpoint_from_socket(os.environ.get("ADB_SERVER_SOCKET"))
71
+ has_explicit_host = bool(os.environ.get("ADB_HOST") or os.environ.get("ADB_PORT"))
72
+ endpoint = (
73
+ socket_endpoint
74
+ if socket_endpoint is not None and not has_explicit_host
75
+ else self.current_endpoint()
76
+ )
77
+ self._activate(endpoint)
78
+ return endpoint
79
+
80
+ async def connect(
81
+ self,
82
+ host: str,
83
+ port: int,
84
+ *,
85
+ persist: bool = True,
86
+ ) -> dict[str, Any]:
87
+ """Probe an endpoint, then make it the process-wide ADB server on success."""
88
+ endpoint = self.validate_endpoint(host, port)
89
+ probe_result = await self.probe(host, port)
90
+ if not probe_result["success"]:
91
+ return probe_result
92
+
93
+ persisted = False
94
+ persistence_error: str | None = None
95
+ async with self._activation_lock:
96
+ self._activate(endpoint)
97
+ if persist:
98
+ try:
99
+ await asyncio.to_thread(self._persist, endpoint)
100
+ persisted = True
101
+ except Exception as exc:
102
+ persistence_error = str(exc)
103
+ logger.warning(f"Unable to persist ADB server endpoint: {exc}")
104
+
105
+ message = probe_result["message"]
106
+ if persist and not persisted:
107
+ message = (
108
+ f"{message} The connection is active for this session, but Artemis could not "
109
+ "save it for the next launch."
110
+ )
111
+
112
+ return {
113
+ **probe_result,
114
+ "endpoint": endpoint.to_dict(),
115
+ "message": message,
116
+ "persisted": persisted,
117
+ "persistence_error": persistence_error,
118
+ }
119
+
120
+ async def probe(self, host: str, port: int) -> dict[str, Any]:
121
+ """Test an endpoint without changing the process preference."""
122
+ endpoint = self.validate_endpoint(host, port)
123
+ return await asyncio.to_thread(self._probe_sync, endpoint)
124
+
125
+ async def use_local_server(self, *, persist: bool = True) -> dict[str, Any]:
126
+ """Restore the standard local ADB server without killing any remote daemon."""
127
+ endpoint = AdbEndpoint.local()
128
+ persisted = False
129
+ persistence_error: str | None = None
130
+ async with self._activation_lock:
131
+ self._activate(endpoint)
132
+ if persist:
133
+ try:
134
+ await asyncio.to_thread(self._persist, endpoint)
135
+ persisted = True
136
+ except Exception as exc:
137
+ persistence_error = str(exc)
138
+ logger.warning(f"Unable to persist local ADB server endpoint: {exc}")
139
+
140
+ message = "Artemis is now using the local ADB server."
141
+ if persist and not persisted:
142
+ message += " The setting could not be saved for the next launch."
143
+ return {
144
+ "success": True,
145
+ "endpoint": endpoint.to_dict(),
146
+ "persisted": persisted,
147
+ "persistence_error": persistence_error,
148
+ "message": message,
149
+ }
150
+
151
+ def _probe_sync(self, endpoint: AdbServerEndpoint) -> dict[str, Any]:
152
+ adb_path = self._adb_resolver()
153
+ if not adb_path:
154
+ return {
155
+ "success": False,
156
+ "error_code": "adb_not_found",
157
+ "message": "Android Platform Tools (adb) is not installed or not on PATH.",
158
+ "endpoint": endpoint.to_dict(),
159
+ "devices": [],
160
+ }
161
+
162
+ session = AdbSession(endpoint, adb_path=adb_path)
163
+ command = session.command(["devices", "-l"])
164
+ try:
165
+ result = subprocess.run(
166
+ command,
167
+ capture_output=True,
168
+ text=True,
169
+ encoding="utf-8",
170
+ errors="replace",
171
+ timeout=8,
172
+ env=self._probe_environment(),
173
+ )
174
+ except subprocess.TimeoutExpired:
175
+ return {
176
+ "success": False,
177
+ "error_code": "timeout",
178
+ "message": (
179
+ "The ADB server did not respond. Check that the host and port are correct "
180
+ "and accessible from this computer."
181
+ ),
182
+ "endpoint": endpoint.to_dict(),
183
+ "devices": [],
184
+ }
185
+ except OSError as exc:
186
+ logger.warning(f"Failed to execute ADB endpoint probe: {exc}")
187
+ return {
188
+ "success": False,
189
+ "error_code": "adb_execution_failed",
190
+ "message": f"Unable to run adb: {exc}",
191
+ "endpoint": endpoint.to_dict(),
192
+ "devices": [],
193
+ }
194
+
195
+ stdout = result.stdout or ""
196
+ stderr = result.stderr or ""
197
+ output = "\n".join(part.strip() for part in (stdout, stderr) if part.strip())
198
+ devices = self._parse_devices(stdout)
199
+ if result.returncode != 0:
200
+ return {
201
+ "success": False,
202
+ "error_code": "server_unreachable",
203
+ "message": self._connection_error_message(output),
204
+ "output": output,
205
+ "endpoint": endpoint.to_dict(),
206
+ "devices": [],
207
+ }
208
+
209
+ ready_devices = [device for device in devices if device["state"] == "device"]
210
+ if ready_devices:
211
+ message = (
212
+ f"Connected to {endpoint.host}:{endpoint.port} and found "
213
+ f"{len(ready_devices)} ready device{'s' if len(ready_devices) != 1 else ''}."
214
+ )
215
+ elif devices:
216
+ message = (
217
+ "The ADB server is reachable, but its devices require attention. "
218
+ "Unlock the phone and approve USB debugging if prompted."
219
+ )
220
+ else:
221
+ message = (
222
+ "The ADB server is reachable, but no devices are attached to the remote computer."
223
+ )
224
+
225
+ return {
226
+ "success": True,
227
+ "message": message,
228
+ "output": output,
229
+ "endpoint": endpoint.to_dict(),
230
+ "devices": devices,
231
+ }
232
+
233
+ @staticmethod
234
+ def _parse_devices(output: str) -> list[dict[str, str | None]]:
235
+ devices: list[dict[str, str | None]] = []
236
+ for raw_line in output.splitlines():
237
+ line = raw_line.strip()
238
+ if not line or line.lower().startswith("list of devices") or line.startswith("*"):
239
+ continue
240
+ parts = line.split()
241
+ if len(parts) < 2:
242
+ continue
243
+ metadata = {
244
+ key: value.replace("_", " ")
245
+ for token in parts[2:]
246
+ if ":" in token
247
+ for key, value in [token.split(":", 1)]
248
+ }
249
+ devices.append(
250
+ {
251
+ "serial": parts[0],
252
+ "state": parts[1],
253
+ "model": metadata.get("model"),
254
+ "product": metadata.get("product"),
255
+ }
256
+ )
257
+ return devices
258
+
259
+ @staticmethod
260
+ def _connection_error_message(output: str) -> str:
261
+ lowered = output.lower()
262
+ if "cannot connect" in lowered or "failed to connect" in lowered:
263
+ return (
264
+ "No ADB server is listening at this address. Verify that the host and port are "
265
+ "correct and accessible from this computer."
266
+ )
267
+ if "version" in lowered and "mismatch" in lowered:
268
+ return (
269
+ "The local and remote ADB versions are incompatible. Use matching Android "
270
+ "Platform Tools versions on both computers."
271
+ )
272
+ return output or "Unable to reach the ADB server endpoint."
273
+
274
+ @staticmethod
275
+ def _probe_environment() -> dict[str, str]:
276
+ environment = os.environ.copy()
277
+ environment.pop("ADB_SERVER_SOCKET", None)
278
+ return environment
279
+
280
+ @classmethod
281
+ def _endpoint_from_socket(cls, value: str | None) -> AdbServerEndpoint | None:
282
+ if not value or not value.startswith("tcp:"):
283
+ return None
284
+ address = value.removeprefix("tcp:")
285
+ if address.startswith("["):
286
+ match = re.fullmatch(r"\[([^]]+)]:(\d+)", address)
287
+ else:
288
+ match = re.fullmatch(r"(.+):(\d+)", address)
289
+ if match is None:
290
+ return None
291
+ try:
292
+ return cls.validate_endpoint(match.group(1), int(match.group(2)))
293
+ except (InvalidAdbServerEndpoint, ValueError):
294
+ return None
295
+
296
+ @staticmethod
297
+ def _activate(endpoint: AdbServerEndpoint) -> None:
298
+ settings.ADB_HOST = endpoint.host
299
+ settings.ADB_PORT = endpoint.port
300
+ endpoint.apply_to_environment()
301
+ logger.info(
302
+ f"Activated {endpoint.mode} ADB server endpoint {endpoint.host}:{endpoint.port}"
303
+ )
304
+
305
+ def _persist(self, endpoint: AdbServerEndpoint) -> None:
306
+ for env_file in self._environment_files():
307
+ env_file.parent.mkdir(parents=True, exist_ok=True)
308
+ if not env_file.exists():
309
+ env_file.touch()
310
+ set_key(str(env_file), "ADB_HOST", endpoint.host, quote_mode="never")
311
+ set_key(str(env_file), "ADB_PORT", str(endpoint.port), quote_mode="never")
312
+
313
+ def _environment_files(self) -> list[Path]:
314
+ if self._env_files is not None:
315
+ candidates = self._env_files
316
+ else:
317
+ candidates = [ROOT_DIR / ".env", get_app_dir() / ".env"]
318
+
319
+ unique_files: list[Path] = []
320
+ seen: set[Path] = set()
321
+ for candidate in candidates:
322
+ resolved = candidate.resolve()
323
+ if resolved not in seen:
324
+ seen.add(resolved)
325
+ unique_files.append(candidate)
326
+ return unique_files
327
+
328
+
329
+ adb_server_connection = AdbServerConnectionManager()
330
+ adb_server_connection.synchronize_environment()