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,120 @@
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 IME and keyboard text typing helper."""
16
+
17
+ import asyncio
18
+ import base64
19
+ from typing import Any
20
+ from third_party.mobile_use.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ def _escape_for_adb_text(s: str) -> str:
26
+ """Escapes special characters for adb shell input text."""
27
+ return (
28
+ s.replace("\\", "\\\\")
29
+ .replace('"', '\\"')
30
+ .replace("'", "\\'")
31
+ .replace("`", "\\`")
32
+ .replace("$", "\\$")
33
+ .replace("&", "\\&")
34
+ .replace("|", "\\|")
35
+ .replace(";", "\\;")
36
+ .replace("<", "\\<")
37
+ .replace(">", "\\>")
38
+ .replace("(", "\\(")
39
+ .replace(")", "\\)")
40
+ .replace("*", "\\*")
41
+ .replace("?", "\\?")
42
+ .replace("~", "\\~")
43
+ .replace(" ", "%s")
44
+ )
45
+
46
+
47
+ class AndroidInputIME:
48
+ """Handles text input, unicode typing, and key combination injection."""
49
+
50
+ def __init__(self, adb_device: Any, ui_client: Any = None):
51
+ self.device = adb_device
52
+ self.ui_client = ui_client
53
+
54
+ async def type_text(self, text: str, clear_existing: bool = True) -> bool:
55
+ """Types text using adb input text, UIAutomator, or broadcast IME."""
56
+ try:
57
+ if clear_existing:
58
+ # Safe & robust clearing: Move to End -> Shift+Home selection -> Delete -> Fallback backspaces
59
+ clear_cmd = (
60
+ "input keyevent 123 && "
61
+ "input keyevent --meta 1 122 && "
62
+ "input keyevent 67 && "
63
+ "input keyevent 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67"
64
+ )
65
+ await asyncio.to_thread(self.device.shell, clear_cmd)
66
+ else:
67
+ # Append mode: move cursor to the end of existing text
68
+ await asyncio.to_thread(
69
+ self.device.shell,
70
+ "input keyevent 123", # KEYCODE_MOVE_END
71
+ )
72
+
73
+ # 1. Fast path for ASCII text: Use ADB input text directly.
74
+ if text.isascii():
75
+ lines = text.split("\n")
76
+ for i, line in enumerate(lines):
77
+ if i > 0:
78
+ # Send Enter key between lines
79
+ await asyncio.to_thread(self.device.shell, "input keyevent 66")
80
+ if line:
81
+ escaped = _escape_for_adb_text(line)
82
+ await asyncio.to_thread(self.device.shell, f"input text {escaped}")
83
+ return True
84
+
85
+ # 2. For non-ASCII / Unicode text: Try UIAutomator client
86
+ if self.ui_client and hasattr(self.ui_client, "send_text"):
87
+ try:
88
+ res = self.ui_client.send_text(text)
89
+ if asyncio.iscoroutine(res):
90
+ await res
91
+ return True
92
+ except Exception as e:
93
+ logger.warning(f"UIAutomator send_text failed: {e}")
94
+
95
+ # 3. Check if ADBKeyboard is currently active
96
+ try:
97
+ default_ime = await asyncio.to_thread(
98
+ self.device.shell, "settings get secure default_input_method"
99
+ )
100
+ if "adbkeyboard" in str(default_ime).lower():
101
+ b64_text = base64.b64encode(text.encode("utf-8")).decode("utf-8")
102
+ broadcast_cmd = f"am broadcast -a ADB_INPUT_B64 --es msg '{b64_text}'"
103
+ await asyncio.to_thread(self.device.shell, broadcast_cmd)
104
+ return True
105
+ except Exception as e:
106
+ # ADBKeyboard probe/broadcast failed; fall through to ADB input.
107
+ logger.debug(f"ADBKeyboard IME path failed, falling back to ADB input: {e}")
108
+
109
+ # 4. Fallback: ADB input text
110
+ lines = text.split("\n")
111
+ for i, line in enumerate(lines):
112
+ if i > 0:
113
+ await asyncio.to_thread(self.device.shell, "input keyevent 66")
114
+ if line:
115
+ escaped = _escape_for_adb_text(line)
116
+ await asyncio.to_thread(self.device.shell, f"input text {escaped}")
117
+ return True
118
+ except Exception as e:
119
+ logger.error(f"Failed to type text '{text}': {e}")
120
+ return False
@@ -0,0 +1,57 @@
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 screen recording and video transcode utilities."""
16
+
17
+ import asyncio
18
+ from pathlib import Path
19
+ from typing import Any
20
+ from third_party.mobile_use.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class ScreenRecorder:
26
+ """Manages background screen recording sessions using adb screenrecord."""
27
+
28
+ def __init__(self, adb_device: Any, output_path: Path):
29
+ self.device = adb_device
30
+ self.output_path = output_path
31
+ self.is_recording = False
32
+ self._remote_path = "/sdcard/artemis_record.mp4"
33
+
34
+ async def start(self) -> None:
35
+ """Starts remote screen recording."""
36
+ self.is_recording = True
37
+ logger.info("Starting screen recording on device...")
38
+ # Start background screenrecord
39
+ asyncio.create_task(
40
+ asyncio.to_thread(self.device.shell, f"screenrecord {self._remote_path}")
41
+ )
42
+
43
+ async def stop(self) -> Path | None:
44
+ """Stops screen recording and pulls file to host."""
45
+ if not self.is_recording:
46
+ return None
47
+ self.is_recording = False
48
+ logger.info("Stopping screen recording...")
49
+ try:
50
+ await asyncio.to_thread(self.device.shell, "pkill -2 screenrecord")
51
+ await asyncio.sleep(1.0)
52
+ self.output_path.parent.mkdir(parents=True, exist_ok=True)
53
+ await asyncio.to_thread(self.device.sync.pull, self._remote_path, str(self.output_path))
54
+ return self.output_path
55
+ except Exception as e:
56
+ logger.warning(f"Failed to pull screen recording: {e}")
57
+ return None
@@ -0,0 +1,290 @@
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 Base Device Driver Protocol.
16
+
17
+ Defines the contract for hardware and simulator device interaction,
18
+ abstracting low-level protocols (ADB, UIAutomator2, WebDriver, Cloud RPC)
19
+ behind a clean, unified async interface.
20
+ """
21
+
22
+ from abc import ABC, abstractmethod
23
+ import asyncio
24
+ from enum import Enum
25
+ from pathlib import Path
26
+ import re
27
+ import time
28
+ from typing import Any, Literal
29
+ from pydantic import BaseModel, ConfigDict, Field
30
+
31
+ from third_party.mobile_use.controllers.types import ElementQuery
32
+
33
+
34
+ class SwipeDirection(str, Enum):
35
+ UP = "up"
36
+ DOWN = "down"
37
+ LEFT = "left"
38
+ RIGHT = "right"
39
+
40
+
41
+ class KeyCode(str, Enum):
42
+ HOME = "home"
43
+ BACK = "back"
44
+ ENTER = "enter"
45
+ DELETE = "delete"
46
+ POWER = "power"
47
+ APP_SWITCH = "app_switch"
48
+ VOLUME_UP = "volume_up"
49
+ VOLUME_DOWN = "volume_down"
50
+
51
+
52
+ class ScreenData(BaseModel):
53
+ """Encapsulates raw and parsed UI screen observation data."""
54
+
55
+ screenshot_bytes: bytes = Field(..., description="Raw PNG/JPEG screenshot bytes")
56
+ screenshot_base64: str = Field(..., description="Base64 encoded screenshot string")
57
+ ui_hierarchy_xml: str | None = Field(default=None, description="Raw UI layout XML hierarchy")
58
+ ui_elements: list[dict[str, Any]] = Field(
59
+ default_factory=list, description="Parsed element node list"
60
+ )
61
+ width: int = Field(default=1080, description="Device screen width in pixels")
62
+ height: int = Field(default=2400, description="Device screen height in pixels")
63
+ timestamp: float = Field(default_factory=time.time, description="Capture timestamp")
64
+ platform: str = Field(default="android", description="Platform identifier")
65
+
66
+ model_config = ConfigDict(arbitrary_types_allowed=True)
67
+
68
+
69
+ class BaseDeviceDriver(ABC):
70
+ """Abstract Base Class for mobile device and emulator drivers."""
71
+
72
+ @property
73
+ @abstractmethod
74
+ def device_id(self) -> str:
75
+ """Returns the unique hardware or simulator serial/identifier."""
76
+ ...
77
+
78
+ @property
79
+ @abstractmethod
80
+ def screen_size(self) -> tuple[int, int]:
81
+ """Returns (width, height) in physical device pixels."""
82
+ ...
83
+
84
+ @abstractmethod
85
+ async def connect(self) -> None:
86
+ """Establishes connection to the target device."""
87
+ ...
88
+
89
+ @abstractmethod
90
+ async def disconnect(self) -> None:
91
+ """Closes connection and releases allocated resources."""
92
+ ...
93
+
94
+ @abstractmethod
95
+ async def get_screen_data(self, skip_settling: bool = False) -> ScreenData:
96
+ """Captures real-time screenshot and UI element hierarchy.
97
+
98
+ Args:
99
+ skip_settling: If True, captures immediately without waiting for screen stabilization.
100
+ """
101
+ ...
102
+
103
+ @abstractmethod
104
+ async def tap(
105
+ self,
106
+ x: int,
107
+ y: int,
108
+ duration_ms: int = 100,
109
+ times: int = 1,
110
+ delay_ms: int = 100,
111
+ ) -> bool:
112
+ """Performs single or multiple tap interactions at absolute pixel coordinates.
113
+
114
+ Args:
115
+ x: Absolute horizontal coordinate in pixels.
116
+ y: Absolute vertical coordinate in pixels.
117
+ duration_ms: Touch hold duration in milliseconds.
118
+ times: Number of consecutive taps (e.g. 2 for double tap, 7 for dev options).
119
+ delay_ms: Delay in milliseconds between consecutive taps.
120
+ """
121
+ ...
122
+
123
+ @abstractmethod
124
+ async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> bool:
125
+ """Performs a long press at absolute pixel coordinates."""
126
+ ...
127
+
128
+ @abstractmethod
129
+ async def swipe(
130
+ self,
131
+ start_x: int,
132
+ start_y: int,
133
+ end_x: int,
134
+ end_y: int,
135
+ duration_ms: int = 800,
136
+ ) -> bool:
137
+ """Performs a drag/swipe gesture from start to end pixel coordinates."""
138
+ ...
139
+
140
+ @abstractmethod
141
+ async def swipe_direction(
142
+ self,
143
+ direction: SwipeDirection | Literal["up", "down", "left", "right"],
144
+ duration_ms: int = 800,
145
+ ) -> bool:
146
+ """Performs standard cardinal scrolling gesture across device viewport."""
147
+ ...
148
+
149
+ @abstractmethod
150
+ async def input_text(self, text: str, clear_existing: bool = True) -> bool:
151
+ """Types text into the currently focused input field."""
152
+ ...
153
+
154
+ @abstractmethod
155
+ async def press_key(self, key: KeyCode | str | int) -> bool:
156
+ """Simulates physical or soft key press (e.g. HOME, BACK, ENTER)."""
157
+ ...
158
+
159
+ @abstractmethod
160
+ async def launch_app(self, package_name: str) -> bool:
161
+ """Launches target application package into the foreground."""
162
+ ...
163
+
164
+ @abstractmethod
165
+ async def stop_app(self, package_name: str) -> bool:
166
+ """Force-stops target application package."""
167
+ ...
168
+
169
+ @abstractmethod
170
+ async def get_current_package(self) -> str | None:
171
+ """Retrieves currently focused foreground application package name."""
172
+ ...
173
+
174
+ @abstractmethod
175
+ async def execute_shell(self, command: str, timeout_seconds: float = 15.0) -> str:
176
+ """Executes a system shell command directly on the device."""
177
+ ...
178
+
179
+ @abstractmethod
180
+ async def start_video_recording(self, output_dir: Path | None = None) -> None:
181
+ """Starts dynamic screen video capture in the background."""
182
+ ...
183
+
184
+ @abstractmethod
185
+ async def stop_video_recording(self) -> str | None:
186
+ """Stops background video capture and returns local recording file path."""
187
+ ...
188
+
189
+ async def wait_for_delay(self, seconds: float = 1.0) -> bool:
190
+ """Waits for specified duration in seconds."""
191
+ await asyncio.sleep(seconds)
192
+ return True
193
+
194
+ async def tap_normalized(
195
+ self,
196
+ norm_x: int,
197
+ norm_y: int,
198
+ long_press: bool = False,
199
+ duration_ms: int = 1000,
200
+ times: int = 1,
201
+ delay_ms: int = 100,
202
+ ) -> bool:
203
+ """Taps at normalized (0-1000 scale) screen coordinates."""
204
+ width, height = self.screen_size
205
+ abs_x = int(max(0, min(width - 1, norm_x * width / 1000.0)))
206
+ abs_y = int(max(0, min(height - 1, norm_y * height / 1000.0)))
207
+ if long_press:
208
+ return await self.long_press(abs_x, abs_y, duration_ms=duration_ms)
209
+ return await self.tap(abs_x, abs_y, duration_ms=100, times=times, delay_ms=delay_ms)
210
+
211
+ async def swipe_normalized(
212
+ self,
213
+ start_norm: list[int],
214
+ end_norm: list[int],
215
+ duration_ms: int = 800,
216
+ ) -> bool:
217
+ """Swipes between normalized (0-1000 scale) screen coordinates."""
218
+ width, height = self.screen_size
219
+ sx = int(max(0, min(width - 1, start_norm[0] * width / 1000.0)))
220
+ sy = int(max(0, min(height - 1, start_norm[1] * height / 1000.0)))
221
+ ex = int(max(0, min(width - 1, end_norm[0] * width / 1000.0)))
222
+ ey = int(max(0, min(height - 1, end_norm[1] * height / 1000.0)))
223
+ return await self.swipe(sx, sy, ex, ey, duration_ms=duration_ms)
224
+
225
+ async def find_element(
226
+ self, query: ElementQuery, screen_data: ScreenData | None = None
227
+ ) -> tuple[dict[str, Any] | None, list[int] | None, str | None]:
228
+ """Finds matching UI element in screen hierarchy by resource_id or text."""
229
+ resource_id, text, index = query.resource_id, query.text, query.index
230
+ data = screen_data or await self.get_screen_data()
231
+ elements = data.ui_elements or []
232
+
233
+ matched = []
234
+ for elem in elements:
235
+ r_id = elem.get("resource_id") or elem.get("resource-id") or ""
236
+ t_val = elem.get("text") or elem.get("content-desc") or ""
237
+
238
+ if resource_id and resource_id in r_id:
239
+ matched.append(elem)
240
+ elif text and text.lower() in t_val.lower():
241
+ matched.append(elem)
242
+
243
+ if not matched:
244
+ return None, None, f"Element not found ({query.describe()})"
245
+
246
+ if index >= len(matched):
247
+ return None, None, f"Element index {index} out of range (matched {len(matched)})"
248
+
249
+ elem = matched[index]
250
+ bounds = elem.get("bounds")
251
+ parsed_bounds = elem.get("parsed_bounds")
252
+ numeric_bounds: list[int] | None = None
253
+ if isinstance(parsed_bounds, dict):
254
+ keys = ("left", "top", "right", "bottom")
255
+ if all(isinstance(parsed_bounds.get(key), (int, float)) for key in keys):
256
+ numeric_bounds = [int(parsed_bounds[key]) for key in keys]
257
+ if numeric_bounds is None and isinstance(bounds, (list, tuple)) and len(bounds) == 4:
258
+ if all(isinstance(value, (int, float)) for value in bounds):
259
+ numeric_bounds = [int(value) for value in bounds]
260
+ if numeric_bounds is None and isinstance(bounds, str):
261
+ match = re.fullmatch(
262
+ r"\[\s*(-?\d+)\s*,\s*(-?\d+)\s*\]\[\s*(-?\d+)\s*,\s*(-?\d+)\s*\]",
263
+ bounds,
264
+ )
265
+ if match:
266
+ numeric_bounds = [int(value) for value in match.groups()]
267
+ # Bounds belong to the freshly captured hierarchy. Some UIAutomator
268
+ # adapters also attach a precomputed center which can lag one layout
269
+ # transition behind (for example when an expanding drawer shifts a
270
+ # keypad). Always derive the tap point from live bounds when possible;
271
+ # retain center only for adapters that do not expose bounds.
272
+ if numeric_bounds is not None:
273
+ center = [
274
+ (numeric_bounds[0] + numeric_bounds[2]) // 2,
275
+ (numeric_bounds[1] + numeric_bounds[3]) // 2,
276
+ ]
277
+ else:
278
+ center = elem.get("center")
279
+ return elem, center, None
280
+
281
+ async def tap_element(
282
+ self, query: ElementQuery, long_press: bool = False, duration_ms: int = 1000
283
+ ) -> bool:
284
+ """Finds and taps a UI element by resource_id or text."""
285
+ _, center, error = await self.find_element(query)
286
+ if error or not center:
287
+ return False
288
+ if long_press:
289
+ return await self.long_press(center[0], center[1], duration_ms=duration_ms)
290
+ return await self.tap(center[0], center[1])
@@ -0,0 +1,117 @@
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
+ """Remote Cloud Mobile Device Driver."""
16
+
17
+ from pathlib import Path
18
+ from typing import Literal
19
+ from artemis.drivers.base import BaseDeviceDriver, KeyCode, ScreenData, SwipeDirection
20
+ from third_party.mobile_use.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class CloudDeviceDriver(BaseDeviceDriver):
26
+ """Driver communicating with remote Android devices via Cloud Gateway RPC."""
27
+
28
+ def __init__(
29
+ self,
30
+ device_id: str = "cloud-device",
31
+ gateway_url: str = "http://127.0.0.1:8000",
32
+ session_id: str = "default-cloud-session",
33
+ width: int = 1080,
34
+ height: int = 2400,
35
+ ):
36
+ self._device_id = device_id
37
+ self.gateway_url = gateway_url
38
+ self.session_id = session_id
39
+ self._width = width
40
+ self._height = height
41
+
42
+ @property
43
+ def device_id(self) -> str:
44
+ return self._device_id
45
+
46
+ @property
47
+ def screen_size(self) -> tuple[int, int]:
48
+ return (self._width, self._height)
49
+
50
+ async def connect(self) -> None:
51
+ logger.info(f"Connecting to Cloud Device {self._device_id} at {self.gateway_url}")
52
+
53
+ async def disconnect(self) -> None:
54
+ logger.info(f"Disconnecting Cloud Device {self._device_id}")
55
+
56
+ async def get_screen_data(self, skip_settling: bool = False) -> ScreenData:
57
+ return ScreenData(
58
+ screenshot_bytes=b"",
59
+ screenshot_base64="",
60
+ width=self._width,
61
+ height=self._height,
62
+ platform="cloud",
63
+ )
64
+
65
+ async def tap(
66
+ self,
67
+ x: int,
68
+ y: int,
69
+ duration_ms: int = 100,
70
+ times: int = 1,
71
+ delay_ms: int = 100,
72
+ ) -> bool:
73
+ return True
74
+
75
+ async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> bool:
76
+ return True
77
+
78
+ async def swipe(
79
+ self,
80
+ start_x: int,
81
+ start_y: int,
82
+ end_x: int,
83
+ end_y: int,
84
+ duration_ms: int = 400,
85
+ ) -> bool:
86
+ return True
87
+
88
+ async def swipe_direction(
89
+ self,
90
+ direction: SwipeDirection | Literal["up", "down", "left", "right"],
91
+ duration_ms: int = 400,
92
+ ) -> bool:
93
+ return True
94
+
95
+ async def input_text(self, text: str, clear_existing: bool = True) -> bool:
96
+ return True
97
+
98
+ async def press_key(self, key: KeyCode | str | int) -> bool:
99
+ return True
100
+
101
+ async def launch_app(self, package_name: str) -> bool:
102
+ return True
103
+
104
+ async def stop_app(self, package_name: str) -> bool:
105
+ return True
106
+
107
+ async def get_current_package(self) -> str | None:
108
+ return None
109
+
110
+ async def execute_shell(self, command: str, timeout_seconds: float = 15.0) -> str:
111
+ return f"cloud_output: {command}"
112
+
113
+ async def start_video_recording(self, output_dir: Path | None = None) -> None:
114
+ pass
115
+
116
+ async def stop_video_recording(self) -> str | None:
117
+ return None
@@ -0,0 +1,80 @@
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
+ """Device Driver Factory & Registry."""
16
+
17
+ import os
18
+ from typing import TYPE_CHECKING
19
+
20
+ from adbutils import AdbClient
21
+
22
+ from artemis.config import settings
23
+ from artemis.drivers.android.adb_driver import AndroidAdbDriver
24
+ from artemis.drivers.base import BaseDeviceDriver
25
+ from artemis.drivers.mock.mock_driver import MockDeviceDriver
26
+ from third_party.mobile_use.utils.logger import get_logger
27
+
28
+ if TYPE_CHECKING:
29
+ from artemis.context import ArtemisContext
30
+
31
+ logger = get_logger(__name__)
32
+
33
+
34
+ def create_driver(ctx: "ArtemisContext") -> BaseDeviceDriver:
35
+ """Instantiates the appropriate BaseDeviceDriver based on the runtime context."""
36
+ # 1. Cloud mode check. Cloud devices are reached through the gateway's
37
+ # RemoteUIAutomatorClient; ARTEMIS_HIERARCHY_BACKEND does not apply there
38
+ # because the Accessibility Helper needs a local adb forward.
39
+ if os.environ.get("ARTEMIS_CLOUD_MODE") == "1":
40
+ if ctx.adb_client is None:
41
+ from cloud_service.virtualization import RemoteAdbClient
42
+
43
+ ctx.adb_client = RemoteAdbClient()
44
+ if ctx.ui_adb_client is None:
45
+ from cloud_service.virtualization import RemoteUIAutomatorClient
46
+
47
+ ctx.ui_adb_client = RemoteUIAutomatorClient(adb_client=ctx.adb_client)
48
+
49
+ # 2. Mock mode check
50
+ if (
51
+ getattr(ctx.device, "mobile_platform", None) == "mock"
52
+ or os.environ.get("ARTEMIS_MOCK_DRIVER") == "1"
53
+ ):
54
+ return MockDeviceDriver(
55
+ device_id=ctx.device.device_id if ctx.device else "mock-device",
56
+ width=ctx.device.device_width if ctx.device else 1080,
57
+ height=ctx.device.device_height if ctx.device else 2400,
58
+ )
59
+
60
+ # 3. Default Android ADB driver
61
+ if ctx.adb_client is None:
62
+ ctx.adb_client = AdbClient(
63
+ host=settings.ADB_HOST or "localhost", port=settings.ADB_PORT or 5037
64
+ )
65
+
66
+ return AndroidAdbDriver(
67
+ device_id=ctx.device.device_id,
68
+ adb_client=ctx.adb_client,
69
+ ui_adb_client=getattr(ctx, "ui_adb_client", None),
70
+ width=ctx.device.device_width,
71
+ height=ctx.device.device_height,
72
+ )
73
+
74
+
75
+ def get_driver(ctx: "ArtemisContext") -> BaseDeviceDriver:
76
+ """Cached accessor for device driver in the current context."""
77
+ if not hasattr(ctx, "_active_driver") or getattr(ctx, "_active_driver") is None:
78
+ driver = create_driver(ctx)
79
+ setattr(ctx, "_active_driver", driver)
80
+ return getattr(ctx, "_active_driver")