android-midscene-automation 0.2.3 → 1.1.0
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.
- package/CHANGELOG.md +11 -1
- package/README.md +63 -1
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +10 -8
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +13 -1
- package/src/App.vue +72 -23
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +369 -186
- package/src/types.ts +1 -1
package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html
ADDED
|
@@ -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>
|