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,1436 @@
1
+ <!--
2
+ Copyright 2026 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ -->
16
+
17
+ <div class="agent-stream-container">
18
+ <!-- VS Code Style Navigation Header (for responsive small screens) -->
19
+ <header class="vscode-nav-header">
20
+ <div class="vscode-tab-container">
21
+ <button
22
+ #taskQueueOrigin="cdkOverlayOrigin"
23
+ cdkOverlayOrigin
24
+ class="vscode-tab-btn"
25
+ [class.active]="isTaskDropdownOpen()"
26
+ (click)="toggleTaskDropdown($event)"
27
+ [title]="'Task Queue' | t"
28
+ [attr.aria-label]="'Task Queue' | t"
29
+ >
30
+ <span class="material-symbols-outlined tab-icon">list_alt</span>
31
+ <span class="tab-label">{{ 'Task Queue' | t }}</span>
32
+ </button>
33
+ <button
34
+ #notesOrigin="cdkOverlayOrigin"
35
+ cdkOverlayOrigin
36
+ class="vscode-tab-btn"
37
+ [class.active]="isNotesDropdownOpen()"
38
+ (click)="toggleNotesDropdown($event)"
39
+ [title]="'Notes & Plans' | t"
40
+ [attr.aria-label]="'Notes & Plans' | t"
41
+ >
42
+ <span class="material-symbols-outlined tab-icon">description</span>
43
+ <span class="tab-label">{{ 'Notes & Plans' | t }}</span>
44
+ </button>
45
+ </div>
46
+
47
+ <!-- Dropdown Popover Panel for Task Queue -->
48
+ <ng-template
49
+ cdkConnectedOverlay
50
+ [cdkConnectedOverlayOrigin]="taskQueueOrigin"
51
+ [cdkConnectedOverlayOpen]="isTaskDropdownOpen()"
52
+ [cdkConnectedOverlayHasBackdrop]="true"
53
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
54
+ (backdropClick)="isTaskDropdownOpen.set(false)"
55
+ [cdkConnectedOverlayPositions]="dropdownPositions"
56
+ >
57
+ <div class="vscode-dropdown-menu task-dropdown">
58
+ <div class="popover-title-header">
59
+ <span class="material-symbols-outlined">list_alt</span>
60
+ <span>{{ 'Task Queue' | t }}</span>
61
+ </div>
62
+ <div class="dropdown-section-label">
63
+ <span class="material-symbols-outlined icon-bolt">bolt</span>
64
+ <span>{{ 'Active Queue (' | t }}{{ activeQueue().length }})</span>
65
+ </div>
66
+ @if (activeQueue().length === 0) {
67
+ <div class="dropdown-empty-state">
68
+ {{ 'No active or queued tasks' | t }}
69
+ </div>
70
+ }
71
+ @for (session of activeQueue(); track trackSession($index, session)) {
72
+ <div
73
+ class="dropdown-item-card"
74
+ [class.selected]="agentService.currentSessionId() === session.session_id"
75
+ [class.running]="getTaskStatus(session) === 'running'"
76
+ (click)="selectTask(session.session_id, $event)"
77
+ >
78
+ <div class="item-header">
79
+ <span class="task-badge" [ngClass]="getTaskStatus(session)">
80
+ @if (getTaskStatus(session) === 'running') {
81
+ <span class="status-dot"></span>
82
+ }
83
+ {{ getTaskStatus(session) | uppercase | t }}
84
+ </span>
85
+ <div class="item-header-actions">
86
+ @if (getDeviceSerial(session)) {
87
+ <span class="item-device" [title]="('Device: ' | t) + getDeviceSerial(session)">
88
+ <span class="material-symbols-outlined device-icon">phone_android</span>
89
+ <span class="device-name">{{ getDeviceSerial(session) }}</span>
90
+ </span>
91
+ }
92
+ @if (getDeviceSerial(session)) {
93
+ <span class="item-time-divider">·</span>
94
+ }
95
+ <span class="item-time">{{ session.start_time * 1000 | date:'shortTime' }}</span>
96
+ @if (getTaskStatus(session) === 'running' || getTaskStatus(session) === 'paused') {
97
+ <button
98
+ type="button"
99
+ class="btn-stop-card"
100
+ (click)="stopTask(session.session_id, $event)"
101
+ [title]="'Stop this task' | t"
102
+ [attr.aria-label]="'Stop this task' | t"
103
+ >
104
+ <span class="material-symbols-outlined">stop_circle</span>
105
+ </button>
106
+ }
107
+ @if (getTaskStatus(session) !== 'running' && getTaskStatus(session) !== 'paused') {
108
+ <button
109
+ type="button"
110
+ class="btn-delete-card"
111
+ (click)="deleteTask(session.session_id, $event)"
112
+ [title]="'Delete this task' | t"
113
+ [attr.aria-label]="'Delete this task' | t"
114
+ >
115
+ <span class="material-symbols-outlined">delete</span>
116
+ </button>
117
+ }
118
+ </div>
119
+ </div>
120
+ <div class="item-goal" title="{{ session.initial_goal }}">
121
+ {{ session.initial_goal || ('Unnamed Task' | t) }}
122
+ </div>
123
+ <div class="item-footer">
124
+ <span class="item-id" [title]="session.session_id">{{ 'ID:' | t }} {{ session.session_id }}</span>
125
+ <span class="item-date">{{ session.start_time * 1000 | date:'MM/dd/yy' }}</span>
126
+ </div>
127
+ </div>
128
+ }
129
+ <div class="dropdown-section-label history-label">
130
+ <span class="material-symbols-outlined icon-history">history</span>
131
+ <span>{{ 'Past Runs & History (' | t }}{{ historyTasks().length }})</span>
132
+ </div>
133
+ @if (historyTasks().length === 0) {
134
+ <div class="dropdown-empty-state">
135
+ {{ 'No past task history' | t }}
136
+ </div>
137
+ }
138
+ <div class="dropdown-history-list">
139
+ @for (session of historyTasks(); track trackSession($index, session)) {
140
+ <div
141
+ class="dropdown-item-card"
142
+ [class.selected]="agentService.currentSessionId() === session.session_id"
143
+ [class.running]="getTaskStatus(session) === 'running'"
144
+ (click)="selectTask(session.session_id, $event)"
145
+ >
146
+ <div class="item-header">
147
+ <span class="task-badge" [ngClass]="getTaskStatus(session)">
148
+ @if (getTaskStatus(session) === 'running') {
149
+ <span class="status-dot"></span>
150
+ }
151
+ {{ getTaskStatus(session) | uppercase | t }}
152
+ </span>
153
+ <div class="item-header-actions">
154
+ @if (getDeviceSerial(session)) {
155
+ <span class="item-device" [title]="('Device: ' | t) + getDeviceSerial(session)">
156
+ <span class="material-symbols-outlined device-icon">phone_android</span>
157
+ <span class="device-name">{{ getDeviceSerial(session) }}</span>
158
+ </span>
159
+ }
160
+ @if (getDeviceSerial(session)) {
161
+ <span class="item-time-divider">·</span>
162
+ }
163
+ <span class="item-time">{{ session.start_time * 1000 | date:'shortTime' }}</span>
164
+ @if (getTaskStatus(session) !== 'running' && getTaskStatus(session) !== 'paused') {
165
+ <button
166
+ type="button"
167
+ class="btn-delete-card"
168
+ (click)="deleteTask(session.session_id, $event)"
169
+ [title]="'Delete this task' | t"
170
+ [attr.aria-label]="'Delete this task' | t"
171
+ >
172
+ <span class="material-symbols-outlined">delete</span>
173
+ </button>
174
+ }
175
+ </div>
176
+ </div>
177
+ <div class="item-goal" title="{{ session.initial_goal }}">
178
+ {{ session.initial_goal || ('Unnamed Task' | t) }}
179
+ </div>
180
+ <div class="item-footer">
181
+ <span class="item-id" [title]="session.session_id">{{ 'ID:' | t }} {{ session.session_id }}</span>
182
+ <span class="item-date">{{ session.start_time * 1000 | date:'MM/dd/yy' }}</span>
183
+ </div>
184
+ </div>
185
+ }
186
+ </div>
187
+ </div>
188
+ </ng-template>
189
+
190
+ <!-- Dropdown Popover Panel for Notes & Plans -->
191
+ <ng-template
192
+ cdkConnectedOverlay
193
+ [cdkConnectedOverlayOrigin]="notesOrigin"
194
+ [cdkConnectedOverlayOpen]="isNotesDropdownOpen()"
195
+ [cdkConnectedOverlayHasBackdrop]="true"
196
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
197
+ (backdropClick)="isNotesDropdownOpen.set(false)"
198
+ [cdkConnectedOverlayPositions]="dropdownPositions"
199
+ >
200
+ <div class="vscode-dropdown-menu notes-dropdown">
201
+ <div class="popover-title-header">
202
+ <span class="material-symbols-outlined">description</span>
203
+ <span>{{ 'Notes & Plans' | t }}</span>
204
+ </div>
205
+ <!-- Notes Selector / Pills (Shown if there are note files) -->
206
+ @if (noteKeys().length > 1) {
207
+ <div class="notes-selector-container">
208
+ <div class="notes-selector-list">
209
+ @for (key of noteKeys(); track trackNoteKey($index, key)) {
210
+ <button
211
+ class="note-selector-pill"
212
+ [class.active]="agentService.selectedNoteKey() === key"
213
+ (click)="selectNote(key, $event)"
214
+ >
215
+ <span class="material-symbols-outlined pill-icon">draft</span>
216
+ {{ key }}
217
+ </button>
218
+ }
219
+ </div>
220
+ </div>
221
+ }
222
+ @if (noteKeys().length === 0) {
223
+ <div class="dropdown-empty-state">
224
+ <span class="material-symbols-outlined empty-icon">description</span>
225
+ <span>{{ 'No notes available for this session.' | t }}</span>
226
+ </div>
227
+ }
228
+ <!-- Rich Notes Content Viewer -->
229
+ @if (noteKeys().length > 0) {
230
+ <div class="dropdown-notes-content">
231
+ @if (parsedNote().title) {
232
+ <h3 class="note-title">{{ parsedNote().title }}</h3>
233
+ }
234
+ <!-- Milestones list -->
235
+ @if (parsedNote().milestones.length > 0) {
236
+ <div class="milestones-container">
237
+ @for (milestone of parsedNote().milestones; track trackMilestone($index, milestone)) {
238
+ <div
239
+ class="milestone-block"
240
+ [ngClass]="milestone.type"
241
+ >
242
+ <div class="milestone-title-row">
243
+ @switch (milestone.type) {
244
+ @case ('checked') {
245
+ <span class="material-symbols-outlined milestone-status-icon checked">check_box</span>
246
+ }
247
+ @case ('progress') {
248
+ <span class="material-symbols-outlined milestone-status-icon progress">indeterminate_check_box</span>
249
+ }
250
+ @case ('unchecked') {
251
+ <span class="material-symbols-outlined milestone-status-icon unchecked">check_box_outline_blank</span>
252
+ }
253
+ }
254
+ <div class="milestone-text">
255
+ @for (seg of milestone.segments; track seg) {
256
+ <span
257
+ [class.bold-segment]="seg.bold"
258
+ [class.code-segment]="seg.code"
259
+ >{{ seg.text }}</span>
260
+ }
261
+ </div>
262
+ </div>
263
+ <!-- Sub-steps list -->
264
+ @if (milestone.subSteps.length > 0) {
265
+ <div class="milestone-substeps">
266
+ @for (step of milestone.subSteps; track trackMarkdownLine($index, step)) {
267
+ <div
268
+ class="substep-row"
269
+ [ngClass]="step.type"
270
+ [style.padding-left.px]="(step.indent > 2 ? (step.indent - 2) * 12 : 0)"
271
+ >
272
+ @switch (step.type) {
273
+ @case ('checked') {
274
+ <span class="material-symbols-outlined check-icon checked">check_box</span>
275
+ }
276
+ @case ('progress') {
277
+ <span class="material-symbols-outlined check-icon progress">indeterminate_check_box</span>
278
+ }
279
+ @case ('unchecked') {
280
+ <span class="material-symbols-outlined check-icon unchecked">check_box_outline_blank</span>
281
+ }
282
+ @case ('list-item') {
283
+ <span class="bullet">•</span>
284
+ }
285
+ }
286
+ @if (step.type !== 'empty') {
287
+ <span class="line-text">
288
+ @for (seg of step.segments; track seg) {
289
+ <span
290
+ [class.bold-segment]="seg.bold"
291
+ [class.code-segment]="seg.code"
292
+ >{{ seg.text }}</span>
293
+ }
294
+ </span>
295
+ }
296
+ </div>
297
+ }
298
+ </div>
299
+ }
300
+ <!-- Verification result -->
301
+ @if (milestone.checks.length > 0) {
302
+ <div class="milestone-checks-container">
303
+ @for (check of milestone.checks; track trackMarkdownLine($index, check)) {
304
+ <div
305
+ class="verification-card"
306
+ [ngClass]="check.type"
307
+ [style.margin-left.px]="(check.indent > 2 ? (check.indent - 2) * 12 : 0)"
308
+ >
309
+ <div class="card-header">
310
+ <span class="card-tag" [class.assert]="check.checkKind === 'assert'">{{ (check.checkKind || 'verify') | t }}{{ check.atEnd ? (' @end' | t) : '' }}</span>
311
+ </div>
312
+ <div class="card-body">
313
+ @for (seg of check.segments; track seg) {
314
+ <span
315
+ [class.bold-segment]="seg.bold"
316
+ [class.code-segment]="seg.code"
317
+ >{{ seg.text }}</span>
318
+ }
319
+ </div>
320
+ </div>
321
+ }
322
+ </div>
323
+ }
324
+ </div>
325
+ }
326
+ </div>
327
+ }
328
+ <!-- Other Markdown Lines Container -->
329
+ @if (parsedNote().otherLines.length > 0) {
330
+ <div class="other-lines-container">
331
+ @for (line of parsedNote().otherLines; track trackMarkdownLine($index, line)) {
332
+ @if (line.type === 'verify' || line.type === 'finding') {
333
+ <div class="verification-card standalone" [ngClass]="line.type" [style.margin-left.px]="line.indent * 12">
334
+ <div class="card-header">
335
+ <span class="card-tag" [class.assert]="line.checkKind === 'assert'">{{ (line.checkKind || 'verify') | t }}{{ line.atEnd ? (' @end' | t) : '' }}</span>
336
+ </div>
337
+ <div class="card-body">
338
+ @for (seg of line.segments; track seg) {
339
+ <span
340
+ [class.bold-segment]="seg.bold"
341
+ [class.code-segment]="seg.code"
342
+ >{{ seg.text }}</span>
343
+ }
344
+ </div>
345
+ </div>
346
+ } @else {
347
+ <div
348
+ class="md-line"
349
+ [ngClass]="line.type"
350
+ [style.margin-left.px]="line.indent * 12"
351
+ >
352
+ @switch (line.type) {
353
+ @case ('checked') {
354
+ <span class="material-symbols-outlined check-icon checked">check_box</span>
355
+ }
356
+ @case ('progress') {
357
+ <span class="material-symbols-outlined check-icon progress">indeterminate_check_box</span>
358
+ }
359
+ @case ('unchecked') {
360
+ <span class="material-symbols-outlined check-icon unchecked">check_box_outline_blank</span>
361
+ }
362
+ @case ('list-item') {
363
+ <span class="bullet">•</span>
364
+ }
365
+ }
366
+ @if (line.type !== 'empty') {
367
+ <span class="line-text">
368
+ @for (seg of line.segments; track seg) {
369
+ <span
370
+ [class.bold-segment]="seg.bold"
371
+ [class.code-segment]="seg.code"
372
+ >{{ seg.text }}</span>
373
+ }
374
+ </span>
375
+ }
376
+ </div>
377
+ }
378
+ }
379
+ </div>
380
+ }
381
+ </div>
382
+ }
383
+ </div>
384
+ </ng-template>
385
+ </header>
386
+
387
+ <!-- Floating Top-Right Badges & Toolbar (Pinned overlay, hidden in empty/initial state) -->
388
+ @if (consolidatedBlocks().length > 0 || showPlanningLoader()) {
389
+ <div class="stream-floating-toolbar">
390
+ <!-- Active Architecture Chip: click opens the run info popover -->
391
+ @if (agentService.viewedModel(); as model) {
392
+ <button
393
+ type="button"
394
+ #runInfoOrigin="cdkOverlayOrigin"
395
+ cdkOverlayOrigin
396
+ class="model-info-chip"
397
+ [ngClass]="getModelClass(model.name)"
398
+ [class.active]="isRunInfoOpen()"
399
+ [title]="getArchitectureTooltip(model)"
400
+ aria-haspopup="dialog"
401
+ [attr.aria-expanded]="isRunInfoOpen()"
402
+ (click)="toggleRunInfo($event)"
403
+ >
404
+ <span class="material-symbols-outlined model-icon">{{ getModelIcon(model.name) }}</span>
405
+ <span class="model-label">{{ 'Architecture:' | t }}</span>
406
+ <span class="model-name">{{ getModelDisplayName(model.name) }}</span>
407
+ </button>
408
+
409
+ <ng-template
410
+ cdkConnectedOverlay
411
+ [cdkConnectedOverlayOrigin]="runInfoOrigin"
412
+ [cdkConnectedOverlayOpen]="isRunInfoOpen()"
413
+ [cdkConnectedOverlayHasBackdrop]="true"
414
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
415
+ (backdropClick)="isRunInfoOpen.set(false)"
416
+ [cdkConnectedOverlayPositions]="dropdownPositions"
417
+ >
418
+ <div class="vscode-dropdown-menu run-info-dropdown" role="dialog" [attr.aria-label]="'Run info' | t">
419
+ <div class="run-info-header">
420
+ <span class="material-symbols-outlined" [class.is-pro]="runInfoIsPro()">{{ getModelIcon(model.name) }}</span>
421
+ <span class="run-info-title">{{ getModelDisplayName(model.name) }}</span>
422
+ @if (model.id) {
423
+ <span class="run-info-model" [title]="model.id">{{ model.id }}</span>
424
+ }
425
+ </div>
426
+
427
+ <div class="run-info-row">
428
+ <span class="run-info-label">{{ 'Elapsed' | t }}</span>
429
+ <span class="run-info-value">{{ runInfoElapsed() }}</span>
430
+ </div>
431
+
432
+ <div class="run-info-row">
433
+ <span class="run-info-label">{{ 'Tokens' | t }}</span>
434
+ @if (runUsage(); as usage) {
435
+ <span class="run-info-value">
436
+ {{ formatRunTokens(usage.total_tokens) }}
437
+ <span class="run-info-sub">{{ formatRunTokens(usage.prompt_tokens) }} {{ 'in ·' | t }} {{ formatRunTokens(usage.completion_tokens) }} {{ 'out' | t }}</span>
438
+ </span>
439
+ } @else {
440
+ <span class="run-info-value is-muted">—</span>
441
+ }
442
+ </div>
443
+
444
+ <div class="run-info-row is-stacked">
445
+ <div class="run-info-line">
446
+ <span class="run-info-label">{{ 'Context' | t }}</span>
447
+ @if (runInfoContextPercent() !== null) {
448
+ <span class="run-info-value">
449
+ {{ runInfoContextPercent() }}%
450
+ <span class="run-info-sub-inline">{{ 'of' | t }} {{ formatRunTokens(runUsage()?.operator_context_window_tokens) }}</span>
451
+ </span>
452
+ } @else {
453
+ <span class="run-info-value is-muted">—</span>
454
+ }
455
+ </div>
456
+ <div
457
+ class="run-info-meter"
458
+ [class.is-high]="(runInfoContextPercent() ?? 0) >= 70"
459
+ [class.is-critical]="(runInfoContextPercent() ?? 0) >= 90"
460
+ >
461
+ <div class="run-info-meter-fill" [style.width.%]="runInfoContextPercent() ?? 0"></div>
462
+ </div>
463
+ </div>
464
+
465
+ @if (runInfoIsPro()) {
466
+ <div class="run-info-row">
467
+ <span class="run-info-label">{{ 'Result check' | t }}</span>
468
+ <span class="run-info-value">{{ runTuningLabel('verify', runInfoTuning()?.verification_level) | t }}</span>
469
+ </div>
470
+ <div class="run-info-row">
471
+ <span class="run-info-label">{{ 'Screen reading' | t }}</span>
472
+ <span class="run-info-value">{{ runTuningLabel('explore', runInfoTuning()?.explorer_mode) | t }}</span>
473
+ </div>
474
+ } @else {
475
+ <div class="run-info-note">{{ 'No extra settings for Flash.' | t }}</div>
476
+ }
477
+ </div>
478
+ </ng-template>
479
+ }
480
+ <!-- Screen Recording Action Button (Interactive Button) -->
481
+ <button
482
+ class="btn-screen-recording"
483
+ [class.active]="agentService.isVideoWindowOpen()"
484
+ [class.recording]="agentService.isCurrentSessionRunning()"
485
+ [class.processing]="!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() === 'processing'"
486
+ [class.has-video]="!agentService.isCurrentSessionRunning() && !!agentService.currentSessionVideoUrl()"
487
+ [class.has-steps]="!agentService.isCurrentSessionRunning() && !agentService.currentSessionVideoUrl() && agentService.hasCurrentSessionStepFrames()"
488
+ [class.failed]="!agentService.isCurrentSessionRunning() && !agentService.currentSessionVideoUrl() && !agentService.hasCurrentSessionStepFrames() && agentService.currentSessionRecordingStatus() === 'failed'"
489
+ (click)="agentService.toggleVideoPlayer()"
490
+ [title]="getScreenRecordingButtonTitle() | t"
491
+ >
492
+ @if (agentService.isCurrentSessionRunning()) {
493
+ <span class="material-symbols-outlined video-icon">
494
+ videocam
495
+ </span>
496
+ }
497
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() === 'processing') {
498
+ <span class="material-symbols-outlined video-icon spin-animation">
499
+ progress_activity
500
+ </span>
501
+ }
502
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && agentService.currentSessionVideoUrl()) {
503
+ <span class="material-symbols-outlined video-icon play-icon">
504
+ smart_display
505
+ </span>
506
+ }
507
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && agentService.hasCurrentSessionStepFrames()) {
508
+ <span class="material-symbols-outlined video-icon steps-icon">
509
+ slideshow
510
+ </span>
511
+ }
512
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && !agentService.hasCurrentSessionStepFrames() && agentService.currentSessionRecordingStatus() === 'failed') {
513
+ <span class="material-symbols-outlined video-icon error-icon">
514
+ videocam_off
515
+ </span>
516
+ }
517
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && !agentService.hasCurrentSessionStepFrames() && agentService.currentSessionRecordingStatus() !== 'failed') {
518
+ <span class="material-symbols-outlined video-icon">
519
+ videocam
520
+ </span>
521
+ }
522
+ @if (agentService.isCurrentSessionRunning()) {
523
+ <span class="btn-text">{{ 'Recording...' | t }}</span>
524
+ }
525
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() === 'processing') {
526
+ <span class="btn-text">{{ 'Preparing...' | t }}</span>
527
+ }
528
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && agentService.currentSessionVideoUrl()) {
529
+ <span class="btn-text">{{ 'Play Recording' | t }}</span>
530
+ }
531
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && agentService.hasCurrentSessionStepFrames()) {
532
+ <span class="btn-text">{{ 'Step Replay' | t }}</span>
533
+ }
534
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && !agentService.hasCurrentSessionStepFrames() && agentService.currentSessionRecordingStatus() === 'failed') {
535
+ <span class="btn-text">{{ 'Recording Failed' | t }}</span>
536
+ }
537
+ @if (!agentService.isCurrentSessionRunning() && agentService.currentSessionRecordingStatus() !== 'processing' && !agentService.currentSessionVideoUrl() && !agentService.hasCurrentSessionStepFrames() && agentService.currentSessionRecordingStatus() !== 'failed') {
538
+ <span class="btn-text">{{ 'Screen Recording' | t }}</span>
539
+ }
540
+ @if (agentService.isCurrentSessionRunning()) {
541
+ <span class="video-pulse-dot red"></span>
542
+ }
543
+ @if (!agentService.isCurrentSessionRunning() && !agentService.currentSessionVideoUrl() && agentService.hasCurrentSessionStepFrames()) {
544
+ <span class="steps-count-badge">
545
+ {{ agentService.currentSessionStepFrames().length }}
546
+ </span>
547
+ }
548
+ </button>
549
+ </div>
550
+ }
551
+
552
+ <!-- Event Logs Section -->
553
+ <main class="stream-logs-content">
554
+ @if (agentService.isSessionContentLoading()) {
555
+ <div class="session-loading-state" aria-live="polite">
556
+ <span class="material-symbols-outlined spin-animation">progress_activity</span>
557
+ <span>{{ 'Loading run history...' | t }}</span>
558
+ </div>
559
+ }
560
+
561
+ @if (!agentService.isSessionContentLoading() && consolidatedBlocks().length === 0 && !showPlanningLoader() && startupWorkItems().length === 0) {
562
+ <div class="empty-state">
563
+ <div class="empty-icon"><span class="material-symbols-outlined">sensors</span></div>
564
+ <h3>{{ 'No Stream Activity Selected' | t }}</h3>
565
+ <p>{{ 'Please select a session from the Task List on the right to view its real-time log payloads.' | t }}</p>
566
+ </div>
567
+ }
568
+
569
+ <!-- The phase-based session logs list -->
570
+ @if (!agentService.isSessionContentLoading() && (phases().length > 0 || showPlanningLoader() || startupWorkItems().length > 0)) {
571
+ <div class="logs-list-container">
572
+ @if (startupWorkItems().length > 0) {
573
+ <div class="phase-container startup-phase" aria-live="polite">
574
+ <div class="phase-header first-phase">
575
+ <span class="phase-worked-time">{{ 'Worked for' | t }} {{ startupPreparationDuration() }}{{ 's' | t }}</span>
576
+ </div>
577
+ <div class="phase-body" [attr.aria-label]="'Task startup progress' | t">
578
+ <div class="block-item type-stream">
579
+ <div class="step-body">
580
+ <div class="step-actions-container">
581
+ <div class="llm-retry-notes">
582
+ @for (item of startupWorkItems(); track item) {
583
+ <div class="llm-retry-note">
584
+ <span class="stream-status-dot" [class.streaming]="item.isActive"></span>
585
+ <div class="llm-retry-note-text">
586
+ <span class="llm-retry-status-line">
587
+ {{ item.message }} · {{ item.isActive ? ('Waiting ' | t) : ('Waited ' | t) }}{{ item.elapsed }}
588
+ </span>
589
+ </div>
590
+ </div>
591
+ }
592
+ </div>
593
+ </div>
594
+ </div>
595
+ </div>
596
+ </div>
597
+ </div>
598
+ }
599
+ @for (phase of phases(); track trackPhase($index, phase); let first = $first) {
600
+ <div class="phase-container">
601
+ @if (hasVisibleBlocks(phase)) {
602
+ <div
603
+ class="phase-header"
604
+ [class.first-phase]="first && startupWorkItems().length === 0"
605
+ >
606
+ <span class="phase-worked-time">{{ isCheckerPhase(phase) ? ('Checked' | t) : ('Worked' | t) }} {{ 'for' | t }} {{ phase.durationSeconds }}{{ 's' | t }}</span>
607
+ @if (phase.tokens) {
608
+ <span class="phase-token-usage" [title]="getTokenTooltip(phase)">
609
+ <span class="token-dot">·</span>
610
+ <span class="token-text">{{ formatTokenCount(phase.tokens) }}</span>
611
+ </span>
612
+ }
613
+ </div>
614
+ }
615
+ @if (hasVisibleBlocks(phase)) {
616
+ <div class="phase-body">
617
+ @for (block of phase.blocks; track trackBlock($index, block)) {
618
+ @if (hasVisibleContent(block)) {
619
+ <div
620
+ class="block-item"
621
+ [ngClass]="['type-' + block.type]"
622
+ >
623
+ <!-- Unified Step / Stream Body -->
624
+ <div class="step-body">
625
+ <!-- Check block (verifier lane): header, then the declared check items
626
+ and how each is judged; the chronological stream follows, and the
627
+ result rows close the block (see check-result below). -->
628
+ @if (isCheckerBlock(block)) {
629
+ <div class="check-lane" [ngClass]="'tone-' + getCheckerTone(block)">
630
+ <div class="check-header">
631
+ <span class="stream-status-dot" [class.streaming]="isBlockRunning(block)"></span>
632
+ <span class="check-title">{{ getCheckerPhaseLabel(block) | t }}</span>
633
+ @if (block.data.phase !== 'outcome' && block.data.subgoal_text) {
634
+ <span class="check-subject" [title]="block.data.subgoal_text">{{ block.data.subgoal_text }}</span>
635
+ }
636
+ <span class="check-status">{{ getCheckerStatusLabel(block) | t }}</span>
637
+ </div>
638
+ @if (!isCheckerMuted(block) && getCheckerItems(block).length > 0) {
639
+ <ol class="check-items">
640
+ @for (item of getCheckerItems(block); track $index) {
641
+ <li class="check-item">
642
+ <span class="check-index">{{ $index + 1 }}</span>
643
+ <span class="check-text">{{ item.text }}</span>
644
+ <span class="check-method">{{ getCheckMethodLabel(item, block) | t }}</span>
645
+ </li>
646
+ }
647
+ </ol>
648
+ }
649
+ </div>
650
+ }
651
+ <!-- Text, tool calls, and actions share one chronological timeline. -->
652
+ @if (getSortedStepEvents(block.data).length > 0) {
653
+ <div class="step-actions-container">
654
+ @for (item of getSortedStepEvents(block.data); track trackStepEvent(itemIdx, item); let itemIdx = $index) {
655
+ <!-- Check block: one Thought / Work entry per streamed turn, in time order with its tool calls -->
656
+ @if (isCheckerBlock(block) && item.type === 'thinking') {
657
+ <div
658
+ class="thinking-stream-details native-thinking-details"
659
+ [class.is-running]="isCheckerSegmentRunning(block, itemIdx) && !isCheckerSegmentResetNotice(item)"
660
+ [class.is-resetting]="isCheckerSegmentResetNotice(item)"
661
+ [class.collapsed]="isCheckerSegmentCollapsed(block, itemIdx)">
662
+ <div class="details-summary" (click)="toggleCheckerSegment(block, itemIdx)">
663
+ <span class="stream-status-dot" [class.streaming]="isCheckerSegmentRunning(block, itemIdx) && !isCheckerSegmentResetNotice(item)" [class.resetting]="isCheckerSegmentResetNotice(item)"></span>
664
+ <span class="header-label">{{ isCheckerSegmentResetNotice(item) ? getCheckerSegmentResetMessage(item) : ('Thought' | t) }}</span>
665
+ </div>
666
+ @if (item.data.text && item.data.text.trim()) {
667
+ <div class="details-content-wrapper">
668
+ <div class="details-content stream-box">
669
+ <div class="stream-text" [innerHTML]="renderMarkdown(item.data.text, block.id + '-seg-' + itemIdx)"></div>
670
+ </div>
671
+ </div>
672
+ }
673
+ </div>
674
+ }
675
+ @if (isCheckerBlock(block) && item.type === 'text' && isCheckerTextVisible(item)) {
676
+ @if (item.data.isReset) {
677
+ <div class="llm-retry-notes">
678
+ <div class="llm-retry-note">
679
+ <span class="stream-status-dot" [class.streaming]="isCheckerSegmentRunning(block, itemIdx)"></span>
680
+ <div class="llm-retry-note-text">
681
+ <span class="llm-retry-status-line">{{ formatResetMessage(getCheckerSegmentResetMessage(item), isCheckerSegmentRunning(block, itemIdx)) }}</span>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ }
686
+ @if (item.data.text && item.data.text.trim()) {
687
+ <div
688
+ class="step-raw-thinking-message"
689
+ [class.is-running]="isCheckerSegmentRunning(block, itemIdx)">
690
+ <div class="report-header">
691
+ <span class="stream-status-dot" [class.streaming]="isCheckerSegmentRunning(block, itemIdx)"></span>
692
+ <span class="header-label">{{ 'Work' | t }}</span>
693
+ </div>
694
+ <div class="report-content stream-box">
695
+ <div class="stream-text" [innerHTML]="renderMarkdown(item.data.text, block.id + '-seg-' + itemIdx)"></div>
696
+ </div>
697
+ </div>
698
+ }
699
+ }
700
+ <!-- Native Thinking (Thought) -->
701
+ @if (!isCheckerBlock(block) && item.type === 'thinking') {
702
+ @if (getBlockResets(block, true).length > 0) {
703
+ <div class="llm-retry-notes">
704
+ @for (reset of getBlockResets(block, true); track reset.id || $index) {
705
+ <div class="llm-retry-note">
706
+ <span class="stream-status-dot" [class.streaming]="reset.isWaiting"></span>
707
+ <div class="llm-retry-note-text">
708
+ <span class="llm-retry-status-line">{{ formatResetMessage(reset.message, reset.isWaiting) }}</span>
709
+ </div>
710
+ </div>
711
+ }
712
+ </div>
713
+ }
714
+ @if (getStreamDisplayText(block.id + '-native', item.data.text) || (isBlockRunning(block) && !isStreamRewinding(block.id + '-native') && !hasWaitingReset(block, true))) {
715
+ <div
716
+ class="thinking-stream-details native-thinking-details"
717
+ [class.is-running]="isBlockRunning(block)"
718
+ [class.collapsed]="collapsedStreams().has(block.data.execution_id || block.data.step_id || block.id)">
719
+ <div class="details-summary" (click)="toggleCollapse(block.data.execution_id || block.data.step_id || block.id)">
720
+ <span class="stream-status-dot" [class.streaming]="isBlockRunning(block)"></span>
721
+ <span class="header-label">{{ 'Thought' | t }}</span>
722
+ </div>
723
+ @if (getStreamDisplayText(block.id + '-native', item.data.text)) {
724
+ <div class="details-content-wrapper">
725
+ <div class="details-content stream-box">
726
+ <div class="stream-text" [innerHTML]="renderMarkdown(getStreamDisplayText(block.id + '-native', item.data.text), block.id + '-native')"></div>
727
+ </div>
728
+ </div>
729
+ }
730
+ </div>
731
+ }
732
+ }
733
+ <!-- Raw Thinking / Plan / Message (Work) -->
734
+ @if (!isCheckerBlock(block) && item.type === 'text') {
735
+ @if (getBlockResets(block, false).length > 0) {
736
+ <div class="llm-retry-notes">
737
+ @for (reset of getBlockResets(block, false); track reset.id || $index) {
738
+ <div class="llm-retry-note">
739
+ <span class="stream-status-dot" [class.streaming]="reset.isWaiting"></span>
740
+ <div class="llm-retry-note-text">
741
+ <span class="llm-retry-status-line">{{ formatResetMessage(reset.message, reset.isWaiting) }}</span>
742
+ </div>
743
+ </div>
744
+ }
745
+ </div>
746
+ }
747
+ @if (getStreamDisplayText(block.id, item.data.text) || (isBlockRunning(block) && !isStreamRewinding(block.id) && !hasWaitingReset(block, false))) {
748
+ <div
749
+ class="step-raw-thinking-message"
750
+ [class.is-running]="isBlockRunning(block)">
751
+ <div class="report-header">
752
+ <span class="stream-status-dot" [class.streaming]="isBlockRunning(block)"></span>
753
+ <span class="header-label">{{ 'Work' | t }}</span>
754
+ </div>
755
+ @if (getStreamDisplayText(block.id, item.data.text)) {
756
+ <div class="report-content stream-box">
757
+ <div class="stream-text" [innerHTML]="renderMarkdown(getStreamDisplayText(block.id, item.data.text), block.id)"></div>
758
+ </div>
759
+ }
760
+ </div>
761
+ }
762
+ }
763
+ <!-- Task Submission / Report Card (when item is report_task_status action or tool) -->
764
+ @if (isReportStatusAction(item.data)) {
765
+ <div class="step-android-action-card task-report-card"
766
+ [class.is-completed]="getReportStatusValue(item.data) === 'completed' || getReportStatusValue(item.data) === 'success'"
767
+ [class.is-failed]="getReportStatusValue(item.data) === 'failed'"
768
+ [class.is-expanded]="isActionExpanded('action-' + block.id + '-' + itemIdx)"
769
+ >
770
+ <div class="action-card-header" (click)="toggleActionExpanded('action-' + block.id + '-' + itemIdx, $event)">
771
+ <span class="material-symbols-outlined action-icon report-icon" [class.is-failed]="getReportStatusValue(item.data) === 'failed'">
772
+ {{ getReportStatusValue(item.data) === 'failed' ? 'cancel' : 'task_alt' }}
773
+ </span>
774
+ <span class="action-title">
775
+ {{ getReportStatusValue(item.data) === 'failed' ? ('Task Execution Failed' | t) : ('Task Completed' | t) }}
776
+ </span>
777
+ <span class="action-status-badge" [class.is-failed]="getReportStatusValue(item.data) === 'failed'" [class.is-completed]="getReportStatusValue(item.data) !== 'failed'">
778
+ <span class="material-symbols-outlined status-icon">
779
+ {{ getReportStatusValue(item.data) === 'failed' ? 'error' : 'check_circle' }}
780
+ </span>
781
+ {{ getReportStatusValue(item.data) === 'failed' ? ('Failed' | t) : ('Completed' | t) }}
782
+ </span>
783
+ <span class="action-expand-icon material-symbols-outlined">
784
+ {{ isActionExpanded('action-' + block.id + '-' + itemIdx) ? 'expand_less' : 'expand_more' }}
785
+ </span>
786
+ </div>
787
+ <!-- Compact preview body (when collapsed, matches standard action card) -->
788
+ @if (!isActionExpanded('action-' + block.id + '-' + itemIdx) && getReportStatusExplanation(item.data)) {
789
+ <div class="action-card-body">
790
+ <div class="action-detail-row">
791
+ <span class="detail-label">{{ 'Summary:' | t }}</span>
792
+ <span class="detail-value">"{{ getReportStatusExplanation(item.data) }}"</span>
793
+ </div>
794
+ </div>
795
+ }
796
+ <!-- Detailed expanded view (when clicked, matches standard action card structure) -->
797
+ @if (isActionExpanded('action-' + block.id + '-' + itemIdx)) {
798
+ <div class="action-card-expanded-details" (click)="$event.stopPropagation()">
799
+ <div class="expanded-grid">
800
+ @if (getReportStatusExplanation(item.data); as expText) {
801
+ <div class="grid-item full-width">
802
+ <span class="grid-label">{{ 'Report & Findings' | t }}</span>
803
+ <span class="grid-value report-markdown-body" [innerHTML]="renderMarkdown(expText, 'report-' + block.id + '-' + itemIdx)"></span>
804
+ </div>
805
+ }
806
+ </div>
807
+ <!-- Final Verified Screenshot -->
808
+ @if (getStepPreImageUrl(block.data, item.data) || getStepPostImageUrl(block.data, item.data)) {
809
+ <div class="expanded-screenshots-container">
810
+ @if (getStepPreImageUrl(block.data, item.data)) {
811
+ <div class="screenshot-column">
812
+ <span class="screenshot-label">{{ 'Final Verified State' | t }}</span>
813
+ <div class="image-wrapper" (click)="openImageModal(getStepPreImageUrl(block.data, item.data)!, item.data, $event)">
814
+ <div class="img-relative-box">
815
+ <img [src]="getStepPreImageUrl(block.data, item.data)" [alt]="'Final Screen Screenshot' | t" />
816
+ </div>
817
+ </div>
818
+ </div>
819
+ }
820
+ @if (getStepPostImageUrl(block.data, item.data)) {
821
+ <div class="screenshot-column">
822
+ <span class="screenshot-label">{{ 'Post Verification' | t }}</span>
823
+ <div class="image-wrapper" (click)="openImageModal(getStepPostImageUrl(block.data, item.data)!, null, $event)">
824
+ <div class="img-relative-box">
825
+ <img [src]="getStepPostImageUrl(block.data, item.data)" [alt]="'Post Screenshot' | t" />
826
+ </div>
827
+ </div>
828
+ </div>
829
+ }
830
+ </div>
831
+ }
832
+ </div>
833
+ }
834
+ </div>
835
+ }
836
+ <!-- Android Action Card (when item is an action) -->
837
+ @if (item.type === 'action' && !isReportStatusAction(item.data) && (isAndroidAction(item.data) || item.data?.type === 'action')) {
838
+ <div class="step-android-action-card"
839
+ [class.is-running]="isItemRunning(item, block)"
840
+ [class.is-failed]="isActionFailed(item.data, block.data)"
841
+ [class.is-expanded]="isActionExpanded('action-' + block.id + '-' + itemIdx)"
842
+ >
843
+ <div class="action-card-header" (click)="toggleActionExpanded('action-' + block.id + '-' + itemIdx, $event)">
844
+ <span class="material-symbols-outlined action-icon" [class.is-failed]="isActionFailed(item.data, block.data)">{{ getActionIcon(item.data) }}</span>
845
+ <span class="action-title">{{ getActionTitle(item.data) }}</span>
846
+ @if (isActionFailed(item.data, block.data)) {
847
+ <span class="action-status-badge is-failed">
848
+ <span class="material-symbols-outlined status-icon">error</span>
849
+ {{ 'Failed' | t }}
850
+ </span>
851
+ }
852
+ <span class="action-expand-icon material-symbols-outlined">
853
+ {{ isActionExpanded('action-' + block.id + '-' + itemIdx) ? 'expand_less' : 'expand_more' }}
854
+ </span>
855
+ </div>
856
+ <!-- Compact preview body (when collapsed) -->
857
+ @if (!isActionExpanded('action-' + block.id + '-' + itemIdx) && (getActionTargetText(item.data) || getActionInputText(item.data) || getActionCoords(item.data))) {
858
+ <div class="action-card-body">
859
+ @if (getActionTargetText(item.data)) {
860
+ <div class="action-detail-row">
861
+ <span class="detail-label">{{ 'Target:' | t }}</span>
862
+ <span class="detail-value">"{{ getActionTargetText(item.data) }}"</span>
863
+ </div>
864
+ }
865
+ @if (getActionInputText(item.data)) {
866
+ <div class="action-detail-row">
867
+ <span class="detail-label">{{ getActionInputLabel(item.data) | t }}:</span>
868
+ <span class="detail-value">"{{ getActionInputText(item.data) }}"</span>
869
+ </div>
870
+ }
871
+ @if (getActionCoords(item.data)) {
872
+ <div class="action-detail-row">
873
+ <span class="detail-label">{{ 'Coordinates:' | t }}</span>
874
+ <span class="detail-value coordinate-badge">{{ getActionCoords(item.data) }}</span>
875
+ </div>
876
+ }
877
+ </div>
878
+ }
879
+ <!-- Detailed expanded view (when clicked) -->
880
+ @if (isActionExpanded('action-' + block.id + '-' + itemIdx)) {
881
+ <div class="action-card-expanded-details" (click)="$event.stopPropagation()">
882
+ <div class="expanded-grid">
883
+ @if (getActionTargetText(item.data)) {
884
+ <div class="grid-item">
885
+ <span class="grid-label">{{ 'Target' | t }}</span>
886
+ <span class="grid-value">"{{ getActionTargetText(item.data) }}"</span>
887
+ </div>
888
+ }
889
+ @if (getActionInputText(item.data)) {
890
+ <div class="grid-item">
891
+ <span class="grid-label">{{ getActionInputLabel(item.data) | t }}</span>
892
+ <span class="grid-value">"{{ getActionInputText(item.data) }}"</span>
893
+ </div>
894
+ }
895
+ @if (getActionCoords(item.data)) {
896
+ <div class="grid-item">
897
+ <span class="grid-label">{{ 'Coordinates' | t }}</span>
898
+ <span class="grid-value code-pill">{{ getActionCoords(item.data) }}</span>
899
+ </div>
900
+ }
901
+ @if (getActionBounds(item.data)) {
902
+ <div class="grid-item">
903
+ <span class="grid-label">{{ 'Target Bounds' | t }}</span>
904
+ <span class="grid-value code-pill">[{{ getActionBounds(item.data) }}]</span>
905
+ </div>
906
+ }
907
+ @if (getActionClass(item.data)) {
908
+ <div class="grid-item">
909
+ <span class="grid-label">{{ 'Target Class' | t }}</span>
910
+ <span class="grid-value code-pill">{{ getActionClass(item.data) }}</span>
911
+ </div>
912
+ }
913
+ @for (param of getActionExtraParams(item.data); track trackParam($index, param)) {
914
+ <div class="grid-item">
915
+ <span class="grid-label">{{ param.key }}</span>
916
+ <span class="grid-value code-pill">{{ param.value }}</span>
917
+ </div>
918
+ }
919
+ <!-- Fallback item if all detail fields are empty -->
920
+ @if (!getActionTargetText(item.data) && !getActionInputText(item.data) && !getActionCoords(item.data) && !getActionBounds(item.data) && !getActionClass(item.data) && getActionExtraParams(item.data).length === 0) {
921
+ <div class="grid-item">
922
+ <span class="grid-label">{{ 'Action' | t }}</span>
923
+ <span class="grid-value">{{ getActionTitle(item.data) }}</span>
924
+ </div>
925
+ }
926
+ </div>
927
+ <!-- Action Screenshots (Pre & Post Action) -->
928
+ @if (getStepPreImageUrl(block.data, item.data) || getStepPostImageUrl(block.data, item.data)) {
929
+ <div class="expanded-screenshots-container">
930
+ @if (getStepPreImageUrl(block.data, item.data)) {
931
+ <div class="screenshot-column">
932
+ <span class="screenshot-label">{{ 'Pre-Action (Before)' | t }}</span>
933
+ <div class="image-wrapper" (click)="openImageModal(getStepPreImageUrl(block.data, item.data)!, item.data, $event)">
934
+ <div class="img-relative-box">
935
+ <img #preImg [src]="getStepPreImageUrl(block.data, item.data)" (load)="onImageLoad(preImg, preOverlay, item.data)" [alt]="'Pre-Action Screenshot' | t" />
936
+ <div #preOverlay class="overlay-layer"></div>
937
+ </div>
938
+ </div>
939
+ </div>
940
+ }
941
+ @if (getStepPostImageUrl(block.data, item.data)) {
942
+ <div class="screenshot-column">
943
+ <span class="screenshot-label">{{ 'Post-Action (After)' | t }}</span>
944
+ <div class="image-wrapper" (click)="openImageModal(getStepPostImageUrl(block.data, item.data)!, null, $event)">
945
+ <div class="img-relative-box">
946
+ <img [src]="getStepPostImageUrl(block.data, item.data)" [alt]="'Post-Action Screenshot' | t" />
947
+ </div>
948
+ </div>
949
+ </div>
950
+ }
951
+ </div>
952
+ }
953
+ @if (isActionFailed(item.data, block.data)) {
954
+ <div class="expanded-error-banner">
955
+ <span class="material-symbols-outlined icon">error</span>
956
+ <span class="error-text">{{ getActionErrorMessage(item.data, block.data) }}</span>
957
+ </div>
958
+ }
959
+ </div>
960
+ }
961
+ </div>
962
+ }
963
+ <!-- Generic Tools & LLM Calls (when item is a tool) -->
964
+ @if (item.type === 'tool') {
965
+ <!-- UI-hierarchy source changed mid-run (helper <-> UIAutomator2). -->
966
+ @if (isBackendSwitchNote(item.data)) {
967
+ <div class="llm-retry-notes">
968
+ <div class="llm-retry-note">
969
+ <span class="stream-status-dot"></span>
970
+ <div class="llm-retry-note-text">
971
+ <span class="llm-retry-status-line">{{ getBackendSwitchMessage(item.data) }}</span>
972
+ </div>
973
+ </div>
974
+ </div>
975
+ }
976
+ <!-- Only SDK-observed Gemini retries are shown as quiet stream notes. -->
977
+ @if (isLLMRetry(item.data)) {
978
+ <div class="llm-retry-notes">
979
+ @for (retry of getLLMRetryEntries(item.data); track retry; let retryIndex = $index) {
980
+ <div class="llm-retry-note">
981
+ <span class="stream-status-dot"
982
+ [class.streaming]="retryIndex === getLLMRetryEntries(item.data).length - 1 && isLLMRetryEntryWaiting(retry)"></span>
983
+ <div class="llm-retry-note-text">
984
+ @if (isLLMRetryEntryWaiting(retry)) {
985
+ <span class="llm-retry-status-line">
986
+ {{ 'LLM temporarily unavailable' | t }}
987
+ @if (getLLMRetryEntryDelay(retry); as retryDelay) {
988
+ {{ '· Retrying in' | t }} {{ retryDelay }}…
989
+ }
990
+ </span>
991
+ <span class="llm-retry-guidance">
992
+ {{ 'Frequent retries may indicate a low API rate limit. Consider increasing the limit or reducing request concurrency.' | t }}
993
+ </span>
994
+ }
995
+ @if (!isLLMRetryEntryWaiting(retry)) {
996
+ <span class="llm-retry-status-line">
997
+ {{ 'Retrying LLM request · Waited' | t }} {{ getLLMRetryEntryWaited(retry) }}
998
+ </span>
999
+ }
1000
+ </div>
1001
+ </div>
1002
+ }
1003
+ </div>
1004
+ }
1005
+ <!-- Failed LLM Calls (Permanently maintain Action Card format in red) -->
1006
+ @if (isDisplayableLLMFailure(item.data)) {
1007
+ <div class="llm-failure-stack">
1008
+ <div class="step-android-action-card tool-action-card is-failed"
1009
+ [class.has-details]="hasLLMFailureRetryEntries(item.data)"
1010
+ [class.is-expanded]="isActionExpanded('llm-failure-' + block.id + '-' + itemIdx)"
1011
+ (click)="hasLLMFailureRetryEntries(item.data) && toggleActionExpanded('llm-failure-' + block.id + '-' + itemIdx, $event)">
1012
+ <div class="action-card-header llm-failure-header">
1013
+ <span class="material-symbols-outlined action-icon is-failed">error</span>
1014
+ <span class="action-title">{{ 'LLM Request Failed' | t }}</span>
1015
+ @if (isTerminalLLMFailure(item.data)) {
1016
+ <span class="action-status-badge is-paused">
1017
+ <span class="material-symbols-outlined status-icon">pause_circle</span>
1018
+ {{ 'Paused' | t }}
1019
+ </span>
1020
+ } @else {
1021
+ <span class="action-status-badge is-failed">
1022
+ <span class="material-symbols-outlined status-icon">warning</span>
1023
+ {{ 'Failed' | t }}
1024
+ </span>
1025
+ }
1026
+ @if (hasLLMFailureRetryEntries(item.data)) {
1027
+ <span class="action-expand-icon material-symbols-outlined">
1028
+ {{ isActionExpanded('llm-failure-' + block.id + '-' + itemIdx) ? 'expand_less' : 'expand_more' }}
1029
+ </span>
1030
+ }
1031
+ </div>
1032
+ <div class="action-card-body">
1033
+ <div class="action-detail-row">
1034
+ <span class="detail-label">{{ 'What happened:' | t }}</span>
1035
+ <span class="detail-value">{{ 'The AI request could not be completed after automatic retries.' | t }}</span>
1036
+ </div>
1037
+ <div class="action-detail-row">
1038
+ <span class="detail-label">{{ 'Failure reason:' | t }}</span>
1039
+ <span class="detail-value">{{ getLLMErrorText(item.data) }}</span>
1040
+ </div>
1041
+ @if (getLLMFailureWaited(item.data); as waited) {
1042
+ <div class="action-detail-row">
1043
+ <span class="detail-label">{{ 'Waited:' | t }}</span>
1044
+ <span class="detail-value">{{ waited }}</span>
1045
+ </div>
1046
+ }
1047
+ @if (isActionExpanded('llm-failure-' + block.id + '-' + itemIdx)) {
1048
+ <div class="failure-retry-details">
1049
+ <div class="failure-retry-title">{{ 'Retry details' | t }}</div>
1050
+ @for (retry of getLLMFailureRetryEntries(item.data); track retry; let retryIndex = $index) {
1051
+ <div class="failure-retry-row">
1052
+ <span>{{ 'Retry' | t }} {{ retryIndex + 1 }}</span>
1053
+ <span>
1054
+ {{ 'Planned' | t }} {{ getLLMRetryEntryDelay(retry) || '0s' }}
1055
+ {{ '· Waited' | t }} {{ getLLMRetryEntryWaited(retry) }}
1056
+ </span>
1057
+ </div>
1058
+ }
1059
+ </div>
1060
+ }
1061
+ </div>
1062
+ </div>
1063
+ @if (isTerminalLLMFailure(item.data)) {
1064
+ <div class="llm-resume-row">
1065
+ <button
1066
+ type="button"
1067
+ class="btn-continue-task"
1068
+ (click)="resumePausedTask($event)"
1069
+ [title]="'Continue the paused task' | t"
1070
+ [attr.aria-label]="'Continue the paused task' | t"
1071
+ >
1072
+ <span class="material-symbols-outlined continue-icon">play_arrow</span>
1073
+ <span>{{ 'Continue task' | t }}</span>
1074
+ <span class="material-symbols-outlined continue-arrow">arrow_forward</span>
1075
+ </button>
1076
+ <span class="llm-resume-hint">{{ 'Task paused · no steps are running' | t }}</span>
1077
+ </div>
1078
+ }
1079
+ </div>
1080
+ }
1081
+ <!-- Generic Tool Calls (Text format for note tools, video analyzer, and other sub-agents) -->
1082
+ @if (shouldShowTool(item.data, block.data) && !isDeviceActionTool(item.data)) {
1083
+ <div class="step-generic-tool"
1084
+ [class.is-running]="isToolLineRunning(item, block)"
1085
+ [class.is-note-tool]="isNoteTool(item.data)"
1086
+ [class.is-video-tool]="isVideoTool(item.data)"
1087
+ [class.is-video-attention]="isVideoTool(item.data) && isVideoAnalysisAttention(item.data)"
1088
+ [attr.title]="getCompressionPhaseTitle(item.data)"
1089
+ >
1090
+ <div class="tool-call-label">
1091
+ <span class="stream-status-dot" [class.streaming]="isToolLineRunning(item, block)"></span>
1092
+ <div class="tool-call-text">
1093
+ <span>{{ isVideoTool(item.data) ? getVideoAnalysisLabel(item.data) : getToolDisplayLabel(item.data) }}{{ ((isNoteTool(item.data) && getToolKey(item.data)) || isVideoTool(item.data)) ? ':' : '' }}</span>
1094
+ <!-- Note Tool Pill -->
1095
+ @if (isNoteTool(item.data) && getToolKey(item.data); as key) {
1096
+ <span
1097
+ class="tool-key-pill"
1098
+ (click)="onToolKeyClick(key)"
1099
+ [title]="'Click to view note content' | t"
1100
+ >
1101
+ <span class="material-symbols-outlined pill-icon">draft</span>
1102
+ {{ key }}
1103
+ </span>
1104
+ }
1105
+ <!-- Video Tool Pill -->
1106
+ @if (isVideoTool(item.data)) {
1107
+ <span
1108
+ class="tool-key-pill"
1109
+ (click)="onVideoToolClick(item.data)"
1110
+ [title]="('Open recording at ' | t) + getVideoAnalysisRangeLabel(item.data)"
1111
+ >
1112
+ <span class="material-symbols-outlined pill-icon">videocam</span>
1113
+ {{ getVideoAnalysisRangeLabel(item.data) }}
1114
+ </span>
1115
+ @if (getVideoAnalysisDetail(item.data); as detail) {
1116
+ <span class="video-status-detail">· {{ detail }}</span>
1117
+ }
1118
+ }
1119
+ </div>
1120
+ </div>
1121
+ </div>
1122
+ }
1123
+ <!-- Action Card format (For Device Actions & ADB Commands) -->
1124
+ @if (shouldShowTool(item.data, block.data) && isDeviceActionTool(item.data)) {
1125
+ <div class="step-android-action-card tool-action-card"
1126
+ [class.is-running]="isItemRunning(item, block)"
1127
+ [class.has-no-details]="isAdbCommandTool(item.data) ? !getAdbCommandLine(item.data) : (!getToolTargetText(item.data) && !getToolInputText(item.data) && !getToolCoords(item.data) && !getToolAnalysisText(item.data) && !getToolGenericDetails(item.data) && getToolExtraParams(item.data).length === 0)"
1128
+ [class.is-failed]="isToolFailed(item.data)"
1129
+ [class.is-expanded]="isActionExpanded('tool-' + block.id + '-' + itemIdx)"
1130
+ >
1131
+ <div class="action-card-header" (click)="toggleActionExpanded('tool-' + block.id + '-' + itemIdx, $event)">
1132
+ <span class="material-symbols-outlined action-icon" [class.is-failed]="isToolFailed(item.data)">{{ getToolIcon(item.data) }}</span>
1133
+ <span class="action-title">{{ getToolTitle(item.data) }}</span>
1134
+ @if (isToolFailed(item.data)) {
1135
+ <span class="action-status-badge is-failed">
1136
+ <span class="material-symbols-outlined status-icon">error</span>
1137
+ {{ 'Failed' | t }}
1138
+ </span>
1139
+ }
1140
+ <span class="action-expand-icon material-symbols-outlined">
1141
+ {{ isActionExpanded('tool-' + block.id + '-' + itemIdx) ? 'expand_less' : 'expand_more' }}
1142
+ </span>
1143
+ </div>
1144
+ <!-- Compact preview body (when collapsed) -->
1145
+ @if (!isActionExpanded('tool-' + block.id + '-' + itemIdx) && (isAdbCommandTool(item.data) ? getAdbCommandLine(item.data) : (getToolTargetText(item.data) || getToolInputText(item.data) || getToolCoords(item.data) || getToolAnalysisText(item.data) || getToolGenericDetails(item.data)))) {
1146
+ <div class="action-card-body">
1147
+ <!-- ADB command details -->
1148
+ @if (isAdbCommandTool(item.data)) {
1149
+ @if (getAdbCommandLine(item.data)) {
1150
+ <div class="action-detail-row">
1151
+ <span class="detail-label">{{ 'Command:' | t }}</span>
1152
+ <span class="detail-value command-value"><code>{{ getAdbCommandLine(item.data) }}</code></span>
1153
+ </div>
1154
+ }
1155
+ @if (getAdbCwd(item.data)) {
1156
+ <div class="action-detail-row">
1157
+ <span class="detail-label">{{ 'Cwd:' | t }}</span>
1158
+ <span class="detail-value">"{{ getAdbCwd(item.data) }}"</span>
1159
+ </div>
1160
+ }
1161
+ @if (getAdbTerminalId(item.data)) {
1162
+ <div class="action-detail-row">
1163
+ <span class="detail-label">{{ 'Terminal ID:' | t }}</span>
1164
+ <span class="detail-value">"{{ getAdbTerminalId(item.data) }}"</span>
1165
+ </div>
1166
+ }
1167
+ }
1168
+ <!-- Standard details -->
1169
+ @if (!isAdbCommandTool(item.data)) {
1170
+ @if (getToolTargetText(item.data)) {
1171
+ <div class="action-detail-row">
1172
+ <span class="detail-label">{{ 'Target:' | t }}</span>
1173
+ <span class="detail-value">"{{ getToolTargetText(item.data) }}"</span>
1174
+ </div>
1175
+ }
1176
+ @if (getToolInputText(item.data)) {
1177
+ <div class="action-detail-row">
1178
+ <span class="detail-label">{{ getToolInputLabel(item.data) | t }}:</span>
1179
+ <span class="detail-value">"{{ getToolInputText(item.data) }}"</span>
1180
+ </div>
1181
+ }
1182
+ @if (getToolCoords(item.data)) {
1183
+ <div class="action-detail-row">
1184
+ <span class="detail-label">{{ 'Coordinates:' | t }}</span>
1185
+ <span class="detail-value coordinate-badge">{{ getToolCoords(item.data) }}</span>
1186
+ </div>
1187
+ }
1188
+ @if (getToolAnalysisText(item.data)) {
1189
+ <div class="action-detail-row">
1190
+ <span class="detail-label">{{ 'Analysis:' | t }}</span>
1191
+ <span class="detail-value">"{{ getToolAnalysisText(item.data) }}"</span>
1192
+ </div>
1193
+ }
1194
+ @if (getToolGenericDetails(item.data)) {
1195
+ <div class="action-detail-row">
1196
+ <span class="detail-label">{{ 'Details:' | t }}</span>
1197
+ <span class="detail-value">"{{ getToolGenericDetails(item.data) }}"</span>
1198
+ </div>
1199
+ }
1200
+ }
1201
+ </div>
1202
+ }
1203
+ <!-- Detailed expanded view (when clicked) -->
1204
+ @if (isActionExpanded('tool-' + block.id + '-' + itemIdx)) {
1205
+ <div class="action-card-expanded-details" (click)="$event.stopPropagation()">
1206
+ <div class="expanded-grid">
1207
+ @if (isAdbCommandTool(item.data)) {
1208
+ @if (getAdbCommandLine(item.data)) {
1209
+ <div class="grid-item full-width">
1210
+ <span class="grid-label">{{ 'Command' | t }}</span>
1211
+ <span class="grid-value command-value"><code>{{ getAdbCommandLine(item.data) }}</code></span>
1212
+ </div>
1213
+ }
1214
+ @if (getAdbCwd(item.data)) {
1215
+ <div class="grid-item">
1216
+ <span class="grid-label">{{ 'Working Directory' | t }}</span>
1217
+ <span class="grid-value">"{{ getAdbCwd(item.data) }}"</span>
1218
+ </div>
1219
+ }
1220
+ @if (getAdbTerminalId(item.data)) {
1221
+ <div class="grid-item">
1222
+ <span class="grid-label">{{ 'Terminal ID' | t }}</span>
1223
+ <span class="grid-value code-pill">{{ getAdbTerminalId(item.data) }}</span>
1224
+ </div>
1225
+ }
1226
+ }
1227
+ @if (!isAdbCommandTool(item.data)) {
1228
+ @if (getToolTargetText(item.data)) {
1229
+ <div class="grid-item">
1230
+ <span class="grid-label">{{ 'Target' | t }}</span>
1231
+ <span class="grid-value">"{{ getToolTargetText(item.data) }}"</span>
1232
+ </div>
1233
+ }
1234
+ @if (getToolInputText(item.data)) {
1235
+ <div class="grid-item">
1236
+ <span class="grid-label">{{ getToolInputLabel(item.data) | t }}</span>
1237
+ <span class="grid-value">"{{ getToolInputText(item.data) }}"</span>
1238
+ </div>
1239
+ }
1240
+ @if (getToolCoords(item.data)) {
1241
+ <div class="grid-item">
1242
+ <span class="grid-label">{{ 'Coordinates' | t }}</span>
1243
+ <span class="grid-value code-pill">{{ getToolCoords(item.data) }}</span>
1244
+ </div>
1245
+ }
1246
+ @if (getToolAnalysisText(item.data)) {
1247
+ <div class="grid-item">
1248
+ <span class="grid-label">{{ 'Analysis' | t }}</span>
1249
+ <span class="grid-value">"{{ getToolAnalysisText(item.data) }}"</span>
1250
+ </div>
1251
+ }
1252
+ @if (getToolGenericDetails(item.data)) {
1253
+ <div class="grid-item">
1254
+ <span class="grid-label">{{ 'Details' | t }}</span>
1255
+ <span class="grid-value">"{{ getToolGenericDetails(item.data) }}"</span>
1256
+ </div>
1257
+ }
1258
+ @for (param of getToolExtraParams(item.data); track trackParam($index, param)) {
1259
+ <div class="grid-item">
1260
+ <span class="grid-label">{{ param.key }}</span>
1261
+ <span class="grid-value code-pill">{{ param.value }}</span>
1262
+ </div>
1263
+ }
1264
+ }
1265
+ </div>
1266
+ <!-- Action Screenshots (Pre & Post Action) -->
1267
+ @if (getStepPreImageUrl(block.data, item.data) || getStepPostImageUrl(block.data, item.data)) {
1268
+ <div class="expanded-screenshots-container">
1269
+ @if (getStepPreImageUrl(block.data, item.data)) {
1270
+ <div class="screenshot-column">
1271
+ <span class="screenshot-label">{{ 'Pre-Action (Before)' | t }}</span>
1272
+ <div class="image-wrapper" (click)="openImageModal(getStepPreImageUrl(block.data, item.data)!, item.data, $event)">
1273
+ <div class="img-relative-box">
1274
+ <img #preImgTool [src]="getStepPreImageUrl(block.data, item.data)" (load)="onImageLoad(preImgTool, preOverlayTool, item.data)" [alt]="'Pre-Action Screenshot' | t" />
1275
+ <div #preOverlayTool class="overlay-layer"></div>
1276
+ </div>
1277
+ </div>
1278
+ </div>
1279
+ }
1280
+ @if (getStepPostImageUrl(block.data, item.data)) {
1281
+ <div class="screenshot-column">
1282
+ <span class="screenshot-label">{{ 'Post-Action (After)' | t }}</span>
1283
+ <div class="image-wrapper" (click)="openImageModal(getStepPostImageUrl(block.data, item.data)!, null, $event)">
1284
+ <div class="img-relative-box">
1285
+ <img [src]="getStepPostImageUrl(block.data, item.data)" [alt]="'Post-Action Screenshot' | t" />
1286
+ </div>
1287
+ </div>
1288
+ </div>
1289
+ }
1290
+ </div>
1291
+ }
1292
+ @if (isToolFailed(item.data)) {
1293
+ <div class="expanded-error-banner">
1294
+ <span class="material-symbols-outlined icon">error</span>
1295
+ <span class="error-text">{{ getToolErrorMessage(item.data) }}</span>
1296
+ </div>
1297
+ }
1298
+ </div>
1299
+ }
1300
+ </div>
1301
+ }
1302
+ }
1303
+ }
1304
+ </div>
1305
+ }
1306
+ <!-- Check block result: one row per check item, keyed by its number above -->
1307
+ @if (hasCheckerResult(block)) {
1308
+ <div class="check-result" [ngClass]="'tone-' + getCheckerTone(block)">
1309
+ @for (verdict of getCheckerVerdicts(block); track $index) {
1310
+ <div class="check-verdict" [ngClass]="verdict.status">
1311
+ <span class="check-index">{{ getVerdictItemNumber(block, verdict, $index) }}</span>
1312
+ <span class="material-symbols-outlined verdict-icon">{{ getVerdictIcon(verdict.status) }}</span>
1313
+ <span class="verdict-body">
1314
+ <span class="verdict-evidence">{{ verdict.evidence || getVerdictStatusText(verdict.status) }}</span>
1315
+ @if (verdict.suggestion) {
1316
+ <span class="verdict-suggestion">{{ verdict.suggestion }}</span>
1317
+ }
1318
+ </span>
1319
+ </div>
1320
+ }
1321
+ @for (note of getCheckerNotes(block); track note) {
1322
+ <div class="check-note">{{ note }}</div>
1323
+ }
1324
+ </div>
1325
+ }
1326
+ </div>
1327
+ </div>
1328
+ }
1329
+ }
1330
+ </div>
1331
+ }
1332
+ </div>
1333
+ }
1334
+ <!-- Outputter Post-Execution Synthesis Card (Rendered ONCE at the end of stream) -->
1335
+ @if (outputterReport(); as outReport) {
1336
+ <div class="outputter-synthesis-stream-card">
1337
+ <div class="synthesis-card-header">
1338
+ <div class="header-left">
1339
+ <span class="material-symbols-outlined icon-sparkle">assignment_turned_in</span>
1340
+ <span class="main-title">{{ 'Task Report' | t }}</span>
1341
+ </div>
1342
+ <div class="header-right">
1343
+ <button type="button" class="btn-open-note" (click)="openOutputterNote($event)">
1344
+ <span class="material-symbols-outlined">visibility</span>
1345
+ <span>{{ 'View in Notes' | t }}</span>
1346
+ </button>
1347
+ </div>
1348
+ </div>
1349
+ <div class="synthesis-card-preview">
1350
+ @if (parsedOutputReport(); as parsedOut) {
1351
+ <div class="parsed-note-container">
1352
+ @if (parsedOut.title) {
1353
+ <h3 class="parsed-note-title">{{ parsedOut.title }}</h3>
1354
+ }
1355
+ @if (parsedOut.milestones.length > 0) {
1356
+ <div class="parsed-milestones">
1357
+ @for (m of parsedOut.milestones; track trackMilestone($index, m)) {
1358
+ <div class="milestone-item" [class]="'status-' + m.type">
1359
+ <span class="material-symbols-outlined status-icon">
1360
+ {{ m.type === 'checked' ? 'check_circle' : (m.type === 'progress' ? 'pending' : 'radio_button_unchecked') }}
1361
+ </span>
1362
+ <span class="milestone-text">
1363
+ @for (seg of m.segments; track seg) {
1364
+ @if (seg.bold) {
1365
+ <strong>{{ seg.text }}</strong>
1366
+ }
1367
+ @if (seg.code) {
1368
+ <code>{{ seg.text }}</code>
1369
+ }
1370
+ @if (!seg.bold && !seg.code) {
1371
+ <span>{{ seg.text }}</span>
1372
+ }
1373
+ }
1374
+ </span>
1375
+ </div>
1376
+ }
1377
+ </div>
1378
+ }
1379
+ @if (parsedOut.otherLines.length > 0) {
1380
+ <div class="parsed-other-lines">
1381
+ @for (line of parsedOut.otherLines; track trackMarkdownLine($index, line)) {
1382
+ <p class="other-line" [class]="'line-' + line.type">
1383
+ @if (line.type === 'verify') {
1384
+ <span class="verify-badge" [class.assert]="line.checkKind === 'assert'">{{ (line.checkKind || 'verify') | t }}{{ line.atEnd ? '@end' : '' }}</span>
1385
+ }
1386
+ @if (line.type === 'finding') {
1387
+ <span class="finding-badge">{{ 'finding' | t }}</span>
1388
+ }
1389
+ @for (seg of line.segments; track seg) {
1390
+ @if (seg.bold) {
1391
+ <strong>{{ seg.text }}</strong>
1392
+ }
1393
+ @if (seg.code) {
1394
+ <code>{{ seg.text }}</code>
1395
+ }
1396
+ @if (!seg.bold && !seg.code) {
1397
+ <span>{{ seg.text }}</span>
1398
+ }
1399
+ }
1400
+ </p>
1401
+ }
1402
+ </div>
1403
+ }
1404
+ </div>
1405
+ }
1406
+ </div>
1407
+ </div>
1408
+ }
1409
+ <!-- Planning Loader -->
1410
+ @if (showPlanningLoader() && (startupWorkItems().length === 0 || startupPreparationIsComplete() || consolidatedBlocks().length > 0)) {
1411
+ <div class="planning-loader-container">
1412
+ <div class="planning-loader">
1413
+ <span class="material-symbols-outlined spin-animation">progress_activity</span>
1414
+ <span class="loader-text">{{ currentPlanningText() | t }}</span>
1415
+ </div>
1416
+ </div>
1417
+ }
1418
+ </div>
1419
+ }
1420
+ </main>
1421
+
1422
+ <!-- Fullscreen Image Zoom Lightbox Modal -->
1423
+ @if (selectedZoomImage()) {
1424
+ <div class="image-modal-backdrop" (click)="closeImageModal($event)">
1425
+ <div class="image-modal-content" (click)="$event.stopPropagation()">
1426
+ <button class="image-modal-close" (click)="closeImageModal($event)" [attr.aria-label]="'Close image preview' | t">
1427
+ <span class="material-symbols-outlined">close</span>
1428
+ </button>
1429
+ <div class="img-relative-box">
1430
+ <img #modalImg [src]="selectedZoomImage()" (load)="onImageLoad(modalImg, modalOverlay, selectedZoomAction())" [alt]="'Zoomed Screenshot' | t" />
1431
+ <div #modalOverlay class="overlay-layer"></div>
1432
+ </div>
1433
+ </div>
1434
+ </div>
1435
+ }
1436
+ </div>