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,511 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Asynchronous Visual Step Summarizer for Artemis Flash profile.
16
+
17
+ Processes Before/After screenshot pairs and action metadata in the background
18
+ to generate high-density, strictly objective visual state transition summaries.
19
+
20
+ Scheduling (zero-blocking dispatch, bounded retry, bounded flush, step_id
21
+ keying with tool_call_id aliases) lives in the shared
22
+ :class:`artemis.memory.step_memory.StepMemoryService`; this class contributes
23
+ only the visual-transition lens: red action-marker overlay on the BEFORE
24
+ frame, the ``flash_summarizer.md`` neutral-wording contract, and versioned
25
+ ``summary_status`` writes to the DataEngine.
26
+ """
27
+
28
+ import asyncio
29
+ import base64
30
+ from pathlib import Path
31
+ from typing import Any
32
+ from uuid import UUID
33
+
34
+ from jinja2 import Template
35
+ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
36
+
37
+ from artemis.context import ArtemisContext
38
+ from artemis.llm.parser import extract_thinking_tags
39
+ from artemis.llm.structured import content_to_text
40
+ from artemis.memory.step_memory import JobKey, StepMemoryService
41
+ from artemis.services.llm import RobustChatModelWrapper, get_google_llm, get_llm
42
+ from artemis.services.token_meter import record_llm_usage
43
+ from artemis.utils.task_tree import format_actions_clean
44
+ from artemis.utils.visualization import draw_action_overlay_on_image
45
+ from third_party.mobile_use.utils.logger import get_logger
46
+
47
+ logger = get_logger(__name__)
48
+
49
+ # Degenerate-output guard (§5 echo validation): a summary that echoes the
50
+ # input's section markers, or falls outside sane length bounds, fails the
51
+ # attempt so the bounded service retry regenerates it.
52
+ SUMMARY_ECHO_MARKER = "--- ["
53
+ SUMMARY_MIN_CHARS = 15
54
+ SUMMARY_MAX_CHARS = 1500
55
+
56
+
57
+ def degenerate_summary_reason(text: str) -> str | None:
58
+ """Why a lens output is unusable (echo/length), or None when it is fine."""
59
+ if text.lower().startswith(("<think>", "<thought>", "<thinking>")):
60
+ return "unclosed thinking block; no final summary"
61
+ if SUMMARY_ECHO_MARKER in text or text.startswith("---"):
62
+ return "echoes an input section marker"
63
+ if len(text) < SUMMARY_MIN_CHARS:
64
+ return f"too short ({len(text)} chars < {SUMMARY_MIN_CHARS})"
65
+ if len(text) > SUMMARY_MAX_CHARS:
66
+ return f"too long ({len(text)} chars > {SUMMARY_MAX_CHARS})"
67
+ return None
68
+
69
+
70
+ #: Header of the operator-focus block that leads the lens input. It shares
71
+ #: the ``--- [`` shape of the frame headers so an echoed header trips the
72
+ #: same degenerate-output guard.
73
+ FOCUS_BLOCK_HEADER = "--- [0] OPERATOR FOCUS (context for attention, not evidence) ---"
74
+
75
+ #: Cap on the operator reasoning excerpt carried in the focus block. The
76
+ #: expectation statement closes the reasoning, so the tail is kept intact
77
+ #: and the cut lands in the middle.
78
+ FOCUS_INTENT_MAX_CHARS = 900
79
+ FOCUS_INTENT_TAIL_CHARS = 600
80
+
81
+
82
+ def _cap_intent(text: str) -> str:
83
+ if len(text) <= FOCUS_INTENT_MAX_CHARS:
84
+ return text
85
+ head_len = FOCUS_INTENT_MAX_CHARS - FOCUS_INTENT_TAIL_CHARS
86
+ head, tail = text[:head_len], text[-FOCUS_INTENT_TAIL_CHARS:]
87
+ cut = len(text) - len(head) - len(tail)
88
+ return f"{head}\n[… {cut} characters cut here …]\n{tail}"
89
+
90
+
91
+ def build_focus_context(
92
+ *,
93
+ intent: str | None = None,
94
+ goal: str | None = None,
95
+ subgoal: str | None = None,
96
+ injected_instruction: str | None = None,
97
+ ) -> dict[str, str] | None:
98
+ """Collect operator context to guide the visual summary's focus.
99
+
100
+ Target descriptions remain on the action. Return None if all fields are empty.
101
+ """
102
+ focus: dict[str, str] = {}
103
+ if isinstance(goal, str) and goal.strip():
104
+ focus["goal"] = goal.strip()
105
+ if isinstance(subgoal, str) and subgoal.strip():
106
+ focus["subgoal"] = subgoal.strip()
107
+ if isinstance(injected_instruction, str) and injected_instruction.strip():
108
+ focus["injected_instruction"] = injected_instruction.strip()
109
+ if isinstance(intent, str) and intent.strip():
110
+ focus["intent"] = _cap_intent(intent.strip())
111
+ return focus or None
112
+
113
+
114
+ def render_focus_block(focus: dict[str, str], *, dual: bool = True) -> str:
115
+ """Plain-text focus block (goal, sub-goal, instruction, then reasoning).
116
+
117
+ ``dual=False`` (a single decision frame, every Pro step) labels the
118
+ reasoning so its expectation is read as the operator's aim only: the
119
+ outcome is not in the frame and must not be described.
120
+ """
121
+ lines = [FOCUS_BLOCK_HEADER]
122
+ if focus.get("goal"):
123
+ lines.append(f"Task goal: {focus['goal']}")
124
+ if focus.get("subgoal"):
125
+ lines.append(f"Active sub-goal: {focus['subgoal']}")
126
+ if focus.get("injected_instruction"):
127
+ lines.append(f"User instruction at this step: {focus['injected_instruction']}")
128
+ if focus.get("intent"):
129
+ if dual:
130
+ lines.append(
131
+ "Operator reasoning for this step (why this target, what screen"
132
+ " change it expected):"
133
+ )
134
+ else:
135
+ lines.append(
136
+ "Operator reasoning for this step (why this target; the expected"
137
+ " outcome is NOT in this frame and must not be described):"
138
+ )
139
+ lines.append(focus["intent"])
140
+ return "\n".join(lines)
141
+
142
+
143
+ class VisualStepSummarizer(StepMemoryService):
144
+ """Visual-transition lens on top of the shared step-memory runtime.
145
+
146
+ Key design properties (inherited from StepMemoryService):
147
+ 1. Zero-blocking dispatch: The main Flash runner dispatches and proceeds immediately.
148
+ 2. Lossless pending state: The compressor retains the source image until its summary is ready.
149
+ 3. Independent bounded retry: Every action owns a retry loop capped at 1 + retry_limit attempts.
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ ctx: ArtemisContext,
155
+ model_name: str | None = None,
156
+ retry_limit: int = 3,
157
+ *,
158
+ max_concurrency: int = 1,
159
+ flush_timeout_s: float = 30.0,
160
+ ):
161
+ super().__init__(
162
+ ctx,
163
+ max_concurrency=max_concurrency,
164
+ retry_limit=retry_limit,
165
+ flush_timeout_s=flush_timeout_s,
166
+ )
167
+
168
+ # Legacy lightweight Gemini names apply only to Google configurations.
169
+ target_model = model_name or "gemini-2.5-flash-lite"
170
+ self._model_name = target_model
171
+ if ctx.llm_config is None:
172
+ from artemis.config.llm import get_default_llm_config
173
+ ctx.llm_config = get_default_llm_config()
174
+ role = ctx.llm_config.get_agent("summarizer")
175
+ if role.provider != "google" or not model_name:
176
+ self._llm = get_llm(ctx, name="summarizer", temperature=0.0)
177
+ self._model_name = role.model
178
+ else:
179
+ self._llm = get_google_llm(model_name=target_model, temperature=0.0)
180
+ try:
181
+ configured = getattr(self._llm, "model", None) or getattr(self._llm, "model_name", None)
182
+ if isinstance(configured, str) and configured:
183
+ self._model_name = configured
184
+ except Exception as exc:
185
+ logger.debug(
186
+ "Could not read the configured summarizer model name; keeping %s: %s",
187
+ self._model_name,
188
+ exc,
189
+ exc_info=True,
190
+ )
191
+
192
+ # Load system prompt templates: the dual-frame transition prompt and
193
+ # the single-frame variant (§5 revision: describe whichever frames
194
+ # exist — a Pro step record never carries an independent after-frame,
195
+ # by design, and must not be prompted as if one were expected).
196
+ prompt_path = Path(__file__).parent / "flash_summarizer.md"
197
+ if prompt_path.exists():
198
+ self._prompt_template = prompt_path.read_text(encoding="utf-8")
199
+ else:
200
+ self._prompt_template = (
201
+ "You are the Step Summarizer for an Android UI automation agent.\n"
202
+ "Synthesize the physical action and visual delta between BEFORE and AFTER screens in exactly ONE "
203
+ "continuous first-person paragraph using 'I' (e.g., 'In Step {{ step_number }}, I tapped... and observed...').\n"
204
+ "Strictly avoid subjective validation words: successfully, completed, failed, achieved, navigated to."
205
+ )
206
+ single_path = Path(__file__).parent / "flash_summarizer_single.md"
207
+ if single_path.exists():
208
+ self._single_prompt_template = single_path.read_text(encoding="utf-8")
209
+ else:
210
+ self._single_prompt_template = (
211
+ "You are the Step Summarizer for an Android UI automation agent.\n"
212
+ "Exactly ONE screenshot (the decision frame) is available; there is NO after-action screenshot —"
213
+ " that only means no independent post-action evidence exists.\n"
214
+ "Describe strictly what THIS screen shows and where the action landed (red marker), in exactly ONE"
215
+ " continuous first-person paragraph using 'I' for Step {{ step_number }}. Never describe or guess"
216
+ " the post-action state.\n"
217
+ "Strictly avoid subjective validation words: successfully, completed, failed, achieved, navigated to."
218
+ )
219
+
220
+ def dispatch(
221
+ self,
222
+ step_number: int,
223
+ action_name: str,
224
+ action_args: dict[str, Any],
225
+ pre_img_bytes: bytes | None,
226
+ post_img_bytes: bytes | None,
227
+ exec_outcome: str,
228
+ *,
229
+ action_key: str | None = None,
230
+ data_engine_step_id: UUID | str | None = None,
231
+ focus: dict[str, str] | None = None,
232
+ ) -> None:
233
+ """Dispatches an asynchronous summarization task without blocking the caller.
234
+
235
+ Jobs are keyed by the DataEngine step id when one is available; the
236
+ tool_call_id (``action_key``) is retained as an alias so the message
237
+ compressor can keep querying by it. Callers with neither provide the
238
+ step ordinal, matching the legacy keying.
239
+
240
+ ``focus`` (:func:`build_focus_context`) is the operator's own context
241
+ for the step; the lens renders it ahead of the frames so the details
242
+ the operator was after are transcribed rather than summarized away.
243
+ """
244
+ key: JobKey
245
+ aliases: tuple[JobKey, ...] = ()
246
+ if data_engine_step_id is not None:
247
+ key = str(data_engine_step_id)
248
+ if action_key is not None:
249
+ aliases = (action_key,)
250
+ else:
251
+ key = action_key if action_key is not None else step_number
252
+
253
+ payload = {
254
+ "step_number": step_number,
255
+ "action_name": action_name,
256
+ "action_args": action_args,
257
+ "pre_img_bytes": pre_img_bytes,
258
+ "post_img_bytes": post_img_bytes,
259
+ "exec_outcome": exec_outcome,
260
+ "data_engine_step_id": data_engine_step_id,
261
+ "focus": focus,
262
+ }
263
+ self.submit(key, payload, aliases=aliases)
264
+
265
+ @staticmethod
266
+ def _action_phrase(action_name: str, action_args: dict[str, Any] | None) -> str:
267
+ """The action as every other history reader sees it (``format_actions_clean``).
268
+
269
+ ``action_args`` is the recorded action minus its verb (what the Pro
270
+ summarizer extracts from ``action_taken`` and what the Flash runner
271
+ builds from the same record shape). The action name always wins over
272
+ an argument of the same key: ``manage_app``'s own ``action="launch"``
273
+ argument becomes the ``intent`` the renderer reads, never the verb. A
274
+ described coordinate target renders as ``'play button'
275
+ (self-described)``, an observed element as ``'Play'``; a Pro burst
276
+ lists every member.
277
+ """
278
+ args = dict(action_args or {})
279
+ extra = args.pop("additional_actions", None)
280
+ own_verb = args.pop("action", None)
281
+ if own_verb is not None and own_verb != action_name:
282
+ args.setdefault("intent", own_verb)
283
+ # Flash's coordinate swipe names its points ``start``/``end``; the
284
+ # history renderer reads ``start_coordinates``/``end_coordinates``.
285
+ if action_name == "swipe":
286
+ raw = args.get("args") if isinstance(args.get("args"), dict) else {}
287
+ start = args.get("start", raw.get("start"))
288
+ end = args.get("end", raw.get("end"))
289
+ if start is not None and end is not None:
290
+ args.setdefault("start_coordinates", start)
291
+ args.setdefault("end_coordinates", end)
292
+ try:
293
+ first = {**args, "action": action_name}
294
+ actions = [first, *extra] if isinstance(extra, list) and extra else first
295
+ return format_actions_clean(actions)
296
+ except Exception as exc:
297
+ logger.debug(f"Action phrase rendering fell back to raw args: {exc}", exc_info=True)
298
+ return f"{action_name}({action_args})"
299
+
300
+ @staticmethod
301
+ def _verbatim_args(action_args: dict[str, Any] | None) -> dict[str, Any]:
302
+ """The agent-facing arguments of the action.
303
+
304
+ A Flash record keeps the tool call's arguments under ``args``; a Pro
305
+ record is the arguments themselves. Bookkeeping keys never count.
306
+ """
307
+ args = action_args or {}
308
+ raw = args.get("args")
309
+ if isinstance(raw, dict):
310
+ return dict(raw)
311
+ internal = {
312
+ "additional_actions",
313
+ "coordinate_space",
314
+ "normalized_coordinates",
315
+ "normalized_start_coordinates",
316
+ "normalized_end_coordinates",
317
+ }
318
+ return {k: v for k, v in args.items() if k not in internal}
319
+
320
+ @staticmethod
321
+ def _phrase_carries(phrase: str, args: dict[str, Any]) -> bool:
322
+ """Whether every argument value is visible in the rendered phrase.
323
+
324
+ Booleans and ``None`` never count (the phrase spells their effect, not
325
+ their value); everything else must appear verbatim, so an index
326
+ target, a repeat count or a duration the phrase folded away keeps the
327
+ verbatim argument line.
328
+ """
329
+ haystack = phrase.lower()
330
+
331
+ def _shown(value: Any) -> bool:
332
+ if isinstance(value, (list, tuple)):
333
+ # A point renders whole (``at [880, 410]``); a sequence or a
334
+ # description list renders member by member.
335
+ return str(list(value)).lower() in haystack or (
336
+ bool(value) and all(_shown(v) for v in value)
337
+ )
338
+ return str(value).lower() in haystack
339
+
340
+ return all(
341
+ _shown(value)
342
+ for value in args.values()
343
+ if value is not None and not isinstance(value, bool)
344
+ )
345
+
346
+ def _meter_lens_call(self, response: Any) -> None:
347
+ """Meter one raw-model lens call as an ``llm_usage`` trace, best-effort.
348
+
349
+ Gateway-wrapped models already meter at the wrapper exit; only the raw
350
+ ``get_google_llm`` bypass needs explicit metering here. Lens prompts
351
+ are tiny and must not overwrite the session's ``last_prompt_tokens``
352
+ (the compaction thresholds' live context base), hence
353
+ ``update_last_prompt=False``.
354
+ """
355
+ if isinstance(self._llm, RobustChatModelWrapper):
356
+ return
357
+ engine = getattr(self.ctx, "data_engine", None) if self.ctx else None
358
+ record_llm_usage(
359
+ engine,
360
+ response,
361
+ source=f"lens:visual_transition:{self._model_name}",
362
+ update_last_prompt=False,
363
+ )
364
+
365
+ def _on_status(self, key: JobKey, status: str) -> None:
366
+ """Best-effort DataEngine summary-status write (pending/failed)."""
367
+ if not self.ctx.data_engine:
368
+ return
369
+ payload = self._step_inputs.get(key) or {}
370
+ target_step = payload.get("data_engine_step_id")
371
+ if not target_step:
372
+ return
373
+ try:
374
+ self.ctx.data_engine.update_step_summary(
375
+ target_step,
376
+ None,
377
+ status=status,
378
+ source="visual_transition",
379
+ model=self._model_name,
380
+ )
381
+ except Exception as de_err:
382
+ logger.debug(f"DataEngine summary status update skipped: {de_err}")
383
+
384
+ async def _attempt(self, key: JobKey) -> bool:
385
+ """Execute one lightweight VLM attempt for a step transition."""
386
+ input_data = self._step_inputs.get(key)
387
+ if not input_data:
388
+ return False
389
+
390
+ step_number = input_data["step_number"]
391
+ action_name = input_data["action_name"]
392
+ action_args = input_data["action_args"]
393
+ pre_bytes = input_data["pre_img_bytes"]
394
+ post_bytes = input_data["post_img_bytes"]
395
+ exec_outcome = input_data["exec_outcome"]
396
+
397
+ try:
398
+ # §5 revision: the lens input adapts to whichever frames exist.
399
+ # Both frames -> the classic BEFORE/AFTER transition prompt; a
400
+ # single frame (Pro steps never carry an independent after-frame,
401
+ # by design) -> the single-frame variant, which has no AFTER
402
+ # ACTION section and forbids synthesizing a transition.
403
+ dual = bool(pre_bytes) and bool(post_bytes)
404
+ template = self._prompt_template if dual else self._single_prompt_template
405
+ rendered_prompt = Template(template).render(step_number=step_number)
406
+ phrase = self._action_phrase(action_name, action_args)
407
+ lead_lines = [f"Step {step_number} Physical Action: {phrase}"]
408
+ # The verbatim arguments are worth a line only when the phrase
409
+ # folded some of them away (an index target, a repeat count, ...).
410
+ verbatim = self._verbatim_args(action_args)
411
+ if verbatim and not self._phrase_carries(phrase, verbatim):
412
+ lead_lines.append(f"Action arguments (verbatim): {action_name}({verbatim})")
413
+ lead_lines.append(f"Controller Outcome: {exec_outcome}")
414
+ focus = input_data.get("focus")
415
+ if isinstance(focus, dict) and focus:
416
+ lead_lines.extend(["", render_focus_block(focus, dual=dual)])
417
+ content_blocks: list[dict[str, Any]] = [{"type": "text", "text": "\n".join(lead_lines)}]
418
+
419
+ if pre_bytes:
420
+ # 🎨 Visually mark the exact action (tap ripple, sequence numbers, swipe arrow) on the BEFORE screenshot
421
+ annotated_pre_bytes = draw_action_overlay_on_image(
422
+ image_bytes=pre_bytes,
423
+ action_name=action_name,
424
+ action_args=action_args,
425
+ )
426
+ b64_pre = base64.b64encode(annotated_pre_bytes).decode("utf-8")
427
+ content_blocks.append(
428
+ {
429
+ "type": "text",
430
+ "text": (
431
+ "--- [1] BEFORE ACTION SCREEN (Action Marked Visually in Red) ---"
432
+ if dual
433
+ else "--- [1] DECISION FRAME: SCREEN AT ACTION TIME"
434
+ " (Action Marked Visually in Red) ---"
435
+ ),
436
+ }
437
+ )
438
+ content_blocks.append(
439
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_pre}"}}
440
+ )
441
+
442
+ if post_bytes:
443
+ b64_post = base64.b64encode(post_bytes).decode("utf-8")
444
+ content_blocks.append(
445
+ {
446
+ "type": "text",
447
+ "text": (
448
+ "--- [2] AFTER ACTION SCREEN ---"
449
+ if dual
450
+ else "--- [1] SCREEN OBSERVED AFTER THE ACTION"
451
+ " (no decision frame available) ---"
452
+ ),
453
+ }
454
+ )
455
+ content_blocks.append(
456
+ {
457
+ "type": "image_url",
458
+ "image_url": {"url": f"data:image/jpeg;base64,{b64_post}"},
459
+ }
460
+ )
461
+
462
+ messages: list[BaseMessage] = [
463
+ SystemMessage(content=rendered_prompt),
464
+ HumanMessage(content=content_blocks),
465
+ ]
466
+
467
+ response = await asyncio.wait_for(self._llm.ainvoke(messages), timeout=25.0)
468
+ self._meter_lens_call(response)
469
+ # Only the final answer is a summary. Reasoning can be much longer
470
+ # than the summary limit and must not enter memory or trigger retries.
471
+ summary_text, _ = extract_thinking_tags(content_to_text(response.content))
472
+ degenerate = degenerate_summary_reason(summary_text) if summary_text else None
473
+ if degenerate:
474
+ logger.warning(
475
+ f"VisualStepSummarizer: Discarding degenerate summary for"
476
+ f" Step {step_number} ({degenerate}): {summary_text[:80]!r}"
477
+ )
478
+ return False
479
+ if summary_text:
480
+ self._summaries[key] = summary_text
481
+ logger.info(
482
+ f"VisualStepSummarizer: Generated summary for Step {step_number}: {summary_text[:80]}..."
483
+ )
484
+
485
+ # Free binary image buffers (and the focus text) once the
486
+ # summary is secured; a landed job is never re-rendered.
487
+ if key in self._step_inputs:
488
+ self._step_inputs[key]["pre_img_bytes"] = None
489
+ self._step_inputs[key]["post_img_bytes"] = None
490
+ self._step_inputs[key]["focus"] = None
491
+
492
+ # Update DataEngine telemetry if active
493
+ if self.ctx.data_engine:
494
+ try:
495
+ target_step = input_data.get("data_engine_step_id") or step_number
496
+ self.ctx.data_engine.update_step_summary(
497
+ target_step,
498
+ summary_text,
499
+ status="ready",
500
+ source="visual_transition",
501
+ model=self._model_name,
502
+ )
503
+ except Exception as de_err:
504
+ logger.debug(f"DataEngine step summary update skipped: {de_err}")
505
+ return True
506
+
507
+ except Exception as e:
508
+ logger.warning(
509
+ f"VisualStepSummarizer: Error generating summary for step {step_number}: {e}"
510
+ )
511
+ return False
@@ -0,0 +1,8 @@
1
+ # IDENTITY
2
+ You are a **History Analyzer**, expert in analyzing multi-agent session execution history to answer user queries.
3
+
4
+ # MANDATE
5
+ 1. You are provided with the **Task Plan and Execution History** summarizing the high-level plan and history steps.
6
+ 2. For queries about specific step details (such as precise actions, coordinates, the reasoning behind a step, or its execution result), call `replay_steps` for the step or range in question; use `search_history` to locate the right steps by keyword when the step numbers are not obvious, and `get_step_screenshot` when you need to see what the screen showed.
7
+ 3. Never assume, guess, or invent details not visible in the Task Plan and Execution History. Always query `replay_steps` for specifics. If details are unavailable, state it clearly.
8
+ 4. Make good use of note tools to see what notes are available. When analyzing history records, open and cross-reference relevant notes to ensure you have grasped all necessary information.