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,563 @@
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
+ """search_history: deterministic keyword / step-range lookup over stored history.
16
+
17
+ One search surface over a :class:`~artemis.data_engine.history_reader.HistoryReader`'s
18
+ raw records — step summaries, history chunks, exact actions and execution
19
+ results, reasoning, the tool calls a step made (name, arguments, result
20
+ text), notes, the OCR/UI-tree text of the step's pre and post screenshots
21
+ (which also carries package/activity strings) plus any screenshot a tool
22
+ result embedded, and the foreground-app stamp. Keyword + step-range filtering
23
+ in Python; no vector database, no model call.
24
+
25
+ Hard boundaries (config ``agent.memory.recall``):
26
+
27
+ - at most ``max_results`` results (default 5);
28
+ - one response is capped at ``max_text_tokens`` estimated tokens (char/4);
29
+ - screen text is scanned for the most recent ``screen_scan_steps`` steps;
30
+ - every result carries a step number / step id;
31
+ - large raw sources return only excerpts around the match plus a reference.
32
+
33
+ Two surfaces per step: the *scoring* haystack keeps the raw record (action
34
+ JSON, execution result JSON, UI-tree XML) so resource ids, bounds and package
35
+ names stay searchable; the *excerpt* shown as ``Match:`` is built from the
36
+ readable twin (the ledger action phrase, result status/error words, element
37
+ text / content descriptions / OCR text) so the model never reads JSON.
38
+
39
+ A ``step_range`` additionally returns the full-width ``build_action_ledger``
40
+ rows of that range — the re-entry point for compressed-history marker lines.
41
+ """
42
+
43
+ from __future__ import annotations
44
+
45
+ import json
46
+ import sqlite3
47
+ from typing import Any
48
+
49
+ from artemis.core.tool_failure import ToolFailure
50
+ from artemis.utils.task_tree import format_actions_clean
51
+ from third_party.mobile_use.utils.logger import get_logger
52
+
53
+ logger = get_logger(__name__)
54
+
55
+ #: Character clamp for one matched excerpt.
56
+ EXCERPT_CHARS = 240
57
+
58
+ #: Per-tool-result clamp on the *search* surface (scoring/excerpts only; the
59
+ #: replay rendering has its own, looser clamp).
60
+ RESULT_SEARCH_CHARS = 8000
61
+
62
+ DEFAULT_MAX_RESULTS = 5
63
+ DEFAULT_MAX_TEXT_TOKENS = 2000
64
+ DEFAULT_SCREEN_SCAN_STEPS = 150
65
+
66
+
67
+ def _terms(query: str) -> list[str]:
68
+ return [t for t in (query or "").lower().split() if t]
69
+
70
+
71
+ def _estimate_tokens(text: str) -> int:
72
+ return len(text) // 4
73
+
74
+
75
+ def _clamp(text: str, limit: int) -> str:
76
+ text = (text or "").strip()
77
+ if len(text) <= limit:
78
+ return text
79
+ return text[: limit - 1].rstrip() + "…"
80
+
81
+
82
+ def _excerpt(text: str, terms: list[str], limit: int) -> str:
83
+ """A window of ``text`` around the first term hit (flattened whitespace)."""
84
+ flat = " ".join((text or "").split())
85
+ lower = flat.lower()
86
+ pos = -1
87
+ for term in terms:
88
+ pos = lower.find(term)
89
+ if pos >= 0:
90
+ break
91
+ if pos < 0:
92
+ return _clamp(flat, limit)
93
+ start = max(0, pos - limit // 3)
94
+ window = flat[start : start + limit]
95
+ prefix = "…" if start > 0 else ""
96
+ suffix = "…" if start + limit < len(flat) else ""
97
+ return f"{prefix}{window.strip()}{suffix}"
98
+
99
+
100
+ def _score(haystack: str, terms: list[str]) -> int:
101
+ lower = haystack.lower()
102
+ return sum(1 for term in terms if term in lower)
103
+
104
+
105
+ def result_search_text(result: Any) -> str:
106
+ """Searchable text of a tool result. Content-block lists contribute their
107
+ text blocks — image blocks have already been rewritten into
108
+ ``[screenshot: …]`` descriptions by the DataEngine; any raw image block
109
+ that slipped through contributes nothing (its OCR/XML description joins
110
+ the haystack via :func:`_screen_text`), so base64 never enters the search."""
111
+ if result is None:
112
+ return ""
113
+ # A content-block list stored as its JSON string (trace serialization)
114
+ # is unwrapped first so its text blocks render, not the JSON.
115
+ if isinstance(result, str) and result.lstrip().startswith("[{"):
116
+ try:
117
+ parsed = json.loads(result)
118
+ except ValueError:
119
+ parsed = None
120
+ if isinstance(parsed, list) and any(isinstance(b, dict) and "type" in b for b in parsed):
121
+ return result_search_text(parsed)
122
+ if isinstance(result, list):
123
+ texts = [
124
+ str(block.get("text") or "")
125
+ for block in result
126
+ if isinstance(block, dict) and block.get("type") == "text"
127
+ ]
128
+ if texts or any(
129
+ isinstance(block, dict) and block.get("type") in ("image_url", "image")
130
+ for block in result
131
+ ):
132
+ return "\n".join(texts)[:RESULT_SEARCH_CHARS]
133
+ if isinstance(result, dict):
134
+ return json.dumps(result, ensure_ascii=False, default=str)[:RESULT_SEARCH_CHARS]
135
+ return str(result)[:RESULT_SEARCH_CHARS]
136
+
137
+
138
+ def _events_text(step: dict) -> str:
139
+ parts: list[str] = []
140
+ for event in step.get("interleaved_events") or []:
141
+ etype = event.get("type")
142
+ if etype == "tool_call":
143
+ parts.append(str(event.get("name") or ""))
144
+ parts.append(json.dumps(event.get("args") or {}, ensure_ascii=False, default=str))
145
+ result_text = result_search_text(event.get("result"))
146
+ if result_text:
147
+ parts.append(result_text)
148
+ elif etype and "thought" in etype:
149
+ parts.append(str(event.get("content") or ""))
150
+ return "\n".join(parts)
151
+
152
+
153
+ def _rendered_action(action_taken: Any) -> str:
154
+ """The shared ledger phrase of a step's action (``format_actions_clean``)."""
155
+ if not action_taken:
156
+ return ""
157
+ try:
158
+ return format_actions_clean(action_taken)
159
+ except Exception:
160
+ return ""
161
+
162
+
163
+ def _action_text(action_taken: Any) -> str:
164
+ """Combine the rendered action with raw fields for *scoring*.
165
+
166
+ Rendered text identifies self-described targets. Raw JSON also makes
167
+ resource IDs, bounds, and other fields searchable. Never shown as-is:
168
+ the excerpt comes from :func:`_step_display_text`.
169
+ """
170
+ if not action_taken:
171
+ return ""
172
+ rendered = _rendered_action(action_taken)
173
+ raw = json.dumps(action_taken, ensure_ascii=False, default=str)
174
+ return f"{rendered}\n{raw}" if rendered else raw
175
+
176
+
177
+ def _step_haystack(step: dict) -> str:
178
+ """The scoring surface of a step: every recorded field, raw JSON included."""
179
+ extra = step.get("extra_metadata") or {}
180
+ fields = [
181
+ str(step.get("summary") or ""),
182
+ _action_text(step.get("action_taken")),
183
+ json.dumps(step.get("last_execution_result"), ensure_ascii=False, default=str)
184
+ if step.get("last_execution_result")
185
+ else "",
186
+ str(step.get("operator_raw_thinking") or ""),
187
+ _events_text(step),
188
+ str(extra.get("injected_instruction") or ""),
189
+ # The foreground app package stamped by record_step joins the search
190
+ # surface, so package-name queries never depend on the screen-text
191
+ # scan (capped to recent steps) alone.
192
+ str(extra.get("foreground_app") or ""),
193
+ ]
194
+ return "\n".join(f for f in fields if f)
195
+
196
+
197
+ def _result_display_text(result: Any) -> str:
198
+ """The execution result as words: its status and result/error strings
199
+ (an execution incident renders as its one-line phrase), never JSON."""
200
+ if not result:
201
+ return ""
202
+ if not isinstance(result, dict):
203
+ return str(result)
204
+ parts: list[str] = []
205
+ status = result.get("status")
206
+ if status:
207
+ parts.append(f"status {status}")
208
+ try:
209
+ from artemis.utils.task_tree import format_result_clean
210
+
211
+ incident_or_error = format_result_clean(result)
212
+ except Exception:
213
+ incident_or_error = None
214
+ if incident_or_error:
215
+ parts.append(str(incident_or_error))
216
+ else:
217
+ for key in ("result", "error", "error_msg", "message"):
218
+ value = result.get(key)
219
+ if isinstance(value, str) and value.strip():
220
+ parts.append(value.strip())
221
+ return " | ".join(parts)
222
+
223
+
224
+ def _events_display_text(step: dict) -> str:
225
+ """Tool calls and reasoning of a step as the replay shows them (name,
226
+ arguments, result text), without the raw event JSON."""
227
+ parts: list[str] = []
228
+ for event in step.get("interleaved_events") or []:
229
+ etype = event.get("type")
230
+ if etype == "tool_call":
231
+ name = str(event.get("name") or "")
232
+ args = event.get("args") or {}
233
+ result_text = result_search_text(event.get("result"))
234
+ try:
235
+ call = f"{name}({json.dumps(args, ensure_ascii=False, default=str)})"
236
+ except (TypeError, ValueError):
237
+ call = name
238
+ parts.append(f"{call} -> {result_text}" if result_text else call)
239
+ elif etype and "thought" in etype:
240
+ parts.append(str(event.get("content") or ""))
241
+ return "\n".join(p for p in parts if p)
242
+
243
+
244
+ def _step_display_text(step: dict) -> str:
245
+ """The *excerpt* surface of a step: the same fields as the haystack, as the
246
+ agent would read them. The summary is left out — it already heads the
247
+ result as the ``Screen:`` line."""
248
+ extra = step.get("extra_metadata") or {}
249
+ fields = [
250
+ _rendered_action(step.get("action_taken")),
251
+ _result_display_text(step.get("last_execution_result")),
252
+ str(step.get("operator_raw_thinking") or ""),
253
+ _events_display_text(step),
254
+ str(extra.get("injected_instruction") or ""),
255
+ str(extra.get("foreground_app") or ""),
256
+ ]
257
+ # Excerpts flatten whitespace, so the field boundary must survive as text.
258
+ return " | ".join(f.strip() for f in fields if f and f.strip())
259
+
260
+
261
+ def _image_text(reader: Any, image_name: str | None) -> str:
262
+ """Raw XML/OCR text stored for one screenshot — the textual description
263
+ of that image (package/activity strings live inside the XML blob)."""
264
+ if not image_name:
265
+ return ""
266
+ try:
267
+ record = reader.storage.get_image(image_name)
268
+ except (sqlite3.Error, ValueError):
269
+ return ""
270
+ if record is None:
271
+ return ""
272
+ parts = []
273
+ for blob in (record.ui_tree, record.ocr_result):
274
+ if blob:
275
+ parts.append(
276
+ blob if isinstance(blob, str) else json.dumps(blob, ensure_ascii=False, default=str)
277
+ )
278
+ return "\n".join(parts)
279
+
280
+
281
+ def referenced_image_names(step: dict) -> list[str]:
282
+ """Screenshots that tool results of this step embedded (rewritten by the
283
+ DataEngine into ``[screenshot: …]`` text blocks carrying ``image_name``)."""
284
+ names: list[str] = []
285
+
286
+ def walk(value: Any) -> None:
287
+ if isinstance(value, dict):
288
+ name = value.get("image_name")
289
+ if value.get("type") == "text" and name and name not in names:
290
+ names.append(str(name))
291
+ for child in value.values():
292
+ walk(child)
293
+ elif isinstance(value, list):
294
+ for child in value:
295
+ walk(child)
296
+
297
+ for event in step.get("interleaved_events") or []:
298
+ if event.get("type") == "tool_call":
299
+ walk(event.get("result"))
300
+ return names
301
+
302
+
303
+ #: UI-tree node attributes whose values are human-readable screen content.
304
+ _UI_NODE_TEXT_KEYS = ("text", "content-desc", "content_desc", "resource-id", "resource_id")
305
+
306
+
307
+ def _collect_screen_strings(value: Any, out: list[str], depth: int = 0) -> None:
308
+ """Walks a stored UI tree / OCR blob and appends its readable values
309
+ (element text, content description, resource id, OCR text) in order."""
310
+ if depth > 50:
311
+ return
312
+ if isinstance(value, str):
313
+ text = " ".join(value.split())
314
+ if text and text not in out:
315
+ out.append(text)
316
+ elif isinstance(value, dict):
317
+ for key in _UI_NODE_TEXT_KEYS:
318
+ text = value.get(key)
319
+ if isinstance(text, str):
320
+ text = " ".join(text.split())
321
+ if text and text not in out:
322
+ out.append(text)
323
+ for child_key in ("children", "nodes", "elements"):
324
+ children = value.get(child_key)
325
+ if isinstance(children, (list, tuple)):
326
+ for child in children:
327
+ _collect_screen_strings(child, out, depth + 1)
328
+ elif isinstance(value, (list, tuple)):
329
+ for child in value:
330
+ _collect_screen_strings(child, out, depth + 1)
331
+
332
+
333
+ def _screen_names(step: dict) -> list[str]:
334
+ names: list[str] = []
335
+ for name in (
336
+ step.get("pre_image_name"),
337
+ step.get("post_image_name"),
338
+ *referenced_image_names(step),
339
+ ):
340
+ if name and name not in names:
341
+ names.append(name)
342
+ return names
343
+
344
+
345
+ def _screen_text(reader: Any, step: dict) -> str:
346
+ """Description text of the screenshots behind a step (its own pre- and
347
+ post-action screenshots plus any screenshot a tool result embedded) —
348
+ the raw XML/OCR blobs, for *scoring* only."""
349
+ return "\n".join(t for t in (_image_text(reader, n) for n in _screen_names(step)) if t)
350
+
351
+
352
+ def _screen_display_text(reader: Any, step: dict) -> str:
353
+ """The readable content of the same screenshots for the *excerpt*: element
354
+ text / content descriptions / resource ids and OCR text joined by
355
+ ``" | "``, never the UI-tree JSON."""
356
+ strings: list[str] = []
357
+ for name in _screen_names(step):
358
+ try:
359
+ record = reader.storage.get_image(name)
360
+ except (sqlite3.Error, ValueError, AttributeError):
361
+ continue
362
+ if record is None:
363
+ continue
364
+ for blob in (record.ui_tree, record.ocr_result):
365
+ if blob:
366
+ _collect_screen_strings(blob, strings)
367
+ return " | ".join(strings)
368
+
369
+
370
+ def _step_label(step: dict, session_start: float | None) -> str:
371
+ from artemis.memory.chunking import step_offset_label
372
+
373
+ return f"Step {step.get('step_number')} ({step_offset_label(step, session_start)})"
374
+
375
+
376
+ def _note_writer_steps(steps: list[dict], key: str) -> list[int]:
377
+ writers: list[int] = []
378
+ for step in steps:
379
+ for event in step.get("interleaved_events") or []:
380
+ if (
381
+ event.get("type") == "tool_call"
382
+ and event.get("name") in ("save_note", "update_note", "append_note")
383
+ and (event.get("args") or {}).get("key") == key
384
+ ):
385
+ number = step.get("step_number")
386
+ if isinstance(number, int):
387
+ writers.append(number)
388
+ return writers
389
+
390
+
391
+ def _parse_range(step_range: list[int] | None) -> tuple[int | None, int | None]:
392
+ if not step_range:
393
+ return None, None
394
+ try:
395
+ start, end = int(step_range[0]), int(step_range[-1])
396
+ except (TypeError, ValueError, IndexError):
397
+ return None, None
398
+ if start > end:
399
+ start, end = end, start
400
+ return start, end
401
+
402
+
403
+ # pylint: disable=too-many-branches,too-many-locals,too-many-statements
404
+ def search_history_text(
405
+ reader: Any,
406
+ *,
407
+ query: str = "",
408
+ step_range: list[int] | None = None,
409
+ max_results: int = DEFAULT_MAX_RESULTS,
410
+ recall_config: Any = None,
411
+ ) -> str:
412
+ """Deterministic history search over ``reader``; always returns text."""
413
+ cfg_max_results = int(getattr(recall_config, "max_results", None) or DEFAULT_MAX_RESULTS)
414
+ cfg_max_tokens = int(getattr(recall_config, "max_text_tokens", None) or DEFAULT_MAX_TEXT_TOKENS)
415
+ scan_steps = int(getattr(recall_config, "screen_scan_steps", None) or DEFAULT_SCREEN_SCAN_STEPS)
416
+ max_results = max(1, min(int(max_results or cfg_max_results), cfg_max_results))
417
+ char_budget = cfg_max_tokens * 4
418
+
419
+ try:
420
+ steps: list[dict] = reader.get_agent_friendly_steps() or []
421
+ except Exception as e:
422
+ return ToolFailure(f"search_history failed to load history: {e}")
423
+
424
+ range_start, range_end = _parse_range(step_range)
425
+
426
+ def in_range(number: Any) -> bool:
427
+ if range_start is None:
428
+ return True
429
+ return isinstance(number, int) and range_start <= number <= range_end
430
+
431
+ scoped_steps = [s for s in steps if in_range(s.get("step_number"))]
432
+ terms = _terms(query)
433
+ session_start = getattr(reader, "session_start_time", None)
434
+
435
+ if not steps:
436
+ return "search_history: no recorded steps yet."
437
+ if step_range and not scoped_steps:
438
+ return f"search_history: no recorded steps in range {range_start}–{range_end}."
439
+
440
+ sections: list[str] = []
441
+
442
+ # --- Range ledger: the explicit re-entry point for compressed-history
443
+ # marker lines — full-width build_action_ledger rows.
444
+ if range_start is not None and scoped_steps:
445
+ from artemis.memory.chunking import build_action_ledger
446
+
447
+ ledger = build_action_ledger(scoped_steps, session_start)
448
+ sections.append(
449
+ f"Action ledger for Steps {range_start}–{range_end}"
450
+ f" ({len(scoped_steps)} recorded steps):\n{ledger}"
451
+ )
452
+
453
+ # --- Keyword search over steps.
454
+ results: list[tuple[int, int, str]] = [] # (score, step_number, rendered)
455
+ if terms:
456
+ screen_eligible = {id(s) for s in scoped_steps[-scan_steps:]} if scan_steps > 0 else set()
457
+ for step in scoped_steps:
458
+ haystack = _step_haystack(step)
459
+ screen = _screen_text(reader, step) if id(step) in screen_eligible else ""
460
+ score = _score(haystack, terms)
461
+ screen_score = _score(screen, terms) if screen else 0
462
+ total = score + screen_score
463
+ if total <= 0:
464
+ continue
465
+ lines = [f"[{_step_label(step, session_start)} | id {step.get('step_id')}]"]
466
+ summary = step.get("summary")
467
+ if summary:
468
+ lines.append(f" Screen: {_clamp(str(summary), EXCERPT_CHARS)}")
469
+ # The excerpt comes from the readable twin of whichever surface
470
+ # scored higher; the raw JSON only ever scores. When the hit sits
471
+ # in the summary alone, the Screen line already shows it.
472
+ display = (
473
+ _step_display_text(step)
474
+ if score >= screen_score
475
+ else _screen_display_text(reader, step)
476
+ )
477
+ display_hit = _score(display, terms) > 0
478
+ summary_hit = bool(summary) and _score(str(summary), terms) > 0
479
+ if display and (display_hit or not summary_hit):
480
+ lines.append(f" Match: {_excerpt(display, terms, EXCERPT_CHARS)}")
481
+ number = step.get("step_number") or 0
482
+ results.append((total, number, "\n".join(lines)))
483
+
484
+ # --- History chunks (bands ①②③ / rendered text).
485
+ try:
486
+ chunk_rows = reader.get_history_chunks() or []
487
+ except (sqlite3.Error, ValueError):
488
+ chunk_rows = []
489
+ for row in chunk_rows:
490
+ start_n = getattr(row, "start_step_number", None)
491
+ end_n = getattr(row, "end_step_number", None)
492
+ if range_start is not None and not (
493
+ isinstance(start_n, int)
494
+ and isinstance(end_n, int)
495
+ and start_n <= range_end
496
+ and end_n >= range_start
497
+ ):
498
+ continue
499
+ text = getattr(row, "rendered_text", None) or "\n".join(
500
+ filter(None, [getattr(row, "band2", None), getattr(row, "band3", None)])
501
+ )
502
+ score = _score(text or "", terms)
503
+ if score <= 0:
504
+ continue
505
+ rendered = (
506
+ f"[History chunk | Steps {start_n}–{end_n} | status"
507
+ f" {getattr(row, 'status', '?')}]\n Match: {_excerpt(text, terms, EXCERPT_CHARS)}"
508
+ )
509
+ results.append((score, int(end_n or 0), rendered))
510
+
511
+ # --- Notes (filesystem; attributed to their writing steps).
512
+ try:
513
+ from artemis.utils.notes import list_notes_keys, read_note_content
514
+
515
+ base_dir = getattr(reader, "base_dir", None)
516
+ note_keys = list_notes_keys(base_dir) if base_dir else []
517
+ except OSError:
518
+ note_keys = []
519
+ for key in note_keys:
520
+ try:
521
+ content = read_note_content(base_dir, key)
522
+ except (OSError, UnicodeError):
523
+ continue
524
+ score = _score(content, terms) + _score(key, terms)
525
+ if score <= 0:
526
+ continue
527
+ writers = _note_writer_steps(steps, key)
528
+ if (
529
+ range_start is not None
530
+ and writers
531
+ and not any(range_start <= n <= range_end for n in writers)
532
+ ):
533
+ continue
534
+ anchor = writers[-1] if writers else (steps[-1].get("step_number") or 0)
535
+ origin = f"last written at Step {writers[-1]}" if writers else f"as of Step {anchor}"
536
+ results.append(
537
+ (
538
+ score,
539
+ int(anchor),
540
+ f"[Note '{key}' | {origin}]\n Match: {_excerpt(content, terms, EXCERPT_CHARS)}",
541
+ )
542
+ )
543
+
544
+ results.sort(key=lambda r: (-r[0], -r[1]))
545
+ kept = results[:max_results]
546
+ dropped = len(results) - len(kept)
547
+
548
+ if kept:
549
+ header = f"Top {len(kept)} match(es) for {terms}:"
550
+ if dropped > 0:
551
+ header += f" ({dropped} more not shown; narrow the query or step_range)"
552
+ sections.append(header + "\n\n" + "\n".join(r[2] for r in kept))
553
+ elif terms:
554
+ sections.append(f"No matches for {terms} in the searched history.")
555
+
556
+ text = "\n\n".join(sections)
557
+ if _estimate_tokens(text) > cfg_max_tokens:
558
+ text = (
559
+ text[:char_budget].rstrip()
560
+ + "\n… (response truncated at the search token budget; narrow the"
561
+ " query or step_range)"
562
+ )
563
+ return text
@@ -0,0 +1,124 @@
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
+ """Universal Image Processor tool for executing image transformations."""
16
+
17
+ from typing import Any
18
+
19
+ from langchain_core.tools import BaseTool
20
+ from pydantic import BaseModel, Field
21
+
22
+ from artemis.context import ArtemisContext
23
+ from artemis.data_engine.trace import trace, trace_langchain_tool
24
+ from artemis.drivers.base import BaseDeviceDriver
25
+ from artemis.graph.state import State
26
+ from artemis.tools.base import ArtemisTool
27
+ from artemis.tools.tool_wrapper import ToolWrapper
28
+ from artemis.utils.cython_compat import CyFunctionDetector
29
+ from third_party.mobile_use.utils.logger import get_logger
30
+
31
+ logger = get_logger(__name__)
32
+
33
+
34
+ # pylint: disable=too-few-public-methods
35
+ class AskVisionCoderArgs(BaseModel):
36
+ """Arguments schema for invoking the Image Processor subagent."""
37
+
38
+ model_config = {"ignored_types": (CyFunctionDetector,)}
39
+ instruction: str = Field(
40
+ ...,
41
+ description=(
42
+ "Step-by-step instructions on what image processing or"
43
+ " modifications should be performed."
44
+ ),
45
+ )
46
+ target_image_id: str = Field(
47
+ ...,
48
+ description=(
49
+ "The ID of the image in the Image Pool to start from (e.g. 'img_0', 'img_1')."
50
+ ),
51
+ )
52
+
53
+
54
+ # Alias for naming consistency
55
+ AskImageProcessorArgs = AskVisionCoderArgs
56
+
57
+
58
+ class AskImageProcessorTool(ArtemisTool):
59
+ """Universal tool for executing image transformations via ImageProcessor subagent."""
60
+
61
+ def __init__(self):
62
+ super().__init__(
63
+ name="ask_image_processor",
64
+ description=(
65
+ "Call this tool to ask the image processor agent to run"
66
+ " complex image transformations or modifications."
67
+ ),
68
+ args_schema=AskVisionCoderArgs,
69
+ category="custom",
70
+ )
71
+
72
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
73
+ async def execute(
74
+ self,
75
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
76
+ ctx: ArtemisContext | None = None,
77
+ instruction: str | None = None,
78
+ target_image_id: str | None = None,
79
+ state: State | None = None,
80
+ **kwargs: Any,
81
+ ) -> Any:
82
+ inst = instruction or kwargs.get("Instruction") or ""
83
+ tid = target_image_id or kwargs.get("TargetImageId") or kwargs.get("target_image_id") or ""
84
+ st = state if state is not None else kwargs.get("state")
85
+ return await _run_image_processor_logic(ctx, st, inst, tid)
86
+
87
+
88
+ # Universal tool instance & aliases
89
+ ask_image_processor = AskImageProcessorTool()
90
+ AskImageProcessor = AskImageProcessorTool
91
+ AskVisionCoderTool = AskImageProcessorTool
92
+ ImageProcessorTool = AskImageProcessorTool
93
+
94
+
95
+ def get_ask_image_processor_tool(ctx: ArtemisContext) -> BaseTool:
96
+ """Exports ask_image_processor as a LangChain BaseTool."""
97
+ return trace_langchain_tool(ask_image_processor.to_langchain_tool(ctx), ctx)
98
+
99
+
100
+ async def _run_image_processor_logic(
101
+ ctx: ArtemisContext | None,
102
+ state: State | None,
103
+ instruction: str,
104
+ target_image_id: str, # pylint: disable=unused-argument
105
+ ) -> Any:
106
+ @trace(type="agent", name="image_processor", ctx=ctx)
107
+ async def run_image_processor_agent(inst: str, img_path: str) -> dict:
108
+ from artemis.agents.image_processor.image_processor import ( # pylint: disable=import-outside-toplevel
109
+ ImageProcessor,
110
+ )
111
+
112
+ agent = ImageProcessor(ctx)
113
+ return await agent.run(inst, img_path)
114
+
115
+ screenshot_path = state.latest_screenshot if state else None
116
+ result = await run_image_processor_agent(instruction, screenshot_path)
117
+ return result
118
+
119
+
120
+ ask_image_processor_wrapper = ToolWrapper(
121
+ tool_fn_getter=get_ask_image_processor_tool,
122
+ on_success_fn=lambda output: f"Image Processor replied:\n{output}",
123
+ on_failure_fn=lambda error: f"Image Processor failed: {error}",
124
+ )