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
@@ -1,15 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
- import { createRequire } from 'node:module';
4
3
  import fs from 'node:fs';
5
4
  import os from 'node:os';
6
5
  import path from 'node:path';
7
6
  import { fileURLToPath } from 'node:url';
8
7
 
9
- const require = createRequire(import.meta.url);
10
8
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
- const viteRoot = path.dirname(require.resolve('vite/package.json'));
12
- const viteBin = path.join(viteRoot, 'bin', 'vite.js');
13
9
  const args = process.argv.slice(2);
14
10
  const userRoot = process.cwd();
15
11
  const githubUrl = 'https://github.com/oooooooko/android-midscene-automation';
@@ -90,7 +86,7 @@ console.log(`\nGitHub 源码 / 二次修改:${githubUrl}`);
90
86
  console.log(`问题反馈 / 功能建议:${githubUrl}/issues\n`);
91
87
  console.log(`本地数据目录:${dataRoot}\n`);
92
88
 
93
- const child = spawn(process.execPath, [viteBin, ...args], {
89
+ const child = spawn(process.execPath, [path.join(packageRoot, 'bin/start-dev.mjs'), ...args], {
94
90
  cwd: packageRoot,
95
91
  env: {
96
92
  ...process.env,
@@ -0,0 +1,15 @@
1
+ import { constants, copyFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ export function ensureArtemisConfig(projectDir) {
5
+ const configDir = resolve(projectDir, 'config');
6
+ const target = resolve(configDir, 'artemis.jsonc');
7
+ if (existsSync(target)) return;
8
+ mkdirSync(configDir, { recursive: true });
9
+ try {
10
+ copyFileSync(resolve(configDir, 'artemis.example.jsonc'), target, constants.COPYFILE_EXCL);
11
+ } catch (error) {
12
+ // Never overwrite a user's configuration, including concurrent first starts.
13
+ if (error.code !== 'EEXIST') throw error;
14
+ }
15
+ }
@@ -0,0 +1,56 @@
1
+ """Launch an unmodified Artemis checkout with an explicit frame-ancestor allowlist."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ import sys
6
+ from urllib.parse import urlsplit
7
+
8
+
9
+ def frame_policy(origins: str) -> str:
10
+ allowed = []
11
+ for value in origins.split(','):
12
+ origin = value.strip()
13
+ parsed = urlsplit(origin)
14
+ if (parsed.scheme not in {'http', 'https'} or not parsed.hostname
15
+ or parsed.username or parsed.password or parsed.path not in {'', '/'}
16
+ or parsed.query or parsed.fragment
17
+ or any(char in origin for char in " \t\r\n;*'")):
18
+ raise ValueError(f'Invalid ARTEMIS_EMBED_ORIGINS origin: {origin!r}')
19
+ allowed.append(f'{parsed.scheme}://{parsed.netloc}')
20
+ return "frame-ancestors 'self' " + ' '.join(allowed)
21
+
22
+
23
+ class EmbedBoundary:
24
+ def __init__(self, app, policy: str):
25
+ self.app = app
26
+ self.policy = policy.encode('ascii')
27
+
28
+ async def __call__(self, scope, receive, send):
29
+ async def send_headers(message):
30
+ if message['type'] == 'http.response.start':
31
+ # Replace only the framing restriction; retain Host/Origin checks
32
+ # and all other headers installed by Artemis's inner middleware.
33
+ headers = [(key, value) for key, value in message.get('headers', [])
34
+ if key.lower() != b'x-frame-options']
35
+ # Multiple CSP headers are enforced together by the browser.
36
+ headers.append((b'content-security-policy', self.policy))
37
+ message = {**message, 'headers': headers}
38
+ await send(message)
39
+
40
+ await self.app(scope, receive, send_headers)
41
+
42
+
43
+ if __name__ == '__main__':
44
+ policy = frame_policy(os.environ.get(
45
+ 'ARTEMIS_EMBED_ORIGINS',
46
+ 'http://127.0.0.1:5173,http://localhost:5173,'
47
+ 'http://127.0.0.1:4173,http://localhost:4173',
48
+ ))
49
+ sys.path.insert(0, str(Path(os.environ['ARTEMIS_PROJECT_DIR']).resolve()))
50
+ from apps.admin_console.server import app, run_ui_server
51
+ from artemis.interfaces.cli.commands.ui import ensure_showcase_built
52
+ from rich.console import Console
53
+
54
+ ensure_showcase_built(Console())
55
+ app.add_middleware(EmbedBoundary, policy=policy)
56
+ run_ui_server(host='127.0.0.1', port=int(os.environ.get('ARTEMIS_PORT', '8000')))
@@ -0,0 +1,30 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { ensureArtemisConfig } from './artemis-config.mjs';
6
+
7
+ const binDir = dirname(fileURLToPath(import.meta.url));
8
+ const projectDir = resolve(process.env.ARTEMIS_PROJECT_DIR || resolve(binDir, '../artemis-main'));
9
+ if (!existsSync(resolve(projectDir, 'pyproject.toml'))) {
10
+ console.error('找不到 Artemis 源码。请将源码放在 artemis-main,或设置 ARTEMIS_PROJECT_DIR。');
11
+ process.exit(1);
12
+ }
13
+
14
+ ensureArtemisConfig(projectDir);
15
+
16
+ const child = spawn('uv', ['run', '--project', projectDir, 'python', resolve(binDir, 'artemis-embedded.py')], {
17
+ cwd: projectDir,
18
+ stdio: 'inherit',
19
+ env: { ...process.env, ARTEMIS_PROJECT_DIR: projectDir },
20
+ });
21
+ child.once('error', error => {
22
+ console.error(`Artemis 启动失败,请确认已安装 uv:${error.message}`);
23
+ process.exitCode = 1;
24
+ });
25
+ for (const signal of ['SIGINT', 'SIGTERM']) {
26
+ process.on(signal, () => child.kill(signal));
27
+ }
28
+ child.once('exit', (code, signal) => {
29
+ process.exitCode = code ?? (signal ? 1 : 0);
30
+ });
@@ -0,0 +1,112 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const binDir = dirname(fileURLToPath(import.meta.url));
9
+ const projectRoot = resolve(binDir, '..');
10
+ const args = process.argv.slice(2);
11
+ const portIndex = args.indexOf('--port');
12
+ const vitePort = args.find(arg => arg.startsWith('--port='))?.slice(7)
13
+ || (portIndex >= 0 ? args[portIndex + 1] : '5173');
14
+ const artemisPort = process.env.ARTEMIS_PORT || '8000';
15
+ const localArtemisUrl = `http://127.0.0.1:${artemisPort}`;
16
+ const env = {
17
+ ...process.env,
18
+ ARTEMIS_URL: process.env.ARTEMIS_URL || localArtemisUrl,
19
+ ARTEMIS_EMBED_ORIGINS: process.env.ARTEMIS_EMBED_ORIGINS
20
+ || `http://127.0.0.1:${vitePort},http://localhost:${vitePort},http://127.0.0.1:4173,http://localhost:4173`,
21
+ };
22
+ const children = new Set();
23
+ let stopping = false;
24
+ let killTimer;
25
+
26
+ function signalChild(child, signal) {
27
+ try {
28
+ if (!child.devProcessGroup) child.kill(signal);
29
+ else process.kill(-child.pid, signal);
30
+ } catch (error) {
31
+ if (error.code !== 'ESRCH') console.error(`停止开发服务失败:${error.message}`);
32
+ }
33
+ }
34
+
35
+ function stop(code = 0) {
36
+ if (stopping) return;
37
+ stopping = true;
38
+ process.exitCode = code;
39
+ for (const child of children) signalChild(child, 'SIGTERM');
40
+ // uv and Python belong to the launcher group, so descendants are cleaned too.
41
+ killTimer = setTimeout(() => {
42
+ for (const child of children) signalChild(child, 'SIGKILL');
43
+ }, 5000);
44
+ killTimer.unref();
45
+ }
46
+
47
+ function start(script, scriptArgs, required) {
48
+ const devProcessGroup = !required && process.platform !== 'win32';
49
+ const child = spawn(process.execPath, [script, ...scriptArgs], {
50
+ cwd: projectRoot, env, stdio: 'inherit', detached: devProcessGroup,
51
+ });
52
+ child.devProcessGroup = devProcessGroup;
53
+ children.add(child);
54
+ child.once('error', error => {
55
+ console.error(`${required ? '主项目' : 'Artemis'} 启动失败:${error.message}`);
56
+ children.delete(child);
57
+ if (required) stop(1);
58
+ });
59
+ child.once('exit', code => {
60
+ // Clean any descendants even if their direct parent has already exited.
61
+ signalChild(child, 'SIGTERM');
62
+ children.delete(child);
63
+ if (!children.size) clearTimeout(killTimer);
64
+ if (stopping) return;
65
+ if (required) stop(code ?? 1);
66
+ else console.warn('Artemis 服务已退出,主项目继续运行;排查上方日志后可执行 npm run artemis 重试。');
67
+ });
68
+ }
69
+
70
+ // npm may forward a signal already delivered by the terminal; keep both handlers.
71
+ process.on('SIGINT', () => stop());
72
+ process.on('SIGTERM', () => stop());
73
+ process.on('exit', () => {
74
+ for (const child of children) signalChild(child, 'SIGTERM');
75
+ });
76
+
77
+ async function startArtemis() {
78
+ if (env.ARTEMIS_AUTO_START === '0') return;
79
+ try {
80
+ const response = await fetch(new URL('/api/status', env.ARTEMIS_URL), {
81
+ signal: AbortSignal.timeout(1500), redirect: 'error',
82
+ });
83
+ const data = await response.json();
84
+ if (response.ok && data.model_info) {
85
+ console.log(`复用已运行的 Artemis:${env.ARTEMIS_URL}`);
86
+ return;
87
+ }
88
+ } catch { /* No running Artemis; try the local launcher below. */ }
89
+ if (stopping) return;
90
+ const target = new URL(env.ARTEMIS_URL);
91
+ if (!['127.0.0.1', 'localhost'].includes(target.hostname)
92
+ || target.protocol !== 'http:' || (target.port || '80') !== artemisPort) {
93
+ console.warn('ARTEMIS_URL 指向外部或不同端口的服务,请单独启动该服务;主项目继续运行。');
94
+ return;
95
+ }
96
+ const projectDir = resolve(env.ARTEMIS_PROJECT_DIR || resolve(projectRoot, 'artemis-main'));
97
+ if (!existsSync(resolve(projectDir, 'pyproject.toml'))) {
98
+ console.warn('未找到 Artemis 源码,跳过自动启动。请准备 artemis-main 或设置 ARTEMIS_PROJECT_DIR;主项目继续运行。');
99
+ return;
100
+ }
101
+ console.log(`正在自动启动 Artemis:${localArtemisUrl}`);
102
+ start(resolve(binDir, 'start-artemis.mjs'), [], false);
103
+ }
104
+
105
+ const viteBin = resolve(dirname(require.resolve('vite/package.json')), 'bin/vite.js');
106
+ // Keep the port fixed so iframe origins match the actual dev server.
107
+ start(viteBin, ['--host', '127.0.0.1', '--strictPort', ...args], true);
108
+ try {
109
+ await startArtemis();
110
+ } catch (error) {
111
+ console.warn(`Artemis 自动启动失败:${error.message};主项目继续运行。`);
112
+ }
@@ -0,0 +1,44 @@
1
+ import asyncio
2
+ import importlib.util
3
+ from pathlib import Path
4
+ import unittest
5
+
6
+ spec = importlib.util.spec_from_file_location('embedded', Path(__file__).with_name('artemis-embedded.py'))
7
+ embedded = importlib.util.module_from_spec(spec)
8
+ spec.loader.exec_module(embedded)
9
+
10
+
11
+ class EmbedTests(unittest.TestCase):
12
+ def test_only_explicit_origins(self):
13
+ self.assertEqual(embedded.frame_policy('http://localhost:5173/'),
14
+ "frame-ancestors 'self' http://localhost:5173")
15
+ for origin in ['*', 'https://*.example.com', 'http://localhost:5173;script-src *',
16
+ 'http://user:pass@localhost:5173', 'http://localhost/path', 'null']:
17
+ with self.subTest(origin=origin), self.assertRaises(ValueError):
18
+ embedded.frame_policy(origin)
19
+
20
+ def test_preserves_origin_rejection_and_other_headers(self):
21
+ async def run():
22
+ async def app(scope, receive, send):
23
+ await send({'type': 'http.response.start', 'status': 403, 'headers': [
24
+ (b'x-frame-options', b'DENY'), (b'x-content-type-options', b'nosniff'),
25
+ (b'content-security-policy', b"script-src 'self'"),
26
+ ]})
27
+ await send({'type': 'http.response.body', 'body': b'Forbidden'})
28
+ messages = []
29
+ async def send(message):
30
+ messages.append(message)
31
+ await embedded.EmbedBoundary(app, embedded.frame_policy('http://localhost:5173'))(
32
+ {'type': 'http'}, None, send)
33
+ self.assertEqual(messages[0]['status'], 403)
34
+ self.assertEqual(messages[1]['body'], b'Forbidden')
35
+ headers = messages[0]['headers']
36
+ self.assertIn((b'x-content-type-options', b'nosniff'), headers)
37
+ self.assertIn((b'content-security-policy', b"script-src 'self'"), headers)
38
+ self.assertNotIn((b'x-frame-options', b'DENY'), headers)
39
+ self.assertIn((b'content-security-policy', b"frame-ancestors 'self' http://localhost:5173"), headers)
40
+ asyncio.run(run())
41
+
42
+
43
+ if __name__ == '__main__':
44
+ unittest.main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "android-midscene-automation",
3
- "version": "0.2.3",
3
+ "version": "1.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oooooooko/android-midscene-automation.git"
@@ -14,6 +14,7 @@
14
14
  "android-midscene-automation": "bin/android-midscene-automation.js"
15
15
  },
16
16
  "files": [
17
+ "artemis-main",
17
18
  "bin",
18
19
  "index.html",
19
20
  "remote-agent",
@@ -29,7 +30,8 @@
29
30
  "CHANGELOG.md"
30
31
  ],
31
32
  "scripts": {
32
- "dev": "vite --host 127.0.0.1",
33
+ "artemis": "node bin/start-artemis.mjs",
34
+ "dev": "node bin/start-dev.mjs",
33
35
  "prepack": "node bin/sync-readme-docs.mjs",
34
36
  "remote-agent": "tsx remote-agent/index.ts",
35
37
  "build": "vue-tsc -b && vite build",
@@ -45,12 +47,12 @@
45
47
  "mediabunny": "1.58.0",
46
48
  "@dagrejs/dagre": "1.1.5",
47
49
  "@element-plus/icons-vue": "2.3.2",
48
- "@midscene/android": "https://registry.npmjs.org/@midscene/android/-/android-1.12.7.tgz",
49
- "@midscene/android-playground": "https://registry.npmjs.org/@midscene/android-playground/-/android-playground-1.12.7.tgz",
50
- "@midscene/core": "https://registry.npmjs.org/@midscene/core/-/core-1.12.7.tgz",
51
- "@midscene/playground": "https://registry.npmjs.org/@midscene/playground/-/playground-1.12.7.tgz",
52
- "@midscene/shared": "https://registry.npmjs.org/@midscene/shared/-/shared-1.12.7.tgz",
53
- "@midscene/web": "https://registry.npmjs.org/@midscene/web/-/web-1.12.7.tgz",
50
+ "@midscene/android": "https://registry.npmjs.org/@midscene/android/-/android-1.14.0.tgz",
51
+ "@midscene/android-playground": "https://registry.npmjs.org/@midscene/android-playground/-/android-playground-1.14.0.tgz",
52
+ "@midscene/core": "https://registry.npmjs.org/@midscene/core/-/core-1.14.0.tgz",
53
+ "@midscene/playground": "https://registry.npmjs.org/@midscene/playground/-/playground-1.14.0.tgz",
54
+ "@midscene/shared": "https://registry.npmjs.org/@midscene/shared/-/shared-1.14.0.tgz",
55
+ "@midscene/web": "https://registry.npmjs.org/@midscene/web/-/web-1.14.0.tgz",
54
56
  "@techstark/opencv-js": "5.0.0-release.1",
55
57
  "@vitejs/plugin-vue": "6.0.7",
56
58
  "@vue-flow/core": "1.48.2",
@@ -0,0 +1,34 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { appPath } from './paths';
4
+
5
+ export async function getArtemisVersion() {
6
+ try {
7
+ const projectDir = process.env.ARTEMIS_PROJECT_DIR || appPath('artemis-main');
8
+ const source = await readFile(resolve(projectDir, 'pyproject.toml'), 'utf8');
9
+ const project = source.split(/^\[project\]\s*$/m)[1]?.split(/^\[/m)[0] || '';
10
+ const version = project.match(/^version\s*=\s*["']([^"']+)["']/m)?.[1];
11
+ return { version: version || '', message: version ? '本地 Artemis 源码版本' : '未找到 Artemis 版本信息' };
12
+ } catch {
13
+ return { version: '', message: '未找到本地 Artemis 源码,请检查 ARTEMIS_PROJECT_DIR' };
14
+ }
15
+ }
16
+
17
+ export async function getArtemisStatus() {
18
+ const configuredUrl = process.env.ARTEMIS_URL || 'http://127.0.0.1:8000';
19
+ try {
20
+ const url = new URL(configuredUrl);
21
+ const response = await fetch(new URL('/api/status', url), {
22
+ signal: AbortSignal.timeout(3000),
23
+ redirect: 'error',
24
+ });
25
+ const data = await response.json() as { model_info?: unknown };
26
+ if (!response.ok || !data.model_info) throw new Error('服务响应不是 Artemis 控制台');
27
+ const policy = response.headers.get('content-security-policy') || '';
28
+ const ancestors = response.headers.get('x-frame-options')
29
+ ? [] : policy.match(/(?:^|;)\s*frame-ancestors\s+([^;]+)/)?.[1].split(/\s+/) || [];
30
+ return { url: url.origin, available: true, ancestors, message: '' };
31
+ } catch {
32
+ return { url: '', available: false, ancestors: [], message: '未连接到 Artemis,请先在项目根目录运行 npm run artemis,并检查 ARTEMIS_URL 配置。' };
33
+ }
34
+ }
@@ -1,4 +1,5 @@
1
1
  import { getTestAnalytics } from './analytics';
2
+ import { getArtemisStatus, getArtemisVersion } from './artemis';
2
3
  import { readBody, readRawBody, requestErrorStatus } from './request-body';
3
4
  import { canManageConfig, publicConfig, resolveSavedModelKey } from './config-access';
4
5
  import { resolveReportSummary } from '../src/appium-recorder/report-summary';
@@ -79,7 +80,7 @@ function delay(ms: number) {
79
80
  });
80
81
  }
81
82
 
82
- // Work around @midscene/android-playground v1.12.x ESM __dirname usage on Node 24.
83
+ // Keep the Android Playground launcher compatible with Node 24 ESM execution.
83
84
  const START_ANDROID_PLAYGROUND_WITHOUT_BROWSER = [
84
85
  "import { createRequire } from 'node:module';",
85
86
  'const require = createRequire(import.meta.url);',
@@ -615,6 +616,17 @@ export function createApiMiddleware() {
615
616
  const requestUrl = new URL(req.url || '/', 'http://localhost');
616
617
  const pathname = requestUrl.pathname;
617
618
  const allowLocalDevice = isLocalRequest(req);
619
+ if (['/api/artemis/status', '/api/artemis/version'].includes(pathname) && req.method === 'GET') {
620
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
621
+ res.setHeader('Cache-Control', 'no-store');
622
+ if (!canManageConfig(req)) {
623
+ res.statusCode = 403;
624
+ res.end(JSON.stringify({ message: 'Artemis 集成仅允许在本机页面访问' }));
625
+ } else {
626
+ res.end(JSON.stringify(await (pathname.endsWith('/version') ? getArtemisVersion() : getArtemisStatus())));
627
+ }
628
+ return;
629
+ }
618
630
  if ((pathname === '/api/config' || pathname.startsWith('/api/config/') || pathname === '/api/test-model') && !canManageConfig(req)) {
619
631
  res.statusCode = 403;
620
632
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
package/src/App.vue CHANGED
@@ -36,9 +36,11 @@ import { useDevicePreview } from './composables/use-device-preview';
36
36
  import { useScriptExecution } from './composables/use-script-execution';
37
37
  import { useInitialResource } from './composables/use-initial-resource';
38
38
  import AppSidebar from './components/AppSidebar.vue';
39
+ import MidsceneMaintenanceNotice from './components/MidsceneMaintenanceNotice.vue';
39
40
  const AnalysisPage = defineAsyncComponent(() => import('./pages/AnalysisPage.vue'));
40
41
  const AutomationPage = defineAsyncComponent(() => import('./pages/AutomationPage.vue'));
41
42
  const AppiumPage = defineAsyncComponent(() => import('./appium-recorder/AppiumPage.vue'));
43
+ const ArtemisPage = defineAsyncComponent(() => import('./pages/ArtemisPage.vue'));
42
44
  const ConfigPage = defineAsyncComponent(() => import('./pages/ConfigPage.vue'));
43
45
  const GeneratorPage = defineAsyncComponent(() => import('./pages/GeneratorPage.vue'));
44
46
  const HelpCenter = defineAsyncComponent(() => import('./components/help/HelpCenter.vue'));
@@ -50,7 +52,7 @@ import type {
50
52
  } from './types';
51
53
 
52
54
  const ACTIVE_MENU_STORAGE_KEY = 'android-midscene-automation:active-menu';
53
- const menuKeys: MenuKey[] = ['analysis', 'generator', 'automation', 'config', 'appium', 'about'];
55
+ const menuKeys: MenuKey[] = ['appium', 'artemis', 'generator', 'automation', 'analysis', 'config', 'about'];
54
56
  const storedMenu = window.localStorage.getItem(ACTIVE_MENU_STORAGE_KEY) as MenuKey | null;
55
57
  const activeMenu = ref<MenuKey>(storedMenu && menuKeys.includes(storedMenu) ? storedMenu : 'generator');
56
58
  const activeGeneratorMode = ref<GeneratorMode>('ai');
@@ -110,8 +112,9 @@ const { isRunningScript, isStoppingScript, executionLog, executionProcess, lastR
110
112
  const visitedMenus = reactive(new Set<MenuKey>([activeMenu.value]));
111
113
  const openMenus = ref<MenuKey[]>([...menuKeys]);
112
114
  const visibleMenuItems = computed(() => openMenus.value.map(key => menuItems.find(item => item.key === key)!));
113
- function closeMenu(key: MenuKey) {
115
+ async function closeMenu(key: MenuKey) {
114
116
  if (openMenus.value.length === 1) return;
117
+ if (key === 'generator' && !(await confirmUnsavedGeneratorNavigation())) return;
115
118
  const index = openMenus.value.indexOf(key);
116
119
  openMenus.value.splice(index, 1);
117
120
  if (activeMenu.value === key) activeMenu.value = openMenus.value[Math.max(0, index - 1)];
@@ -119,10 +122,11 @@ function closeMenu(key: MenuKey) {
119
122
 
120
123
  const sidebarCollapsed = shallowRef(false);
121
124
  const menuItems = [
122
- { key: 'analysis', label: '分析页', icon: DataAnalysis },
125
+ { key: 'appium', label: 'Appium', icon: Monitor },
126
+ { key: 'artemis', label: 'Artemis', icon: Monitor },
123
127
  { key: 'generator', label: '测试脚本生成', icon: Operation },
124
128
  { key: 'automation', label: '自动化测试', icon: Monitor },
125
- { key: 'appium', label: 'Appium', icon: Monitor },
129
+ { key: 'analysis', label: '分析页', icon: DataAnalysis },
126
130
  { key: 'config', label: '参数配置', icon: Setting },
127
131
  { key: 'about', label: '关于', icon: InfoFilled },
128
132
  ] as const;
@@ -144,6 +148,23 @@ const appPromptPrefix = computed(() => {
144
148
  const effectiveSourcePrompt = computed(() => `${appPromptPrefix.value}${sourcePrompt.value}`.trim());
145
149
  const generatedCodeFromSteps = computed(() => buildScript(form, steps.value));
146
150
  const generatedCode = computed(() => generatedCodeOverride.value ?? generatedCodeFromSteps.value);
151
+ const generatorContentSnapshot = computed(() => JSON.stringify({
152
+ form: {
153
+ promptTitle: form.promptTitle,
154
+ testName: form.testName,
155
+ appPresetId: form.appPresetId,
156
+ },
157
+ sourcePrompt: sourcePrompt.value,
158
+ steps: steps.value,
159
+ generatedCodeOverride: generatedCodeOverride.value,
160
+ generatedCodeDraft: generatedCodeEditing.value ? generatedCodeDraft.value : '',
161
+ }));
162
+ const savedGeneratorSnapshot = shallowRef('');
163
+ const generatorHasUnsavedContent = computed(() => (
164
+ showGeneratedCode.value
165
+ && Boolean(generatedCode.value.trim())
166
+ && generatorContentSnapshot.value !== savedGeneratorSnapshot.value
167
+ ));
147
168
  const selectedScript = computed(() => savedScripts.value.find((item) => item.id === selectedScriptId.value));
148
169
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
149
170
  const deviceActions = [
@@ -155,8 +176,11 @@ const deviceActions = [
155
176
  { key: 'tasks', label: '任务', icon: tasksIcon, keyCode: 187 },
156
177
  ] as const;
157
178
 
158
- const selectMenu = (key: string) => {
159
- activeMenu.value = key as MenuKey;
179
+ const selectMenu = async (key: string) => {
180
+ const nextMenu = key as MenuKey;
181
+ if (nextMenu === activeMenu.value) return;
182
+ if (activeMenu.value === 'generator' && !(await confirmUnsavedGeneratorNavigation())) return;
183
+ activeMenu.value = nextMenu;
160
184
  };
161
185
 
162
186
  const resetGeneratedCodeEditor = () => {
@@ -166,11 +190,6 @@ const resetGeneratedCodeEditor = () => {
166
190
  generatedCodeEditSaving.value = false;
167
191
  };
168
192
 
169
- watch(activeGeneratorMode, () => {
170
- showGeneratedCode.value = false;
171
- resetGeneratedCodeEditor();
172
- });
173
-
174
193
  function formatScriptTime(value: string) {
175
194
  if (!value) return '';
176
195
  const date = new Date(value);
@@ -455,16 +474,16 @@ const buildSourcePromptForSave = () => {
455
474
 
456
475
  const saveCurrentScript = async () => {
457
476
  const validInput = validateCurrentGeneratorInputs();
458
- if (!validInput) return;
477
+ if (!validInput) return false;
459
478
 
460
479
  if (generatedCodeEditing.value) {
461
480
  ElMessage.error('请先保存或撤回代码修改');
462
- return;
481
+ return false;
463
482
  }
464
483
 
465
484
  if (!showGeneratedCode.value || !generatedCode.value.trim()) {
466
485
  ElMessage.error('请先点击 AI 生成,生成代码后再保存脚本');
467
- return;
486
+ return false;
468
487
  }
469
488
 
470
489
  const { scriptName, promptTitle } = validInput;
@@ -478,7 +497,7 @@ const saveCurrentScript = async () => {
478
497
  cancelButtonText: '取消',
479
498
  });
480
499
  } catch {
481
- return;
500
+ return false;
482
501
  }
483
502
  }
484
503
 
@@ -495,10 +514,34 @@ const saveCurrentScript = async () => {
495
514
  });
496
515
 
497
516
  await loadSavedScripts(payload.script?.id || '');
517
+ savedGeneratorSnapshot.value = generatorContentSnapshot.value;
498
518
  await sleep(Math.max(0, 1200 - (Date.now() - startedAt)));
499
519
  closeActionDialog();
520
+ return true;
500
521
  } catch (error) {
501
522
  failActionDialog(error instanceof Error ? error.message : '保存脚本失败');
523
+ return false;
524
+ }
525
+ };
526
+
527
+ const confirmUnsavedGeneratorNavigation = async () => {
528
+ if (!generatorHasUnsavedContent.value) return true;
529
+
530
+ try {
531
+ await ElMessageBox.confirm(
532
+ '当前生成内容尚未保存。可以先保存脚本,或暂不保存并继续切换页面。',
533
+ '未保存的测试脚本',
534
+ {
535
+ type: 'warning',
536
+ confirmButtonText: '保存脚本',
537
+ cancelButtonText: '暂不保存',
538
+ distinguishCancelAndClose: true,
539
+ closeOnClickModal: false,
540
+ },
541
+ );
542
+ return await saveCurrentScript();
543
+ } catch (action) {
544
+ return action === 'cancel';
502
545
  }
503
546
  };
504
547
 
@@ -554,7 +597,7 @@ const generateWithModel = async () => {
554
597
  errorMessage.value = '';
555
598
  const controller = new AbortController();
556
599
  aiGenerateAbortController = controller;
557
- openActionDialog('AI生成', {
600
+ openActionDialog('AI 生成', {
558
601
  canRunInBackground: true,
559
602
  canCancel: true,
560
603
  cancelText: '终止',
@@ -674,7 +717,7 @@ watch(activeMenu, menu => {
674
717
  if (!openMenus.value.includes(menu)) openMenus.value.push(menu);
675
718
  }, { flush: 'sync' });
676
719
  const warnBeforeUnload = (event: BeforeUnloadEvent) => {
677
- if (!appiumAutoSave.dirty.value) return;
720
+ if (!appiumAutoSave.dirty.value && !generatorHasUnsavedContent.value) return;
678
721
  event.preventDefault();
679
722
  event.returnValue = '';
680
723
  };
@@ -695,7 +738,9 @@ onUnmounted(() => {
695
738
  <el-dialog
696
739
  v-model="actionDialog.visible"
697
740
  :title="actionDialog.title"
698
- width="360px"
741
+ width="min(440px, calc(100vw - 32px))"
742
+ align-center
743
+ append-to-body
699
744
  :close-on-click-modal="false"
700
745
  :close-on-press-escape="!actionDialog.loading"
701
746
  :show-close="!actionDialog.loading"
@@ -712,7 +757,7 @@ onUnmounted(() => {
712
757
  <div v-if="actionDialog.loading" class="action-dialog__footer-actions">
713
758
  <el-button
714
759
  v-if="actionDialog.canCancel"
715
- class="action-dialog__footer-button action-dialog__footer-button--secondary"
760
+ class="action-dialog__footer-button"
716
761
  :icon="VideoPause"
717
762
  :loading="actionDialog.canceling"
718
763
  @click="stopGenerateWithModel"
@@ -721,7 +766,8 @@ onUnmounted(() => {
721
766
  </el-button>
722
767
  <el-button
723
768
  v-if="actionDialog.canRunInBackground"
724
- class="action-dialog__footer-button action-dialog__footer-button--primary"
769
+ type="primary"
770
+ class="action-dialog__footer-button"
725
771
  @click="sendActionDialogToBackground"
726
772
  >
727
773
  后台处理
@@ -729,7 +775,8 @@ onUnmounted(() => {
729
775
  </div>
730
776
  <el-button
731
777
  v-else
732
- class="action-dialog__footer-button action-dialog__footer-button--primary"
778
+ type="primary"
779
+ class="action-dialog__footer-button"
733
780
  @click="closeActionDialog"
734
781
  >
735
782
  关闭
@@ -831,6 +878,7 @@ onUnmounted(() => {
831
878
  </div>
832
879
  </nav>
833
880
 
881
+ <MidsceneMaintenanceNotice v-if="activeMenu === 'generator' || activeMenu === 'automation'" />
834
882
  <div v-if="connectionError" class="connection-status" role="status">
835
883
  <span>{{ connectionError }}</span><el-button link type="primary" :loading="reconnecting" @click="reconnect">重新连接</el-button>
836
884
  </div>
@@ -841,12 +889,12 @@ onUnmounted(() => {
841
889
  <main
842
890
  class="page-body"
843
891
  :class="{
844
- 'page-body--fixed': activeMenu === 'generator' && activeGeneratorMode === 'ai',
845
892
  'page-body--appium': activeMenu === 'appium',
846
893
  'page-body--config': activeMenu === 'config',
894
+ 'page-body--artemis': activeMenu === 'artemis',
847
895
  }"
848
896
  >
849
- <el-alert v-if="activeMenu !== 'appium' && activeMenu !== 'config' && activeMenu !== 'about' && errorMessage" type="error" :closable="false" show-icon class="page-alert">
897
+ <el-alert v-if="activeMenu !== 'appium' && activeMenu !== 'artemis' && activeMenu !== 'config' && activeMenu !== 'about' && errorMessage" type="error" :closable="false" show-icon class="page-alert">
850
898
  <template #title>{{ errorMessage }}</template>
851
899
  </el-alert>
852
900
 
@@ -962,6 +1010,7 @@ onUnmounted(() => {
962
1010
  />
963
1011
 
964
1012
  <HelpCenter v-if="visitedMenus.has('about')" v-show="activeMenu === 'about'" :active="activeMenu === 'about'" />
1013
+ <ArtemisPage v-if="visitedMenus.has('artemis')" v-show="activeMenu === 'artemis'" :active="activeMenu === 'artemis'" />
965
1014
  </main>
966
1015
  </div>
967
1016
 
package/src/api.ts CHANGED
@@ -48,6 +48,11 @@ export async function getAppiumVersion() {
48
48
  return readJson<AppiumVersionInfo>(response);
49
49
  }
50
50
 
51
+ export async function getArtemisVersion() {
52
+ const response = await fetch(`${APP_BASE}/api/artemis/version`, { signal: AbortSignal.timeout(8000) });
53
+ return readJson<{ version: string; message: string }>(response);
54
+ }
55
+
51
56
  // 读取已保存的脚本列表,自动化测试页面左侧脚本列表使用。
52
57
  export async function getScripts() {
53
58
  const response = await fetch(`${APP_BASE}/api/scripts`);