android-midscene-automation 0.2.4 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,482 @@
1
+ # Copyright 2025-2026 Minitap, Inc.
2
+ # Modifications Copyright 2026 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ # Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
17
+ # See third_party/mobile_use/METADATA for the upstream source and local modifications.
18
+
19
+ """UIAutomator2 client for Android device screen data retrieval.
20
+
21
+ Provides an alternative to the Maestro-based screen API with direct device
22
+ access.
23
+ Handles Maestro blocker detection and removal before connecting.
24
+ """
25
+
26
+ import base64
27
+ from io import BytesIO
28
+ import re as _re
29
+ import subprocess
30
+ import time
31
+ from typing import TYPE_CHECKING
32
+ import xml.etree.ElementTree as ET
33
+
34
+ from PIL import Image
35
+ from pydantic import BaseModel
36
+ import uiautomator2 as u2
37
+
38
+ from artemis.runtime.adb_endpoint import adb_command
39
+ from artemis.runtime.awake_service import ensure_device_awake
40
+ from artemis.utils.cython_compat import CyFunctionDetector
41
+ from third_party.mobile_use.utils.logger import get_logger
42
+
43
+ if TYPE_CHECKING:
44
+ from uiautomator2 import Device
45
+
46
+ logger = get_logger(__name__)
47
+
48
+
49
+ MAESTRO_PACKAGE = "dev.mobile.maestro"
50
+
51
+ # Boolean attributes of a UIAutomator hierarchy node.
52
+ UIAUTOMATOR_BOOL_ATTRIBUTES = (
53
+ "checkable",
54
+ "checked",
55
+ "clickable",
56
+ "enabled",
57
+ "focusable",
58
+ "focused",
59
+ "scrollable",
60
+ "long-clickable",
61
+ "password",
62
+ "selected",
63
+ )
64
+
65
+
66
+ class UIAutomatorScreenData(BaseModel):
67
+ """Screen data response from UIAutomator2."""
68
+
69
+ model_config = {"ignored_types": (CyFunctionDetector,)}
70
+
71
+ base64: str
72
+ hierarchy_xml: str
73
+ elements: list[dict]
74
+ width: int
75
+ height: int
76
+
77
+
78
+ def parse_hierarchy_xml_to_elements(hierarchy_xml: str) -> list[dict]:
79
+ """Parse uiautomator2 XML hierarchy into a flat list of element dictionaries.
80
+
81
+ The output format matches the existing screen API format with attributes
82
+ like:
83
+ - resource-id
84
+ - text
85
+ - content-desc
86
+ - bounds (as string "[x1,y1][x2,y2]")
87
+ - class
88
+ - package
89
+ - checkable, checked, clickable, enabled, focusable, focused
90
+ - scrollable, long-clickable, password, selected
91
+
92
+ Args:
93
+ hierarchy_xml: XML string from uiautomator2.dump_hierarchy()
94
+
95
+ Returns:
96
+ Flat list of element dictionaries
97
+ """
98
+ elements: list[dict] = []
99
+
100
+ try:
101
+ root = ET.fromstring(hierarchy_xml)
102
+ except ET.ParseError as e:
103
+ logger.error(f"Failed to parse hierarchy XML: {e}")
104
+ return elements
105
+
106
+ def _extract_element(node: ET.Element) -> None:
107
+ """Recursively extract elements from XML nodes."""
108
+ element: dict = {}
109
+
110
+ for attr_name, attr_value in node.attrib.items():
111
+ if attr_name == "resource-id":
112
+ element["resource-id"] = attr_value
113
+ elif attr_name == "text":
114
+ element["text"] = attr_value
115
+ elif attr_name == "content-desc":
116
+ element["content-desc"] = attr_value
117
+ element["accessibilityText"] = attr_value
118
+ elif attr_name == "bounds":
119
+ element["bounds"] = attr_value
120
+
121
+ match = _re.match(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]", attr_value)
122
+ if match:
123
+ x1, y1, x2, y2 = map(int, match.groups())
124
+ element["parsed_bounds"] = {
125
+ "left": x1,
126
+ "top": y1,
127
+ "right": x2,
128
+ "bottom": y2,
129
+ }
130
+ elif attr_name == "class":
131
+ element["class"] = attr_value
132
+ elif attr_name == "package":
133
+ element["package"] = attr_value
134
+ elif attr_name in UIAUTOMATOR_BOOL_ATTRIBUTES:
135
+ element[attr_name] = attr_value
136
+ else:
137
+ element[attr_name] = attr_value
138
+
139
+ if element:
140
+ elements.append(element)
141
+
142
+ for child in node:
143
+ _extract_element(child)
144
+
145
+ _extract_element(root)
146
+
147
+ return elements
148
+
149
+
150
+ def pil_to_base64(img: Image.Image, format: str = "JPEG", quality: int = 80) -> str:
151
+ """Convert PIL Image to base64 string with high-quality compression."""
152
+ buffer = BytesIO()
153
+ if format.upper() in ("JPEG", "JPG"):
154
+ format = "JPEG"
155
+ if img.mode in ("RGBA", "P"):
156
+ img = img.convert("RGB")
157
+ img.save(buffer, format=format, quality=quality, optimize=True)
158
+ else:
159
+ img.save(buffer, format=format)
160
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
161
+
162
+
163
+ def _is_package_installed(device_id: str, pkg: str) -> bool:
164
+ """Check if a package is installed on the device."""
165
+ try:
166
+ result = subprocess.run(
167
+ adb_command(["-s", device_id, "shell", "pm", "list", "packages"]),
168
+ stdin=subprocess.DEVNULL,
169
+ capture_output=True,
170
+ text=True,
171
+ timeout=10,
172
+ )
173
+ if result.returncode != 0:
174
+ logger.warning(f"Failed to list packages: {result.stderr}")
175
+ return False
176
+
177
+ lines = result.stdout.splitlines()
178
+ target = f"package:{pkg}"
179
+ return target in lines
180
+ except subprocess.TimeoutExpired:
181
+ logger.warning("Timeout checking installed packages")
182
+ return False
183
+ except (OSError, subprocess.SubprocessError) as e:
184
+ logger.warning(f"Error checking installed packages: {e}")
185
+ return False
186
+
187
+
188
+ def _uninstall_package(device_id: str, pkg: str) -> bool:
189
+ """Uninstall a package from the device for the current user."""
190
+ try:
191
+ result = subprocess.run(
192
+ adb_command(
193
+ [
194
+ "-s",
195
+ device_id,
196
+ "shell",
197
+ "pm",
198
+ "uninstall",
199
+ "--user",
200
+ "0",
201
+ pkg,
202
+ ]
203
+ ),
204
+ stdin=subprocess.DEVNULL,
205
+ capture_output=True,
206
+ text=True,
207
+ timeout=30,
208
+ )
209
+ if result.returncode == 0:
210
+ logger.info(f"Successfully uninstalled {pkg}")
211
+ return True
212
+ else:
213
+ logger.warning(f"Failed to uninstall {pkg}: {result.stderr}")
214
+ return False
215
+ except subprocess.TimeoutExpired:
216
+ logger.warning(f"Timeout uninstalling {pkg}")
217
+ return False
218
+ except Exception as e:
219
+ logger.warning(f"Error uninstalling {pkg}: {e}")
220
+ return False
221
+
222
+
223
+ def _ensure_maestro_not_installed(device_id: str) -> None:
224
+ """Check if Maestro is installed and uninstall it.
225
+
226
+ Maestro conflicts with uiautomator2 - if Maestro's package is installed,
227
+ u2.connect() will fail. This function ensures Maestro is removed before
228
+ attempting to connect.
229
+ """
230
+ if _is_package_installed(device_id, MAESTRO_PACKAGE):
231
+ logger.warning(
232
+ f"Maestro ({MAESTRO_PACKAGE}) detected - uninstalling to enable UIAutomator2..."
233
+ )
234
+ _uninstall_package(device_id, MAESTRO_PACKAGE)
235
+
236
+
237
+ class UIAutomatorClient:
238
+ """UIAutomator2 client for Android screen data retrieval.
239
+
240
+ This client uses uiautomator2 library for direct device communication,
241
+ providing faster hierarchy and screenshot retrieval compared to Maestro.
242
+
243
+ Important: Maestro must not be installed on the device as it conflicts
244
+ with uiautomator2. This client automatically handles Maestro removal.
245
+ """
246
+
247
+ def __init__(self, device_id: str):
248
+ """Initialize the UIAutomator client.
249
+
250
+ Args:
251
+ device_id: The Android device serial number (e.g., "emulator-5554")
252
+ """
253
+ self._device_id = device_id
254
+ self._device: Device | None = None
255
+ self._awake_strategy: str | None = None
256
+
257
+ def _ensure_connected(self) -> "Device":
258
+ """Ensure connection to the device, handling Maestro blocker.
259
+
260
+ Returns:
261
+ Connected uiautomator2 Device instance
262
+ """
263
+ if self._device is not None:
264
+ try:
265
+ # Quick check if connection is still alive
266
+ self._device.info
267
+ return self._device
268
+ except Exception:
269
+ logger.warning("UIAutomator2 connection lost, reconnecting...")
270
+ self._device = None
271
+
272
+ # Ensure Maestro is not blocking us
273
+ _ensure_maestro_not_installed(self._device_id)
274
+
275
+ # Enroll the device in the containing Artemis service lifetime before
276
+ # UIAutomator2 reads the screen. Client disconnects do not release it.
277
+ if self._awake_strategy is None:
278
+ self._awake_strategy = ensure_device_awake(self._device_id)
279
+
280
+ # Retry connections while ADB recovers from a reboot or USB disconnect.
281
+ logger.info(f"Connecting UIAutomator2 to device: {self._device_id}")
282
+ last_error: Exception | None = None
283
+ for attempt in range(3):
284
+ if attempt:
285
+ time.sleep(1.0 * attempt)
286
+ try:
287
+ self._device = u2.connect(self._device_id)
288
+ break
289
+ except Exception as exc:
290
+ last_error = exc
291
+ logger.warning(
292
+ f"UIAutomator2 connect attempt {attempt + 1}/3 to"
293
+ f" {self._device_id} failed: {exc}"
294
+ )
295
+ else:
296
+ self._awake_strategy = None
297
+ raise last_error
298
+ logger.info("UIAutomator2 connected successfully")
299
+ return self._device
300
+
301
+ def connect(self) -> None:
302
+ """Connect and activate this session's awake strategy without an action."""
303
+ self._ensure_connected()
304
+
305
+ def press_key(self, key: str):
306
+ """Press a key on the device.
307
+
308
+ Args:
309
+ key: Key to press (e.g., "home", "back", "enter"...)
310
+ """
311
+ device = self._ensure_connected()
312
+ return device.press(key=key)
313
+
314
+ def send_text(self, text: str) -> None:
315
+ """Send text input to the device using FastInputIME.
316
+
317
+ This method supports special characters (e.g., 'ö') that ADB shell
318
+ input text cannot handle. The original IME is restored after input.
319
+
320
+ Args:
321
+ text: The text to input
322
+ """
323
+
324
+ device = self._ensure_connected()
325
+ device.set_fastinput_ime(True)
326
+ # Allow IME to initialize
327
+ time.sleep(0.3)
328
+ try:
329
+ device.send_keys(text)
330
+ # Give FastInputIME time to process the broadcast and commit text
331
+ # before switching it off and killing it.
332
+ time.sleep(0.5)
333
+ finally:
334
+ device.set_fastinput_ime(False)
335
+
336
+ def clear_text(self) -> bool:
337
+ """Clear text of the currently focused element using UIAutomator2.
338
+
339
+ Returns True on success, False on failure.
340
+ """
341
+ try:
342
+ device = self._ensure_connected()
343
+ device(focused=True).clear_text()
344
+ return True
345
+ except Exception as e:
346
+ logger.error(f"UIAutomator2 clear_text failed: {e}")
347
+ return False
348
+
349
+ def set_clipboard(self, text: str) -> bool:
350
+ """Set text to system clipboard via UIAutomator2.
351
+
352
+ Args:
353
+ text: The string to set in the device clipboard.
354
+
355
+ Returns:
356
+ True if successful, False otherwise.
357
+ """
358
+ try:
359
+ device = self._ensure_connected()
360
+ device.set_clipboard(text)
361
+ return True
362
+ except Exception as e:
363
+ logger.debug(f"UIAutomator2 set_clipboard failed: {e}")
364
+ return False
365
+
366
+ def get_hierarchy(self) -> str:
367
+ """Get the UI hierarchy XML from the device.
368
+
369
+ Returns:
370
+ Compressed UI hierarchy XML string
371
+ """
372
+ device = self._ensure_connected()
373
+ return device.dump_hierarchy(compressed=True)
374
+
375
+ def get_screenshot(self) -> Image.Image | None:
376
+ """Capture a screenshot from the device.
377
+
378
+ Uses adb screencap for more reliable capture of all layers (including
379
+ popups), falling back to UIAutomator2 if it fails.
380
+
381
+ Returns:
382
+ PIL Image or None if capture failed
383
+ """
384
+ try:
385
+ result = subprocess.run(
386
+ adb_command(["-s", self._device_id, "exec-out", "screencap", "-p"]),
387
+ stdin=subprocess.DEVNULL,
388
+ capture_output=True,
389
+ timeout=10,
390
+ check=True,
391
+ )
392
+ return Image.open(BytesIO(result.stdout))
393
+ except (OSError, subprocess.SubprocessError) as e:
394
+ logger.warning(
395
+ f"Failed to capture screenshot via adb: {e}, falling back to uiautomator2"
396
+ )
397
+ device = self._ensure_connected()
398
+ return device.screenshot()
399
+
400
+ def get_screenshot_base64(self) -> str | None:
401
+ """Capture a screenshot and return as base64 string.
402
+
403
+ Returns:
404
+ Base64 encoded PNG screenshot or None if capture failed
405
+ """
406
+ screenshot = self.get_screenshot()
407
+ if screenshot is None:
408
+ return None
409
+ return pil_to_base64(screenshot, format="JPEG")
410
+
411
+ def get_screen_data(self) -> UIAutomatorScreenData:
412
+ """Get complete screen data including screenshot and hierarchy.
413
+
414
+ Returns:
415
+ UIAutomatorScreenData with screenshot, hierarchy, elements, and
416
+ dimensions
417
+ """
418
+ device = self._ensure_connected()
419
+
420
+ # Get screenshot
421
+ screenshot = self.get_screenshot()
422
+ if screenshot is None:
423
+ raise RuntimeError("Failed to capture screenshot")
424
+
425
+ # Get hierarchy
426
+ hierarchy_xml = device.dump_hierarchy(compressed=True)
427
+
428
+ # Parse XML to flat elements list
429
+ elements = parse_hierarchy_xml_to_elements(hierarchy_xml)
430
+
431
+ return UIAutomatorScreenData(
432
+ base64=pil_to_base64(screenshot, format="JPEG"),
433
+ hierarchy_xml=hierarchy_xml,
434
+ elements=elements,
435
+ width=screenshot.width,
436
+ height=screenshot.height,
437
+ )
438
+
439
+ def disconnect(self, stop_server: bool = False) -> None:
440
+ """Disconnect this client without ending the host's awake lifetime.
441
+
442
+ ``stop_server`` also kills uiautomator2's device server. Its UiAutomation
443
+ connection suppresses every accessibility service (the Artemis helper
444
+ included) for as long as it lives, so a caller that prefers the helper
445
+ must release it; the pure UIAutomator2 backend keeps it for fast reconnects.
446
+ """
447
+ device = self._device
448
+ self._awake_strategy = None
449
+ self._device = None
450
+ if stop_server and device is not None:
451
+ self.stop_server(device)
452
+ logger.info("UIAutomator2 client disconnected")
453
+
454
+ def stop_server(self, device: "Device | None" = None) -> bool:
455
+ """Kill uiautomator2's on-device server (see :meth:`disconnect`)."""
456
+ device = device or self._device
457
+ if device is None:
458
+ return False
459
+ try:
460
+ device.stop_uiautomator()
461
+ logger.info(f"UIAutomator2 device server stopped on {self._device_id}")
462
+ return True
463
+ except (
464
+ OSError,
465
+ RuntimeError,
466
+ subprocess.SubprocessError,
467
+ u2.exceptions.DeviceError,
468
+ ) as exc:
469
+ logger.debug(f"Stopping the UIAutomator2 server on {self._device_id} failed: {exc}")
470
+ return False
471
+
472
+
473
+ def get_client(device_id: str) -> UIAutomatorClient:
474
+ """Factory function to create a UIAutomatorClient.
475
+
476
+ Args:
477
+ device_id: The Android device serial number
478
+
479
+ Returns:
480
+ UIAutomatorClient instance
481
+ """
482
+ return UIAutomatorClient(device_id=device_id)
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,183 @@
1
+ # Copyright 2025-2026 Minitap, Inc.
2
+ # Modifications Copyright 2026 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ # Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
17
+ # See third_party/mobile_use/METADATA for the upstream source and local modifications.
18
+
19
+ """LLM provider models, the base LLM configuration model and its loaders."""
20
+
21
+ from collections.abc import Callable
22
+ import os
23
+ from pathlib import Path
24
+ from typing import Any, Literal
25
+
26
+ import google.auth
27
+ from google.auth.exceptions import DefaultCredentialsError
28
+ from pydantic import BaseModel, ValidationError
29
+
30
+ from artemis.config.constants import AgentNode, LLMProvider, LLMUtilsNode
31
+ from artemis.config.settings import settings
32
+ from artemis.utils.cython_compat import CyFunctionDetector
33
+ from third_party.mobile_use.utils.file import load_jsonc
34
+ from third_party.mobile_use.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+
38
+ LLMUtilsNodeWithFallback = LLMUtilsNode
39
+ AgentNodeWithFallback = AgentNode
40
+
41
+
42
+ def validate_vertex_ai_credentials() -> None:
43
+ """Validate Google Application Default Credentials for VertexAI provider."""
44
+ try:
45
+ _, project = google.auth.default()
46
+ if not project:
47
+ raise Exception("VertexAI requires a Google Cloud project to be set.")
48
+ except DefaultCredentialsError as e:
49
+ raise Exception(
50
+ f"VertexAI requires valid Google Application Default Credentials (ADC): {e}"
51
+ )
52
+
53
+
54
+ class LLM(BaseModel):
55
+ """Base model representing an LLM model provider and runtime parameters."""
56
+
57
+ model_config = {"ignored_types": (CyFunctionDetector,)}
58
+ provider: LLMProvider
59
+ model: str
60
+ temperature: float | None = None
61
+ thinking_budget: int | None = None
62
+ thinking_level: Literal["minimal", "low", "medium", "high"] | None = None
63
+ reasoning_effort: Literal["none", "low", "medium", "high"] | None = None
64
+ include_thoughts: bool | None = None
65
+ enable_grounding: bool | None = None
66
+
67
+ def validate_provider(self, name: str) -> None:
68
+ """Ensure the required API key or credentials exist in settings for this provider."""
69
+ if self.provider == "openai":
70
+ if not settings.OPENAI_API_KEY:
71
+ raise Exception(f"{name} requires OPENAI_API_KEY in .env")
72
+ elif self.provider == "google":
73
+ if not settings.GOOGLE_API_KEY:
74
+ raise Exception(f"{name} requires GOOGLE_API_KEY in .env")
75
+ elif self.provider == "vertexai":
76
+ validate_vertex_ai_credentials()
77
+ elif self.provider == "anthropic":
78
+ if not (settings.ANTHROPIC_API_KEY or os.environ.get("ANTHROPIC_API_KEY")):
79
+ raise Exception(f"{name} requires ANTHROPIC_API_KEY in .env")
80
+ elif self.provider == "openrouter":
81
+ if not settings.OPEN_ROUTER_API_KEY:
82
+ raise Exception(f"{name} requires OPEN_ROUTER_API_KEY in .env")
83
+ elif self.provider == "xai":
84
+ if not settings.XAI_API_KEY:
85
+ raise Exception(f"{name} requires XAI_API_KEY in .env")
86
+
87
+ def __str__(self) -> str:
88
+ return f"{self.provider}/{self.model}"
89
+
90
+
91
+ class LLMWithFallback(LLM):
92
+ """LLM configuration with automatic secondary fallback and timeout specs."""
93
+
94
+ fallback: LLM
95
+ fix_model: str | None = None
96
+ timeout: float | None = None
97
+
98
+ def __str__(self) -> str:
99
+ return f"{self.provider}/{self.model} (fallback: {self.fallback})"
100
+
101
+
102
+ class LLMConfigUtils(BaseModel):
103
+ """Configuration container for auxiliary utility agents/nodes."""
104
+
105
+ model_config = {"ignored_types": (CyFunctionDetector,)}
106
+ outputter: LLMWithFallback
107
+ hopper: LLMWithFallback
108
+ video_analyzer: LLMWithFallback | None = None
109
+ object_detector: LLMWithFallback | None = None
110
+
111
+
112
+ class LLMConfigBase(BaseModel):
113
+ """Base LLM configuration: planner, utility nodes and their accessors."""
114
+
115
+ model_config = {"ignored_types": (CyFunctionDetector,)}
116
+ planner: LLMWithFallback
117
+ utils: LLMConfigUtils
118
+
119
+ def __str__(self) -> str:
120
+ return f"""
121
+ 📃 Planner: {self.planner}
122
+ 🧩 Utils:
123
+ 🔽 Hopper: {self.utils.hopper}
124
+ 📝 Outputter: {self.utils.outputter}
125
+ 🎬 Video Analyzer: {self.utils.video_analyzer or "Not configured"}
126
+ 👁️ Object Detector: {self.utils.object_detector or "Not configured"}
127
+ """
128
+
129
+ def validate_providers(self) -> None:
130
+ """Validate credentials of the planner and the utility nodes."""
131
+ self.planner.validate_provider("Planner")
132
+ self.utils.outputter.validate_provider("Outputter")
133
+ self.utils.hopper.validate_provider("Hopper")
134
+ if self.utils.video_analyzer:
135
+ self.utils.video_analyzer.validate_provider("VideoAnalyzer")
136
+ if self.utils.object_detector:
137
+ self.utils.object_detector.validate_provider("ObjectDetector")
138
+
139
+ def get_utils(self, item: LLMUtilsNode) -> LLMWithFallback:
140
+ """Retrieve model configuration for a specific utility node."""
141
+ value = getattr(self.utils, item)
142
+ if value is None:
143
+ raise ValueError(
144
+ f"Utils '{item}' is not configured. Please add it to your LLM "
145
+ "config or enable it via AgentConfigBuilder."
146
+ )
147
+ return value
148
+
149
+
150
+ def initialize_llm_config[C: LLMConfigBase](parse: Callable[[], C]) -> C:
151
+ """Parse the LLM config with ``parse`` and validate provider credentials."""
152
+ llm_config = parse()
153
+ llm_config.validate_providers()
154
+ logger.success("LLM config initialized")
155
+ return llm_config
156
+
157
+
158
+ def load_llm_config_override[C: LLMConfigBase](
159
+ path: Path,
160
+ get_default: Callable[[], C],
161
+ merge: Callable[[C, dict[str, Any]], C],
162
+ ) -> C:
163
+ """Merge the JSON/JSONC overrides at ``path`` onto the default config.
164
+
165
+ Falls back to the default config when the file is missing or the merged
166
+ config is invalid.
167
+ """
168
+ default_config = get_default()
169
+
170
+ override_config_dict: dict[str, Any] = {}
171
+ if path.exists():
172
+ logger.info(f"Loading custom LLM config from {path.resolve()}...")
173
+ with open(path, encoding="utf-8") as f:
174
+ override_config_dict = load_jsonc(f)
175
+ else:
176
+ logger.warning(f"Custom LLM config not found at {path} - using default config")
177
+
178
+ try:
179
+ return merge(default_config, override_config_dict)
180
+ except ValidationError as e:
181
+ logger.error(f"Invalid LLM config: {e}")
182
+ logger.info("Falling back to default config")
183
+ return default_config