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,234 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import base64
17
+ import hashlib
18
+ import json
19
+ from pathlib import Path
20
+
21
+ from artemis.context import ArtemisContext
22
+ from artemis.controllers.unified_controller import UnifiedMobileController
23
+ from artemis.data_engine.trace import trace
24
+ from artemis.graph.state import State
25
+ from artemis.graph.visibility import strict_state
26
+ from artemis.utils.ocr_api import is_ocr_configured, perform_ocr
27
+ from artemis.utils.ocr_xml_fusion import (
28
+ _crop_image_remove_status_bar,
29
+ _detect_status_bar_height,
30
+ _map_coordinates_back,
31
+ fuse_ocr_with_xml,
32
+ )
33
+ from third_party.mobile_use.utils.logger import get_logger
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ def _should_skip_settling(state: State) -> bool:
39
+ """Determine if we should skip screen settling based on the last executed action.
40
+
41
+ Purely logical, non-UI-drawing, or wait operations don't require dynamic
42
+ settling checks.
43
+ """
44
+ if not state or not state.structured_decisions:
45
+ # First turn or no prior action, skip settling
46
+ return True
47
+
48
+ try:
49
+ actions = json.loads(state.structured_decisions)
50
+ if not actions or not isinstance(actions, list):
51
+ return True
52
+
53
+ last_action = actions[-1]
54
+ action_name = last_action.get("action")
55
+
56
+ # Actions that are known to have already completed full wait loops or don't render UI
57
+ skip_actions = {
58
+ "wait_for_delay",
59
+ }
60
+
61
+ if action_name in skip_actions:
62
+ logger.info(f"Last action was '{action_name}'. Skipping dynamic screen settling.")
63
+ return True
64
+
65
+ return False
66
+ except Exception as e:
67
+ logger.warning(
68
+ f"Failed to parse last action for settling heuristic: {e}."
69
+ " Defaulting to settling check."
70
+ )
71
+ return False
72
+
73
+
74
+ @trace(type="agent", name="perception")
75
+ def _check_injected_instruction_file(base_dir: str) -> dict | None:
76
+ """Consumes a pending injected-instruction payload, if any.
77
+
78
+ Returns the payload dict: `instruction` (str | None) plus the explicit
79
+ `release_loop` stop signal (bool) declared by the injecting caller.
80
+ """
81
+ instruction_path = Path(base_dir) / "injected_instruction.json"
82
+ if instruction_path.exists():
83
+ with open(instruction_path, encoding="utf-8") as f:
84
+ data = json.load(f)
85
+ instruction_path.unlink()
86
+ payload = {
87
+ "instruction": data.get("instruction"),
88
+ "release_loop": bool(data.get("release_loop")),
89
+ }
90
+ logger.info(f"Read and deleted injected instruction payload: {payload}")
91
+ return payload
92
+ return None
93
+
94
+
95
+ def _unprotect_current_check_lines(ctx: ArtemisContext) -> None:
96
+ """Record the plan's current check lines as editable under user guidance."""
97
+ from artemis.utils.notes import get_note_file_path
98
+ from artemis.utils.plan_grammar import parse_plan
99
+
100
+ unprotected = getattr(ctx, "guidance_unprotected_checks", None)
101
+ if not isinstance(unprotected, set):
102
+ unprotected = set()
103
+ try:
104
+ plan_path = get_note_file_path(ctx.data_engine.base_dir, "task_plan")
105
+ text = plan_path.read_text(encoding="utf-8") if plan_path.exists() else ""
106
+ except Exception as e:
107
+ logger.warning(f"Could not read task_plan for the guidance check-line waiver: {e}")
108
+ text = ""
109
+ added = {(ci.kind, ci.text) for ci in parse_plan(text).check_items}
110
+ unprotected |= added
111
+ ctx.guidance_unprotected_checks = unprotected
112
+ logger.info(
113
+ f"User guidance received: {len(added)} declared check line(s) may now be edited"
114
+ " by the Operator."
115
+ )
116
+
117
+
118
+ async def perception_node(state: State, ctx: ArtemisContext) -> dict:
119
+ """Perception node (fast path): captures screenshot, extracts XML hierarchy, and performs OCR.
120
+
121
+ Always executes at the start of an operator cycle.
122
+ """
123
+ state = strict_state(state, "perception")
124
+ logger.info("Entering Perception Node...")
125
+
126
+ if not ctx.data_engine.current_step_id:
127
+ ctx.data_engine.allocate_step_id()
128
+ logger.info(f"Pre-allocated step ID in Perception Node: {ctx.data_engine.current_step_id}")
129
+
130
+ # Check for injected instruction without blocking event loop
131
+ injected_instruction = None
132
+ user_stop_requested = False
133
+ if ctx.data_engine and ctx.data_engine.base_dir:
134
+ injected_payload = await asyncio.to_thread(
135
+ _check_injected_instruction_file, str(ctx.data_engine.base_dir)
136
+ )
137
+ if injected_payload:
138
+ injected_instruction = injected_payload.get("instruction")
139
+ user_stop_requested = bool(injected_payload.get("release_loop"))
140
+ if isinstance(injected_instruction, str) and injected_instruction.strip():
141
+ # User guidance outranks the plan's declared standards: every
142
+ # check line that exists right now loses its machine
143
+ # restoration for the rest of the run (kept on the run context,
144
+ # not the per-turn State), so the Operator can drop or reword
145
+ # it in whichever later write it gets to it. Lines added after
146
+ # this moment stay protected until the next instruction.
147
+ _unprotect_current_check_lines(ctx)
148
+
149
+ controller = UnifiedMobileController(ctx)
150
+
151
+ # 1. Capture screenshot with hybrid settling strategy
152
+ if _should_skip_settling(state):
153
+ logger.info("Skipping settling. Retrieving screen data directly...")
154
+ device_data = await controller.get_screen_data()
155
+ latest_screenshot_b64 = device_data.base64
156
+ xml_hierarchy = device_data.elements
157
+ else:
158
+ logger.info("Screen settling required. Delaying 0.4s and capturing screen data...")
159
+ await asyncio.sleep(0.4)
160
+ device_data = await controller.get_screen_data()
161
+ latest_screenshot_b64 = device_data.base64
162
+ xml_hierarchy = device_data.elements
163
+
164
+ latest_screenshot_bytes = base64.b64decode(latest_screenshot_b64)
165
+
166
+ # Update context device dimensions dynamically
167
+ if ctx.device:
168
+ ctx.device.device_width = device_data.width
169
+ ctx.device.device_height = device_data.height
170
+ logger.info(f"Updated context device dimensions: {device_data.width}x{device_data.height}")
171
+
172
+ # 3. Perform OCR (if configured, else fallback to pure XML layout)
173
+ ocr_results = []
174
+ if is_ocr_configured():
175
+ screen_height = device_data.height
176
+ status_bar_height = _detect_status_bar_height(xml_hierarchy, screen_height)
177
+ try:
178
+ if status_bar_height > 0:
179
+ logger.info(
180
+ f"Detected status bar height: {status_bar_height}. Cropping screenshot for OCR."
181
+ )
182
+ cropped_b64, _, _ = await asyncio.to_thread(
183
+ _crop_image_remove_status_bar,
184
+ latest_screenshot_b64,
185
+ status_bar_height,
186
+ )
187
+ raw_ocr_results = await perform_ocr(cropped_b64)
188
+ ocr_results = _map_coordinates_back(raw_ocr_results, status_bar_height)
189
+ else:
190
+ logger.info("No status bar detected. Performing OCR on full screenshot.")
191
+ ocr_results = await perform_ocr(latest_screenshot_b64)
192
+ except Exception as ocr_err:
193
+ logger.warning(
194
+ f"Perception OCR execution encountered error: {ocr_err}. Proceeding with XML-only layout."
195
+ )
196
+ ocr_results = []
197
+ else:
198
+ logger.debug("OCR is not configured. Proceeding with XML-only layout.")
199
+
200
+ # 4. Fuse OCR with XML (if ocr_results is empty, returns original xml_hierarchy intact)
201
+ fused_xml = fuse_ocr_with_xml(xml_hierarchy, ocr_results)
202
+
203
+ # 5. Save to Data Engine in background thread
204
+ image_name = None
205
+ screenshot_path = None
206
+ if ctx.data_engine:
207
+ image_name = hashlib.sha256(latest_screenshot_bytes).hexdigest()
208
+ screenshot_path = str(ctx.data_engine.get_image_path(image_name))
209
+ asyncio.create_task(
210
+ asyncio.to_thread(
211
+ ctx.data_engine.get_or_create_image,
212
+ latest_screenshot_bytes,
213
+ ui_tree=xml_hierarchy,
214
+ ocr_result=ocr_results,
215
+ )
216
+ )
217
+ logger.info(f"Offloaded perception data saving to background with image_name: {image_name}")
218
+
219
+ # Return state update
220
+ update = {
221
+ "latest_screenshot": screenshot_path,
222
+ "latest_ui_hierarchy": fused_xml,
223
+ "operator_raw_data": {
224
+ "screenshot_b64": latest_screenshot_b64,
225
+ "xml_hierarchy": xml_hierarchy if xml_hierarchy is not None else [],
226
+ "ocr_results": ocr_results,
227
+ "width": device_data.width,
228
+ "height": device_data.height,
229
+ },
230
+ "injected_instruction": injected_instruction,
231
+ # sticky_or reducer: once latched True, later False updates keep it True
232
+ "user_stop_requested": user_stop_requested,
233
+ }
234
+ return update
@@ -0,0 +1,151 @@
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
+ from typing import Annotated
16
+
17
+ from pydantic import BaseModel, ConfigDict
18
+
19
+ from third_party.mobile_use.graph import take_last
20
+ from third_party.mobile_use.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ def sticky_or(a, b):
26
+ """Reducer that latches True: once set, the flag survives later updates."""
27
+ return bool(a) or bool(b)
28
+
29
+
30
+ class State(BaseModel):
31
+ """Graph channel state.
32
+
33
+ Every field is explicit (``extra="forbid"``): an undeclared write or an
34
+ unknown constructor key fails loudly instead of becoming a silent ghost
35
+ key. Undeclared *reads* (``getattr(state, k, default)``) are guarded by the
36
+ node visibility manifest in ``artemis.graph.visibility`` instead.
37
+ """
38
+
39
+ model_config = ConfigDict(
40
+ arbitrary_types_allowed=True,
41
+ extra="forbid",
42
+ )
43
+
44
+ # ── Control plane (cross-turn signals & routing) ─────────────────────
45
+ initial_goal: Annotated[str, "Initial goal given by the user"]
46
+ injected_instruction: Annotated[
47
+ str | None,
48
+ "Injected instruction from user for micro-adjustment",
49
+ take_last,
50
+ ] = None
51
+ user_stop_requested: Annotated[
52
+ bool,
53
+ "Latched True once the user externally signals stop (release_loop),"
54
+ " unlocking completion of [Loop:continuous] milestones",
55
+ sticky_or,
56
+ ] = False
57
+ checker_success: Annotated[
58
+ bool | None,
59
+ "True if checker succeeded or not triggered, False if failed",
60
+ take_last,
61
+ ] = None
62
+ operator_feedback: Annotated[
63
+ list[str] | None,
64
+ "Source-tagged findings ([checker]/[planner]/[final check]) injected"
65
+ " append-only into the Operator's next prompt (never switches the"
66
+ " prompt template)",
67
+ take_last,
68
+ ] = None
69
+ run_outcome: Annotated[
70
+ dict | None,
71
+ "Machine-readable run outcome (task_status + test summary), populated"
72
+ " by exit settlement before END",
73
+ take_last,
74
+ ] = None
75
+ exit_settlement_route: Annotated[
76
+ str | None,
77
+ "Routing decision produced by exit_settlement_node ('continue' | 'end')",
78
+ take_last,
79
+ ] = None
80
+
81
+ # ── Perception (moves behind PerceptionStore in Phase 2) ─────────────
82
+ latest_ui_hierarchy: Annotated[
83
+ list[dict] | None, "Latest UI hierarchy of the device", take_last
84
+ ] = None
85
+ latest_screenshot: Annotated[str | None, "Path to the latest screenshot", take_last] = None
86
+ operator_raw_data: Annotated[
87
+ dict | None,
88
+ "Raw perception data from operator (screenshot, xml, ocr)",
89
+ take_last,
90
+ ] = None
91
+ indexed_points: Annotated[
92
+ list[list[int]] | None,
93
+ "Active coordinate mappings matching indexed text elements",
94
+ take_last,
95
+ ] = None
96
+ indexed_elements: Annotated[
97
+ list[dict] | None,
98
+ "Active interactable elements matching indexed text elements,"
99
+ " containing text, bounds, class, and center coordinates.",
100
+ take_last,
101
+ ] = None
102
+
103
+ # ── Turn products (previous turn's decisions & results) ──────────────
104
+ current_step_id: Annotated[str | None, "Current step ID from data engine", take_last] = None
105
+ structured_decisions: Annotated[
106
+ str | None,
107
+ "Structured decisions made by the operator, for the validator to follow",
108
+ take_last,
109
+ ] = None
110
+ operator_raw_thinking: Annotated[
111
+ str | None,
112
+ "Raw thinking process of the operator",
113
+ take_last,
114
+ ] = None
115
+ operator_native_thinking: Annotated[
116
+ str | None,
117
+ "Native/implicit thinking process of the operator",
118
+ take_last,
119
+ ] = None
120
+ last_execution_result: Annotated[
121
+ dict | None, "Last execution result from validator", take_last
122
+ ] = None
123
+ open_incident: Annotated[
124
+ dict | None,
125
+ "Execution incident (blocked/failed terminal action) still awaiting the"
126
+ " Operator's resolution; None once a later action executes successfully",
127
+ take_last,
128
+ ] = None
129
+ last_closed_incident: Annotated[
130
+ dict | None,
131
+ "The incident the most recent successful terminal action closed (with"
132
+ " closed_at_step); rendered once so the Operator settles the original intent",
133
+ take_last,
134
+ ] = None
135
+ subagent_calls: Annotated[
136
+ list[str], "List of sub-agent calls in current session", take_last
137
+ ] = []
138
+ operator_tool_limit_exceeded: Annotated[
139
+ bool | None,
140
+ "True if operator exceeded tool call limit in the previous turn",
141
+ take_last,
142
+ ] = None
143
+
144
+ @classmethod
145
+ def initial(cls, goal: str) -> "State":
146
+ """Single source for the graph's initial state.
147
+
148
+ Every entrypoint (SDK, engine runners, tests) must construct the
149
+ first State through here so required fields stay in one place.
150
+ """
151
+ return cls(initial_goal=goal)
@@ -0,0 +1,238 @@
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
+ """Per-node State visibility manifests with test-time enforcement.
16
+
17
+ ``extra="forbid"`` on :class:`~artemis.graph.state.State` catches ghost
18
+ *writes*; ghost *reads* (``getattr(state, key, default)`` silently returning
19
+ the default) can only be caught by policing reads against a declared manifest.
20
+
21
+ The manifest below is the declared visible domain of every graph node — the
22
+ authoritative answer to "which State fields does this agent see / produce".
23
+ Enforcement is zero-cost in production: :func:`strict_state` returns the state
24
+ unchanged unless ``ARTEMIS_STRICT_STATE=1`` (set in tests), in which case field
25
+ reads outside the node's manifest raise :class:`VisibilityError`.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import os
31
+ from dataclasses import dataclass
32
+ from typing import Any
33
+
34
+ from artemis.graph.state import State
35
+
36
+
37
+ class VisibilityError(Exception):
38
+ """A node touched a State field outside its declared visible domain."""
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class NodeVisibility:
43
+ reads: frozenset[str]
44
+ writes: frozenset[str]
45
+
46
+ @property
47
+ def visible(self) -> frozenset[str]:
48
+ return self.reads | self.writes
49
+
50
+
51
+ def _vis(reads: set[str], writes: set[str]) -> NodeVisibility:
52
+ return NodeVisibility(reads=frozenset(reads), writes=frozenset(writes))
53
+
54
+
55
+ #: Declared visible domain per node. Reads include fields touched by the tools
56
+ #: a node invokes with ``InjectedState`` (they run inside the node's turn).
57
+ #: Start generous, tighten over time — an entry here is a documented decision.
58
+ NODE_VISIBILITY: dict[str, NodeVisibility] = {
59
+ "perception": _vis(
60
+ reads={"structured_decisions"},
61
+ writes={
62
+ "latest_screenshot",
63
+ "latest_ui_hierarchy",
64
+ "operator_raw_data",
65
+ "injected_instruction",
66
+ "user_stop_requested",
67
+ },
68
+ ),
69
+ "planner": _vis(
70
+ reads={"initial_goal", "latest_screenshot"},
71
+ # Mutates latest_screenshot in place when it captures its own frame
72
+ # (planner runs before perception on the first turn).
73
+ writes={"latest_screenshot"},
74
+ ),
75
+ "operator": _vis(
76
+ reads={
77
+ "initial_goal",
78
+ "injected_instruction",
79
+ "user_stop_requested",
80
+ "operator_feedback",
81
+ "operator_raw_data",
82
+ "latest_ui_hierarchy",
83
+ "latest_screenshot",
84
+ "indexed_points",
85
+ "indexed_elements",
86
+ "subagent_calls",
87
+ "current_step_id",
88
+ "operator_tool_limit_exceeded",
89
+ "structured_decisions",
90
+ "operator_raw_thinking",
91
+ "operator_native_thinking",
92
+ # Transcript path (M2): committing the previous turn into the
93
+ # ledger appends its validator result message.
94
+ "last_execution_result",
95
+ # Execution incident hand-off from the Validator (open block /
96
+ # one-turn CLOSED notice in the observation tail).
97
+ "open_incident",
98
+ "last_closed_incident",
99
+ },
100
+ writes={
101
+ "structured_decisions",
102
+ "operator_raw_thinking",
103
+ "operator_native_thinking",
104
+ "indexed_points",
105
+ "indexed_elements",
106
+ "current_step_id",
107
+ "subagent_calls",
108
+ "operator_tool_limit_exceeded",
109
+ },
110
+ ),
111
+ "execution_check": _vis(
112
+ reads={
113
+ "operator_raw_data",
114
+ "structured_decisions",
115
+ "operator_raw_thinking",
116
+ "operator_native_thinking",
117
+ "user_stop_requested",
118
+ "initial_goal",
119
+ # The turn's injected instruction is stamped verbatim onto the
120
+ # step record so the chunk ledger can preserve it (never-evict).
121
+ "injected_instruction",
122
+ },
123
+ writes={
124
+ "checker_success",
125
+ "structured_decisions",
126
+ "current_step_id",
127
+ "operator_feedback",
128
+ "subagent_calls",
129
+ },
130
+ ),
131
+ "validator": _vis(
132
+ reads={
133
+ "open_incident",
134
+ "initial_goal",
135
+ "structured_decisions",
136
+ "latest_screenshot",
137
+ "latest_ui_hierarchy",
138
+ "current_step_id",
139
+ "operator_raw_data",
140
+ "operator_raw_thinking",
141
+ "operator_native_thinking",
142
+ "indexed_points",
143
+ "indexed_elements",
144
+ },
145
+ writes={
146
+ "open_incident",
147
+ "last_closed_incident",
148
+ "last_execution_result",
149
+ # In-place turn-scoped mutations (formalized as TurnWorkspace in
150
+ # Phase 2): post-action screenshot and re-derived index mappings.
151
+ "latest_screenshot",
152
+ "indexed_points",
153
+ "indexed_elements",
154
+ },
155
+ ),
156
+ "summarizer": _vis(
157
+ reads={
158
+ "current_step_id",
159
+ "structured_decisions",
160
+ "operator_raw_thinking",
161
+ "operator_native_thinking",
162
+ "last_execution_result",
163
+ },
164
+ writes=set(),
165
+ ),
166
+ "convergence": _vis(
167
+ reads={"checker_success", "user_stop_requested"},
168
+ writes=set(),
169
+ ),
170
+ "exit_settlement": _vis(
171
+ reads={"user_stop_requested", "initial_goal"},
172
+ writes={"exit_settlement_route", "operator_feedback", "run_outcome"},
173
+ ),
174
+ }
175
+
176
+ _STATE_FIELDS = frozenset(State.model_fields.keys())
177
+
178
+
179
+ def strict_enabled() -> bool:
180
+ return os.environ.get("ARTEMIS_STRICT_STATE", "") == "1"
181
+
182
+
183
+ class StateView:
184
+ """Read-policing proxy handed to nodes under ``ARTEMIS_STRICT_STATE=1``.
185
+
186
+ Only declared State *fields* are policed; methods and pydantic internals
187
+ pass through. Attribute writes delegate to the underlying State (in-place
188
+ turn mutations are Phase 2 scope) but are still checked against the
189
+ node's write set.
190
+ """
191
+
192
+ __slots__ = ("_state", "_node", "_visibility")
193
+
194
+ def __init__(self, state: State, node: str):
195
+ object.__setattr__(self, "_state", state)
196
+ object.__setattr__(self, "_node", node)
197
+ object.__setattr__(self, "_visibility", NODE_VISIBILITY[node])
198
+
199
+ def __getattr__(self, name: str) -> Any:
200
+ if name in _STATE_FIELDS and name not in self._visibility.visible:
201
+ raise VisibilityError(
202
+ f"Node '{self._node}' read undeclared State field '{name}'."
203
+ " Declare it in NODE_VISIBILITY or stop reading it."
204
+ )
205
+ return getattr(self._state, name)
206
+
207
+ def __setattr__(self, name: str, value: Any) -> None:
208
+ if name in _STATE_FIELDS and name not in self._visibility.writes:
209
+ raise VisibilityError(
210
+ f"Node '{self._node}' mutated undeclared State field '{name}'."
211
+ " Declare it in NODE_VISIBILITY or stop writing it."
212
+ )
213
+ setattr(self._state, name, value)
214
+
215
+
216
+ def strict_state(state: State, node: str) -> State | StateView:
217
+ """Wraps ``state`` in a policing view when strict mode is on (tests)."""
218
+ if strict_enabled() and isinstance(state, State):
219
+ return StateView(state, node)
220
+ return state
221
+
222
+
223
+ def check_update(node: str, update: dict | None) -> None:
224
+ """Validates a node's returned update keys against its write manifest."""
225
+ if not update:
226
+ return
227
+ illegal = set(update) & _STATE_FIELDS - NODE_VISIBILITY[node].writes
228
+ if illegal:
229
+ raise VisibilityError(f"Node '{node}' returned undeclared update keys: {sorted(illegal)}.")
230
+
231
+
232
+ def validate_manifest() -> list[str]:
233
+ """Returns problems in the manifest itself (ghost fields). Empty = clean."""
234
+ problems = []
235
+ for node, vis in NODE_VISIBILITY.items():
236
+ for field in vis.visible - _STATE_FIELDS:
237
+ problems.append(f"{node}: '{field}' is not a declared State field")
238
+ return problems
@@ -0,0 +1,19 @@
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
+ """ARTEMIS CLI interface package."""
16
+
17
+ from artemis.interfaces.cli.main import app, cli
18
+
19
+ __all__ = ["app", "cli"]
@@ -0,0 +1,15 @@
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
+ """CLI command modules."""