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,267 @@
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
+ """Session-level LLM token usage and cache-hit metering.
16
+
17
+ Every gateway-managed LLM call reports its real ``usage_metadata`` here. The
18
+ meter accumulates per-session totals and records one ``llm_usage`` trace per
19
+ call into the DataEngine, carrying:
20
+
21
+ - the call's measured ``prompt_tokens`` (``context_base_tokens`` in the trace
22
+ payload, kept for the timeline). Note: the session meter's
23
+ ``last_prompt_tokens`` is whichever call happened last in the session —
24
+ Planner, Validator, Checker, sub-agents included — so the compaction
25
+ thresholds do NOT read it; the operator records its own prompt size on the
26
+ transcript ledger (``TranscriptLedger.record_prompt_tokens``);
27
+ - provider-reported cache hits (Gemini ``cached_content_token_count`` /
28
+ LangChain ``input_token_details.cache_read``) to establish the cache-hit-rate
29
+ baseline;
30
+ - running per-session totals, including ``session_cached_ratio``
31
+ (cached / prompt tokens over the session) and the same ratio for the call's
32
+ own ``source`` (``source_cached_ratio``), so a session whose operator calls
33
+ never hit the prompt cache is visible on its ``llm_usage`` traces and in the
34
+ session summary line (:func:`log_session_summary`).
35
+
36
+ This module never raises into the LLM call path and makes no decisions.
37
+ """
38
+
39
+ import threading
40
+ from typing import Any
41
+
42
+ from artemis.data_engine.context_vars import CURRENT_NODE_NAME
43
+ from artemis.llm.google import usage_from_message
44
+ from third_party.mobile_use.utils.logger import get_logger
45
+
46
+ logger = get_logger(__name__)
47
+
48
+ _LOCK = threading.Lock()
49
+ _METERS: dict[str, "SessionTokenMeter"] = {}
50
+
51
+ # Size of the model context window the live Operator/FlashRunner prompt is
52
+ # measured against when the UI reports "context used". The prompt size of the
53
+ # last executor call (``context_base_tokens`` on its ``llm_usage`` trace) is
54
+ # the numerator; this is the denominator.
55
+ OPERATOR_CONTEXT_WINDOW_TOKENS = 1_000_000
56
+
57
+ # Traced node names whose LLM calls carry the live executor context. Both the
58
+ # Pro Operator and the Flash runner are "the operator" from the user's view.
59
+ OPERATOR_NODE_NAMES = frozenset({"operator", "flashrunner"})
60
+
61
+
62
+ def extract_usage(response: Any) -> dict[str, int] | None:
63
+ """Extracts unified token usage from an LLM response message, best-effort.
64
+
65
+ Thin wrapper over :func:`artemis.llm.google.usage.usage_from_message`
66
+ that keeps the meter's historical four-key shape. Returns None when the
67
+ response carries no usable usage numbers.
68
+ """
69
+ usage = usage_from_message(response)
70
+ if usage is None:
71
+ return None
72
+ return {
73
+ "prompt_tokens": usage["prompt_tokens"],
74
+ "completion_tokens": usage["completion_tokens"],
75
+ "total_tokens": usage["total_tokens"],
76
+ "cached_tokens": usage["cached_tokens"],
77
+ }
78
+
79
+
80
+ def cached_ratio(cached_tokens: int, prompt_tokens: int) -> float:
81
+ """``cached / prompt`` rounded to three decimals; 0.0 with no prompt tokens."""
82
+ if prompt_tokens <= 0:
83
+ return 0.0
84
+ return round(cached_tokens / prompt_tokens, 3)
85
+
86
+
87
+ class SessionTokenMeter:
88
+ """Accumulates measured LLM usage for one session, in total and per source."""
89
+
90
+ def __init__(self, session_id: str):
91
+ self.session_id = session_id
92
+ self._lock = threading.Lock()
93
+ self.llm_calls = 0
94
+ self.prompt_tokens = 0
95
+ self.completion_tokens = 0
96
+ self.cached_tokens = 0
97
+ self.cache_hit_calls = 0
98
+ self.last_prompt_tokens = 0
99
+ # Per-source totals (source = gateway endpoint key or lens label):
100
+ # {"llm_calls", "prompt_tokens", "cached_tokens", "cache_hit_calls"}.
101
+ self.sources: dict[str, dict[str, int]] = {}
102
+
103
+ def record(
104
+ self,
105
+ usage: dict[str, int],
106
+ *,
107
+ update_last_prompt: bool = True,
108
+ source: str | None = None,
109
+ ) -> dict[str, Any]:
110
+ """Accumulates one call's usage; returns a session snapshot.
111
+
112
+ ``update_last_prompt=False`` accumulates the totals without touching
113
+ ``last_prompt_tokens`` — background lens calls carry tiny prompts that
114
+ must not masquerade as the live context base consumed by the L2/L3
115
+ compaction thresholds. With a ``source`` the call also joins that
116
+ source's totals and the snapshot carries ``source_cached_ratio``.
117
+ """
118
+ with self._lock:
119
+ prompt = usage.get("prompt_tokens", 0)
120
+ cached = usage.get("cached_tokens", 0)
121
+ self.llm_calls += 1
122
+ self.prompt_tokens += prompt
123
+ self.completion_tokens += usage.get("completion_tokens", 0)
124
+ self.cached_tokens += cached
125
+ if cached > 0:
126
+ self.cache_hit_calls += 1
127
+ if update_last_prompt:
128
+ self.last_prompt_tokens = prompt
129
+ snapshot = self._snapshot_locked()
130
+ if source:
131
+ totals = self.sources.setdefault(
132
+ source,
133
+ {"llm_calls": 0, "prompt_tokens": 0, "cached_tokens": 0, "cache_hit_calls": 0},
134
+ )
135
+ totals["llm_calls"] += 1
136
+ totals["prompt_tokens"] += prompt
137
+ totals["cached_tokens"] += cached
138
+ if cached > 0:
139
+ totals["cache_hit_calls"] += 1
140
+ snapshot["source_cached_ratio"] = cached_ratio(
141
+ totals["cached_tokens"], totals["prompt_tokens"]
142
+ )
143
+ return snapshot
144
+
145
+ def _snapshot_locked(self) -> dict[str, Any]:
146
+ return {
147
+ "session_llm_calls": self.llm_calls,
148
+ "session_prompt_tokens": self.prompt_tokens,
149
+ "session_completion_tokens": self.completion_tokens,
150
+ "session_cached_tokens": self.cached_tokens,
151
+ "session_cache_hit_calls": self.cache_hit_calls,
152
+ "session_cached_ratio": cached_ratio(self.cached_tokens, self.prompt_tokens),
153
+ }
154
+
155
+ def snapshot(self) -> dict[str, Any]:
156
+ with self._lock:
157
+ return self._snapshot_locked()
158
+
159
+ def summary(self) -> dict[str, Any]:
160
+ """Session totals plus every source's totals with its ``cached_ratio``."""
161
+ with self._lock:
162
+ summary: dict[str, Any] = {"session_id": self.session_id, **self._snapshot_locked()}
163
+ summary["sources"] = {
164
+ source: {
165
+ **totals,
166
+ "cached_ratio": cached_ratio(totals["cached_tokens"], totals["prompt_tokens"]),
167
+ }
168
+ for source, totals in self.sources.items()
169
+ }
170
+ return summary
171
+
172
+
173
+ def format_session_summary(session_id: Any) -> str:
174
+ """One log line: session cache ratio, then each source's calls and ratio."""
175
+ summary = get_meter(session_id).summary()
176
+ parts = [
177
+ f"LLM usage for session {summary['session_id']}:"
178
+ f" {summary['session_llm_calls']} calls,"
179
+ f" {summary['session_prompt_tokens']} prompt tokens,"
180
+ f" {summary['session_cached_tokens']} cached"
181
+ f" (cached_ratio={summary['session_cached_ratio']:.3f},"
182
+ f" cache-hit calls {summary['session_cache_hit_calls']})"
183
+ ]
184
+ for source, totals in summary["sources"].items():
185
+ parts.append(
186
+ f"{source}: {totals['llm_calls']} calls, cached_ratio={totals['cached_ratio']:.3f}"
187
+ )
188
+ return "; ".join(parts)
189
+
190
+
191
+ def log_session_summary(session_id: Any) -> dict[str, Any] | None:
192
+ """Log the session's usage summary (INFO) at session end, best-effort.
193
+
194
+ Returns the summary dict, or None when the session never metered a call
195
+ (nothing is logged then). Never raises.
196
+ """
197
+ try:
198
+ key = str(session_id)
199
+ with _LOCK:
200
+ meter = _METERS.get(key)
201
+ if meter is None or meter.llm_calls == 0:
202
+ return None
203
+ logger.info(format_session_summary(key))
204
+ return meter.summary()
205
+ except Exception as exc:
206
+ logger.debug(f"Token meter session summary skipped: {exc}")
207
+ return None
208
+
209
+
210
+ def get_meter(session_id: Any) -> SessionTokenMeter:
211
+ """Returns (creating on first use) the meter for a session id."""
212
+ key = str(session_id)
213
+ with _LOCK:
214
+ meter = _METERS.get(key)
215
+ if meter is None:
216
+ meter = SessionTokenMeter(key)
217
+ _METERS[key] = meter
218
+ return meter
219
+
220
+
221
+ def record_llm_usage(
222
+ engine: Any,
223
+ response: Any,
224
+ *,
225
+ source: str | None = None,
226
+ update_last_prompt: bool = True,
227
+ ) -> dict | None:
228
+ """Meters one LLM response and records an ``llm_usage`` trace, best-effort.
229
+
230
+ ``context_base_tokens`` in the payload is this call's measured prompt size —
231
+ the running estimate of the live context for threshold decisions in later
232
+ milestones. Returns the recorded payload, or None when nothing was
233
+ recorded (no engine/session/usage). Never raises.
234
+ """
235
+ try:
236
+ if engine is None:
237
+ return None
238
+ session_id = getattr(engine, "current_session_id", None)
239
+ usage = extract_usage(response)
240
+ if not session_id or usage is None:
241
+ return None
242
+
243
+ snapshot = get_meter(session_id).record(
244
+ usage, update_last_prompt=update_last_prompt, source=source
245
+ )
246
+ payload: dict[str, Any] = {
247
+ **usage,
248
+ "context_base_tokens": usage["prompt_tokens"],
249
+ **snapshot,
250
+ }
251
+ if source:
252
+ payload["source"] = source
253
+ node = CURRENT_NODE_NAME.get()
254
+ if node:
255
+ payload["node"] = str(node)
256
+
257
+ engine.record_trace(
258
+ type="llm_call",
259
+ name="llm_usage",
260
+ payload=payload,
261
+ step_id=getattr(engine, "current_step_id", None),
262
+ status="success",
263
+ )
264
+ return payload
265
+ except Exception as meter_err:
266
+ logger.debug(f"Token metering skipped: {meter_err}")
267
+ return None
@@ -0,0 +1,35 @@
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
+ """Telemetry and distributed tracing layer for ARTEMIS."""
16
+
17
+ from artemis.telemetry.models import (
18
+ SpanType,
19
+ TelemetrySpan,
20
+ TokenUsage,
21
+ )
22
+ from artemis.telemetry.tracer import (
23
+ CURRENT_PARENT_SPAN_ID,
24
+ CURRENT_TRACE_ID,
25
+ TelemetryTracer,
26
+ )
27
+
28
+ __all__ = [
29
+ "TelemetryTracer",
30
+ "TelemetrySpan",
31
+ "SpanType",
32
+ "TokenUsage",
33
+ "CURRENT_TRACE_ID",
34
+ "CURRENT_PARENT_SPAN_ID",
35
+ ]
@@ -0,0 +1,64 @@
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
+ """Telemetry Data Models and Schema Definitions."""
16
+
17
+ from enum import Enum
18
+ import time
19
+ from typing import Any
20
+ from uuid import uuid4
21
+ from pydantic import BaseModel, Field
22
+
23
+
24
+ class SpanType(str, Enum):
25
+ AGENT = "agent"
26
+ LLM_CALL = "llm_call"
27
+ TOOL_EXECUTION = "tool_execution"
28
+ DEVICE_ACTION = "device_action"
29
+ SCREEN_CAPTURE = "screen_capture"
30
+ CUSTOM = "custom"
31
+
32
+
33
+ class TokenUsage(BaseModel):
34
+ """LLM token consumption metrics."""
35
+
36
+ prompt_tokens: int = Field(default=0, description="Input prompt tokens")
37
+ completion_tokens: int = Field(default=0, description="Output generated tokens")
38
+ total_tokens: int = Field(default=0, description="Total tokens consumed")
39
+
40
+
41
+ class TelemetrySpan(BaseModel):
42
+ """Structured telemetry span representing a single unit of agentic execution."""
43
+
44
+ span_id: str = Field(default_factory=lambda: str(uuid4()))
45
+ trace_id: str = Field(..., description="Root trace/session identifier")
46
+ parent_id: str | None = Field(default=None, description="Parent span identifier")
47
+ name: str = Field(..., description="Human-readable span operation name")
48
+ type: SpanType = Field(default=SpanType.CUSTOM, description="Span categorization")
49
+ start_time: float = Field(default_factory=time.time, description="Start timestamp")
50
+ end_time: float | None = Field(default=None, description="Completion timestamp")
51
+ duration_ms: float | None = Field(default=None, description="Elapsed execution time in ms")
52
+ status: str = Field(default="running", description="Status ('running', 'success', 'failed')")
53
+ payload: dict[str, Any] = Field(
54
+ default_factory=dict, description="Custom parameters and payloads"
55
+ )
56
+ error: str | None = Field(default=None, description="Exception message if failed")
57
+ token_usage: TokenUsage | None = Field(default=None, description="Token consumption metrics")
58
+
59
+ def finish(self, status: str = "success", error: str | None = None) -> None:
60
+ """Closes the span and calculates duration."""
61
+ self.end_time = time.time()
62
+ self.duration_ms = (self.end_time - self.start_time) * 1000.0
63
+ self.status = status
64
+ self.error = error
@@ -0,0 +1,34 @@
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
+ """Trace Replay and Inspection utilities."""
16
+
17
+ from pathlib import Path
18
+ from typing import Any
19
+ from artemis.telemetry.storage.json_storage import JsonStorage
20
+
21
+
22
+ class TraceReplayer:
23
+ """Loads and formats historic task traces for replay and visual inspection."""
24
+
25
+ def __init__(self, traces_dir: Path | str = "traces"):
26
+ self.storage = JsonStorage(storage_dir=traces_dir)
27
+
28
+ def get_trace_summary(self, trace_id: str) -> dict[str, Any]:
29
+ spans = self.storage.read_spans(trace_id)
30
+ return {
31
+ "trace_id": trace_id,
32
+ "total_spans": len(spans),
33
+ "spans": spans,
34
+ }
@@ -0,0 +1,47 @@
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
+ """JSON Lines Telemetry Storage Backend."""
16
+
17
+ import json
18
+ from pathlib import Path
19
+ from typing import Any
20
+ from artemis.telemetry.models import TelemetrySpan
21
+
22
+
23
+ class JsonStorage:
24
+ """Persists spans into newline-delimited JSON files."""
25
+
26
+ def __init__(self, storage_dir: Path | str = "traces"):
27
+ self.storage_dir = Path(storage_dir)
28
+
29
+ def write_span(self, trace_id: str, span: TelemetrySpan) -> None:
30
+ target_dir = self.storage_dir / trace_id
31
+ target_dir.mkdir(parents=True, exist_ok=True)
32
+ log_file = target_dir / "spans.jsonl"
33
+
34
+ with open(log_file, "a", encoding="utf-8") as f:
35
+ f.write(json.dumps(span.model_dump(), ensure_ascii=False) + "\n")
36
+
37
+ def read_spans(self, trace_id: str) -> list[dict[str, Any]]:
38
+ log_file = self.storage_dir / trace_id / "spans.jsonl"
39
+ if not log_file.exists():
40
+ return []
41
+
42
+ spans = []
43
+ with open(log_file, encoding="utf-8") as f:
44
+ for line in f:
45
+ if line.strip():
46
+ spans.append(json.loads(line))
47
+ return spans
@@ -0,0 +1,85 @@
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
+ """Telemetry Tracer and Span Life Cycle Manager."""
16
+
17
+ from contextlib import asynccontextmanager
18
+ from contextvars import ContextVar
19
+ import json
20
+ from pathlib import Path
21
+ from typing import Any
22
+ from collections.abc import AsyncIterator
23
+ from uuid import uuid4
24
+
25
+ from artemis.telemetry.models import SpanType, TelemetrySpan
26
+ from third_party.mobile_use.utils.logger import get_logger
27
+
28
+ logger = get_logger(__name__)
29
+
30
+ CURRENT_TRACE_ID: ContextVar[str] = ContextVar("CURRENT_TRACE_ID", default="global-session")
31
+ CURRENT_PARENT_SPAN_ID: ContextVar[str | None] = ContextVar("CURRENT_PARENT_SPAN_ID", default=None)
32
+
33
+
34
+ class TelemetryTracer:
35
+ """Central tracer for recording and persisting telemetry spans."""
36
+
37
+ def __init__(self, trace_id: str | None = None, traces_dir: Path | None = None):
38
+ self.trace_id = trace_id or str(uuid4())
39
+ self.traces_dir = traces_dir or Path("traces") / self.trace_id
40
+ self.spans: list[TelemetrySpan] = []
41
+ self._log_file: Path | None = None
42
+
43
+ def _ensure_log_file(self) -> Path:
44
+ if self._log_file is None:
45
+ self.traces_dir.mkdir(parents=True, exist_ok=True)
46
+ self._log_file = self.traces_dir / "telemetry_spans.jsonl"
47
+ return self._log_file
48
+
49
+ def record_span(self, span: TelemetrySpan) -> None:
50
+ """Appends and persists completed span."""
51
+ self.spans.append(span)
52
+ try:
53
+ log_file = self._ensure_log_file()
54
+ with open(log_file, "a", encoding="utf-8") as f:
55
+ f.write(json.dumps(span.model_dump(), ensure_ascii=False) + "\n")
56
+ except Exception as e:
57
+ logger.warning(f"Failed to persist telemetry span {span.span_id}: {e}")
58
+
59
+ @asynccontextmanager
60
+ async def start_span(
61
+ self,
62
+ name: str,
63
+ span_type: SpanType = SpanType.CUSTOM,
64
+ payload: dict[str, Any] | None = None,
65
+ ) -> AsyncIterator[TelemetrySpan]:
66
+ """Asynchronous context manager creating and recording a span."""
67
+ parent_id = CURRENT_PARENT_SPAN_ID.get()
68
+ span = TelemetrySpan(
69
+ trace_id=self.trace_id,
70
+ parent_id=parent_id,
71
+ name=name,
72
+ type=span_type,
73
+ payload=payload or {},
74
+ )
75
+
76
+ token = CURRENT_PARENT_SPAN_ID.set(span.span_id)
77
+ try:
78
+ yield span
79
+ span.finish(status="success")
80
+ except Exception as e:
81
+ span.finish(status="failed", error=str(e))
82
+ raise
83
+ finally:
84
+ CURRENT_PARENT_SPAN_ID.reset(token)
85
+ self.record_span(span)
@@ -0,0 +1,52 @@
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
+ """Managed Toolchain and Binary Discovery Subsystem."""
16
+
17
+ import os
18
+ from pathlib import Path
19
+
20
+ from artemis.toolchain.descriptors import TOOLS, ToolDescriptor
21
+ from artemis.toolchain.resolver import ToolchainResolver, toolchain
22
+
23
+
24
+ def ensure_toolchain_in_path() -> None:
25
+ """Prepend resolved toolchain paths (e.g. adb, scrcpy, ffmpeg) to process PATH."""
26
+ for tool_name in ("adb", "ffmpeg", "scrcpy"):
27
+ tool_path = toolchain.resolve(tool_name)
28
+ if tool_path:
29
+ parent_dir = str(Path(tool_path).parent)
30
+ current_path = os.environ.get("PATH", "")
31
+ if parent_dir not in current_path.split(os.pathsep):
32
+ os.environ["PATH"] = f"{parent_dir}{os.pathsep}{current_path}"
33
+
34
+
35
+ # Auto-configure process PATH upon import
36
+ ensure_toolchain_in_path()
37
+
38
+ find_adb = toolchain.find_adb
39
+ find_ffmpeg = toolchain.find_ffmpeg
40
+ find_scrcpy = toolchain.find_scrcpy
41
+ is_installed = toolchain.is_installed
42
+
43
+ __all__ = [
44
+ "toolchain",
45
+ "ToolchainResolver",
46
+ "ToolDescriptor",
47
+ "TOOLS",
48
+ "find_adb",
49
+ "find_ffmpeg",
50
+ "find_scrcpy",
51
+ "is_installed",
52
+ ]
@@ -0,0 +1,92 @@
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
+ """Toolchain descriptors and metadata for cross-platform binary discovery."""
16
+
17
+ from collections.abc import Callable
18
+ from dataclasses import dataclass
19
+ import logging
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def _get_embedded_adb() -> str | None:
25
+ """Fallback to adb binary bundled with adbutils if available."""
26
+ try:
27
+ import adbutils
28
+
29
+ if hasattr(adbutils, "adb_path"):
30
+ return adbutils.adb_path()
31
+ except Exception as exc: # pylint: disable=broad-exception-caught
32
+ # Optional dependency probe: not installed or no bundled binary.
33
+ logger.debug("Embedded adb probe via adbutils skipped: %s", exc, exc_info=True)
34
+ return None
35
+
36
+
37
+ def _get_embedded_ffmpeg() -> str | None:
38
+ """Fallback to ffmpeg binary bundled with imageio_ffmpeg if available."""
39
+ try:
40
+ import imageio_ffmpeg
41
+
42
+ if hasattr(imageio_ffmpeg, "get_ffmpeg_exe"):
43
+ return imageio_ffmpeg.get_ffmpeg_exe()
44
+ except Exception as exc: # pylint: disable=broad-exception-caught
45
+ # Optional dependency probe: not installed, or imageio_ffmpeg raises
46
+ # RuntimeError when it ships no binary for this platform.
47
+ logger.debug("Embedded ffmpeg probe via imageio_ffmpeg skipped: %s", exc, exc_info=True)
48
+ return None
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class ToolDescriptor:
53
+ """Metadata and resolution strategies for an external binary/toolchain."""
54
+
55
+ name: str
56
+ binary_name: str
57
+ win_binary_name: str
58
+ sdk_relative_path: str | None = None
59
+ embedded_fallback: Callable[[], str | None] | None = None
60
+ description: str = ""
61
+
62
+
63
+ # Standard supported toolchains
64
+ TOOLS: dict[str, ToolDescriptor] = {
65
+ "adb": ToolDescriptor(
66
+ name="adb",
67
+ binary_name="adb",
68
+ win_binary_name="adb.exe",
69
+ sdk_relative_path="platform-tools/adb",
70
+ embedded_fallback=_get_embedded_adb,
71
+ description="Android Debug Bridge client",
72
+ ),
73
+ "ffmpeg": ToolDescriptor(
74
+ name="ffmpeg",
75
+ binary_name="ffmpeg",
76
+ win_binary_name="ffmpeg.exe",
77
+ embedded_fallback=_get_embedded_ffmpeg,
78
+ description="FFmpeg multimedia processing framework",
79
+ ),
80
+ "scrcpy": ToolDescriptor(
81
+ name="scrcpy",
82
+ binary_name="scrcpy",
83
+ win_binary_name="scrcpy.exe",
84
+ description="Screen Copy display and control tool",
85
+ ),
86
+ "uv": ToolDescriptor(
87
+ name="uv",
88
+ binary_name="uv",
89
+ win_binary_name="uv.exe",
90
+ description="Fast Python package manager",
91
+ ),
92
+ }