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,48 @@
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
+ """Universal Device Driver framework for ARTEMIS."""
16
+
17
+ from artemis.drivers.base import (
18
+ BaseDeviceDriver,
19
+ KeyCode,
20
+ ScreenData,
21
+ SwipeDirection,
22
+ )
23
+ from artemis.drivers.factory import create_driver, get_driver
24
+ from artemis.drivers.mock.mock_driver import MockDeviceDriver
25
+ from artemis.drivers.types import (
26
+ Bounds,
27
+ CoordinatesSelectorRequest,
28
+ DeviceInfo,
29
+ PercentagesSelectorRequest,
30
+ SwipeRequest,
31
+ TapOutput,
32
+ )
33
+
34
+ __all__ = [
35
+ "BaseDeviceDriver",
36
+ "KeyCode",
37
+ "ScreenData",
38
+ "SwipeDirection",
39
+ "Bounds",
40
+ "CoordinatesSelectorRequest",
41
+ "PercentagesSelectorRequest",
42
+ "SwipeRequest",
43
+ "TapOutput",
44
+ "DeviceInfo",
45
+ "create_driver",
46
+ "get_driver",
47
+ "MockDeviceDriver",
48
+ ]
@@ -0,0 +1,507 @@
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
+ """Concrete Android Device Driver implementation using ADB and UIAutomator2."""
16
+
17
+ import asyncio
18
+ import base64
19
+ from io import BytesIO
20
+ from pathlib import Path
21
+ from typing import Any, Literal
22
+
23
+ from adbutils import AdbClient, AdbDevice
24
+ from artemis.config.paths import get_temp_dir
25
+ from artemis.drivers.base import BaseDeviceDriver, KeyCode, ScreenData, SwipeDirection
26
+ from artemis.toolchain import find_ffmpeg, find_scrcpy
27
+ from artemis.utils.video import build_scrcpy_record_command
28
+ from artemis.utils.ui_filter import filter_ui_hierarchy
29
+ from third_party.mobile_use.clients.ui_automator_client import (
30
+ UIAutomatorClient,
31
+ parse_hierarchy_xml_to_elements,
32
+ )
33
+ from third_party.mobile_use.utils.logger import get_logger
34
+
35
+ logger = get_logger(__name__)
36
+
37
+ # KeyCode mapping from string name to Android keycode integer
38
+ ANDROID_KEYCODE_MAP: dict[str, int] = {
39
+ "home": 3,
40
+ "back": 4,
41
+ "enter": 66,
42
+ "delete": 67,
43
+ "power": 26,
44
+ "app_switch": 187,
45
+ "volume_up": 24,
46
+ "volume_down": 25,
47
+ }
48
+
49
+
50
+ def _escape_for_adb_text(s: str) -> str:
51
+ """Escapes special characters for adb shell input text."""
52
+ return (
53
+ s.replace("\\", "\\\\")
54
+ .replace('"', '\\"')
55
+ .replace("'", "\\'")
56
+ .replace("`", "\\`")
57
+ .replace("$", "\\$")
58
+ .replace("&", "\\&")
59
+ .replace("|", "\\|")
60
+ .replace(";", "\\;")
61
+ .replace("<", "\\<")
62
+ .replace(">", "\\>")
63
+ .replace("(", "\\(")
64
+ .replace(")", "\\)")
65
+ .replace("*", "\\*")
66
+ .replace("?", "\\?")
67
+ .replace("~", "\\~")
68
+ .replace(" ", "%s")
69
+ )
70
+
71
+
72
+ class AndroidAdbDriver(BaseDeviceDriver):
73
+ """Android device driver using ADB and UIAutomator2."""
74
+
75
+ def __init__(
76
+ self,
77
+ device_id: str,
78
+ adb_client: AdbClient,
79
+ ui_adb_client: UIAutomatorClient | None = None,
80
+ width: int = 1080,
81
+ height: int = 2400,
82
+ ):
83
+ self._device_id = device_id
84
+ self._adb_client = adb_client
85
+ self._ui_adb_client = ui_adb_client
86
+ self._width = width
87
+ self._height = height
88
+ self._device: AdbDevice | None = None
89
+ self._recording_process: asyncio.subprocess.Process | None = None
90
+ self._recording_output_path: Path | None = None
91
+
92
+ @property
93
+ def device_id(self) -> str:
94
+ return self._device_id
95
+
96
+ @property
97
+ def screen_size(self) -> tuple[int, int]:
98
+ return (self._width, self._height)
99
+
100
+ @property
101
+ def device(self) -> AdbDevice:
102
+ if self._device is None:
103
+ self._device = self._adb_client.device(serial=self._device_id)
104
+ return self._device
105
+
106
+ async def connect(self) -> None:
107
+ """Verify device availability."""
108
+ try:
109
+ state = self.device.get_state()
110
+ logger.info(f"Android device '{self._device_id}' connected (state: {state})")
111
+ except Exception as e:
112
+ logger.warning(f"Connecting to Android device '{self._device_id}': {e}")
113
+
114
+ async def disconnect(self) -> None:
115
+ """Clean up ongoing recordings and tunnels."""
116
+ if self._recording_process:
117
+ await self.stop_video_recording()
118
+ if self._ui_adb_client and hasattr(self._ui_adb_client, "disconnect"):
119
+ await asyncio.to_thread(self._ui_adb_client.disconnect)
120
+
121
+ async def get_screen_data(self, skip_settling: bool = False) -> ScreenData:
122
+ """Captures screenshot and XML hierarchy concurrently."""
123
+ if not skip_settling:
124
+ await asyncio.sleep(0.3)
125
+
126
+ # 1. Capture raw screenshot
127
+ screenshot_bytes: bytes = b""
128
+ screenshot_base64: str = ""
129
+ ui_hierarchy_xml: str | None = None
130
+ ui_elements: list[dict[str, Any]] = []
131
+
132
+ # Check if ui_adb_client has direct screen data support
133
+ if self._ui_adb_client and hasattr(self._ui_adb_client, "get_screen_data"):
134
+ try:
135
+ raw_ui_data = self._ui_adb_client.get_screen_data()
136
+ if (
137
+ hasattr(raw_ui_data, "base64")
138
+ and isinstance(raw_ui_data.base64, str)
139
+ and raw_ui_data.base64
140
+ ):
141
+ screenshot_base64 = raw_ui_data.base64
142
+ screenshot_bytes = base64.b64decode(screenshot_base64)
143
+ if hasattr(raw_ui_data, "width") and isinstance(raw_ui_data.width, int):
144
+ self._width = raw_ui_data.width
145
+ if hasattr(raw_ui_data, "height") and isinstance(raw_ui_data.height, int):
146
+ self._height = raw_ui_data.height
147
+ if hasattr(raw_ui_data, "hierarchy_xml") and isinstance(
148
+ raw_ui_data.hierarchy_xml, str
149
+ ):
150
+ ui_hierarchy_xml = raw_ui_data.hierarchy_xml
151
+ if hasattr(raw_ui_data, "elements"):
152
+ raw_elements = raw_ui_data.elements
153
+ if isinstance(raw_elements, dict):
154
+ ui_elements = [raw_elements]
155
+ elif isinstance(raw_elements, list):
156
+ ui_elements = raw_elements
157
+ except Exception as e:
158
+ logger.debug(f"Direct ui_adb_client.get_screen_data error: {e}")
159
+
160
+ if not screenshot_base64 or not isinstance(screenshot_base64, str):
161
+ try:
162
+ pil_img = await asyncio.to_thread(self.device.screenshot)
163
+ if hasattr(pil_img, "save"):
164
+ buf = BytesIO()
165
+ pil_img.save(buf, format="PNG")
166
+ screenshot_bytes = buf.getvalue()
167
+ self._width, self._height = pil_img.size
168
+ screenshot_base64 = base64.b64encode(screenshot_bytes).decode("utf-8")
169
+ except Exception as e:
170
+ logger.warning(f"Device screenshot capture failed on {self._device_id}: {e}")
171
+
172
+ if not screenshot_base64 or not isinstance(screenshot_base64, str):
173
+ # Fallback for headless testing environments
174
+ screenshot_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
175
+ screenshot_bytes = base64.b64decode(screenshot_base64)
176
+
177
+ # 2. Capture UI Hierarchy via UIAutomator
178
+ if self._ui_adb_client and not ui_elements:
179
+ try:
180
+ if hasattr(self._ui_adb_client, "get_hierarchy"):
181
+ res = self._ui_adb_client.get_hierarchy()
182
+ hierarchy = await res if asyncio.iscoroutine(res) else res
183
+ if isinstance(hierarchy, str):
184
+ ui_hierarchy_xml = hierarchy
185
+ ui_elements = parse_hierarchy_xml_to_elements(hierarchy)
186
+ else:
187
+ ui_elements = hierarchy
188
+ elif hasattr(self._ui_adb_client, "get_ui_elements"):
189
+ res = self._ui_adb_client.get_ui_elements()
190
+ ui_elements = await res if asyncio.iscoroutine(res) else res
191
+ except Exception as e:
192
+ logger.debug(f"UI hierarchy extraction failed: {e}")
193
+
194
+ if isinstance(ui_elements, dict):
195
+ clean_ui_elements = [ui_elements]
196
+ elif isinstance(ui_elements, list):
197
+ clean_ui_elements = ui_elements
198
+ else:
199
+ clean_ui_elements = []
200
+
201
+ w = self._width if isinstance(self._width, int) else 1080
202
+ h = self._height if isinstance(self._height, int) else 2400
203
+ clean_ui_elements = filter_ui_hierarchy(
204
+ clean_ui_elements,
205
+ screen_width=w,
206
+ screen_height=h,
207
+ )
208
+ clean_b64 = (
209
+ screenshot_base64
210
+ if isinstance(screenshot_base64, str) and screenshot_base64
211
+ else "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
212
+ )
213
+ clean_bytes = (
214
+ screenshot_bytes
215
+ if isinstance(screenshot_bytes, bytes) and screenshot_bytes
216
+ else base64.b64decode(clean_b64)
217
+ )
218
+
219
+ return ScreenData(
220
+ screenshot_bytes=clean_bytes,
221
+ screenshot_base64=clean_b64,
222
+ ui_hierarchy_xml=ui_hierarchy_xml,
223
+ ui_elements=clean_ui_elements,
224
+ width=w,
225
+ height=h,
226
+ platform="android",
227
+ )
228
+
229
+ async def tap(
230
+ self,
231
+ x: int,
232
+ y: int,
233
+ duration_ms: int = 100,
234
+ times: int = 1,
235
+ delay_ms: int = 100,
236
+ ) -> bool:
237
+ try:
238
+ if duration_ms >= 500:
239
+ cmd = f"input swipe {x} {y} {x} {y} {duration_ms}"
240
+ else:
241
+ if times <= 1:
242
+ cmd = f"input tap {x} {y}"
243
+ else:
244
+ taps = [f"input tap {x} {y}"] * times
245
+ cmd = f" && sleep {delay_ms / 1000.0:.3f} && ".join(taps)
246
+
247
+ logger.info(f"[ADB] {cmd}")
248
+ await asyncio.to_thread(self.device.shell, cmd)
249
+ return True
250
+ except Exception as e:
251
+ logger.error(f"Tap failed at ({x}, {y}): {e}")
252
+ return False
253
+
254
+ async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> bool:
255
+ return await self.tap(x=x, y=y, duration_ms=duration_ms)
256
+
257
+ async def swipe(
258
+ self,
259
+ start_x: int,
260
+ start_y: int,
261
+ end_x: int,
262
+ end_y: int,
263
+ duration_ms: int = 800,
264
+ ) -> bool:
265
+ try:
266
+ cmd = f"input swipe {start_x} {start_y} {end_x} {end_y} {duration_ms}"
267
+ logger.info(f"[ADB] {cmd}")
268
+ await asyncio.to_thread(self.device.shell, cmd)
269
+ return True
270
+ except Exception as e:
271
+ logger.error(f"Swipe failed from ({start_x},{start_y}) to ({end_x},{end_y}): {e}")
272
+ return False
273
+
274
+ async def swipe_direction(
275
+ self,
276
+ direction: SwipeDirection | Literal["up", "down", "left", "right"],
277
+ duration_ms: int = 800,
278
+ ) -> bool:
279
+ dir_str = str(direction).lower()
280
+ mid_x = int(
281
+ self._width * 0.6
282
+ ) # Lock to 60% width to avoid edge gestures and alphabet fast-scroll sidebar
283
+ mid_y = self._height // 2
284
+
285
+ if dir_str == "up":
286
+ # Drag bottom to top -> scrolls down (0.7 -> 0.3 leaves ~50-60% overlap, 800ms prevents fling)
287
+ return await self.swipe(
288
+ mid_x, int(self._height * 0.7), mid_x, int(self._height * 0.3), duration_ms
289
+ )
290
+ elif dir_str == "down":
291
+ # Drag top to bottom -> scrolls up
292
+ return await self.swipe(
293
+ mid_x, int(self._height * 0.3), mid_x, int(self._height * 0.7), duration_ms
294
+ )
295
+ elif dir_str == "left":
296
+ # Drag right to left -> scrolls right
297
+ return await self.swipe(
298
+ int(self._width * 0.75), mid_y, int(self._width * 0.25), mid_y, duration_ms
299
+ )
300
+ elif dir_str == "right":
301
+ # Drag left to right -> scrolls left
302
+ return await self.swipe(
303
+ int(self._width * 0.25), mid_y, int(self._width * 0.75), mid_y, duration_ms
304
+ )
305
+ return False
306
+
307
+ async def input_text(self, text: str, clear_existing: bool = True) -> bool:
308
+ try:
309
+ if clear_existing:
310
+ # Safe & robust clearing: Move to End -> Shift+Home selection -> Delete -> Fallback backspaces
311
+ clear_cmd = (
312
+ "input keyevent 123 && "
313
+ "input keyevent --meta 1 122 && "
314
+ "input keyevent 67 && "
315
+ "input keyevent 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67"
316
+ )
317
+ await asyncio.to_thread(self.device.shell, clear_cmd)
318
+ else:
319
+ # Append mode: move cursor to the very end of existing text
320
+ await asyncio.to_thread(
321
+ self.device.shell,
322
+ "input keyevent 123", # KEYCODE_MOVE_END
323
+ )
324
+
325
+ # Normalize literal escaped newlines from LLM / tool call serialization
326
+ norm_text = text.replace(r"\r\n", "\n").replace(r"\n", "\n").replace(r"\r", "\n")
327
+
328
+ # 1. Tier 1: Try clipboard injection + KEYCODE_PASTE (Zero IME interference, preserves multiline, works for all charsets)
329
+ if self._ui_adb_client:
330
+ try:
331
+ set_clip_ok = False
332
+ if hasattr(self._ui_adb_client, "set_clipboard"):
333
+ set_clip_ok = self._ui_adb_client.set_clipboard(norm_text)
334
+ elif hasattr(self._ui_adb_client, "_device") and self._ui_adb_client._device:
335
+ self._ui_adb_client._device.set_clipboard(norm_text)
336
+ set_clip_ok = True
337
+
338
+ if set_clip_ok:
339
+ await asyncio.to_thread(self.device.shell, "input keyevent 279")
340
+ return True
341
+ except Exception as e:
342
+ logger.debug(f"Clipboard paste fallback to ADB input: {e}")
343
+
344
+ # 2. Tier 2: Check if ADBKeyboard is currently active
345
+ try:
346
+ default_ime = await asyncio.to_thread(
347
+ self.device.shell, "settings get secure default_input_method"
348
+ )
349
+ if "adbkeyboard" in str(default_ime).lower():
350
+ b64_text = base64.b64encode(norm_text.encode("utf-8")).decode("utf-8")
351
+ broadcast_cmd = f"am broadcast -a ADB_INPUT_B64 --es msg '{b64_text}'"
352
+ await asyncio.to_thread(self.device.shell, broadcast_cmd)
353
+ return True
354
+ except Exception as e:
355
+ # ADBKeyboard probe/broadcast failed; fall through to native input.
356
+ logger.debug(f"ADBKeyboard IME path failed, falling back to ADB input: {e}")
357
+
358
+ # 3. Tier 3: Universal Native ADB input text fallback
359
+ lines = norm_text.split("\n")
360
+ for i, line in enumerate(lines):
361
+ if i > 0:
362
+ # Send Enter key between lines
363
+ await asyncio.to_thread(self.device.shell, "input keyevent 66")
364
+ if line:
365
+ escaped = _escape_for_adb_text(line)
366
+ await asyncio.to_thread(self.device.shell, f"input text {escaped}")
367
+ return True
368
+ except Exception as e:
369
+ logger.error(f"Input text failed for '{text}': {e}")
370
+ return False
371
+
372
+ async def press_key(self, key: KeyCode | str | int) -> bool:
373
+ try:
374
+ keycode_val = key
375
+ if isinstance(key, (KeyCode, str)):
376
+ key_name = str(key).lower().replace("keycode.", "")
377
+ keycode_val = ANDROID_KEYCODE_MAP.get(key_name, key)
378
+
379
+ await asyncio.to_thread(self.device.shell, f"input keyevent {keycode_val}")
380
+ return True
381
+ except Exception as e:
382
+ logger.error(f"Press key failed for '{key}': {e}")
383
+ return False
384
+
385
+ async def launch_app(self, package_name: str) -> bool:
386
+ try:
387
+ cmd = f"monkey -p {package_name} -c android.intent.category.LAUNCHER 1"
388
+ await asyncio.to_thread(self.device.shell, cmd)
389
+ return True
390
+ except Exception as e:
391
+ logger.error(f"Launch app failed for '{package_name}': {e}")
392
+ return False
393
+
394
+ async def stop_app(self, package_name: str) -> bool:
395
+ try:
396
+ await asyncio.to_thread(self.device.shell, f"am force-stop {package_name}")
397
+ return True
398
+ except Exception as e:
399
+ logger.error(f"Stop app failed for '{package_name}': {e}")
400
+ return False
401
+
402
+ async def get_current_package(self) -> str | None:
403
+ try:
404
+ # 1. Try modern current_app from adbutils
405
+ if hasattr(self.device, "current_app"):
406
+ try:
407
+ app_info = await asyncio.to_thread(self.device.current_app)
408
+ if app_info and getattr(app_info, "package", None):
409
+ return app_info.package
410
+ except Exception as e:
411
+ # current_app is unsupported on some devices; use dumpsys below.
412
+ logger.debug(f"current_app query failed, falling back to dumpsys: {e}")
413
+
414
+ # 2. Structured dumpsys extraction
415
+ out = await asyncio.to_thread(
416
+ self.device.shell, "dumpsys window displays | grep -E 'mCurrentFocus|mFocusedApp'"
417
+ )
418
+ for line in str(out).splitlines():
419
+ if "/" in line:
420
+ for token in line.split():
421
+ if "/" in token and "." in token:
422
+ clean = token.split("/")[0].strip("{} ,")
423
+ if clean and not clean.startswith("Window") and "." in clean:
424
+ return clean
425
+ except Exception as e:
426
+ logger.debug(f"Error querying current package: {e}")
427
+ return None
428
+
429
+ async def execute_shell(self, command: str, timeout_seconds: float = 15.0) -> str:
430
+ try:
431
+ return await asyncio.wait_for(
432
+ asyncio.to_thread(self.device.shell, command),
433
+ timeout=timeout_seconds,
434
+ )
435
+ except Exception as e:
436
+ return f"Error: {e}"
437
+
438
+ async def start_video_recording(self, output_dir: Path | None = None) -> None:
439
+ """Starts screen recording via scrcpy in background."""
440
+ out_dir = output_dir or get_temp_dir("recordings")
441
+ out_dir.mkdir(parents=True, exist_ok=True)
442
+ self._recording_mkv_path = out_dir / "recording.mkv"
443
+ self._recording_output_path = out_dir / "recording.mp4"
444
+ logger.info(f"Starting scrcpy video recording to {self._recording_mkv_path}...")
445
+ try:
446
+ scrcpy_bin = find_scrcpy()
447
+ cmd = build_scrcpy_record_command(
448
+ scrcpy_bin,
449
+ self.device_id,
450
+ self._recording_mkv_path,
451
+ lock_capture_orientation=False,
452
+ )
453
+ self._scrcpy_process = await asyncio.create_subprocess_exec(
454
+ *cmd,
455
+ stdout=asyncio.subprocess.PIPE,
456
+ stderr=asyncio.subprocess.PIPE,
457
+ )
458
+ await asyncio.sleep(0.5)
459
+ except Exception as e:
460
+ logger.warning(f"Failed to start scrcpy subprocess: {e}")
461
+
462
+ async def stop_video_recording(self) -> str | None:
463
+ """Stops background video capture and returns local recording file path."""
464
+ if hasattr(self, "_scrcpy_process") and self._scrcpy_process:
465
+ try:
466
+ self._scrcpy_process.terminate()
467
+ await asyncio.wait_for(self._scrcpy_process.wait(), timeout=5.0)
468
+ except (TimeoutError, ProcessLookupError, OSError) as e:
469
+ # Already exited, or did not stop within the timeout; a lingering
470
+ # scrcpy may keep the MKV file locked on Windows.
471
+ logger.debug(f"scrcpy process did not terminate cleanly: {e}")
472
+ self._scrcpy_process = None
473
+
474
+ mkv = getattr(self, "_recording_mkv_path", None)
475
+ mp4 = getattr(self, "_recording_output_path", None)
476
+
477
+ if mkv and mkv.exists() and mp4:
478
+ try:
479
+ proc = await asyncio.create_subprocess_exec(
480
+ find_ffmpeg(),
481
+ "-y",
482
+ "-i",
483
+ str(mkv),
484
+ "-c",
485
+ "copy",
486
+ "-movflags",
487
+ "+faststart",
488
+ str(mp4),
489
+ stdout=asyncio.subprocess.PIPE,
490
+ stderr=asyncio.subprocess.PIPE,
491
+ )
492
+ await proc.wait()
493
+ if mp4.exists() and mp4.stat().st_size > 0:
494
+ try:
495
+ mkv.unlink()
496
+ except OSError:
497
+ # Best-effort cleanup of the intermediate MKV file.
498
+ pass
499
+ return str(mp4)
500
+ except Exception as e:
501
+ logger.warning(f"Failed to convert MKV to MP4 in AdbDriver: {e}")
502
+
503
+ if mp4 and mp4.exists():
504
+ return str(mp4)
505
+ if mkv and mkv.exists():
506
+ return str(mkv)
507
+ return None
@@ -0,0 +1,64 @@
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
+ """Android XML Layout Hierarchy parser and semantic node extractor."""
16
+
17
+ import re
18
+ from typing import Any
19
+ import xml.etree.ElementTree as ET
20
+ from third_party.mobile_use.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ def parse_ui_hierarchy(xml_string: str) -> list[dict[str, Any]]:
26
+ """Parses raw Android XML hierarchy dump into structured interactive UI nodes."""
27
+ if not xml_string:
28
+ return []
29
+
30
+ elements: list[dict[str, Any]] = []
31
+ try:
32
+ root = ET.fromstring(xml_string)
33
+ for node in root.iter():
34
+ attribs = node.attrib
35
+ bounds_str = attribs.get("bounds", "")
36
+ match = re.match(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]", bounds_str)
37
+ if not match:
38
+ continue
39
+
40
+ x1, y1, x2, y2 = map(int, match.groups())
41
+ # Filter zero-area nodes
42
+ if x1 >= x2 or y1 >= y2:
43
+ continue
44
+
45
+ text = attribs.get("text") or attribs.get("content-desc") or ""
46
+ class_name = attribs.get("class", "")
47
+ resource_id = attribs.get("resource-id", "")
48
+ clickable = attribs.get("clickable") == "true"
49
+
50
+ if text or clickable or resource_id:
51
+ elements.append(
52
+ {
53
+ "text": text,
54
+ "resource_id": resource_id,
55
+ "class": class_name,
56
+ "clickable": clickable,
57
+ "bounds": [x1, y1, x2, y2],
58
+ "center": [(x1 + x2) // 2, (y1 + y2) // 2],
59
+ }
60
+ )
61
+ except Exception as e:
62
+ logger.warning(f"Failed to parse XML hierarchy: {e}")
63
+
64
+ return elements