android-midscene-automation 0.2.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +35 -0
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,86 @@
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
+ """Typed results for the device action layer.
16
+
17
+ These models replace the ``"Error" in text`` substring sniffing: success is carried by
18
+ ``ok``/``code``, while ``message`` remains the human/LLM-facing summary and keeps its
19
+ historical wording. Kept dependency-free (pydantic only) so servers, adapters, and
20
+ tests can all import it cheaply.
21
+ """
22
+
23
+ from enum import Enum
24
+ from typing import Any
25
+
26
+ from pydantic import BaseModel, Field
27
+
28
+ __all__ = ["ActionCode", "ActionResult", "ObserveResult"]
29
+
30
+
31
+ class ActionCode(str, Enum):
32
+ """Machine-readable outcome classification for a device action."""
33
+
34
+ OK = "OK"
35
+ INVALID_ARGS = "INVALID_ARGS"
36
+ TARGET_NOT_FOUND = "TARGET_NOT_FOUND"
37
+ DEVICE_ERROR = "DEVICE_ERROR"
38
+ PACKAGE_NOT_FOUND = "PACKAGE_NOT_FOUND"
39
+ TIMEOUT = "TIMEOUT"
40
+ UNSUPPORTED = "UNSUPPORTED"
41
+
42
+
43
+ class ActionResult(BaseModel):
44
+ """Outcome of one device action, independent of any transport or agent."""
45
+
46
+ ok: bool
47
+ code: ActionCode
48
+ action: str
49
+ message: str
50
+ """Agent-facing summary; wording matches the historical executor outcomes."""
51
+ detail: str | None = None
52
+ """Raw driver/controller error for diagnostics; never shown to the LLM."""
53
+ normalized_coordinates: list[int] | None = None
54
+ duration_ms: int | None = None
55
+
56
+ @classmethod
57
+ def success(cls, action: str, message: str, **kwargs: Any) -> "ActionResult":
58
+ return cls(ok=True, code=ActionCode.OK, action=action, message=message, **kwargs)
59
+
60
+ @classmethod
61
+ def failure(
62
+ cls,
63
+ action: str,
64
+ message: str,
65
+ code: ActionCode = ActionCode.DEVICE_ERROR,
66
+ **kwargs: Any,
67
+ ) -> "ActionResult":
68
+ return cls(ok=False, code=code, action=action, message=message, **kwargs)
69
+
70
+
71
+ class ObserveResult(BaseModel):
72
+ """Post-action observation: persisted screenshot plus the parsed element list."""
73
+
74
+ ok: bool
75
+ code: ActionCode = ActionCode.OK
76
+ message: str = ""
77
+ screenshot_path: str | None = None
78
+ image_name: str | None = None
79
+ """Stem of ``screenshot_path``; stable key used by trace/replay consumers."""
80
+ width: int | None = None
81
+ height: int | None = None
82
+ elements_text: str | None = None
83
+ elements: list[dict[str, Any]] = Field(default_factory=list)
84
+ hierarchy_ok: bool = True
85
+ """False when the screenshot succeeded but the UI hierarchy parse failed; callers
86
+ must then keep their previous element index rather than clobbering it."""
@@ -0,0 +1,21 @@
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
+ """Actuator backends: pluggable implementations of the device action contract."""
16
+
17
+ from artemis.mcp.actuators.base import Actuator
18
+ from artemis.mcp.actuators.adb import AdbActuator
19
+ from artemis.mcp.actuators.mock import MockActuator
20
+
21
+ __all__ = ["Actuator", "AdbActuator", "MockActuator"]
@@ -0,0 +1,414 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """ADB-backed actuator: the reference implementation of the actuator contract.
16
+
17
+ Outcome messages describe the dispatched command, such as
18
+ "Tapped at [x, y] (normalized)." The agent verifies the effect from subsequent
19
+ observations.
20
+
21
+ Error convention: device-level refusals the controller *reports* (an ``error`` field,
22
+ a falsy success flag) come back as ``ActionResult(ok=False)`` with the historical
23
+ message. Unexpected exceptions are allowed to propagate -- the caller (executor or MCP
24
+ server layer) owns the ``"Error during X: {e}"`` wrapping, exactly as the original
25
+ executor's ``try/except`` did.
26
+ """
27
+
28
+ import asyncio
29
+ import inspect
30
+ import re
31
+ import time
32
+ from typing import Any
33
+
34
+ from artemis.context import ArtemisContext
35
+ from artemis.controllers.unified_controller import UnifiedMobileController
36
+ from artemis.mcp.action_manifest import DEVICE_ACTIONS, ExtensionTool
37
+ from artemis.mcp.action_types import ActionCode, ActionResult
38
+ from third_party.mobile_use.utils.logger import get_logger
39
+
40
+ logger = get_logger(__name__)
41
+
42
+ __all__ = ["AdbActuator", "find_element_at_coords", "ensure_focus_at_coords"]
43
+
44
+
45
+ def find_element_at_coords(elements: list[dict], x: int, y: int) -> dict | None:
46
+ """Finds the smallest (leaf-most) focusable element containing pixel [x, y].
47
+
48
+ Moved from ``artemis.mcp.adb_server`` so both the stdio server and in-process
49
+ actuator share one implementation.
50
+ """
51
+ matching_element = None
52
+ min_area = float("inf")
53
+
54
+ for elem in elements:
55
+ is_focusable = (
56
+ elem.get("focusable") == "true"
57
+ or elem.get("clickable") == "true"
58
+ or "EditText" in str(elem.get("class", ""))
59
+ )
60
+ if not is_focusable:
61
+ continue
62
+
63
+ bounds_str = elem.get("bounds")
64
+ if bounds_str and isinstance(bounds_str, str):
65
+ match = re.match(r"\[(\-?\d+),(\-?\d+)\]\[(\-?\d+),(\-?\d+)\]", bounds_str)
66
+ if match:
67
+ x1, y1, x2, y2 = map(int, match.groups())
68
+ if x1 <= x <= x2 and y1 <= y <= y2:
69
+ area = (x2 - x1) * (y2 - y1)
70
+ if area < min_area:
71
+ min_area = area
72
+ matching_element = elem
73
+
74
+ return matching_element
75
+
76
+
77
+ async def ensure_focus_at_coords(controller, x: int, y: int) -> str | None:
78
+ """Ensures the element at pixel [x, y] is focused, tapping only if needed.
79
+
80
+ Returns an error string on failure, ``None`` on success. Moved from
81
+ ``artemis.mcp.adb_server``.
82
+ """
83
+ try:
84
+ elements = await controller.get_ui_elements()
85
+ elem = find_element_at_coords(elements, x, y)
86
+ if elem and elem.get("focused") == "true":
87
+ logger.info(f"Element under [{x}, {y}] is already focused. Skipping tap.")
88
+ return None
89
+ except Exception as e:
90
+ logger.warning(f"Failed to check focus status: {e}. Falling back to unconditional tap.")
91
+
92
+ result = await controller.tap_at(x=x, y=y)
93
+ if hasattr(result, "error") and result.error:
94
+ return result.error
95
+
96
+ # Wait for the UI to settle and keyboard to pop up after tapping
97
+ await asyncio.sleep(1.0)
98
+ return None
99
+
100
+
101
+ class AdbActuator:
102
+ """Drives an Android device through ``UnifiedMobileController`` over ADB."""
103
+
104
+ def __init__(
105
+ self,
106
+ ctx: ArtemisContext,
107
+ controller: UnifiedMobileController | None = None,
108
+ ):
109
+ self.ctx = ctx
110
+ self.controller = controller or UnifiedMobileController(ctx)
111
+
112
+ # --- Contract --------------------------------------------------------------------
113
+
114
+ def capabilities(self) -> frozenset[str]:
115
+ return DEVICE_ACTIONS
116
+
117
+ def extensions(self) -> list[ExtensionTool]:
118
+ return []
119
+
120
+ # --- Coordinate helpers ----------------------------------------------------------
121
+
122
+ def _dims(self) -> tuple[int, int]:
123
+ width = getattr(self.ctx.device, "device_width", 1080) if self.ctx.device else 1080
124
+ height = getattr(self.ctx.device, "device_height", 2400) if self.ctx.device else 2400
125
+ return width or 1080, height or 2400
126
+
127
+ def _to_px(self, nx: int, ny: int) -> tuple[int, int]:
128
+ width, height = self._dims()
129
+ x = int(max(0, min(width - 1, int(nx) * width / 1000)))
130
+ y = int(max(0, min(height - 1, int(ny) * height / 1000)))
131
+ return x, y
132
+
133
+ # --- Required action -------------------------------------------------------------
134
+
135
+ async def click_sequence(
136
+ self, points: list[tuple[int, int]], delay_ms: int = 50
137
+ ) -> ActionResult:
138
+ outcomes = []
139
+ for i, (nx, ny) in enumerate(points):
140
+ x, y = self._to_px(nx, ny)
141
+ result = await self.controller.tap_at(x, y)
142
+ err = getattr(result, "error", None) if result else None
143
+ if err:
144
+ return ActionResult.failure(
145
+ "click_sequence",
146
+ f"Error executing click at step {i + 1}: {err}",
147
+ detail=str(err),
148
+ )
149
+ outcomes.append(f"[{nx}, {ny}]")
150
+ if i < len(points) - 1:
151
+ await asyncio.sleep(max(0, delay_ms) / 1000.0)
152
+
153
+ return ActionResult.success(
154
+ "click_sequence",
155
+ f"Tapped in sequence at {'; '.join(outcomes)} (normalized).",
156
+ )
157
+
158
+ # --- Optional actions ------------------------------------------------------------
159
+
160
+ async def click(self, nx: int, ny: int, times: int = 1, delay_ms: int = 100) -> ActionResult:
161
+ x, y = self._to_px(nx, ny)
162
+ result = await self.controller.tap_at(x, y, times=times, delay_ms=delay_ms)
163
+ err = getattr(result, "error", None) if result else None
164
+ if err is not None:
165
+ return ActionResult.failure("click", f"Error executing click: {err}", detail=str(err))
166
+ return ActionResult.success(
167
+ "click",
168
+ f"Tapped at [{nx}, {ny}] (normalized).",
169
+ normalized_coordinates=[int(nx), int(ny)],
170
+ )
171
+
172
+ async def long_press(self, nx: int, ny: int, duration_ms: int = 1000) -> ActionResult:
173
+ x, y = self._to_px(nx, ny)
174
+ result = await self.controller.tap_at(
175
+ x, y, long_press=True, long_press_duration=duration_ms
176
+ )
177
+ err = getattr(result, "error", None) if result else None
178
+ if err is not None:
179
+ return ActionResult.failure(
180
+ "long_press", f"Error executing long press: {err}", detail=str(err)
181
+ )
182
+ return ActionResult.success(
183
+ "long_press",
184
+ f"Long-pressed at [{nx}, {ny}] (normalized) for {duration_ms}ms.",
185
+ normalized_coordinates=[int(nx), int(ny)],
186
+ duration_ms=duration_ms,
187
+ )
188
+
189
+ async def input_text(
190
+ self,
191
+ text: str,
192
+ target: tuple[int, int] | None = None,
193
+ clear_exist: bool = True,
194
+ ) -> ActionResult:
195
+ coords = None
196
+ if target:
197
+ nx, ny = target
198
+ x, y = self._to_px(nx, ny)
199
+ coords = [int(nx), int(ny)]
200
+ err = await ensure_focus_at_coords(self.controller, x, y)
201
+ if err:
202
+ return ActionResult.failure(
203
+ "input_text",
204
+ f"Error focusing element: {err}",
205
+ detail=str(err),
206
+ normalized_coordinates=coords,
207
+ )
208
+ if clear_exist:
209
+ if hasattr(self.controller, "erase_text") and not await self.controller.erase_text():
210
+ return ActionResult.failure(
211
+ "input_text",
212
+ "Failed to clear existing text",
213
+ normalized_coordinates=coords,
214
+ )
215
+ elif hasattr(self.controller, "press_key"):
216
+ # KEYCODE_MOVE_END: move cursor to the end for reliable append.
217
+ await self.controller.press_key("123")
218
+ if hasattr(self.controller, "type_text") and not await self.controller.type_text(
219
+ text, clear_existing=False
220
+ ):
221
+ return ActionResult.failure(
222
+ "input_text",
223
+ f"Failed typing '{text}'.",
224
+ normalized_coordinates=coords,
225
+ )
226
+
227
+ return ActionResult.success(
228
+ "input_text",
229
+ f"Typed '{text}'"
230
+ + (f" at [{coords[0]}, {coords[1]}] (normalized)." if coords else "."),
231
+ normalized_coordinates=coords,
232
+ )
233
+
234
+ async def swipe(
235
+ self,
236
+ start: tuple[int, int],
237
+ end: tuple[int, int],
238
+ duration_ms: int = 800,
239
+ ) -> ActionResult:
240
+ nx1, ny1 = start
241
+ nx2, ny2 = end
242
+ px1, py1 = self._to_px(nx1, ny1)
243
+ px2, py2 = self._to_px(nx2, ny2)
244
+ err = await self.controller.swipe_coords(px1, py1, px2, py2, duration_ms)
245
+ if err:
246
+ return ActionResult.failure("swipe", f"Error dragging: {err}", detail=str(err))
247
+ return ActionResult.success(
248
+ "swipe",
249
+ f"Swiped from [{nx1}, {ny1}] to [{nx2}, {ny2}] (normalized).",
250
+ normalized_coordinates=[int(nx1), int(ny1), int(nx2), int(ny2)],
251
+ duration_ms=duration_ms,
252
+ )
253
+
254
+ async def press_key(self, key: str) -> ActionResult:
255
+ key_str = str(key).lower()
256
+ if not key_str:
257
+ return ActionResult.failure(
258
+ "press_key",
259
+ f"Error executing key press '{key}'.",
260
+ code=ActionCode.INVALID_ARGS,
261
+ )
262
+
263
+ if key_str == "back" and hasattr(self.controller, "go_back"):
264
+ await self.controller.go_back()
265
+ elif key_str == "home" and hasattr(self.controller, "go_home"):
266
+ await self.controller.go_home()
267
+ elif key_str == "enter" and hasattr(self.controller, "press_enter"):
268
+ await self.controller.press_enter()
269
+ elif hasattr(self.controller, "press_key"):
270
+ # Pass unrecognized keycodes through verbatim (KEYCODE_* names, numeric
271
+ # codes): the driver resolves or forwards them, matching the historical
272
+ # adb_server behavior of accepting any Android key event.
273
+ res = await self.controller.press_key(key)
274
+ err = getattr(res, "error", None) if res else None
275
+ if err:
276
+ return ActionResult.failure(
277
+ "press_key",
278
+ f"Error executing key press '{key}': {err}",
279
+ detail=str(err),
280
+ )
281
+ if not res:
282
+ return ActionResult.failure("press_key", f"Error executing key press '{key}'.")
283
+
284
+ return ActionResult.success("press_key", f"Pressed key '{key}'.")
285
+
286
+ async def manage_app(self, action: str, app_name: str) -> ActionResult:
287
+ # Imported lazily: launch_app pulls in tool wrappers that are costly at import.
288
+ from artemis.tools.mobile.launch_app import find_package
289
+ from third_party.mobile_use.utils.app_launch_utils import launch_app_with_retries
290
+
291
+ res = find_package(self.ctx, app_name, use_fallback=False)
292
+ pkg = await res if inspect.iscoroutine(res) else res
293
+ if not pkg:
294
+ return ActionResult.failure(
295
+ "manage_app",
296
+ f"Error finding package for app: {app_name}",
297
+ code=ActionCode.PACKAGE_NOT_FOUND,
298
+ )
299
+ target_pkg = pkg
300
+
301
+ if action.lower() == "launch":
302
+ res_launch = launch_app_with_retries(self.ctx, target_pkg)
303
+ if inspect.iscoroutine(res_launch):
304
+ res_launch = await res_launch
305
+ if isinstance(res_launch, tuple):
306
+ success, error_msg = res_launch
307
+ else:
308
+ success, error_msg = bool(res_launch), ""
309
+
310
+ if success:
311
+ return ActionResult.success(
312
+ "manage_app",
313
+ f"Launched app '{app_name}' ({target_pkg}); foreground confirmed.",
314
+ )
315
+ return ActionResult.failure(
316
+ "manage_app",
317
+ f"Failed to launch app '{app_name}': {error_msg}",
318
+ detail=error_msg or None,
319
+ )
320
+ elif action.lower() == "stop":
321
+ if hasattr(self.controller, "terminate_app"):
322
+ res_term = self.controller.terminate_app(target_pkg)
323
+ if inspect.iscoroutine(res_term):
324
+ await res_term
325
+ return ActionResult.success(
326
+ "manage_app", f"Force-stopped app '{app_name}' ({target_pkg})."
327
+ )
328
+ return ActionResult.failure(
329
+ "manage_app",
330
+ f"Invalid manage_app action: {action}",
331
+ code=ActionCode.INVALID_ARGS,
332
+ )
333
+
334
+ async def wait_for_delay(self, time_in_ms: int) -> ActionResult:
335
+ delay_s = max(0.0, float(time_in_ms) / 1000.0)
336
+ await asyncio.sleep(delay_s)
337
+ return ActionResult.success(
338
+ "wait_for_delay",
339
+ f"Waited {time_in_ms}ms.",
340
+ duration_ms=int(time_in_ms),
341
+ )
342
+
343
+ async def wait_for_text(
344
+ self, text: str, wait_state: str | None = None, timeout_ms: int | None = None
345
+ ) -> ActionResult:
346
+ timeout_s = (timeout_ms or 5000) / 1000.0
347
+ start = time.time()
348
+ found = False
349
+ target_state = (wait_state or "appear").lower()
350
+
351
+ while time.time() - start < timeout_s:
352
+ tree = await self.controller.get_ui_elements()
353
+ text_present = text.lower() in str(tree or "").lower()
354
+ if target_state == "appear" and text_present:
355
+ found = True
356
+ break
357
+ elif target_state == "disappear" and not text_present:
358
+ found = True
359
+ break
360
+ await asyncio.sleep(0.5)
361
+
362
+ if found:
363
+ return ActionResult.success(
364
+ "wait_for_text",
365
+ f"Text '{text}' {'appeared' if target_state == 'appear' else 'disappeared'}"
366
+ f" in the UI tree after {int((time.time() - start) * 1000)}ms.",
367
+ )
368
+ return ActionResult.failure(
369
+ "wait_for_text",
370
+ f"Timed out waiting for text '{text}' to {target_state}.",
371
+ code=ActionCode.TIMEOUT,
372
+ )
373
+
374
+ async def open_link(self, url: str) -> ActionResult:
375
+ success = await self.controller.open_url(url)
376
+ if success:
377
+ return ActionResult.success("open_link", f"Opened link '{url}'.")
378
+ return ActionResult.failure("open_link", f"Failed to open link '{url}'.")
379
+
380
+ async def erase_one_char(self) -> ActionResult:
381
+ success = await self.controller.erase_text(nb_chars=1)
382
+ if success:
383
+ return ActionResult.success("erase_one_char", "Erased one character.")
384
+ return ActionResult.failure("erase_one_char", "Failed to erase one character.")
385
+
386
+ async def focus_and_clear_text(self, nx: int, ny: int) -> ActionResult:
387
+ x, y = self._to_px(nx, ny)
388
+ err = await ensure_focus_at_coords(self.controller, x, y)
389
+ if err:
390
+ return ActionResult.failure(
391
+ "focus_and_clear_text",
392
+ f"Error focusing element: {err}",
393
+ detail=str(err),
394
+ normalized_coordinates=[int(nx), int(ny)],
395
+ )
396
+ success = await self.controller.erase_text()
397
+ if success:
398
+ return ActionResult.success(
399
+ "focus_and_clear_text",
400
+ f"Cleared text at [{nx}, {ny}] (normalized).",
401
+ normalized_coordinates=[int(nx), int(ny)],
402
+ )
403
+ return ActionResult.failure("focus_and_clear_text", "Failed to erase text.")
404
+
405
+ # --- Internal observation primitives ---------------------------------------------
406
+
407
+ async def take_screenshot(self) -> str:
408
+ return await self.controller.take_screenshot()
409
+
410
+ async def get_ui_elements(self) -> Any:
411
+ return await self.controller.get_ui_elements()
412
+
413
+ async def get_screen_data(self) -> Any:
414
+ return await self.controller.get_screen_data()
@@ -0,0 +1,113 @@
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
+ """The actuator protocol: what any backend must look like to drive a device.
16
+
17
+ An actuator answers exactly one question -- *how to perform a physical action* -- in
18
+ the canonical 0-1000 normalized coordinate space. It knows nothing about LangGraph
19
+ ``State``, prompts, agents, screenshots-as-observations, or MCP. Pixel geometry (or a
20
+ robot arm's motion planning) is a private detail behind this boundary.
21
+
22
+ A backend advertises what it can do through :meth:`Actuator.capabilities` (names from
23
+ ``artemis.mcp.action_manifest.DEVICE_ACTIONS``) and may contribute tools nobody
24
+ anticipated through :meth:`Actuator.extensions`. ``validate_actuator`` in the manifest
25
+ enforces the contract at session start.
26
+
27
+ Methods for actions a backend does not implement should simply not exist (or raise
28
+ ``NotImplementedError``); callers consult ``capabilities()`` first.
29
+ """
30
+
31
+ from typing import Any, Protocol, runtime_checkable
32
+
33
+ from artemis.mcp.action_manifest import ExtensionTool
34
+ from artemis.mcp.action_types import ActionResult
35
+
36
+ __all__ = ["Actuator"]
37
+
38
+
39
+ @runtime_checkable
40
+ class Actuator(Protocol):
41
+ """Protocol every actuator backend implements.
42
+
43
+ All coordinates are 0-1000 normalized. Every action coroutine returns an
44
+ :class:`ActionResult` and must not raise for device-level failures -- those are
45
+ reported through ``ok=False`` so a failed tap reaches the agent as an observation
46
+ rather than an exception.
47
+ """
48
+
49
+ def capabilities(self) -> frozenset[str]:
50
+ """Names from ``DEVICE_ACTIONS`` this backend implements."""
51
+ ...
52
+
53
+ def extensions(self) -> list[ExtensionTool]:
54
+ """Backend-specific tools unknown to the manifest."""
55
+ ...
56
+
57
+ # --- Required action -------------------------------------------------------------
58
+
59
+ async def click_sequence(
60
+ self, points: list[tuple[int, int]], delay_ms: int = 50
61
+ ) -> ActionResult: ...
62
+
63
+ # --- Optional actions ------------------------------------------------------------
64
+
65
+ async def click(
66
+ self, nx: int, ny: int, times: int = 1, delay_ms: int = 100
67
+ ) -> ActionResult: ...
68
+
69
+ async def long_press(self, nx: int, ny: int, duration_ms: int = 1000) -> ActionResult: ...
70
+
71
+ async def input_text(
72
+ self,
73
+ text: str,
74
+ target: tuple[int, int] | None = None,
75
+ clear_exist: bool = True,
76
+ ) -> ActionResult: ...
77
+
78
+ async def swipe(
79
+ self,
80
+ start: tuple[int, int],
81
+ end: tuple[int, int],
82
+ duration_ms: int = 800,
83
+ ) -> ActionResult: ...
84
+
85
+ async def press_key(self, key: str) -> ActionResult: ...
86
+
87
+ async def manage_app(self, action: str, app_name: str) -> ActionResult: ...
88
+
89
+ async def wait_for_delay(self, time_in_ms: int) -> ActionResult: ...
90
+
91
+ async def wait_for_text(
92
+ self, text: str, wait_state: str | None = None, timeout_ms: int | None = None
93
+ ) -> ActionResult: ...
94
+
95
+ async def open_link(self, url: str) -> ActionResult: ...
96
+
97
+ async def erase_one_char(self) -> ActionResult: ...
98
+
99
+ async def focus_and_clear_text(self, nx: int, ny: int) -> ActionResult: ...
100
+
101
+ # --- Internal observation primitives ---------------------------------------------
102
+
103
+ async def take_screenshot(self) -> str:
104
+ """Returns the current screen as a base64-encoded JPEG/PNG string."""
105
+ ...
106
+
107
+ async def get_ui_elements(self) -> Any:
108
+ """Returns the raw UI hierarchy elements as provided by the device."""
109
+ ...
110
+
111
+ async def get_screen_data(self) -> Any:
112
+ """Returns combined screen data (screenshot + elements + dimensions)."""
113
+ ...