android-midscene-automation 0.2.4 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,269 @@
1
+ """Comprehensive Edge-Case Test Suite for Artemis Multi-Device Concurrency & Unified Queue.
2
+
3
+ Covers:
4
+ 1. Single Device FIFO Serial Queueing & Relay
5
+ 2. Multi-Device True Parallel Execution
6
+ 3. Global Concurrency Serialization across Distinct Devices
7
+ 4. Mixed Multi-Device Queueing
8
+ """
9
+
10
+ import asyncio
11
+ import os
12
+ import sys
13
+ import time
14
+ import requests
15
+
16
+ # Ensure workspace in path
17
+ WORKSPACE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ if WORKSPACE_ROOT not in sys.path:
19
+ sys.path.insert(0, WORKSPACE_ROOT)
20
+
21
+ from artemis.interfaces.sdk.client import ArtemisClient
22
+ from artemis.runtime.device_lock import DeviceExecutionLock
23
+ from artemis.toolchain import ensure_toolchain_in_path
24
+
25
+ ensure_toolchain_in_path()
26
+
27
+ from artemis.runtime import device_pool
28
+
29
+
30
+ def _resolve_devices() -> tuple[str, str]:
31
+ all_devs = device_pool.list_devices()
32
+ real_dev = next((d for d in all_devs if not d.is_emulator and d.state == "device"), None)
33
+ emu_dev = next((d for d in all_devs if d.is_emulator and d.state == "device"), None)
34
+ phone = os.environ.get("ARTEMIS_PHONE_SERIAL") or (
35
+ real_dev.serial if real_dev else "63191FDKX00062"
36
+ )
37
+ emu = os.environ.get("ARTEMIS_EMULATOR_SERIAL") or (
38
+ emu_dev.serial if emu_dev else "emulator-5554"
39
+ )
40
+ return phone, emu
41
+
42
+
43
+ PHONE_SERIAL, EMULATOR_SERIAL = _resolve_devices()
44
+ API_STATUS_URL = "http://127.0.0.1:8000/api/status"
45
+
46
+
47
+ def query_api_status():
48
+ try:
49
+ res = requests.get(API_STATUS_URL, timeout=3).json()
50
+ active = res.get("active_tasks", [])
51
+ queue = res.get("queue", [])
52
+ return active, queue
53
+ except Exception as e:
54
+ return [], []
55
+
56
+
57
+ def print_status_snapshot(stage_name: str):
58
+ active, queue = query_api_status()
59
+ print(f"\n[{stage_name}] Live Snapshot from Web Console (/api/status):")
60
+ print(f" ⚡ Running Tasks ({len(active)}):")
61
+ for a in active:
62
+ print(f" - Device: {a.get('device_id')} | Goal: {a.get('goal')} (PID: {a.get('pid')})")
63
+ print(f" ⏳ Queued Tasks ({len(queue)}):")
64
+ for q in queue:
65
+ dev = q.get("device_serial") or q.get("device_id")
66
+ print(f" - Device: {dev} | Goal: {q.get('goal')} [status={q.get('status')}]")
67
+ print(f" 📊 Total in Active Queue (Running + Pending) = {len(active) + len(queue)}\n")
68
+
69
+
70
+ async def stage_1_single_device_queueing():
71
+ print("\n" + "=" * 80)
72
+ print("STAGE 1: Single Device FIFO Queueing & Relay")
73
+ print(f"Target Device: {PHONE_SERIAL} (Pixel 11 Pro)")
74
+ print("=" * 80)
75
+
76
+ client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
77
+
78
+ t1_started = False
79
+ t2_queued = False
80
+
81
+ async def task_1a():
82
+ print(f"🚀 [Task 1A] Launching on {PHONE_SERIAL}: Press HOME and open Settings...")
83
+ res = await client_phone.run("Press HOME key and open Settings", profile="flash")
84
+ print(f"✅ [Task 1A] Finished with status: {res.status}")
85
+ return res
86
+
87
+ async def task_1b():
88
+ # Small delay so 1A is definitely acquired first
89
+ await asyncio.sleep(0.4)
90
+ print(
91
+ f"🚀 [Task 1B] Launching on {PHONE_SERIAL}: Press HOME and open Clock (Should queue!)..."
92
+ )
93
+ res = await client_phone.run("Press HOME key and open Clock", profile="flash")
94
+ print(f"✅ [Task 1B] Finished with status: {res.status}")
95
+ return res
96
+
97
+ # Run both simultaneously
98
+ task_a_coro = asyncio.create_task(task_1a())
99
+ task_b_coro = asyncio.create_task(task_1b())
100
+
101
+ # Wait 1.5s and inspect status while 1A is running and 1B is queued
102
+ await asyncio.sleep(1.2)
103
+ print_status_snapshot("STAGE 1 In-Progress")
104
+ active, queue = query_api_status()
105
+ assert len(active) >= 1, "Expected Task 1A to be actively running!"
106
+ # 1B should be in the queue or 1A just finished
107
+ print(f"✓ Verified: Task 1A is running on {PHONE_SERIAL}")
108
+
109
+ res_a, res_b = await asyncio.gather(task_a_coro, task_b_coro)
110
+ print("✓ Stage 1 Complete: Both tasks executed in order and completed successfully!")
111
+
112
+
113
+ async def stage_2_multi_device_parallel():
114
+ print("\n" + "=" * 80)
115
+ print("STAGE 2: Multi-Device True Parallel Execution")
116
+ print(f"Devices: {PHONE_SERIAL} (Pixel 11 Pro) AND {EMULATOR_SERIAL} (Emulator)")
117
+ print("Concurrency Mode: per_device (Should run simultaneously!)")
118
+ print("=" * 80)
119
+
120
+ client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
121
+ client_emu = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="per_device")
122
+
123
+ start_time = time.time()
124
+
125
+ async def task_phone():
126
+ print(f"📱 [Phone Task] Running on {PHONE_SERIAL}: Open Settings...")
127
+ res = await client_phone.run("Press HOME key and open Settings", profile="flash")
128
+ print(f"✅ [Phone Task] Completed in {time.time() - start_time:.2f}s")
129
+ return res
130
+
131
+ async def task_emu():
132
+ print(f"💻 [Emulator Task] Running on {EMULATOR_SERIAL}: Open Settings...")
133
+ res = await client_emu.run("Press HOME key and open Settings", profile="flash")
134
+ print(f"✅ [Emulator Task] Completed in {time.time() - start_time:.2f}s")
135
+ return res
136
+
137
+ p_task = asyncio.create_task(task_phone())
138
+ e_task = asyncio.create_task(task_emu())
139
+
140
+ # Check during run
141
+ await asyncio.sleep(1.2)
142
+ print_status_snapshot("STAGE 2 Parallel In-Progress")
143
+ active, queue = query_api_status()
144
+ print(f"Active devices count: {len(active)}")
145
+ active_devices = {a.get("device_id") for a in active}
146
+ print(f"Active devices: {active_devices}")
147
+
148
+ await asyncio.gather(p_task, e_task)
149
+ total_duration = time.time() - start_time
150
+ print(f"✓ Stage 2 Complete: Both devices completed parallel tasks in {total_duration:.2f}s!")
151
+
152
+
153
+ async def stage_3_global_concurrency_mode():
154
+ print("\n" + "=" * 80)
155
+ print("STAGE 3: Global Concurrency Mode Serialization")
156
+ print("Concurrency Mode: global (max 1 task system-wide even across different devices)")
157
+ print("=" * 80)
158
+
159
+ client_phone_global = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="global")
160
+ client_emu_global = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="global")
161
+
162
+ async def task_phone_g():
163
+ print(f"📱 [Task 3A - Phone] Acquiring global lock on {PHONE_SERIAL}...")
164
+ res = await client_phone_global.run("Press HOME key and open Settings", profile="flash")
165
+ print("✅ [Task 3A - Phone] Finished.")
166
+ return res
167
+
168
+ async def task_emu_g():
169
+ await asyncio.sleep(0.4)
170
+ print(
171
+ f"💻 [Task 3B - Emulator] Requesting run on {EMULATOR_SERIAL} under global mode (Should queue!)..."
172
+ )
173
+ res = await client_emu_global.run("Press HOME key and open Settings", profile="flash")
174
+ print("✅ [Task 3B - Emulator] Finished.")
175
+ return res
176
+
177
+ p_task = asyncio.create_task(task_phone_g())
178
+ e_task = asyncio.create_task(task_emu_g())
179
+
180
+ await asyncio.sleep(1.2)
181
+ print_status_snapshot("STAGE 3 Global Mode In-Progress")
182
+ active, queue = query_api_status()
183
+ print(f"Active count: {len(active)}, Queue count: {len(queue)}")
184
+
185
+ await asyncio.gather(p_task, e_task)
186
+ print(
187
+ "✓ Stage 3 Complete: Global lock correctly serialized execution across physical phone and emulator!"
188
+ )
189
+
190
+
191
+ async def stage_4_mixed_queue_and_parallel():
192
+ print("\n" + "=" * 80)
193
+ print("STAGE 4: Mixed Multi-Device Queueing")
194
+ print("Scenario: Phone gets 2 tasks (4A, 4B), Emulator gets 1 task (4C).")
195
+ print("Expectation: 4A (Phone) and 4C (Emulator) run concurrently; 4B waits for Phone.")
196
+ print("=" * 80)
197
+
198
+ client_phone = ArtemisClient(device_serial=PHONE_SERIAL, concurrency_mode="per_device")
199
+ client_emu = ArtemisClient(device_serial=EMULATOR_SERIAL, concurrency_mode="per_device")
200
+
201
+ async def task_4a():
202
+ print("📱 [Task 4A - Phone] Running...")
203
+ return await client_phone.run("Press HOME key and open Settings", profile="flash")
204
+
205
+ async def task_4b():
206
+ await asyncio.sleep(0.4)
207
+ print("📱 [Task 4B - Phone] Queued behind 4A...")
208
+ return await client_phone.run("Press HOME key and open Clock", profile="flash")
209
+
210
+ async def task_4c():
211
+ print("💻 [Task 4C - Emulator] Running in parallel with 4A...")
212
+ return await client_emu.run("Press HOME key and open Settings", profile="flash")
213
+
214
+ t4a = asyncio.create_task(task_4a())
215
+ t4b = asyncio.create_task(task_4b())
216
+ t4c = asyncio.create_task(task_4c())
217
+
218
+ await asyncio.sleep(1.2)
219
+ print_status_snapshot("STAGE 4 Mixed Multi-Device In-Progress")
220
+
221
+ await asyncio.gather(t4a, t4b, t4c)
222
+ print("✓ Stage 4 Complete: 4A and 4C ran in parallel, 4B queued and picked up phone relay!")
223
+
224
+
225
+ async def main():
226
+ import argparse
227
+
228
+ global PHONE_SERIAL, EMULATOR_SERIAL
229
+
230
+ parser = argparse.ArgumentParser(description="ARTEMIS All Edge-Cases Comprehensive Test Suite")
231
+ parser.add_argument("--phone", default=None, help="Target physical phone serial number")
232
+ parser.add_argument("--emulator", default=None, help="Target Android emulator serial number")
233
+ args = parser.parse_args()
234
+
235
+ if args.phone:
236
+ PHONE_SERIAL = args.phone
237
+ if args.emulator:
238
+ EMULATOR_SERIAL = args.emulator
239
+
240
+ print("\n" + "#" * 80)
241
+ print("# ARTEMIS ALL EDGE-CASES COMPREHENSIVE CONCURRENCY & QUEUE TEST SUITE")
242
+ print(f"# Devices: Real Phone ({PHONE_SERIAL}) + Android Emulator ({EMULATOR_SERIAL})")
243
+ print("# Web Console API: http://127.0.0.1:8000/api/status")
244
+ print("#" * 80)
245
+
246
+ # Initial cleanup
247
+ DeviceExecutionLock.cleanup_stale_locks()
248
+ print_status_snapshot("Initial System State")
249
+
250
+ # Run all 4 stages
251
+ await stage_1_single_device_queueing()
252
+ await asyncio.sleep(1)
253
+
254
+ await stage_2_multi_device_parallel()
255
+ await asyncio.sleep(1)
256
+
257
+ await stage_3_global_concurrency_mode()
258
+ await asyncio.sleep(1)
259
+
260
+ await stage_4_mixed_queue_and_parallel()
261
+
262
+ print("\n" + "=" * 80)
263
+ print("🎉 ALL EDGE-CASE STAGES COMPLETED SUCCESSFULLY!")
264
+ print("=" * 80)
265
+ print_status_snapshot("Final System State")
266
+
267
+
268
+ if __name__ == "__main__":
269
+ asyncio.run(main())
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright 2026 Google LLC
3
+ """Live experiment demonstrating Artemis concurrency modes and device targeting.
4
+
5
+ Covers:
6
+ 1. Single-device queueing and relay (FIFO mutual exclusion on the same device).
7
+ 2. Multi-device parallel execution (per_device concurrency mode across real device & emulator).
8
+ 3. Cross-device global serialization (global concurrency mode restricting all devices to 1 task).
9
+ 4. Full parameter coverage (device_serial, set_device, Task objects, list_devices, get_idle_devices).
10
+ """
11
+
12
+ import asyncio
13
+ import os
14
+ import sys
15
+ import time
16
+ from datetime import datetime
17
+
18
+ # Add project root to sys.path
19
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
20
+
21
+ from artemis import ArtemisClient, ConcurrencyMode, Task
22
+ from artemis.runtime import DeviceBusyError, device_pool
23
+
24
+
25
+ def print_header(title: str):
26
+ print("\n" + "=" * 76)
27
+ print(f" {title}")
28
+ print("=" * 76)
29
+
30
+
31
+ def now_str() -> str:
32
+ return datetime.now().strftime("%H:%M:%S.%f")[:-3]
33
+
34
+
35
+ async def run_experiment_1_single_device_relay(real_serial: str):
36
+ """Experiment 1: Single device FIFO queueing & relay."""
37
+ print_header("[Experiment 1] Single-Device Task Queueing & Relay (FIFO Relay on Single Device)")
38
+ print(f"Target Device: {real_serial} (Physical Pixel 11 Pro)")
39
+
40
+ client = ArtemisClient(
41
+ device_serial=real_serial, default_profile="flash", concurrency_mode="per_device"
42
+ )
43
+ print(
44
+ f"Client Init: device_serial='{client.device_serial}', concurrency_mode='{client.concurrency_mode}'"
45
+ )
46
+
47
+ task_1_launched = 0.0
48
+ task_1_start = 0.0
49
+ task_1_end = 0.0
50
+
51
+ task_2_launched = 0.0
52
+ task_2_start = 0.0
53
+ task_2_end = 0.0
54
+
55
+ async def execute_task_1():
56
+ nonlocal task_1_launched, task_1_start, task_1_end
57
+ task_1_launched = time.time()
58
+ print(f"[{now_str()}] ▶ [Task 1] Submitted for scheduling (target device: {real_serial})")
59
+ task_1_start = time.time()
60
+ res = await client.run(
61
+ "Open Settings and check battery level",
62
+ device_serial=real_serial,
63
+ max_turns=3,
64
+ )
65
+ task_1_end = time.time()
66
+ print(
67
+ f"[{now_str()}] ◀ [Task 1] Execution completed! Status={res.status}, "
68
+ f"Trace={res.trace_id[:8]}..., Elapsed={task_1_end - task_1_start:.3f}s"
69
+ )
70
+ return res
71
+
72
+ async def execute_task_2():
73
+ nonlocal task_2_launched, task_2_start, task_2_end
74
+ task_2_launched = time.time()
75
+ print(
76
+ f"[{now_str()}] ▶ [Task 2] Submitted concurrently (detects device occupied by Task 1, entering FIFO queue...)"
77
+ )
78
+
79
+ task_2_start = time.time()
80
+ res = await client.run(
81
+ "Return to home screen and open Clock",
82
+ device_serial=real_serial,
83
+ max_turns=3,
84
+ )
85
+ task_2_end = time.time()
86
+ print(
87
+ f"[{now_str()}] ◀ [Task 2] Relayed execution completed! Status={res.status}, "
88
+ f"Trace={res.trace_id[:8]}..., Elapsed={task_2_end - task_2_start:.3f}s"
89
+ )
90
+ return res
91
+
92
+ # Launch Task 1 first, then Task 2 40ms later while Task 1 is holding the lock
93
+ t_coro_1 = asyncio.create_task(execute_task_1())
94
+ await asyncio.sleep(0.04)
95
+ t_coro_2 = asyncio.create_task(execute_task_2())
96
+ res1, res2 = await asyncio.gather(t_coro_1, t_coro_2)
97
+
98
+ print("\n[Timing & Relay Analysis]:")
99
+ print(
100
+ f" Task 1 runtime interval: [{task_1_start:.3f}s ~ {task_1_end:.3f}s] (elapsed {task_1_end - task_1_start:.3f}s)"
101
+ )
102
+ print(f" Task 2 submission time: {task_2_launched:.3f}s")
103
+ print(f" Task 2 actual start: {task_2_start:.3f}s (waited for Task 1 lock release)")
104
+ print(f" Task 2 end time: {task_2_end:.3f}s (elapsed {task_2_end - task_2_start:.3f}s)")
105
+
106
+ assert task_2_end > task_1_end, "Task 2 did not complete after Task 1!"
107
+ assert task_2_end - task_1_start >= (task_1_end - task_1_start) + 0.1, (
108
+ "Tasks failed to execute in sequential queue!"
109
+ )
110
+ print(
111
+ " ✅ Verification successful: Task 2 queued properly and relayed immediately after Task 1 released the lock!"
112
+ )
113
+
114
+
115
+ async def run_experiment_2_multi_device_parallel(real_serial: str, emu_serial: str):
116
+ """Experiment 2: Multi-device parallel execution under per_device concurrency."""
117
+ print_header("[Experiment 2] Multi-Device Parallel Execution Test")
118
+ print(f"Device A (Physical): {real_serial}")
119
+ print(f"Device B (Emulator): {emu_serial}")
120
+
121
+ client_a = ArtemisClient(
122
+ device_serial=real_serial, default_profile="flash", concurrency_mode="per_device"
123
+ )
124
+ client_b = ArtemisClient(
125
+ device_serial=emu_serial, default_profile="flash", concurrency_mode="per_device"
126
+ )
127
+
128
+ a_start, a_end = 0.0, 0.0
129
+ b_start, b_end = 0.0, 0.0
130
+
131
+ async def run_on_device_a():
132
+ nonlocal a_start, a_end
133
+ print(f"[{now_str()}] ▶ [Device A Task] Starting on {real_serial}")
134
+ a_start = time.time()
135
+ res = await client_a.run("Test operation A on physical device", max_turns=3)
136
+ a_end = time.time()
137
+ print(
138
+ f"[{now_str()}] ◀ [Device A Task] Completed! Elapsed: {a_end - a_start:.3f}s, Device: {res.device_id}"
139
+ )
140
+ return res
141
+
142
+ async def run_on_device_b():
143
+ nonlocal b_start, b_end
144
+ print(f"[{now_str()}] ▶ [Device B Task] Starting on {emu_serial}")
145
+ b_start = time.time()
146
+ res = await client_b.run("Test operation B on emulator", max_turns=3)
147
+ b_end = time.time()
148
+ print(
149
+ f"[{now_str()}] ◀ [Device B Task] Completed! Elapsed: {b_end - b_start:.3f}s, Device: {res.device_id}"
150
+ )
151
+ return res
152
+
153
+ t_start = time.time()
154
+ task_a = asyncio.create_task(run_on_device_a())
155
+ task_b = asyncio.create_task(run_on_device_b())
156
+ res_a, res_b = await asyncio.gather(task_a, task_b)
157
+ t_total = time.time() - t_start
158
+
159
+ print("\n[Dual-Device Concurrency Analysis]:")
160
+ print(
161
+ f" Device A execution interval: {a_start:.3f}s ~ {a_end:.3f}s (elapsed {a_end - a_start:.3f}s)"
162
+ )
163
+ print(
164
+ f" Device B execution interval: {b_start:.3f}s ~ {b_end:.3f}s (elapsed {b_end - b_start:.3f}s)"
165
+ )
166
+ print(
167
+ f" Total concurrent duration: {t_total:.3f}s (sum of sequential durations: {(a_end - a_start) + (b_end - b_start):.3f}s)"
168
+ )
169
+
170
+ # Overlap verification
171
+ overlap = min(a_end, b_end) - max(a_start, b_start)
172
+ print(f" Parallel overlap duration: {overlap:.3f}s")
173
+ assert overlap > 0.0, "Dual devices failed to run concurrently with overlap!"
174
+ assert t_total < ((a_end - a_start) + (b_end - b_start) * 0.95), (
175
+ "Total duration should be less than sum of both tasks!"
176
+ )
177
+ print(" ✅ Verification successful: Tasks on both devices ran concurrently without blocking!")
178
+
179
+
180
+ async def run_experiment_3_global_concurrency_serialization(real_serial: str, emu_serial: str):
181
+ """Experiment 3: Global concurrency serialization across devices."""
182
+ print_header("[Experiment 3] Global Concurrency Mode Test")
183
+ print(
184
+ "Mode description: concurrency_mode='global', entire system allows only 1 task across all devices."
185
+ )
186
+
187
+ client_a = ArtemisClient(
188
+ device_serial=real_serial, default_profile="flash", concurrency_mode="global"
189
+ )
190
+ client_b = ArtemisClient(
191
+ device_serial=emu_serial, default_profile="flash", concurrency_mode="global"
192
+ )
193
+
194
+ # 3.1 Test non-blocking mode rejection
195
+ print("\n[Subtest 3.1: Non-blocking rejection limit]")
196
+ from artemis.runtime import DeviceExecutionLock
197
+
198
+ lock_a = DeviceExecutionLock(real_serial, "Placeholder test lock", concurrency_mode="global")
199
+ await asyncio.to_thread(lock_a.acquire)
200
+ try:
201
+ print(f"[{now_str()}] Device A ({real_serial}) holds the global lock...")
202
+ try:
203
+ print(f"[{now_str()}] Device B ({emu_serial}) attempts run with blocking=False...")
204
+ await client_b.run("Emulator non-blocking task", blocking=False)
205
+ print(" ❌ Exception: Was not intercepted by global lock!")
206
+ except DeviceBusyError as exc:
207
+ print(f" ✅ Correctly intercepted with DeviceBusyError: {exc}")
208
+ finally:
209
+ await asyncio.to_thread(lock_a.release)
210
+
211
+ # 3.2 Test cross-device queueing and relay
212
+ print("\n[Subtest 3.2: Cross-device global queueing & relay]")
213
+ t1_submitted, t1_end = 0.0, 0.0
214
+ t2_submitted, t2_end = 0.0, 0.0
215
+
216
+ async def run_global_task_1():
217
+ nonlocal t1_submitted, t1_end
218
+ t1_submitted = time.time()
219
+ print(f"[{now_str()}] ▶ [Global Task 1] Started on physical device ({real_serial})...")
220
+ res = await client_a.run("Global task 1", max_turns=3)
221
+ t1_end = time.time()
222
+ print(f"[{now_str()}] ◀ [Global Task 1] Completed! Elapsed: {t1_end - t1_submitted:.3f}s")
223
+ return res
224
+
225
+ async def run_global_task_2():
226
+ nonlocal t2_submitted, t2_end
227
+ # Submit task 2 shortly after task 1
228
+ await asyncio.sleep(0.04)
229
+ t2_submitted = time.time()
230
+ print(
231
+ f"[{now_str()}] ▶ [Global Task 2] Submitted to emulator ({emu_serial}), entering queue due to global limit..."
232
+ )
233
+ res = await client_b.run("Global task 2", max_turns=3)
234
+ t2_end = time.time()
235
+ print(
236
+ f"[{now_str()}] ◀ [Global Task 2] Relayed completion! Total time (including queue): {t2_end - t2_submitted:.3f}s"
237
+ )
238
+ return res
239
+
240
+ res1, res2 = await asyncio.gather(run_global_task_1(), run_global_task_2())
241
+ print(
242
+ f" Global Task 1 interval: {t1_submitted:.3f}s ~ {t1_end:.3f}s (elapsed {t1_end - t1_submitted:.3f}s)"
243
+ )
244
+ print(f" Global Task 2 submit time: {t2_submitted:.3f}s")
245
+ print(
246
+ f" Global Task 2 end time: {t2_end:.3f}s (waited for Task 1 completion before executing)"
247
+ )
248
+ assert t2_end > t1_end, "Global Task 2 did not complete after Global Task 1!"
249
+ assert (t2_end - t1_end) >= 0.08, (
250
+ "Global Task 2 did not execute sequentially after Task 1 released global lock!"
251
+ )
252
+ print(
253
+ " ✅ Verification successful: Under global mode, strictly 1 task executed sequentially across different devices!"
254
+ )
255
+
256
+
257
+ async def run_experiment_4_parameters_and_utilities(real_serial: str, emu_serial: str):
258
+ """Experiment 4: Comprehensive SDK parameter and utility testing."""
259
+ print_header("[Experiment 4] SDK Parameter & Utility Comprehensive Test")
260
+
261
+ client = ArtemisClient()
262
+ print(f"1. Default idle device selection: client.device_serial = '{client.device_serial}'")
263
+
264
+ # Chain switch device
265
+ client.set_device(real_serial)
266
+ print(
267
+ f"2. Chain switch to physical device: client.set_device('{real_serial}') -> '{client.device_serial}'"
268
+ )
269
+ assert client.device_serial == real_serial
270
+
271
+ # Attribute switch
272
+ client.device_serial = emu_serial
273
+ print(
274
+ f"3. Property assignment to emulator: client.device_serial = '{emu_serial}' -> '{client.device_id}'"
275
+ )
276
+ assert client.device_id == emu_serial
277
+
278
+ # Dynamically change concurrency mode
279
+ client.set_concurrency_mode(ConcurrencyMode.GLOBAL)
280
+ print(
281
+ f"4. Chain change concurrency mode: client.concurrency_mode = '{client.concurrency_mode}'"
282
+ )
283
+ assert client.concurrency_mode == "global"
284
+ client.concurrency_mode = "per_device"
285
+
286
+ # Query all available devices
287
+ print("5. client.list_devices() status:")
288
+ devices = client.list_devices()
289
+ for d in devices:
290
+ print(
291
+ f" • [{d.serial}] State: {d.state}, Model: {d.model}, IsEmulator: {d.is_emulator}, Busy: {d.is_busy}"
292
+ )
293
+
294
+ # Query idle devices
295
+ idle = client.get_idle_devices()
296
+ print(f"6. client.get_idle_devices(): {[d.serial for d in idle]}")
297
+
298
+ # Use Task structured object with run_task
299
+ print("7. Calling client.run_task() with structured Task entity:")
300
+ task_obj = Task(
301
+ goal="Verify physical device call via Task object",
302
+ profile="flash",
303
+ device_serial=real_serial,
304
+ concurrency_mode="per_device",
305
+ max_turns=3,
306
+ )
307
+ result = await client.run_task(task_obj)
308
+ print(
309
+ f" Task result: Status={result.status}, Device={result.device_id}, Trace={result.trace_id[:8]}..."
310
+ )
311
+ assert result.device_id == real_serial
312
+
313
+ # Use stream_run for live event stream
314
+ print("8. Using client.stream_run() for live event stream:")
315
+ events_received = 0
316
+ async for ev in client.stream_run(
317
+ "Stream operation test",
318
+ device_serial=emu_serial,
319
+ profile="flash",
320
+ max_turns=3,
321
+ ):
322
+ events_received += 1
323
+ print(f" • [{ev.event_type}] payload={ev.payload}")
324
+ assert events_received >= 2
325
+ print(f" ✅ Stream events captured successfully! Captured {events_received} events.")
326
+
327
+
328
+ async def main():
329
+ # Detect devices
330
+ devices = device_pool.list_devices()
331
+ print("=" * 76)
332
+ print("[ARTEMIS Python SDK Concurrency & Device Targeting Experiment]")
333
+ print(f"Detected connected devices count: {len(devices)}")
334
+ for d in devices:
335
+ print(
336
+ f" • Serial: {d.serial} | Model: {d.model} | State: {d.state} | IsEmulator: {d.is_emulator}"
337
+ )
338
+ print("=" * 76)
339
+
340
+ import argparse
341
+
342
+ parser = argparse.ArgumentParser(
343
+ description="Live experiment demonstrating Artemis concurrency modes and device targeting."
344
+ )
345
+ parser.add_argument(
346
+ "--phone",
347
+ default=os.environ.get("ARTEMIS_PHONE_SERIAL"),
348
+ help="Target physical phone serial number",
349
+ )
350
+ parser.add_argument(
351
+ "--emulator",
352
+ default=os.environ.get("ARTEMIS_EMULATOR_SERIAL"),
353
+ help="Target Android emulator serial number",
354
+ )
355
+ args, _ = parser.parse_known_args()
356
+
357
+ # Auto-detect connected devices if not explicitly provided
358
+ real_dev = next((d for d in devices if not d.is_emulator and d.state == "device"), None)
359
+ emu_dev = next((d for d in devices if d.is_emulator and d.state == "device"), None)
360
+
361
+ real_serial = args.phone or (real_dev.serial if real_dev else "63191FDKX00062")
362
+ emu_serial = args.emulator or (emu_dev.serial if emu_dev else "emulator-5554")
363
+
364
+ # Verify both devices are available
365
+ serials = [d.serial for d in devices]
366
+ if real_serial not in serials or emu_serial not in serials:
367
+ print(
368
+ f"Error: Both physical device '{real_serial}' and emulator '{emu_serial}' must be connected for full experiment."
369
+ )
370
+ print(f"Currently online devices: {serials}")
371
+ return
372
+
373
+ # Run experiments in sequence
374
+ await run_experiment_1_single_device_relay(real_serial)
375
+ await run_experiment_2_multi_device_parallel(real_serial, emu_serial)
376
+ await run_experiment_3_global_concurrency_serialization(real_serial, emu_serial)
377
+ await run_experiment_4_parameters_and_utilities(real_serial, emu_serial)
378
+
379
+ print_header(
380
+ "🎉 All experiments completed successfully! All concurrency and device targeting verified!"
381
+ )
382
+
383
+
384
+ if __name__ == "__main__":
385
+ asyncio.run(main())