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,517 @@
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
+ import asyncio
16
+ from contextlib import suppress
17
+ import json
18
+ from typing import Any
19
+ from fastapi import APIRouter, HTTPException, Request
20
+ from fastapi.responses import StreamingResponse
21
+ from artemis.core.diagnostics import readiness_engine
22
+ from artemis.runtime import DeviceExecutionLock, device_pool
23
+
24
+ try:
25
+ from admin_console.core.state import state
26
+ from admin_console.database.repositories.session_repository import session_repo
27
+ from admin_console.schemas.task_schema import RunRequest
28
+ from admin_console.services.ipc_service import ipc_service
29
+ from admin_console.services.model_service import model_service
30
+ from admin_console.services.task_preset_catalog import task_recommendation_engine
31
+ from admin_console.services.task_queue_service import task_queue_service
32
+ except ImportError:
33
+ from apps.admin_console.core.state import state
34
+ from apps.admin_console.database.repositories.session_repository import session_repo
35
+ from apps.admin_console.schemas.task_schema import RunRequest
36
+ from apps.admin_console.services.ipc_service import ipc_service
37
+ from apps.admin_console.services.model_service import model_service
38
+ from apps.admin_console.services.task_preset_catalog import task_recommendation_engine
39
+ from apps.admin_console.services.task_queue_service import task_queue_service
40
+
41
+
42
+ router = APIRouter(tags=["tasks"])
43
+
44
+
45
+ @router.get("/api/tasks/presets")
46
+ async def get_task_presets(
47
+ category: str = "recommended",
48
+ packages: str | None = None,
49
+ limit: int = 24,
50
+ ):
51
+ """Retrieve smart task recommendations optionally tailored to detected device packages."""
52
+ pkg_list = [p.strip() for p in packages.split(",") if p.strip()] if packages else None
53
+ return task_recommendation_engine.recommend_tasks(
54
+ installed_packages=pkg_list,
55
+ category=category,
56
+ limit=limit,
57
+ )
58
+
59
+
60
+ @router.get("/api/tasks/catalog")
61
+ async def get_task_catalog():
62
+ """Retrieve full catalog of predefined tasks and app package registry."""
63
+ return {
64
+ "tasks": [t.model_dump() for t in task_recommendation_engine.get_all_tasks()],
65
+ "app_registry": task_recommendation_engine.get_app_registry(),
66
+ }
67
+
68
+
69
+ @router.post("/api/run")
70
+ async def run_task(request: RunRequest):
71
+ incoming_goals = []
72
+ if request.goals:
73
+ incoming_goals = request.goals
74
+ elif request.goal:
75
+ incoming_goals = [request.goal]
76
+
77
+ if not incoming_goals:
78
+ raise HTTPException(
79
+ status_code=400,
80
+ detail="Either 'goal' or 'goals' list must be provided.",
81
+ )
82
+
83
+ # Idempotent SDK retries must never re-run device readiness checks. A task
84
+ # can hold the device while its admission response is lost in transit; in
85
+ # that state, probing the same device again may fail or block even though
86
+ # the original task was accepted successfully.
87
+ if request.session_id and len(incoming_goals) == 1:
88
+ requested_sid = str(request.session_id)
89
+ existing_item = next(
90
+ (
91
+ item
92
+ for item in state.queue_items
93
+ if isinstance(item, dict) and str(item.get("session_id")) == requested_sid
94
+ ),
95
+ None,
96
+ )
97
+ persisted_session = session_repo.get_session_by_id(requested_sid)
98
+ is_active = (
99
+ str(state.active_session_id) == requested_sid
100
+ or requested_sid in state.active_connections
101
+ )
102
+ if existing_item or persisted_session or is_active:
103
+ task_payload = dict(existing_item or persisted_session or {})
104
+ task_payload.setdefault("session_id", requested_sid)
105
+ task_payload.setdefault("goal", incoming_goals[0])
106
+ task_payload.setdefault("profile", request.profile or "flash")
107
+ task_payload.setdefault("device_serial", request.device_serial)
108
+ task_payload.setdefault("status", "running" if is_active else "queued")
109
+ return {
110
+ "status": task_payload["status"],
111
+ "tasks": [task_payload],
112
+ "enqueued_count": 0,
113
+ "total_queued": len(state.queue_tasks),
114
+ }
115
+
116
+ # Reject an explicit unknown/offline target before running the more
117
+ # expensive readiness probe. Besides producing a stable SDK response,
118
+ # this avoids probing the currently active device for a serial that can
119
+ # never be selected. Only a successful, non-empty enumeration may reject:
120
+ # an indeterminate one (adb blip, startup) lets the submission queue and
121
+ # fail downstream with a clear error instead.
122
+ if request.device_serial:
123
+ try:
124
+ rejection = await device_pool.validate_explicit_serial_async(request.device_serial)
125
+ except Exception:
126
+ rejection = None
127
+ if rejection:
128
+ return {
129
+ "status": "rejected",
130
+ "error": rejection,
131
+ "tasks": [],
132
+ "enqueued_count": 0,
133
+ "total_queued": len(state.queue_tasks),
134
+ }
135
+
136
+ # Re-check immediately before enqueueing so a device locked between UI
137
+ # polling intervals cannot start through a stale Ready state. Use the
138
+ # bounded submission probe: the full diagnostics path also scans packages,
139
+ # emulator installations, Android version, and screen size.
140
+ # With no explicit serial the probe itself resolves a live target (it
141
+ # prefers the diagnostics target preference, then any unlocked ready
142
+ # device); the verified serial is bound below.
143
+ target_serial = request.device_serial
144
+ device_probe = await readiness_engine.run_device_submission_probe(target_serial=target_serial)
145
+ if device_probe and device_probe.summary in {"Device Locked", "Lock State Unknown"}:
146
+ locked_serial = (
147
+ device_probe.metadata.get("active_device", {}).get("serial") or target_serial or ""
148
+ )
149
+ detail = (
150
+ f"Android device {locked_serial} is locked. Unlock it and enter the home screen before running a task.".replace(
151
+ " ", " "
152
+ ).strip()
153
+ if device_probe.summary == "Device Locked"
154
+ else f"Android device {locked_serial} lock state could not be verified. Keep it unlocked on the home screen and try again.".replace(
155
+ " ", " "
156
+ ).strip()
157
+ )
158
+ raise HTTPException(status_code=409, detail=detail)
159
+
160
+ if device_probe and device_probe.metadata.get("active_device"):
161
+ verified_serial = device_probe.metadata["active_device"].get("serial")
162
+ # Only auto-selected targets may be re-bound to the probed device. An
163
+ # explicitly requested serial is never silently replaced -- if it is
164
+ # invalid, enqueue_tasks rejects the submission with a clear error.
165
+ if verified_serial and not request.device_serial:
166
+ target_serial = verified_serial
167
+
168
+ return await task_queue_service.enqueue_tasks(
169
+ incoming_goals,
170
+ profile=request.profile or "flash",
171
+ expected_output=request.expected_output,
172
+ enable_outputter=request.enable_outputter,
173
+ verification_level=request.verification_level,
174
+ explorer_mode=request.explorer_mode,
175
+ locked_app_package=request.locked_app_package,
176
+ app_path=request.app_path,
177
+ device_serial=target_serial,
178
+ ingress=request.ingress or "frontend",
179
+ session_id=request.session_id,
180
+ conversation_id=request.conversation_id,
181
+ )
182
+
183
+
184
+ @router.get("/api/run/defaults")
185
+ async def get_run_defaults():
186
+ """Effective Pro-profile tuning defaults from the agent config.
187
+
188
+ The launcher's sliders start here so they reflect ``artemis.jsonc`` (and the
189
+ ``ARTEMIS_EXPLORER_VERSION`` override) instead of a hard-coded guess.
190
+ """
191
+ from artemis.config import load_agent_config, verification_level_for_checker
192
+
193
+ agent_cfg = load_agent_config()
194
+ return {
195
+ "verification_level": verification_level_for_checker(agent_cfg.checker),
196
+ "explorer_mode": agent_cfg.explorer.resolve(profile="pro"),
197
+ }
198
+
199
+
200
+ @router.get("/api/devices")
201
+ async def list_devices():
202
+ """List all connected Android devices with their busy / idle status."""
203
+ devices = await device_pool.list_devices_async()
204
+ return {"devices": [d.to_dict() for d in devices]}
205
+
206
+
207
+ @router.post("/api/stop")
208
+ async def stop_task(
209
+ request: Request,
210
+ all: bool = False,
211
+ session_id: str | None = None,
212
+ device_id: str | None = None,
213
+ ):
214
+ target_all = all
215
+ target_sid = session_id
216
+ target_dev = device_id
217
+
218
+ try:
219
+ body = await request.json()
220
+ if isinstance(body, dict):
221
+ if "all" in body:
222
+ target_all = bool(body["all"]) or target_all
223
+ if body.get("session_id"):
224
+ target_sid = str(body["session_id"])
225
+ if body.get("device_id"):
226
+ target_dev = str(body["device_id"])
227
+ except ValueError:
228
+ # Empty or non-JSON body: fall back to the query parameters.
229
+ pass
230
+
231
+ # stop_tasks updates scheduler state and asyncio events owned by this loop.
232
+ stopped = task_queue_service.stop_tasks(
233
+ clear_all=target_all,
234
+ session_id=target_sid,
235
+ device_id=target_dev,
236
+ )
237
+ if stopped:
238
+ return {"status": "stopped", "session_id": target_sid}
239
+ return {"status": "no_running_task"}
240
+
241
+
242
+ @router.post("/api/resume")
243
+ async def resume_task():
244
+ resumed = task_queue_service.resume_task()
245
+ if resumed:
246
+ return {"status": "resumed"}
247
+ return {"status": "not_paused"}
248
+
249
+
250
+ @router.get("/api/status")
251
+ async def get_status():
252
+ # Watchdog check to ensure background worker is alive
253
+ task_queue_service.ensure_worker_running()
254
+
255
+ latest_session = session_repo.get_latest_session()
256
+ latest_session_id = latest_session.get("session_id") if latest_session else None
257
+ bg_tasks = session_repo.get_background_tasks(latest_session_id) if latest_session_id else []
258
+
259
+ global_owner = DeviceExecutionLock.get_active_owner()
260
+ is_running = state.is_running or global_owner is not None
261
+ running_task = next(
262
+ (t for t in state.queue_items if isinstance(t, dict) and t.get("status") == "running"), None
263
+ )
264
+ if not running_task and is_running:
265
+ running_task = next(
266
+ (t for t in state.queue_items if isinstance(t, dict) and t.get("status") == "pending"),
267
+ None,
268
+ )
269
+
270
+ active_owners = DeviceExecutionLock.get_active_owners()
271
+ active_tasks = [
272
+ {
273
+ "device_id": owner.device_id,
274
+ "session_id": owner.session_id,
275
+ "goal": owner.description,
276
+ "pid": owner.pid,
277
+ "ingress": owner.ingress,
278
+ "acquired_at": owner.acquired_at,
279
+ }
280
+ for owner in active_owners.values()
281
+ ]
282
+
283
+ running_sid = (
284
+ (global_owner.session_id if global_owner else None)
285
+ or state.active_session_id
286
+ or (running_task.get("session_id") if running_task else None)
287
+ or (
288
+ active_tasks[0]["session_id"]
289
+ if active_tasks and active_tasks[0].get("session_id")
290
+ else None
291
+ )
292
+ or (session_repo.get_running_session_id() if is_running else None)
293
+ )
294
+ owner_connection = state.active_connections.get(str(running_sid), {}) if running_sid else {}
295
+ running_goal = (
296
+ owner_connection.get("goal")
297
+ or (global_owner.description if global_owner else None)
298
+ or state.current_goal
299
+ or (running_task.get("goal") if running_task else None)
300
+ or (active_tasks[0]["goal"] if active_tasks else None)
301
+ )
302
+
303
+ active_profile = (
304
+ owner_connection.get("profile")
305
+ or state.current_profile
306
+ or (running_task.get("profile") if running_task and not global_owner else None)
307
+ )
308
+ if not active_profile and (running_sid or latest_session_id):
309
+ check_sid = running_sid or latest_session_id
310
+ sess_row = session_repo.get_session_by_id(check_sid)
311
+ if sess_row:
312
+ llm_traces = session_repo.get_llm_traces_for_profile(check_sid)
313
+ agent_names = session_repo.get_agent_trace_names(check_sid)
314
+ active_profile = model_service.resolve_session_profile(
315
+ sess_row, llm_traces, agent_names=agent_names
316
+ )
317
+
318
+ model_info = model_service.get_active_model_info(active_profile)
319
+
320
+ # Unified Global Queue: merge web tasks and external SDK/CLI device queue tickets
321
+ global_queued = DeviceExecutionLock.get_queued_tasks()
322
+ seen_ids = set()
323
+ queue_data: list[dict[str, Any]] = []
324
+
325
+ for item in state.queue_tasks:
326
+ sid = item.get("session_id")
327
+ if sid:
328
+ seen_ids.add(str(sid))
329
+ queue_data.append(item)
330
+
331
+ for g_item in global_queued:
332
+ sid = str(g_item.get("session_id"))
333
+ if sid not in seen_ids:
334
+ seen_ids.add(sid)
335
+ queue_data.append(g_item)
336
+
337
+ if is_running:
338
+ is_paused = state.is_paused
339
+ return {
340
+ "status": "paused" if is_paused else "running",
341
+ "paused_error": state.paused_error if is_paused else None,
342
+ "goal": running_goal,
343
+ "pid": (
344
+ global_owner.pid
345
+ if global_owner
346
+ else state.current_process.pid
347
+ if state.current_process
348
+ else None
349
+ ),
350
+ "session_id": running_sid,
351
+ "background_tasks": bg_tasks,
352
+ "queue": queue_data,
353
+ "model_info": model_info,
354
+ "active_tasks": active_tasks,
355
+ }
356
+
357
+ if latest_session_id and str(latest_session_id) in state.active_connections:
358
+ conn_info = state.active_connections[str(latest_session_id)]
359
+ is_paused = state.is_paused
360
+ conn_profile = conn_info.get("profile") or active_profile
361
+ conn_model_info = model_service.get_active_model_info(conn_profile)
362
+ return {
363
+ "status": "paused" if is_paused else "running",
364
+ "paused_error": state.paused_error if is_paused else None,
365
+ "goal": conn_info.get("goal"),
366
+ "pid": conn_info.get("pid"),
367
+ "session_id": latest_session_id,
368
+ "background_tasks": bg_tasks,
369
+ "queue": queue_data,
370
+ "model_info": conn_model_info,
371
+ "active_tasks": active_tasks,
372
+ "ipc_port": state.ipc_port,
373
+ }
374
+
375
+ return {
376
+ "status": "idle",
377
+ "session_id": latest_session_id,
378
+ "background_tasks": bg_tasks,
379
+ "queue": queue_data,
380
+ "active_tasks": active_tasks,
381
+ "model_info": model_info,
382
+ "ipc_port": state.ipc_port,
383
+ }
384
+
385
+
386
+ @router.get("/api/stream")
387
+ @router.get("/api/stream/{session_id}")
388
+ async def stream_events(session_id: str = "active", client: str | None = None):
389
+ async def event_generator():
390
+ queue = asyncio.Queue()
391
+ event_loop = asyncio.get_running_loop()
392
+
393
+ def callback(event_type, data):
394
+ try:
395
+ # Global queue lifecycle events should always be delivered
396
+ if event_type not in (
397
+ "session_started",
398
+ "session_ended",
399
+ "background_tasks_updated",
400
+ ):
401
+ # Filter events by session_id when subscribed to a specific session
402
+ if session_id and session_id not in ("all", "active"):
403
+ evt_session_id = None
404
+ if isinstance(data, dict):
405
+ evt_session_id = data.get("session_id")
406
+
407
+ if evt_session_id and str(evt_session_id) != str(session_id):
408
+ return
409
+
410
+ if (
411
+ not evt_session_id
412
+ and state.active_session_id
413
+ and str(state.active_session_id) != str(session_id)
414
+ ):
415
+ return
416
+
417
+ sanitized_data = ipc_service.sanitize_event_data(event_type, data)
418
+ event_loop.call_soon_threadsafe(queue.put_nowait, (event_type, sanitized_data))
419
+ except RuntimeError:
420
+ pass
421
+
422
+ state.add_subscriber(callback)
423
+ yield f'event: info\ndata: {{"message": "Subscribed to session {session_id}"}}\n\n'
424
+
425
+ if session_id in ("all", "active"):
426
+ active_sid = state.active_session_id
427
+ if not active_sid:
428
+ running_item = next(
429
+ (
430
+ t
431
+ for t in state.queue_items
432
+ if isinstance(t, dict) and t.get("status") == "running"
433
+ ),
434
+ None,
435
+ )
436
+ if running_item:
437
+ active_sid = running_item.get("session_id")
438
+ if not active_sid:
439
+ owner = DeviceExecutionLock.get_active_owner()
440
+ if owner and owner.session_id:
441
+ active_sid = owner.session_id
442
+
443
+ if active_sid:
444
+ goal = state.current_goal or ""
445
+ profile = state.current_profile or "flash"
446
+ yield (
447
+ "event: session_started\n"
448
+ f"data: {json.dumps({'session_id': str(active_sid), 'initial_goal': goal, 'profile': profile}, default=str)}\n\n"
449
+ )
450
+ for progress_event in state.get_startup_progress(str(active_sid)):
451
+ yield (
452
+ "event: startup_progress\n"
453
+ f"data: {json.dumps(progress_event, default=str)}\n\n"
454
+ )
455
+ try:
456
+ from apps.admin_console.database.repositories.step_repository import step_repo
457
+
458
+ recorded_steps = step_repo.get_session_steps(str(active_sid))
459
+ for step_dict in recorded_steps:
460
+ yield (
461
+ f"event: step_recorded\ndata: {json.dumps(step_dict, default=str)}\n\n"
462
+ )
463
+ except Exception as exc:
464
+ print(f"[Stream] Could not replay active steps: {exc}")
465
+ else:
466
+ for progress_event in state.get_startup_progress(session_id):
467
+ yield (
468
+ f"event: startup_progress\ndata: {json.dumps(progress_event, default=str)}\n\n"
469
+ )
470
+
471
+ shutdown_waiter = asyncio.create_task(state.shutdown_event.wait())
472
+ queue_waiter = None
473
+ try:
474
+ while not state.is_shutting_down:
475
+ queue_waiter = asyncio.create_task(queue.get())
476
+ done, _ = await asyncio.wait(
477
+ {queue_waiter, shutdown_waiter},
478
+ timeout=5.0,
479
+ return_when=asyncio.FIRST_COMPLETED,
480
+ )
481
+ if shutdown_waiter in done:
482
+ queue_waiter.cancel()
483
+ with suppress(asyncio.CancelledError):
484
+ await queue_waiter
485
+ queue_waiter = None
486
+ break
487
+ if queue_waiter in done:
488
+ event_type, data = queue_waiter.result()
489
+ queue_waiter = None
490
+ yield f"event: {event_type}\ndata: {json.dumps(data, default=str)}\n\n"
491
+ else:
492
+ queue_waiter.cancel()
493
+ with suppress(asyncio.CancelledError):
494
+ await queue_waiter
495
+ queue_waiter = None
496
+ yield "event: keep-alive\ndata: {}\n\n"
497
+ except asyncio.CancelledError:
498
+ raise
499
+ finally:
500
+ waiters = (queue_waiter, shutdown_waiter)
501
+ for waiter in waiters:
502
+ if waiter is not None and not waiter.done():
503
+ waiter.cancel()
504
+ for waiter in waiters:
505
+ if waiter is not None:
506
+ with suppress(asyncio.CancelledError):
507
+ await waiter
508
+ state.remove_subscriber(callback)
509
+
510
+ return StreamingResponse(
511
+ event_generator(),
512
+ media_type="text/event-stream",
513
+ headers={
514
+ "Cache-Control": "no-cache, no-transform",
515
+ "X-Accel-Buffering": "no",
516
+ },
517
+ )
@@ -0,0 +1,13 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,47 @@
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
+ from pydantic import BaseModel
16
+
17
+
18
+ class RunRequest(BaseModel):
19
+ goal: str | None = None
20
+ goals: list[str] | None = None
21
+ profile: str | None = "flash"
22
+ expected_output: str | None = None
23
+ enable_outputter: bool | None = None
24
+ # Pro-profile tuning (ignored by the Flash profile): a coarse Checker preset
25
+ # ('off' | 'final' | 'checkpoints' | 'strict') and the Explorer perception
26
+ # version used by the Operator ('flash' | 'pro' | 'ultra').
27
+ verification_level: str | None = None
28
+ explorer_mode: str | None = None
29
+ locked_app_package: str | None = None
30
+ app_path: str | None = None
31
+ device_serial: str | None = None
32
+ ingress: str | None = "frontend"
33
+ session_id: str | None = None
34
+ conversation_id: str | None = None
35
+
36
+
37
+ class ReplayRequest(BaseModel):
38
+ device_id: str
39
+ user_submits: dict
40
+ tool_name: str = "ask_explorer"
41
+ replay_id: str | None = None
42
+
43
+
44
+ class StopRequest(BaseModel):
45
+ session_id: str | None = None
46
+ device_id: str | None = None
47
+ all: bool = False