android-midscene-automation 0.2.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/README.md +63 -1
  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 +10 -8
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +13 -1
  507. package/src/App.vue +72 -23
  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 +369 -186
  525. package/src/types.ts +1 -1
@@ -0,0 +1,508 @@
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
+ """Artemis Admin & Trace Console Server
16
+
17
+ Modular entrypoint for full trace inspection, step replay, and task execution management.
18
+ """
19
+
20
+ import argparse
21
+ import asyncio
22
+ from contextlib import asynccontextmanager
23
+ import os
24
+ from pathlib import Path
25
+ import secrets
26
+ import signal
27
+ import sys
28
+ from types import FrameType
29
+
30
+ # Bootstrap sys.path to allow running from any CWD
31
+ _current_p = Path(__file__).resolve().parent
32
+ while _current_p != _current_p.parent:
33
+ if (_current_p / "pyproject.toml").exists() or (_current_p / "artemis").is_dir():
34
+ _workspace_root = _current_p
35
+ break
36
+ _current_p = _current_p.parent
37
+ else:
38
+ _workspace_root = Path(__file__).resolve().parent.parent.parent
39
+
40
+ _apps_dir = _workspace_root / "apps"
41
+ _admin_console_dir = _apps_dir / "admin_console"
42
+ _cloud_service_dir = _apps_dir / "cloud_service"
43
+
44
+ for _p in (str(_workspace_root), str(_apps_dir), str(_admin_console_dir), str(_cloud_service_dir)):
45
+ if _p not in sys.path:
46
+ sys.path.insert(0, _p)
47
+
48
+ from fastapi import FastAPI
49
+ from fastapi.responses import FileResponse, HTMLResponse
50
+ import uvicorn
51
+
52
+ from artemis.runtime import (
53
+ DeviceExecutionLock,
54
+ clear_server_info,
55
+ device_pool,
56
+ shutdown_awake_service,
57
+ start_awake_service,
58
+ write_server_info,
59
+ )
60
+
61
+ from artemis.config import (
62
+ DB_PATH,
63
+ IMAGES_DIR,
64
+ REPLAY_BASE_DIR,
65
+ TEST_DATA_DIR,
66
+ TEST_OUTPUTS_DIR,
67
+ TRACES_PATH,
68
+ WORKSPACE_ROOT,
69
+ init_ls_address,
70
+ )
71
+ from artemis.resources import get_bundled_showcase_dist
72
+
73
+ try:
74
+ from admin_console.core.security import SameOriginBoundaryMiddleware
75
+ from admin_console.core.state import ServerState, state
76
+ from admin_console.database.connection import db_session, get_db
77
+ from admin_console.database.repositories.session_repository import session_repo
78
+ from admin_console.database.repositories.step_repository import step_repo
79
+ from admin_console.database.repositories.trace_repository import trace_repo
80
+ from admin_console.routers import media, replay, sessions, steps, stream, system, tasks
81
+ from admin_console.routers.replay import replay_manager
82
+ from admin_console.services.ipc_service import ipc_service
83
+ from admin_console.services.media_service import media_service
84
+ from admin_console.services.model_service import model_service
85
+ from admin_console.services.task_queue_service import task_queue_service
86
+ except ImportError:
87
+ from apps.admin_console.core.security import SameOriginBoundaryMiddleware
88
+ from apps.admin_console.core.state import state
89
+ from apps.admin_console.database.repositories.session_repository import session_repo
90
+ from apps.admin_console.routers import media, replay, sessions, steps, stream, system, tasks
91
+ from apps.admin_console.routers.replay import replay_manager
92
+ from apps.admin_console.services.ipc_service import ipc_service
93
+ from apps.admin_console.services.model_service import model_service
94
+ from apps.admin_console.services.task_queue_service import task_queue_service
95
+
96
+ # Initialize language server synchronization address
97
+ init_ls_address()
98
+
99
+
100
+ @asynccontextmanager
101
+ async def _lifespan(_app: "FastAPI"):
102
+ """Start server resources and release them on shutdown."""
103
+ await on_startup()
104
+ try:
105
+ yield
106
+ finally:
107
+ await on_shutdown()
108
+
109
+
110
+ # Initialize FastAPI application
111
+ app = FastAPI(title="Artemis Admin & Trace Console", lifespan=_lifespan)
112
+ LIFECYCLE_TOKEN = os.environ.get("ARTEMIS_LIFECYCLE_TOKEN") or secrets.token_urlsafe(32)
113
+ app.state.lifecycle_token = LIFECYCLE_TOKEN
114
+
115
+ # The console UI is served same-origin from this process, so no CORS grants
116
+ # exist at all; the boundary middleware rejects cross-origin browser traffic
117
+ # and unrecognized Host headers (DNS rebinding) instead.
118
+ app.add_middleware(SameOriginBoundaryMiddleware)
119
+
120
+
121
+ async def on_startup():
122
+ """Startup lifecycle hooks."""
123
+ state.is_shutting_down = False
124
+ state.shutdown_event.clear()
125
+ write_server_info(
126
+ port=getattr(state, "port", 8000),
127
+ host=getattr(state, "host", "127.0.0.1"),
128
+ lifecycle_token=LIFECYCLE_TOKEN,
129
+ )
130
+ await asyncio.to_thread(start_awake_service)
131
+ cleaned_device_locks = DeviceExecutionLock.cleanup_stale_locks()
132
+ if cleaned_device_locks:
133
+ print(f"[ServerStartup] Removed {cleaned_device_locks} stale device lock(s).")
134
+
135
+ # Warm the adb server and complete one enumeration before requests are
136
+ # served, so submissions in the first seconds never race an adb cold start
137
+ # and get a false "no devices attached" rejection.
138
+ try:
139
+ adb_warmed = await device_pool.warm_up_async()
140
+ except Exception as exc:
141
+ adb_warmed = False
142
+ print(f"[ServerStartup] ADB warm-up failed: {exc}")
143
+ if not adb_warmed:
144
+ print(
145
+ "[ServerStartup] ADB warm-up did not confirm a responding adb server; "
146
+ "device enumeration will retry on demand."
147
+ )
148
+ asyncio.create_task(asyncio.to_thread(task_queue_service.archive_older_replays_on_launch))
149
+ asyncio.create_task(
150
+ asyncio.to_thread(task_queue_service.verify_chunks_exist_on_launch, replay_manager)
151
+ )
152
+
153
+ cleaned = session_repo.cleanup_orphans_on_startup()
154
+ if cleaned > 0:
155
+ print(f"[ServerStartup] Marked {cleaned} orphaned running session(s) as failed.")
156
+ # Workers killed together with a previous daemon never remuxed their
157
+ # recordings; publish whatever raw files they left behind.
158
+ asyncio.create_task(asyncio.to_thread(task_queue_service.recover_orphaned_recordings_on_launch))
159
+
160
+ await ipc_service.start_server()
161
+ state.worker_task = asyncio.create_task(task_queue_service.queue_worker())
162
+
163
+
164
+ async def on_shutdown():
165
+ """Stop task and IPC children before the UI server exits."""
166
+ state.is_shutting_down = True
167
+ state.shutdown_event.set()
168
+ task_queue_service._broadcast_event("server_shutdown", {"status": "stopping"})
169
+ owned_session_ids = {
170
+ str(item["session_id"])
171
+ for item in state.queue_items
172
+ if isinstance(item, dict) and item.get("status") == "running" and item.get("session_id")
173
+ }
174
+
175
+ worker = state.worker_task
176
+ if worker is not None and not worker.done():
177
+ worker.cancel()
178
+ try:
179
+ await asyncio.wait_for(worker, timeout=5.0)
180
+ except (asyncio.CancelledError, TimeoutError):
181
+ pass
182
+ state.worker_task = None
183
+
184
+ # Cancel in-flight run coroutines and wait for their finalizers (DB status,
185
+ # session_ended broadcast, trace sync, recording recovery) to run.
186
+ run_tasks = [t for t in list(task_queue_service._run_tasks) if not t.done()]
187
+ for run_task in run_tasks:
188
+ run_task.cancel()
189
+ if run_tasks:
190
+ try:
191
+ await asyncio.wait_for(asyncio.gather(*run_tasks, return_exceptions=True), timeout=5.0)
192
+ except (TimeoutError, asyncio.CancelledError):
193
+ pass
194
+ for run in list(state.active_runs.values()):
195
+ run_proc = run.get("process")
196
+ if run_proc is not None and run_proc.returncode is None:
197
+ await task_queue_service._terminate_worker_process(run_proc)
198
+ state.active_runs.clear()
199
+ if state.current_process is not None and state.current_process.returncode is None:
200
+ await task_queue_service._terminate_worker_process(state.current_process)
201
+ for item in state.queue_items:
202
+ if isinstance(item, dict):
203
+ DeviceExecutionLock.cancel_reservation(item.get("queue_ticket"))
204
+ DeviceExecutionLock.cleanup_stale_locks()
205
+ state.current_process = None
206
+ state.queue_items.clear()
207
+ for session_id in owned_session_ids:
208
+ session_repo.update_session_status(session_id, "cancelled")
209
+
210
+ await ipc_service.stop_server()
211
+ state.ipc_subscribers.clear()
212
+ await asyncio.to_thread(shutdown_awake_service)
213
+ clear_server_info(
214
+ port=getattr(state, "port", 8000),
215
+ lifecycle_token=LIFECYCLE_TOKEN,
216
+ )
217
+
218
+
219
+ # Mount modular routers
220
+ app.include_router(stream.router)
221
+ app.include_router(media.router)
222
+ app.include_router(sessions.router)
223
+ app.include_router(steps.router)
224
+ app.include_router(tasks.router)
225
+ app.include_router(replay.router)
226
+ app.include_router(system.router)
227
+
228
+ # Mount cloud gateway router for Frappe / Cloud integration if present
229
+ try:
230
+ from cloud_service.gateway import cloud_router
231
+
232
+ app.include_router(cloud_router)
233
+ except ImportError:
234
+ try:
235
+ from apps.cloud_service.gateway import cloud_router
236
+
237
+ app.include_router(cloud_router)
238
+ except ImportError:
239
+ pass
240
+
241
+
242
+ # ------------------------------------------------------------------------------
243
+ # Showcase UI (Angular 19) Unified Single-Port SPA Static Hosting
244
+ # ------------------------------------------------------------------------------
245
+ def _get_showcase_dist() -> Path:
246
+ """Return the source build, or the immutable build bundled in a wheel."""
247
+ base_dist = _workspace_root / "apps" / "showcase_ui" / "dist"
248
+ candidates = [
249
+ base_dist / "frontend" / "browser",
250
+ base_dist / "browser",
251
+ base_dist / "frontend",
252
+ base_dist,
253
+ ]
254
+ for candidate in candidates:
255
+ if candidate.is_dir() and (candidate / "index.html").exists():
256
+ return candidate
257
+ bundled_dist = get_bundled_showcase_dist()
258
+ if bundled_dist is not None:
259
+ return bundled_dist
260
+ return base_dist / "frontend" / "browser"
261
+
262
+
263
+ def _resolve_static_file(root: Path, relative_path: str) -> Path | None:
264
+ """Return the requested file only when it truly lives under ``root``."""
265
+ try:
266
+ root = root.resolve()
267
+ target = (root / relative_path).resolve()
268
+ except (OSError, ValueError):
269
+ return None
270
+ if target.is_file() and target.is_relative_to(root):
271
+ return target
272
+ return None
273
+
274
+
275
+ @app.get("/", include_in_schema=False)
276
+ async def serve_showcase_root():
277
+ """Explicitly serve the Showcase UI at the root path by default."""
278
+ return await serve_showcase_spa("")
279
+
280
+
281
+ @app.get("/{full_path:path}", include_in_schema=False)
282
+ async def serve_showcase_spa(full_path: str):
283
+ # Do not intercept API, media, or replay paths
284
+ if (
285
+ full_path.startswith("api/")
286
+ or full_path.startswith("images/")
287
+ or full_path.startswith("videos/")
288
+ or full_path.startswith("local_file")
289
+ or full_path == "docs"
290
+ or full_path == "openapi.json"
291
+ or full_path.startswith("redoc")
292
+ ):
293
+ from fastapi import HTTPException
294
+
295
+ raise HTTPException(status_code=404, detail="Endpoint not found")
296
+
297
+ clean_path = full_path.strip("/")
298
+
299
+ # Admin / Debug Console routes
300
+ if (
301
+ clean_path in ("admin", "debug")
302
+ or clean_path.startswith("admin/")
303
+ or clean_path.startswith("debug/")
304
+ ):
305
+ admin_index = _admin_console_dir / "index.html"
306
+ if admin_index.exists():
307
+ return HTMLResponse(admin_index.read_text(encoding="utf-8"))
308
+
309
+ showcase_dist = _get_showcase_dist()
310
+
311
+ # Exact static file match (js, css, images, fonts, favicon, logo).
312
+ # Resolve and re-anchor under the static root so encoded traversal
313
+ # sequences cannot address files outside the build directories.
314
+ if clean_path:
315
+ target_file = _resolve_static_file(showcase_dist, clean_path)
316
+ if target_file is not None:
317
+ return FileResponse(target_file)
318
+
319
+ # Fallback for icons/logos if showcase UI hasn't been built yet
320
+ public_root = _workspace_root / "apps" / "showcase_ui" / "public"
321
+ public_file = _resolve_static_file(public_root, clean_path)
322
+ if public_file is not None:
323
+ return FileResponse(public_file)
324
+
325
+ # Serve Showcase UI Angular SPA index.html
326
+ index_file = showcase_dist / "index.html"
327
+ if index_file.exists():
328
+ return HTMLResponse(index_file.read_text(encoding="utf-8"))
329
+
330
+ # Fallback message if Showcase UI is not built
331
+ fallback_html = """<!DOCTYPE html>
332
+ <html lang="en">
333
+ <head>
334
+ <meta charset="UTF-8">
335
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
336
+ <title>Artemis Showcase UI - Not Built</title>
337
+ <style>
338
+ body {
339
+ font-family: system-ui, -apple-system, sans-serif;
340
+ background: #0f172a;
341
+ color: #f8fafc;
342
+ display: flex;
343
+ align-items: center;
344
+ justify-content: center;
345
+ height: 100vh;
346
+ margin: 0;
347
+ }
348
+ .card {
349
+ background: #1e293b;
350
+ padding: 2.5rem;
351
+ border-radius: 1rem;
352
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
353
+ max-width: 500px;
354
+ text-align: center;
355
+ border: 1px solid #334155;
356
+ }
357
+ h1 { color: #38bdf8; margin-top: 0; }
358
+ code {
359
+ background: #0f172a;
360
+ padding: 0.25rem 0.5rem;
361
+ border-radius: 0.375rem;
362
+ color: #e2e8f0;
363
+ font-size: 0.9em;
364
+ }
365
+ .btn {
366
+ display: inline-block;
367
+ margin-top: 1.5rem;
368
+ padding: 0.75rem 1.5rem;
369
+ background: #38bdf8;
370
+ color: #0f172a;
371
+ font-weight: bold;
372
+ text-decoration: none;
373
+ border-radius: 0.5rem;
374
+ transition: background 0.2s;
375
+ }
376
+ .btn:hover { background: #0284c7; color: #fff; }
377
+ </style>
378
+ </head>
379
+ <body>
380
+ <div class="card">
381
+ <h1>✨ Artemis Showcase UI</h1>
382
+ <p>The Showcase UI (Angular frontend) has not been built yet.</p>
383
+ <p>To compile the Showcase UI, run:</p>
384
+ <p><code>cd apps/showcase_ui && npm install && npm run build</code></p>
385
+ <p>Or launch using <code>./start.sh</code> or <code>artemis ui</code> to build automatically.</p>
386
+ <a class="btn" href="/admin">Go to Admin Debug Console →</a>
387
+ </div>
388
+ </body>
389
+ </html>"""
390
+ return HTMLResponse(fallback_html)
391
+
392
+
393
+ # ------------------------------------------------------------------------------
394
+ # Backward compatibility exports
395
+ # ------------------------------------------------------------------------------
396
+ get_active_model_info = model_service.get_active_model_info
397
+ start_ipc_server = ipc_service.start_server
398
+ queue_worker = task_queue_service.queue_worker
399
+ active_connections = state.active_connections
400
+ ipc_subscribers = state.ipc_subscribers
401
+ task_queue = state.task_queue
402
+ queue_goals = state.queue_goals
403
+
404
+
405
+ class ArtemisUvicornServer(uvicorn.Server):
406
+ """Notify application streams before Uvicorn waits for them to close."""
407
+
408
+ @staticmethod
409
+ def _task_is_active() -> bool:
410
+ """Check task state without doing process I/O from a signal handler."""
411
+ proc = state.current_process
412
+ if proc is not None and proc.returncode is None:
413
+ return True
414
+ return any(
415
+ isinstance(item, dict) and item.get("status") == "running" for item in state.queue_items
416
+ )
417
+
418
+ @staticmethod
419
+ def _schedule_signal_report(message: str) -> None:
420
+ """Defer console output so a signal cannot re-enter Colorama writes."""
421
+ try:
422
+ asyncio.get_running_loop().call_soon(print, message)
423
+ except RuntimeError:
424
+ try:
425
+ os.write(2, f"{message}\n".encode(errors="replace"))
426
+ except OSError:
427
+ pass
428
+
429
+ def handle_exit(self, sig: int, frame: FrameType | None) -> None:
430
+ try:
431
+ signal_name = signal.Signals(sig).name
432
+ except ValueError:
433
+ signal_name = "UNKNOWN"
434
+ frame_location = "unknown"
435
+ if frame is not None:
436
+ frame_location = f"{frame.f_code.co_filename}:{frame.f_lineno}"
437
+ signal_report = (
438
+ f"[ServerSignal] Received {signal_name} ({sig}); "
439
+ f"server PID={os.getpid()}, parent PID={os.getppid()}, frame={frame_location}"
440
+ )
441
+ self._schedule_signal_report(signal_report)
442
+
443
+ if sys.platform == "win32" and sig == signal.SIGINT and self._task_is_active():
444
+ self._schedule_signal_report(
445
+ "[ServerSignal] Ignored Windows SIGINT while a mobile task is active. "
446
+ "Stop the task first, or press Ctrl+Break to force server shutdown."
447
+ )
448
+ return
449
+
450
+ state.is_shutting_down = True
451
+ state.shutdown_event.set()
452
+ super().handle_exit(sig, frame)
453
+
454
+
455
+ def run_ui_server(host: str, port: int, reload: bool = False) -> None:
456
+ """Run the UI server with bounded, signal-aware graceful shutdown."""
457
+ state.host = host
458
+ state.port = port
459
+ write_server_info(port=port, host=host, lifecycle_token=LIFECYCLE_TOKEN)
460
+
461
+ try:
462
+ if reload:
463
+ uvicorn.run(
464
+ "apps.admin_console.server:app",
465
+ host=host,
466
+ port=port,
467
+ reload=True,
468
+ timeout_graceful_shutdown=5,
469
+ )
470
+ return
471
+
472
+ config = uvicorn.Config(
473
+ app,
474
+ host=host,
475
+ port=port,
476
+ timeout_graceful_shutdown=5,
477
+ )
478
+ server = ArtemisUvicornServer(config)
479
+ app.state.uvicorn_server = server
480
+ server.run()
481
+ finally:
482
+ app.state.uvicorn_server = None
483
+ clear_server_info(port=port, lifecycle_token=LIFECYCLE_TOKEN)
484
+
485
+
486
+ def main(argv: list[str] | None = None) -> None:
487
+ """Parse the daemon/standalone server bind address and run Uvicorn."""
488
+ parser = argparse.ArgumentParser(description="Run the Artemis Admin Console server")
489
+ parser.add_argument(
490
+ "--host",
491
+ default=os.environ.get("ARTEMIS_SERVER_HOST", "127.0.0.1"),
492
+ help=(
493
+ "Address to bind the HTTP server to. Defaults to loopback; expose "
494
+ "remotely via a Tailscale/SSH tunnel rather than a wide bind."
495
+ ),
496
+ )
497
+ parser.add_argument(
498
+ "--port",
499
+ type=int,
500
+ default=int(os.environ.get("ANTIGRAVITY_SIDECAR_WEB_PORT", "8000")),
501
+ help="TCP port for the HTTP server.",
502
+ )
503
+ args = parser.parse_args(argv)
504
+ run_ui_server(host=args.host, port=args.port)
505
+
506
+
507
+ if __name__ == "__main__":
508
+ main()
@@ -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,137 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Device Live Screen Streaming Service.
16
+
17
+ Provides real-time, low-latency device screen frames over HTTP MJPEG and WebSocket.
18
+ Operates concurrently with ADB agent actions with zero interference.
19
+ """
20
+
21
+ import asyncio
22
+ import logging
23
+ import subprocess
24
+ import time
25
+ from collections.abc import AsyncGenerator
26
+
27
+ from artemis.toolchain import find_adb
28
+
29
+ logger = logging.getLogger("artemis.stream_service")
30
+
31
+
32
+ class DeviceStreamService:
33
+ """Manages real-time screen capture and distribution to web clients."""
34
+
35
+ def __init__(self):
36
+ self._active_listeners = 0
37
+ self._lock = asyncio.Lock()
38
+ self._latest_frame: bytes | None = None
39
+ self._last_frame_time: float = 0.0
40
+ self._is_capturing = False
41
+ self._capture_task: asyncio.Task | None = None
42
+
43
+ async def get_device_serial(self) -> str | None:
44
+ """Find the currently connected active ADB device serial."""
45
+ try:
46
+ adb_bin = find_adb()
47
+ proc = await asyncio.create_subprocess_exec(
48
+ adb_bin,
49
+ "devices",
50
+ stdout=subprocess.PIPE,
51
+ stderr=subprocess.PIPE,
52
+ )
53
+ stdout, _ = await proc.communicate()
54
+ lines = stdout.decode().strip().splitlines()
55
+ for line in lines[1:]:
56
+ parts = line.strip().split()
57
+ if len(parts) >= 2 and parts[1] == "device":
58
+ return parts[0]
59
+ except Exception as e:
60
+ logger.warning(f"Error checking adb devices: {e}")
61
+ return None
62
+
63
+ async def _capture_loop(self):
64
+ """Background frame capture loop that runs while listeners > 0."""
65
+ logger.info("[StreamService] Starting live screen capture loop...")
66
+ while self._active_listeners > 0:
67
+ try:
68
+ start_t = time.time()
69
+ serial = await self.get_device_serial()
70
+ adb_bin = find_adb()
71
+ cmd = (
72
+ [adb_bin, "-s", serial, "exec-out", "screencap", "-p"]
73
+ if serial
74
+ else [adb_bin, "exec-out", "screencap", "-p"]
75
+ )
76
+ proc = await asyncio.create_subprocess_exec(
77
+ *cmd,
78
+ stdout=subprocess.PIPE,
79
+ stderr=subprocess.PIPE,
80
+ )
81
+ stdout, _ = await proc.communicate()
82
+ if proc.returncode == 0 and len(stdout) > 1000:
83
+ self._latest_frame = stdout
84
+ self._last_frame_time = time.time()
85
+
86
+ elapsed = time.time() - start_t
87
+ delay = max(0.03, 0.08 - elapsed)
88
+ await asyncio.sleep(delay)
89
+ except asyncio.CancelledError:
90
+ break
91
+ except Exception as e:
92
+ logger.error(f"[StreamService] Capture error: {e}")
93
+ await asyncio.sleep(0.5)
94
+
95
+ logger.info("[StreamService] Stopping live screen capture loop (0 listeners).")
96
+ self._is_capturing = False
97
+
98
+ async def start_capturing(self):
99
+ """Register a new listener and start background capture if needed."""
100
+ async with self._lock:
101
+ self._active_listeners += 1
102
+ if not self._is_capturing or self._capture_task is None or self._capture_task.done():
103
+ self._is_capturing = True
104
+ self._capture_task = asyncio.create_task(self._capture_loop())
105
+
106
+ async def stop_capturing(self):
107
+ """Deregister a listener and stop capture loop when count reaches 0."""
108
+ async with self._lock:
109
+ self._active_listeners = max(0, self._active_listeners - 1)
110
+ if self._active_listeners == 0 and self._capture_task and not self._capture_task.done():
111
+ self._capture_task.cancel()
112
+ self._is_capturing = False
113
+
114
+ async def mjpeg_frame_generator(self) -> AsyncGenerator[bytes, None]:
115
+ """Async generator streaming MJPEG multipart bytes to HTTP response."""
116
+ await self.start_capturing()
117
+ try:
118
+ last_sent_time = 0.0
119
+ while True:
120
+ if self._latest_frame and self._last_frame_time > last_sent_time:
121
+ last_sent_time = self._last_frame_time
122
+ frame_bytes = self._latest_frame
123
+ yield (
124
+ b"--frame\r\n"
125
+ b"Content-Type: image/png\r\n"
126
+ b"Content-Length: "
127
+ + str(len(frame_bytes)).encode()
128
+ + b"\r\n\r\n"
129
+ + frame_bytes
130
+ + b"\r\n"
131
+ )
132
+ await asyncio.sleep(0.04)
133
+ finally:
134
+ await self.stop_capturing()
135
+
136
+
137
+ device_stream_service = DeviceStreamService()