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,320 @@
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
+ """Contract layer between actuator backends and the agent-facing tool surface.
16
+
17
+ This module is the single place that declares *what a backend must provide*. It sits
18
+ between the actuator implementations (``artemis/mcp/actuators/``) and the agents that
19
+ consume tools, so that a backend -- ADB today, a robot arm tomorrow -- can omit tools
20
+ it cannot perform, and add tools nobody anticipated, without any agent knowing.
21
+
22
+ Two rules govern the split between required and optional:
23
+
24
+ 1. A tool is REQUIRED only when a prompt depends on it *structurally*, i.e. the prompt
25
+ cannot be assembled around its absence. Today that is ``click_sequence`` alone: the
26
+ Flash runner prompt's "Time-Sensitive Tasks" teaching is built around "atomic
27
+ chained execution to defeat turn latency", which *is* ``click_sequence``. (The Pro
28
+ Operator needs no such tool: a multi-action turn is executed by the Validator as an
29
+ unvetted fast-action burst.)
30
+ 2. Everything else is OPTIONAL, because every other prompt reference lives in a tool
31
+ enumeration slot or a self-contained instruction block that the assembly layer
32
+ (``artemis/agents/prompt_assembly.py``) renders conditionally. An optional tool that
33
+ a backend does not implement disappears from the declarations *and* from the prompt,
34
+ so it costs the model nothing.
35
+
36
+ ``tests/unit/mcp/test_action_manifest.py`` re-derives rule 2 by scanning the prompt
37
+ sources, so adding a tool name to a prompt without classifying it here fails CI.
38
+ """
39
+
40
+ from collections.abc import Awaitable, Callable
41
+ from dataclasses import dataclass, field
42
+ from typing import Any
43
+
44
+ __all__ = [
45
+ "ActuatorContractError",
46
+ "AGENT_ROLES",
47
+ "ALL_KNOWN_TOOLS",
48
+ "BACKEND_INDEPENDENT_TOOLS",
49
+ "DEVICE_ACTIONS",
50
+ "ExtensionTool",
51
+ "INTERNAL_ACTIONS",
52
+ "MIN_EXTENSION_DESCRIPTION_LENGTH",
53
+ "OPTIONAL_ACTIONS",
54
+ "REQUIRED_ACTIONS",
55
+ "available_device_actions",
56
+ "filter_declarations",
57
+ "validate_actuator",
58
+ ]
59
+
60
+
61
+ # --- Tool classification -------------------------------------------------------------
62
+
63
+ #: Device actions a backend MUST implement. See rule 1 in the module docstring.
64
+ #:
65
+ #: ``click_sequence`` implies the ability to tap, so a backend providing it can
66
+ #: trivially provide ``click`` as well; ``click`` is nonetheless optional because no
67
+ #: prompt depends on it structurally once the enumeration slots are assembled.
68
+ REQUIRED_ACTIONS: frozenset[str] = frozenset(
69
+ {
70
+ "click_sequence",
71
+ }
72
+ )
73
+
74
+ #: Device actions a backend MAY implement. Absent ones vanish from both the tool
75
+ #: declarations and the rendered prompts.
76
+ OPTIONAL_ACTIONS: frozenset[str] = frozenset(
77
+ {
78
+ "click",
79
+ "long_press",
80
+ "input_text",
81
+ "swipe",
82
+ "press_key",
83
+ "manage_app",
84
+ "wait_for_delay",
85
+ # The four below appear in no prompt at all. `wait_for_text` corroborates the
86
+ # classification: the former legacy executor dispatched it, but it has never had
87
+ # a ToolDeclaration, i.e. it has always been unreachable by any LLM.
88
+ "wait_for_text",
89
+ "open_link",
90
+ "erase_one_char",
91
+ "focus_and_clear_text",
92
+ }
93
+ )
94
+
95
+ #: Used by the client-side adapter only; never declared to any LLM, so the
96
+ #: "no cognitive load" rule does not apply. The adapter cannot function without them,
97
+ #: so they are validated as strictly as REQUIRED_ACTIONS.
98
+ INTERNAL_ACTIONS: frozenset[str] = frozenset(
99
+ {
100
+ "observe_screen",
101
+ "take_screenshot",
102
+ "get_ui_hierarchy",
103
+ }
104
+ )
105
+
106
+ #: Tools that are not device control at all -- notes, sub-agents, diagnostics, and the
107
+ #: loop-termination sentinels. They are unaffected by which actuator is installed.
108
+ #:
109
+ #: ``run_adb_command`` and ``manage_app`` ride the *system channel* rather than physical
110
+ #: actuation: a robot arm driving a handset still has ADB attached, so ``am start``
111
+ #: suffices and no icon-hunting is required. (``manage_app`` is nonetheless classified
112
+ #: as an optional device action above, since it is declared through the same path.)
113
+ BACKEND_INDEPENDENT_TOOLS: frozenset[str] = frozenset(
114
+ {
115
+ "read_note",
116
+ "list_notes",
117
+ "save_note",
118
+ "update_note",
119
+ "append_note",
120
+ "ask_explorer",
121
+ "ask_diagnoser",
122
+ "ask_committee",
123
+ "video_analyzer",
124
+ "run_adb_command",
125
+ "manage_task",
126
+ "analyze_task_output",
127
+ "search_history",
128
+ "replay_steps",
129
+ "get_step_screenshot",
130
+ "report_task_status",
131
+ }
132
+ )
133
+
134
+ #: Everything an actuator may legitimately claim in ``capabilities()``.
135
+ DEVICE_ACTIONS: frozenset[str] = REQUIRED_ACTIONS | OPTIONAL_ACTIONS | INTERNAL_ACTIONS
136
+
137
+ #: Every name the manifest knows about. Extension tools must not collide with these.
138
+ ALL_KNOWN_TOOLS: frozenset[str] = DEVICE_ACTIONS | BACKEND_INDEPENDENT_TOOLS
139
+
140
+ #: Agent roles an extension tool may target.
141
+ AGENT_ROLES: frozenset[str] = frozenset(
142
+ {
143
+ "operator",
144
+ "flash",
145
+ "validator",
146
+ }
147
+ )
148
+
149
+ #: Extension descriptions carry the entire usage contract (see ``ExtensionTool``), so a
150
+ #: one-word description is a bug rather than a terse style choice.
151
+ MIN_EXTENSION_DESCRIPTION_LENGTH = 40
152
+
153
+
154
+ class ActuatorContractError(RuntimeError):
155
+ """Raised when an actuator does not satisfy the manifest contract.
156
+
157
+ Always raised at session start rather than mid-run, so a misconfigured backend
158
+ fails before the agent has taken any action on the device.
159
+ """
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class ExtensionTool:
164
+ """A tool contributed by a backend that the manifest does not know about.
165
+
166
+ Extension tools are invisible to the prompt layer by construction: prompts are
167
+ static text that cannot anticipate future backends, so an extension gets **no**
168
+ prompt-level teaching. Its ``description`` must therefore be self-contained --
169
+ stating when to reach for it and what it does -- or the model will never use it
170
+ correctly. :func:`validate_actuator` enforces a length floor as a crude guard.
171
+ """
172
+
173
+ name: str
174
+ description: str
175
+ input_schema: dict[str, Any]
176
+ handler: Callable[..., Awaitable[Any]]
177
+ targets: frozenset[str] = field(default=AGENT_ROLES)
178
+
179
+
180
+ # --- Contract validation -------------------------------------------------------------
181
+
182
+
183
+ def validate_actuator(actuator: Any) -> None:
184
+ """Checks an actuator against the manifest, raising on any violation.
185
+
186
+ Called once from ``get_action_session`` before the first device interaction.
187
+ """
188
+ caps = frozenset(actuator.capabilities())
189
+
190
+ missing_required = REQUIRED_ACTIONS - caps
191
+ if missing_required:
192
+ raise ActuatorContractError(
193
+ f"{type(actuator).__name__} does not implement required action(s): "
194
+ f"{sorted(missing_required)}. These are structurally depended on by prompts "
195
+ f"and cannot be assembled away."
196
+ )
197
+
198
+ missing_internal = INTERNAL_ACTIONS - caps
199
+ if missing_internal:
200
+ raise ActuatorContractError(
201
+ f"{type(actuator).__name__} does not implement internal action(s): "
202
+ f"{sorted(missing_internal)}. The client-side adapter cannot observe the "
203
+ f"device without them."
204
+ )
205
+
206
+ unknown = caps - DEVICE_ACTIONS
207
+ if unknown:
208
+ raise ActuatorContractError(
209
+ f"{type(actuator).__name__} claims capabilities unknown to the manifest: "
210
+ f"{sorted(unknown)}. Backend-specific tools belong in extensions(), not "
211
+ f"capabilities()."
212
+ )
213
+
214
+ _validate_extensions(actuator)
215
+
216
+
217
+ def _validate_extensions(actuator: Any) -> None:
218
+ """Validates extension tools for name collisions and self-sufficient descriptions."""
219
+ extensions = list(actuator.extensions())
220
+ seen: set[str] = set()
221
+
222
+ for ext in extensions:
223
+ if ext.name in ALL_KNOWN_TOOLS:
224
+ raise ActuatorContractError(
225
+ f"Extension tool '{ext.name}' collides with a manifest tool of the same "
226
+ f"name. Extension names must be unique across the whole tool surface."
227
+ )
228
+ if ext.name in seen:
229
+ raise ActuatorContractError(
230
+ f"{type(actuator).__name__} registers extension '{ext.name}' more than once."
231
+ )
232
+ seen.add(ext.name)
233
+
234
+ unknown_targets = frozenset(ext.targets) - AGENT_ROLES
235
+ if unknown_targets:
236
+ raise ActuatorContractError(
237
+ f"Extension tool '{ext.name}' targets unknown agent role(s): "
238
+ f"{sorted(unknown_targets)}. Valid roles: {sorted(AGENT_ROLES)}."
239
+ )
240
+
241
+ if len(ext.description.strip()) < MIN_EXTENSION_DESCRIPTION_LENGTH:
242
+ raise ActuatorContractError(
243
+ f"Extension tool '{ext.name}' has a description of "
244
+ f"{len(ext.description.strip())} characters. Extensions receive no "
245
+ f"prompt-level guidance, so the description must state when to use the "
246
+ f"tool and what it does (minimum "
247
+ f"{MIN_EXTENSION_DESCRIPTION_LENGTH} characters)."
248
+ )
249
+
250
+
251
+ # --- Surface derivation --------------------------------------------------------------
252
+
253
+
254
+ def available_device_actions(actuator: Any) -> frozenset[str]:
255
+ """Returns the LLM-facing device actions this actuator supports.
256
+
257
+ Internal actions are excluded: they are adapter plumbing and never reach a model.
258
+ """
259
+ caps = frozenset(actuator.capabilities())
260
+ return (REQUIRED_ACTIONS | OPTIONAL_ACTIONS) & caps
261
+
262
+
263
+ def filter_declarations(
264
+ declarations: list[Any],
265
+ actuator: Any,
266
+ agent: str,
267
+ ) -> list[Any]:
268
+ """Filters a declaration list down to what ``actuator`` actually provides.
269
+
270
+ Declarations for known tools keep coming from the hand-tuned ``ToolDeclaration``
271
+ constants -- this layer only removes entries, it never rewrites descriptions.
272
+ Extension declarations are appended from the actuator itself, since the manifest
273
+ cannot know them in advance.
274
+
275
+ Args:
276
+ declarations: Declarations the agent would use if every tool existed.
277
+ actuator: The installed backend.
278
+ agent: One of :data:`AGENT_ROLES`; selects which extensions apply.
279
+
280
+ Returns:
281
+ Declarations in their original order, with unavailable device actions dropped
282
+ and this agent's extension tools appended.
283
+ """
284
+ if agent not in AGENT_ROLES:
285
+ raise ValueError(f"Unknown agent role '{agent}'. Valid roles: {sorted(AGENT_ROLES)}.")
286
+
287
+ available = available_device_actions(actuator)
288
+
289
+ kept = [
290
+ decl
291
+ for decl in declarations
292
+ # Only device actions are gated. Backend-independent tools and anything the
293
+ # manifest does not classify (e.g. an agent's own bespoke tool) pass through.
294
+ if _declaration_name(decl) not in DEVICE_ACTIONS or _declaration_name(decl) in available
295
+ ]
296
+
297
+ for ext in actuator.extensions():
298
+ if agent in ext.targets:
299
+ kept.append(_extension_declaration(ext))
300
+
301
+ return kept
302
+
303
+
304
+ def _declaration_name(declaration: Any) -> str:
305
+ """Reads a tool name off either a ToolDeclaration mapping or a LangChain tool."""
306
+ name = getattr(declaration, "name", None)
307
+ if name is None and isinstance(declaration, dict):
308
+ name = declaration.get("name")
309
+ return name or ""
310
+
311
+
312
+ def _extension_declaration(ext: ExtensionTool) -> Any:
313
+ """Renders an ExtensionTool into the same ToolDeclaration shape as known tools."""
314
+ from artemis.core.tool_declaration import ToolDeclaration
315
+
316
+ return ToolDeclaration(
317
+ name=ext.name,
318
+ description=ext.description,
319
+ parameters=ext.input_schema,
320
+ )
@@ -0,0 +1,172 @@
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 one place where the historical verb split is resolved.
16
+
17
+ Canonical tool names are the agent-facing ones (``click``, ``long_press``,
18
+ ``input_text``, ``manage_app``, ...) because those are what the prompts teach and the
19
+ prompts are the contract. The Operator/Validator-internal vocabulary
20
+ (``tap``, ``long_press_on``, ``focus_and_input_text``, ``launch_app``/``stop_app``,
21
+ ``back``) is translated here and nowhere else.
22
+
23
+ ``to_canonical_call`` converts a Validator action item (the dicts Operator writes into
24
+ ``state.structured_decisions``) into a canonical ``(name, kwargs)`` pair for the
25
+ actuator/session layer, using the ``normalized_coordinates`` the Operator already
26
+ computed so the canonical surface never needs a pixel escape hatch.
27
+ """
28
+
29
+ from typing import Any
30
+
31
+ __all__ = ["OPERATOR_ACTION_TO_CANONICAL", "to_canonical_call"]
32
+
33
+ #: Operator-internal action verb -> canonical tool name.
34
+ OPERATOR_ACTION_TO_CANONICAL: dict[str, str] = {
35
+ "tap": "click",
36
+ "long_press_on": "long_press",
37
+ "swipe": "swipe",
38
+ "focus_and_input_text": "input_text",
39
+ "focus_and_clear_text": "focus_and_clear_text",
40
+ "erase_one_char": "erase_one_char",
41
+ "press_key": "press_key",
42
+ "back": "press_key",
43
+ "launch_app": "manage_app",
44
+ "stop_app": "manage_app",
45
+ "open_link": "open_link",
46
+ "wait_for_delay": "wait_for_delay",
47
+ }
48
+
49
+
50
+ def _norm_pair(
51
+ action_item: dict[str, Any], dims: tuple[int, int] | None = None
52
+ ) -> tuple[int, int] | None:
53
+ """Reads the Operator-computed normalized [nx, ny] off an action item.
54
+
55
+ Falls back to converting the pixel ``coordinates`` when the item predates the
56
+ ``normalized_coordinates`` field and screen ``dims`` are provided.
57
+ """
58
+ coords = action_item.get("normalized_coordinates")
59
+ if isinstance(coords, (list, tuple)) and len(coords) >= 2:
60
+ return int(coords[0]), int(coords[1])
61
+
62
+ pixel = action_item.get("coordinates")
63
+ if dims and isinstance(pixel, (list, tuple)) and len(pixel) >= 2:
64
+ width, height = dims
65
+ nx = int(max(0, min(1000, round(int(pixel[0]) * 1000 / max(1, width)))))
66
+ ny = int(max(0, min(1000, round(int(pixel[1]) * 1000 / max(1, height)))))
67
+ return nx, ny
68
+ return None
69
+
70
+
71
+ def to_canonical_call(
72
+ action_item: dict[str, Any], dims: tuple[int, int] | None = None
73
+ ) -> tuple[str, dict[str, Any]]:
74
+ """Translates a Validator action item into a canonical (tool, kwargs) call.
75
+
76
+ Args:
77
+ action_item: One entry of the Operator's structured decisions.
78
+ dims: Optional (width, height) for pixel->normalized fallback on legacy items
79
+ lacking ``normalized_coordinates``.
80
+
81
+ Raises:
82
+ ValueError: for an unknown action verb or an item missing the coordinates the
83
+ canonical call requires.
84
+ """
85
+ verb = str(action_item.get("action", ""))
86
+ canonical = OPERATOR_ACTION_TO_CANONICAL.get(verb)
87
+ if canonical is None:
88
+ raise ValueError(f"Unsupported action: {verb}")
89
+
90
+ if verb == "tap":
91
+ pair = _norm_pair(action_item, dims)
92
+ if pair is None:
93
+ raise ValueError("Invalid coords")
94
+ return "click", {
95
+ "target": [pair[0], pair[1]],
96
+ "times": action_item.get("times") or action_item.get("click_times") or 1,
97
+ "delay_ms": action_item.get("delay_ms") or action_item.get("delay") or 100,
98
+ }
99
+
100
+ if verb == "long_press_on":
101
+ pair = _norm_pair(action_item, dims)
102
+ if pair is None:
103
+ raise ValueError("Invalid coords")
104
+ return "long_press", {
105
+ "target": [pair[0], pair[1]],
106
+ "duration_ms": action_item.get("duration", 1000),
107
+ }
108
+
109
+ if verb == "swipe":
110
+ coords = action_item.get("normalized_coordinates")
111
+ if not (isinstance(coords, (list, tuple)) and len(coords) == 4):
112
+ pixel = action_item.get("coordinates")
113
+ if dims and isinstance(pixel, (list, tuple)) and len(pixel) == 4:
114
+ width, height = dims
115
+ coords = [
116
+ int(max(0, min(1000, round(int(pixel[0]) * 1000 / max(1, width))))),
117
+ int(max(0, min(1000, round(int(pixel[1]) * 1000 / max(1, height))))),
118
+ int(max(0, min(1000, round(int(pixel[2]) * 1000 / max(1, width))))),
119
+ int(max(0, min(1000, round(int(pixel[3]) * 1000 / max(1, height))))),
120
+ ]
121
+ else:
122
+ raise ValueError("Invalid coords")
123
+ return "swipe", {
124
+ "start": [int(coords[0]), int(coords[1])],
125
+ "end": [int(coords[2]), int(coords[3])],
126
+ "duration_ms": action_item.get("duration", 400),
127
+ }
128
+
129
+ if verb == "focus_and_input_text":
130
+ pair = _norm_pair(action_item, dims)
131
+ if pair is None:
132
+ raise ValueError("Invalid coords")
133
+ return "input_text", {
134
+ "text": action_item.get("text", ""),
135
+ "target": [pair[0], pair[1]],
136
+ "clear_exist": bool(action_item.get("clear_before_input", False)),
137
+ }
138
+
139
+ if verb == "focus_and_clear_text":
140
+ pair = _norm_pair(action_item, dims)
141
+ if pair is None:
142
+ raise ValueError("Invalid coords")
143
+ return "focus_and_clear_text", {"target": [pair[0], pair[1]]}
144
+
145
+ if verb == "erase_one_char":
146
+ return "erase_one_char", {}
147
+
148
+ if verb == "press_key":
149
+ keycode = str(action_item.get("keycode", ""))
150
+ # Operator emits Android-style KEYCODE_* names; the canonical tool takes the
151
+ # bare key word for the common keys. Anything else keeps its original spelling
152
+ # so the driver can forward it verbatim (arbitrary KEYCODE_* / numeric codes).
153
+ bare = keycode.removeprefix("KEYCODE_").lower() if keycode else ""
154
+ known = {"home", "back", "enter", "delete", "tab", "search", "menu", "app_switch"}
155
+ return "press_key", {"key": bare if bare in known else keycode}
156
+
157
+ if verb == "back":
158
+ return "press_key", {"key": "back"}
159
+
160
+ if verb in ("launch_app", "stop_app"):
161
+ return "manage_app", {
162
+ "action": "launch" if verb == "launch_app" else "stop",
163
+ "app_name": action_item.get("app_name", ""),
164
+ }
165
+
166
+ if verb == "open_link":
167
+ return "open_link", {"url": action_item.get("url", "")}
168
+
169
+ if verb == "wait_for_delay":
170
+ return "wait_for_delay", {"time_in_ms": action_item.get("time_in_ms", 0)}
171
+
172
+ raise ValueError(f"Unsupported action: {verb}")
@@ -0,0 +1,202 @@
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 unified device action MCP server.
16
+
17
+ ``build_action_server`` turns an actuator into a ``FastMCP`` server that registers
18
+ **only** the tools the actuator implements, plus its extension tools -- so
19
+ ``list_tools()`` is clean by construction and an absent optional tool is invisible to
20
+ every consumer.
21
+
22
+ Return protocol: every device action returns a ``CallToolResult`` whose
23
+ ``structuredContent`` is an ``ActionResult`` -- ``ok``/``code`` carry the real status
24
+ (killing the historical ``"Error" in text`` sniffing) while ``content`` keeps the
25
+ historical human-readable message. ``isError`` is reserved for protocol-level
26
+ failures; a device refusing an action is ``isError=False, ok=False`` so it reaches the
27
+ model as an observation, not an exception.
28
+
29
+ Exceptions raised by the actuator are wrapped here with the historical
30
+ ``"Error during X: {e}"`` wording the agents' transcripts already contain.
31
+ """
32
+
33
+ import asyncio
34
+ from typing import Annotated, Any
35
+
36
+ from mcp.server.fastmcp import FastMCP
37
+ from mcp.types import CallToolResult, ImageContent, TextContent
38
+
39
+ from artemis.mcp.action_manifest import validate_actuator
40
+ from artemis.mcp.action_specs import (
41
+ exception_prefix,
42
+ make_wire_handler,
43
+ wire_dialects,
44
+ )
45
+ from artemis.mcp.action_types import ActionCode, ActionResult, ObserveResult
46
+ from artemis.mcp.actuators.base import Actuator
47
+ from artemis.mcp.observation import observe as observe_impl
48
+ from third_party.mobile_use.utils.logger import get_logger
49
+
50
+ logger = get_logger(__name__)
51
+
52
+ __all__ = ["build_action_server"]
53
+
54
+
55
+ def _wrap(res: ActionResult) -> CallToolResult:
56
+ return CallToolResult(
57
+ content=[TextContent(type="text", text=res.message)],
58
+ structuredContent=res.model_dump(mode="json"),
59
+ isError=False,
60
+ )
61
+
62
+
63
+ def _wrap_exception(action: str, exc: Exception) -> CallToolResult:
64
+ return _wrap(
65
+ ActionResult.failure(action, f"{exception_prefix(action)}: {exc}", detail=repr(exc))
66
+ )
67
+
68
+
69
+ def build_action_server(actuator: Actuator, name: str = "artemis_actions") -> FastMCP:
70
+ """Builds a FastMCP server exposing exactly what ``actuator`` implements.
71
+
72
+ Raises ``ActuatorContractError`` when the actuator violates the manifest, so a
73
+ misconfigured backend fails before serving anything.
74
+ """
75
+ validate_actuator(actuator)
76
+ caps = actuator.capabilities()
77
+ mcp = FastMCP(name)
78
+
79
+ # --- Device actions (registered only when implemented) ---------------------------
80
+ # Declarations and argument conversion both come from the canonical manifest
81
+ # (artemis/mcp/action_specs.py); this server contributes only the transport
82
+ # wrapping, so the served schema cannot drift from the manifest.
83
+
84
+ for spec in wire_dialects():
85
+ if spec.name in caps:
86
+ mcp.add_tool(
87
+ make_wire_handler(spec, actuator, _wrap, _wrap_exception),
88
+ name=spec.name,
89
+ description=spec.wire.description,
90
+ )
91
+
92
+ # --- Internal observation tools (never declared to an LLM) -----------------------
93
+
94
+ if "observe_screen" in caps:
95
+
96
+ @mcp.tool()
97
+ async def observe_screen(
98
+ settle_ms: int = 400, include_image: bool = False
99
+ ) -> Annotated[CallToolResult, ObserveResult]:
100
+ """Capture and index the current screen (adapter-internal)."""
101
+ try:
102
+ obs, img_bytes = await observe_impl(
103
+ getattr(actuator, "ctx", None),
104
+ actuator.controller,
105
+ settle_ms=settle_ms,
106
+ )
107
+ content: list[Any] = [TextContent(type="text", text=obs.message)]
108
+ if include_image and img_bytes:
109
+ import base64 as _b64
110
+
111
+ content.append(
112
+ ImageContent(
113
+ type="image",
114
+ data=_b64.b64encode(img_bytes).decode("utf-8"),
115
+ mimeType="image/jpeg",
116
+ )
117
+ )
118
+ return CallToolResult(
119
+ content=content,
120
+ structuredContent=obs.model_dump(mode="json"),
121
+ isError=False,
122
+ )
123
+ except Exception as e:
124
+ obs = ObserveResult(
125
+ ok=False,
126
+ code=ActionCode.DEVICE_ERROR,
127
+ message=f"Error during observe_screen: {e}",
128
+ hierarchy_ok=False,
129
+ )
130
+ return CallToolResult(
131
+ content=[TextContent(type="text", text=obs.message)],
132
+ structuredContent=obs.model_dump(mode="json"),
133
+ isError=False,
134
+ )
135
+
136
+ # Timeouts on the two observation tools run INSIDE the tool, around the actuator
137
+ # call only. They must never be expressed as a transport-level read timeout
138
+ # (``read_timeout_seconds``) or a caller-side ``asyncio.wait_for``: both produce a
139
+ # late response to an abandoned request, which crashes the mcp-1.29 client
140
+ # receive loop (``_handle_response`` sends into the request's already-closed
141
+ # response stream) and bricks the in-memory session for every later caller.
142
+ # A server-side timeout yields a normal error payload and no late response.
143
+
144
+ if "take_screenshot" in caps:
145
+
146
+ @mcp.tool()
147
+ async def take_screenshot(timeout_ms: int | None = None) -> CallToolResult:
148
+ """Return the current screen as base64 (adapter-internal, no persistence)."""
149
+ try:
150
+ coro = actuator.take_screenshot()
151
+ if timeout_ms:
152
+ coro = asyncio.wait_for(coro, timeout=timeout_ms / 1000.0)
153
+ b64 = await coro
154
+ payload: dict[str, Any] = {"ok": True, "image_b64": b64}
155
+ except TimeoutError:
156
+ payload = {"ok": False, "error": f"screenshot timed out after {timeout_ms}ms"}
157
+ except Exception as e:
158
+ payload = {"ok": False, "error": str(e)}
159
+ return CallToolResult(
160
+ content=[
161
+ TextContent(type="text", text="screenshot" if payload["ok"] else str(payload))
162
+ ],
163
+ structuredContent=payload,
164
+ isError=False,
165
+ )
166
+
167
+ if "get_ui_hierarchy" in caps:
168
+
169
+ @mcp.tool()
170
+ async def get_ui_hierarchy(timeout_ms: int | None = None) -> CallToolResult:
171
+ """Return the raw UI element hierarchy (adapter-internal)."""
172
+ try:
173
+ coro = actuator.get_ui_elements()
174
+ if timeout_ms:
175
+ coro = asyncio.wait_for(coro, timeout=timeout_ms / 1000.0)
176
+ elements = await coro
177
+ payload: dict[str, Any] = {"ok": True, "elements": elements}
178
+ except TimeoutError:
179
+ payload = {
180
+ "ok": False,
181
+ "error": f"ui hierarchy fetch timed out after {timeout_ms}ms",
182
+ }
183
+ except Exception as e:
184
+ payload = {"ok": False, "error": str(e)}
185
+ return CallToolResult(
186
+ content=[
187
+ TextContent(type="text", text="ui_hierarchy" if payload["ok"] else str(payload))
188
+ ],
189
+ structuredContent=payload,
190
+ isError=False,
191
+ )
192
+
193
+ # --- Backend extension tools -----------------------------------------------------
194
+
195
+ for ext in actuator.extensions():
196
+ mcp.add_tool(ext.handler, name=ext.name, description=ext.description)
197
+
198
+ logger.info(
199
+ f"Action server '{name}' built with {len(caps)} device actions and"
200
+ f" {len(actuator.extensions())} extension tools."
201
+ )
202
+ return mcp