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,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,198 @@
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 pathlib import Path
17
+ from fastapi import APIRouter, HTTPException
18
+ from fastapi.responses import FileResponse, HTMLResponse
19
+
20
+ from artemis.config import IMAGES_DIR, TRACES_PATH, WORKSPACE_ROOT
21
+
22
+ try:
23
+ from admin_console.database.repositories.session_repository import session_repo
24
+ from admin_console.services.media_service import media_service
25
+ except ImportError:
26
+ from apps.admin_console.database.repositories.session_repository import session_repo
27
+ from apps.admin_console.services.media_service import media_service
28
+
29
+ router = APIRouter(tags=["media"])
30
+
31
+ _VIDEO_MEDIA_TYPES = {
32
+ ".mp4": "video/mp4",
33
+ ".webm": "video/webm",
34
+ ".mkv": "video/x-matroska",
35
+ }
36
+
37
+
38
+ def _allowed_media_roots() -> list[Path]:
39
+ """Directories the media endpoints are permitted to serve from."""
40
+ roots = []
41
+ for root in (WORKSPACE_ROOT, TRACES_PATH):
42
+ try:
43
+ roots.append(Path(root).resolve())
44
+ except OSError:
45
+ continue
46
+ return roots
47
+
48
+
49
+ def _resolve_media_path(raw_path: str, allowed_suffixes: set[str]) -> Path:
50
+ """Resolve a client-supplied path strictly inside the allowed media roots.
51
+
52
+ Every candidate is fully resolved (symlinks, `..`, mixed separators) and
53
+ then re-checked against the allowed roots and an extension allowlist, so a
54
+ crafted URL can never address source code, databases, or dotfiles.
55
+ """
56
+ roots = _allowed_media_roots()
57
+ candidate = Path(raw_path)
58
+ attempts = [candidate] if candidate.is_absolute() else [root / candidate for root in roots]
59
+ for attempt in attempts:
60
+ try:
61
+ resolved = attempt.resolve(strict=True)
62
+ except (OSError, ValueError):
63
+ continue
64
+ if not resolved.is_file():
65
+ continue
66
+ if not any(resolved.is_relative_to(root) for root in roots):
67
+ continue
68
+ if resolved.suffix.lower() not in allowed_suffixes:
69
+ raise HTTPException(status_code=403, detail="File type is not served by the media API.")
70
+ return resolved
71
+ raise HTTPException(status_code=404, detail="Media file not found")
72
+
73
+
74
+ @router.get("/admin", response_class=HTMLResponse)
75
+ @router.get("/debug", response_class=HTMLResponse)
76
+ async def get_admin_index():
77
+ admin_index = Path(__file__).resolve().parent.parent / "index.html"
78
+ if admin_index.exists():
79
+ return admin_index.read_text(encoding="utf-8")
80
+
81
+ admin_index_alt = WORKSPACE_ROOT / "apps" / "admin_console" / "index.html"
82
+ if admin_index_alt.exists():
83
+ return admin_index_alt.read_text(encoding="utf-8")
84
+
85
+ return "<h1>Admin Console index.html not found</h1>"
86
+
87
+
88
+ @router.get("/images/{image_name}")
89
+ @router.get("/api/images/{image_name}")
90
+ async def get_image(image_name: str):
91
+ if not image_name.endswith(".jpg"):
92
+ image_name += ".jpg"
93
+ try:
94
+ images_root = IMAGES_DIR.resolve()
95
+ image_path = (images_root / image_name).resolve(strict=True)
96
+ except (OSError, ValueError):
97
+ raise HTTPException(status_code=404, detail="Image not found")
98
+ if not image_path.is_file() or not image_path.is_relative_to(images_root):
99
+ raise HTTPException(status_code=404, detail="Image not found")
100
+
101
+ return FileResponse(image_path, media_type="image/jpeg")
102
+
103
+
104
+ @router.get("/videos/{video_path:path}")
105
+ async def get_video(video_path: str):
106
+ path = _resolve_media_path(video_path, set(_VIDEO_MEDIA_TYPES))
107
+ return FileResponse(path, media_type=_VIDEO_MEDIA_TYPES[path.suffix.lower()])
108
+
109
+
110
+ @router.get("/api/sessions/{session_id}/video")
111
+ async def get_session_video(session_id: str):
112
+ # Blocking work (sqlite, filesystem scan, possible ffmpeg conversion) runs
113
+ # off the event loop.
114
+ return await asyncio.to_thread(_get_session_video_sync, session_id)
115
+
116
+
117
+ def _get_session_video_sync(session_id: str):
118
+ video_rec_map = session_repo.get_video_recordings_map()
119
+ video_idx = media_service.build_video_index()
120
+ row = session_repo.get_session_by_id(session_id)
121
+ row_dict = dict(row) if row else {"session_id": session_id}
122
+ recording = session_repo.get_video_recording_for_session(session_id)
123
+ recording_status = str((recording or {}).get("status") or "")
124
+
125
+ if recording_status in ("recording", "finalizing"):
126
+ return {
127
+ "session_id": session_id,
128
+ "status": "processing",
129
+ "has_video": False,
130
+ "video_url": None,
131
+ "video_segments": [],
132
+ "retry_after_ms": 750,
133
+ }
134
+
135
+ if recording_status == "failed":
136
+ v_url = media_service.resolve_video_url(row_dict, video_rec_map, video_idx)
137
+ if not v_url:
138
+ return {
139
+ "session_id": session_id,
140
+ "status": "failed",
141
+ "has_video": False,
142
+ "video_url": None,
143
+ "video_segments": [],
144
+ "message": recording.get("error") or "Recording finalization failed",
145
+ }
146
+ # If a video was recovered/found, update DB to ready and proceed to serve it
147
+ session_repo.mark_recording_ready(session_id, v_url)
148
+
149
+ v_url = media_service.resolve_video_url(row_dict, video_rec_map, video_idx)
150
+ video_segments = media_service.resolve_video_segments(v_url)
151
+ if v_url:
152
+ version = int(
153
+ float((recording or {}).get("end_time") or row_dict.get("end_time") or 0) * 1000
154
+ )
155
+ separator = "&" if "?" in v_url else "?"
156
+ versioned_url = f"{v_url}{separator}v={version}" if version else v_url
157
+ for segment in video_segments:
158
+ segment_separator = "&" if "?" in segment["url"] else "?"
159
+ segment["url"] = (
160
+ f"{segment['url']}{segment_separator}v={version}" if version else segment["url"]
161
+ )
162
+ return {
163
+ "session_id": session_id,
164
+ "status": "ready",
165
+ "has_video": True,
166
+ "video_url": versioned_url,
167
+ "video_segments": video_segments,
168
+ }
169
+
170
+ return {
171
+ "session_id": session_id,
172
+ "status": "unavailable",
173
+ "has_video": False,
174
+ "video_url": None,
175
+ "video_segments": [],
176
+ }
177
+
178
+
179
+ @router.get("/local_file")
180
+ async def get_local_file(path: str):
181
+ p, media_type = media_service.get_safe_local_file(path)
182
+ return FileResponse(p, media_type=media_type)
183
+
184
+
185
+ @router.get("/api/sessions/{session_id}/plan")
186
+ async def get_task_plan(session_id: str):
187
+ return {"plan": media_service.get_task_plan_content(session_id)}
188
+
189
+
190
+ @router.get("/api/sessions/{session_id}/notes")
191
+ async def get_all_notes(session_id: str):
192
+ return {"notes": media_service.get_session_notes_content(session_id)}
193
+
194
+
195
+ @router.get("/api/sessions/{session_id}/checks")
196
+ async def get_session_checks(session_id: str):
197
+ """Checker verdict ledger + run outcome (backfill for the Checker panel)."""
198
+ return media_service.get_session_checks(session_id)
@@ -0,0 +1,91 @@
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 traceback
16
+ from fastapi import APIRouter, HTTPException
17
+
18
+ from artemis.config import WORKSPACE_ROOT
19
+
20
+ try:
21
+ from admin_console.schemas.task_schema import ReplayRequest
22
+ from admin_console.replay_manager import ReplayManager
23
+ except ImportError:
24
+ from apps.admin_console.schemas.task_schema import ReplayRequest
25
+ from apps.admin_console.replay_manager import ReplayManager
26
+
27
+ replay_manager = ReplayManager(WORKSPACE_ROOT)
28
+
29
+
30
+ router = APIRouter(tags=["replay"])
31
+
32
+
33
+ @router.get("/api/devices")
34
+ async def list_devices():
35
+ """Dynamically queries the ADB server for connected Android devices."""
36
+ return replay_manager.list_devices()
37
+
38
+
39
+ @router.get("/api/replay/tools")
40
+ async def get_replay_tools():
41
+ """Returns the list of registered tools available for replay."""
42
+ return replay_manager.get_replay_tools()
43
+
44
+
45
+ @router.get("/api/replay/config")
46
+ async def get_replay_config(tool_name: str = "ask_explorer"):
47
+ """Returns the parameter list and types of the selected tool logic."""
48
+ try:
49
+ return replay_manager.get_replay_config(tool_name)
50
+ except Exception as e:
51
+ raise HTTPException(status_code=500, detail=str(e))
52
+
53
+
54
+ @router.get("/api/sessions/{session_id}/replay_steps")
55
+ async def get_replay_steps(session_id: str):
56
+ """Loads and formats metadata for all chunked steps in the session."""
57
+ try:
58
+ return replay_manager.get_replay_steps(session_id)
59
+ except FileNotFoundError as e:
60
+ raise HTTPException(status_code=404, detail=str(e))
61
+ except Exception as e:
62
+ raise HTTPException(status_code=500, detail=str(e))
63
+
64
+
65
+ @router.post("/api/sessions/{session_id}/steps/{step_number}/replay")
66
+ async def trigger_step_replay_endpoint(session_id: str, step_number: int, req: ReplayRequest):
67
+ """Triggers sandbox execution of step replay and returns the resulting traces trees."""
68
+ try:
69
+ return await replay_manager.run_step_replay(
70
+ session_id=session_id,
71
+ step_number=step_number,
72
+ device_id=req.device_id,
73
+ user_submits=req.user_submits,
74
+ tool_name=req.tool_name,
75
+ replay_id=req.replay_id,
76
+ )
77
+ except Exception as e:
78
+ traceback.print_exc()
79
+ raise HTTPException(status_code=500, detail=str(e))
80
+
81
+
82
+ @router.get("/api/sessions/{session_id}/steps/{step_number}/replay_traces")
83
+ async def get_step_replay_traces_endpoint(
84
+ session_id: str, step_number: int, tool_name: str = "ask_explorer"
85
+ ):
86
+ """Retrieves previously generated step replay traces if they exist."""
87
+ try:
88
+ return replay_manager.get_step_replay_traces(session_id, step_number, tool_name)
89
+ except Exception as e:
90
+ traceback.print_exc()
91
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,239 @@
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
+ import json
17
+ import time
18
+ from uuid import UUID
19
+ from fastapi import APIRouter, HTTPException
20
+
21
+ from artemis.config import DB_PATH, TRACES_PATH
22
+ from artemis.runtime import trace_store
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.database.repositories.trace_repository import trace_repo
28
+ from admin_console.services.media_service import media_service
29
+ from admin_console.services.model_service import model_service
30
+ except ImportError:
31
+ from apps.admin_console.core.state import state
32
+ from apps.admin_console.database.repositories.session_repository import session_repo
33
+ from apps.admin_console.database.repositories.trace_repository import trace_repo
34
+ from apps.admin_console.services.media_service import media_service
35
+ from apps.admin_console.services.model_service import model_service
36
+
37
+
38
+ router = APIRouter(tags=["sessions"])
39
+
40
+
41
+ @router.get("/api/sessions")
42
+ async def list_sessions():
43
+ # The body does blocking work (sqlite queries, filesystem scans, ffmpeg
44
+ # conversion on first sight of a new recording), so run it off the event
45
+ # loop — the frontend polls this endpoint and it must not stall other
46
+ # requests.
47
+ try:
48
+ return await asyncio.to_thread(_list_sessions_sync)
49
+ except Exception as e:
50
+ raise HTTPException(status_code=500, detail=str(e))
51
+
52
+
53
+ def _list_sessions_sync():
54
+ rows = session_repo.get_all_sessions()
55
+ video_rec_map = session_repo.get_video_recordings_map()
56
+ latest_recordings = session_repo.get_latest_video_recordings_map()
57
+ agent_names_by_session = session_repo.get_agent_trace_names_map()
58
+ video_idx = media_service.build_video_index()
59
+ default_model_info = model_service.get_active_model_info()
60
+
61
+ orphaned_ids = []
62
+ unresolved_profiles = []
63
+ result = []
64
+
65
+ try:
66
+ from artemis.runtime import DeviceExecutionLock
67
+
68
+ active_owners = DeviceExecutionLock.get_active_owners()
69
+ active_owner_sids = {
70
+ str(owner.session_id) for owner in active_owners.values() if owner.session_id
71
+ }
72
+ except Exception:
73
+ active_owner_sids = set()
74
+
75
+ for row_dict in rows:
76
+ s_id = str(row_dict.get("session_id"))
77
+ recording = latest_recordings.get(s_id)
78
+ d_info_raw = row_dict.get("device_info")
79
+ device_id = None
80
+ if d_info_raw:
81
+ try:
82
+ d_info = json.loads(d_info_raw) if isinstance(d_info_raw, str) else d_info_raw
83
+ if isinstance(d_info, dict):
84
+ device_id = d_info.get("device_id") or d_info.get("device_serial")
85
+ except (ValueError, TypeError):
86
+ # Malformed device_info JSON: fall back to the recording's device_id.
87
+ pass
88
+ if not device_id and recording:
89
+ device_id = recording.get("device_id")
90
+ row_dict["device_id"] = device_id
91
+ row_dict["device_serial"] = device_id
92
+
93
+ if row_dict.get("status") == "running":
94
+ is_active = (
95
+ s_id in active_owner_sids
96
+ or s_id in state.active_connections
97
+ or (state.is_running and s_id == str(state.active_session_id))
98
+ )
99
+ worker_is_alive = session_repo.process_is_alive(row_dict.get("pid"))
100
+ if not is_active and not worker_is_alive:
101
+ row_dict["status"] = "failed"
102
+ row_dict["end_time"] = time.time()
103
+ orphaned_ids.append(s_id)
104
+
105
+ recording_status = str((recording or {}).get("status") or "unavailable")
106
+ resolved_v_url = (
107
+ media_service.resolve_video_url(row_dict, video_rec_map, video_idx)
108
+ if recording_status != "recording"
109
+ else None
110
+ )
111
+ if resolved_v_url:
112
+ recording_status = "ready"
113
+ row_dict["video_url"] = resolved_v_url
114
+ else:
115
+ row_dict["video_url"] = None
116
+ row_dict["recording_status"] = recording_status
117
+
118
+ agent_names = agent_names_by_session.get(s_id, [])
119
+ sess_profile = model_service.resolve_session_profile(
120
+ row_dict, None, state.current_profile, agent_names=agent_names
121
+ )
122
+ row_dict["model_info"] = (
123
+ model_service.get_active_model_info(sess_profile)
124
+ if sess_profile
125
+ else default_model_info
126
+ )
127
+ if not sess_profile:
128
+ unresolved_profiles.append(row_dict)
129
+ result.append(row_dict)
130
+
131
+ # Most sessions carry a profile in device_info or an agent trace name.
132
+ # Only inspect the much larger LLM payload for legacy rows that remain
133
+ # ambiguous after those cheap checks.
134
+ if unresolved_profiles:
135
+ unresolved_ids = [str(row.get("session_id")) for row in unresolved_profiles]
136
+ llm_traces_by_session = session_repo.get_llm_traces_for_profiles_map(unresolved_ids)
137
+ for row_dict in unresolved_profiles:
138
+ s_id = str(row_dict.get("session_id"))
139
+ llm_traces = llm_traces_by_session.get(s_id)
140
+ if not llm_traces:
141
+ continue
142
+ sess_profile = model_service.resolve_session_profile(
143
+ row_dict, llm_traces, state.current_profile
144
+ )
145
+ if sess_profile:
146
+ row_dict["model_info"] = model_service.get_active_model_info(sess_profile)
147
+
148
+ if orphaned_ids:
149
+ try:
150
+ harvested = session_repo.harvest_orphaned_sessions(orphaned_ids)
151
+ print(f"[list_sessions] Auto-harvested {harvested} orphaned running session(s).")
152
+ try:
153
+ for o_id in orphaned_ids:
154
+ if trace_store.read_status(str(o_id)):
155
+ trace_store.update_trace_status(
156
+ str(o_id),
157
+ "failed",
158
+ error="Process terminated unexpectedly (auto-harvested).",
159
+ )
160
+ except OSError as e:
161
+ # read_status never raises; this guards the lock/write side
162
+ # of update_trace_status.
163
+ print(f"[list_sessions] Could not mark harvested traces failed: {e}")
164
+ except Exception as e:
165
+ print(f"[list_sessions] Failed to update orphaned sessions: {e}")
166
+
167
+ return result
168
+
169
+
170
+ @router.get("/api/sessions/{session_id}")
171
+ async def get_session_details(session_id: str):
172
+ """Retrieve details for a single automation session."""
173
+ row = session_repo.get_session_by_id(session_id)
174
+ if not row:
175
+ raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
176
+ return dict(row)
177
+
178
+
179
+ @router.get("/api/sessions/{session_id}/usage")
180
+ async def get_session_usage(session_id: str):
181
+ """Session-wide LLM token totals, live executor context size and run tuning."""
182
+ try:
183
+ return await asyncio.to_thread(session_repo.get_session_usage, session_id)
184
+ except Exception as e:
185
+ raise HTTPException(status_code=500, detail=str(e))
186
+
187
+
188
+ @router.get("/api/sessions/{session_id}/tree")
189
+ async def get_tree(session_id: str):
190
+ try:
191
+ return trace_repo.get_trace_tree(session_id)
192
+ except Exception as e:
193
+ raise HTTPException(status_code=500, detail=str(e))
194
+
195
+
196
+ @router.get("/api/sessions/{session_id}/background_tasks")
197
+ async def get_session_background_tasks(session_id: str):
198
+ try:
199
+ return session_repo.get_background_tasks(session_id)
200
+ except Exception:
201
+ return []
202
+
203
+
204
+ @router.get("/api/sessions/{session_id}/startup_progress")
205
+ async def get_session_startup_progress(session_id: str):
206
+ try:
207
+ return state.get_startup_progress(session_id)
208
+ except Exception:
209
+ return []
210
+
211
+
212
+ @router.post("/api/cleanup")
213
+ async def cleanup_history_endpoint():
214
+ try:
215
+ from artemis.data_engine.storage import StorageManager
216
+
217
+ storage = StorageManager(DB_PATH, TRACES_PATH)
218
+ storage.clear_all_data()
219
+ return {
220
+ "status": "success",
221
+ "message": "History cleaned up successfully",
222
+ }
223
+ except Exception as e:
224
+ raise HTTPException(status_code=500, detail=str(e))
225
+
226
+
227
+ @router.post("/api/sessions/{session_id}/delete")
228
+ async def delete_session_endpoint(session_id: str):
229
+ try:
230
+ from artemis.data_engine.storage import StorageManager
231
+
232
+ storage = StorageManager(DB_PATH, TRACES_PATH)
233
+ storage.delete_session(UUID(session_id))
234
+ return {
235
+ "status": "success",
236
+ "message": f"Session {session_id} deleted successfully",
237
+ }
238
+ except Exception as e:
239
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,109 @@
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 json
16
+ import traceback
17
+ from fastapi import APIRouter, HTTPException
18
+ from fastapi.responses import PlainTextResponse
19
+
20
+ from artemis.config import TEST_OUTPUTS_DIR
21
+
22
+ try:
23
+ from admin_console.database.repositories.step_repository import step_repo
24
+ from admin_console.database.repositories.trace_repository import trace_repo
25
+ from admin_console.services.media_service import media_service
26
+ except ImportError:
27
+ from apps.admin_console.database.repositories.step_repository import step_repo
28
+ from apps.admin_console.database.repositories.trace_repository import trace_repo
29
+ from apps.admin_console.services.media_service import media_service
30
+
31
+
32
+ router = APIRouter(tags=["steps"])
33
+
34
+
35
+ @router.get("/api/sessions/{session_id}/steps")
36
+ async def get_session_steps(session_id: str, client: str | None = None):
37
+ try:
38
+ return step_repo.get_session_steps(session_id, client=client)
39
+ except Exception as e:
40
+ raise HTTPException(status_code=500, detail=str(e))
41
+
42
+
43
+ @router.get("/api/steps/{step_id}/traces")
44
+ async def get_step_traces_endpoint(step_id: str):
45
+ try:
46
+ session_id = step_repo.get_step_session_id(step_id)
47
+ if not session_id:
48
+ raise HTTPException(status_code=404, detail="Step not found")
49
+ return trace_repo.get_step_traces_tree(session_id, step_id)
50
+ except HTTPException as e:
51
+ raise e
52
+ except Exception as e:
53
+ raise HTTPException(status_code=500, detail=str(e))
54
+
55
+
56
+ def _resolve_trace_db_path(session_id: str | None, step_number: int | None):
57
+ if session_id and step_number is not None:
58
+ sandbox_db_path = (
59
+ TEST_OUTPUTS_DIR
60
+ / f"{session_id}_step_{step_number:02d}"
61
+ / "temp_traces"
62
+ / "data_engine.db"
63
+ )
64
+ if sandbox_db_path.exists():
65
+ return sandbox_db_path
66
+ return None
67
+
68
+
69
+ @router.get("/api/traces/{trace_id}")
70
+ async def get_trace(trace_id: str, session_id: str = None, step_number: int = None):
71
+ try:
72
+ db_path = _resolve_trace_db_path(session_id, step_number)
73
+ trace_dict = trace_repo.get_trace_by_id(trace_id, db_path=db_path)
74
+ if not trace_dict:
75
+ raise HTTPException(status_code=404, detail="Trace not found")
76
+
77
+ if trace_dict.get("payload"):
78
+ try:
79
+ payload_obj = json.loads(trace_dict["payload"])
80
+ trace_dict["payload"] = media_service.unwrap_payload(payload_obj)
81
+ except (ValueError, TypeError, KeyError, AttributeError):
82
+ # Non-JSON or unexpectedly shaped payload: serve it raw.
83
+ pass
84
+
85
+ return trace_dict
86
+ except HTTPException as e:
87
+ raise e
88
+ except Exception as e:
89
+ traceback.print_exc()
90
+ raise HTTPException(status_code=500, detail=str(e))
91
+
92
+
93
+ @router.get("/api/traces/{trace_id}/download")
94
+ async def download_trace(trace_id: str, session_id: str = None, step_number: int = None):
95
+ try:
96
+ db_path = _resolve_trace_db_path(session_id, step_number)
97
+ trace_dict = trace_repo.get_trace_by_id(trace_id, db_path=db_path)
98
+ if not trace_dict or not trace_dict.get("payload"):
99
+ raise HTTPException(status_code=404, detail="Payload not found")
100
+
101
+ return PlainTextResponse(
102
+ trace_dict["payload"],
103
+ headers={"Content-Disposition": f'attachment; filename="trace_{trace_id}.json"'},
104
+ )
105
+ except HTTPException as e:
106
+ raise e
107
+ except Exception as e:
108
+ traceback.print_exc()
109
+ raise HTTPException(status_code=500, detail=str(e))