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,70 @@
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
+ """Mock actuator for tests: real actuator code paths over an in-memory device.
16
+
17
+ Subclasses :class:`AdbActuator` deliberately, so tests exercise the same coordinate
18
+ conversion and controller dispatch as production -- only the driver is in-memory.
19
+ ``capabilities``/``extensions`` are overridable to model partial and extended
20
+ backends.
21
+ """
22
+
23
+ from artemis.context import ArtemisContext, DeviceContext, DevicePlatform
24
+ from artemis.drivers.mock.mock_driver import MockDeviceDriver
25
+ from artemis.mcp.action_manifest import DEVICE_ACTIONS, ExtensionTool
26
+ from artemis.mcp.actuators.adb import AdbActuator
27
+ from artemis.platform import platform
28
+
29
+ __all__ = ["MockActuator"]
30
+
31
+
32
+ class MockActuator(AdbActuator):
33
+ """An :class:`AdbActuator` bound to a :class:`MockDeviceDriver`."""
34
+
35
+ def __init__(
36
+ self,
37
+ width: int = 1080,
38
+ height: int = 2400,
39
+ capabilities: frozenset[str] | None = None,
40
+ extensions: list[ExtensionTool] | None = None,
41
+ ):
42
+ driver = MockDeviceDriver(width=width, height=height)
43
+ ctx = ArtemisContext(
44
+ trace_id="mock-actuator",
45
+ device=DeviceContext(
46
+ host_platform=platform.os_type.name,
47
+ mobile_platform=DevicePlatform.ANDROID,
48
+ device_id=driver.device_id,
49
+ device_width=width,
50
+ device_height=height,
51
+ ),
52
+ )
53
+ # get_driver(ctx) returns a cached `_active_driver` when present
54
+ # (artemis/drivers/factory.py), the established test seam.
55
+ ctx._active_driver = driver
56
+ super().__init__(ctx)
57
+ self.driver = driver
58
+ self._capabilities = capabilities if capabilities is not None else DEVICE_ACTIONS
59
+ self._extensions = list(extensions or [])
60
+
61
+ def capabilities(self) -> frozenset[str]:
62
+ return self._capabilities
63
+
64
+ def extensions(self) -> list[ExtensionTool]:
65
+ return self._extensions
66
+
67
+ @property
68
+ def action_history(self) -> list[dict]:
69
+ """The mock driver's recorded actions, for assertions."""
70
+ return self.driver.action_history
@@ -0,0 +1,490 @@
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
+ from collections.abc import Callable
16
+ import json
17
+ import logging
18
+ import os
19
+ from pathlib import Path
20
+ import sys
21
+ import textwrap
22
+ from typing import Any
23
+
24
+ # Ensure repository root is in sys.path when executed directly or via MCP runner
25
+ _REPO_ROOT = str(Path(__file__).resolve().parent.parent.parent)
26
+ if _REPO_ROOT not in sys.path:
27
+ sys.path.insert(0, _REPO_ROOT)
28
+
29
+ # pylint: disable=wrong-import-position
30
+ from adbutils import AdbClient
31
+ from mcp.server.fastmcp import Context, FastMCP
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ try:
36
+ from mcp.server.fastmcp.server import Settings as FastMCPSettings
37
+
38
+ FastMCPSettings.model_rebuild()
39
+ except Exception as exc: # pylint: disable=broad-exception-caught
40
+ # Best-effort compatibility shim for FastMCP/pydantic version drift.
41
+ logger.debug("FastMCP Settings model_rebuild skipped: %s", exc, exc_info=True)
42
+
43
+ from artemis.clients.screen_client_factory import create_screen_client
44
+ from artemis.context import ArtemisContext, DeviceContext, DevicePlatform
45
+ from artemis.controllers.unified_controller import UnifiedMobileController
46
+ from artemis.platform import platform
47
+ from third_party.mobile_use.utils.app_launch_utils import launch_app_with_retries
48
+
49
+
50
+ def configure_stdio_mode() -> None:
51
+ """Applies process-wide settings required when serving MCP over stdio.
52
+
53
+ These are destructive to the host process (console logging is removed and the
54
+ DataEngine IPC port is cleared), so they must only run when this module owns the
55
+ process -- i.e. from the ``__main__`` block. Importing this module for its tools
56
+ or for :func:`_get_controller` must leave the caller's logging and environment
57
+ untouched.
58
+ """
59
+ # Redirect stdio to prevent logs from breaking MCP JSON-RPC protocol and causing deadlocks.
60
+ try:
61
+ from artemis.config import settings
62
+
63
+ log_path = Path(settings.TRACES_PATH) / "mcp_server.log"
64
+ except Exception:
65
+ log_path = Path("traces") / "mcp_server.log"
66
+
67
+ log_path.parent.mkdir(parents=True, exist_ok=True)
68
+
69
+ for name in ["artemis", __name__]:
70
+ log_instance = logging.getLogger(name)
71
+ log_instance.setLevel(logging.DEBUG)
72
+ log_instance.propagate = False
73
+ # Remove standard streams handlers
74
+ log_instance.handlers = [
75
+ h for h in log_instance.handlers if not isinstance(h, logging.StreamHandler)
76
+ ]
77
+ fh = logging.FileHandler(log_path, encoding="utf-8")
78
+ fh.setFormatter(logging.Formatter("%(asctime)s | %(name)s | %(levelname)s | %(message)s"))
79
+ log_instance.addHandler(fh)
80
+
81
+ logger.info("MCP Server Python process fully started. Configuring FastMCP service...")
82
+
83
+ # Avoid concurrent SQLite WAL locks by blocking child processes from connecting
84
+ # to the primary DataEngine.
85
+ os.environ["ARTEMIS_IPC_PORT"] = ""
86
+
87
+
88
+ # Create minimal MCP server
89
+ mcp = FastMCP("Android_ADB_Controller")
90
+
91
+
92
+ def _tool_description(fn: Callable[..., Any]) -> str:
93
+ """Returns ``fn``'s docstring normalized identically on every Python version.
94
+
95
+ FastMCP publishes ``__doc__`` verbatim as the tool description. Python 3.13+
96
+ strips the common indentation of docstring continuation lines at compile time,
97
+ while 3.12 keeps it, so the schema external MCP clients see would otherwise
98
+ depend on the interpreter. This reproduces the 3.13 form (first line as-is,
99
+ remaining lines dedented, whitespace-only lines emptied); it is a no-op there.
100
+ """
101
+ first, sep, rest = (fn.__doc__ or "").partition("\n")
102
+ return first + sep + textwrap.dedent(rest)
103
+
104
+
105
+ def _tool() -> Callable[[Callable[..., Any]], Callable[..., Any]]:
106
+ """``mcp.tool()`` with a Python-version-independent description."""
107
+
108
+ def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
109
+ return mcp.tool(description=_tool_description(fn))(fn)
110
+
111
+ return decorator
112
+
113
+
114
+ _GLOBAL_CONTROLLER = None
115
+ _CONTROLLERS: dict[str, Any] = {}
116
+
117
+
118
+ def _get_controller(device_serial: str | None = None):
119
+ """Lazy-load device controller on-demand, caching per device serial."""
120
+ global _GLOBAL_CONTROLLER, _CONTROLLERS
121
+ target_serial = (
122
+ device_serial or os.environ.get("ARTEMIS_DEVICE_ID") or os.environ.get("ADB_DEVICE_SERIAL")
123
+ )
124
+ if target_serial and target_serial in _CONTROLLERS:
125
+ return _CONTROLLERS[target_serial]
126
+ if not target_serial and _GLOBAL_CONTROLLER is not None:
127
+ return _GLOBAL_CONTROLLER
128
+
129
+ logger.info("Initializing lazy device controller...")
130
+ if os.environ.get("ARTEMIS_CLOUD_MODE") == "1":
131
+ logger.info(
132
+ "GCP CLOUD MODE active: initializing CloudMobileDeviceController via UnifiedMobileController..."
133
+ )
134
+ resolved_serial = target_serial or "cloud_device"
135
+ ctx = ArtemisContext(
136
+ device=DeviceContext(
137
+ platform=DevicePlatform.ANDROID,
138
+ device_id=resolved_serial,
139
+ width=1080,
140
+ height=2400,
141
+ )
142
+ )
143
+ controller = UnifiedMobileController(ctx=ctx)
144
+ if resolved_serial:
145
+ _CONTROLLERS[resolved_serial] = controller
146
+ if _GLOBAL_CONTROLLER is None:
147
+ _GLOBAL_CONTROLLER = controller
148
+ return controller
149
+
150
+ host = os.environ.get("ADB_HOST", "localhost")
151
+ port_str = os.environ.get("ADB_PORT", "5037")
152
+ port = int(port_str) if port_str.isdigit() else 5037
153
+ if "ADB_SERVER_SOCKET" not in os.environ and (host != "localhost" or port != 5037):
154
+ os.environ["ADB_SERVER_SOCKET"] = f"tcp:{host}:{port}"
155
+ adb = AdbClient(host=host, port=port)
156
+
157
+ devices = adb.device_list()
158
+ if not devices:
159
+ raise Exception(f"No Android devices found at {host}:{port}")
160
+
161
+ if target_serial:
162
+ matched = [d for d in devices if d.serial == target_serial]
163
+ if not matched:
164
+ raise Exception(
165
+ f"Target Android device '{target_serial}' not found at"
166
+ f" {host}:{port} among available devices."
167
+ )
168
+ device = matched[0]
169
+ else:
170
+ device = devices[0]
171
+ device_id = device.serial
172
+
173
+ # Observer path: the helper is used only when it is already installed and
174
+ # running; nothing is installed from here (that happens inside a task's
175
+ # device-lock boundary or via `artemis helper install`).
176
+ ui_client = create_screen_client(device_id)
177
+ try:
178
+ ui_data = ui_client.get_screen_data()
179
+ width, height = ui_data.width, ui_data.height
180
+ except Exception as e:
181
+ logger.warning(f"Failed initial screen data check, using defaults: {e}")
182
+ width, height = 1080, 2400
183
+
184
+ ctx = ArtemisContext(
185
+ trace_id="mcp-session",
186
+ device=DeviceContext(
187
+ host_platform=platform.os_type.name,
188
+ mobile_platform=DevicePlatform.ANDROID,
189
+ device_id=device_id,
190
+ device_width=width,
191
+ device_height=height,
192
+ ),
193
+ adb_client=adb,
194
+ ui_adb_client=ui_client,
195
+ )
196
+
197
+ controller = UnifiedMobileController(ctx)
198
+ if device_id:
199
+ _CONTROLLERS[device_id] = controller
200
+ if _GLOBAL_CONTROLLER is None:
201
+ _GLOBAL_CONTROLLER = controller
202
+ logger.info(f"Lazy device controller fully initialized for device: {device_id}")
203
+ return controller
204
+
205
+
206
+ # Shared with the in-process actuator layer; this stdio server keeps its legacy
207
+ # pixel-space contract for external MCP clients, but the element/focus logic has a
208
+ # single implementation in artemis.mcp.actuators.adb.
209
+ from artemis.mcp.actuators.adb import ( # noqa: E402 pylint: disable=wrong-import-position
210
+ ensure_focus_at_coords as _ensure_focus_at_coords,
211
+ )
212
+
213
+
214
+ @_tool()
215
+ async def tap(
216
+ ctx: Context,
217
+ coordinates: list[int],
218
+ times: int = 1,
219
+ delay_ms: int = 100,
220
+ ) -> str:
221
+ """Taps on the screen at coordinates.
222
+
223
+ 'coordinates' is a list [x, y].
224
+ 'times' is the number of consecutive clicks (default 1).
225
+ 'delay_ms' is the delay in milliseconds between consecutive clicks (default
226
+ 100).
227
+ """
228
+ try:
229
+ controller = _get_controller()
230
+ except Exception as e:
231
+ return f"Error: Controller lazy initialization failed: {e}"
232
+
233
+ if len(coordinates) != 2:
234
+ return "Error: coordinates must be [x, y]"
235
+
236
+ result = await controller.tap_at(
237
+ x=coordinates[0], y=coordinates[1], times=times, delay_ms=delay_ms
238
+ )
239
+
240
+ if result.error:
241
+ return f"Error: {result.error}"
242
+ return "Success"
243
+
244
+
245
+ @_tool()
246
+ async def long_press_on(
247
+ ctx: Context,
248
+ coordinates: list[int],
249
+ duration: int = 1000,
250
+ ) -> str:
251
+ """Long presses on the screen at coordinates.
252
+
253
+ 'coordinates' is a list [x, y].
254
+ 'duration' is in milliseconds (default 1000).
255
+ """
256
+ try:
257
+ controller = _get_controller()
258
+ except Exception as e:
259
+ return f"Error: Controller lazy initialization failed: {e}"
260
+
261
+ if len(coordinates) != 2:
262
+ return "Error: coordinates must be [x, y]"
263
+
264
+ result = await controller.tap_at(
265
+ x=coordinates[0],
266
+ y=coordinates[1],
267
+ long_press=True,
268
+ long_press_duration=duration,
269
+ )
270
+
271
+ if result.error:
272
+ return f"Error: {result.error}"
273
+ return "Success"
274
+
275
+
276
+ @_tool()
277
+ async def swipe(
278
+ ctx: Context,
279
+ coordinates: list[int],
280
+ duration: int = 400,
281
+ ) -> str:
282
+ """Swipes from start coordinates to end coordinates.
283
+
284
+ 'coordinates' is a list [start_x, start_y, end_x, end_y].
285
+ 'duration' is in milliseconds (default 400).
286
+
287
+ Set duration >= 1000 to drag-and-drop. Drag slightly past the target
288
+ position to trigger reordering.
289
+ """
290
+ try:
291
+ controller = _get_controller()
292
+ except Exception as e:
293
+ return f"Error: Controller lazy initialization failed: {e}"
294
+
295
+ if len(coordinates) != 4:
296
+ return "Error: coordinates must be [start_x, start_y, end_x, end_y]"
297
+
298
+ error = await controller.swipe_coords(
299
+ start_x=coordinates[0],
300
+ start_y=coordinates[1],
301
+ end_x=coordinates[2],
302
+ end_y=coordinates[3],
303
+ duration=duration,
304
+ )
305
+ if error:
306
+ return f"Error: {error}"
307
+ return "Success"
308
+
309
+
310
+ @_tool()
311
+ async def back(ctx: Context) -> str:
312
+ """Simulates pressing the system back button."""
313
+ try:
314
+ controller = _get_controller()
315
+ except Exception as e:
316
+ return f"Error: Controller lazy initialization failed: {e}"
317
+
318
+ success = await controller.go_back()
319
+ return "Success" if success else "Failed"
320
+
321
+
322
+ @_tool()
323
+ async def launch_app(ctx: Context, package_name: str) -> str:
324
+ """Launches an application by its Android package name with retries and smart polling."""
325
+ try:
326
+ controller = _get_controller()
327
+ except Exception as e:
328
+ return f"Error: Controller lazy initialization failed: {e}"
329
+
330
+ success, error_msg = await launch_app_with_retries(controller.ctx, package_name)
331
+ return "Success" if success else f"Failed: {error_msg}"
332
+
333
+
334
+ @_tool()
335
+ async def stop_app(ctx: Context, package_name: str) -> str:
336
+ """Force stops an application by its Android package name."""
337
+ try:
338
+ controller = _get_controller()
339
+ except Exception as e:
340
+ return f"Error: Controller lazy initialization failed: {e}"
341
+
342
+ success = await controller.terminate_app(package_name)
343
+ return "Success" if success else "Failed"
344
+
345
+
346
+ @_tool()
347
+ async def open_link(ctx: Context, url: str) -> str:
348
+ """Opens a URL or deep link on the device."""
349
+ try:
350
+ controller = _get_controller()
351
+ except Exception as e:
352
+ return f"Error: Controller lazy initialization failed: {e}"
353
+
354
+ success = await controller.open_url(url)
355
+ return "Success" if success else "Failed"
356
+
357
+
358
+ @_tool()
359
+ async def focus_and_input_text(
360
+ ctx: Context,
361
+ coordinates: list[int],
362
+ text: str,
363
+ clear_before_input: bool = False,
364
+ ) -> str:
365
+ """Focuses on a UI element at coordinates and inputs text.
366
+
367
+ 'coordinates' is a list [x, y] to tap first to gain focus.
368
+ 'clear_before_input' if True, clears all existing text before typing. If False, appends text at the end of existing content.
369
+ 'text' supports multi-line content with '\\n'.
370
+ """
371
+ try:
372
+ controller = _get_controller()
373
+ except Exception as e:
374
+ return f"Error: Controller lazy initialization failed: {e}"
375
+
376
+ if len(coordinates) != 2:
377
+ return "Error: coordinates must be [x, y]"
378
+
379
+ err = await _ensure_focus_at_coords(controller, coordinates[0], coordinates[1])
380
+ if err:
381
+ return f"Error focusing element: {err}"
382
+
383
+ if clear_before_input:
384
+ success = await controller.erase_text()
385
+ if not success:
386
+ return "Failed to clear existing text"
387
+ else:
388
+ # Move cursor to the end for reliable append
389
+ await controller.press_key("123")
390
+
391
+ success = await controller.type_text(text, clear_existing=False)
392
+ return "Success" if success else "Failed"
393
+
394
+
395
+ @_tool()
396
+ async def focus_and_clear_text(
397
+ ctx: Context,
398
+ coordinates: list[int],
399
+ ) -> str:
400
+ """Focuses on a UI element at coordinates and clears its text content.
401
+
402
+ 'coordinates' is a list [x, y] to tap first to gain focus.
403
+ """
404
+ try:
405
+ controller = _get_controller()
406
+ except Exception as e:
407
+ return f"Error: Controller lazy initialization failed: {e}"
408
+
409
+ if len(coordinates) != 2:
410
+ return "Error: coordinates must be [x, y]"
411
+
412
+ err = await _ensure_focus_at_coords(controller, coordinates[0], coordinates[1])
413
+ if err:
414
+ return f"Error focusing element: {err}"
415
+
416
+ success = await controller.erase_text()
417
+ return "Success" if success else "Failed"
418
+
419
+
420
+ @_tool()
421
+ async def erase_one_char(ctx: Context) -> str:
422
+ """Erases a single character (simulates Backspace)."""
423
+ try:
424
+ controller = _get_controller()
425
+ except Exception as e:
426
+ return f"Error: Controller lazy initialization failed: {e}"
427
+
428
+ success = await controller.erase_text(nb_chars=1)
429
+ return "Success" if success else "Failed"
430
+
431
+
432
+ @_tool()
433
+ async def press_key(ctx: Context, keycode: str) -> str:
434
+ """Presses a specific Android key event (e.g., KEYCODE_ENTER, KEYCODE_HOME)."""
435
+ try:
436
+ controller = _get_controller()
437
+ except Exception as e:
438
+ return f"Error: Controller lazy initialization failed: {e}"
439
+
440
+ try:
441
+ success = await controller.press_key(keycode)
442
+ return "Success" if success else "Failed"
443
+ except Exception as e:
444
+ return f"Error: {e}"
445
+
446
+
447
+ @_tool()
448
+ async def take_screenshot(ctx: Context) -> str:
449
+ """Takes a screenshot of the device screen.
450
+
451
+ Returns the screenshot as a base64 encoded JPEG string.
452
+ """
453
+ try:
454
+ controller = _get_controller()
455
+ except Exception as e:
456
+ return f"Error: Controller lazy initialization failed: {e}"
457
+
458
+ try:
459
+ screenshot_b64 = await controller.take_screenshot()
460
+ return screenshot_b64
461
+ except Exception as e:
462
+ return f"Error: {e}"
463
+
464
+
465
+ @_tool()
466
+ async def get_ui_hierarchy(ctx: Context) -> str:
467
+ """Retrieves the current UI elements hierarchy from the device."""
468
+ try:
469
+ controller = _get_controller()
470
+ except Exception as e:
471
+ logger.exception("Failed to initialize controller in get_ui_hierarchy")
472
+ raise e
473
+
474
+ try:
475
+ elements = await controller.get_ui_elements()
476
+ return json.dumps(elements)
477
+ except Exception as e:
478
+ logger.exception("Failed to get UI elements in get_ui_hierarchy")
479
+ raise e
480
+
481
+
482
+ if __name__ == "__main__":
483
+ from artemis.runtime import shutdown_awake_service, start_awake_service
484
+
485
+ configure_stdio_mode()
486
+ start_awake_service()
487
+ try:
488
+ mcp.run(transport="stdio")
489
+ finally:
490
+ shutdown_awake_service()
@@ -0,0 +1,133 @@
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
+ """Screen observation: capture, fuse, index, and persist -- without LangGraph State.
16
+
17
+ This is the act-then-observe half that used to be welded into every executor action
18
+ (``capture_screenshot_and_parse_ui``). Split out so that actuation and observation are
19
+ separate calls: the Flash adapter observes after each action, while the
20
+ Validator -- which runs its own UI-change polling -- simply never calls it.
21
+ """
22
+
23
+ import asyncio
24
+ import base64
25
+ import time
26
+
27
+ from artemis.config import get_temp_dir
28
+ from artemis.context import ArtemisContext
29
+ from artemis.controllers.unified_controller import UnifiedMobileController
30
+ from artemis.mcp.action_types import ActionCode, ObserveResult
31
+ from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml
32
+ from artemis.utils.visualization import format_minimal_list_with_elements
33
+ from third_party.mobile_use.utils.logger import get_logger
34
+
35
+ logger = get_logger(__name__)
36
+
37
+ __all__ = ["observe"]
38
+
39
+
40
+ async def observe(
41
+ ctx: ArtemisContext,
42
+ controller: UnifiedMobileController,
43
+ settle_ms: int = 400,
44
+ ocr_results: list | None = None,
45
+ ) -> tuple[ObserveResult, bytes | None]:
46
+ """Captures and indexes the current screen.
47
+
48
+ Returns the structured observation plus the raw screenshot bytes (an in-process
49
+ convenience; the serialized ``ObserveResult`` carries only the persisted path).
50
+
51
+ Mirrors the historical ``capture_screenshot_and_parse_ui`` behavior exactly:
52
+ a settling delay, screen capture, XML fusion and element indexing, persistence via
53
+ the data engine with a temp-file fallback -- and the same failure semantics: a
54
+ failed capture yields ``ok=False``; a failed hierarchy parse still returns the
55
+ screenshot with ``hierarchy_ok=False``.
56
+ """
57
+ try:
58
+ if settle_ms > 0:
59
+ logger.info(f"Delaying {settle_ms / 1000:.1f}s before capturing screen state...")
60
+ await asyncio.sleep(settle_ms / 1000.0)
61
+ else:
62
+ logger.info("Retrieving screen data directly (skip settling)...")
63
+ device_data = await controller.get_screen_data()
64
+ screenshot_bytes = base64.b64decode(device_data.base64)
65
+ except Exception as e:
66
+ logger.error(f"Failed to capture screen state: {e}")
67
+ return (
68
+ ObserveResult(
69
+ ok=False,
70
+ code=ActionCode.DEVICE_ERROR,
71
+ message=f"Failed to capture screen state: {e}",
72
+ hierarchy_ok=False,
73
+ ),
74
+ None,
75
+ )
76
+
77
+ xml_hierarchy = None
78
+ ocr_results = ocr_results or []
79
+ elements: list[dict] = []
80
+ hierarchy_ok = True
81
+ width = height = None
82
+ try:
83
+ xml_hierarchy = device_data.elements
84
+ width = device_data.width or 1080
85
+ height = device_data.height or 2400
86
+ if ctx.device:
87
+ ctx.device.device_width = width
88
+ ctx.device.device_height = height
89
+
90
+ fused_xml = fuse_ocr_with_xml(xml_hierarchy, ocr_results)
91
+ minimal_list_str, elements, _ = format_minimal_list_with_elements(fused_xml, width, height)
92
+ except Exception as e:
93
+ logger.error(f"Failed to fetch or fuse UI hierarchy: {e}")
94
+ minimal_list_str = "Not available due to hierarchy error."
95
+ hierarchy_ok = False
96
+
97
+ screenshot_path = None
98
+ if ctx.data_engine:
99
+ try:
100
+ image_name = ctx.data_engine.get_or_create_image(
101
+ screenshot_bytes,
102
+ ui_tree=xml_hierarchy,
103
+ ocr_result=ocr_results,
104
+ )
105
+ screenshot_path = str(ctx.data_engine.get_image_path(image_name))
106
+ except Exception as e:
107
+ logger.error(f"Failed to save image in DataEngine: {e}")
108
+
109
+ if not screenshot_path:
110
+ try:
111
+ temp_dir = get_temp_dir("screenshots")
112
+ temp_file = temp_dir / f"screenshot_{int(time.time())}.jpg"
113
+ temp_file.write_bytes(screenshot_bytes)
114
+ screenshot_path = str(temp_file)
115
+ except Exception as e:
116
+ logger.error(f"Failed to save fallback screenshot: {e}")
117
+
118
+ from pathlib import Path
119
+
120
+ return (
121
+ ObserveResult(
122
+ ok=True,
123
+ message="Screen observed.",
124
+ screenshot_path=screenshot_path,
125
+ image_name=Path(screenshot_path).stem if screenshot_path else None,
126
+ width=width,
127
+ height=height,
128
+ elements_text=minimal_list_str,
129
+ elements=elements,
130
+ hierarchy_ok=hierarchy_ok,
131
+ ),
132
+ screenshot_bytes,
133
+ )